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. 4631 If this is wrong, pass -version=WindowsXP to dmd when compiling and it will 4632 use the older version. 4633 +/ 4634 version(OSXCocoa) {} else // NotYetImplementedException 4635 class NotificationAreaIcon : CapableOfHandlingNativeEvent { 4636 4637 version(X11) { 4638 void recreateAfterDisconnect() { 4639 stateDiscarded = false; 4640 clippixmap = None; 4641 throw new Exception("NOT IMPLEMENTED"); 4642 } 4643 4644 bool stateDiscarded; 4645 void discardConnectionState() { 4646 stateDiscarded = true; 4647 } 4648 } 4649 4650 4651 version(X11) { 4652 Image img; 4653 4654 NativeEventHandler getNativeEventHandler() { 4655 return delegate int(XEvent e) { 4656 switch(e.type) { 4657 case EventType.Expose: 4658 //case EventType.VisibilityNotify: 4659 redraw(); 4660 break; 4661 case EventType.ClientMessage: 4662 version(sddddd) { 4663 writeln("\t", e.xclient.message_type == GetAtom!("_XEMBED")(XDisplayConnection.get)); 4664 writeln("\t", e.xclient.format); 4665 writeln("\t", e.xclient.data.l); 4666 } 4667 break; 4668 case EventType.ButtonPress: 4669 auto event = e.xbutton; 4670 if (onClick !is null || onClickEx !is null) { 4671 MouseButton mb = cast(MouseButton)0; 4672 switch (event.button) { 4673 case 1: mb = MouseButton.left; break; // left 4674 case 2: mb = MouseButton.middle; break; // middle 4675 case 3: mb = MouseButton.right; break; // right 4676 case 4: mb = MouseButton.wheelUp; break; // scroll up 4677 case 5: mb = MouseButton.wheelDown; break; // scroll down 4678 case 6: break; // scroll left... 4679 case 7: break; // scroll right... 4680 case 8: mb = MouseButton.backButton; break; 4681 case 9: mb = MouseButton.forwardButton; break; 4682 default: 4683 } 4684 if (mb) { 4685 try { onClick()(mb); } catch (Exception) {} 4686 if (onClickEx !is null) try { onClickEx(event.x_root, event.y_root, mb, cast(ModifierState)event.state); } catch (Exception) {} 4687 } 4688 } 4689 break; 4690 case EventType.EnterNotify: 4691 if (onEnter !is null) { 4692 onEnter(e.xcrossing.x_root, e.xcrossing.y_root, cast(ModifierState)e.xcrossing.state); 4693 } 4694 break; 4695 case EventType.LeaveNotify: 4696 if (onLeave !is null) try { onLeave(); } catch (Exception) {} 4697 break; 4698 case EventType.DestroyNotify: 4699 active = false; 4700 CapableOfHandlingNativeEvent.nativeHandleMapping.remove(nativeHandle); 4701 break; 4702 case EventType.ConfigureNotify: 4703 auto event = e.xconfigure; 4704 this.width = event.width; 4705 this.height = event.height; 4706 // writeln(width, " x " , height, " @ ", event.x, " ", event.y); 4707 redraw(); 4708 break; 4709 default: return 1; 4710 } 4711 return 1; 4712 }; 4713 } 4714 4715 /* private */ void hideBalloon() { 4716 balloon.close(); 4717 version(with_timer) 4718 timer.destroy(); 4719 balloon = null; 4720 version(with_timer) 4721 timer = null; 4722 } 4723 4724 void redraw() { 4725 if (!active) return; 4726 4727 auto display = XDisplayConnection.get; 4728 GC gc; 4729 4730 // from https://stackoverflow.com/questions/10492275/how-to-upload-32-bit-image-to-server-side-pixmap 4731 4732 int gc_depth(int depth, Display *dpy, Window root, GC *gc) { 4733 Visual *visual; 4734 XVisualInfo vis_info; 4735 XSetWindowAttributes win_attr; 4736 c_ulong win_mask; 4737 4738 if(!XMatchVisualInfo(dpy, 0, depth, 4 /*TrueColor*/, &vis_info)) { 4739 assert(0); 4740 // return 1; 4741 } 4742 4743 visual = vis_info.visual; 4744 4745 win_attr.colormap = XCreateColormap(dpy, root, visual, AllocNone); 4746 win_attr.background_pixel = 0; 4747 win_attr.border_pixel = 0; 4748 4749 win_mask = CWBackPixel | CWColormap | CWBorderPixel; 4750 4751 *gc = XCreateGC(dpy, nativeHandle, 0, null); 4752 4753 return 0; 4754 } 4755 4756 if(useAlpha) 4757 gc_depth(32, display, RootWindow(display, DefaultScreen(display)), &gc); 4758 else 4759 gc = DefaultGC(display, DefaultScreen(display)); 4760 4761 XClearWindow(display, nativeHandle); 4762 4763 if(!useAlpha && img !is null) 4764 XSetClipMask(display, gc, clippixmap); 4765 4766 /+ 4767 XSetForeground(display, gc, 4768 cast(uint) 0 << 16 | 4769 cast(uint) 0 << 8 | 4770 cast(uint) 0); 4771 XFillRectangle(display, nativeHandle, gc, 0, 0, width, height); 4772 +/ 4773 4774 if (img is null) { 4775 XSetForeground(display, gc, 4776 cast(uint) 0 << 16 | 4777 cast(uint) 127 << 8 | 4778 cast(uint) 0); 4779 XFillArc(display, nativeHandle, 4780 gc, width / 4, height / 4, width * 2 / 4, height * 2 / 4, 0 * 64, 360 * 64); 4781 } else { 4782 int dx = 0; 4783 int dy = 0; 4784 if(width > img.width) 4785 dx = (width - img.width) / 2; 4786 if(height > img.height) 4787 dy = (height - img.height) / 2; 4788 // writeln(img.width, " ", img.height, " vs ", width, " ", height); 4789 XSetClipOrigin(display, gc, dx, dy); 4790 4791 int max(int a, int b) { 4792 if(a > b) return a; else return b; 4793 } 4794 4795 if (img.usingXshm) 4796 XShmPutImage(display, cast(Drawable)nativeHandle, gc, img.handle, 0, 0, dx, dy, max(img.width, width), max(img.height, height), false); 4797 else 4798 XPutImage(display, cast(Drawable)nativeHandle, gc, img.handle, 0, 0, dx, dy, max(img.width, width), max(img.height, height)); 4799 } 4800 XSetClipMask(display, gc, None); 4801 flushGui(); 4802 } 4803 4804 static Window getTrayOwner() { 4805 auto display = XDisplayConnection.get; 4806 auto i = cast(int) DefaultScreen(display); 4807 if(i < 10 && i >= 0) { 4808 static Atom atom; 4809 if(atom == None) 4810 atom = XInternAtom(display, cast(char*) ("_NET_SYSTEM_TRAY_S"~(cast(char) (i + '0')) ~ '\0').ptr, false); 4811 return XGetSelectionOwner(display, atom); 4812 } 4813 return None; 4814 } 4815 4816 static void sendTrayMessage(arch_long message, arch_long d1, arch_long d2, arch_long d3) { 4817 auto to = getTrayOwner(); 4818 auto display = XDisplayConnection.get; 4819 XEvent ev; 4820 ev.xclient.type = EventType.ClientMessage; 4821 ev.xclient.window = to; 4822 ev.xclient.message_type = GetAtom!("_NET_SYSTEM_TRAY_OPCODE", true)(display); 4823 ev.xclient.format = 32; 4824 ev.xclient.data.l[0] = CurrentTime; 4825 ev.xclient.data.l[1] = message; 4826 ev.xclient.data.l[2] = d1; 4827 ev.xclient.data.l[3] = d2; 4828 ev.xclient.data.l[4] = d3; 4829 4830 XSendEvent(XDisplayConnection.get, to, false, EventMask.NoEventMask, &ev); 4831 } 4832 4833 private static NotificationAreaIcon[] activeIcons; 4834 4835 // FIXME: possible leak with this stuff, should be able to clear it and stuff. 4836 private void newManager() { 4837 close(); 4838 createXWin(); 4839 4840 if(this.clippixmap) 4841 XFreePixmap(XDisplayConnection.get, clippixmap); 4842 if(this.originalMemoryImage) 4843 this.icon = this.originalMemoryImage; 4844 else if(this.img) 4845 this.icon = this.img; 4846 } 4847 4848 private bool useAlpha = false; 4849 4850 private void createXWin () { 4851 // create window 4852 auto display = XDisplayConnection.get; 4853 4854 // to check for MANAGER on root window to catch new/changed tray owners 4855 XDisplayConnection.addRootInput(EventMask.StructureNotifyMask); 4856 // so if a thing does appear, we can handle it 4857 foreach(ai; activeIcons) 4858 if(ai is this) 4859 goto alreadythere; 4860 activeIcons ~= this; 4861 alreadythere: 4862 4863 // and check for an existing tray 4864 auto trayOwner = getTrayOwner(); 4865 if(trayOwner == None) 4866 return; 4867 //throw new Exception("No notification area found"); 4868 4869 Visual* v = cast(Visual*) CopyFromParent; 4870 4871 // GNOME's default is 22x22 and KDE assumes all icons are going to match that then bitmap scales 4872 // from there. It is ugly and stupid but this gives the fewest artifacts. Good environments will send 4873 // a resize event later. 4874 width = 22; 4875 height = 22; 4876 4877 // if they system gave us a 32 bit visual we need to switch to it too 4878 int depth = 24; 4879 4880 auto visualProp = getX11PropertyData(trayOwner, GetAtom!("_NET_SYSTEM_TRAY_VISUAL", true)(display)); 4881 if(visualProp !is null) { 4882 c_ulong[] info = cast(c_ulong[]) visualProp; 4883 if(info.length == 1) { 4884 auto vid = info[0]; 4885 int returned; 4886 XVisualInfo t; 4887 t.visualid = vid; 4888 auto got = XGetVisualInfo(display, VisualIDMask, &t, &returned); 4889 if(got !is null) { 4890 if(returned == 1) { 4891 v = got.visual; 4892 depth = got.depth; 4893 // writeln("using special visual ", got.depth); 4894 // writeln(depth); 4895 } 4896 XFree(got); 4897 } 4898 } 4899 } 4900 4901 int CWFlags = CWBackPixel | CWBorderPixel | CWOverrideRedirect; 4902 XSetWindowAttributes attr; 4903 attr.background_pixel = 0; 4904 attr.border_pixel = 0; 4905 attr.override_redirect = 0; 4906 if(v !is cast(Visual*) CopyFromParent) { 4907 attr.colormap = XCreateColormap(display, RootWindow(display, DefaultScreen(display)), v, AllocNone); 4908 CWFlags |= CWColormap; 4909 if(depth == 32) 4910 useAlpha = true; 4911 else 4912 goto plain; 4913 } else { 4914 plain: 4915 attr.background_pixmap = 1 /* ParentRelative */; 4916 CWFlags |= CWBackPixmap; 4917 } 4918 4919 auto nativeWindow = XCreateWindow(display, RootWindow(display, DefaultScreen(display)), 0, 0, width, height, 0, depth, InputOutput, v, CWFlags, &attr); 4920 4921 assert(nativeWindow); 4922 4923 if(!useAlpha) 4924 XSetWindowBackgroundPixmap(display, nativeWindow, 1 /* ParentRelative */); 4925 4926 nativeHandle = nativeWindow; 4927 4928 ///+ 4929 arch_ulong[2] info; 4930 info[0] = 0; 4931 info[1] = 1; 4932 4933 string title = this.name is null ? "simpledisplay.d program" : this.name; 4934 auto XA_UTF8 = XInternAtom(display, "UTF8_STRING".ptr, false); 4935 auto XA_NETWM_NAME = XInternAtom(display, "_NET_WM_NAME".ptr, false); 4936 XChangeProperty(display, nativeWindow, XA_NETWM_NAME, XA_UTF8, 8, PropModeReplace, title.ptr, cast(uint)title.length); 4937 4938 XChangeProperty( 4939 display, 4940 nativeWindow, 4941 GetAtom!("_XEMBED_INFO", true)(display), 4942 GetAtom!("_XEMBED_INFO", true)(display), 4943 32 /* bits */, 4944 0 /*PropModeReplace*/, 4945 info.ptr, 4946 2); 4947 4948 import core.sys.posix.unistd; 4949 arch_ulong pid = getpid(); 4950 4951 XChangeProperty( 4952 display, 4953 nativeWindow, 4954 GetAtom!("_NET_WM_PID", true)(display), 4955 XA_CARDINAL, 4956 32 /* bits */, 4957 0 /*PropModeReplace*/, 4958 &pid, 4959 1); 4960 4961 updateNetWmIcon(); 4962 4963 if (sdpyWindowClassStr !is null && sdpyWindowClassStr[0]) { 4964 //{ import core.stdc.stdio; printf("winclass: [%s]\n", sdpyWindowClassStr); } 4965 XClassHint klass; 4966 XWMHints wh; 4967 XSizeHints size; 4968 klass.res_name = sdpyWindowClassStr; 4969 klass.res_class = sdpyWindowClassStr; 4970 XSetWMProperties(display, nativeWindow, null, null, null, 0, &size, &wh, &klass); 4971 } 4972 4973 // believe it or not, THIS is what xfce needed for the 9999 issue 4974 XSizeHints sh; 4975 c_long spr; 4976 XGetWMNormalHints(display, nativeWindow, &sh, &spr); 4977 sh.flags |= PMaxSize | PMinSize; 4978 // FIXME maybe nicer resizing 4979 sh.min_width = 16; 4980 sh.min_height = 16; 4981 sh.max_width = 22; 4982 sh.max_height = 22; 4983 XSetWMNormalHints(display, nativeWindow, &sh); 4984 4985 4986 //+/ 4987 4988 4989 XSelectInput(display, nativeWindow, 4990 EventMask.ButtonPressMask | EventMask.ExposureMask | EventMask.StructureNotifyMask | EventMask.VisibilityChangeMask | 4991 EventMask.EnterWindowMask | EventMask.LeaveWindowMask); 4992 4993 sendTrayMessage(SYSTEM_TRAY_REQUEST_DOCK, nativeWindow, 0, 0); 4994 // XMapWindow(display, nativeWindow); // to demo it w/o a tray 4995 4996 CapableOfHandlingNativeEvent.nativeHandleMapping[nativeWindow] = this; 4997 active = true; 4998 } 4999 5000 void updateNetWmIcon() { 5001 if(img is null) return; 5002 auto display = XDisplayConnection.get; 5003 // FIXME: ensure this is correct 5004 arch_ulong[] buffer; 5005 auto imgMi = img.toTrueColorImage; 5006 buffer ~= imgMi.width; 5007 buffer ~= imgMi.height; 5008 foreach(c; imgMi.imageData.colors) { 5009 arch_ulong b; 5010 b |= c.a << 24; 5011 b |= c.r << 16; 5012 b |= c.g << 8; 5013 b |= c.b; 5014 buffer ~= b; 5015 } 5016 5017 XChangeProperty( 5018 display, 5019 nativeHandle, 5020 GetAtom!"_NET_WM_ICON"(display), 5021 GetAtom!"CARDINAL"(display), 5022 32 /* bits */, 5023 0 /*PropModeReplace*/, 5024 buffer.ptr, 5025 cast(int) buffer.length); 5026 } 5027 5028 5029 5030 private SimpleWindow balloon; 5031 version(with_timer) 5032 private Timer timer; 5033 5034 private Window nativeHandle; 5035 private Pixmap clippixmap = None; 5036 private int width = 16; 5037 private int height = 16; 5038 private bool active = false; 5039 5040 void delegate (int x, int y, MouseButton button, ModifierState mods) onClickEx; /// x and y are globals (relative to root window). X11 only. 5041 void delegate (int x, int y, ModifierState mods) onEnter; /// x and y are global window coordinates. X11 only. 5042 void delegate () onLeave; /// X11 only. 5043 5044 @property bool closed () const pure nothrow @safe @nogc { return !active; } /// 5045 5046 /// X11 only. Get global window coordinates and size. This can be used to show various notifications. 5047 void getWindowRect (out int x, out int y, out int width, out int height) { 5048 if (!active) { width = 1; height = 1; return; } // 1: just in case 5049 Window dummyw; 5050 auto dpy = XDisplayConnection.get; 5051 //XWindowAttributes xwa; 5052 //XGetWindowAttributes(dpy, nativeHandle, &xwa); 5053 //XTranslateCoordinates(dpy, nativeHandle, RootWindow(dpy, DefaultScreen(dpy)), xwa.x, xwa.y, &x, &y, &dummyw); 5054 XTranslateCoordinates(dpy, nativeHandle, RootWindow(dpy, DefaultScreen(dpy)), x, y, &x, &y, &dummyw); 5055 width = this.width; 5056 height = this.height; 5057 } 5058 } 5059 5060 /+ 5061 What I actually want from this: 5062 5063 * set / change: icon, tooltip 5064 * handle: mouse click, right click 5065 * show: notification bubble. 5066 +/ 5067 5068 version(Windows) { 5069 WindowsIcon win32Icon; 5070 HWND hwnd; 5071 5072 NOTIFYICONDATAW data; 5073 5074 NativeEventHandler getNativeEventHandler() { 5075 return delegate int(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam, out int mustReturn) { 5076 if(msg == WM_USER) { 5077 auto event = LOWORD(lParam); 5078 auto iconId = HIWORD(lParam); 5079 //auto x = GET_X_LPARAM(wParam); 5080 //auto y = GET_Y_LPARAM(wParam); 5081 switch(event) { 5082 case WM_LBUTTONDOWN: 5083 onClick()(MouseButton.left); 5084 break; 5085 case WM_RBUTTONDOWN: 5086 onClick()(MouseButton.right); 5087 break; 5088 case WM_MBUTTONDOWN: 5089 onClick()(MouseButton.middle); 5090 break; 5091 case WM_MOUSEMOVE: 5092 // sent, we could use it. 5093 break; 5094 case WM_MOUSEWHEEL: 5095 // NOT SENT 5096 break; 5097 //case NIN_KEYSELECT: 5098 //case NIN_SELECT: 5099 //break; 5100 default: {} 5101 } 5102 } 5103 return 0; 5104 }; 5105 } 5106 5107 enum NIF_SHOWTIP = 0x00000080; 5108 5109 private static struct NOTIFYICONDATAW { 5110 DWORD cbSize; 5111 HWND hWnd; 5112 UINT uID; 5113 UINT uFlags; 5114 UINT uCallbackMessage; 5115 HICON hIcon; 5116 WCHAR[128] szTip; 5117 DWORD dwState; 5118 DWORD dwStateMask; 5119 WCHAR[256] szInfo; 5120 union { 5121 UINT uTimeout; 5122 UINT uVersion; 5123 } 5124 WCHAR[64] szInfoTitle; 5125 DWORD dwInfoFlags; 5126 GUID guidItem; 5127 HICON hBalloonIcon; 5128 } 5129 5130 } 5131 5132 /++ 5133 Note that on Windows, only left, right, and middle buttons are sent. 5134 Mouse wheel buttons are NOT set, so don't rely on those events if your 5135 program is meant to be used on Windows too. 5136 +/ 5137 this(string name, MemoryImage icon, void delegate(MouseButton button) onClick) { 5138 // The canonical constructor for Windows needs the MemoryImage, so it is here, 5139 // but on X, we need an Image, so its canonical ctor is there. They should 5140 // forward to each other though. 5141 version(X11) { 5142 this.name = name; 5143 this.onClick = onClick; 5144 createXWin(); 5145 this.icon = icon; 5146 } else version(Windows) { 5147 this.onClick = onClick; 5148 this.win32Icon = new WindowsIcon(icon); 5149 5150 HINSTANCE hInstance = cast(HINSTANCE) GetModuleHandle(null); 5151 5152 static bool registered = false; 5153 if(!registered) { 5154 WNDCLASSEX wc; 5155 wc.cbSize = wc.sizeof; 5156 wc.hInstance = hInstance; 5157 wc.lpfnWndProc = &WndProc; 5158 wc.lpszClassName = "arsd_simpledisplay_notification_icon"w.ptr; 5159 if(!RegisterClassExW(&wc)) 5160 throw new WindowsApiException("RegisterClass", GetLastError()); 5161 registered = true; 5162 } 5163 5164 this.hwnd = CreateWindowW("arsd_simpledisplay_notification_icon"w.ptr, "test"w.ptr /* name */, 0 /* dwStyle */, 0, 0, 0, 0, HWND_MESSAGE, null, hInstance, null); 5165 if(hwnd is null) 5166 throw new WindowsApiException("CreateWindow", GetLastError()); 5167 5168 data.cbSize = data.sizeof; 5169 data.hWnd = hwnd; 5170 data.uID = cast(uint) cast(void*) this; 5171 data.uFlags = NIF_MESSAGE | NIF_ICON | NIF_TIP | NIF_STATE | NIF_SHOWTIP /* use default tooltip, for now. */; 5172 // NIF_INFO means show balloon 5173 data.uCallbackMessage = WM_USER; 5174 data.hIcon = this.win32Icon.hIcon; 5175 data.szTip = ""; // FIXME 5176 data.dwState = 0; // NIS_HIDDEN; // windows vista 5177 data.dwStateMask = NIS_HIDDEN; // windows vista 5178 5179 data.uVersion = 4; // NOTIFYICON_VERSION_4; // Windows Vista and up 5180 5181 5182 Shell_NotifyIcon(NIM_ADD, cast(NOTIFYICONDATA*) &data); 5183 5184 CapableOfHandlingNativeEvent.nativeHandleMapping[this.hwnd] = this; 5185 } else version(OSXCocoa) { 5186 throw new NotYetImplementedException(); 5187 } else static assert(0); 5188 } 5189 5190 /// ditto 5191 this(string name, Image icon, void delegate(MouseButton button) onClick) { 5192 version(X11) { 5193 this.onClick = onClick; 5194 this.name = name; 5195 createXWin(); 5196 this.icon = icon; 5197 } else version(Windows) { 5198 this(name, icon is null ? null : icon.toTrueColorImage(), onClick); 5199 } else version(OSXCocoa) { 5200 throw new NotYetImplementedException(); 5201 } else static assert(0); 5202 } 5203 5204 version(X11) { 5205 /++ 5206 X-specific extension (for now at least) 5207 +/ 5208 this(string name, MemoryImage icon, void delegate(int x, int y, MouseButton button, ModifierState mods) onClickEx) { 5209 this.onClickEx = onClickEx; 5210 createXWin(); 5211 if (icon !is null) this.icon = icon; 5212 } 5213 5214 /// ditto 5215 this(string name, Image icon, void delegate(int x, int y, MouseButton button, ModifierState mods) onClickEx) { 5216 this.onClickEx = onClickEx; 5217 createXWin(); 5218 this.icon = icon; 5219 } 5220 } 5221 5222 private void delegate (MouseButton button) onClick_; 5223 5224 /// 5225 @property final void delegate(MouseButton) onClick() { 5226 if(onClick_ is null) 5227 onClick_ = delegate void(MouseButton) {}; 5228 return onClick_; 5229 } 5230 5231 /// ditto 5232 @property final void onClick(void delegate(MouseButton) handler) { 5233 // I made this a property setter so we can wrap smaller arg 5234 // delegates and just forward all to onClickEx or something. 5235 onClick_ = handler; 5236 } 5237 5238 5239 string name_; 5240 @property void name(string n) { 5241 name_ = n; 5242 } 5243 5244 @property string name() { 5245 return name_; 5246 } 5247 5248 private MemoryImage originalMemoryImage; 5249 5250 /// 5251 @property void icon(MemoryImage i) { 5252 version(X11) { 5253 this.originalMemoryImage = i; 5254 if (!active) return; 5255 if (i !is null) { 5256 this.img = Image.fromMemoryImage(i, useAlpha, false); 5257 if(!useAlpha) 5258 this.clippixmap = transparencyMaskFromMemoryImage(i, nativeHandle); 5259 // writeln("using pixmap ", clippixmap); 5260 updateNetWmIcon(); 5261 redraw(); 5262 } else { 5263 if (this.img !is null) { 5264 this.img = null; 5265 redraw(); 5266 } 5267 } 5268 } else version(Windows) { 5269 this.win32Icon = new WindowsIcon(i); 5270 5271 data.uFlags = NIF_ICON; 5272 data.hIcon = this.win32Icon.hIcon; 5273 5274 Shell_NotifyIcon(NIM_MODIFY, cast(NOTIFYICONDATA*) &data); 5275 } else version(OSXCocoa) { 5276 throw new NotYetImplementedException(); 5277 } else static assert(0); 5278 } 5279 5280 /// ditto 5281 @property void icon (Image i) { 5282 version(X11) { 5283 if (!active) return; 5284 if (i !is img) { 5285 originalMemoryImage = null; 5286 img = i; 5287 redraw(); 5288 } 5289 } else version(Windows) { 5290 this.icon(i is null ? null : i.toTrueColorImage()); 5291 } else version(OSXCocoa) { 5292 throw new NotYetImplementedException(); 5293 } else static assert(0); 5294 } 5295 5296 /++ 5297 Shows a balloon notification. You can only show one balloon at a time, if you call 5298 it twice while one is already up, the first balloon will be replaced. 5299 5300 5301 The user is free to block notifications and they will automatically disappear after 5302 a timeout period. 5303 5304 Params: 5305 title = Title of the notification. Must be 40 chars or less or the OS may truncate it. 5306 message = The message to pop up. Must be 220 chars or less or the OS may truncate it. 5307 icon = the icon to display with the notification. If null, it uses your existing icon. 5308 onclick = delegate called if the user clicks the balloon. (not yet implemented) 5309 timeout = your suggested timeout period. The operating system is free to ignore your suggestion. 5310 +/ 5311 void showBalloon(string title, string message, MemoryImage icon = null, void delegate() onclick = null, int timeout = 2_500) { 5312 bool useCustom = true; 5313 version(libnotify) { 5314 if(onclick is null) // libnotify impl doesn't support callbacks yet because it doesn't do a dbus message loop 5315 try { 5316 if(!active) return; 5317 5318 if(libnotify is null) { 5319 libnotify = new C_DynamicLibrary("libnotify.so"); 5320 libnotify.call!("notify_init", int, const char*)()((ApplicationName ~ "\0").ptr); 5321 } 5322 5323 auto n = libnotify.call!("notify_notification_new", void*, const char*, const char*, const char*)()((title~"\0").ptr, (message~"\0").ptr, null /* icon */); 5324 5325 libnotify.call!("notify_notification_set_timeout", void, void*, int)()(n, timeout); 5326 5327 if(onclick) { 5328 libnotify_action_delegates[libnotify_action_delegates_count] = onclick; 5329 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); 5330 libnotify_action_delegates_count++; 5331 } 5332 5333 // FIXME icon 5334 5335 // set hint image-data 5336 // set default action for onclick 5337 5338 void* error; 5339 libnotify.call!("notify_notification_show", bool, void*, void**)()(n, &error); 5340 5341 useCustom = false; 5342 } catch(Exception e) { 5343 5344 } 5345 } 5346 5347 version(X11) { 5348 if(useCustom) { 5349 if(!active) return; 5350 if(balloon) { 5351 hideBalloon(); 5352 } 5353 // I know there are two specs for this, but one is never 5354 // implemented by any window manager I have ever seen, and 5355 // the other is a bloated mess and too complicated for simpledisplay... 5356 // so doing my own little window instead. 5357 balloon = new SimpleWindow(380, 120, null, OpenGlOptions.no, Resizability.fixedSize, WindowTypes.notification, WindowFlags.dontAutoShow/*, window*/); 5358 5359 int x, y, width, height; 5360 getWindowRect(x, y, width, height); 5361 5362 int bx = x - balloon.width; 5363 int by = y - balloon.height; 5364 if(bx < 0) 5365 bx = x + width + balloon.width; 5366 if(by < 0) 5367 by = y + height; 5368 5369 // just in case, make sure it is actually on scren 5370 if(bx < 0) 5371 bx = 0; 5372 if(by < 0) 5373 by = 0; 5374 5375 balloon.move(bx, by); 5376 auto painter = balloon.draw(); 5377 painter.fillColor = Color(220, 220, 220); 5378 painter.outlineColor = Color.black; 5379 painter.drawRectangle(Point(0, 0), balloon.width, balloon.height); 5380 auto iconWidth = icon is null ? 0 : icon.width; 5381 if(icon) 5382 painter.drawImage(Point(4, 4), Image.fromMemoryImage(icon)); 5383 iconWidth += 6; // margin around the icon 5384 5385 // draw a close button 5386 painter.outlineColor = Color(44, 44, 44); 5387 painter.fillColor = Color(255, 255, 255); 5388 painter.drawRectangle(Point(balloon.width - 15, 3), 13, 13); 5389 painter.pen = Pen(Color.black, 3); 5390 painter.drawLine(Point(balloon.width - 14, 4), Point(balloon.width - 4, 14)); 5391 painter.drawLine(Point(balloon.width - 4, 4), Point(balloon.width - 14, 13)); 5392 painter.pen = Pen(Color.black, 1); 5393 painter.fillColor = Color(220, 220, 220); 5394 5395 // Draw the title and message 5396 painter.drawText(Point(4 + iconWidth, 4), title); 5397 painter.drawLine( 5398 Point(4 + iconWidth, 4 + painter.fontHeight + 1), 5399 Point(balloon.width - 4, 4 + painter.fontHeight + 1), 5400 ); 5401 painter.drawText(Point(4 + iconWidth, 4 + painter.fontHeight + 4), message); 5402 5403 balloon.setEventHandlers( 5404 (MouseEvent ev) { 5405 if(ev.type == MouseEventType.buttonPressed) { 5406 if(ev.x > balloon.width - 16 && ev.y < 16) 5407 hideBalloon(); 5408 else if(onclick) 5409 onclick(); 5410 } 5411 } 5412 ); 5413 balloon.show(); 5414 5415 version(with_timer) 5416 timer = new Timer(timeout, &hideBalloon); 5417 else {} // FIXME 5418 } 5419 } else version(Windows) { 5420 enum NIF_INFO = 0x00000010; 5421 5422 data.uFlags = NIF_INFO; 5423 5424 // FIXME: go back to the last valid unicode code point 5425 if(title.length > 40) 5426 title = title[0 .. 40]; 5427 if(message.length > 220) 5428 message = message[0 .. 220]; 5429 5430 enum NIIF_RESPECT_QUIET_TIME = 0x00000080; 5431 enum NIIF_LARGE_ICON = 0x00000020; 5432 enum NIIF_NOSOUND = 0x00000010; 5433 enum NIIF_USER = 0x00000004; 5434 enum NIIF_ERROR = 0x00000003; 5435 enum NIIF_WARNING = 0x00000002; 5436 enum NIIF_INFO = 0x00000001; 5437 enum NIIF_NONE = 0; 5438 5439 WCharzBuffer t = WCharzBuffer(title); 5440 WCharzBuffer m = WCharzBuffer(message); 5441 5442 t.copyInto(data.szInfoTitle); 5443 m.copyInto(data.szInfo); 5444 data.dwInfoFlags = NIIF_RESPECT_QUIET_TIME; 5445 5446 if(icon !is null) { 5447 auto i = new WindowsIcon(icon); 5448 data.hBalloonIcon = i.hIcon; 5449 data.dwInfoFlags |= NIIF_USER; 5450 } 5451 5452 Shell_NotifyIcon(NIM_MODIFY, cast(NOTIFYICONDATA*) &data); 5453 } else version(OSXCocoa) { 5454 throw new NotYetImplementedException(); 5455 } else static assert(0); 5456 } 5457 5458 /// 5459 //version(Windows) 5460 void show() { 5461 version(X11) { 5462 if(!hidden) 5463 return; 5464 sendTrayMessage(SYSTEM_TRAY_REQUEST_DOCK, nativeHandle, 0, 0); 5465 hidden = false; 5466 } else version(Windows) { 5467 data.uFlags = NIF_STATE; 5468 data.dwState = 0; // NIS_HIDDEN; // windows vista 5469 data.dwStateMask = NIS_HIDDEN; // windows vista 5470 Shell_NotifyIcon(NIM_MODIFY, cast(NOTIFYICONDATA*) &data); 5471 } else version(OSXCocoa) { 5472 throw new NotYetImplementedException(); 5473 } else static assert(0); 5474 } 5475 5476 version(X11) 5477 bool hidden = false; 5478 5479 /// 5480 //version(Windows) 5481 void hide() { 5482 version(X11) { 5483 if(hidden) 5484 return; 5485 hidden = true; 5486 XUnmapWindow(XDisplayConnection.get, nativeHandle); 5487 } else version(Windows) { 5488 data.uFlags = NIF_STATE; 5489 data.dwState = NIS_HIDDEN; // windows vista 5490 data.dwStateMask = NIS_HIDDEN; // windows vista 5491 Shell_NotifyIcon(NIM_MODIFY, cast(NOTIFYICONDATA*) &data); 5492 } else version(OSXCocoa) { 5493 throw new NotYetImplementedException(); 5494 } else static assert(0); 5495 } 5496 5497 /// 5498 void close () { 5499 version(X11) { 5500 if (active) { 5501 active = false; // event handler will set this too, but meh 5502 XUnmapWindow(XDisplayConnection.get, nativeHandle); // 'cause why not; let's be polite 5503 XDestroyWindow(XDisplayConnection.get, nativeHandle); 5504 flushGui(); 5505 } 5506 } else version(Windows) { 5507 Shell_NotifyIcon(NIM_DELETE, cast(NOTIFYICONDATA*) &data); 5508 } else version(OSXCocoa) { 5509 throw new NotYetImplementedException(); 5510 } else static assert(0); 5511 } 5512 5513 ~this() { 5514 if(!thisIsGuiThread) return; // FIXME: leaks if multithreaded gc 5515 version(X11) 5516 if(clippixmap != None) 5517 XFreePixmap(XDisplayConnection.get, clippixmap); 5518 close(); 5519 } 5520 } 5521 5522 version(X11) 5523 /// Call `XFreePixmap` on the return value. 5524 Pixmap transparencyMaskFromMemoryImage(MemoryImage i, Window window) { 5525 char[] data = new char[](i.width * i.height / 8 + 2); 5526 data[] = 0; 5527 5528 int bitOffset = 0; 5529 foreach(c; i.getAsTrueColorImage().imageData.colors) { // FIXME inefficient unnecessary conversion in palette cases 5530 ubyte v = c.a > 128 ? 1 : 0; 5531 data[bitOffset / 8] |= v << (bitOffset%8); 5532 bitOffset++; 5533 } 5534 auto handle = XCreateBitmapFromData(XDisplayConnection.get, cast(Drawable) window, data.ptr, i.width, i.height); 5535 return handle; 5536 } 5537 5538 5539 // basic functions to make timers 5540 /** 5541 A timer that will trigger your function on a given interval. 5542 5543 5544 You create a timer with an interval and a callback. It will continue 5545 to fire on the interval until it is destroyed. 5546 5547 There are currently no one-off timers (instead, just create one and 5548 destroy it when it is triggered) nor are there pause/resume functions - 5549 the timer must again be destroyed and recreated if you want to pause it. 5550 5551 --- 5552 auto timer = new Timer(50, { it happened!; }); 5553 timer.destroy(); 5554 --- 5555 5556 Timers can only be expected to fire when the event loop is running and only 5557 once per iteration through the event loop. 5558 5559 History: 5560 Prior to December 9, 2020, a timer pulse set too high with a handler too 5561 slow could lock up the event loop. It now guarantees other things will 5562 get a chance to run between timer calls, even if that means not keeping up 5563 with the requested interval. 5564 */ 5565 version(with_timer) { 5566 class Timer { 5567 // FIXME: needs pause and unpause 5568 // FIXME: I might add overloads for ones that take a count of 5569 // how many elapsed since last time (on Windows, it will divide 5570 // the ticks thing given, on Linux it is just available) and 5571 // maybe one that takes an instance of the Timer itself too 5572 /// Create a timer with a callback when it triggers. 5573 this(int intervalInMilliseconds, void delegate() onPulse) { 5574 assert(onPulse !is null); 5575 5576 this.intervalInMilliseconds = intervalInMilliseconds; 5577 this.onPulse = onPulse; 5578 5579 version(Windows) { 5580 /* 5581 handle = SetTimer(null, handle, intervalInMilliseconds, &timerCallback); 5582 if(handle == 0) 5583 throw new WindowsApiException("SetTimer", GetLastError()); 5584 */ 5585 5586 // thanks to Archival 998 for the WaitableTimer blocks 5587 handle = CreateWaitableTimer(null, false, null); 5588 long initialTime = -intervalInMilliseconds; 5589 if(handle is null || !SetWaitableTimer(handle, cast(LARGE_INTEGER*)&initialTime, intervalInMilliseconds, &timerCallback, handle, false)) 5590 throw new WindowsApiException("SetWaitableTimer", GetLastError()); 5591 5592 mapping[handle] = this; 5593 5594 } else version(linux) { 5595 static import ep = core.sys.linux.epoll; 5596 5597 import core.sys.linux.timerfd; 5598 5599 fd = timerfd_create(CLOCK_MONOTONIC, 0); 5600 if(fd == -1) 5601 throw new Exception("timer create failed"); 5602 5603 mapping[fd] = this; 5604 5605 itimerspec value; 5606 value.it_value.tv_sec = cast(int) (intervalInMilliseconds / 1000); 5607 value.it_value.tv_nsec = (intervalInMilliseconds % 1000) * 1000_000; 5608 5609 value.it_interval.tv_sec = cast(int) (intervalInMilliseconds / 1000); 5610 value.it_interval.tv_nsec = (intervalInMilliseconds % 1000) * 1000_000; 5611 5612 if(timerfd_settime(fd, 0, &value, null) == -1) 5613 throw new Exception("couldn't make pulse timer"); 5614 5615 version(with_eventloop) { 5616 import arsd.eventloop; 5617 addFileEventListeners(fd, &trigger, null, null); 5618 } else { 5619 prepareEventLoop(); 5620 5621 ep.epoll_event ev = void; 5622 ev.events = ep.EPOLLIN; 5623 ev.data.fd = fd; 5624 ep.epoll_ctl(epollFd, ep.EPOLL_CTL_ADD, fd, &ev); 5625 } 5626 } else featureNotImplemented(); 5627 } 5628 5629 private int intervalInMilliseconds; 5630 5631 // just cuz I sometimes call it this. 5632 alias dispose = destroy; 5633 5634 /// Stop and destroy the timer object. 5635 void destroy() { 5636 version(Windows) { 5637 staticDestroy(handle); 5638 handle = null; 5639 } else version(linux) { 5640 staticDestroy(fd); 5641 fd = -1; 5642 } else featureNotImplemented(); 5643 } 5644 5645 version(Windows) 5646 static void staticDestroy(HANDLE handle) { 5647 if(handle) { 5648 // KillTimer(null, handle); 5649 CancelWaitableTimer(cast(void*)handle); 5650 mapping.remove(handle); 5651 CloseHandle(handle); 5652 } 5653 } 5654 else version(linux) 5655 static void staticDestroy(int fd) { 5656 if(fd != -1) { 5657 import unix = core.sys.posix.unistd; 5658 static import ep = core.sys.linux.epoll; 5659 5660 version(with_eventloop) { 5661 import arsd.eventloop; 5662 removeFileEventListeners(fd); 5663 } else { 5664 ep.epoll_event ev = void; 5665 ev.events = ep.EPOLLIN; 5666 ev.data.fd = fd; 5667 5668 ep.epoll_ctl(epollFd, ep.EPOLL_CTL_DEL, fd, &ev); 5669 } 5670 unix.close(fd); 5671 mapping.remove(fd); 5672 } 5673 } 5674 5675 ~this() { 5676 version(Windows) { if(handle) 5677 cleanupQueue.queue!staticDestroy(handle); 5678 } else version(linux) { if(fd != -1) 5679 cleanupQueue.queue!staticDestroy(fd); 5680 } 5681 } 5682 5683 5684 void changeTime(int intervalInMilliseconds) 5685 { 5686 this.intervalInMilliseconds = intervalInMilliseconds; 5687 version(Windows) 5688 { 5689 if(handle) 5690 { 5691 //handle = SetTimer(null, handle, intervalInMilliseconds, &timerCallback); 5692 long initialTime = -intervalInMilliseconds; 5693 if(handle is null || !SetWaitableTimer(handle, cast(LARGE_INTEGER*)&initialTime, intervalInMilliseconds, &timerCallback, handle, false)) 5694 throw new WindowsApiException("couldn't change pulse timer", GetLastError()); 5695 } 5696 } 5697 } 5698 5699 5700 private: 5701 5702 void delegate() onPulse; 5703 5704 int lastEventLoopRoundTriggered; 5705 5706 void trigger() { 5707 version(linux) { 5708 import unix = core.sys.posix.unistd; 5709 long val; 5710 unix.read(fd, &val, val.sizeof); // gotta clear the pipe 5711 } else version(Windows) { 5712 if(this.lastEventLoopRoundTriggered == eventLoopRound) 5713 return; // never try to actually run faster than the event loop 5714 lastEventLoopRoundTriggered = eventLoopRound; 5715 } else featureNotImplemented(); 5716 5717 onPulse(); 5718 } 5719 5720 version(Windows) 5721 void rearm() { 5722 5723 } 5724 5725 version(Windows) 5726 extern(Windows) 5727 //static void timerCallback(HWND, UINT, UINT_PTR timer, DWORD dwTime) nothrow { 5728 static void timerCallback(HANDLE timer, DWORD lowTime, DWORD hiTime) nothrow { 5729 if(Timer* t = timer in mapping) { 5730 try 5731 (*t).trigger(); 5732 catch(Exception e) { sdpy_abort(e); assert(0); } 5733 } 5734 } 5735 5736 version(Windows) { 5737 //UINT_PTR handle; 5738 //static Timer[UINT_PTR] mapping; 5739 HANDLE handle; 5740 __gshared Timer[HANDLE] mapping; 5741 } else version(linux) { 5742 int fd = -1; 5743 __gshared Timer[int] mapping; 5744 } else version(OSXCocoa) { 5745 } else static assert(0, "timer not supported"); 5746 } 5747 } 5748 5749 version(Windows) 5750 private int eventLoopRound; 5751 5752 version(Windows) 5753 /// 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 5754 class WindowsHandleReader { 5755 /// 5756 this(void delegate() onReady, HANDLE handle) { 5757 this.onReady = onReady; 5758 this.handle = handle; 5759 5760 mapping[handle] = this; 5761 5762 enable(); 5763 } 5764 5765 /// 5766 void enable() { 5767 auto el = EventLoop.get().impl; 5768 el.handles ~= handle; 5769 } 5770 5771 /// 5772 void disable() { 5773 auto el = EventLoop.get().impl; 5774 for(int i = 0; i < el.handles.length; i++) { 5775 if(el.handles[i] is handle) { 5776 el.handles[i] = el.handles[$-1]; 5777 el.handles = el.handles[0 .. $-1]; 5778 return; 5779 } 5780 } 5781 } 5782 5783 void dispose() { 5784 disable(); 5785 if(handle) 5786 mapping.remove(handle); 5787 handle = null; 5788 } 5789 5790 void ready() { 5791 if(onReady) 5792 onReady(); 5793 } 5794 5795 HANDLE handle; 5796 void delegate() onReady; 5797 5798 __gshared WindowsHandleReader[HANDLE] mapping; 5799 } 5800 5801 version(Posix) 5802 /// Lets you add files to the event loop for reading. Use at your own risk. 5803 class PosixFdReader { 5804 /// 5805 this(void delegate() onReady, int fd, bool captureReads = true, bool captureWrites = false) { 5806 this((int, bool, bool) { onReady(); }, fd, captureReads, captureWrites); 5807 } 5808 5809 /// 5810 this(void delegate(int) onReady, int fd, bool captureReads = true, bool captureWrites = false) { 5811 this((int fd, bool, bool) { onReady(fd); }, fd, captureReads, captureWrites); 5812 } 5813 5814 /// 5815 this(void delegate(int fd, bool read, bool write) onReady, int fd, bool captureReads = true, bool captureWrites = false) { 5816 this.onReady = onReady; 5817 this.fd = fd; 5818 this.captureWrites = captureWrites; 5819 this.captureReads = captureReads; 5820 5821 mapping[fd] = this; 5822 5823 version(with_eventloop) { 5824 import arsd.eventloop; 5825 addFileEventListeners(fd, &readyel); 5826 } else { 5827 enable(); 5828 } 5829 } 5830 5831 bool captureReads; 5832 bool captureWrites; 5833 5834 version(with_eventloop) {} else 5835 /// 5836 void enable() { 5837 prepareEventLoop(); 5838 5839 enabled = true; 5840 5841 version(linux) { 5842 static import ep = core.sys.linux.epoll; 5843 ep.epoll_event ev = void; 5844 ev.events = (captureReads ? ep.EPOLLIN : 0) | (captureWrites ? ep.EPOLLOUT : 0); 5845 // writeln("enable ", fd, " ", captureReads, " ", captureWrites); 5846 ev.data.fd = fd; 5847 ep.epoll_ctl(epollFd, ep.EPOLL_CTL_ADD, fd, &ev); 5848 } else { 5849 5850 } 5851 } 5852 5853 version(with_eventloop) {} else 5854 /// 5855 void disable() { 5856 prepareEventLoop(); 5857 5858 enabled = false; 5859 5860 version(linux) { 5861 static import ep = core.sys.linux.epoll; 5862 ep.epoll_event ev = void; 5863 ev.events = (captureReads ? ep.EPOLLIN : 0) | (captureWrites ? ep.EPOLLOUT : 0); 5864 // writeln("disable ", fd, " ", captureReads, " ", captureWrites); 5865 ev.data.fd = fd; 5866 ep.epoll_ctl(epollFd, ep.EPOLL_CTL_DEL, fd, &ev); 5867 } 5868 } 5869 5870 version(with_eventloop) {} else 5871 /// 5872 void dispose() { 5873 if(enabled) 5874 disable(); 5875 if(fd != -1) 5876 mapping.remove(fd); 5877 fd = -1; 5878 } 5879 5880 void delegate(int, bool, bool) onReady; 5881 5882 version(with_eventloop) 5883 void readyel() { 5884 onReady(fd, true, true); 5885 } 5886 5887 void ready(uint flags) { 5888 version(linux) { 5889 static import ep = core.sys.linux.epoll; 5890 onReady(fd, (flags & ep.EPOLLIN) ? true : false, (flags & ep.EPOLLOUT) ? true : false); 5891 } else { 5892 import core.sys.posix.poll; 5893 onReady(fd, (flags & POLLIN) ? true : false, (flags & POLLOUT) ? true : false); 5894 } 5895 } 5896 5897 void hup(uint flags) { 5898 if(onHup) 5899 onHup(); 5900 } 5901 5902 void delegate() onHup; 5903 5904 int fd = -1; 5905 private bool enabled; 5906 __gshared PosixFdReader[int] mapping; 5907 } 5908 5909 // basic functions to access the clipboard 5910 /+ 5911 5912 5913 http://msdn.microsoft.com/en-us/library/windows/desktop/ff729168%28v=vs.85%29.aspx 5914 http://msdn.microsoft.com/en-us/library/windows/desktop/ms649039%28v=vs.85%29.aspx 5915 http://msdn.microsoft.com/en-us/library/windows/desktop/ms649035%28v=vs.85%29.aspx 5916 http://msdn.microsoft.com/en-us/library/windows/desktop/ms649051%28v=vs.85%29.aspx 5917 http://msdn.microsoft.com/en-us/library/windows/desktop/ms649037%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/ms649016%28v=vs.85%29.aspx 5920 5921 +/ 5922 5923 /++ 5924 this does a delegate because it is actually an async call on X... 5925 the receiver may never be called if the clipboard is empty or unavailable 5926 gets plain text from the clipboard. 5927 +/ 5928 void getClipboardText(SimpleWindow clipboardOwner, void delegate(in char[]) receiver) { 5929 version(Windows) { 5930 HWND hwndOwner = clipboardOwner ? clipboardOwner.impl.hwnd : null; 5931 if(OpenClipboard(hwndOwner) == 0) 5932 throw new WindowsApiException("OpenClipboard", GetLastError()); 5933 scope(exit) 5934 CloseClipboard(); 5935 // see: https://docs.microsoft.com/en-us/windows/win32/api/winuser/nf-winuser-getpriorityclipboardformat 5936 if(auto dataHandle = GetClipboardData(CF_UNICODETEXT)) { 5937 5938 if(auto data = cast(wchar*) GlobalLock(dataHandle)) { 5939 scope(exit) 5940 GlobalUnlock(dataHandle); 5941 5942 // FIXME: CR/LF conversions 5943 // FIXME: I might not have to copy it now that the receiver is in char[] instead of string 5944 int len = 0; 5945 auto d = data; 5946 while(*d) { 5947 d++; 5948 len++; 5949 } 5950 string s; 5951 s.reserve(len); 5952 foreach(dchar ch; data[0 .. len]) { 5953 s ~= ch; 5954 } 5955 receiver(s); 5956 } 5957 } 5958 } else version(X11) { 5959 getX11Selection!"CLIPBOARD"(clipboardOwner, receiver); 5960 } else version(OSXCocoa) { 5961 throw new NotYetImplementedException(); 5962 } else static assert(0); 5963 } 5964 5965 // FIXME: a clipboard listener might be cool btw 5966 5967 /++ 5968 this does a delegate because it is actually an async call on X... 5969 the receiver may never be called if the clipboard is empty or unavailable 5970 gets image from the clipboard. 5971 5972 templated because it introduces an optional dependency on arsd.bmp 5973 +/ 5974 void getClipboardImage()(SimpleWindow clipboardOwner, void delegate(MemoryImage) receiver) { 5975 version(Windows) { 5976 HWND hwndOwner = clipboardOwner ? clipboardOwner.impl.hwnd : null; 5977 if(OpenClipboard(hwndOwner) == 0) 5978 throw new WindowsApiException("OpenClipboard", GetLastError()); 5979 scope(exit) 5980 CloseClipboard(); 5981 if(auto dataHandle = GetClipboardData(CF_DIBV5)) { 5982 if(auto data = cast(ubyte*) GlobalLock(dataHandle)) { 5983 scope(exit) 5984 GlobalUnlock(dataHandle); 5985 5986 auto len = GlobalSize(dataHandle); 5987 5988 import arsd.bmp; 5989 auto img = readBmp(data[0 .. len], false); 5990 receiver(img); 5991 } 5992 } 5993 } else version(X11) { 5994 getX11Selection!"CLIPBOARD"(clipboardOwner, receiver); 5995 } else version(OSXCocoa) { 5996 throw new NotYetImplementedException(); 5997 } else static assert(0); 5998 } 5999 6000 /// Copies some text to the clipboard. 6001 void setClipboardText(SimpleWindow clipboardOwner, string text) { 6002 assert(clipboardOwner !is null); 6003 version(Windows) { 6004 if(OpenClipboard(clipboardOwner.impl.hwnd) == 0) 6005 throw new WindowsApiException("OpenClipboard", GetLastError()); 6006 scope(exit) 6007 CloseClipboard(); 6008 EmptyClipboard(); 6009 auto sz = sizeOfConvertedWstring(text, WindowsStringConversionFlags.convertNewLines | WindowsStringConversionFlags.zeroTerminate); 6010 auto handle = GlobalAlloc(GMEM_MOVEABLE, sz * 2); // zero terminated wchars 6011 if(handle is null) throw new WindowsApiException("GlobalAlloc", GetLastError()); 6012 if(auto data = cast(wchar*) GlobalLock(handle)) { 6013 auto slice = data[0 .. sz]; 6014 scope(failure) 6015 GlobalUnlock(handle); 6016 6017 auto str = makeWindowsString(text, slice, WindowsStringConversionFlags.convertNewLines | WindowsStringConversionFlags.zeroTerminate); 6018 6019 GlobalUnlock(handle); 6020 SetClipboardData(CF_UNICODETEXT, handle); 6021 } 6022 } else version(X11) { 6023 setX11Selection!"CLIPBOARD"(clipboardOwner, text); 6024 } else version(OSXCocoa) { 6025 throw new NotYetImplementedException(); 6026 } else static assert(0); 6027 } 6028 6029 void setClipboardImage()(SimpleWindow clipboardOwner, MemoryImage img) { 6030 assert(clipboardOwner !is null); 6031 version(Windows) { 6032 if(OpenClipboard(clipboardOwner.impl.hwnd) == 0) 6033 throw new WindowsApiException("OpenClipboard", GetLastError()); 6034 scope(exit) 6035 CloseClipboard(); 6036 EmptyClipboard(); 6037 6038 6039 import arsd.bmp; 6040 ubyte[] mdata; 6041 mdata.reserve(img.width * img.height); 6042 void sink(ubyte b) { 6043 mdata ~= b; 6044 } 6045 writeBmpIndirect(img, &sink, false); 6046 6047 auto handle = GlobalAlloc(GMEM_MOVEABLE, mdata.length); 6048 if(handle is null) throw new WindowsApiException("GlobalAlloc", GetLastError()); 6049 if(auto data = cast(ubyte*) GlobalLock(handle)) { 6050 auto slice = data[0 .. mdata.length]; 6051 scope(failure) 6052 GlobalUnlock(handle); 6053 6054 slice[] = mdata[]; 6055 6056 GlobalUnlock(handle); 6057 SetClipboardData(CF_DIB, handle); 6058 } 6059 } else version(X11) { 6060 static class X11SetSelectionHandler_Image : X11SetSelectionHandler { 6061 mixin X11SetSelectionHandler_Basics; 6062 private const(ubyte)[] mdata; 6063 private const(ubyte)[] mdata_original; 6064 this(MemoryImage img) { 6065 import arsd.bmp; 6066 6067 mdata.reserve(img.width * img.height); 6068 void sink(ubyte b) { 6069 mdata ~= b; 6070 } 6071 writeBmpIndirect(img, &sink, true); 6072 6073 mdata_original = mdata; 6074 } 6075 6076 Atom[] availableFormats() { 6077 auto display = XDisplayConnection.get; 6078 return [ 6079 GetAtom!"image/bmp"(display), 6080 GetAtom!"TARGETS"(display) 6081 ]; 6082 } 6083 6084 ubyte[] getData(Atom format, return scope ubyte[] data) { 6085 if(mdata.length < data.length) { 6086 data[0 .. mdata.length] = mdata[]; 6087 auto ret = data[0 .. mdata.length]; 6088 mdata = mdata[$..$]; 6089 return ret; 6090 } else { 6091 data[] = mdata[0 .. data.length]; 6092 mdata = mdata[data.length .. $]; 6093 return data[]; 6094 } 6095 } 6096 6097 void done() { 6098 mdata = mdata_original; 6099 } 6100 } 6101 6102 setX11Selection!"CLIPBOARD"(clipboardOwner, new X11SetSelectionHandler_Image(img)); 6103 } else version(OSXCocoa) { 6104 throw new NotYetImplementedException(); 6105 } else static assert(0); 6106 } 6107 6108 6109 version(X11) { 6110 // and the PRIMARY on X, be sure to put these in static if(UsingSimpledisplayX11) 6111 6112 private Atom*[] interredAtoms; // for discardAndRecreate 6113 6114 // FIXME: do a GetAtomUpfront too that just queues all at CT and combines it all. 6115 /// Platform-specific for X11. 6116 /// History: On February 21, 2021, I changed the default value of `create` to be true. 6117 @property Atom GetAtom(string name, bool create = true)(Display* display) { 6118 static Atom a; 6119 if(!a) { 6120 a = XInternAtom(display, name, !create); 6121 interredAtoms ~= &a; 6122 } 6123 if(a == None) 6124 throw new Exception("XInternAtom " ~ name ~ " " ~ (create ? "true":"false")); 6125 return a; 6126 } 6127 6128 /// Platform-specific for X11 - gets atom names as a string. 6129 string getAtomName(Atom atom, Display* display) { 6130 auto got = XGetAtomName(display, atom); 6131 scope(exit) XFree(got); 6132 import core.stdc.string; 6133 string s = got[0 .. strlen(got)].idup; 6134 return s; 6135 } 6136 6137 /// Asserts ownership of PRIMARY and copies the text into a buffer that clients can request later. 6138 void setPrimarySelection(SimpleWindow window, string text) { 6139 setX11Selection!"PRIMARY"(window, text); 6140 } 6141 6142 /// Asserts ownership of SECONDARY and copies the text into a buffer that clients can request later. 6143 void setSecondarySelection(SimpleWindow window, string text) { 6144 setX11Selection!"SECONDARY"(window, text); 6145 } 6146 6147 interface X11SetSelectionHandler { 6148 // should include TARGETS right now 6149 Atom[] availableFormats(); 6150 // Return the slice of data you filled, empty slice if done. 6151 // this is to support the incremental thing 6152 ubyte[] getData(Atom format, return scope ubyte[] data); 6153 6154 void done(); 6155 6156 void handleRequest(XEvent); 6157 6158 bool matchesIncr(Window, Atom); 6159 void sendMoreIncr(XPropertyEvent*); 6160 } 6161 6162 mixin template X11SetSelectionHandler_Basics() { 6163 Window incrWindow; 6164 Atom incrAtom; 6165 Atom selectionAtom; 6166 Atom formatAtom; 6167 ubyte[] toSend; 6168 bool matchesIncr(Window w, Atom a) { 6169 return incrAtom && incrAtom == a && w == incrWindow; 6170 } 6171 void sendMoreIncr(XPropertyEvent* event) { 6172 auto display = XDisplayConnection.get; 6173 6174 XChangeProperty (display, 6175 incrWindow, 6176 incrAtom, 6177 formatAtom, 6178 8 /* bits */, PropModeReplace, 6179 toSend.ptr, cast(int) toSend.length); 6180 6181 if(toSend.length != 0) { 6182 toSend = this.getData(formatAtom, toSend[]); 6183 } else { 6184 this.done(); 6185 incrWindow = None; 6186 incrAtom = None; 6187 selectionAtom = None; 6188 formatAtom = None; 6189 toSend = null; 6190 } 6191 } 6192 void handleRequest(XEvent ev) { 6193 6194 auto display = XDisplayConnection.get; 6195 6196 XSelectionRequestEvent* event = &ev.xselectionrequest; 6197 XSelectionEvent selectionEvent; 6198 selectionEvent.type = EventType.SelectionNotify; 6199 selectionEvent.display = event.display; 6200 selectionEvent.requestor = event.requestor; 6201 selectionEvent.selection = event.selection; 6202 selectionEvent.time = event.time; 6203 selectionEvent.target = event.target; 6204 6205 bool supportedType() { 6206 foreach(t; this.availableFormats()) 6207 if(t == event.target) 6208 return true; 6209 return false; 6210 } 6211 6212 if(event.property == None) { 6213 selectionEvent.property = event.target; 6214 6215 XSendEvent(display, selectionEvent.requestor, false, 0, cast(XEvent*) &selectionEvent); 6216 XFlush(display); 6217 } if(event.target == GetAtom!"TARGETS"(display)) { 6218 /* respond with the supported types */ 6219 auto tlist = this.availableFormats(); 6220 XChangeProperty(display, event.requestor, event.property, XA_ATOM, 32, PropModeReplace, cast(void*)tlist.ptr, cast(int) tlist.length); 6221 selectionEvent.property = event.property; 6222 6223 XSendEvent(display, selectionEvent.requestor, false, 0, cast(XEvent*) &selectionEvent); 6224 XFlush(display); 6225 } else if(supportedType()) { 6226 auto buffer = new ubyte[](1024 * 64); 6227 auto toSend = this.getData(event.target, buffer[]); 6228 6229 if(toSend.length < 32 * 1024) { 6230 // small enough to send directly... 6231 selectionEvent.property = event.property; 6232 XChangeProperty (display, 6233 selectionEvent.requestor, 6234 selectionEvent.property, 6235 event.target, 6236 8 /* bits */, 0 /* PropModeReplace */, 6237 toSend.ptr, cast(int) toSend.length); 6238 6239 XSendEvent(display, selectionEvent.requestor, false, 0, cast(XEvent*) &selectionEvent); 6240 XFlush(display); 6241 } else { 6242 // large, let's send incrementally 6243 arch_ulong l = toSend.length; 6244 6245 // if I wanted other events from this window don't want to clear that out.... 6246 XWindowAttributes xwa; 6247 XGetWindowAttributes(display, selectionEvent.requestor, &xwa); 6248 6249 XSelectInput(display, selectionEvent.requestor, cast(EventMask) (xwa.your_event_mask | EventMask.PropertyChangeMask)); 6250 6251 incrWindow = event.requestor; 6252 incrAtom = event.property; 6253 formatAtom = event.target; 6254 selectionAtom = event.selection; 6255 this.toSend = toSend; 6256 6257 selectionEvent.property = event.property; 6258 XChangeProperty (display, 6259 selectionEvent.requestor, 6260 selectionEvent.property, 6261 GetAtom!"INCR"(display), 6262 32 /* bits */, PropModeReplace, 6263 &l, 1); 6264 6265 XSendEvent(display, selectionEvent.requestor, false, 0, cast(XEvent*) &selectionEvent); 6266 XFlush(display); 6267 } 6268 //if(after) 6269 //after(); 6270 } else { 6271 debug(sdpy_clip) { 6272 writeln("Unsupported data ", getAtomName(event.target, display)); 6273 } 6274 selectionEvent.property = None; // I don't know how to handle this type... 6275 XSendEvent(display, selectionEvent.requestor, false, EventMask.NoEventMask, cast(XEvent*) &selectionEvent); 6276 XFlush(display); 6277 } 6278 } 6279 } 6280 6281 class X11SetSelectionHandler_Text : X11SetSelectionHandler { 6282 mixin X11SetSelectionHandler_Basics; 6283 private const(ubyte)[] text; 6284 private const(ubyte)[] text_original; 6285 this(string text) { 6286 this.text = cast(const ubyte[]) text; 6287 this.text_original = this.text; 6288 } 6289 Atom[] availableFormats() { 6290 auto display = XDisplayConnection.get; 6291 return [ 6292 GetAtom!"UTF8_STRING"(display), 6293 GetAtom!"text/plain"(display), 6294 XA_STRING, 6295 GetAtom!"TARGETS"(display) 6296 ]; 6297 } 6298 6299 ubyte[] getData(Atom format, return scope ubyte[] data) { 6300 if(text.length < data.length) { 6301 data[0 .. text.length] = text[]; 6302 return data[0 .. text.length]; 6303 } else { 6304 data[] = text[0 .. data.length]; 6305 text = text[data.length .. $]; 6306 return data[]; 6307 } 6308 } 6309 6310 void done() { 6311 text = text_original; 6312 } 6313 } 6314 6315 /// 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?!) 6316 void setX11Selection(string atomName)(SimpleWindow window, string text, void delegate() after = null) { 6317 setX11Selection!atomName(window, new X11SetSelectionHandler_Text(text), after); 6318 } 6319 6320 void setX11Selection(string atomName)(SimpleWindow window, X11SetSelectionHandler data, void delegate() after = null) { 6321 assert(window !is null); 6322 6323 auto display = XDisplayConnection.get(); 6324 static if (atomName == "PRIMARY") Atom a = XA_PRIMARY; 6325 else static if (atomName == "SECONDARY") Atom a = XA_SECONDARY; 6326 else Atom a = GetAtom!atomName(display); 6327 6328 XSetSelectionOwner(display, a, window.impl.window, 0 /* CurrentTime */); 6329 6330 window.impl.setSelectionHandlers[a] = data; 6331 } 6332 6333 /// 6334 void getPrimarySelection(SimpleWindow window, void delegate(in char[]) handler) { 6335 getX11Selection!"PRIMARY"(window, handler); 6336 } 6337 6338 // added July 28, 2020 6339 // undocumented as experimental tho 6340 interface X11GetSelectionHandler { 6341 void handleData(Atom target, in ubyte[] data); 6342 Atom findBestFormat(Atom[] answer); 6343 6344 void prepareIncremental(Window, Atom); 6345 bool matchesIncr(Window, Atom); 6346 void handleIncrData(Atom, in ubyte[] data); 6347 } 6348 6349 mixin template X11GetSelectionHandler_Basics() { 6350 Window incrWindow; 6351 Atom incrAtom; 6352 6353 void prepareIncremental(Window w, Atom a) { 6354 incrWindow = w; 6355 incrAtom = a; 6356 } 6357 bool matchesIncr(Window w, Atom a) { 6358 return incrWindow == w && incrAtom == a; 6359 } 6360 6361 Atom incrFormatAtom; 6362 ubyte[] incrData; 6363 void handleIncrData(Atom format, in ubyte[] data) { 6364 incrFormatAtom = format; 6365 6366 if(data.length) 6367 incrData ~= data; 6368 else 6369 handleData(incrFormatAtom, incrData); 6370 6371 } 6372 } 6373 6374 /// 6375 void getX11Selection(string atomName)(SimpleWindow window, void delegate(in char[]) handler, Time timestamp = 0 /* CurrentTime */) { 6376 assert(window !is null); 6377 6378 auto display = XDisplayConnection.get(); 6379 auto atom = GetAtom!atomName(display); 6380 6381 static class X11GetSelectionHandler_Text : X11GetSelectionHandler { 6382 this(void delegate(in char[]) handler) { 6383 this.handler = handler; 6384 } 6385 6386 mixin X11GetSelectionHandler_Basics; 6387 6388 void delegate(in char[]) handler; 6389 6390 void handleData(Atom target, in ubyte[] data) { 6391 if(target == GetAtom!"UTF8_STRING"(XDisplayConnection.get) || target == XA_STRING || target == GetAtom!"text/plain"(XDisplayConnection.get)) 6392 handler(cast(const char[]) data); 6393 } 6394 6395 Atom findBestFormat(Atom[] answer) { 6396 Atom best = None; 6397 foreach(option; answer) { 6398 if(option == GetAtom!"UTF8_STRING"(XDisplayConnection.get)) { 6399 best = option; 6400 break; 6401 } else if(option == XA_STRING) { 6402 best = option; 6403 } else if(option == GetAtom!"text/plain"(XDisplayConnection.get)) { 6404 best = option; 6405 } 6406 } 6407 return best; 6408 } 6409 } 6410 6411 window.impl.getSelectionHandlers[atom] = new X11GetSelectionHandler_Text(handler); 6412 6413 auto target = GetAtom!"TARGETS"(display); 6414 6415 // SDD_DATA is "simpledisplay.d data" 6416 XConvertSelection(display, atom, target, GetAtom!("SDD_DATA", true)(display), window.impl.window, timestamp); 6417 } 6418 6419 /// Gets the image on the clipboard, if there is one. Added July 2020. 6420 void getX11Selection(string atomName)(SimpleWindow window, void delegate(MemoryImage) handler) { 6421 assert(window !is null); 6422 6423 auto display = XDisplayConnection.get(); 6424 auto atom = GetAtom!atomName(display); 6425 6426 static class X11GetSelectionHandler_Image : X11GetSelectionHandler { 6427 this(void delegate(MemoryImage) handler) { 6428 this.handler = handler; 6429 } 6430 6431 mixin X11GetSelectionHandler_Basics; 6432 6433 void delegate(MemoryImage) handler; 6434 6435 void handleData(Atom target, in ubyte[] data) { 6436 if(target == GetAtom!"image/bmp"(XDisplayConnection.get)) { 6437 import arsd.bmp; 6438 handler(readBmp(data)); 6439 } 6440 } 6441 6442 Atom findBestFormat(Atom[] answer) { 6443 Atom best = None; 6444 foreach(option; answer) { 6445 if(option == GetAtom!"image/bmp"(XDisplayConnection.get)) { 6446 best = option; 6447 } 6448 } 6449 return best; 6450 } 6451 6452 } 6453 6454 6455 window.impl.getSelectionHandlers[atom] = new X11GetSelectionHandler_Image(handler); 6456 6457 auto target = GetAtom!"TARGETS"(display); 6458 6459 // SDD_DATA is "simpledisplay.d data" 6460 XConvertSelection(display, atom, target, GetAtom!("SDD_DATA", true)(display), window.impl.window, 0 /*CurrentTime*/); 6461 } 6462 6463 6464 /// 6465 void[] getX11PropertyData(Window window, Atom property, Atom type = AnyPropertyType) { 6466 Atom actualType; 6467 int actualFormat; 6468 arch_ulong actualItems; 6469 arch_ulong bytesRemaining; 6470 void* data; 6471 6472 auto display = XDisplayConnection.get(); 6473 if(XGetWindowProperty(display, window, property, 0, 0x7fffffff, false, type, &actualType, &actualFormat, &actualItems, &bytesRemaining, &data) == Success) { 6474 if(actualFormat == 0) 6475 return null; 6476 else { 6477 int byteLength; 6478 if(actualFormat == 32) { 6479 // 32 means it is a C long... which is variable length 6480 actualFormat = cast(int) arch_long.sizeof * 8; 6481 } 6482 6483 // then it is just a bit count 6484 byteLength = cast(int) (actualItems * actualFormat / 8); 6485 6486 auto d = new ubyte[](byteLength); 6487 d[] = cast(ubyte[]) data[0 .. byteLength]; 6488 XFree(data); 6489 return d; 6490 } 6491 } 6492 return null; 6493 } 6494 6495 /* defined in the systray spec */ 6496 enum SYSTEM_TRAY_REQUEST_DOCK = 0; 6497 enum SYSTEM_TRAY_BEGIN_MESSAGE = 1; 6498 enum SYSTEM_TRAY_CANCEL_MESSAGE = 2; 6499 6500 6501 /** Global hotkey handler. Simpledisplay will usually create one for you, but if you want to use subclassing 6502 * instead of delegates, you can subclass this, and override `doHandle()` method. */ 6503 public class GlobalHotkey { 6504 KeyEvent key; 6505 void delegate () handler; 6506 6507 void doHandle () { if (handler !is null) handler(); } /// this will be called by hotkey manager 6508 6509 /// Create from initialzed KeyEvent object 6510 this (KeyEvent akey, void delegate () ahandler=null) { 6511 if (akey.key == 0 || !GlobalHotkeyManager.isGoodModifierMask(akey.modifierState)) throw new Exception("invalid global hotkey"); 6512 key = akey; 6513 handler = ahandler; 6514 } 6515 6516 /// Create from emacs-like key name ("C-M-Y", etc.) 6517 this (const(char)[] akey, void delegate () ahandler=null) { 6518 key = KeyEvent.parse(akey); 6519 if (key.key == 0 || !GlobalHotkeyManager.isGoodModifierMask(key.modifierState)) throw new Exception("invalid global hotkey"); 6520 handler = ahandler; 6521 } 6522 6523 } 6524 6525 private extern(C) int XGrabErrorHandler (Display* dpy, XErrorEvent* evt) nothrow @nogc { 6526 //conwriteln("failed to grab key"); 6527 GlobalHotkeyManager.ghfailed = true; 6528 return 0; 6529 } 6530 6531 private extern(C) int XShmErrorHandler (Display* dpy, XErrorEvent* evt) nothrow @nogc { 6532 Image.impl.xshmfailed = true; 6533 return 0; 6534 } 6535 6536 private __gshared int errorHappened; 6537 private extern(C) int adrlogger (Display* dpy, XErrorEvent* evt) nothrow @nogc { 6538 import core.stdc.stdio; 6539 char[265] buffer; 6540 XGetErrorText(dpy, evt.error_code, buffer.ptr, cast(int) buffer.length); 6541 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); 6542 errorHappened = true; 6543 return 0; 6544 } 6545 6546 /++ 6547 Global hotkey manager. It contains static methods to manage global hotkeys. 6548 6549 --- 6550 try { 6551 GlobalHotkeyManager.register("M-H-A", delegate () { hideShowWindows(); }); 6552 } catch (Exception e) { 6553 conwriteln("ERROR registering hotkey!"); 6554 } 6555 EventLoop.get.run(); 6556 --- 6557 6558 The key strings are based on Emacs. In practical terms, 6559 `M` means `alt` and `H` means the Windows logo key. `C` 6560 is `ctrl`. 6561 6562 $(WARNING 6563 This is X-specific right now. If you are on 6564 Windows, try [registerHotKey] instead. 6565 6566 We will probably merge these into a single 6567 interface later. 6568 ) 6569 +/ 6570 public class GlobalHotkeyManager : CapableOfHandlingNativeEvent { 6571 version(X11) { 6572 void recreateAfterDisconnect() { 6573 throw new Exception("NOT IMPLEMENTED"); 6574 } 6575 void discardConnectionState() { 6576 throw new Exception("NOT IMPLEMENTED"); 6577 } 6578 } 6579 6580 private static immutable uint[8] masklist = [ 0, 6581 KeyOrButtonMask.LockMask, 6582 KeyOrButtonMask.Mod2Mask, 6583 KeyOrButtonMask.Mod3Mask, 6584 KeyOrButtonMask.LockMask|KeyOrButtonMask.Mod2Mask, 6585 KeyOrButtonMask.LockMask|KeyOrButtonMask.Mod3Mask, 6586 KeyOrButtonMask.LockMask|KeyOrButtonMask.Mod2Mask|KeyOrButtonMask.Mod3Mask, 6587 KeyOrButtonMask.Mod2Mask|KeyOrButtonMask.Mod3Mask, 6588 ]; 6589 private __gshared GlobalHotkeyManager ghmanager; 6590 private __gshared bool ghfailed = false; 6591 6592 private static bool isGoodModifierMask (uint modmask) pure nothrow @safe @nogc { 6593 if (modmask == 0) return false; 6594 if (modmask&(KeyOrButtonMask.LockMask|KeyOrButtonMask.Mod2Mask|KeyOrButtonMask.Mod3Mask)) return false; 6595 if (modmask&~(KeyOrButtonMask.Mod5Mask-1)) return false; 6596 return true; 6597 } 6598 6599 private static uint cleanupModifiers (uint modmask) pure nothrow @safe @nogc { 6600 modmask &= ~(KeyOrButtonMask.LockMask|KeyOrButtonMask.Mod2Mask|KeyOrButtonMask.Mod3Mask); // remove caps, num, scroll 6601 modmask &= (KeyOrButtonMask.Mod5Mask-1); // and other modifiers 6602 return modmask; 6603 } 6604 6605 private static uint keyEvent2KeyCode() (scope auto ref const KeyEvent ke) { 6606 uint keycode = cast(uint)ke.key; 6607 auto dpy = XDisplayConnection.get; 6608 return XKeysymToKeycode(dpy, keycode); 6609 } 6610 6611 private static ulong keyCode2Hash() (uint keycode, uint modstate) pure nothrow @safe @nogc { return ((cast(ulong)modstate)<<32)|keycode; } 6612 6613 private __gshared GlobalHotkey[ulong] globalHotkeyList; 6614 6615 NativeEventHandler getNativeEventHandler () { 6616 return delegate int (XEvent e) { 6617 if (e.type != EventType.KeyPress) return 1; 6618 auto kev = cast(const(XKeyEvent)*)&e; 6619 auto hash = keyCode2Hash(e.xkey.keycode, cleanupModifiers(e.xkey.state)); 6620 if (auto ghkp = hash in globalHotkeyList) { 6621 try { 6622 ghkp.doHandle(); 6623 } catch (Exception e) { 6624 import core.stdc.stdio : stderr, fprintf; 6625 stderr.fprintf("HOTKEY HANDLER EXCEPTION: %.*s", cast(uint)e.msg.length, e.msg.ptr); 6626 } 6627 } 6628 return 1; 6629 }; 6630 } 6631 6632 private this () { 6633 auto dpy = XDisplayConnection.get; 6634 auto root = RootWindow(dpy, DefaultScreen(dpy)); 6635 CapableOfHandlingNativeEvent.nativeHandleMapping[root] = this; 6636 XDisplayConnection.addRootInput(EventMask.KeyPressMask); 6637 } 6638 6639 /// Register new global hotkey with initialized `GlobalHotkey` object. 6640 /// This function will throw if it failed to register hotkey (i.e. hotkey is invalid or already taken). 6641 static void register (GlobalHotkey gh) { 6642 if (gh is null) return; 6643 if (gh.key.key == 0 || !isGoodModifierMask(gh.key.modifierState)) throw new Exception("invalid global hotkey"); 6644 6645 auto dpy = XDisplayConnection.get; 6646 immutable keycode = keyEvent2KeyCode(gh.key); 6647 6648 auto hash = keyCode2Hash(keycode, gh.key.modifierState); 6649 if (hash in globalHotkeyList) throw new Exception("duplicate global hotkey"); 6650 if (ghmanager is null) ghmanager = new GlobalHotkeyManager(); 6651 XSync(dpy, 0/*False*/); 6652 6653 Window root = RootWindow(dpy, DefaultScreen(dpy)); 6654 XErrorHandler savedErrorHandler = XSetErrorHandler(&XGrabErrorHandler); 6655 ghfailed = false; 6656 foreach (immutable uint ormask; masklist[]) { 6657 XGrabKey(dpy, keycode, gh.key.modifierState|ormask, /*grab_window*/root, /*owner_events*/0/*False*/, GrabMode.GrabModeAsync, GrabMode.GrabModeAsync); 6658 } 6659 XSync(dpy, 0/*False*/); 6660 XSetErrorHandler(savedErrorHandler); 6661 6662 if (ghfailed) { 6663 savedErrorHandler = XSetErrorHandler(&XGrabErrorHandler); 6664 foreach (immutable uint ormask; masklist[]) XUngrabKey(dpy, keycode, gh.key.modifierState|ormask, /*grab_window*/root); 6665 XSync(dpy, 0/*False*/); 6666 XSetErrorHandler(savedErrorHandler); 6667 throw new Exception("cannot register global hotkey"); 6668 } 6669 6670 globalHotkeyList[hash] = gh; 6671 } 6672 6673 /// Ditto 6674 static void register (const(char)[] akey, void delegate () ahandler) { 6675 register(new GlobalHotkey(akey, ahandler)); 6676 } 6677 6678 private static void removeByHash (ulong hash) { 6679 if (auto ghp = hash in globalHotkeyList) { 6680 auto dpy = XDisplayConnection.get; 6681 immutable keycode = keyEvent2KeyCode(ghp.key); 6682 Window root = RootWindow(dpy, DefaultScreen(dpy)); 6683 XSync(dpy, 0/*False*/); 6684 XErrorHandler savedErrorHandler = XSetErrorHandler(&XGrabErrorHandler); 6685 foreach (immutable uint ormask; masklist[]) XUngrabKey(dpy, keycode, ghp.key.modifierState|ormask, /*grab_window*/root); 6686 XSync(dpy, 0/*False*/); 6687 XSetErrorHandler(savedErrorHandler); 6688 globalHotkeyList.remove(hash); 6689 } 6690 } 6691 6692 /// Register new global hotkey with previously used `GlobalHotkey` object. 6693 /// It is safe to unregister unknown or invalid hotkey. 6694 static void unregister (GlobalHotkey gh) { 6695 //TODO: add second AA for faster search? prolly doesn't worth it. 6696 if (gh is null) return; 6697 foreach (const ref kv; globalHotkeyList.byKeyValue) { 6698 if (kv.value is gh) { 6699 removeByHash(kv.key); 6700 return; 6701 } 6702 } 6703 } 6704 6705 /// Ditto. 6706 static void unregister (const(char)[] key) { 6707 auto kev = KeyEvent.parse(key); 6708 immutable keycode = keyEvent2KeyCode(kev); 6709 removeByHash(keyCode2Hash(keycode, kev.modifierState)); 6710 } 6711 } 6712 } 6713 6714 version(Windows) { 6715 /++ 6716 See [SyntheticInput.sendSyntheticInput] instead for cross-platform applications. 6717 6718 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). 6719 +/ 6720 void sendSyntheticInput(wstring s) { 6721 INPUT[] inputs; 6722 inputs.reserve(s.length * 2); 6723 6724 foreach(wchar c; s) { 6725 INPUT input; 6726 input.type = INPUT_KEYBOARD; 6727 input.ki.wScan = c; 6728 input.ki.dwFlags = KEYEVENTF_UNICODE; 6729 inputs ~= input; 6730 6731 input.ki.dwFlags |= KEYEVENTF_KEYUP; 6732 inputs ~= input; 6733 } 6734 6735 if(SendInput(cast(int) inputs.length, inputs.ptr, INPUT.sizeof) != inputs.length) { 6736 throw new WindowsApiException("SendInput", GetLastError()); 6737 } 6738 6739 } 6740 6741 6742 // global hotkey helper function 6743 6744 /// Platform-specific for Windows. Registers a global hotkey. Returns a registration ID. See [GlobalHotkeyManager] for Linux. Maybe some day I will merge these. 6745 int registerHotKey(SimpleWindow window, UINT modifiers, UINT vk, void delegate() handler) { 6746 __gshared int hotkeyId = 0; 6747 int id = ++hotkeyId; 6748 if(!RegisterHotKey(window.impl.hwnd, id, modifiers, vk)) 6749 throw new Exception("RegisterHotKey"); 6750 6751 __gshared void delegate()[WPARAM][HWND] handlers; 6752 6753 handlers[window.impl.hwnd][id] = handler; 6754 6755 int delegate(HWND, UINT, WPARAM, LPARAM, out int) oldHandler; 6756 6757 auto nativeEventHandler = delegate int(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam, out int mustReturn) { 6758 switch(msg) { 6759 // http://msdn.microsoft.com/en-us/library/windows/desktop/ms646279%28v=vs.85%29.aspx 6760 case WM_HOTKEY: 6761 if(auto list = hwnd in handlers) { 6762 if(auto h = wParam in *list) { 6763 (*h)(); 6764 return 0; 6765 } 6766 } 6767 goto default; 6768 default: 6769 } 6770 if(oldHandler) 6771 return oldHandler(hwnd, msg, wParam, lParam, mustReturn); 6772 return 1; // pass it on 6773 }; 6774 6775 if(window.handleNativeEvent.funcptr !is nativeEventHandler.funcptr) { 6776 oldHandler = window.handleNativeEvent; 6777 window.handleNativeEvent = nativeEventHandler; 6778 } 6779 6780 return id; 6781 } 6782 6783 /// Platform-specific for Windows. Unregisters a key. The id is the value returned by [registerHotKey]. 6784 void unregisterHotKey(SimpleWindow window, int id) { 6785 if(!UnregisterHotKey(window.impl.hwnd, id)) 6786 throw new WindowsApiException("UnregisterHotKey", GetLastError()); 6787 } 6788 } 6789 6790 version (X11) { 6791 pragma(lib, "dl"); 6792 import core.sys.posix.dlfcn; 6793 } 6794 6795 /++ 6796 Allows for sending synthetic input to the X server via the Xtst 6797 extension or on Windows using SendInput. 6798 6799 Please remember user input is meant to be user - don't use this 6800 if you have some other alternative! 6801 6802 History: 6803 Added May 17, 2020 with the X implementation. 6804 6805 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.) 6806 Bugs: 6807 All methods on OSX Cocoa will throw not yet implemented exceptions. 6808 +/ 6809 struct SyntheticInput { 6810 @disable this(); 6811 6812 private int* refcount; 6813 6814 version(X11) { 6815 private void* lib; 6816 6817 private extern(C) { 6818 void function(Display*, uint keycode, bool press, arch_ulong delay) XTestFakeKeyEvent; 6819 void function(Display*, uint button, bool press, arch_ulong delay) XTestFakeButtonEvent; 6820 } 6821 } 6822 6823 /// The dummy param must be 0. 6824 this(int dummy) { 6825 version(X11) { 6826 lib = dlopen("libXtst.so", RTLD_NOW); 6827 if(lib is null) 6828 throw new Exception("cannot load xtest lib extension"); 6829 scope(failure) 6830 dlclose(lib); 6831 6832 XTestFakeButtonEvent = cast(typeof(XTestFakeButtonEvent)) dlsym(lib, "XTestFakeButtonEvent"); 6833 XTestFakeKeyEvent = cast(typeof(XTestFakeKeyEvent)) dlsym(lib, "XTestFakeKeyEvent"); 6834 6835 if(XTestFakeKeyEvent is null) 6836 throw new Exception("No XTestFakeKeyEvent"); 6837 if(XTestFakeButtonEvent is null) 6838 throw new Exception("No XTestFakeButtonEvent"); 6839 } 6840 6841 refcount = new int; 6842 *refcount = 1; 6843 } 6844 6845 this(this) { 6846 if(refcount) 6847 *refcount += 1; 6848 } 6849 6850 ~this() { 6851 if(refcount) { 6852 *refcount -= 1; 6853 if(*refcount == 0) 6854 // I commented this because if I close the lib before 6855 // XCloseDisplay, it is liable to segfault... so just 6856 // gonna keep it loaded if it is loaded, no big deal 6857 // anyway. 6858 {} // dlclose(lib); 6859 } 6860 } 6861 6862 /++ 6863 Simulates typing a string into the keyboard. 6864 6865 Bugs: 6866 On X11, this ONLY works with basic ascii! On Windows, it can handle more. 6867 6868 Not implemented except on Windows and X11. 6869 +/ 6870 void sendSyntheticInput(string s) { 6871 version(Windows) { 6872 INPUT[] inputs; 6873 inputs.reserve(s.length * 2); 6874 6875 auto ei = GetMessageExtraInfo(); 6876 6877 foreach(wchar c; s) { 6878 INPUT input; 6879 input.type = INPUT_KEYBOARD; 6880 input.ki.wScan = c; 6881 input.ki.dwFlags = KEYEVENTF_UNICODE; 6882 input.ki.dwExtraInfo = ei; 6883 inputs ~= input; 6884 6885 input.ki.dwFlags |= KEYEVENTF_KEYUP; 6886 inputs ~= input; 6887 } 6888 6889 if(SendInput(cast(int) inputs.length, inputs.ptr, INPUT.sizeof) != inputs.length) { 6890 throw new WindowsApiException("SendInput", GetLastError()); 6891 } 6892 } else version(X11) { 6893 int delay = 0; 6894 foreach(ch; s) { 6895 pressKey(cast(Key) ch, true, delay); 6896 pressKey(cast(Key) ch, false, delay); 6897 delay += 5; 6898 } 6899 } else throw new NotYetImplementedException(); 6900 } 6901 6902 /++ 6903 Sends a fake press or release key event. 6904 6905 Please note you need to call [flushGui] or return to the event loop for this to actually be sent on X11. 6906 6907 Bugs: 6908 The `delay` parameter is not implemented yet on Windows. 6909 6910 Not implemented except on Windows and X11. 6911 +/ 6912 void pressKey(Key key, bool pressed, int delay = 0) { 6913 version(Windows) { 6914 INPUT input; 6915 input.type = INPUT_KEYBOARD; 6916 input.ki.wVk = cast(ushort) key; 6917 6918 input.ki.dwFlags = pressed ? 0 : KEYEVENTF_KEYUP; 6919 input.ki.dwExtraInfo = GetMessageExtraInfo(); 6920 6921 if(SendInput(1, &input, INPUT.sizeof) != 1) { 6922 throw new WindowsApiException("SendInput", GetLastError()); 6923 } 6924 } else version(X11) { 6925 XTestFakeKeyEvent(XDisplayConnection.get, XKeysymToKeycode(XDisplayConnection.get, key), pressed, delay + pressed ? 0 : 5); 6926 } else throw new NotYetImplementedException(); 6927 } 6928 6929 /++ 6930 Sends a fake mouse button press or release event. 6931 6932 Please note you need to call [flushGui] or return to the event loop for this to actually be sent on X11. 6933 6934 `pressed` param must be `true` if button is `wheelUp` or `wheelDown`. 6935 6936 Bugs: 6937 The `delay` parameter is not implemented yet on Windows. 6938 6939 The backButton and forwardButton will throw NotYetImplementedException on Windows. 6940 6941 All arguments will throw NotYetImplementedException on OSX Cocoa. 6942 +/ 6943 void pressMouseButton(MouseButton button, bool pressed, int delay = 0) { 6944 version(Windows) { 6945 INPUT input; 6946 input.type = INPUT_MOUSE; 6947 input.mi.dwExtraInfo = GetMessageExtraInfo(); 6948 6949 // input.mi.mouseData for a wheel event 6950 6951 switch(button) { 6952 case MouseButton.left: input.mi.dwFlags = pressed ? MOUSEEVENTF_LEFTDOWN : MOUSEEVENTF_LEFTUP; break; 6953 case MouseButton.middle: input.mi.dwFlags = pressed ? MOUSEEVENTF_MIDDLEDOWN : MOUSEEVENTF_MIDDLEUP; break; 6954 case MouseButton.right: input.mi.dwFlags = pressed ? MOUSEEVENTF_RIGHTDOWN : MOUSEEVENTF_RIGHTUP; break; 6955 case MouseButton.wheelUp: 6956 case MouseButton.wheelDown: 6957 input.mi.dwFlags = MOUSEEVENTF_WHEEL; 6958 input.mi.mouseData = button == MouseButton.wheelUp ? 120 : -120; 6959 break; 6960 case MouseButton.backButton: throw new NotYetImplementedException(); 6961 case MouseButton.forwardButton: throw new NotYetImplementedException(); 6962 default: 6963 } 6964 6965 if(SendInput(1, &input, INPUT.sizeof) != 1) { 6966 throw new WindowsApiException("SendInput", GetLastError()); 6967 } 6968 } else version(X11) { 6969 int btn; 6970 6971 switch(button) { 6972 case MouseButton.left: btn = 1; break; 6973 case MouseButton.middle: btn = 2; break; 6974 case MouseButton.right: btn = 3; break; 6975 case MouseButton.wheelUp: btn = 4; break; 6976 case MouseButton.wheelDown: btn = 5; break; 6977 case MouseButton.backButton: btn = 8; break; 6978 case MouseButton.forwardButton: btn = 9; break; 6979 default: 6980 } 6981 6982 assert(btn); 6983 6984 XTestFakeButtonEvent(XDisplayConnection.get, btn, pressed, delay); 6985 } else throw new NotYetImplementedException(); 6986 } 6987 6988 /// 6989 static void moveMouseArrowBy(int dx, int dy) { 6990 version(Windows) { 6991 INPUT input; 6992 input.type = INPUT_MOUSE; 6993 input.mi.dwExtraInfo = GetMessageExtraInfo(); 6994 input.mi.dx = dx; 6995 input.mi.dy = dy; 6996 input.mi.dwFlags = MOUSEEVENTF_MOVE; 6997 6998 if(SendInput(1, &input, INPUT.sizeof) != 1) { 6999 throw new WindowsApiException("SendInput", GetLastError()); 7000 } 7001 } else version(X11) { 7002 auto disp = XDisplayConnection.get(); 7003 XWarpPointer(disp, None, None, 0, 0, 0, 0, dx, dy); 7004 XFlush(disp); 7005 } else throw new NotYetImplementedException(); 7006 } 7007 7008 /// 7009 static void moveMouseArrowTo(int x, int y) { 7010 version(Windows) { 7011 INPUT input; 7012 input.type = INPUT_MOUSE; 7013 input.mi.dwExtraInfo = GetMessageExtraInfo(); 7014 input.mi.dx = x; 7015 input.mi.dy = y; 7016 input.mi.dwFlags = MOUSEEVENTF_MOVE | MOUSEEVENTF_ABSOLUTE; 7017 7018 if(SendInput(1, &input, INPUT.sizeof) != 1) { 7019 throw new WindowsApiException("SendInput", GetLastError()); 7020 } 7021 } else version(X11) { 7022 auto disp = XDisplayConnection.get(); 7023 auto root = RootWindow(disp, DefaultScreen(disp)); 7024 XWarpPointer(disp, None, root, 0, 0, 0, 0, x, y); 7025 XFlush(disp); 7026 } else throw new NotYetImplementedException(); 7027 } 7028 } 7029 7030 7031 7032 /++ 7033 [ScreenPainter] operations can use different operations to combine the color with the color on screen. 7034 7035 See_Also: 7036 $(LIST 7037 *[ScreenPainter] 7038 *[ScreenPainter.rasterOp] 7039 ) 7040 +/ 7041 enum RasterOp { 7042 normal, /// Replaces the pixel. 7043 xor, /// Uses bitwise xor to draw. 7044 } 7045 7046 // being phobos-free keeps the size WAY down 7047 private const(char)* toStringz(string s) { return (s ~ '\0').ptr; } 7048 package(arsd) const(wchar)* toWStringz(wstring s) { return (s ~ '\0').ptr; } 7049 package(arsd) const(wchar)* toWStringz(string s) { 7050 wstring r; 7051 foreach(dchar c; s) 7052 r ~= c; 7053 r ~= '\0'; 7054 return r.ptr; 7055 } 7056 private string[] split(in void[] a, char c) { 7057 string[] ret; 7058 size_t previous = 0; 7059 foreach(i, char ch; cast(ubyte[]) a) { 7060 if(ch == c) { 7061 ret ~= cast(string) a[previous .. i]; 7062 previous = i + 1; 7063 } 7064 } 7065 if(previous != a.length) 7066 ret ~= cast(string) a[previous .. $]; 7067 return ret; 7068 } 7069 7070 version(without_opengl) { 7071 enum OpenGlOptions { 7072 no, 7073 } 7074 } else { 7075 /++ 7076 Determines if you want an OpenGL context created on the new window. 7077 7078 7079 See more: [#topics-3d|in the 3d topic]. 7080 7081 --- 7082 import arsd.simpledisplay; 7083 void main() { 7084 auto window = new SimpleWindow(500, 500, "OpenGL Test", OpenGlOptions.yes); 7085 7086 // Set up the matrix 7087 window.setAsCurrentOpenGlContext(); // make this window active 7088 7089 // This is called on each frame, we will draw our scene 7090 window.redrawOpenGlScene = delegate() { 7091 7092 }; 7093 7094 window.eventLoop(0); 7095 } 7096 --- 7097 +/ 7098 enum OpenGlOptions { 7099 no, /// No OpenGL context is created 7100 yes, /// Yes, create an OpenGL context 7101 } 7102 7103 version(X11) { 7104 static if (!SdpyIsUsingIVGLBinds) { 7105 7106 7107 struct __GLXFBConfigRec {} 7108 alias GLXFBConfig = __GLXFBConfigRec*; 7109 7110 //pragma(lib, "GL"); 7111 //pragma(lib, "GLU"); 7112 interface GLX { 7113 extern(C) nothrow @nogc { 7114 XVisualInfo* glXChooseVisual(Display *dpy, int screen, 7115 const int *attrib_list); 7116 7117 void glXCopyContext(Display *dpy, GLXContext src, 7118 GLXContext dst, arch_ulong mask); 7119 7120 GLXContext glXCreateContext(Display *dpy, XVisualInfo *vis, 7121 GLXContext share_list, Bool direct); 7122 7123 GLXPixmap glXCreateGLXPixmap(Display *dpy, XVisualInfo *vis, 7124 Pixmap pixmap); 7125 7126 void glXDestroyContext(Display *dpy, GLXContext ctx); 7127 7128 void glXDestroyGLXPixmap(Display *dpy, GLXPixmap pix); 7129 7130 int glXGetConfig(Display *dpy, XVisualInfo *vis, 7131 int attrib, int *value); 7132 7133 GLXContext glXGetCurrentContext(); 7134 7135 GLXDrawable glXGetCurrentDrawable(); 7136 7137 Bool glXIsDirect(Display *dpy, GLXContext ctx); 7138 7139 Bool glXMakeCurrent(Display *dpy, GLXDrawable drawable, 7140 GLXContext ctx); 7141 7142 Bool glXQueryExtension(Display *dpy, int *error_base, int *event_base); 7143 7144 Bool glXQueryVersion(Display *dpy, int *major, int *minor); 7145 7146 void glXSwapBuffers(Display *dpy, GLXDrawable drawable); 7147 7148 void glXUseXFont(Font font, int first, int count, int list_base); 7149 7150 void glXWaitGL(); 7151 7152 void glXWaitX(); 7153 7154 7155 GLXFBConfig* glXChooseFBConfig (Display*, int, int*, int*); 7156 int glXGetFBConfigAttrib (Display*, GLXFBConfig, int, int*); 7157 XVisualInfo* glXGetVisualFromFBConfig (Display*, GLXFBConfig); 7158 7159 char* glXQueryExtensionsString (Display*, int); 7160 void* glXGetProcAddress (const(char)*); 7161 7162 } 7163 } 7164 7165 version(OSX) 7166 mixin DynamicLoad!(GLX, "GL", 0, openGlLibrariesSuccessfullyLoaded) glx; 7167 else 7168 mixin DynamicLoad!(GLX, "GLX", 0, openGlLibrariesSuccessfullyLoaded) glx; 7169 shared static this() { 7170 glx.loadDynamicLibrary(); 7171 } 7172 7173 alias glbindGetProcAddress = glXGetProcAddress; 7174 } 7175 } else version(Windows) { 7176 /* it is done below by interface GL */ 7177 } else 7178 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."); 7179 } 7180 7181 deprecated("Sorry, I misspelled it in the first version! Use `Resizability` instead.") 7182 alias Resizablity = Resizability; 7183 7184 /// When you create a SimpleWindow, you can see its resizability to be one of these via the constructor... 7185 enum Resizability { 7186 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. 7187 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. 7188 /++ 7189 $(PITFALL 7190 Planned for the future but not implemented. 7191 ) 7192 7193 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. 7194 7195 History: 7196 Added November 11, 2022, but not yet implemented and may not be for some time. 7197 +/ 7198 /*@__future*/ allowResizingMaintainingAspectRatio, 7199 /++ 7200 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. 7201 7202 History: 7203 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. 7204 7205 Your programs should not be affected, as they will continue to function as if the user simply never resized the window at all. 7206 +/ 7207 automaticallyScaleIfPossible, 7208 } 7209 /// ditto 7210 alias Resizeability = Resizability; 7211 7212 7213 /++ 7214 Alignment for [ScreenPainter.drawText]. Left, Center, or Right may be combined with VerticalTop, VerticalCenter, or VerticalBottom via bitwise or. 7215 +/ 7216 enum TextAlignment : uint { 7217 Left = 0, /// 7218 Center = 1, /// 7219 Right = 2, /// 7220 7221 VerticalTop = 0, /// 7222 VerticalCenter = 4, /// 7223 VerticalBottom = 8, /// 7224 } 7225 7226 public import arsd.color; // no longer stand alone... :-( but i need a common type for this to work with images easily. 7227 alias Rectangle = arsd.color.Rectangle; 7228 7229 7230 /++ 7231 Keyboard press and release events. 7232 +/ 7233 struct KeyEvent { 7234 /// see table below. Always use the symbolic names, even for ASCII characters, since the actual numbers vary across platforms. See [Key] 7235 Key key; 7236 ubyte hardwareCode; /// A platform and hardware specific code for the key 7237 bool pressed; /// true if the key was just pressed, false if it was just released. note: released events aren't always sent... 7238 7239 deprecated("This never actually worked anyway, you should do a character event handler instead.") dchar character; 7240 7241 uint modifierState; /// see enum [ModifierState]. They are bitwise combined together. 7242 7243 SimpleWindow window; /// associated Window 7244 7245 /++ 7246 A view into the upcoming buffer holding coming character events that are sent if and only if neither 7247 the alt or super modifier keys are pressed (check this with `!(modifierState & (ModifierState.window | ModifierState.alt))` 7248 to predict if char events are actually coming.. 7249 7250 Only available on X systems since this information is not given ahead of time elsewhere. 7251 (Well, you COULD probably dig it up, but as far as I know right now, it isn't terribly pretty.) 7252 7253 I'm adding this because it is useful to the terminal emulator, but given its platform specificness 7254 and potential quirks I'd recommend avoiding it. 7255 7256 History: 7257 Added April 26, 2021 (dub v9.5) 7258 +/ 7259 version(X11) 7260 dchar[] charsPossible; 7261 7262 // convert key event to simplified string representation a-la emacs 7263 const(char)[] toStrBuf(bool growdest=false) (char[] dest) const nothrow @trusted { 7264 uint dpos = 0; 7265 void put (const(char)[] s...) nothrow @trusted { 7266 static if (growdest) { 7267 foreach (char ch; s) if (dpos < dest.length) dest.ptr[dpos++] = ch; else { dest ~= ch; ++dpos; } 7268 } else { 7269 foreach (char ch; s) if (dpos < dest.length) dest.ptr[dpos++] = ch; 7270 } 7271 } 7272 7273 void putMod (ModifierState mod, Key key, string text) nothrow @trusted { 7274 if ((this.modifierState&mod) != 0 && (this.pressed || this.key != key)) put(text); 7275 } 7276 7277 if (!this.key && !(this.modifierState&(ModifierState.ctrl|ModifierState.alt|ModifierState.shift|ModifierState.windows))) return null; 7278 7279 // put modifiers 7280 // releasing modifier keys can produce bizarre things like "Ctrl+Ctrl", so hack around it 7281 putMod(ModifierState.ctrl, Key.Ctrl, "Ctrl+"); 7282 putMod(ModifierState.alt, Key.Alt, "Alt+"); 7283 putMod(ModifierState.windows, Key.Shift, "Windows+"); 7284 putMod(ModifierState.shift, Key.Shift, "Shift+"); 7285 7286 if (this.key) { 7287 foreach (string kn; __traits(allMembers, Key)) { 7288 if (this.key == __traits(getMember, Key, kn)) { 7289 // HACK! 7290 static if (kn == "N0") put("0"); 7291 else static if (kn == "N1") put("1"); 7292 else static if (kn == "N2") put("2"); 7293 else static if (kn == "N3") put("3"); 7294 else static if (kn == "N4") put("4"); 7295 else static if (kn == "N5") put("5"); 7296 else static if (kn == "N6") put("6"); 7297 else static if (kn == "N7") put("7"); 7298 else static if (kn == "N8") put("8"); 7299 else static if (kn == "N9") put("9"); 7300 else put(kn); 7301 return dest[0..dpos]; 7302 } 7303 } 7304 put("Unknown"); 7305 } else { 7306 if (dpos && dest[dpos-1] == '+') --dpos; 7307 } 7308 return dest[0..dpos]; 7309 } 7310 7311 string toStr() () { return cast(string)toStrBuf!true(null); } // it is safe to cast here 7312 7313 /** Parse string into key name with modifiers. It accepts things like: 7314 * 7315 * C-H-1 -- emacs style (ctrl, and windows, and 1) 7316 * 7317 * Ctrl+Win+1 -- windows style 7318 * 7319 * Ctrl-Win-1 -- '-' is a valid delimiter too 7320 * 7321 * Ctrl Win 1 -- and space 7322 * 7323 * and even "Win + 1 + Ctrl". 7324 */ 7325 static KeyEvent parse (const(char)[] name, bool* ignoreModsOut=null, int* updown=null) nothrow @trusted @nogc { 7326 auto nanchor = name; // keep it anchored, 'cause `name` may have NO_INTERIOR set 7327 7328 // remove trailing spaces 7329 while (name.length && name[$-1] <= ' ') name = name[0..$-1]; 7330 7331 // tokens delimited by blank, '+', or '-' 7332 // null on eol 7333 const(char)[] getToken () nothrow @trusted @nogc { 7334 // remove leading spaces and delimiters 7335 while (name.length && (name[0] <= ' ' || name[0] == '+' || name[0] == '-')) name = name[1..$]; 7336 if (name.length == 0) return null; // oops, no more tokens 7337 // get token 7338 size_t epos = 0; 7339 while (epos < name.length && name[epos] > ' ' && name[epos] != '+' && name[epos] != '-') ++epos; 7340 assert(epos > 0 && epos <= name.length); 7341 auto res = name[0..epos]; 7342 name = name[epos..$]; 7343 return res; 7344 } 7345 7346 static bool strEquCI (const(char)[] s0, const(char)[] s1) pure nothrow @trusted @nogc { 7347 if (s0.length != s1.length) return false; 7348 foreach (immutable ci, char c0; s0) { 7349 if (c0 >= 'A' && c0 <= 'Z') c0 += 32; // poor man's tolower 7350 char c1 = s1[ci]; 7351 if (c1 >= 'A' && c1 <= 'Z') c1 += 32; // poor man's tolower 7352 if (c0 != c1) return false; 7353 } 7354 return true; 7355 } 7356 7357 if (ignoreModsOut !is null) *ignoreModsOut = false; 7358 if (updown !is null) *updown = -1; 7359 KeyEvent res; 7360 res.key = cast(Key)0; // just in case 7361 const(char)[] tk, tkn; // last token 7362 bool allowEmascStyle = true; 7363 bool ignoreModifiers = false; 7364 tokenloop: for (;;) { 7365 tk = tkn; 7366 tkn = getToken(); 7367 //k8: yay, i took "Bloody Mess" trait from Fallout! 7368 if (tkn.length != 0 && tk.length == 0) { tk = tkn; continue tokenloop; } 7369 if (tkn.length == 0 && tk.length == 0) break; // no more tokens 7370 if (allowEmascStyle && tkn.length != 0) { 7371 if (tk.length == 1) { 7372 char mdc = tk[0]; 7373 if (mdc >= 'a' && mdc <= 'z') mdc -= 32; // poor man's toupper() 7374 if (mdc == 'C' && (res.modifierState&ModifierState.ctrl) == 0) {res.modifierState |= ModifierState.ctrl; continue tokenloop; } 7375 if (mdc == 'M' && (res.modifierState&ModifierState.alt) == 0) { res.modifierState |= ModifierState.alt; continue tokenloop; } 7376 if (mdc == 'H' && (res.modifierState&ModifierState.windows) == 0) { res.modifierState |= ModifierState.windows; continue tokenloop; } 7377 if (mdc == 'S' && (res.modifierState&ModifierState.shift) == 0) { res.modifierState |= ModifierState.shift; continue tokenloop; } 7378 if (mdc == '*') { ignoreModifiers = true; continue tokenloop; } 7379 if (mdc == 'U' || mdc == 'R') { if (updown !is null) *updown = 0; continue tokenloop; } 7380 if (mdc == 'D' || mdc == 'P') { if (updown !is null) *updown = 1; continue tokenloop; } 7381 } 7382 } 7383 allowEmascStyle = false; 7384 if (strEquCI(tk, "Ctrl")) { res.modifierState |= ModifierState.ctrl; continue tokenloop; } 7385 if (strEquCI(tk, "Alt")) { res.modifierState |= ModifierState.alt; continue tokenloop; } 7386 if (strEquCI(tk, "Win") || strEquCI(tk, "Windows")) { res.modifierState |= ModifierState.windows; continue tokenloop; } 7387 if (strEquCI(tk, "Shift")) { res.modifierState |= ModifierState.shift; continue tokenloop; } 7388 if (strEquCI(tk, "Release")) { if (updown !is null) *updown = 0; continue tokenloop; } 7389 if (strEquCI(tk, "Press")) { if (updown !is null) *updown = 1; continue tokenloop; } 7390 if (tk == "*") { ignoreModifiers = true; continue tokenloop; } 7391 if (tk.length == 0) continue; 7392 // try key name 7393 if (res.key == 0) { 7394 // little hack 7395 if (tk.length == 1 && tk[0] >= '0' && tk[0] <= '9') { 7396 final switch (tk[0]) { 7397 case '0': tk = "N0"; break; 7398 case '1': tk = "N1"; break; 7399 case '2': tk = "N2"; break; 7400 case '3': tk = "N3"; break; 7401 case '4': tk = "N4"; break; 7402 case '5': tk = "N5"; break; 7403 case '6': tk = "N6"; break; 7404 case '7': tk = "N7"; break; 7405 case '8': tk = "N8"; break; 7406 case '9': tk = "N9"; break; 7407 } 7408 } 7409 foreach (string kn; __traits(allMembers, Key)) { 7410 if (strEquCI(tk, kn)) { res.key = __traits(getMember, Key, kn); continue tokenloop; } 7411 } 7412 } 7413 // unknown or duplicate key name, get out of here 7414 break; 7415 } 7416 if (ignoreModsOut !is null) *ignoreModsOut = ignoreModifiers; 7417 return res; // something 7418 } 7419 7420 bool opEquals() (const(char)[] name) const nothrow @trusted @nogc { 7421 enum modmask = (ModifierState.ctrl|ModifierState.alt|ModifierState.shift|ModifierState.windows); 7422 void doModKey (ref uint mask, ref Key kk, Key k, ModifierState mst) { 7423 if (kk == k) { mask |= mst; kk = cast(Key)0; } 7424 } 7425 bool ignoreMods; 7426 int updown; 7427 auto ke = KeyEvent.parse(name, &ignoreMods, &updown); 7428 if ((updown == 0 && this.pressed) || (updown == 1 && !this.pressed)) return false; 7429 if (this.key != ke.key) { 7430 // things like "ctrl+alt" are complicated 7431 uint tkm = this.modifierState&modmask; 7432 uint kkm = ke.modifierState&modmask; 7433 Key tk = this.key; 7434 // ke 7435 doModKey(kkm, ke.key, Key.Ctrl, ModifierState.ctrl); 7436 doModKey(kkm, ke.key, Key.Alt, ModifierState.alt); 7437 doModKey(kkm, ke.key, Key.Windows, ModifierState.windows); 7438 doModKey(kkm, ke.key, Key.Shift, ModifierState.shift); 7439 // this 7440 doModKey(tkm, tk, Key.Ctrl, ModifierState.ctrl); 7441 doModKey(tkm, tk, Key.Alt, ModifierState.alt); 7442 doModKey(tkm, tk, Key.Windows, ModifierState.windows); 7443 doModKey(tkm, tk, Key.Shift, ModifierState.shift); 7444 return (tk == ke.key && tkm == kkm); 7445 } 7446 return (ignoreMods || ((this.modifierState&modmask) == (ke.modifierState&modmask))); 7447 } 7448 } 7449 7450 /// Sets the application name. 7451 @property string ApplicationName(string name) { 7452 return _applicationName = name; 7453 } 7454 7455 string _applicationName; 7456 7457 /// ditto 7458 @property string ApplicationName() { 7459 if(_applicationName is null) { 7460 import core.runtime; 7461 return Runtime.args[0]; 7462 } 7463 return _applicationName; 7464 } 7465 7466 7467 /// Type of a [MouseEvent]. 7468 enum MouseEventType : int { 7469 motion = 0, /// The mouse moved inside the window 7470 buttonPressed = 1, /// A mouse button was pressed or the wheel was spun 7471 buttonReleased = 2, /// A mouse button was released 7472 } 7473 7474 // FIXME: mouse move should be distinct from presses+releases, so we can avoid subscribing to those events in X unnecessarily 7475 /++ 7476 Listen for this on your event listeners if you are interested in mouse action. 7477 7478 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. 7479 7480 Examples: 7481 7482 This will draw boxes on the window with the mouse as you hold the left button. 7483 --- 7484 import arsd.simpledisplay; 7485 7486 void main() { 7487 auto window = new SimpleWindow(); 7488 7489 window.eventLoop(0, 7490 (MouseEvent ev) { 7491 if(ev.modifierState & ModifierState.leftButtonDown) { 7492 auto painter = window.draw(); 7493 painter.fillColor = Color.red; 7494 painter.outlineColor = Color.black; 7495 painter.drawRectangle(Point(ev.x / 16 * 16, ev.y / 16 * 16), 16, 16); 7496 } 7497 } 7498 ); 7499 } 7500 --- 7501 +/ 7502 struct MouseEvent { 7503 MouseEventType type; /// movement, press, release, double click. See [MouseEventType] 7504 7505 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. 7506 int y; /// Current Y position of the cursor when the event fired. 7507 7508 int dx; /// Change in X position since last report 7509 int dy; /// Change in Y position since last report 7510 7511 MouseButton button; /// See [MouseButton] 7512 int modifierState; /// See [ModifierState] 7513 7514 version(X11) 7515 private Time timestamp; 7516 7517 /// Returns a linear representation of mouse button, 7518 /// for use with static arrays. Guaranteed to be >= 0 && <= 15 7519 /// 7520 /// Its implementation is based on range-limiting `core.bitop.bsf(button) + 1`. 7521 @property ubyte buttonLinear() const { 7522 import core.bitop; 7523 if(button == 0) 7524 return 0; 7525 return (bsf(button) + 1) & 0b1111; 7526 } 7527 7528 bool doubleClick; /// was it a double click? Only set on type == [MouseEventType.buttonPressed] 7529 7530 SimpleWindow window; /// The window in which the event happened. 7531 7532 Point globalCoordinates() { 7533 Point p; 7534 if(window is null) 7535 throw new Exception("wtf"); 7536 static if(UsingSimpledisplayX11) { 7537 Window child; 7538 XTranslateCoordinates( 7539 XDisplayConnection.get, 7540 window.impl.window, 7541 RootWindow(XDisplayConnection.get, DefaultScreen(XDisplayConnection.get)), 7542 x, y, &p.x, &p.y, &child); 7543 return p; 7544 } else version(Windows) { 7545 POINT[1] points; 7546 points[0].x = x; 7547 points[0].y = y; 7548 MapWindowPoints( 7549 window.impl.hwnd, 7550 null, 7551 points.ptr, 7552 points.length 7553 ); 7554 p.x = points[0].x; 7555 p.y = points[0].y; 7556 7557 return p; 7558 } else version(OSXCocoa) { 7559 throw new NotYetImplementedException(); 7560 } else static assert(0); 7561 } 7562 7563 bool opEquals() (const(char)[] str) pure nothrow @trusted @nogc { return equStr(this, str); } 7564 7565 /** 7566 can contain emacs-like modifier prefix 7567 case-insensitive names: 7568 lmbX/leftX 7569 rmbX/rightX 7570 mmbX/middleX 7571 wheelX 7572 motion (no prefix allowed) 7573 'X' is either "up" or "down" (or "-up"/"-down"); if omited, means "down" 7574 */ 7575 static bool equStr() (scope auto ref const MouseEvent event, const(char)[] str) pure nothrow @trusted @nogc { 7576 if (str.length == 0) return false; // just in case 7577 debug(arsd_mevent_strcmp) { import iv.cmdcon; conwriteln("str=<", str, ">"); } 7578 enum Flag : uint { Up = 0x8000_0000U, Down = 0x4000_0000U, Any = 0x1000_0000U } 7579 auto anchor = str; 7580 uint mods = 0; // uint.max == any 7581 // interesting bits in kmod 7582 uint kmodmask = 7583 ModifierState.shift| 7584 ModifierState.ctrl| 7585 ModifierState.alt| 7586 ModifierState.windows| 7587 ModifierState.leftButtonDown| 7588 ModifierState.middleButtonDown| 7589 ModifierState.rightButtonDown| 7590 0; 7591 uint lastButt = uint.max; // otherwise, bit 31 means "down" 7592 bool wasButtons = false; 7593 while (str.length) { 7594 if (str.ptr[0] <= ' ') { 7595 while (str.length && str.ptr[0] <= ' ') str = str[1..$]; 7596 continue; 7597 } 7598 // one-letter modifier? 7599 if (str.length >= 2 && str.ptr[1] == '-') { 7600 switch (str.ptr[0]) { 7601 case '*': // "any" modifier (cannot be undone) 7602 mods = mods.max; 7603 break; 7604 case 'C': case 'c': // emacs "ctrl" 7605 if (mods != mods.max) mods |= ModifierState.ctrl; 7606 break; 7607 case 'M': case 'm': // emacs "meta" 7608 if (mods != mods.max) mods |= ModifierState.alt; 7609 break; 7610 case 'S': case 's': // emacs "shift" 7611 if (mods != mods.max) mods |= ModifierState.shift; 7612 break; 7613 case 'H': case 'h': // emacs "hyper" (aka winkey) 7614 if (mods != mods.max) mods |= ModifierState.windows; 7615 break; 7616 default: 7617 return false; // unknown modifier 7618 } 7619 str = str[2..$]; 7620 continue; 7621 } 7622 // word 7623 char[16] buf = void; // locased 7624 auto wep = 0; 7625 while (str.length) { 7626 immutable char ch = str.ptr[0]; 7627 if (ch <= ' ' || ch == '-') break; 7628 str = str[1..$]; 7629 if (wep > buf.length) return false; // too long 7630 if (ch >= 'A' && ch <= 'Z') buf.ptr[wep++] = cast(char)(ch+32); // poor man tolower 7631 else if (ch >= 'a' && ch <= 'z') buf.ptr[wep++] = ch; 7632 else return false; // invalid char 7633 } 7634 if (wep == 0) return false; // just in case 7635 uint bnum; 7636 enum UpDown { None = -1, Up, Down, Any } 7637 auto updown = UpDown.None; // 0: up; 1: down 7638 switch (buf[0..wep]) { 7639 // left button 7640 case "lmbup": case "leftup": updown = UpDown.Up; goto case "lmb"; 7641 case "lmbdown": case "leftdown": updown = UpDown.Down; goto case "lmb"; 7642 case "lmbany": case "leftany": updown = UpDown.Any; goto case "lmb"; 7643 case "lmb": case "left": bnum = 0; break; 7644 // middle button 7645 case "mmbup": case "middleup": updown = UpDown.Up; goto case "mmb"; 7646 case "mmbdown": case "middledown": updown = UpDown.Down; goto case "mmb"; 7647 case "mmbany": case "middleany": updown = UpDown.Any; goto case "mmb"; 7648 case "mmb": case "middle": bnum = 1; break; 7649 // right button 7650 case "rmbup": case "rightup": updown = UpDown.Up; goto case "rmb"; 7651 case "rmbdown": case "rightdown": updown = UpDown.Down; goto case "rmb"; 7652 case "rmbany": case "rightany": updown = UpDown.Any; goto case "rmb"; 7653 case "rmb": case "right": bnum = 2; break; 7654 // wheel 7655 case "wheelup": updown = UpDown.Up; goto case "wheel"; 7656 case "wheeldown": updown = UpDown.Down; goto case "wheel"; 7657 case "wheelany": updown = UpDown.Any; goto case "wheel"; 7658 case "wheel": bnum = 3; break; 7659 // motion 7660 case "motion": bnum = 7; break; 7661 // unknown 7662 default: return false; 7663 } 7664 debug(arsd_mevent_strcmp) { import iv.cmdcon; conprintfln(" 0: mods=0x%08x; bnum=%u; updown=%s [%s]", mods, bnum, updown, str); } 7665 // parse possible "-up" or "-down" 7666 if (updown == UpDown.None && bnum < 7 && str.length > 0 && str.ptr[0] == '-') { 7667 wep = 0; 7668 foreach (immutable idx, immutable char ch; str[1..$]) { 7669 if (ch <= ' ' || ch == '-') break; 7670 assert(idx == wep); // for now; trick 7671 if (wep > buf.length) { wep = 0; break; } // too long 7672 if (ch >= 'A' && ch <= 'Z') buf.ptr[wep++] = cast(char)(ch+32); // poor man tolower 7673 else if (ch >= 'a' && ch <= 'z') buf.ptr[wep++] = ch; 7674 else { wep = 0; break; } // invalid char 7675 } 7676 if (wep == 2 && buf[0..wep] == "up") updown = UpDown.Up; 7677 else if (wep == 4 && buf[0..wep] == "down") updown = UpDown.Down; 7678 else if (wep == 3 && buf[0..wep] == "any") updown = UpDown.Any; 7679 // remove parsed part 7680 if (updown != UpDown.None) str = str[wep+1..$]; 7681 } 7682 if (updown == UpDown.None) { 7683 updown = UpDown.Down; 7684 } 7685 wasButtons = wasButtons || (bnum <= 2); 7686 //assert(updown != UpDown.None); 7687 debug(arsd_mevent_strcmp) { import iv.cmdcon; conprintfln(" 1: mods=0x%08x; bnum=%u; updown=%s [%s]", mods, bnum, updown, str); } 7688 // if we have a previous button, it goes to modifiers (unless it is a wheel or motion) 7689 if (lastButt != lastButt.max) { 7690 if ((lastButt&0xff) >= 3) return false; // wheel or motion 7691 if (mods != mods.max) { 7692 uint butbit = 0; 7693 final switch (lastButt&0x03) { 7694 case 0: butbit = ModifierState.leftButtonDown; break; 7695 case 1: butbit = ModifierState.middleButtonDown; break; 7696 case 2: butbit = ModifierState.rightButtonDown; break; 7697 } 7698 if (lastButt&Flag.Down) mods |= butbit; 7699 else if (lastButt&Flag.Up) mods &= ~butbit; 7700 else if (lastButt&Flag.Any) kmodmask &= ~butbit; 7701 } 7702 } 7703 // remember last button 7704 lastButt = bnum|(updown == UpDown.Up ? Flag.Up : updown == UpDown.Any ? Flag.Any : Flag.Down); 7705 } 7706 // no button -- nothing to do 7707 if (lastButt == lastButt.max) return false; 7708 // done parsing, check if something's left 7709 foreach (immutable char ch; str) if (ch > ' ') return false; // oops 7710 // remove action button from mask 7711 if ((lastButt&0xff) < 3) { 7712 final switch (lastButt&0x03) { 7713 case 0: kmodmask &= ~cast(uint)ModifierState.leftButtonDown; break; 7714 case 1: kmodmask &= ~cast(uint)ModifierState.middleButtonDown; break; 7715 case 2: kmodmask &= ~cast(uint)ModifierState.rightButtonDown; break; 7716 } 7717 } 7718 // special case: "Motion" means "ignore buttons" 7719 if ((lastButt&0xff) == 7 && !wasButtons) { 7720 debug(arsd_mevent_strcmp) { import iv.cmdcon; conwriteln(" *: special motion"); } 7721 kmodmask &= ~cast(uint)(ModifierState.leftButtonDown|ModifierState.middleButtonDown|ModifierState.rightButtonDown); 7722 } 7723 uint kmod = event.modifierState&kmodmask; 7724 debug(arsd_mevent_strcmp) { import iv.cmdcon; conprintfln(" *: mods=0x%08x; lastButt=0x%08x; kmod=0x%08x; type=%s", mods, lastButt, kmod, event.type); } 7725 // check modifier state 7726 if (mods != mods.max) { 7727 if (kmod != mods) return false; 7728 } 7729 // now check type 7730 if ((lastButt&0xff) == 7) { 7731 // motion 7732 if (event.type != MouseEventType.motion) return false; 7733 } else if ((lastButt&0xff) == 3) { 7734 // wheel 7735 if (lastButt&Flag.Up) return (event.type == MouseEventType.buttonPressed && event.button == MouseButton.wheelUp); 7736 if (lastButt&Flag.Down) return (event.type == MouseEventType.buttonPressed && event.button == MouseButton.wheelDown); 7737 if (lastButt&Flag.Any) return (event.type == MouseEventType.buttonPressed && (event.button == MouseButton.wheelUp || event.button == MouseButton.wheelUp)); 7738 return false; 7739 } else { 7740 // buttons 7741 if (((lastButt&Flag.Down) != 0 && event.type != MouseEventType.buttonPressed) || 7742 ((lastButt&Flag.Up) != 0 && event.type != MouseEventType.buttonReleased)) 7743 { 7744 return false; 7745 } 7746 // button number 7747 switch (lastButt&0x03) { 7748 case 0: if (event.button != MouseButton.left) return false; break; 7749 case 1: if (event.button != MouseButton.middle) return false; break; 7750 case 2: if (event.button != MouseButton.right) return false; break; 7751 default: return false; 7752 } 7753 } 7754 return true; 7755 } 7756 } 7757 7758 version(arsd_mevent_strcmp_test) unittest { 7759 MouseEvent event; 7760 event.type = MouseEventType.buttonPressed; 7761 event.button = MouseButton.left; 7762 event.modifierState = ModifierState.ctrl; 7763 assert(event == "C-LMB"); 7764 assert(event != "C-LMBUP"); 7765 assert(event != "C-LMB-UP"); 7766 assert(event != "C-S-LMB"); 7767 assert(event == "*-LMB"); 7768 assert(event != "*-LMB-UP"); 7769 7770 event.type = MouseEventType.buttonReleased; 7771 assert(event != "C-LMB"); 7772 assert(event == "C-LMBUP"); 7773 assert(event == "C-LMB-UP"); 7774 assert(event != "C-S-LMB"); 7775 assert(event != "*-LMB"); 7776 assert(event == "*-LMB-UP"); 7777 7778 event.button = MouseButton.right; 7779 event.modifierState |= ModifierState.shift; 7780 event.type = MouseEventType.buttonPressed; 7781 assert(event != "C-LMB"); 7782 assert(event != "C-LMBUP"); 7783 assert(event != "C-LMB-UP"); 7784 assert(event != "C-S-LMB"); 7785 assert(event != "*-LMB"); 7786 assert(event != "*-LMB-UP"); 7787 7788 assert(event != "C-RMB"); 7789 assert(event != "C-RMBUP"); 7790 assert(event != "C-RMB-UP"); 7791 assert(event == "C-S-RMB"); 7792 assert(event == "*-RMB"); 7793 assert(event != "*-RMB-UP"); 7794 } 7795 7796 /// This gives a few more options to drawing lines and such 7797 struct Pen { 7798 Color color; /// the foreground color 7799 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. 7800 Style style; /// See [Style] 7801 /+ 7802 // From X.h 7803 7804 #define LineSolid 0 7805 #define LineOnOffDash 1 7806 #define LineDoubleDash 2 7807 LineDou- The full path of the line is drawn, but the 7808 bleDash even dashes are filled differently from the 7809 odd dashes (see fill-style) with CapButt 7810 style used where even and odd dashes meet. 7811 7812 7813 7814 /* capStyle */ 7815 7816 #define CapNotLast 0 7817 #define CapButt 1 7818 #define CapRound 2 7819 #define CapProjecting 3 7820 7821 /* joinStyle */ 7822 7823 #define JoinMiter 0 7824 #define JoinRound 1 7825 #define JoinBevel 2 7826 7827 /* fillStyle */ 7828 7829 #define FillSolid 0 7830 #define FillTiled 1 7831 #define FillStippled 2 7832 #define FillOpaqueStippled 3 7833 7834 7835 +/ 7836 /// Style of lines drawn 7837 enum Style { 7838 Solid, /// a solid line 7839 Dashed, /// a dashed line 7840 Dotted, /// a dotted line 7841 } 7842 } 7843 7844 7845 /++ 7846 Represents an in-memory image in the format that the GUI expects, but with its raw data available to your program. 7847 7848 7849 On Windows, this means a device-independent bitmap. On X11, it is an XImage. 7850 7851 $(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.) 7852 7853 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. 7854 7855 If you intend to draw an image to screen several times, you will want to convert it into a [Sprite]. 7856 7857 $(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. 7858 7859 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! 7860 7861 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!) 7862 7863 Please call `destroy(image);` when you are done with it. The easiest way to do this is with scope: 7864 7865 --- 7866 auto image = new Image(256, 256); 7867 scope(exit) destroy(image); 7868 --- 7869 7870 As long as you don't hold on to it outside the scope. 7871 7872 I might change it to be an owned pointer at some point in the future. 7873 7874 ) 7875 7876 Drawing pixels on the image may be simple, using the `opIndexAssign` function, but 7877 you can also often get a fair amount of speedup by getting the raw data format and 7878 writing some custom code. 7879 7880 FIXME INSERT EXAMPLES HERE 7881 7882 7883 +/ 7884 final class Image { 7885 /// 7886 this(int width, int height, bool forcexshm=false, bool enableAlpha = false) { 7887 this.width = width; 7888 this.height = height; 7889 this.enableAlpha = enableAlpha; 7890 7891 impl.createImage(width, height, forcexshm, enableAlpha); 7892 } 7893 7894 /// 7895 this(Size size, bool forcexshm=false, bool enableAlpha = false) { 7896 this(size.width, size.height, forcexshm, enableAlpha); 7897 } 7898 7899 private bool suppressDestruction; 7900 7901 version(X11) 7902 this(XImage* handle) { 7903 this.handle = handle; 7904 this.rawData = cast(ubyte*) handle.data; 7905 this.width = handle.width; 7906 this.height = handle.height; 7907 this.enableAlpha = handle.depth == 32; 7908 suppressDestruction = true; 7909 } 7910 7911 ~this() { 7912 if(suppressDestruction) return; 7913 impl.dispose(); 7914 } 7915 7916 // these numbers are used for working with rawData itself, skipping putPixel and getPixel 7917 /// if you do the math yourself you might be able to optimize it. Call these functions only once and cache the value. 7918 pure const @system nothrow { 7919 /* 7920 To use these to draw a blue rectangle with size WxH at position X,Y... 7921 7922 // make certain that it will fit before we proceed 7923 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! 7924 7925 // gather all the values you'll need up front. These can be kept until the image changes size if you want 7926 // (though calculating them isn't really that expensive). 7927 auto nextLineAdjustment = img.adjustmentForNextLine(); 7928 auto offR = img.redByteOffset(); 7929 auto offB = img.blueByteOffset(); 7930 auto offG = img.greenByteOffset(); 7931 auto bpp = img.bytesPerPixel(); 7932 7933 auto data = img.getDataPointer(); 7934 7935 // figure out the starting byte offset 7936 auto offset = img.offsetForTopLeftPixel() + nextLineAdjustment*Y + bpp * X; 7937 7938 auto startOfLine = data + offset; // get our pointer lined up on the first pixel 7939 7940 // and now our drawing loop for the rectangle 7941 foreach(y; 0 .. H) { 7942 auto data = startOfLine; // we keep the start of line separately so moving to the next line is simple and portable 7943 foreach(x; 0 .. W) { 7944 // write our color 7945 data[offR] = 0; 7946 data[offG] = 0; 7947 data[offB] = 255; 7948 7949 data += bpp; // moving to the next pixel is just an addition... 7950 } 7951 startOfLine += nextLineAdjustment; 7952 } 7953 7954 7955 As you can see, the loop itself was very simple thanks to the calculations being moved outside. 7956 7957 FIXME: I wonder if I can make the pixel formats consistently 32 bit across platforms, so the color offsets 7958 can be made into a bitmask or something so we can write them as *uint... 7959 */ 7960 7961 /// 7962 int offsetForTopLeftPixel() { 7963 version(X11) { 7964 return 0; 7965 } else version(Windows) { 7966 if(enableAlpha) { 7967 return (width * 4) * (height - 1); 7968 } else { 7969 return (((cast(int) width * 3 + 3) / 4) * 4) * (height - 1); 7970 } 7971 } else version(OSXCocoa) { 7972 return 0 ; //throw new NotYetImplementedException(); 7973 } else static assert(0, "fill in this info for other OSes"); 7974 } 7975 7976 /// 7977 int offsetForPixel(int x, int y) { 7978 version(X11) { 7979 auto offset = (y * width + x) * 4; 7980 return offset; 7981 } else version(Windows) { 7982 if(enableAlpha) { 7983 auto itemsPerLine = width * 4; 7984 // remember, bmps are upside down 7985 auto offset = itemsPerLine * (height - y - 1) + x * 4; 7986 return offset; 7987 } else { 7988 auto itemsPerLine = ((cast(int) width * 3 + 3) / 4) * 4; 7989 // remember, bmps are upside down 7990 auto offset = itemsPerLine * (height - y - 1) + x * 3; 7991 return offset; 7992 } 7993 } else version(OSXCocoa) { 7994 return 0 ; //throw new NotYetImplementedException(); 7995 } else static assert(0, "fill in this info for other OSes"); 7996 } 7997 7998 /// 7999 int adjustmentForNextLine() { 8000 version(X11) { 8001 return width * 4; 8002 } else version(Windows) { 8003 // windows bmps are upside down, so the adjustment is actually negative 8004 if(enableAlpha) 8005 return - (cast(int) width * 4); 8006 else 8007 return -((cast(int) width * 3 + 3) / 4) * 4; 8008 } else version(OSXCocoa) { 8009 return 0 ; //throw new NotYetImplementedException(); 8010 } else static assert(0, "fill in this info for other OSes"); 8011 } 8012 8013 /// once you have the position of a pixel, use these to get to the proper color 8014 int redByteOffset() { 8015 version(X11) { 8016 return 2; 8017 } else version(Windows) { 8018 return 2; 8019 } else version(OSXCocoa) { 8020 return 0 ; //throw new NotYetImplementedException(); 8021 } else static assert(0, "fill in this info for other OSes"); 8022 } 8023 8024 /// 8025 int greenByteOffset() { 8026 version(X11) { 8027 return 1; 8028 } else version(Windows) { 8029 return 1; 8030 } else version(OSXCocoa) { 8031 return 0 ; //throw new NotYetImplementedException(); 8032 } else static assert(0, "fill in this info for other OSes"); 8033 } 8034 8035 /// 8036 int blueByteOffset() { 8037 version(X11) { 8038 return 0; 8039 } else version(Windows) { 8040 return 0; 8041 } else version(OSXCocoa) { 8042 return 0 ; //throw new NotYetImplementedException(); 8043 } else static assert(0, "fill in this info for other OSes"); 8044 } 8045 8046 /// Only valid if [enableAlpha] is true 8047 int alphaByteOffset() { 8048 version(X11) { 8049 return 3; 8050 } else version(Windows) { 8051 return 3; 8052 } else version(OSXCocoa) { 8053 return 3; //throw new NotYetImplementedException(); 8054 } else static assert(0, "fill in this info for other OSes"); 8055 } 8056 } 8057 8058 /// 8059 final void putPixel(int x, int y, Color c) { 8060 if(x < 0 || x >= width) 8061 return; 8062 if(y < 0 || y >= height) 8063 return; 8064 8065 impl.setPixel(x, y, c); 8066 } 8067 8068 /// 8069 final Color getPixel(int x, int y) { 8070 if(x < 0 || x >= width) 8071 return Color.transparent; 8072 if(y < 0 || y >= height) 8073 return Color.transparent; 8074 8075 version(OSXCocoa) throw new NotYetImplementedException(); else 8076 return impl.getPixel(x, y); 8077 } 8078 8079 /// 8080 final void opIndexAssign(Color c, int x, int y) { 8081 putPixel(x, y, c); 8082 } 8083 8084 /// 8085 TrueColorImage toTrueColorImage() { 8086 auto tci = new TrueColorImage(width, height); 8087 convertToRgbaBytes(tci.imageData.bytes); 8088 return tci; 8089 } 8090 8091 /// 8092 static Image fromMemoryImage(MemoryImage i, bool enableAlpha = false, bool premultiply = true) { 8093 auto tci = i.getAsTrueColorImage(); 8094 auto img = new Image(tci.width, tci.height, false, enableAlpha); 8095 static if(UsingSimpledisplayX11) 8096 img.premultiply = premultiply; 8097 img.setRgbaBytes(tci.imageData.bytes); 8098 return img; 8099 } 8100 8101 /// this is here for interop with arsd.image. where can be a TrueColorImage's data member 8102 /// if you pass in a buffer, it will put it right there. length must be width*height*4 already 8103 /// if you pass null, it will allocate a new one. 8104 ubyte[] getRgbaBytes(ubyte[] where = null) { 8105 if(where is null) 8106 where = new ubyte[this.width*this.height*4]; 8107 convertToRgbaBytes(where); 8108 return where; 8109 } 8110 8111 /// this is here for interop with arsd.image. from can be a TrueColorImage's data member 8112 void setRgbaBytes(in ubyte[] from ) { 8113 assert(from.length == this.width * this.height * 4); 8114 setFromRgbaBytes(from); 8115 } 8116 8117 // FIXME: make properly cross platform by getting rgba right 8118 8119 /// warning: this is not portable across platforms because the data format can change 8120 ubyte* getDataPointer() { 8121 return impl.rawData; 8122 } 8123 8124 /// for use with getDataPointer 8125 final int bytesPerLine() const pure @safe nothrow { 8126 version(Windows) 8127 return enableAlpha ? (width * 4) : (((cast(int) width * 3 + 3) / 4) * 4); 8128 else version(X11) 8129 return 4 * width; 8130 else version(OSXCocoa) 8131 return 4 * width; 8132 else static assert(0); 8133 } 8134 8135 /// for use with getDataPointer 8136 final int bytesPerPixel() const pure @safe nothrow { 8137 version(Windows) 8138 return enableAlpha ? 4 : 3; 8139 else version(X11) 8140 return 4; 8141 else version(OSXCocoa) 8142 return 4; 8143 else static assert(0); 8144 } 8145 8146 /// 8147 immutable int width; 8148 8149 /// 8150 immutable int height; 8151 8152 /// 8153 immutable bool enableAlpha; 8154 //private: 8155 mixin NativeImageImplementation!() impl; 8156 } 8157 8158 /++ 8159 A convenience function to pop up a window displaying the image. 8160 If you pass a win, it will draw the image in it. Otherwise, it will 8161 create a window with the size of the image and run its event loop, closing 8162 when a key is pressed. 8163 8164 History: 8165 `BlockingMode` parameter added on December 8, 2021. Previously, it would 8166 always block until the application quit which could cause bizarre behavior 8167 inside a more complex application. Now, the default is to block until 8168 this window closes if it is the only event loop running, and otherwise, 8169 not to block at all and just pop up the display window asynchronously. 8170 +/ 8171 void displayImage(Image image, SimpleWindow win = null, BlockingMode bm = BlockingMode.untilWindowCloses | BlockingMode.onlyIfNotNested) { 8172 if(win is null) { 8173 win = new SimpleWindow(image); 8174 { 8175 auto p = win.draw; 8176 p.drawImage(Point(0, 0), image); 8177 } 8178 win.eventLoopWithBlockingMode( 8179 bm, 0, 8180 (KeyEvent ev) { 8181 if (ev.pressed && (ev.key == Key.Escape || ev.key == Key.Space)) win.close(); 8182 } ); 8183 } else { 8184 win.image = image; 8185 } 8186 } 8187 8188 enum FontWeight : int { 8189 dontcare = 0, 8190 thin = 100, 8191 extralight = 200, 8192 light = 300, 8193 regular = 400, 8194 medium = 500, 8195 semibold = 600, 8196 bold = 700, 8197 extrabold = 800, 8198 heavy = 900 8199 } 8200 8201 /++ 8202 Interface with the common functionality for font measurements between [OperatingSystemFont] and [DrawableFont]. 8203 8204 History: 8205 Added October 24, 2022. The methods were already on [OperatingSystemFont] before that. 8206 +/ 8207 interface MeasurableFont { 8208 /++ 8209 Returns true if it is a monospace font, meaning each of the 8210 glyphs (at least the ascii characters) have matching width 8211 and no kerning, so you can determine the display width of some 8212 strings by simply multiplying the string width by [averageWidth]. 8213 8214 (Please note that multiply doesn't $(I actually) work in general, 8215 consider characters like tab and newline, but it does sometimes.) 8216 +/ 8217 bool isMonospace(); 8218 8219 /++ 8220 The average width of glyphs in the font, traditionally equal to the 8221 width of the lowercase x. Can be used to estimate bounding boxes, 8222 especially if the font [isMonospace]. 8223 8224 Given in pixels. 8225 +/ 8226 int averageWidth(); 8227 /++ 8228 The height of the bounding box of a line. 8229 +/ 8230 int height(); 8231 /++ 8232 The maximum ascent of a glyph above the baseline. 8233 8234 Given in pixels. 8235 +/ 8236 int ascent(); 8237 /++ 8238 The maximum descent of a glyph below the baseline. For example, how low the g might go. 8239 8240 Given in pixels. 8241 +/ 8242 int descent(); 8243 /++ 8244 The display width of the given string, and if you provide a window, it will use it to 8245 make the pixel count on screen more accurate too, but this shouldn't generally be necessary. 8246 8247 Given in pixels. 8248 +/ 8249 int stringWidth(scope const(char)[] s, SimpleWindow window = null); 8250 8251 } 8252 8253 // FIXME: i need a font cache and it needs to handle disconnects. 8254 8255 /++ 8256 Represents a font loaded off the operating system or the X server. 8257 8258 8259 While the api here is unified cross platform, the fonts are not necessarily 8260 available, even across machines of the same platform, so be sure to always check 8261 for null (using [isNull]) and have a fallback plan. 8262 8263 When you have a font you like, use [ScreenPainter.setFont] to load it for drawing. 8264 8265 Worst case, a null font will automatically fall back to the default font loaded 8266 for your system. 8267 +/ 8268 class OperatingSystemFont : MeasurableFont { 8269 // FIXME: when the X Connection is lost, these need to be invalidated! 8270 // that means I need to store the original stuff again to reconstruct it too. 8271 8272 version(X11) { 8273 XFontStruct* font; 8274 XFontSet fontset; 8275 8276 version(with_xft) { 8277 XftFont* xftFont; 8278 bool isXft; 8279 } 8280 } else version(Windows) { 8281 HFONT font; 8282 int width_; 8283 int height_; 8284 } else version(OSXCocoa) { 8285 NSFont font; 8286 } else static assert(0); 8287 8288 /++ 8289 Constructs the class and immediately calls [load]. 8290 +/ 8291 this(string name, int size = 0, FontWeight weight = FontWeight.dontcare, bool italic = false) { 8292 load(name, size, weight, italic); 8293 } 8294 8295 /++ 8296 Constructs the object, but does nothing. Call one of [load] or [loadDefault] to populate the object. 8297 8298 You can also call the platform-specific [loadXft], [loadCoreX], or [loadWin32] functions if appropriate for you. 8299 8300 History: 8301 Added January 24, 2021. 8302 +/ 8303 this() { 8304 // this space intentionally left blank 8305 } 8306 8307 /++ 8308 Constructs a copy of the given font object. 8309 8310 History: 8311 Added January 7, 2023. 8312 +/ 8313 this(OperatingSystemFont font) { 8314 if(font is null || font.loadedInfo is LoadedInfo.init) 8315 loadDefault(); 8316 else 8317 load(font.loadedInfo.tupleof); 8318 } 8319 8320 /++ 8321 Loads specifically with the Xft library - a freetype font from a fontconfig string. 8322 8323 History: 8324 Added November 13, 2020. 8325 +/ 8326 version(with_xft) 8327 bool loadXft(string name, int size = 0, FontWeight weight = FontWeight.dontcare, bool italic = false) { 8328 unload(); 8329 8330 if(!XftLibrary.attempted) { 8331 XftLibrary.loadDynamicLibrary(); 8332 } 8333 8334 if(!XftLibrary.loadSuccessful) 8335 return false; 8336 8337 auto display = XDisplayConnection.get; 8338 8339 char[256] nameBuffer = void; 8340 int nbp = 0; 8341 8342 void add(in char[] a) { 8343 nameBuffer[nbp .. nbp + a.length] = a[]; 8344 nbp += a.length; 8345 } 8346 add(name); 8347 8348 if(size) { 8349 add(":size="); 8350 add(toInternal!string(size)); 8351 } 8352 if(weight != FontWeight.dontcare) { 8353 add(":weight="); 8354 add(weightToString(weight)); 8355 } 8356 if(italic) 8357 add(":slant=100"); 8358 8359 nameBuffer[nbp] = 0; 8360 8361 this.xftFont = XftFontOpenName( 8362 display, 8363 DefaultScreen(display), 8364 nameBuffer.ptr 8365 ); 8366 8367 this.isXft = true; 8368 8369 if(xftFont !is null) { 8370 isMonospace_ = stringWidth("x") == stringWidth("M"); 8371 ascent_ = xftFont.ascent; 8372 descent_ = xftFont.descent; 8373 } 8374 8375 return !isNull(); 8376 } 8377 8378 /++ 8379 Lists available fonts from the system that match the given pattern, finding names that are suitable for passing to [OperatingSystemFont]'s constructor. 8380 8381 8382 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. 8383 8384 If `pattern` is null, it returns all available font families. 8385 8386 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. 8387 8388 The format of the pattern is platform-specific. 8389 8390 History: 8391 Added May 1, 2021 (dub v9.5) 8392 +/ 8393 static void listFonts(string pattern, bool delegate(in char[] name) handler) { 8394 version(Windows) { 8395 auto hdc = GetDC(null); 8396 scope(exit) ReleaseDC(null, hdc); 8397 LOGFONT logfont; 8398 static extern(Windows) int proc(const LOGFONT* lf, const TEXTMETRIC* tm, DWORD type, LPARAM p) { 8399 auto localHandler = *(cast(typeof(handler)*) p); 8400 return localHandler(lf.lfFaceName[].sliceCString) ? 1 : 0; 8401 } 8402 EnumFontFamiliesEx(hdc, &logfont, &proc, cast(LPARAM) &handler, 0); 8403 } else version(X11) { 8404 //import core.stdc.stdio; 8405 bool done = false; 8406 version(with_xft) { 8407 if(!XftLibrary.attempted) { 8408 XftLibrary.loadDynamicLibrary(); 8409 } 8410 8411 if(!XftLibrary.loadSuccessful) 8412 goto skipXft; 8413 8414 if(!FontConfigLibrary.attempted) 8415 FontConfigLibrary.loadDynamicLibrary(); 8416 if(!FontConfigLibrary.loadSuccessful) 8417 goto skipXft; 8418 8419 { 8420 auto got = XftListFonts(XDisplayConnection.get, 0, null, "family".ptr, "style".ptr, null); 8421 if(got is null) 8422 goto skipXft; 8423 scope(exit) FcFontSetDestroy(got); 8424 8425 auto fontPatterns = got.fonts[0 .. got.nfont]; 8426 foreach(candidate; fontPatterns) { 8427 char* where, whereStyle; 8428 8429 char* pmg = FcNameUnparse(candidate); 8430 8431 //FcPatternGetString(candidate, "family", 0, &where); 8432 //FcPatternGetString(candidate, "style", 0, &whereStyle); 8433 //if(where && whereStyle) { 8434 if(pmg) { 8435 if(!handler(pmg.sliceCString)) 8436 return; 8437 //printf("%s || %s %s\n", pmg, where, whereStyle); 8438 } 8439 } 8440 } 8441 } 8442 8443 skipXft: 8444 8445 if(pattern is null) 8446 pattern = "*"; 8447 8448 int count; 8449 auto coreFontsRaw = XListFonts(XDisplayConnection.get, pattern.toStringz, 10000 /* max return */, &count); 8450 scope(exit) XFreeFontNames(coreFontsRaw); 8451 8452 auto coreFonts = coreFontsRaw[0 .. count]; 8453 8454 foreach(font; coreFonts) { 8455 char[128] tmp; 8456 tmp[0 ..5] = "core:"; 8457 auto cf = font.sliceCString; 8458 if(5 + cf.length > tmp.length) 8459 assert(0, "a font name was too long, sorry i didn't bother implementing a fallback"); 8460 tmp[5 .. 5 + cf.length] = cf; 8461 if(!handler(tmp[0 .. 5 + cf.length])) 8462 return; 8463 } 8464 } 8465 } 8466 8467 /++ 8468 Returns the raw content of the ttf file, if possible. This allows you to use OperatingSystemFont 8469 to look up fonts that you then pass to things like [arsd.ttf.OpenGlLimitedFont] or [arsd.nanovega]. 8470 8471 Returns null if impossible. It is impossible if the loaded font is not a local TTF file or if the 8472 underlying system doesn't support returning the raw bytes. 8473 8474 History: 8475 Added September 10, 2021 (dub v10.3) 8476 +/ 8477 ubyte[] getTtfBytes() { 8478 if(isNull) 8479 return null; 8480 8481 version(Windows) { 8482 auto dc = GetDC(null); 8483 auto orig = SelectObject(dc, font); 8484 8485 scope(exit) { 8486 SelectObject(dc, orig); 8487 ReleaseDC(null, dc); 8488 } 8489 8490 auto res = GetFontData(dc, 0 /* whole file */, 0 /* offset */, null, 0); 8491 if(res == GDI_ERROR) 8492 return null; 8493 8494 ubyte[] buffer = new ubyte[](res); 8495 res = GetFontData(dc, 0 /* whole file */, 0 /* offset */, buffer.ptr, cast(DWORD) buffer.length); 8496 if(res == GDI_ERROR) 8497 return null; // wtf really tbh 8498 8499 return buffer; 8500 } else version(with_xft) { 8501 if(isXft && xftFont) { 8502 if(!FontConfigLibrary.attempted) 8503 FontConfigLibrary.loadDynamicLibrary(); 8504 if(!FontConfigLibrary.loadSuccessful) 8505 return null; 8506 8507 char* file; 8508 if (FcPatternGetString(xftFont.pattern, "file", 0, &file) == 0 /*FcResultMatch*/) { 8509 if (file !is null && file[0]) { 8510 import core.stdc.stdio; 8511 auto fp = fopen(file, "rb"); 8512 if(fp is null) 8513 return null; 8514 scope(exit) 8515 fclose(fp); 8516 fseek(fp, 0, SEEK_END); 8517 ubyte[] buffer = new ubyte[](ftell(fp)); 8518 fseek(fp, 0, SEEK_SET); 8519 8520 auto got = fread(buffer.ptr, 1, buffer.length, fp); 8521 if(got != buffer.length) 8522 return null; 8523 8524 return buffer; 8525 } 8526 } 8527 } 8528 return null; 8529 } else throw new NotYetImplementedException(); 8530 } 8531 8532 // see also: XftLockFace(font) which gives a FT_Face. from /usr/include/X11/Xft/Xft.h line 352 8533 8534 private string weightToString(FontWeight weight) { 8535 with(FontWeight) 8536 final switch(weight) { 8537 case dontcare: return "*"; 8538 case thin: return "extralight"; 8539 case extralight: return "extralight"; 8540 case light: return "light"; 8541 case regular: return "regular"; 8542 case medium: return "medium"; 8543 case semibold: return "demibold"; 8544 case bold: return "bold"; 8545 case extrabold: return "demibold"; 8546 case heavy: return "black"; 8547 } 8548 } 8549 8550 /++ 8551 Loads specifically a Core X font - rendered on the X server without antialiasing. Best performance. 8552 8553 History: 8554 Added November 13, 2020. Before then, this code was integrated in the [load] function. 8555 +/ 8556 version(X11) 8557 bool loadCoreX(string name, int size = 0, FontWeight weight = FontWeight.dontcare, bool italic = false) { 8558 unload(); 8559 8560 string xfontstr; 8561 8562 if(name.length > 3 && name[0 .. 3] == "-*-") { 8563 // this is kinda a disgusting hack but if the user sends an exact 8564 // string I'd like to honor it... 8565 xfontstr = name; 8566 } else { 8567 string weightstr = weightToString(weight); 8568 string sizestr; 8569 if(size == 0) 8570 sizestr = "*"; 8571 else 8572 sizestr = toInternal!string(size); 8573 xfontstr = "-*-"~name~"-"~weightstr~"-"~(italic ? "i" : "r")~"-*-*-"~sizestr~"-*-*-*-*-*-*-*\0"; 8574 } 8575 8576 // writeln(xfontstr); 8577 8578 auto display = XDisplayConnection.get; 8579 8580 font = XLoadQueryFont(display, xfontstr.ptr); 8581 if(font is null) 8582 return false; 8583 8584 char** lol; 8585 int lol2; 8586 char* lol3; 8587 fontset = XCreateFontSet(display, xfontstr.ptr, &lol, &lol2, &lol3); 8588 8589 prepareFontInfo(); 8590 8591 return !isNull(); 8592 } 8593 8594 version(X11) 8595 private void prepareFontInfo() { 8596 if(font !is null) { 8597 isMonospace_ = stringWidth("l") == stringWidth("M"); 8598 ascent_ = font.max_bounds.ascent; 8599 descent_ = font.max_bounds.descent; 8600 } 8601 } 8602 8603 version(OSXCocoa) 8604 private void prepareFontInfo() { 8605 if(font !is null) { 8606 isMonospace_ = font.isFixedPitch; 8607 ascent_ = cast(int) font.ascender; 8608 descent_ = cast(int) - font.descender; 8609 } 8610 } 8611 8612 8613 /++ 8614 Loads a Windows font. You probably want to use [load] instead to be more generic. 8615 8616 History: 8617 Added November 13, 2020. Before then, this code was integrated in the [load] function. 8618 +/ 8619 version(Windows) 8620 bool loadWin32(string name, int size = 0, FontWeight weight = FontWeight.dontcare, bool italic = false, HDC hdc = null) { 8621 unload(); 8622 8623 WCharzBuffer buffer = WCharzBuffer(name); 8624 font = CreateFont(size, 0, 0, 0, cast(int) weight, italic, 0, 0, 0, 0, 0, 0, 0, buffer.ptr); 8625 8626 prepareFontInfo(hdc); 8627 8628 return !isNull(); 8629 } 8630 8631 version(Windows) 8632 void prepareFontInfo(HDC hdc = null) { 8633 if(font is null) 8634 return; 8635 8636 TEXTMETRIC tm; 8637 auto dc = hdc ? hdc : GetDC(null); 8638 auto orig = SelectObject(dc, font); 8639 GetTextMetrics(dc, &tm); 8640 SelectObject(dc, orig); 8641 if(hdc is null) 8642 ReleaseDC(null, dc); 8643 8644 width_ = tm.tmAveCharWidth; 8645 height_ = tm.tmHeight; 8646 ascent_ = tm.tmAscent; 8647 descent_ = tm.tmDescent; 8648 // 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. 8649 isMonospace_ = (tm.tmPitchAndFamily & TMPF_FIXED_PITCH) == 0; 8650 } 8651 8652 8653 /++ 8654 `name` is a font name, but it can also be a more complicated string parsed in an OS-specific way. 8655 8656 On X, you may prefix a name with `core:` to bypass the freetype engine causing this function to forward to [loadCoreX]. Otherwise, 8657 it calls [loadXft] if the library is available. If the library or font is not available on Xft, it falls back on [loadCoreX]. 8658 8659 On Windows, it forwards directly to [loadWin32]. 8660 8661 Params: 8662 name = font name. This is looked up by the operating system and may be interpreted differently across platforms or user machines and their preferences. 8663 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. 8664 weight = approximate boldness, results may vary. 8665 italic = try to get a slanted version of the given font. 8666 8667 History: 8668 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. 8669 +/ 8670 bool load(string name, int size = 0, FontWeight weight = FontWeight.dontcare, bool italic = false) { 8671 this.loadedInfo = LoadedInfo(name, size, weight, italic); 8672 version(X11) { 8673 version(with_xft) { 8674 if(name.length > 5 && name[0 .. 5] == "core:") { 8675 goto core; 8676 } 8677 8678 if(loadXft(name, size, weight, italic)) 8679 return true; 8680 // if xft fails, fallback to core to avoid breaking 8681 // code that already depended on this. 8682 } 8683 8684 core: 8685 8686 if(name.length > 5 && name[0 .. 5] == "core:") { 8687 name = name[5 .. $]; 8688 } 8689 8690 return loadCoreX(name, size, weight, italic); 8691 } else version(Windows) { 8692 return loadWin32(name, size, weight, italic); 8693 } else version(OSXCocoa) { 8694 return loadCocoa(name, size, weight, italic); 8695 } else static assert(0); 8696 } 8697 8698 version(OSXCocoa) 8699 bool loadCocoa(string name, int size, FontWeight weight, bool italic) { 8700 unload(); 8701 8702 font = NSFont.fontWithName(MacString(name).borrow, size); // FIXME: weight and italic? 8703 prepareFontInfo(); 8704 8705 return !isNull(); 8706 } 8707 8708 private struct LoadedInfo { 8709 string name; 8710 int size; 8711 FontWeight weight; 8712 bool italic; 8713 } 8714 private LoadedInfo loadedInfo; 8715 8716 /// 8717 void unload() { 8718 if(isNull()) 8719 return; 8720 8721 version(X11) { 8722 auto display = XDisplayConnection.display; 8723 8724 if(display is null) 8725 return; 8726 8727 version(with_xft) { 8728 if(isXft) { 8729 if(xftFont) 8730 XftFontClose(display, xftFont); 8731 isXft = false; 8732 xftFont = null; 8733 return; 8734 } 8735 } 8736 8737 if(font && font !is ScreenPainterImplementation.defaultfont) 8738 XFreeFont(display, font); 8739 if(fontset && fontset !is ScreenPainterImplementation.defaultfontset) 8740 XFreeFontSet(display, fontset); 8741 8742 font = null; 8743 fontset = null; 8744 } else version(Windows) { 8745 DeleteObject(font); 8746 font = null; 8747 } else version(OSXCocoa) { 8748 font.release(); 8749 font = null; 8750 } else static assert(0); 8751 } 8752 8753 private bool isMonospace_; 8754 8755 /++ 8756 History: 8757 Added January 16, 2021 8758 +/ 8759 bool isMonospace() { 8760 return isMonospace_; 8761 } 8762 8763 /++ 8764 Returns the average width of the font, conventionally defined as the width of the lowercase 'x' character. 8765 8766 History: 8767 Added March 26, 2020 8768 Documented January 16, 2021 8769 +/ 8770 int averageWidth() { 8771 version(X11) { 8772 return stringWidth("x"); 8773 } version(OSXCocoa) { 8774 return stringWidth("x"); 8775 } else version(Windows) 8776 return width_; 8777 else assert(0); 8778 } 8779 8780 /++ 8781 Returns the width of the string as drawn on the specified window, or the default screen if the window is null. 8782 8783 History: 8784 Added January 16, 2021 8785 +/ 8786 int stringWidth(scope const(char)[] s, SimpleWindow window = null) { 8787 // FIXME: what about tab? 8788 if(isNull) 8789 return 0; 8790 8791 version(X11) { 8792 version(with_xft) 8793 if(isXft && xftFont !is null) { 8794 //return xftFont.max_advance_width; 8795 XGlyphInfo extents; 8796 XftTextExtentsUtf8(XDisplayConnection.get, xftFont, s.ptr, cast(int) s.length, &extents); 8797 // writeln(extents); 8798 return extents.xOff; 8799 } 8800 if(font is null) 8801 return 0; 8802 else if(fontset) { 8803 XRectangle rect; 8804 Xutf8TextExtents(fontset, s.ptr, cast(int) s.length, null, &rect); 8805 8806 return rect.width; 8807 } else { 8808 return XTextWidth(font, s.ptr, cast(int) s.length); 8809 } 8810 } else version(Windows) { 8811 WCharzBuffer buffer = WCharzBuffer(s); 8812 8813 return stringWidth(buffer.slice, window); 8814 } else version(OSXCocoa) { 8815 /+ 8816 int charCount = [string length]; 8817 CGGlyph glyphs[charCount]; 8818 CGRect rects[charCount]; 8819 8820 CTFontGetGlyphsForCharacters(theCTFont, (const unichar*)[string cStringUsingEncoding:NSUnicodeStringEncoding], glyphs, charCount); 8821 CTFontGetBoundingRectsForGlyphs(theCTFont, kCTFontDefaultOrientation, glyphs, rects, charCount); 8822 8823 int totalwidth = 0, maxheight = 0; 8824 for (int i=0; i < charCount; i++) 8825 { 8826 totalwidth += rects[i].size.width; 8827 maxheight = maxheight < rects[i].size.height ? rects[i].size.height : maxheight; 8828 } 8829 8830 dim = CGSizeMake(totalwidth, maxheight); 8831 +/ 8832 8833 return 16; // FIXME 8834 } 8835 else assert(0); 8836 } 8837 8838 version(Windows) 8839 /// ditto 8840 int stringWidth(scope const(wchar)[] s, SimpleWindow window = null) { 8841 if(isNull) 8842 return 0; 8843 version(Windows) { 8844 SIZE size; 8845 8846 prepareContext(window); 8847 scope(exit) releaseContext(); 8848 8849 GetTextExtentPoint32W(dc, s.ptr, cast(int) s.length, &size); 8850 8851 return size.cx; 8852 } else { 8853 // std.conv can do this easily but it is slow to import and i don't think it is worth it 8854 static assert(0, "not implemented yet"); 8855 //return stringWidth(s, window); 8856 } 8857 } 8858 8859 private { 8860 int prepRefcount; 8861 8862 version(Windows) { 8863 HDC dc; 8864 HANDLE orig; 8865 HWND hwnd; 8866 } 8867 } 8868 /++ 8869 [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. 8870 8871 History: 8872 Added January 23, 2021 8873 +/ 8874 void prepareContext(SimpleWindow window = null) { 8875 prepRefcount++; 8876 if(prepRefcount == 1) { 8877 version(Windows) { 8878 hwnd = window is null ? null : window.impl.hwnd; 8879 dc = GetDC(hwnd); 8880 orig = SelectObject(dc, font); 8881 } 8882 } 8883 } 8884 /// ditto 8885 void releaseContext() { 8886 prepRefcount--; 8887 if(prepRefcount == 0) { 8888 version(Windows) { 8889 SelectObject(dc, orig); 8890 ReleaseDC(hwnd, dc); 8891 hwnd = null; 8892 dc = null; 8893 orig = null; 8894 } 8895 } 8896 } 8897 8898 /+ 8899 FIXME: I think I need advance and kerning pair 8900 8901 int advance(dchar from, dchar to) { } // use dchar.init for first item in string 8902 +/ 8903 8904 /++ 8905 Returns the height of the font. 8906 8907 History: 8908 Added March 26, 2020 8909 Documented January 16, 2021 8910 +/ 8911 int height() { 8912 version(X11) { 8913 version(with_xft) 8914 if(isXft && xftFont !is null) { 8915 return xftFont.ascent + xftFont.descent; // i don't use height here because it doesn't include the baseline pixel 8916 } 8917 if(font is null) 8918 return 0; 8919 return font.max_bounds.ascent + font.max_bounds.descent; 8920 } else version(Windows) { 8921 return height_; 8922 } else version(OSXCocoa) { 8923 if(font is null) 8924 return 0; 8925 return cast(int) font.capHeight; 8926 } 8927 else assert(0); 8928 } 8929 8930 private int ascent_; 8931 private int descent_; 8932 8933 /++ 8934 Max ascent above the baseline. 8935 8936 History: 8937 Added January 22, 2021 8938 +/ 8939 int ascent() { 8940 return ascent_; 8941 } 8942 8943 /++ 8944 Max descent below the baseline. 8945 8946 History: 8947 Added January 22, 2021 8948 +/ 8949 int descent() { 8950 return descent_; 8951 } 8952 8953 /++ 8954 Loads the default font used by [ScreenPainter] if none others are loaded. 8955 8956 Returns: 8957 This method mutates the `this` object, but then returns `this` for 8958 easy chaining like: 8959 8960 --- 8961 auto font = foo.isNull ? foo : foo.loadDefault 8962 --- 8963 8964 History: 8965 Added previously, but left unimplemented until January 24, 2021. 8966 +/ 8967 OperatingSystemFont loadDefault() { 8968 unload(); 8969 8970 loadedInfo = LoadedInfo.init; 8971 8972 version(X11) { 8973 // another option would be https://tronche.com/gui/x/xlib/graphics/font-metrics/XQueryFont.html 8974 // but meh since sdpy does its own thing, this should be ok too 8975 8976 ScreenPainterImplementation.ensureDefaultFontLoaded(); 8977 this.font = ScreenPainterImplementation.defaultfont; 8978 this.fontset = ScreenPainterImplementation.defaultfontset; 8979 8980 prepareFontInfo(); 8981 return this; 8982 } else version(Windows) { 8983 ScreenPainterImplementation.ensureDefaultFontLoaded(); 8984 this.font = ScreenPainterImplementation.defaultGuiFont; 8985 8986 prepareFontInfo(); 8987 return this; 8988 } else version(OSXCocoa) { 8989 this.font = NSFont.systemFontOfSize(12); 8990 8991 prepareFontInfo(); 8992 return this; 8993 } else throw new NotYetImplementedException(); 8994 } 8995 8996 /// 8997 bool isNull() { 8998 version(with_xft) 8999 if(isXft) 9000 return xftFont is null; 9001 return font is null; 9002 } 9003 9004 /* Metrics */ 9005 /+ 9006 GetABCWidth 9007 GetKerningPairs 9008 9009 if I do it right, I can size it all here, and match 9010 what happens when I draw the full string with the OS functions. 9011 9012 subclasses might do the same thing while getting the glyphs on images 9013 struct GlyphInfo { 9014 int glyph; 9015 9016 size_t stringIdxStart; 9017 size_t stringIdxEnd; 9018 9019 Rectangle boundingBox; 9020 } 9021 GlyphInfo[] getCharBoxes() { 9022 // XftTextExtentsUtf8 9023 return null; 9024 9025 } 9026 +/ 9027 9028 ~this() { 9029 if(!thisIsGuiThread) return; // FIXME: leaks if multithreaded gc 9030 unload(); 9031 } 9032 } 9033 9034 version(Windows) 9035 private string sliceCString(const(wchar)[] w) { 9036 return makeUtf8StringFromWindowsString(cast(wchar*) w.ptr); 9037 } 9038 9039 private inout(char)[] sliceCString(inout(char)* s) { 9040 import core.stdc.string; 9041 auto len = strlen(s); 9042 return s[0 .. len]; 9043 } 9044 9045 version(OSXCocoa) 9046 alias PaintingHandle = NSObject; 9047 else 9048 alias PaintingHandle = NativeWindowHandle; 9049 9050 /** 9051 The 2D drawing proxy. You acquire one of these with [SimpleWindow.draw] rather 9052 than constructing it directly. Then, it is reference counted so you can pass it 9053 at around and when the last ref goes out of scope, the buffered drawing activities 9054 are all carried out. 9055 9056 9057 Most functions use the outlineColor instead of taking a color themselves. 9058 ScreenPainter is reference counted and draws its buffer to the screen when its 9059 final reference goes out of scope. 9060 */ 9061 struct ScreenPainter { 9062 CapableOfBeingDrawnUpon window; 9063 this(CapableOfBeingDrawnUpon window, PaintingHandle handle, bool manualInvalidations) { 9064 this.window = window; 9065 if(window.closed) 9066 return; // null painter is now allowed so no need to throw anymore, this likely happens at the end of a program anyway 9067 //currentClipRectangle = arsd.color.Rectangle(0, 0, window.width, window.height); 9068 currentClipRectangle = arsd.color.Rectangle(short.min, short.min, short.max, short.max); 9069 if(window.activeScreenPainter !is null) { 9070 impl = window.activeScreenPainter; 9071 if(impl.referenceCount == 0) { 9072 impl.window = window; 9073 impl.create(handle); 9074 } 9075 impl.manualInvalidations = manualInvalidations; 9076 impl.referenceCount++; 9077 // writeln("refcount ++ ", impl.referenceCount); 9078 } else { 9079 impl = new ScreenPainterImplementation; 9080 impl.window = window; 9081 impl.create(handle); 9082 impl.referenceCount = 1; 9083 impl.manualInvalidations = manualInvalidations; 9084 window.activeScreenPainter = impl; 9085 // writeln("constructed"); 9086 } 9087 9088 copyActiveOriginals(); 9089 } 9090 9091 /++ 9092 EXPERIMENTAL. subject to change. 9093 9094 When you draw a cursor, you can draw this to notify your window of where it is, 9095 for IME systems to use. 9096 +/ 9097 void notifyCursorPosition(int x, int y, int width, int height) { 9098 if(auto w = cast(SimpleWindow) window) { 9099 w.setIMEPopupLocation(x + _originX + width, y + _originY + height); 9100 } 9101 } 9102 9103 /++ 9104 If you are using manual invalidations, this informs the 9105 window system that a section needs to be redrawn. 9106 9107 If you didn't opt into manual invalidation, you don't 9108 have to call this. 9109 9110 History: 9111 Added December 30, 2021 (dub v10.5) 9112 +/ 9113 void invalidateRect(Rectangle rect) { 9114 if(impl is null) return; 9115 9116 // transform(rect) 9117 rect.left += _originX; 9118 rect.right += _originX; 9119 rect.top += _originY; 9120 rect.bottom += _originY; 9121 9122 impl.invalidateRect(rect); 9123 } 9124 9125 private Pen originalPen; 9126 private Color originalFillColor; 9127 private arsd.color.Rectangle originalClipRectangle; 9128 private OperatingSystemFont originalFont; 9129 void copyActiveOriginals() { 9130 if(impl is null) return; 9131 originalPen = impl._activePen; 9132 originalFillColor = impl._fillColor; 9133 originalClipRectangle = impl._clipRectangle; 9134 version(OSXCocoa) {} else 9135 originalFont = impl._activeFont; 9136 } 9137 9138 ~this() { 9139 if(impl is null) return; 9140 impl.referenceCount--; 9141 //writeln("refcount -- ", impl.referenceCount); 9142 if(impl.referenceCount == 0) { 9143 // writeln("destructed"); 9144 impl.dispose(); 9145 *window.activeScreenPainter = ScreenPainterImplementation.init; 9146 // writeln("paint finished"); 9147 } else { 9148 // there is still an active reference, reset stuff so the 9149 // next user doesn't get weirdness via the reference 9150 this.rasterOp = RasterOp.normal; 9151 pen = originalPen; 9152 fillColor = originalFillColor; 9153 if(originalFont) 9154 setFont(originalFont); 9155 impl.setClipRectangle(originalClipRectangle.left, originalClipRectangle.top, originalClipRectangle.width, originalClipRectangle.height); 9156 } 9157 } 9158 9159 this(this) { 9160 if(impl is null) return; 9161 impl.referenceCount++; 9162 //writeln("refcount ++ ", impl.referenceCount); 9163 9164 copyActiveOriginals(); 9165 } 9166 9167 private int _originX; 9168 private int _originY; 9169 @property int originX() { return _originX; } 9170 @property int originY() { return _originY; } 9171 @property int originX(int a) { 9172 _originX = a; 9173 return _originX; 9174 } 9175 @property int originY(int a) { 9176 _originY = a; 9177 return _originY; 9178 } 9179 arsd.color.Rectangle currentClipRectangle; // set BEFORE doing any transformations 9180 private void transform(ref Point p) { 9181 if(impl is null) return; 9182 p.x += _originX; 9183 p.y += _originY; 9184 } 9185 9186 // this needs to be checked BEFORE the originX/Y transformation 9187 private bool isClipped(Point p) { 9188 return !currentClipRectangle.contains(p); 9189 } 9190 private bool isClipped(Point p, int width, int height) { 9191 return !currentClipRectangle.overlaps(arsd.color.Rectangle(p, Size(width + 1, height + 1))); 9192 } 9193 private bool isClipped(Point p, Size s) { 9194 return !currentClipRectangle.overlaps(arsd.color.Rectangle(p, Size(s.width + 1, s.height + 1))); 9195 } 9196 private bool isClipped(Point p, Point p2) { 9197 // need to ensure the end points are actually included inside, so the +1 does that 9198 return !currentClipRectangle.overlaps(arsd.color.Rectangle(p, p2 + Point(1, 1))); 9199 } 9200 9201 9202 /++ 9203 Sets the clipping region for drawing. If width == 0 && height == 0, disabled clipping. 9204 9205 Returns: 9206 The old clip rectangle. 9207 9208 History: 9209 Return value was `void` prior to May 10, 2021. 9210 9211 +/ 9212 arsd.color.Rectangle setClipRectangle(Point pt, int width, int height) { 9213 if(impl is null) return currentClipRectangle; 9214 if(pt == currentClipRectangle.upperLeft && width == currentClipRectangle.width && height == currentClipRectangle.height) 9215 return currentClipRectangle; // no need to do anything 9216 auto old = currentClipRectangle; 9217 currentClipRectangle = arsd.color.Rectangle(pt, Size(width, height)); 9218 transform(pt); 9219 9220 impl.setClipRectangle(pt.x, pt.y, width, height); 9221 9222 return old; 9223 } 9224 9225 /// ditto 9226 arsd.color.Rectangle setClipRectangle(arsd.color.Rectangle rect) { 9227 if(impl is null) return currentClipRectangle; 9228 return setClipRectangle(rect.upperLeft, rect.width, rect.height); 9229 } 9230 9231 /// 9232 void setFont(OperatingSystemFont font) { 9233 if(impl is null) return; 9234 impl.setFont(font); 9235 } 9236 9237 /// 9238 int fontHeight() { 9239 if(impl is null) return 0; 9240 return impl.fontHeight(); 9241 } 9242 9243 private Pen activePen; 9244 9245 /// 9246 @property void pen(Pen p) { 9247 if(impl is null) return; 9248 activePen = p; 9249 impl.pen(p); 9250 } 9251 9252 /// 9253 @scriptable 9254 @property void outlineColor(Color c) { 9255 if(impl is null) return; 9256 if(activePen.color == c) 9257 return; 9258 activePen.color = c; 9259 impl.pen(activePen); 9260 } 9261 9262 /// 9263 @scriptable 9264 @property void fillColor(Color c) { 9265 if(impl is null) return; 9266 impl.fillColor(c); 9267 } 9268 9269 /// 9270 @property void rasterOp(RasterOp op) { 9271 if(impl is null) return; 9272 impl.rasterOp(op); 9273 } 9274 9275 9276 void updateDisplay() { 9277 // FIXME this should do what the dtor does 9278 } 9279 9280 /// 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) 9281 void scrollArea(Point upperLeft, int width, int height, int dx, int dy) { 9282 if(impl is null) return; 9283 if(isClipped(upperLeft, width, height)) return; 9284 transform(upperLeft); 9285 version(Windows) { 9286 // http://msdn.microsoft.com/en-us/library/windows/desktop/bb787589%28v=vs.85%29.aspx 9287 RECT scroll = RECT(upperLeft.x, upperLeft.y, upperLeft.x + width, upperLeft.y + height); 9288 RECT clip = scroll; 9289 RECT uncovered; 9290 HRGN hrgn; 9291 if(!ScrollDC(impl.hdc, -dx, -dy, &scroll, &clip, hrgn, &uncovered)) 9292 throw new WindowsApiException("ScrollDC", GetLastError()); 9293 9294 } else version(X11) { 9295 // FIXME: clip stuff outside this rectangle 9296 XCopyArea(impl.display, impl.d, impl.d, impl.gc, upperLeft.x, upperLeft.y, width, height, upperLeft.x - dx, upperLeft.y - dy); 9297 } else version(OSXCocoa) { 9298 throw new NotYetImplementedException(); 9299 } else static assert(0); 9300 } 9301 9302 /// 9303 void clear(Color color = Color.white()) { 9304 if(impl is null) return; 9305 fillColor = color; 9306 outlineColor = color; 9307 drawRectangle(Point(0, 0), window.width, window.height); 9308 } 9309 9310 /++ 9311 Draws a pixmap (represented by the [Sprite] class) on the drawable. 9312 9313 Params: 9314 upperLeft = point on the window where the upper left corner of the image will be drawn 9315 imageUpperLeft = point on the image to start the slice to draw 9316 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. 9317 History: 9318 The `imageUpperLeft` and `sliceSize` parameters were added on March 11, 2021 (dub v9.3.0) 9319 +/ 9320 void drawPixmap(Sprite s, Point upperLeft, Point imageUpperLeft = Point(0, 0), Size sliceSize = Size(0, 0)) { 9321 if(impl is null) return; 9322 if(isClipped(upperLeft, s.width, s.height)) return; 9323 transform(upperLeft); 9324 impl.drawPixmap(s, upperLeft.x, upperLeft.y, imageUpperLeft.x, imageUpperLeft.y, sliceSize.width, sliceSize.height); 9325 } 9326 9327 /// 9328 void drawImage(Point upperLeft, Image i, Point upperLeftOfImage = Point(0, 0), int w = 0, int h = 0) { 9329 if(impl is null) return; 9330 //if(isClipped(upperLeft, w, h)) return; // FIXME 9331 transform(upperLeft); 9332 if(w == 0 || w > i.width) 9333 w = i.width; 9334 if(h == 0 || h > i.height) 9335 h = i.height; 9336 if(upperLeftOfImage.x < 0) 9337 upperLeftOfImage.x = 0; 9338 if(upperLeftOfImage.y < 0) 9339 upperLeftOfImage.y = 0; 9340 9341 impl.drawImage(upperLeft.x, upperLeft.y, i, upperLeftOfImage.x, upperLeftOfImage.y, w, h); 9342 } 9343 9344 /// 9345 Size textSize(in char[] text) { 9346 if(impl is null) return Size(0, 0); 9347 return impl.textSize(text); 9348 } 9349 9350 /++ 9351 Draws a string in the window with the set font (see [setFont] to change it). 9352 9353 Params: 9354 upperLeft = the upper left point of the bounding box of the text 9355 text = the string to draw 9356 lowerRight = the lower right point of the bounding box of the text. If 0, 0, there is no lower right bound. 9357 alignment = A [arsd.docs.general_concepts#bitflags|combination] of [TextAlignment] flags 9358 +/ 9359 @scriptable 9360 void drawText(Point upperLeft, in char[] text, Point lowerRight = Point(0, 0), uint alignment = 0) { 9361 if(impl is null) return; 9362 if(lowerRight.x != 0 || lowerRight.y != 0) { 9363 if(isClipped(upperLeft, lowerRight)) return; 9364 transform(lowerRight); 9365 } else { 9366 if(isClipped(upperLeft, textSize(text))) return; 9367 } 9368 transform(upperLeft); 9369 impl.drawText(upperLeft.x, upperLeft.y, lowerRight.x, lowerRight.y, text, alignment); 9370 } 9371 9372 /++ 9373 Draws text using a custom font. 9374 9375 This is still MAJOR work in progress. 9376 9377 Creating a [DrawableFont] can be tricky and require additional dependencies. 9378 +/ 9379 void drawText(DrawableFont font, Point upperLeft, in char[] text) { 9380 if(impl is null) return; 9381 if(isClipped(upperLeft, Point(int.max, int.max))) return; 9382 transform(upperLeft); 9383 font.drawString(this, upperLeft, text); 9384 } 9385 9386 version(Windows) 9387 void drawText(Point upperLeft, scope const(wchar)[] text) { 9388 if(impl is null) return; 9389 if(isClipped(upperLeft, Point(int.max, int.max))) return; 9390 transform(upperLeft); 9391 9392 if(text.length && text[$-1] == '\n') 9393 text = text[0 .. $-1]; // tailing newlines are weird on windows... 9394 9395 TextOutW(impl.hdc, upperLeft.x, upperLeft.y, text.ptr, cast(int) text.length); 9396 } 9397 9398 static struct TextDrawingContext { 9399 Point boundingBoxUpperLeft; 9400 Point boundingBoxLowerRight; 9401 9402 Point currentLocation; 9403 9404 Point lastDrewUpperLeft; 9405 Point lastDrewLowerRight; 9406 9407 // how do i do right aligned rich text? 9408 // i kinda want to do a pre-made drawing then right align 9409 // draw the whole block. 9410 // 9411 // That's exactly the diff: inline vs block stuff. 9412 9413 // I need to get coordinates of an inline section out too, 9414 // not just a bounding box, but a series of bounding boxes 9415 // should be ok. Consider what's needed to detect a click 9416 // on a link in the middle of a paragraph breaking a line. 9417 // 9418 // Generally, we should be able to get the rectangles of 9419 // any portion we draw. 9420 // 9421 // It also needs to tell what text is left if it overflows 9422 // out of the box, so we can do stuff like float images around 9423 // it. It should not attempt to draw a letter that would be 9424 // clipped. 9425 // 9426 // I might also turn off word wrap stuff. 9427 } 9428 9429 void drawText(TextDrawingContext context, in char[] text, uint alignment = 0) { 9430 if(impl is null) return; 9431 // FIXME 9432 } 9433 9434 /// Drawing an individual pixel is slow. Avoid it if possible. 9435 void drawPixel(Point where) { 9436 if(impl is null) return; 9437 if(isClipped(where)) return; 9438 transform(where); 9439 impl.drawPixel(where.x, where.y); 9440 } 9441 9442 9443 /// Draws a pen using the current pen / outlineColor 9444 @scriptable 9445 void drawLine(Point starting, Point ending) { 9446 if(impl is null) return; 9447 if(isClipped(starting, ending)) return; 9448 transform(starting); 9449 transform(ending); 9450 impl.drawLine(starting.x, starting.y, ending.x, ending.y); 9451 } 9452 9453 /// Draws a rectangle using the current pen/outline color for the border and brush/fill color for the insides 9454 /// The outer lines, inclusive of x = 0, y = 0, x = width - 1, and y = height - 1 are drawn with the outlineColor 9455 /// The rest of the pixels are drawn with the fillColor. If fillColor is transparent, those pixels are not drawn. 9456 @scriptable 9457 void drawRectangle(Point upperLeft, int width, int height) { 9458 if(impl is null) return; 9459 if(isClipped(upperLeft, width, height)) return; 9460 transform(upperLeft); 9461 impl.drawRectangle(upperLeft.x, upperLeft.y, width, height); 9462 } 9463 9464 /// ditto 9465 void drawRectangle(Point upperLeft, Size size) { 9466 if(impl is null) return; 9467 if(isClipped(upperLeft, size.width, size.height)) return; 9468 transform(upperLeft); 9469 impl.drawRectangle(upperLeft.x, upperLeft.y, size.width, size.height); 9470 } 9471 9472 /// ditto 9473 void drawRectangle(Point upperLeft, Point lowerRightInclusive) { 9474 if(impl is null) return; 9475 if(isClipped(upperLeft, lowerRightInclusive + Point(1, 1))) return; 9476 transform(upperLeft); 9477 transform(lowerRightInclusive); 9478 impl.drawRectangle(upperLeft.x, upperLeft.y, 9479 lowerRightInclusive.x - upperLeft.x + 1, lowerRightInclusive.y - upperLeft.y + 1); 9480 } 9481 9482 // overload added on May 12, 2021 9483 /// ditto 9484 void drawRectangle(Rectangle rect) { 9485 drawRectangle(rect.upperLeft, rect.size); 9486 } 9487 9488 /// Arguments are the points of the bounding rectangle 9489 void drawEllipse(Point upperLeft, Point lowerRight) { 9490 if(impl is null) return; 9491 if(isClipped(upperLeft, lowerRight)) return; 9492 transform(upperLeft); 9493 transform(lowerRight); 9494 impl.drawEllipse(upperLeft.x, upperLeft.y, lowerRight.x, lowerRight.y); 9495 } 9496 9497 /++ 9498 start and finish are units of degrees * 64 9499 9500 History: 9501 The Windows implementation didn't match the Linux implementation until September 24, 2021. 9502 9503 They still don't exactly match in outlining the arc with straight lines (Windows does, Linux doesn't for now). 9504 +/ 9505 void drawArc(Point upperLeft, int width, int height, int start, int finish) { 9506 if(impl is null) return; 9507 // FIXME: not actually implemented 9508 if(isClipped(upperLeft, width, height)) return; 9509 transform(upperLeft); 9510 impl.drawArc(upperLeft.x, upperLeft.y, width, height, start, finish); 9511 } 9512 9513 /// this function draws a circle with the drawEllipse() function above, it requires the upper left point and the radius 9514 void drawCircle(Point upperLeft, int diameter) { 9515 drawEllipse(upperLeft, Point(upperLeft.x + diameter, upperLeft.y + diameter)); 9516 } 9517 9518 /// . 9519 void drawPolygon(Point[] vertexes) { 9520 if(impl is null) return; 9521 assert(vertexes.length); 9522 int minX = int.max, minY = int.max, maxX = int.min, maxY = int.min; 9523 foreach(ref vertex; vertexes) { 9524 if(vertex.x < minX) 9525 minX = vertex.x; 9526 if(vertex.y < minY) 9527 minY = vertex.y; 9528 if(vertex.x > maxX) 9529 maxX = vertex.x; 9530 if(vertex.y > maxY) 9531 maxY = vertex.y; 9532 transform(vertex); 9533 } 9534 if(isClipped(Point(minX, maxY), Point(maxX + 1, maxY + 1))) return; 9535 impl.drawPolygon(vertexes); 9536 } 9537 9538 /// ditto 9539 void drawPolygon(Point[] vertexes...) { 9540 if(impl is null) return; 9541 drawPolygon(vertexes); 9542 } 9543 9544 9545 // and do a draw/fill in a single call maybe. Windows can do it... but X can't, though it could do two calls. 9546 9547 //mixin NativeScreenPainterImplementation!() impl; 9548 9549 9550 // HACK: if I mixin the impl directly, it won't let me override the copy 9551 // constructor! The linker complains about there being multiple definitions. 9552 // I'll make the best of it and reference count it though. 9553 ScreenPainterImplementation* impl; 9554 } 9555 9556 // HACK: I need a pointer to the implementation so it's separate 9557 struct ScreenPainterImplementation { 9558 CapableOfBeingDrawnUpon window; 9559 int referenceCount; 9560 mixin NativeScreenPainterImplementation!(); 9561 } 9562 9563 // FIXME: i haven't actually tested the sprite class on MS Windows 9564 9565 /** 9566 Sprites are optimized for fast drawing on the screen, but slow for direct pixel 9567 access. They are best for drawing a relatively unchanging image repeatedly on the screen. 9568 9569 9570 On X11, this corresponds to an `XPixmap`. On Windows, it still uses a bitmap, 9571 though I'm not sure that's ideal and the implementation might change. 9572 9573 You create one by giving a window and an image. It optimizes for that window, 9574 and copies the image into it to use as the initial picture. Creating a sprite 9575 can be quite slow (especially over a network connection) so you should do it 9576 as little as possible and just hold on to your sprite handles after making them. 9577 simpledisplay does try to do its best though, using the XSHM extension if available, 9578 but you should still write your code as if it will always be slow. 9579 9580 Then you can use `sprite.drawAt(painter, point);` to draw it, which should be 9581 a fast operation - much faster than drawing the Image itself every time. 9582 9583 `Sprite` represents a scarce resource which should be freed when you 9584 are done with it. Use the `dispose` method to do this. Do not use a `Sprite` 9585 after it has been disposed. If you are unsure about this, don't take chances, 9586 just let the garbage collector do it for you. But ideally, you can manage its 9587 lifetime more efficiently. 9588 9589 $(NOTE `Sprite`, like the rest of simpledisplay's `ScreenPainter`, does not 9590 support alpha blending in its drawing at this time. That might change in the 9591 future, but if you need alpha blending right now, use OpenGL instead. See 9592 `gamehelpers.d` for a similar class to `Sprite` that uses OpenGL: `OpenGlTexture`.) 9593 9594 Update: on April 23, 2021, I finally added alpha blending support. You must opt 9595 in by setting the enableAlpha = true in the constructor. 9596 */ 9597 class Sprite : CapableOfBeingDrawnUpon { 9598 9599 /// 9600 ScreenPainter draw() { 9601 return ScreenPainter(this, handle, false); 9602 } 9603 9604 /++ 9605 Copies the sprite's current state into a [TrueColorImage]. 9606 9607 Be warned: this can be a very slow operation 9608 9609 History: 9610 Actually implemented on March 14, 2021 9611 +/ 9612 TrueColorImage takeScreenshot() { 9613 return trueColorImageFromNativeHandle(handle, width, height); 9614 } 9615 9616 void delegate() paintingFinishedDg() { return null; } 9617 bool closed() { return false; } 9618 ScreenPainterImplementation* activeScreenPainter_; 9619 protected ScreenPainterImplementation* activeScreenPainter() { return activeScreenPainter_; } 9620 protected void activeScreenPainter(ScreenPainterImplementation* i) { activeScreenPainter_ = i; } 9621 9622 version(Windows) 9623 private ubyte* rawData; 9624 // FIXME: sprites are lost when disconnecting from X! We need some way to invalidate them... 9625 // ditto on the XPicture stuff 9626 9627 version(X11) { 9628 private static XRenderPictFormat* RGB24; 9629 private static XRenderPictFormat* ARGB32; 9630 9631 private Picture xrenderPicture; 9632 } 9633 9634 version(X11) 9635 private static void requireXRender() { 9636 if(!XRenderLibrary.loadAttempted) { 9637 XRenderLibrary.loadDynamicLibrary(); 9638 } 9639 9640 if(!XRenderLibrary.loadSuccessful) 9641 throw new Exception("XRender library load failure"); 9642 9643 auto display = XDisplayConnection.get; 9644 9645 // FIXME: if we migrate X displays, these need to be changed 9646 if(RGB24 is null) 9647 RGB24 = XRenderFindStandardFormat(display, PictStandardRGB24); 9648 if(ARGB32 is null) 9649 ARGB32 = XRenderFindStandardFormat(display, PictStandardARGB32); 9650 } 9651 9652 protected this() {} 9653 9654 this(SimpleWindow win, int width, int height, bool enableAlpha = false) { 9655 this._width = width; 9656 this._height = height; 9657 this.enableAlpha = enableAlpha; 9658 9659 version(X11) { 9660 auto display = XDisplayConnection.get(); 9661 9662 if(enableAlpha) { 9663 requireXRender(); 9664 } 9665 9666 handle = XCreatePixmap(display, cast(Drawable) win.window, width, height, enableAlpha ? 32 : DefaultDepthOfDisplay(display)); 9667 9668 if(enableAlpha) { 9669 XRenderPictureAttributes attrs; 9670 xrenderPicture = XRenderCreatePicture(display, handle, ARGB32, 0, &attrs); 9671 } 9672 } else version(Windows) { 9673 version(CRuntime_DigitalMars) { 9674 //if(enableAlpha) 9675 //throw new Exception("Alpha support not available, try recompiling with -m32mscoff"); 9676 } 9677 9678 BITMAPINFO infoheader; 9679 infoheader.bmiHeader.biSize = infoheader.bmiHeader.sizeof; 9680 infoheader.bmiHeader.biWidth = width; 9681 infoheader.bmiHeader.biHeight = height; 9682 infoheader.bmiHeader.biPlanes = 1; 9683 infoheader.bmiHeader.biBitCount = enableAlpha ? 32 : 24; 9684 infoheader.bmiHeader.biCompression = BI_RGB; 9685 9686 // FIXME: this should prolly be a device dependent bitmap... 9687 handle = CreateDIBSection( 9688 null, 9689 &infoheader, 9690 DIB_RGB_COLORS, 9691 cast(void**) &rawData, 9692 null, 9693 0); 9694 9695 if(handle is null) 9696 throw new WindowsApiException("couldn't create pixmap", GetLastError()); 9697 } 9698 } 9699 9700 /// Makes a sprite based on the image with the initial contents from the Image 9701 this(SimpleWindow win, Image i) { 9702 this(win, i.width, i.height, i.enableAlpha); 9703 9704 version(X11) { 9705 auto display = XDisplayConnection.get(); 9706 auto gc = XCreateGC(display, this.handle, 0, null); 9707 scope(exit) XFreeGC(display, gc); 9708 if(i.usingXshm) 9709 XShmPutImage(display, cast(Drawable) handle, gc, i.handle, 0, 0, 0, 0, i.width, i.height, false); 9710 else 9711 XPutImage(display, cast(Drawable) handle, gc, i.handle, 0, 0, 0, 0, i.width, i.height); 9712 } else version(Windows) { 9713 auto itemsPerLine = enableAlpha ? (4 * width) : (((cast(int) width * 3 + 3) / 4) * 4); 9714 auto arrLength = itemsPerLine * height; 9715 rawData[0..arrLength] = i.rawData[0..arrLength]; 9716 } else version(OSXCocoa) { 9717 // FIXME: I have no idea if this is even any good 9718 auto colorSpace = CGColorSpaceCreateDeviceRGB(); 9719 handle = CGBitmapContextCreate(null, width, height, 8, 4*width, 9720 colorSpace, 9721 kCGImageAlphaPremultipliedLast 9722 |kCGBitmapByteOrder32Big); 9723 CGColorSpaceRelease(colorSpace); 9724 auto rawData = CGBitmapContextGetData(handle); 9725 9726 auto rdl = (width * height * 4); 9727 rawData[0 .. rdl] = i.rawData[0 .. rdl]; 9728 } else static assert(0); 9729 } 9730 9731 /++ 9732 Draws the image on the specified painter at the specified point. The point is the upper-left point where the image will be drawn. 9733 9734 Params: 9735 where = point on the window where the upper left corner of the image will be drawn 9736 imageUpperLeft = point on the image to start the slice to draw 9737 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. 9738 History: 9739 The `imageUpperLeft` and `sliceSize` parameters were added on March 11, 2021 (dub v9.3.0) 9740 +/ 9741 void drawAt(ScreenPainter painter, Point where, Point imageUpperLeft = Point(0, 0), Size sliceSize = Size(0, 0)) { 9742 painter.drawPixmap(this, where, imageUpperLeft, sliceSize); 9743 } 9744 9745 /// Call this when you're ready to get rid of it 9746 void dispose() { 9747 version(X11) { 9748 staticDispose(xrenderPicture, handle); 9749 xrenderPicture = None; 9750 handle = None; 9751 } else version(Windows) { 9752 staticDispose(handle); 9753 handle = null; 9754 } else version(OSXCocoa) { 9755 staticDispose(handle); 9756 handle = null; 9757 } else static assert(0); 9758 9759 } 9760 9761 version(X11) 9762 static void staticDispose(Picture xrenderPicture, Pixmap handle) { 9763 if(xrenderPicture) 9764 XRenderFreePicture(XDisplayConnection.get, xrenderPicture); 9765 if(handle) 9766 XFreePixmap(XDisplayConnection.get(), handle); 9767 } 9768 else version(Windows) 9769 static void staticDispose(HBITMAP handle) { 9770 if(handle) 9771 DeleteObject(handle); 9772 } 9773 else version(OSXCocoa) 9774 static void staticDispose(CGContextRef context) { 9775 if(context) 9776 CGContextRelease(context); 9777 } 9778 9779 ~this() { 9780 version(X11) { if(xrenderPicture || handle) 9781 cleanupQueue.queue!staticDispose(xrenderPicture, handle); 9782 } else version(Windows) { if(handle) 9783 cleanupQueue.queue!staticDispose(handle); 9784 } else version(OSXCocoa) { if(handle) 9785 cleanupQueue.queue!staticDispose(handle); 9786 } else static assert(0); 9787 } 9788 9789 /// 9790 final @property int width() { return _width; } 9791 9792 /// 9793 final @property int height() { return _height; } 9794 9795 /// 9796 static Sprite fromMemoryImage(SimpleWindow win, MemoryImage img, bool enableAlpha = false) { 9797 return new Sprite(win, Image.fromMemoryImage(img, enableAlpha)); 9798 } 9799 9800 auto nativeHandle() { 9801 return handle; 9802 } 9803 9804 private: 9805 9806 int _width; 9807 int _height; 9808 bool enableAlpha; 9809 version(X11) 9810 Pixmap handle; 9811 else version(Windows) 9812 HBITMAP handle; 9813 else version(OSXCocoa) 9814 CGContextRef handle; 9815 else static assert(0); 9816 } 9817 9818 /++ 9819 Represents a display-side gradient pseudo-image. Actually construct it with [LinearGradient], [RadialGradient], or [ConicalGradient]. 9820 9821 History: 9822 Added November 20, 2021 (dub v10.4) 9823 +/ 9824 version(OSXCocoa) {} else // NotYetImplementedException 9825 abstract class Gradient : Sprite { 9826 protected this(int w, int h) { 9827 version(X11) { 9828 Sprite.requireXRender(); 9829 9830 super(); 9831 enableAlpha = true; 9832 _width = w; 9833 _height = h; 9834 } else version(Windows) { 9835 super(null, w, h, true); // on Windows i'm just making a bitmap myself 9836 } 9837 } 9838 9839 version(Windows) 9840 final void forEachPixel(scope Color delegate(int x, int y) dg) { 9841 auto ptr = rawData; 9842 foreach(j; 0 .. _height) 9843 foreach(i; 0 .. _width) { 9844 auto color = dg(i, _height - j - 1); // cuz of upside down bitmap 9845 *rawData = (color.a * color.b) / 255; rawData++; 9846 *rawData = (color.a * color.g) / 255; rawData++; 9847 *rawData = (color.a * color.r) / 255; rawData++; 9848 *rawData = color.a; rawData++; 9849 } 9850 } 9851 9852 version(X11) 9853 protected void helper(scope Stop[] stops, scope Picture delegate(scope XFixed[] stopsPositions, scope XRenderColor[] colors) dg) { 9854 assert(stops.length > 0); 9855 assert(stops.length <= 16, "I got lazy with buffers"); 9856 9857 XFixed[16] stopsPositions = void; 9858 XRenderColor[16] colors = void; 9859 9860 foreach(idx, stop; stops) { 9861 stopsPositions[idx] = cast(int)(stop.percentage * ushort.max); 9862 auto c = stop.c; 9863 colors[idx] = XRenderColor( 9864 cast(ushort)(c.r * ushort.max / 255), 9865 cast(ushort)(c.g * ushort.max / 255), 9866 cast(ushort)(c.b * ushort.max / 255), 9867 cast(ushort)(c.a * ubyte.max) // max value here is fractional 9868 ); 9869 } 9870 9871 xrenderPicture = dg(stopsPositions, colors); 9872 } 9873 9874 /// 9875 static struct Stop { 9876 float percentage; /// between 0 and 1.0 9877 Color c; 9878 } 9879 } 9880 9881 /++ 9882 Creates a linear gradient between p1 and p2. 9883 9884 X ONLY RIGHT NOW 9885 9886 History: 9887 Added November 20, 2021 (dub v10.4) 9888 9889 Bugs: 9890 Not yet implemented on Windows. 9891 +/ 9892 version(OSXCocoa) {} else // NotYetImplementedException 9893 class LinearGradient : Gradient { 9894 /++ 9895 9896 +/ 9897 this(Point p1, Point p2, Stop[] stops...) { 9898 super(p2.x, p2.y); 9899 9900 version(X11) { 9901 XLinearGradient gradient; 9902 gradient.p1 = XPointFixed(p1.x * ushort.max, p1.y * ushort.max); 9903 gradient.p2 = XPointFixed(p2.x * ushort.max, p2.y * ushort.max); 9904 9905 helper(stops, (scope XFixed[] stopsPositions, scope XRenderColor[] colors) { 9906 return XRenderCreateLinearGradient( 9907 XDisplayConnection.get, 9908 &gradient, 9909 stopsPositions.ptr, 9910 colors.ptr, 9911 cast(int) stops.length); 9912 }); 9913 } else version(Windows) { 9914 // FIXME 9915 forEachPixel((int x, int y) { 9916 import core.stdc.math; 9917 9918 //sqrtf( 9919 9920 return Color.transparent; 9921 // return Color(x * 2, y * 2, 0, 128); // this result is so beautiful 9922 }); 9923 } 9924 } 9925 } 9926 9927 /++ 9928 A conical gradient goes from color to color around a circumference from a center point. 9929 9930 X ONLY RIGHT NOW 9931 9932 History: 9933 Added November 20, 2021 (dub v10.4) 9934 9935 Bugs: 9936 Not yet implemented on Windows. 9937 +/ 9938 version(OSXCocoa) {} else // NotYetImplementedException 9939 class ConicalGradient : Gradient { 9940 /++ 9941 9942 +/ 9943 this(Point center, float angleInDegrees, Stop[] stops...) { 9944 super(center.x * 2, center.y * 2); 9945 9946 version(X11) { 9947 XConicalGradient gradient; 9948 gradient.center = XPointFixed(center.x * ushort.max, center.y * ushort.max); 9949 gradient.angle = cast(int)(angleInDegrees * ushort.max); 9950 9951 helper(stops, (scope XFixed[] stopsPositions, scope XRenderColor[] colors) { 9952 return XRenderCreateConicalGradient( 9953 XDisplayConnection.get, 9954 &gradient, 9955 stopsPositions.ptr, 9956 colors.ptr, 9957 cast(int) stops.length); 9958 }); 9959 } else version(Windows) { 9960 // FIXME 9961 forEachPixel((int x, int y) { 9962 import core.stdc.math; 9963 9964 //sqrtf( 9965 9966 return Color.transparent; 9967 // return Color(x * 2, y * 2, 0, 128); // this result is so beautiful 9968 }); 9969 9970 } 9971 } 9972 } 9973 9974 /++ 9975 A radial gradient goes from color to color based on distance from the center. 9976 It is like rings of color. 9977 9978 X ONLY RIGHT NOW 9979 9980 9981 More specifically, you create two circles: an inner circle and an outer circle. 9982 The gradient is only drawn in the area outside the inner circle but inside the outer 9983 circle. The closest line between those two circles forms the line for the gradient 9984 and the stops are calculated the same as the [LinearGradient]. Then it just sweeps around. 9985 9986 History: 9987 Added November 20, 2021 (dub v10.4) 9988 9989 Bugs: 9990 Not yet implemented on Windows. 9991 +/ 9992 version(OSXCocoa) {} else // NotYetImplementedException 9993 class RadialGradient : Gradient { 9994 /++ 9995 9996 +/ 9997 this(Point innerCenter, float innerRadius, Point outerCenter, float outerRadius, Stop[] stops...) { 9998 super(cast(int)(outerCenter.x + outerRadius + 0.5), cast(int)(outerCenter.y + outerRadius + 0.5)); 9999 10000 version(X11) { 10001 XRadialGradient gradient; 10002 gradient.inner = XCircle(innerCenter.x * ushort.max, innerCenter.y * ushort.max, cast(int) (innerRadius * ushort.max)); 10003 gradient.outer = XCircle(outerCenter.x * ushort.max, outerCenter.y * ushort.max, cast(int) (outerRadius * ushort.max)); 10004 10005 helper(stops, (scope XFixed[] stopsPositions, scope XRenderColor[] colors) { 10006 return XRenderCreateRadialGradient( 10007 XDisplayConnection.get, 10008 &gradient, 10009 stopsPositions.ptr, 10010 colors.ptr, 10011 cast(int) stops.length); 10012 }); 10013 } else version(Windows) { 10014 // FIXME 10015 forEachPixel((int x, int y) { 10016 import core.stdc.math; 10017 10018 //sqrtf( 10019 10020 return Color.transparent; 10021 // return Color(x * 2, y * 2, 0, 128); // this result is so beautiful 10022 }); 10023 } 10024 } 10025 } 10026 10027 10028 10029 /+ 10030 NOT IMPLEMENTED 10031 10032 A display-stored image optimized for relatively quick drawing, like 10033 [Sprite], but this one supports alpha channel blending and does NOT 10034 support direct drawing upon it with a [ScreenPainter]. 10035 10036 You can think of it as an [arsd.game.OpenGlTexture] for usage with a 10037 plain [ScreenPainter]... sort of. 10038 10039 On X11, it requires the Xrender extension and library. This is available 10040 almost everywhere though. 10041 10042 History: 10043 Added November 14, 2020 but NOT ACTUALLY IMPLEMENTED 10044 +/ 10045 version(none) 10046 class AlphaSprite { 10047 /++ 10048 Copies the given image into it. 10049 +/ 10050 this(MemoryImage img) { 10051 10052 if(!XRenderLibrary.loadAttempted) { 10053 XRenderLibrary.loadDynamicLibrary(); 10054 10055 // FIXME: this needs to be reconstructed when the X server changes 10056 repopulateX(); 10057 } 10058 if(!XRenderLibrary.loadSuccessful) 10059 throw new Exception("XRender library load failure"); 10060 10061 // I probably need to put the alpha mask in a separate Picture 10062 // ugh 10063 // maybe the Sprite itself can have an alpha bitmask anyway 10064 10065 10066 auto display = XDisplayConnection.get(); 10067 pixmap = XCreatePixmap(display, cast(Drawable) win.window, width, height, DefaultDepthOfDisplay(display)); 10068 10069 10070 XRenderPictureAttributes attrs; 10071 10072 handle = XRenderCreatePicture( 10073 XDisplayConnection.get, 10074 pixmap, 10075 RGBA, 10076 0, 10077 &attrs 10078 ); 10079 10080 } 10081 10082 // maybe i'll use the create gradient functions too with static factories.. 10083 10084 void drawAt(ScreenPainter painter, Point where) { 10085 //painter.drawPixmap(this, where); 10086 10087 XRenderPictureAttributes attrs; 10088 10089 auto pic = XRenderCreatePicture( 10090 XDisplayConnection.get, 10091 painter.impl.d, 10092 RGB, 10093 0, 10094 &attrs 10095 ); 10096 10097 XRenderComposite( 10098 XDisplayConnection.get, 10099 3, // PictOpOver 10100 handle, 10101 None, 10102 pic, 10103 0, // src 10104 0, 10105 0, // mask 10106 0, 10107 10, // dest 10108 10, 10109 100, // width 10110 100 10111 ); 10112 10113 /+ 10114 XRenderFreePicture( 10115 XDisplayConnection.get, 10116 pic 10117 ); 10118 10119 XRenderFreePicture( 10120 XDisplayConnection.get, 10121 fill 10122 ); 10123 +/ 10124 // on Windows you can stretch but Xrender still can't :( 10125 } 10126 10127 static XRenderPictFormat* RGB; 10128 static XRenderPictFormat* RGBA; 10129 static void repopulateX() { 10130 auto display = XDisplayConnection.get; 10131 RGB = XRenderFindStandardFormat(display, PictStandardRGB24); 10132 RGBA = XRenderFindStandardFormat(display, PictStandardARGB32); 10133 } 10134 10135 XPixmap pixmap; 10136 Picture handle; 10137 } 10138 10139 /// 10140 interface CapableOfBeingDrawnUpon { 10141 /// 10142 ScreenPainter draw(); 10143 /// 10144 int width(); 10145 /// 10146 int height(); 10147 protected ScreenPainterImplementation* activeScreenPainter(); 10148 protected void activeScreenPainter(ScreenPainterImplementation*); 10149 bool closed(); 10150 10151 void delegate() paintingFinishedDg(); 10152 10153 /// Be warned: this can be a very slow operation 10154 TrueColorImage takeScreenshot(); 10155 } 10156 10157 /// 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]. 10158 void flushGui() { 10159 version(X11) { 10160 auto dpy = XDisplayConnection.get(); 10161 XLockDisplay(dpy); 10162 scope(exit) XUnlockDisplay(dpy); 10163 XFlush(dpy); 10164 } 10165 } 10166 10167 /++ 10168 Runs the given code in the GUI thread when its event loop 10169 is available, blocking until it completes. This allows you 10170 to create and manipulate windows from another thread without 10171 invoking undefined behavior. 10172 10173 If this is the gui thread, it runs the code immediately. 10174 10175 If no gui thread exists yet, the current thread is assumed 10176 to be it. Attempting to create windows or run the event loop 10177 in any other thread will cause an assertion failure. 10178 10179 10180 $(TIP 10181 Did you know you can use UFCS on delegate literals? 10182 10183 () { 10184 // code here 10185 }.runInGuiThread; 10186 ) 10187 10188 Returns: 10189 `true` if the function was called, `false` if it was not. 10190 The function may not be called because the gui thread had 10191 already terminated by the time you called this. 10192 10193 History: 10194 Added April 10, 2020 (v7.2.0) 10195 10196 Return value added and implementation tweaked to avoid locking 10197 at program termination on February 24, 2021 (v9.2.1). 10198 +/ 10199 bool runInGuiThread(scope void delegate() dg) @trusted { 10200 claimGuiThread(); 10201 10202 if(thisIsGuiThread) { 10203 dg(); 10204 return true; 10205 } 10206 10207 if(guiThreadTerminating) 10208 return false; 10209 10210 import core.sync.semaphore; 10211 static Semaphore sc; 10212 if(sc is null) 10213 sc = new Semaphore(); 10214 10215 static RunQueueMember* rqm; 10216 if(rqm is null) 10217 rqm = new RunQueueMember; 10218 rqm.dg = cast(typeof(rqm.dg)) dg; 10219 rqm.signal = sc; 10220 rqm.thrown = null; 10221 10222 synchronized(runInGuiThreadLock) { 10223 runInGuiThreadQueue ~= rqm; 10224 } 10225 10226 if(!SimpleWindow.eventWakeUp()) 10227 throw new Error("runInGuiThread impossible; eventWakeUp failed"); 10228 10229 rqm.signal.wait(); 10230 auto t = rqm.thrown; 10231 10232 if(t) 10233 throw t; 10234 10235 return true; 10236 } 10237 10238 // note it runs sync if this is the gui thread.... 10239 void runInGuiThreadAsync(void delegate() dg, void delegate(Exception) nothrow handleError = null) nothrow { 10240 claimGuiThread(); 10241 10242 try { 10243 10244 if(thisIsGuiThread) { 10245 dg(); 10246 return; 10247 } 10248 10249 if(guiThreadTerminating) 10250 return; 10251 10252 RunQueueMember* rqm = new RunQueueMember; 10253 rqm.dg = cast(typeof(rqm.dg)) dg; 10254 rqm.signal = null; 10255 rqm.thrown = null; 10256 10257 synchronized(runInGuiThreadLock) { 10258 runInGuiThreadQueue ~= rqm; 10259 } 10260 10261 if(!SimpleWindow.eventWakeUp()) 10262 throw new Error("runInGuiThread impossible; eventWakeUp failed"); 10263 } catch(Exception e) { 10264 if(handleError) 10265 handleError(e); 10266 } 10267 } 10268 10269 private void runPendingRunInGuiThreadDelegates() { 10270 more: 10271 RunQueueMember* next; 10272 synchronized(runInGuiThreadLock) { 10273 if(runInGuiThreadQueue.length) { 10274 next = runInGuiThreadQueue[0]; 10275 runInGuiThreadQueue = runInGuiThreadQueue[1 .. $]; 10276 } else { 10277 next = null; 10278 } 10279 } 10280 10281 if(next) { 10282 try { 10283 next.dg(); 10284 next.thrown = null; 10285 } catch(Throwable t) { 10286 next.thrown = t; 10287 } 10288 10289 if(next.signal) 10290 next.signal.notify(); 10291 10292 goto more; 10293 } 10294 } 10295 10296 private void claimGuiThread() nothrow { 10297 import core.atomic; 10298 if(cas(&guiThreadExists_, false, true)) 10299 thisIsGuiThread = true; 10300 } 10301 10302 private struct RunQueueMember { 10303 void delegate() dg; 10304 import core.sync.semaphore; 10305 Semaphore signal; 10306 Throwable thrown; 10307 } 10308 10309 private __gshared RunQueueMember*[] runInGuiThreadQueue; 10310 private __gshared Object runInGuiThreadLock = new Object; // intentional CTFE 10311 private bool thisIsGuiThread = false; 10312 private shared bool guiThreadExists_ = false; 10313 private shared bool guiThreadTerminating = false; 10314 10315 /++ 10316 Returns `true` if a gui thread exists, that is, a thread running the simpledisplay.d 10317 event loop. All windows must be exclusively created and managed by a single thread. 10318 10319 If no gui thread exists, simpledisplay.d will automatically adopt the current thread 10320 when you call one of its constructors. 10321 10322 If a gui thread exists, you should check [thisThreadRunningGui] to see if it is this 10323 one. If so, you can run gui functions on it. If not, don't. The helper functions 10324 [runInGuiThread] and [runInGuiThreadAsync] can be used to help you with this automatically. 10325 10326 The reason this function is available is in case you want to message pass between a gui 10327 thread and your current thread. If no gui thread exists or if this is the gui thread, 10328 you're liable to deadlock when trying to communicate since you'd end up talking to yourself. 10329 10330 History: 10331 Added December 3, 2021 (dub v10.5) 10332 +/ 10333 public bool guiThreadExists() { 10334 return guiThreadExists_; 10335 } 10336 10337 /++ 10338 Returns `true` if this thread is either running or set to be running the 10339 simpledisplay.d gui core event loop because it owns windows. 10340 10341 It is important to keep gui-related functionality in the right thread, so you will 10342 want to `runInGuiThread` when you call them (with some specific exceptions called 10343 out in those specific functions' documentation). Notably, all windows must be 10344 created and managed only from the gui thread. 10345 10346 Will return false if simpledisplay's other functions haven't been called 10347 yet; check [guiThreadExists] in addition to this. 10348 10349 History: 10350 Added December 3, 2021 (dub v10.5) 10351 +/ 10352 public bool thisThreadRunningGui() { 10353 return thisIsGuiThread; 10354 } 10355 10356 /++ 10357 Function to help temporarily print debugging info. It will bypass any stdout/err redirection 10358 and go to the controlling tty or console (attaching to the parent and/or allocating one as 10359 needed on Windows. Please note it may overwrite output from other programs in the parent and the 10360 allocated one will not survive if your program crashes. Use the `fileOverride` to print to a log 10361 file instead if you are in one of those situations). 10362 10363 It does not support outputting very many types; just strings and ints are likely to actually work. 10364 10365 It will perform very slowly and swallows any errors that may occur. Moreover, the specific output 10366 is unspecified meaning I can change it at any time. The only point of this function is to help 10367 in temporary use for printf-style debugging. It is NOT nogc, but you can use the `debug` keyword 10368 and the compiler will cheat for you. It is, however, formally nothrow and trusted to ease its use 10369 in those contexts. 10370 10371 $(WARNING 10372 I reserve the right to change this function at any time. You can use it if it helps you 10373 but do not rely on it for anything permanent. 10374 ) 10375 10376 History: 10377 Added December 3, 2021. Not formally supported under any stable tag. 10378 +/ 10379 void sdpyPrintDebugString(string fileOverride = null, T...)(T t) nothrow @trusted { 10380 try { 10381 version(Windows) { 10382 import core.sys.windows.wincon; 10383 if(!AttachConsole(ATTACH_PARENT_PROCESS)) 10384 AllocConsole(); 10385 const(char)* fn = "CONOUT$"; 10386 } else version(Posix) { 10387 const(char)* fn = "/dev/tty"; 10388 } else static assert(0, "Function not implemented for your system"); 10389 10390 if(fileOverride.length) 10391 fn = fileOverride.ptr; 10392 10393 import core.stdc.stdio; 10394 auto fp = fopen(fn, "wt"); 10395 if(fp is null) return; 10396 scope(exit) fclose(fp); 10397 10398 string str; 10399 foreach(item; t) { 10400 static if(is(typeof(item) : const(char)[])) 10401 str ~= item; 10402 else 10403 str ~= toInternal!string(item); 10404 str ~= " "; 10405 } 10406 str ~= "\n"; 10407 10408 fwrite(str.ptr, 1, str.length, fp); 10409 fflush(fp); 10410 } catch(Exception e) { 10411 // sorry no hope 10412 } 10413 } 10414 10415 private void guiThreadFinalize() { 10416 assert(thisIsGuiThread); 10417 10418 guiThreadTerminating = true; // don't add any more from this point on 10419 runPendingRunInGuiThreadDelegates(); 10420 } 10421 10422 /+ 10423 interface IPromise { 10424 void reportProgress(int current, int max, string message); 10425 10426 /+ // not formally in cuz of templates but still 10427 IPromise Then(); 10428 IPromise Catch(); 10429 IPromise Finally(); 10430 +/ 10431 } 10432 10433 /+ 10434 auto promise = async({ ... }); 10435 promise.Then(whatever). 10436 Then(whateverelse). 10437 Catch((exception) { }); 10438 10439 10440 A promise is run inside a fiber and it looks something like: 10441 10442 try { 10443 auto res = whatever(); 10444 auto res2 = whateverelse(res); 10445 } catch(Exception e) { 10446 { }(e); 10447 } 10448 10449 When a thing succeeds, it is passed as an arg to the next 10450 +/ 10451 class Promise(T) : IPromise { 10452 auto Then() { return null; } 10453 auto Catch() { return null; } 10454 auto Finally() { return null; } 10455 10456 // wait for it to resolve and return the value, or rethrow the error if that occurred. 10457 // cannot be called from the gui thread, but this is caught at runtime instead of compile time. 10458 T await(); 10459 } 10460 10461 interface Task { 10462 } 10463 10464 interface Resolvable(T) : Task { 10465 void run(); 10466 10467 void resolve(T); 10468 10469 Resolvable!T then(void delegate(T)); // returns a new promise 10470 Resolvable!T error(Throwable); // js catch 10471 Resolvable!T completed(); // js finally 10472 10473 } 10474 10475 /++ 10476 Runs `work` in a helper thread and sends its return value back to the main gui 10477 thread as the argument to `uponCompletion`. If `work` throws, the exception is 10478 sent to the `uponThrown` if given, or if null, rethrown from the event loop to 10479 kill the program. 10480 10481 You can call reportProgress(position, max, message) to update your parent window 10482 on your progress. 10483 10484 I should also use `shared` methods. FIXME 10485 10486 History: 10487 Added March 6, 2021 (dub version 9.3). 10488 +/ 10489 void runInWorkerThread(T)(T delegate(Task) work, void delegate(T) uponCompletion) { 10490 uponCompletion(work(null)); 10491 } 10492 10493 +/ 10494 10495 /// Used internal to dispatch events to various classes. 10496 interface CapableOfHandlingNativeEvent { 10497 NativeEventHandler getNativeEventHandler(); 10498 10499 /*private*//*protected*/ __gshared CapableOfHandlingNativeEvent[NativeWindowHandle] nativeHandleMapping; 10500 10501 version(X11) { 10502 // if this is impossible, you are allowed to just throw from it 10503 // Note: if you call it from another object, set a flag cuz the manger will call you again 10504 void recreateAfterDisconnect(); 10505 // discard any *connection specific* state, but keep enough that you 10506 // can be recreated if possible. discardConnectionState() is always called immediately 10507 // before recreateAfterDisconnect(), so you can set a flag there to decide if 10508 // you need initialization order 10509 void discardConnectionState(); 10510 } 10511 } 10512 10513 version(X11) 10514 /++ 10515 State of keys on mouse events, especially motion. 10516 10517 Do not trust the actual integer values in this, they are platform-specific. Always use the names. 10518 +/ 10519 enum ModifierState : uint { 10520 shift = 1, /// 10521 capsLock = 2, /// 10522 ctrl = 4, /// 10523 alt = 8, /// Not always available on Windows 10524 windows = 64, /// ditto 10525 numLock = 16, /// 10526 10527 leftButtonDown = 256, /// these aren't available on Windows for key events, so don't use them for that unless your app is X only. 10528 middleButtonDown = 512, /// ditto 10529 rightButtonDown = 1024, /// ditto 10530 } 10531 else version(Windows) 10532 /// ditto 10533 enum ModifierState : uint { 10534 shift = 4, /// 10535 ctrl = 8, /// 10536 10537 // i'm not sure if the next two are available 10538 alt = 256, /// not always available on Windows 10539 windows = 512, /// ditto 10540 10541 capsLock = 1024, /// 10542 numLock = 2048, /// 10543 10544 leftButtonDown = 1, /// not available on key events 10545 middleButtonDown = 16, /// ditto 10546 rightButtonDown = 2, /// ditto 10547 10548 backButtonDown = 0x20, /// not available on X 10549 forwardButtonDown = 0x40, /// ditto 10550 } 10551 else version(OSXCocoa) 10552 // FIXME FIXME NotYetImplementedException 10553 enum ModifierState : uint { 10554 shift = 1, /// 10555 capsLock = 2, /// 10556 ctrl = 4, /// 10557 alt = 8, /// Not always available on Windows 10558 windows = 64, /// ditto 10559 numLock = 16, /// 10560 10561 leftButtonDown = 256, /// these aren't available on Windows for key events, so don't use them for that unless your app is X only. 10562 middleButtonDown = 512, /// ditto 10563 rightButtonDown = 1024, /// ditto 10564 } 10565 10566 /// The names assume a right-handed mouse. These are bitwise combined on the events that use them. 10567 enum MouseButton : int { 10568 none = 0, 10569 left = 1, /// 10570 right = 2, /// 10571 middle = 4, /// 10572 wheelUp = 8, /// 10573 wheelDown = 16, /// 10574 backButton = 32, /// often found on the thumb and used for back in browsers 10575 forwardButton = 64, /// often found on the thumb and used for forward in browsers 10576 } 10577 10578 version(X11) { 10579 // FIXME: match ASCII whenever we can. Most of it is already there, 10580 // but there's a few exceptions and mismatches with Windows 10581 10582 /// Do not trust the numeric values as they are platform-specific. Always use the symbolic name. 10583 enum Key { 10584 Escape = 0xff1b, /// 10585 F1 = 0xffbe, /// 10586 F2 = 0xffbf, /// 10587 F3 = 0xffc0, /// 10588 F4 = 0xffc1, /// 10589 F5 = 0xffc2, /// 10590 F6 = 0xffc3, /// 10591 F7 = 0xffc4, /// 10592 F8 = 0xffc5, /// 10593 F9 = 0xffc6, /// 10594 F10 = 0xffc7, /// 10595 F11 = 0xffc8, /// 10596 F12 = 0xffc9, /// 10597 PrintScreen = 0xff61, /// 10598 ScrollLock = 0xff14, /// 10599 Pause = 0xff13, /// 10600 Grave = 0x60, /// The $(BACKTICK) ~ key 10601 // number keys across the top of the keyboard 10602 N1 = 0x31, /// Number key atop the keyboard 10603 N2 = 0x32, /// 10604 N3 = 0x33, /// 10605 N4 = 0x34, /// 10606 N5 = 0x35, /// 10607 N6 = 0x36, /// 10608 N7 = 0x37, /// 10609 N8 = 0x38, /// 10610 N9 = 0x39, /// 10611 N0 = 0x30, /// 10612 Dash = 0x2d, /// 10613 Equals = 0x3d, /// 10614 Backslash = 0x5c, /// The \ | key 10615 Backspace = 0xff08, /// 10616 Insert = 0xff63, /// 10617 Home = 0xff50, /// 10618 PageUp = 0xff55, /// 10619 Delete = 0xffff, /// 10620 End = 0xff57, /// 10621 PageDown = 0xff56, /// 10622 Up = 0xff52, /// 10623 Down = 0xff54, /// 10624 Left = 0xff51, /// 10625 Right = 0xff53, /// 10626 10627 Tab = 0xff09, /// 10628 Q = 0x71, /// 10629 W = 0x77, /// 10630 E = 0x65, /// 10631 R = 0x72, /// 10632 T = 0x74, /// 10633 Y = 0x79, /// 10634 U = 0x75, /// 10635 I = 0x69, /// 10636 O = 0x6f, /// 10637 P = 0x70, /// 10638 LeftBracket = 0x5b, /// the [ { key 10639 RightBracket = 0x5d, /// the ] } key 10640 CapsLock = 0xffe5, /// 10641 A = 0x61, /// 10642 S = 0x73, /// 10643 D = 0x64, /// 10644 F = 0x66, /// 10645 G = 0x67, /// 10646 H = 0x68, /// 10647 J = 0x6a, /// 10648 K = 0x6b, /// 10649 L = 0x6c, /// 10650 Semicolon = 0x3b, /// 10651 Apostrophe = 0x27, /// 10652 Enter = 0xff0d, /// 10653 Shift = 0xffe1, /// 10654 Z = 0x7a, /// 10655 X = 0x78, /// 10656 C = 0x63, /// 10657 V = 0x76, /// 10658 B = 0x62, /// 10659 N = 0x6e, /// 10660 M = 0x6d, /// 10661 Comma = 0x2c, /// 10662 Period = 0x2e, /// 10663 Slash = 0x2f, /// the / ? key 10664 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 10665 Ctrl = 0xffe3, /// 10666 Windows = 0xffeb, /// 10667 Alt = 0xffe9, /// 10668 Space = 0x20, /// 10669 Alt_r = 0xffea, /// ditto of shift_r 10670 Windows_r = 0xffec, /// 10671 Menu = 0xff67, /// 10672 Ctrl_r = 0xffe4, /// 10673 10674 NumLock = 0xff7f, /// 10675 Divide = 0xffaf, /// The / key on the number pad 10676 Multiply = 0xffaa, /// The * key on the number pad 10677 Minus = 0xffad, /// The - key on the number pad 10678 Plus = 0xffab, /// The + key on the number pad 10679 PadEnter = 0xff8d, /// Numberpad enter key 10680 Pad1 = 0xff9c, /// Numberpad keys 10681 Pad2 = 0xff99, /// 10682 Pad3 = 0xff9b, /// 10683 Pad4 = 0xff96, /// 10684 Pad5 = 0xff9d, /// 10685 Pad6 = 0xff98, /// 10686 Pad7 = 0xff95, /// 10687 Pad8 = 0xff97, /// 10688 Pad9 = 0xff9a, /// 10689 Pad0 = 0xff9e, /// 10690 PadDot = 0xff9f, /// 10691 } 10692 } else version(Windows) { 10693 // the character here is for en-us layouts and for illustration only 10694 // if you actually want to get characters, wait for character events 10695 // (the argument to your event handler is simply a dchar) 10696 // those will be converted by the OS for the right locale. 10697 10698 enum Key { 10699 Escape = 0x1b, 10700 F1 = 0x70, 10701 F2 = 0x71, 10702 F3 = 0x72, 10703 F4 = 0x73, 10704 F5 = 0x74, 10705 F6 = 0x75, 10706 F7 = 0x76, 10707 F8 = 0x77, 10708 F9 = 0x78, 10709 F10 = 0x79, 10710 F11 = 0x7a, 10711 F12 = 0x7b, 10712 PrintScreen = 0x2c, 10713 ScrollLock = 0x91, 10714 Pause = 0x13, 10715 Grave = 0xc0, 10716 // number keys across the top of the keyboard 10717 N1 = 0x31, 10718 N2 = 0x32, 10719 N3 = 0x33, 10720 N4 = 0x34, 10721 N5 = 0x35, 10722 N6 = 0x36, 10723 N7 = 0x37, 10724 N8 = 0x38, 10725 N9 = 0x39, 10726 N0 = 0x30, 10727 Dash = 0xbd, 10728 Equals = 0xbb, 10729 Backslash = 0xdc, 10730 Backspace = 0x08, 10731 Insert = 0x2d, 10732 Home = 0x24, 10733 PageUp = 0x21, 10734 Delete = 0x2e, 10735 End = 0x23, 10736 PageDown = 0x22, 10737 Up = 0x26, 10738 Down = 0x28, 10739 Left = 0x25, 10740 Right = 0x27, 10741 10742 Tab = 0x09, 10743 Q = 0x51, 10744 W = 0x57, 10745 E = 0x45, 10746 R = 0x52, 10747 T = 0x54, 10748 Y = 0x59, 10749 U = 0x55, 10750 I = 0x49, 10751 O = 0x4f, 10752 P = 0x50, 10753 LeftBracket = 0xdb, 10754 RightBracket = 0xdd, 10755 CapsLock = 0x14, 10756 A = 0x41, 10757 S = 0x53, 10758 D = 0x44, 10759 F = 0x46, 10760 G = 0x47, 10761 H = 0x48, 10762 J = 0x4a, 10763 K = 0x4b, 10764 L = 0x4c, 10765 Semicolon = 0xba, 10766 Apostrophe = 0xde, 10767 Enter = 0x0d, 10768 Shift = 0x10, 10769 Z = 0x5a, 10770 X = 0x58, 10771 C = 0x43, 10772 V = 0x56, 10773 B = 0x42, 10774 N = 0x4e, 10775 M = 0x4d, 10776 Comma = 0xbc, 10777 Period = 0xbe, 10778 Slash = 0xbf, 10779 Shift_r = 0xa1, // FIXME Note: this isn't sent on all computers, sometimes it just sends Shift, so don't rely on it 10780 Ctrl = 0x11, 10781 Windows = 0x5b, 10782 Alt = -5, // FIXME 10783 Space = 0x20, 10784 Alt_r = 0xffea, // ditto of shift_r 10785 Windows_r = 0x5c, // ditto of shift_r 10786 Menu = 0x5d, 10787 Ctrl_r = 0xa3, // ditto of shift_r 10788 10789 NumLock = 0x90, 10790 Divide = 0x6f, 10791 Multiply = 0x6a, 10792 Minus = 0x6d, 10793 Plus = 0x6b, 10794 PadEnter = -8, // FIXME 10795 Pad1 = 0x61, 10796 Pad2 = 0x62, 10797 Pad3 = 0x63, 10798 Pad4 = 0x64, 10799 Pad5 = 0x65, 10800 Pad6 = 0x66, 10801 Pad7 = 0x67, 10802 Pad8 = 0x68, 10803 Pad9 = 0x69, 10804 Pad0 = 0x60, 10805 PadDot = 0x6e, 10806 } 10807 10808 // I'm keeping this around for reference purposes 10809 // ideally all these buttons will be listed for all platforms, 10810 // but now now I'm just focusing on my US keyboard 10811 version(none) 10812 enum Key { 10813 LBUTTON = 0x01, 10814 RBUTTON = 0x02, 10815 CANCEL = 0x03, 10816 MBUTTON = 0x04, 10817 //static if (_WIN32_WINNT > = 0x500) { 10818 XBUTTON1 = 0x05, 10819 XBUTTON2 = 0x06, 10820 //} 10821 BACK = 0x08, 10822 TAB = 0x09, 10823 CLEAR = 0x0C, 10824 RETURN = 0x0D, 10825 SHIFT = 0x10, 10826 CONTROL = 0x11, 10827 MENU = 0x12, 10828 PAUSE = 0x13, 10829 CAPITAL = 0x14, 10830 KANA = 0x15, 10831 HANGEUL = 0x15, 10832 HANGUL = 0x15, 10833 JUNJA = 0x17, 10834 FINAL = 0x18, 10835 HANJA = 0x19, 10836 KANJI = 0x19, 10837 ESCAPE = 0x1B, 10838 CONVERT = 0x1C, 10839 NONCONVERT = 0x1D, 10840 ACCEPT = 0x1E, 10841 MODECHANGE = 0x1F, 10842 SPACE = 0x20, 10843 PRIOR = 0x21, 10844 NEXT = 0x22, 10845 END = 0x23, 10846 HOME = 0x24, 10847 LEFT = 0x25, 10848 UP = 0x26, 10849 RIGHT = 0x27, 10850 DOWN = 0x28, 10851 SELECT = 0x29, 10852 PRINT = 0x2A, 10853 EXECUTE = 0x2B, 10854 SNAPSHOT = 0x2C, 10855 INSERT = 0x2D, 10856 DELETE = 0x2E, 10857 HELP = 0x2F, 10858 LWIN = 0x5B, 10859 RWIN = 0x5C, 10860 APPS = 0x5D, 10861 SLEEP = 0x5F, 10862 NUMPAD0 = 0x60, 10863 NUMPAD1 = 0x61, 10864 NUMPAD2 = 0x62, 10865 NUMPAD3 = 0x63, 10866 NUMPAD4 = 0x64, 10867 NUMPAD5 = 0x65, 10868 NUMPAD6 = 0x66, 10869 NUMPAD7 = 0x67, 10870 NUMPAD8 = 0x68, 10871 NUMPAD9 = 0x69, 10872 MULTIPLY = 0x6A, 10873 ADD = 0x6B, 10874 SEPARATOR = 0x6C, 10875 SUBTRACT = 0x6D, 10876 DECIMAL = 0x6E, 10877 DIVIDE = 0x6F, 10878 F1 = 0x70, 10879 F2 = 0x71, 10880 F3 = 0x72, 10881 F4 = 0x73, 10882 F5 = 0x74, 10883 F6 = 0x75, 10884 F7 = 0x76, 10885 F8 = 0x77, 10886 F9 = 0x78, 10887 F10 = 0x79, 10888 F11 = 0x7A, 10889 F12 = 0x7B, 10890 F13 = 0x7C, 10891 F14 = 0x7D, 10892 F15 = 0x7E, 10893 F16 = 0x7F, 10894 F17 = 0x80, 10895 F18 = 0x81, 10896 F19 = 0x82, 10897 F20 = 0x83, 10898 F21 = 0x84, 10899 F22 = 0x85, 10900 F23 = 0x86, 10901 F24 = 0x87, 10902 NUMLOCK = 0x90, 10903 SCROLL = 0x91, 10904 LSHIFT = 0xA0, 10905 RSHIFT = 0xA1, 10906 LCONTROL = 0xA2, 10907 RCONTROL = 0xA3, 10908 LMENU = 0xA4, 10909 RMENU = 0xA5, 10910 //static if (_WIN32_WINNT > = 0x500) { 10911 BROWSER_BACK = 0xA6, 10912 BROWSER_FORWARD = 0xA7, 10913 BROWSER_REFRESH = 0xA8, 10914 BROWSER_STOP = 0xA9, 10915 BROWSER_SEARCH = 0xAA, 10916 BROWSER_FAVORITES = 0xAB, 10917 BROWSER_HOME = 0xAC, 10918 VOLUME_MUTE = 0xAD, 10919 VOLUME_DOWN = 0xAE, 10920 VOLUME_UP = 0xAF, 10921 MEDIA_NEXT_TRACK = 0xB0, 10922 MEDIA_PREV_TRACK = 0xB1, 10923 MEDIA_STOP = 0xB2, 10924 MEDIA_PLAY_PAUSE = 0xB3, 10925 LAUNCH_MAIL = 0xB4, 10926 LAUNCH_MEDIA_SELECT = 0xB5, 10927 LAUNCH_APP1 = 0xB6, 10928 LAUNCH_APP2 = 0xB7, 10929 //} 10930 OEM_1 = 0xBA, 10931 //static if (_WIN32_WINNT > = 0x500) { 10932 OEM_PLUS = 0xBB, 10933 OEM_COMMA = 0xBC, 10934 OEM_MINUS = 0xBD, 10935 OEM_PERIOD = 0xBE, 10936 //} 10937 OEM_2 = 0xBF, 10938 OEM_3 = 0xC0, 10939 OEM_4 = 0xDB, 10940 OEM_5 = 0xDC, 10941 OEM_6 = 0xDD, 10942 OEM_7 = 0xDE, 10943 OEM_8 = 0xDF, 10944 //static if (_WIN32_WINNT > = 0x500) { 10945 OEM_102 = 0xE2, 10946 //} 10947 PROCESSKEY = 0xE5, 10948 //static if (_WIN32_WINNT > = 0x500) { 10949 PACKET = 0xE7, 10950 //} 10951 ATTN = 0xF6, 10952 CRSEL = 0xF7, 10953 EXSEL = 0xF8, 10954 EREOF = 0xF9, 10955 PLAY = 0xFA, 10956 ZOOM = 0xFB, 10957 NONAME = 0xFC, 10958 PA1 = 0xFD, 10959 OEM_CLEAR = 0xFE, 10960 } 10961 10962 } else version(OSXCocoa) { 10963 enum Key { 10964 Escape = 53, 10965 F1 = 122, 10966 F2 = 120, 10967 F3 = 99, 10968 F4 = 118, 10969 F5 = 96, 10970 F6 = 97, 10971 F7 = 98, 10972 F8 = 100, 10973 F9 = 101, 10974 F10 = 109, 10975 F11 = 103, 10976 F12 = 111, 10977 PrintScreen = 105, 10978 ScrollLock = 107, 10979 Pause = 113, 10980 Grave = 50, 10981 // number keys across the top of the keyboard 10982 N1 = 18, 10983 N2 = 19, 10984 N3 = 20, 10985 N4 = 21, 10986 N5 = 23, 10987 N6 = 22, 10988 N7 = 26, 10989 N8 = 28, 10990 N9 = 25, 10991 N0 = 29, 10992 Dash = 27, 10993 Equals = 24, 10994 Backslash = 42, 10995 Backspace = 51, 10996 Insert = 114, 10997 Home = 115, 10998 PageUp = 116, 10999 Delete = 117, 11000 End = 119, 11001 PageDown = 121, 11002 Up = 126, 11003 Down = 125, 11004 Left = 123, 11005 Right = 124, 11006 11007 Tab = 48, 11008 Q = 12, 11009 W = 13, 11010 E = 14, 11011 R = 15, 11012 T = 17, 11013 Y = 16, 11014 U = 32, 11015 I = 34, 11016 O = 31, 11017 P = 35, 11018 LeftBracket = 33, 11019 RightBracket = 30, 11020 CapsLock = 57, 11021 A = 0, 11022 S = 1, 11023 D = 2, 11024 F = 3, 11025 G = 5, 11026 H = 4, 11027 J = 38, 11028 K = 40, 11029 L = 37, 11030 Semicolon = 41, 11031 Apostrophe = 39, 11032 Enter = 36, 11033 Shift = 56, 11034 Z = 6, 11035 X = 7, 11036 C = 8, 11037 V = 9, 11038 B = 11, 11039 N = 45, 11040 M = 46, 11041 Comma = 43, 11042 Period = 47, 11043 Slash = 44, 11044 Shift_r = -4, // FIXME Note: this isn't sent on all computers, sometimes it just sends Shift, so don't rely on it 11045 Ctrl = 59, 11046 Windows = 55, 11047 Alt = 58, 11048 Space = 49, 11049 Alt_r = -3, // ditto of shift_r 11050 Windows_r = -2, 11051 Menu = 110, 11052 Ctrl_r = -1, 11053 11054 NumLock = 1, 11055 Divide = 75, 11056 Multiply = 67, 11057 Minus = 78, 11058 Plus = 69, 11059 PadEnter = 76, 11060 Pad1 = 83, 11061 Pad2 = 84, 11062 Pad3 = 85, 11063 Pad4 = 86, 11064 Pad5 = 87, 11065 Pad6 = 88, 11066 Pad7 = 89, 11067 Pad8 = 91, 11068 Pad9 = 92, 11069 Pad0 = 82, 11070 PadDot = 65, 11071 } 11072 11073 } 11074 11075 /* Additional utilities */ 11076 11077 11078 Color fromHsl(real h, real s, real l) { 11079 return arsd.color.fromHsl([h,s,l]); 11080 } 11081 11082 11083 11084 /* ********** What follows is the system-specific implementations *********/ 11085 version(Windows) { 11086 11087 11088 // helpers for making HICONs from MemoryImages 11089 class WindowsIcon { 11090 struct Win32Icon(int colorCount) { 11091 align(1): 11092 uint biSize; 11093 int biWidth; 11094 int biHeight; 11095 ushort biPlanes; 11096 ushort biBitCount; 11097 uint biCompression; 11098 uint biSizeImage; 11099 int biXPelsPerMeter; 11100 int biYPelsPerMeter; 11101 uint biClrUsed; 11102 uint biClrImportant; 11103 RGBQUAD[colorCount] biColors; 11104 /* Pixels: 11105 Uint8 pixels[] 11106 */ 11107 /* Mask: 11108 Uint8 mask[] 11109 */ 11110 11111 // FIXME: this sux, needs to be dynamic 11112 ubyte[/*4096*/ 256*256*4 + 256*256/8] data; 11113 11114 void fromMemoryImage(MemoryImage mi, out int icon_len, out int width, out int height) { 11115 width = mi.width; 11116 height = mi.height; 11117 11118 auto indexedImage = cast(IndexedImage) mi; 11119 if(indexedImage is null) 11120 indexedImage = quantize(mi.getAsTrueColorImage()); 11121 11122 assert(width <= 256, "image too wide"); 11123 assert(height <= 256, "image too tall"); 11124 assert(width %8 == 0, "image not multiple of 8 width"); // i don't want padding nor do i want the and mask to get fancy 11125 assert(height %4 == 0, "image not multiple of 4 height"); 11126 11127 int icon_plen = height*((width+3)&~3); 11128 int icon_mlen = height*((((width+7)/8)+3)&~3); 11129 icon_len = 40+icon_plen+icon_mlen + cast(int) RGBQUAD.sizeof * colorCount; 11130 11131 biSize = 40; 11132 biWidth = width; 11133 biHeight = height*2; 11134 biPlanes = 1; 11135 biBitCount = 8; 11136 biSizeImage = icon_plen+icon_mlen; 11137 11138 int offset = 0; 11139 int andOff = icon_plen * 8; // the and offset is in bits 11140 for(int y = height - 1; y >= 0; y--) { 11141 int off2 = y * width; 11142 foreach(x; 0 .. width) { 11143 const b = indexedImage.data[off2 + x]; 11144 data[offset] = b; 11145 offset++; 11146 11147 const andBit = andOff % 8; 11148 const andIdx = andOff / 8; 11149 assert(b < indexedImage.palette.length); 11150 // this is anded to the destination, since and 0 means erase, 11151 // we want that to be opaque, and 1 for transparent 11152 auto transparent = (indexedImage.palette[b].a <= 127); 11153 data[andIdx] |= (transparent ? (1 << (7-andBit)) : 0); 11154 11155 andOff++; 11156 } 11157 11158 andOff += andOff % 32; 11159 } 11160 11161 foreach(idx, entry; indexedImage.palette) { 11162 if(entry.a > 127) { 11163 biColors[idx].rgbBlue = entry.b; 11164 biColors[idx].rgbGreen = entry.g; 11165 biColors[idx].rgbRed = entry.r; 11166 } else { 11167 biColors[idx].rgbBlue = 255; 11168 biColors[idx].rgbGreen = 255; 11169 biColors[idx].rgbRed = 255; 11170 } 11171 } 11172 11173 /* 11174 data[0..icon_plen] = getFlippedUnfilteredDatastream(png); 11175 data[icon_plen..icon_plen+icon_mlen] = getANDMask(png); 11176 //icon_win32.biColors[1] = Win32Icon.RGBQUAD(0,255,0,0); 11177 auto pngMap = fetchPaletteWin32(png); 11178 biColors[0..pngMap.length] = pngMap[]; 11179 */ 11180 } 11181 } 11182 11183 11184 Win32Icon!(256) icon_win32; 11185 11186 11187 this(MemoryImage mi) { 11188 int icon_len, width, height; 11189 11190 icon_win32.fromMemoryImage(mi, icon_len, width, height); 11191 11192 /* 11193 PNG* png = readPnpngData); 11194 PNGHeader pngh = getHeader(png); 11195 void* icon_win32; 11196 if(pngh.depth == 4) { 11197 auto i = new Win32Icon!(16); 11198 i.fromPNG(png, pngh, icon_len, width, height); 11199 icon_win32 = i; 11200 } 11201 else if(pngh.depth == 8) { 11202 auto i = new Win32Icon!(256); 11203 i.fromPNG(png, pngh, icon_len, width, height); 11204 icon_win32 = i; 11205 } else assert(0); 11206 */ 11207 11208 hIcon = CreateIconFromResourceEx(cast(ubyte*) &icon_win32, icon_len, true, 0x00030000, width, height, 0); 11209 11210 if(hIcon is null) throw new WindowsApiException("CreateIconFromResourceEx", GetLastError()); 11211 } 11212 11213 ~this() { 11214 if(!thisIsGuiThread) return; // FIXME: leaks if multithreaded gc 11215 DestroyIcon(hIcon); 11216 } 11217 11218 HICON hIcon; 11219 } 11220 11221 11222 11223 11224 11225 11226 alias int delegate(HWND, UINT, WPARAM, LPARAM, out int) NativeEventHandler; 11227 alias HWND NativeWindowHandle; 11228 11229 extern(Windows) 11230 LRESULT WndProc(HWND hWnd, UINT iMessage, WPARAM wParam, LPARAM lParam) nothrow { 11231 try { 11232 if(SimpleWindow.handleNativeGlobalEvent !is null) { 11233 // it returns zero if the message is handled, so we won't do anything more there 11234 // do I like that though? 11235 int mustReturn; 11236 auto ret = SimpleWindow.handleNativeGlobalEvent(hWnd, iMessage, wParam, lParam, mustReturn); 11237 if(mustReturn) 11238 return ret; 11239 } 11240 11241 if(auto window = hWnd in CapableOfHandlingNativeEvent.nativeHandleMapping) { 11242 if(window.getNativeEventHandler !is null) { 11243 int mustReturn; 11244 auto ret = window.getNativeEventHandler()(hWnd, iMessage, wParam, lParam, mustReturn); 11245 if(mustReturn) 11246 return ret; 11247 } 11248 if(auto w = cast(SimpleWindow) (*window)) 11249 return w.windowProcedure(hWnd, iMessage, wParam, lParam); 11250 else 11251 return DefWindowProc(hWnd, iMessage, wParam, lParam); 11252 } else { 11253 return DefWindowProc(hWnd, iMessage, wParam, lParam); 11254 } 11255 } catch (Exception e) { 11256 try { 11257 sdpy_abort(e); 11258 return 0; 11259 } catch(Exception e) { assert(0); } 11260 } 11261 } 11262 11263 void sdpy_abort(Throwable e) nothrow { 11264 try 11265 MessageBoxA(null, (e.toString() ~ "\0").ptr, "Exception caught in WndProc", 0); 11266 catch(Exception e) 11267 MessageBoxA(null, "Exception.toString threw too!", "Exception caught in WndProc", 0); 11268 ExitProcess(1); 11269 } 11270 11271 mixin template NativeScreenPainterImplementation() { 11272 HDC hdc; 11273 HWND hwnd; 11274 //HDC windowHdc; 11275 HBITMAP oldBmp; 11276 11277 void create(PaintingHandle window) { 11278 hwnd = window; 11279 11280 if(auto sw = cast(SimpleWindow) this.window) { 11281 // drawing on a window, double buffer 11282 auto windowHdc = GetDC(hwnd); 11283 11284 auto buffer = sw.impl.buffer; 11285 if(buffer is null) { 11286 hdc = windowHdc; 11287 windowDc = true; 11288 } else { 11289 hdc = CreateCompatibleDC(windowHdc); 11290 11291 ReleaseDC(hwnd, windowHdc); 11292 11293 oldBmp = SelectObject(hdc, buffer); 11294 } 11295 } else { 11296 // drawing on something else, draw directly 11297 hdc = CreateCompatibleDC(null); 11298 SelectObject(hdc, window); 11299 } 11300 11301 // X doesn't draw a text background, so neither should we 11302 SetBkMode(hdc, TRANSPARENT); 11303 11304 ensureDefaultFontLoaded(); 11305 11306 if(defaultGuiFont) { 11307 SelectObject(hdc, defaultGuiFont); 11308 // DeleteObject(defaultGuiFont); 11309 } 11310 } 11311 11312 static HFONT defaultGuiFont; 11313 static void ensureDefaultFontLoaded() { 11314 static bool triedDefaultGuiFont = false; 11315 if(!triedDefaultGuiFont) { 11316 NONCLIENTMETRICS params; 11317 params.cbSize = params.sizeof; 11318 if(SystemParametersInfo(SPI_GETNONCLIENTMETRICS, params.sizeof, ¶ms, 0)) { 11319 defaultGuiFont = CreateFontIndirect(¶ms.lfMessageFont); 11320 } 11321 triedDefaultGuiFont = true; 11322 } 11323 } 11324 11325 private OperatingSystemFont _activeFont; 11326 11327 void setFont(OperatingSystemFont font) { 11328 _activeFont = font; 11329 if(font && font.font) { 11330 if(SelectObject(hdc, font.font) == HGDI_ERROR) { 11331 // error... how to handle tho? 11332 } else { 11333 11334 } 11335 } 11336 else if(defaultGuiFont) 11337 SelectObject(hdc, defaultGuiFont); 11338 } 11339 11340 arsd.color.Rectangle _clipRectangle; 11341 11342 void setClipRectangle(int x, int y, int width, int height) { 11343 auto old = _clipRectangle; 11344 _clipRectangle = arsd.color.Rectangle(Point(x, y), Size(width, height)); 11345 if(old == _clipRectangle) 11346 return; 11347 11348 if(width == 0 || height == 0) { 11349 SelectClipRgn(hdc, null); 11350 } else { 11351 auto region = CreateRectRgn(x, y, x + width, y + height); 11352 SelectClipRgn(hdc, region); 11353 DeleteObject(region); 11354 } 11355 } 11356 11357 11358 // just because we can on Windows... 11359 //void create(Image image); 11360 11361 void invalidateRect(Rectangle invalidRect) { 11362 RECT rect; 11363 rect.left = invalidRect.left; 11364 rect.right = invalidRect.right; 11365 rect.top = invalidRect.top; 11366 rect.bottom = invalidRect.bottom; 11367 InvalidateRect(hwnd, &rect, false); 11368 } 11369 bool manualInvalidations; 11370 11371 void dispose() { 11372 // FIXME: this.window.width/height is probably wrong 11373 // BitBlt(windowHdc, 0, 0, this.window.width, this.window.height, hdc, 0, 0, SRCCOPY); 11374 // ReleaseDC(hwnd, windowHdc); 11375 11376 // FIXME: it shouldn't invalidate the whole thing in all cases... it would be ideal to do this right 11377 if(cast(SimpleWindow) this.window) { 11378 if(!manualInvalidations) 11379 InvalidateRect(hwnd, cast(RECT*)null, false); // no need to erase bg as the whole thing gets bitblt'd ove 11380 } 11381 11382 if(originalPen !is null) 11383 SelectObject(hdc, originalPen); 11384 if(currentPen !is null) 11385 DeleteObject(currentPen); 11386 if(originalBrush !is null) 11387 SelectObject(hdc, originalBrush); 11388 if(currentBrush !is null) 11389 DeleteObject(currentBrush); 11390 11391 SelectObject(hdc, oldBmp); 11392 11393 if(windowDc) 11394 ReleaseDC(hwnd, hdc); 11395 else 11396 DeleteDC(hdc); 11397 11398 if(window.paintingFinishedDg !is null) 11399 window.paintingFinishedDg()(); 11400 } 11401 11402 bool windowDc; 11403 HPEN originalPen; 11404 HPEN currentPen; 11405 11406 Pen _activePen; 11407 11408 Color _outlineColor; 11409 11410 @property void pen(Pen p) { 11411 _activePen = p; 11412 _outlineColor = p.color; 11413 11414 HPEN pen; 11415 if(p.color.a == 0) { 11416 pen = GetStockObject(NULL_PEN); 11417 } else { 11418 int style = PS_SOLID; 11419 final switch(p.style) { 11420 case Pen.Style.Solid: 11421 style = PS_SOLID; 11422 break; 11423 case Pen.Style.Dashed: 11424 style = PS_DASH; 11425 break; 11426 case Pen.Style.Dotted: 11427 style = PS_DOT; 11428 break; 11429 } 11430 pen = CreatePen(style, p.width, RGB(p.color.r, p.color.g, p.color.b)); 11431 } 11432 auto orig = SelectObject(hdc, pen); 11433 if(originalPen is null) 11434 originalPen = orig; 11435 11436 if(currentPen !is null) 11437 DeleteObject(currentPen); 11438 11439 currentPen = pen; 11440 11441 // the outline is like a foreground since it's done that way on X 11442 SetTextColor(hdc, RGB(p.color.r, p.color.g, p.color.b)); 11443 11444 } 11445 11446 @property void rasterOp(RasterOp op) { 11447 int mode; 11448 final switch(op) { 11449 case RasterOp.normal: 11450 mode = R2_COPYPEN; 11451 break; 11452 case RasterOp.xor: 11453 mode = R2_XORPEN; 11454 break; 11455 } 11456 SetROP2(hdc, mode); 11457 } 11458 11459 HBRUSH originalBrush; 11460 HBRUSH currentBrush; 11461 Color _fillColor = Color(1, 1, 1, 1); // what are the odds that they'd set this?? 11462 @property void fillColor(Color c) { 11463 if(c == _fillColor) 11464 return; 11465 _fillColor = c; 11466 HBRUSH brush; 11467 if(c.a == 0) { 11468 brush = GetStockObject(HOLLOW_BRUSH); 11469 } else { 11470 brush = CreateSolidBrush(RGB(c.r, c.g, c.b)); 11471 } 11472 auto orig = SelectObject(hdc, brush); 11473 if(originalBrush is null) 11474 originalBrush = orig; 11475 11476 if(currentBrush !is null) 11477 DeleteObject(currentBrush); 11478 11479 currentBrush = brush; 11480 11481 // background color is NOT set because X doesn't draw text backgrounds 11482 // SetBkColor(hdc, RGB(255, 255, 255)); 11483 } 11484 11485 void drawImage(int x, int y, Image i, int ix, int iy, int w, int h) { 11486 BITMAP bm; 11487 11488 HDC hdcMem = CreateCompatibleDC(hdc); 11489 HBITMAP hbmOld = SelectObject(hdcMem, i.handle); 11490 11491 GetObject(i.handle, bm.sizeof, &bm); 11492 11493 // or should I AlphaBlend!??!?! 11494 BitBlt(hdc, x, y, w /* bm.bmWidth */, /*bm.bmHeight*/ h, hdcMem, ix, iy, SRCCOPY); 11495 11496 SelectObject(hdcMem, hbmOld); 11497 DeleteDC(hdcMem); 11498 } 11499 11500 void drawPixmap(Sprite s, int x, int y, int ix, int iy, int w, int h) { 11501 BITMAP bm; 11502 11503 HDC hdcMem = CreateCompatibleDC(hdc); 11504 HBITMAP hbmOld = SelectObject(hdcMem, s.handle); 11505 11506 GetObject(s.handle, bm.sizeof, &bm); 11507 11508 version(CRuntime_DigitalMars) goto noalpha; 11509 11510 // or should I AlphaBlend!??!?! note it is supposed to be premultiplied http://www.fengyuan.com/article/alphablend.html 11511 if(s.enableAlpha) { 11512 auto dw = w ? w : bm.bmWidth; 11513 auto dh = h ? h : bm.bmHeight; 11514 BLENDFUNCTION bf; 11515 bf.BlendOp = AC_SRC_OVER; 11516 bf.SourceConstantAlpha = 255; 11517 bf.AlphaFormat = AC_SRC_ALPHA; 11518 AlphaBlend(hdc, x, y, dw, dh, hdcMem, ix, iy, dw, dh, bf); 11519 } else { 11520 noalpha: 11521 BitBlt(hdc, x, y, w ? w : bm.bmWidth, h ? h : bm.bmHeight, hdcMem, ix, iy, SRCCOPY); 11522 } 11523 11524 SelectObject(hdcMem, hbmOld); 11525 DeleteDC(hdcMem); 11526 } 11527 11528 Size textSize(scope const(char)[] text) { 11529 bool dummyX; 11530 if(text.length == 0) { 11531 text = " "; 11532 dummyX = true; 11533 } 11534 RECT rect; 11535 WCharzBuffer buffer = WCharzBuffer(text); 11536 DrawTextW(hdc, buffer.ptr, cast(int) buffer.length, &rect, DT_CALCRECT | DT_NOPREFIX); 11537 return Size(dummyX ? 0 : rect.right, rect.bottom); 11538 } 11539 11540 void drawText(int x, int y, int x2, int y2, scope const(char)[] text, uint alignment) { 11541 if(text.length && text[$-1] == '\n') 11542 text = text[0 .. $-1]; // tailing newlines are weird on windows... 11543 if(text.length && text[$-1] == '\r') 11544 text = text[0 .. $-1]; 11545 11546 WCharzBuffer buffer = WCharzBuffer(text, WindowsStringConversionFlags.convertNewLines); 11547 if(x2 == 0 && y2 == 0) { 11548 TextOutW(hdc, x, y, buffer.ptr, cast(int) buffer.length); 11549 } else { 11550 RECT rect; 11551 rect.left = x; 11552 rect.top = y; 11553 rect.right = x2; 11554 rect.bottom = y2; 11555 11556 uint mode = DT_LEFT; 11557 if(alignment & TextAlignment.Right) 11558 mode = DT_RIGHT; 11559 else if(alignment & TextAlignment.Center) 11560 mode = DT_CENTER; 11561 11562 // FIXME: vcenter on windows only works with single line, but I want it to work in all cases 11563 if(alignment & TextAlignment.VerticalCenter) 11564 mode |= DT_VCENTER | DT_SINGLELINE; 11565 11566 DrawTextW(hdc, buffer.ptr, cast(int) buffer.length, &rect, mode | DT_NOPREFIX); 11567 } 11568 11569 /* 11570 uint mode; 11571 11572 if(alignment & TextAlignment.Center) 11573 mode = TA_CENTER; 11574 11575 SetTextAlign(hdc, mode); 11576 */ 11577 } 11578 11579 int fontHeight() { 11580 TEXTMETRIC metric; 11581 if(GetTextMetricsW(hdc, &metric)) { 11582 return metric.tmHeight; 11583 } 11584 11585 return 16; // idk just guessing here, maybe we should throw 11586 } 11587 11588 void drawPixel(int x, int y) { 11589 SetPixel(hdc, x, y, RGB(_activePen.color.r, _activePen.color.g, _activePen.color.b)); 11590 } 11591 11592 // The basic shapes, outlined 11593 11594 void drawLine(int x1, int y1, int x2, int y2) { 11595 MoveToEx(hdc, x1, y1, null); 11596 LineTo(hdc, x2, y2); 11597 } 11598 11599 void drawRectangle(int x, int y, int width, int height) { 11600 // FIXME: with a wider pen this might not draw quite right. im not sure. 11601 gdi.Rectangle(hdc, x, y, x + width, y + height); 11602 } 11603 11604 /// Arguments are the points of the bounding rectangle 11605 void drawEllipse(int x1, int y1, int x2, int y2) { 11606 Ellipse(hdc, x1, y1, x2, y2); 11607 } 11608 11609 void drawArc(int x1, int y1, int width, int height, int start, int finish) { 11610 if((start % (360*64)) == (finish % (360*64))) 11611 drawEllipse(x1, y1, x1 + width, y1 + height); 11612 else { 11613 import core.stdc.math; 11614 float startAngle = cast(float) start / 64.0 / 180.0 * 3.14159265358979323; 11615 float endAngle = cast(float) finish / 64.0 / 180.0 * 3.14159265358979323; 11616 11617 auto c1 = cast(int) roundf(cos(startAngle) * width / 2 + x1 + width / 2); 11618 auto c2 = cast(int) roundf(-sin(startAngle) * height / 2 + y1 + height / 2); 11619 auto c3 = cast(int) roundf(cos(endAngle) * width / 2 + x1 + width / 2); 11620 auto c4 = cast(int) roundf(-sin(endAngle) * height / 2 + y1 + height / 2); 11621 11622 if(_activePen.color.a) 11623 Arc(hdc, x1, y1, x1 + width + 1, y1 + height + 1, c1, c2, c3, c4); 11624 if(_fillColor.a) 11625 Pie(hdc, x1, y1, x1 + width + 1, y1 + height + 1, c1, c2, c3, c4); 11626 } 11627 } 11628 11629 void drawPolygon(Point[] vertexes) { 11630 POINT[] points; 11631 points.length = vertexes.length; 11632 11633 foreach(i, p; vertexes) { 11634 points[i].x = p.x; 11635 points[i].y = p.y; 11636 } 11637 11638 Polygon(hdc, points.ptr, cast(int) points.length); 11639 } 11640 } 11641 11642 11643 // Mix this into the SimpleWindow class 11644 mixin template NativeSimpleWindowImplementation() { 11645 int curHidden = 0; // counter 11646 __gshared static bool[string] knownWinClasses; 11647 static bool altPressed = false; 11648 11649 HANDLE oldCursor; 11650 11651 void hideCursor () { 11652 if(curHidden == 0) 11653 oldCursor = SetCursor(null); 11654 ++curHidden; 11655 } 11656 11657 void showCursor () { 11658 --curHidden; 11659 if(curHidden == 0) { 11660 SetCursor(currentCursor is null ? oldCursor : currentCursor); // show it immediately without waiting for mouse movement 11661 } 11662 } 11663 11664 11665 int minWidth = 0, minHeight = 0, maxWidth = int.max, maxHeight = int.max; 11666 11667 void setMinSize (int minwidth, int minheight) { 11668 minWidth = minwidth; 11669 minHeight = minheight; 11670 } 11671 void setMaxSize (int maxwidth, int maxheight) { 11672 maxWidth = maxwidth; 11673 maxHeight = maxheight; 11674 } 11675 11676 // FIXME i'm not sure that Windows has this functionality 11677 // though it is nonessential anyway. 11678 void setResizeGranularity (int granx, int grany) {} 11679 11680 ScreenPainter getPainter(bool manualInvalidations) { 11681 return ScreenPainter(this, hwnd, manualInvalidations); 11682 } 11683 11684 HBITMAP buffer; 11685 11686 void setTitle(string title) { 11687 WCharzBuffer bfr = WCharzBuffer(title); 11688 SetWindowTextW(hwnd, bfr.ptr); 11689 } 11690 11691 string getTitle() { 11692 auto len = GetWindowTextLengthW(hwnd); 11693 if (!len) 11694 return null; 11695 wchar[256] tmpBuffer; 11696 wchar[] buffer = (len <= tmpBuffer.length) ? tmpBuffer[] : new wchar[len]; 11697 auto len2 = GetWindowTextW(hwnd, buffer.ptr, cast(int) buffer.length); 11698 auto str = buffer[0 .. len2]; 11699 return makeUtf8StringFromWindowsString(str); 11700 } 11701 11702 void move(int x, int y) { 11703 RECT rect; 11704 GetWindowRect(hwnd, &rect); 11705 // move it while maintaining the same size... 11706 MoveWindow(hwnd, x, y, rect.right - rect.left, rect.bottom - rect.top, true); 11707 } 11708 11709 void resize(int w, int h) { 11710 RECT rect; 11711 GetWindowRect(hwnd, &rect); 11712 11713 RECT client; 11714 GetClientRect(hwnd, &client); 11715 11716 rect.right = rect.right - client.right + w; 11717 rect.bottom = rect.bottom - client.bottom + h; 11718 11719 // same position, new size for the client rectangle 11720 MoveWindow(hwnd, rect.left, rect.top, rect.right, rect.bottom, true); 11721 11722 updateOpenglViewportIfNeeded(w, h); 11723 } 11724 11725 void moveResize (int x, int y, int w, int h) { 11726 // what's given is the client rectangle, we need to adjust 11727 11728 RECT rect; 11729 rect.left = x; 11730 rect.top = y; 11731 rect.right = w + x; 11732 rect.bottom = h + y; 11733 if(!AdjustWindowRect(&rect, GetWindowLong(hwnd, GWL_STYLE), GetMenu(hwnd) !is null)) 11734 throw new WindowsApiException("AdjustWindowRect", GetLastError()); 11735 11736 MoveWindow(hwnd, rect.left, rect.top, rect.right - rect.left, rect.bottom - rect.top, true); 11737 updateOpenglViewportIfNeeded(w, h); 11738 if (windowResized !is null) windowResized(w, h); 11739 } 11740 11741 version(without_opengl) {} else { 11742 HGLRC ghRC; 11743 HDC ghDC; 11744 } 11745 11746 void createWindow(int width, int height, string title, OpenGlOptions opengl, SimpleWindow parent) { 11747 string cnamec; 11748 if (sdpyWindowClassStr is null) loadBinNameToWindowClassName(); 11749 if (sdpyWindowClassStr is null || sdpyWindowClassStr[0] == 0) { 11750 cnamec = "DSimpleWindow"; 11751 } else { 11752 cnamec = sdpyWindowClass; 11753 } 11754 11755 WCharzBuffer cn = WCharzBuffer(cnamec); 11756 11757 HINSTANCE hInstance = cast(HINSTANCE) GetModuleHandle(null); 11758 11759 if(cnamec !in knownWinClasses) { 11760 WNDCLASSEX wc; 11761 11762 // FIXME: I might be able to use cbWndExtra to hold the pointer back 11763 // to the object. Maybe. 11764 wc.cbSize = wc.sizeof; 11765 wc.cbClsExtra = 0; 11766 wc.cbWndExtra = 0; 11767 wc.hbrBackground = cast(HBRUSH) (COLOR_WINDOW+1); // GetStockObject(WHITE_BRUSH); 11768 wc.hCursor = LoadCursorW(null, IDC_ARROW); 11769 wc.hIcon = LoadIcon(hInstance, null); 11770 wc.hInstance = hInstance; 11771 wc.lpfnWndProc = &WndProc; 11772 wc.lpszClassName = cn.ptr; 11773 wc.hIconSm = null; 11774 wc.style = CS_HREDRAW | CS_VREDRAW | CS_DBLCLKS; 11775 if(!RegisterClassExW(&wc)) 11776 throw new WindowsApiException("RegisterClassExW", GetLastError()); 11777 knownWinClasses[cnamec] = true; 11778 } 11779 11780 int style; 11781 uint flags = WS_EX_ACCEPTFILES; // accept drag-drop files 11782 11783 // FIXME: windowType and customizationFlags 11784 final switch(windowType) { 11785 case WindowTypes.normal: 11786 if(resizability == Resizability.fixedSize) { 11787 style = WS_SYSMENU | WS_OVERLAPPED | WS_CAPTION; 11788 } else { 11789 style = WS_OVERLAPPEDWINDOW; 11790 } 11791 break; 11792 case WindowTypes.undecorated: 11793 style = WS_POPUP | WS_SYSMENU; 11794 break; 11795 case WindowTypes.eventOnly: 11796 _hidden = true; 11797 break; 11798 case WindowTypes.dropdownMenu: 11799 case WindowTypes.popupMenu: 11800 case WindowTypes.notification: 11801 style = WS_POPUP; 11802 flags |= WS_EX_NOACTIVATE; 11803 break; 11804 case WindowTypes.nestedChild: 11805 style = WS_CHILD; 11806 break; 11807 case WindowTypes.minimallyWrapped: 11808 assert(0, "construct minimally wrapped through the other ctor overlad"); 11809 } 11810 11811 if ((customizationFlags & WindowFlags.extraComposite) != 0) 11812 flags |= WS_EX_LAYERED; // composite window for better performance and effects support 11813 11814 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 11815 CW_USEDEFAULT, CW_USEDEFAULT, width, height, 11816 parent is null ? null : parent.impl.hwnd, null, hInstance, null); 11817 11818 if ((customizationFlags & WindowFlags.extraComposite) != 0) 11819 setOpacity(255); 11820 11821 SimpleWindow.nativeMapping[hwnd] = this; 11822 CapableOfHandlingNativeEvent.nativeHandleMapping[hwnd] = this; 11823 11824 if(windowType == WindowTypes.eventOnly) 11825 return; 11826 11827 HDC hdc = GetDC(hwnd); 11828 11829 11830 version(without_opengl) {} 11831 else { 11832 if(opengl == OpenGlOptions.yes) { 11833 if(!openGlLibrariesSuccessfullyLoaded) throw new Exception("OpenGL libraries did not load"); 11834 static if (SdpyIsUsingIVGLBinds) {if (glbindGetProcAddress("glHint") is null) assert(0, "GL: error loading OpenGL"); } // loads all necessary functions 11835 static if (SdpyIsUsingIVGLBinds) import iv.glbinds; // override druntime windows imports 11836 ghDC = hdc; 11837 PIXELFORMATDESCRIPTOR pfd; 11838 11839 pfd.nSize = PIXELFORMATDESCRIPTOR.sizeof; 11840 pfd.nVersion = 1; 11841 pfd.dwFlags = PFD_DRAW_TO_WINDOW | PFD_SUPPORT_OPENGL | PFD_DOUBLEBUFFER; 11842 pfd.dwLayerMask = PFD_MAIN_PLANE; 11843 pfd.iPixelType = PFD_TYPE_RGBA; 11844 pfd.cColorBits = 24; 11845 pfd.cDepthBits = 24; 11846 pfd.cAccumBits = 0; 11847 pfd.cStencilBits = 8; // any reasonable OpenGL implementation should support this anyway 11848 11849 auto pixelformat = ChoosePixelFormat(hdc, &pfd); 11850 11851 if (pixelformat == 0) 11852 throw new WindowsApiException("ChoosePixelFormat", GetLastError()); 11853 11854 if (SetPixelFormat(hdc, pixelformat, &pfd) == 0) 11855 throw new WindowsApiException("SetPixelFormat", GetLastError()); 11856 11857 if (sdpyOpenGLContextVersion && wglCreateContextAttribsARB is null) { 11858 // windoze is idiotic: we have to have OpenGL context to get function addresses 11859 // so we will create fake context to get that stupid address 11860 auto tmpcc = wglCreateContext(ghDC); 11861 if (tmpcc !is null) { 11862 scope(exit) { wglMakeCurrent(ghDC, null); wglDeleteContext(tmpcc); } 11863 wglMakeCurrent(ghDC, tmpcc); 11864 wglInitOtherFunctions(); 11865 } 11866 } 11867 11868 if (wglCreateContextAttribsARB !is null && sdpyOpenGLContextVersion) { 11869 int[9] contextAttribs = [ 11870 WGL_CONTEXT_MAJOR_VERSION_ARB, (sdpyOpenGLContextVersion>>8), 11871 WGL_CONTEXT_MINOR_VERSION_ARB, (sdpyOpenGLContextVersion&0xff), 11872 WGL_CONTEXT_PROFILE_MASK_ARB, (sdpyOpenGLContextCompatible ? WGL_CONTEXT_COMPATIBILITY_PROFILE_BIT_ARB : WGL_CONTEXT_CORE_PROFILE_BIT_ARB), 11873 // for modern context, set "forward compatibility" flag too 11874 (sdpyOpenGLContextCompatible ? 0/*None*/ : WGL_CONTEXT_FLAGS_ARB), WGL_CONTEXT_FORWARD_COMPATIBLE_BIT_ARB, 11875 0/*None*/, 11876 ]; 11877 ghRC = wglCreateContextAttribsARB(ghDC, null, contextAttribs.ptr); 11878 if (ghRC is null && sdpyOpenGLContextAllowFallback) { 11879 // activate fallback mode 11880 // 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; 11881 ghRC = wglCreateContext(ghDC); 11882 } 11883 if (ghRC is null) 11884 throw new WindowsApiException("wglCreateContextAttribsARB", GetLastError()); 11885 } else { 11886 // try to do at least something 11887 if (sdpyOpenGLContextAllowFallback || sdpyOpenGLContextVersion == 0) { 11888 sdpyOpenGLContextVersion = 0; 11889 ghRC = wglCreateContext(ghDC); 11890 } 11891 if (ghRC is null) 11892 throw new WindowsApiException("wglCreateContext", GetLastError()); 11893 } 11894 } 11895 } 11896 11897 if(opengl == OpenGlOptions.no) { 11898 buffer = CreateCompatibleBitmap(hdc, width, height); 11899 11900 auto hdcBmp = CreateCompatibleDC(hdc); 11901 // make sure it's filled with a blank slate 11902 auto oldBmp = SelectObject(hdcBmp, buffer); 11903 auto oldBrush = SelectObject(hdcBmp, GetStockObject(WHITE_BRUSH)); 11904 auto oldPen = SelectObject(hdcBmp, GetStockObject(WHITE_PEN)); 11905 gdi.Rectangle(hdcBmp, 0, 0, width, height); 11906 SelectObject(hdcBmp, oldBmp); 11907 SelectObject(hdcBmp, oldBrush); 11908 SelectObject(hdcBmp, oldPen); 11909 DeleteDC(hdcBmp); 11910 11911 bmpWidth = width; 11912 bmpHeight = height; 11913 11914 ReleaseDC(hwnd, hdc); // we keep this in opengl mode since it is a class member now 11915 } 11916 11917 // We want the window's client area to match the image size 11918 RECT rcClient, rcWindow; 11919 POINT ptDiff; 11920 GetClientRect(hwnd, &rcClient); 11921 GetWindowRect(hwnd, &rcWindow); 11922 ptDiff.x = (rcWindow.right - rcWindow.left) - rcClient.right; 11923 ptDiff.y = (rcWindow.bottom - rcWindow.top) - rcClient.bottom; 11924 MoveWindow(hwnd,rcWindow.left, rcWindow.top, width + ptDiff.x, height + ptDiff.y, true); 11925 11926 if ((customizationFlags&WindowFlags.dontAutoShow) == 0) { 11927 ShowWindow(hwnd, SW_SHOWNORMAL); 11928 } else { 11929 _hidden = true; 11930 } 11931 this._visibleForTheFirstTimeCalled = false; // hack! 11932 } 11933 11934 11935 void dispose() { 11936 if(buffer) 11937 DeleteObject(buffer); 11938 } 11939 11940 void closeWindow() { 11941 if(ghRC) { 11942 wglDeleteContext(ghRC); 11943 ghRC = null; 11944 } 11945 DestroyWindow(hwnd); 11946 } 11947 11948 bool setOpacity(ubyte alpha) { 11949 return SetLayeredWindowAttributes(hwnd, 0, alpha, LWA_ALPHA) == TRUE; 11950 } 11951 11952 HANDLE currentCursor; 11953 11954 // returns zero if it recognized the event 11955 static int triggerEvents(HWND hwnd, uint msg, WPARAM wParam, LPARAM lParam, int offsetX, int offsetY, SimpleWindow wind) { 11956 MouseEvent mouse; 11957 11958 void mouseEvent(bool isScreen, ulong mods) { 11959 auto x = LOWORD(lParam); 11960 auto y = HIWORD(lParam); 11961 if(isScreen) { 11962 POINT p; 11963 p.x = x; 11964 p.y = y; 11965 ScreenToClient(hwnd, &p); 11966 x = cast(ushort) p.x; 11967 y = cast(ushort) p.y; 11968 } 11969 11970 if(wind.resizability == Resizability.automaticallyScaleIfPossible) { 11971 x = cast(ushort)( x * wind._virtualWidth / wind._width ); 11972 y = cast(ushort)( y * wind._virtualHeight / wind._height ); 11973 } 11974 11975 mouse.x = x + offsetX; 11976 mouse.y = y + offsetY; 11977 11978 wind.mdx(mouse); 11979 mouse.modifierState = cast(int) mods; 11980 mouse.window = wind; 11981 11982 if(wind.handleMouseEvent) 11983 wind.handleMouseEvent(mouse); 11984 } 11985 11986 switch(msg) { 11987 case WM_GETMINMAXINFO: 11988 MINMAXINFO* mmi = cast(MINMAXINFO*) lParam; 11989 11990 if(wind.minWidth > 0) { 11991 RECT rect; 11992 rect.left = 100; 11993 rect.top = 100; 11994 rect.right = wind.minWidth + 100; 11995 rect.bottom = wind.minHeight + 100; 11996 if(!AdjustWindowRect(&rect, GetWindowLong(wind.hwnd, GWL_STYLE), GetMenu(wind.hwnd) !is null)) 11997 throw new WindowsApiException("AdjustWindowRect", GetLastError()); 11998 11999 mmi.ptMinTrackSize.x = rect.right - rect.left; 12000 mmi.ptMinTrackSize.y = rect.bottom - rect.top; 12001 } 12002 12003 if(wind.maxWidth < int.max) { 12004 RECT rect; 12005 rect.left = 100; 12006 rect.top = 100; 12007 rect.right = wind.maxWidth + 100; 12008 rect.bottom = wind.maxHeight + 100; 12009 if(!AdjustWindowRect(&rect, GetWindowLong(wind.hwnd, GWL_STYLE), GetMenu(wind.hwnd) !is null)) 12010 throw new WindowsApiException("AdjustWindowRect", GetLastError()); 12011 12012 mmi.ptMaxTrackSize.x = rect.right - rect.left; 12013 mmi.ptMaxTrackSize.y = rect.bottom - rect.top; 12014 } 12015 break; 12016 case WM_CHAR: 12017 wchar c = cast(wchar) wParam; 12018 if(wind.handleCharEvent) 12019 wind.handleCharEvent(cast(dchar) c); 12020 break; 12021 case WM_SETFOCUS: 12022 case WM_KILLFOCUS: 12023 wind._focused = (msg == WM_SETFOCUS); 12024 if (msg == WM_SETFOCUS) altPressed = false; //k8: reset alt state on defocus (it is better than nothing...) 12025 if(wind.onFocusChange) 12026 wind.onFocusChange(msg == WM_SETFOCUS); 12027 break; 12028 12029 case WM_SYSKEYDOWN: 12030 goto case; 12031 case WM_SYSKEYUP: 12032 if(lParam & (1 << 29)) { 12033 goto case; 12034 } else { 12035 // no window has keyboard focus 12036 goto default; 12037 } 12038 case WM_KEYDOWN: 12039 case WM_KEYUP: 12040 KeyEvent ev; 12041 ev.key = cast(Key) wParam; 12042 ev.pressed = (msg == WM_KEYDOWN || msg == WM_SYSKEYDOWN); 12043 if (wParam == 0x12) ev.key = Key.Alt; // windows does it this way 12044 12045 ev.hardwareCode = (lParam & 0xff0000) >> 16; 12046 12047 if(GetKeyState(Key.Shift)&0x8000 || GetKeyState(Key.Shift_r)&0x8000) 12048 ev.modifierState |= ModifierState.shift; 12049 //k8: this doesn't work; thanks for nothing, windows 12050 /*if(GetKeyState(Key.Alt)&0x8000 || GetKeyState(Key.Alt_r)&0x8000) 12051 ev.modifierState |= ModifierState.alt;*/ 12052 // this never seems to actually be set 12053 // if (lParam & 0x2000 /* KF_ALTDOWN */) ev.modifierState |= ModifierState.alt; else ev.modifierState &= ~ModifierState.alt; 12054 12055 if (wParam == 0x12) { 12056 altPressed = (msg == WM_SYSKEYDOWN); 12057 } 12058 12059 if(msg == WM_KEYDOWN || msg == WM_KEYUP) { 12060 altPressed = false; 12061 } 12062 // sdpyPrintDebugString(altPressed ? "alt down" : " up "); 12063 12064 if (altPressed) ev.modifierState |= ModifierState.alt; else ev.modifierState &= ~ModifierState.alt; 12065 if(GetKeyState(Key.Ctrl)&0x8000 || GetKeyState(Key.Ctrl_r)&0x8000) 12066 ev.modifierState |= ModifierState.ctrl; 12067 if(GetKeyState(Key.Windows)&0x8000 || GetKeyState(Key.Windows_r)&0x8000) 12068 ev.modifierState |= ModifierState.windows; 12069 if(GetKeyState(Key.NumLock)) 12070 ev.modifierState |= ModifierState.numLock; 12071 if(GetKeyState(Key.CapsLock)) 12072 ev.modifierState |= ModifierState.capsLock; 12073 12074 /+ 12075 // we always want to send the character too, so let's convert it 12076 ubyte[256] state; 12077 wchar[16] buffer; 12078 GetKeyboardState(state.ptr); 12079 ToUnicodeEx(wParam, lParam, state.ptr, buffer.ptr, buffer.length, 0, null); 12080 12081 foreach(dchar d; buffer) { 12082 ev.character = d; 12083 break; 12084 } 12085 +/ 12086 12087 ev.window = wind; 12088 if(wind.handleKeyEvent) 12089 wind.handleKeyEvent(ev); 12090 break; 12091 case 0x020a /*WM_MOUSEWHEEL*/: 12092 // send click 12093 mouse.type = cast(MouseEventType) 1; 12094 mouse.button = ((GET_WHEEL_DELTA_WPARAM(wParam) > 0) ? MouseButton.wheelUp : MouseButton.wheelDown); 12095 mouseEvent(true, LOWORD(wParam)); 12096 12097 // also send release 12098 mouse.type = cast(MouseEventType) 2; 12099 mouse.button = ((GET_WHEEL_DELTA_WPARAM(wParam) > 0) ? MouseButton.wheelUp : MouseButton.wheelDown); 12100 mouseEvent(true, LOWORD(wParam)); 12101 break; 12102 case WM_MOUSEMOVE: 12103 mouse.type = cast(MouseEventType) 0; 12104 mouseEvent(false, wParam); 12105 break; 12106 case WM_LBUTTONDOWN: 12107 case WM_LBUTTONDBLCLK: 12108 mouse.type = cast(MouseEventType) 1; 12109 mouse.button = MouseButton.left; 12110 mouse.doubleClick = msg == WM_LBUTTONDBLCLK; 12111 mouseEvent(false, wParam); 12112 break; 12113 case WM_LBUTTONUP: 12114 mouse.type = cast(MouseEventType) 2; 12115 mouse.button = MouseButton.left; 12116 mouseEvent(false, wParam); 12117 break; 12118 case WM_RBUTTONDOWN: 12119 case WM_RBUTTONDBLCLK: 12120 mouse.type = cast(MouseEventType) 1; 12121 mouse.button = MouseButton.right; 12122 mouse.doubleClick = msg == WM_RBUTTONDBLCLK; 12123 mouseEvent(false, wParam); 12124 break; 12125 case WM_RBUTTONUP: 12126 mouse.type = cast(MouseEventType) 2; 12127 mouse.button = MouseButton.right; 12128 mouseEvent(false, wParam); 12129 break; 12130 case WM_MBUTTONDOWN: 12131 case WM_MBUTTONDBLCLK: 12132 mouse.type = cast(MouseEventType) 1; 12133 mouse.button = MouseButton.middle; 12134 mouse.doubleClick = msg == WM_MBUTTONDBLCLK; 12135 mouseEvent(false, wParam); 12136 break; 12137 case WM_MBUTTONUP: 12138 mouse.type = cast(MouseEventType) 2; 12139 mouse.button = MouseButton.middle; 12140 mouseEvent(false, wParam); 12141 break; 12142 case WM_XBUTTONDOWN: 12143 case WM_XBUTTONDBLCLK: 12144 mouse.type = cast(MouseEventType) 1; 12145 mouse.button = HIWORD(wParam) == 1 ? MouseButton.backButton : MouseButton.forwardButton; 12146 mouse.doubleClick = msg == WM_XBUTTONDBLCLK; 12147 mouseEvent(false, wParam); 12148 return 1; // MSDN says special treatment here, return TRUE to bypass simulation programs 12149 case WM_XBUTTONUP: 12150 mouse.type = cast(MouseEventType) 2; 12151 mouse.button = HIWORD(wParam) == 1 ? MouseButton.backButton : MouseButton.forwardButton; 12152 mouseEvent(false, wParam); 12153 return 1; // see: https://msdn.microsoft.com/en-us/library/windows/desktop/ms646246(v=vs.85).aspx 12154 12155 default: return 1; 12156 } 12157 return 0; 12158 } 12159 12160 HWND hwnd; 12161 private int oldWidth; 12162 private int oldHeight; 12163 private bool inSizeMove; 12164 12165 /++ 12166 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. 12167 12168 History: 12169 Added November 23, 2021 12170 12171 Not fully stable, may be moved out of the impl struct. 12172 12173 Default value changed to `true` on February 15, 2021 12174 +/ 12175 bool doLiveResizing = true; 12176 12177 package int bmpWidth; 12178 package int bmpHeight; 12179 12180 // the extern(Windows) wndproc should just forward to this 12181 LRESULT windowProcedure(HWND hwnd, uint msg, WPARAM wParam, LPARAM lParam) { 12182 try { 12183 assert(hwnd is this.hwnd); 12184 12185 if(triggerEvents(hwnd, msg, wParam, lParam, 0, 0, this)) 12186 switch(msg) { 12187 case WM_MENUCHAR: // menu active but key not associated with a thing. 12188 // you would ideally use this for like a search function but sdpy not that ideally designed. alas. 12189 // The main things we can do are select, execute, close, or ignore 12190 // the default is ignore, but it doesn't *just* ignore it - it also dings an audio alert to 12191 // the user. This can be a bit annoying for sdpy things so instead im overriding and setting it 12192 // to close, which can be really annoying when you hit the wrong button. but meh i think for sdpy 12193 // that's the lesser bad choice rn. Can always override by returning true in triggerEvents.... 12194 12195 // returns the value in the *high order word* of the return value 12196 // hence the << 16 12197 return 1 << 16; // MNC_CLOSE, close the menu without dinging at the user 12198 case WM_SETCURSOR: 12199 if(cast(HWND) wParam !is hwnd) 12200 return 0; // further processing elsewhere 12201 12202 if(LOWORD(lParam) == HTCLIENT && (this.curHidden > 0 || currentCursor !is null)) { 12203 SetCursor(this.curHidden > 0 ? null : currentCursor); 12204 return 1; 12205 } else { 12206 return DefWindowProc(hwnd, msg, wParam, lParam); 12207 } 12208 //break; 12209 12210 case WM_CLOSE: 12211 if (this.closeQuery !is null) this.closeQuery(); else this.close(); 12212 break; 12213 case WM_DESTROY: 12214 if (this.onDestroyed !is null) try { this.onDestroyed(); } catch (Exception e) {} // sorry 12215 SimpleWindow.nativeMapping.remove(hwnd); 12216 CapableOfHandlingNativeEvent.nativeHandleMapping.remove(hwnd); 12217 12218 bool anyImportant = false; 12219 foreach(SimpleWindow w; SimpleWindow.nativeMapping) 12220 if(w.beingOpenKeepsAppOpen) { 12221 anyImportant = true; 12222 break; 12223 } 12224 if(!anyImportant) { 12225 PostQuitMessage(0); 12226 } 12227 break; 12228 case 0x02E0 /*WM_DPICHANGED*/: 12229 this.actualDpi_ = LOWORD(wParam); // hiword is the y param but it is the same per docs 12230 12231 RECT* prcNewWindow = cast(RECT*)lParam; 12232 // docs say this is the recommended position and we should honor it 12233 SetWindowPos(hwnd, 12234 null, 12235 prcNewWindow.left, 12236 prcNewWindow.top, 12237 prcNewWindow.right - prcNewWindow.left, 12238 prcNewWindow.bottom - prcNewWindow.top, 12239 SWP_NOZORDER | SWP_NOACTIVATE); 12240 12241 // doing this because of https://github.com/microsoft/Windows-classic-samples/blob/main/Samples/DPIAwarenessPerWindow/client/DpiAwarenessContext.cpp 12242 // im not sure it is completely correct 12243 // but without it the tabs and such do look weird as things change. 12244 if(SystemParametersInfoForDpi) { 12245 LOGFONT lfText; 12246 SystemParametersInfoForDpi(SPI_GETICONTITLELOGFONT, lfText.sizeof, &lfText, FALSE, this.actualDpi_); 12247 HFONT hFontNew = CreateFontIndirect(&lfText); 12248 if (hFontNew) 12249 { 12250 //DeleteObject(hFontOld); 12251 static extern(Windows) BOOL helper(HWND hWnd, LPARAM lParam) { 12252 SendMessage(hWnd, WM_SETFONT, cast(WPARAM)lParam, MAKELPARAM(TRUE, 0)); 12253 return TRUE; 12254 } 12255 EnumChildWindows(hwnd, &helper, cast(LPARAM) hFontNew); 12256 } 12257 } 12258 12259 if(this.onDpiChanged) 12260 this.onDpiChanged(); 12261 break; 12262 case WM_ENTERIDLE: 12263 // when a menu is up, it stops normal event processing (modal message loop) 12264 // but this at least gives us a chance to SOMETIMES catch up 12265 // FIXME: I can use SetTimer while idle to keep working i think... but idk when i'd destroy it. 12266 SimpleWindow.processAllCustomEvents; 12267 SimpleWindow.processAllCustomEvents; 12268 SleepEx(0, true); 12269 break; 12270 case WM_SIZE: 12271 if(wParam == 1 /* SIZE_MINIMIZED */) 12272 break; 12273 _width = LOWORD(lParam); 12274 _height = HIWORD(lParam); 12275 12276 // I want to avoid tearing in the windows (my code is inefficient 12277 // so this is a hack around that) so while sizing, we don't trigger, 12278 // but we do want to trigger on events like mazimize. 12279 if(!inSizeMove || doLiveResizing) 12280 goto size_changed; 12281 break; 12282 /+ 12283 case WM_SIZING: 12284 writeln("size"); 12285 break; 12286 +/ 12287 // I don't like the tearing I get when redrawing on WM_SIZE 12288 // (I know there's other ways to fix that but I don't like that behavior anyway) 12289 // so instead it is going to redraw only at the end of a size. 12290 case 0x0231: /* WM_ENTERSIZEMOVE */ 12291 inSizeMove = true; 12292 break; 12293 case 0x0232: /* WM_EXITSIZEMOVE */ 12294 inSizeMove = false; 12295 12296 size_changed: 12297 12298 // nothing relevant changed, don't bother redrawing 12299 if(oldWidth == _width && oldHeight == _height) { 12300 break; 12301 } 12302 12303 // note: OpenGL windows don't use a backing bmp, so no need to change them 12304 // if resizability is anything other than allowResizing, it is meant to either stretch the one image or just do nothing 12305 if(openglMode == OpenGlOptions.no) { // && resizability == Resizability.allowResizing) { 12306 // gotta get the double buffer bmp to match the window 12307 // FIXME: could this be more efficient? it never relinquishes a large bitmap 12308 12309 // if it is auto-scaled, we keep the backing bitmap the same size all the time 12310 if(resizability != Resizability.automaticallyScaleIfPossible) 12311 if(_width > bmpWidth || _height > bmpHeight) { 12312 auto hdc = GetDC(hwnd); 12313 auto oldBuffer = buffer; 12314 buffer = CreateCompatibleBitmap(hdc, _width, _height); 12315 12316 auto hdcBmp = CreateCompatibleDC(hdc); 12317 auto oldBmp = SelectObject(hdcBmp, buffer); 12318 12319 auto hdcOldBmp = CreateCompatibleDC(hdc); 12320 auto oldOldBmp = SelectObject(hdcOldBmp, oldBuffer); 12321 12322 /+ 12323 RECT r; 12324 r.left = 0; 12325 r.top = 0; 12326 r.right = width; 12327 r.bottom = height; 12328 auto c = Color.green; 12329 auto brush = CreateSolidBrush(RGB(c.r, c.g, c.b)); 12330 FillRect(hdcBmp, &r, brush); 12331 DeleteObject(brush); 12332 +/ 12333 12334 BitBlt(hdcBmp, 0, 0, bmpWidth, bmpHeight, hdcOldBmp, 0, 0, SRCCOPY); 12335 12336 bmpWidth = _width; 12337 bmpHeight = _height; 12338 12339 SelectObject(hdcOldBmp, oldOldBmp); 12340 DeleteDC(hdcOldBmp); 12341 12342 SelectObject(hdcBmp, oldBmp); 12343 DeleteDC(hdcBmp); 12344 12345 ReleaseDC(hwnd, hdc); 12346 12347 DeleteObject(oldBuffer); 12348 } 12349 } 12350 12351 updateOpenglViewportIfNeeded(_width, _height); 12352 12353 if(resizability != Resizability.automaticallyScaleIfPossible) 12354 if(windowResized !is null) 12355 windowResized(_width, _height); 12356 12357 if(inSizeMove) { 12358 SimpleWindow.processAllCustomEvents(); 12359 SimpleWindow.processAllCustomEvents(); 12360 } else { 12361 // when it is all done, make sure everything is freshly drawn or there might be 12362 // weird bugs left. 12363 RedrawWindow(hwnd, null, null, RDW_ERASE | RDW_INVALIDATE | RDW_ALLCHILDREN); 12364 } 12365 12366 oldWidth = this._width; 12367 oldHeight = this._height; 12368 break; 12369 case WM_ERASEBKGND: 12370 // call `visibleForTheFirstTime` here, so we can do initialization as early as possible 12371 if (!this._visibleForTheFirstTimeCalled) { 12372 this._visibleForTheFirstTimeCalled = true; 12373 if (this.visibleForTheFirstTime !is null) { 12374 this.visibleForTheFirstTime(); 12375 } 12376 } 12377 // block it in OpenGL mode, 'cause no sane person will (or should) draw windows controls over OpenGL scene 12378 version(without_opengl) {} else { 12379 if (openglMode == OpenGlOptions.yes) return 1; 12380 } 12381 // call windows default handler, so it can paint standard controls 12382 goto default; 12383 case WM_CTLCOLORBTN: 12384 case WM_CTLCOLORSTATIC: 12385 SetBkMode(cast(HDC) wParam, TRANSPARENT); 12386 return cast(typeof(return)) //GetStockObject(NULL_BRUSH); 12387 GetSysColorBrush(COLOR_3DFACE); 12388 //break; 12389 case WM_SHOWWINDOW: 12390 this._visible = (wParam != 0); 12391 if (!this._visibleForTheFirstTimeCalled && this._visible) { 12392 this._visibleForTheFirstTimeCalled = true; 12393 if (this.visibleForTheFirstTime !is null) { 12394 this.visibleForTheFirstTime(); 12395 } 12396 } 12397 if (this.visibilityChanged !is null) this.visibilityChanged(this._visible); 12398 break; 12399 case WM_PAINT: { 12400 if (!this._visibleForTheFirstTimeCalled) { 12401 this._visibleForTheFirstTimeCalled = true; 12402 if (this.visibleForTheFirstTime !is null) { 12403 this.visibleForTheFirstTime(); 12404 } 12405 } 12406 12407 BITMAP bm; 12408 PAINTSTRUCT ps; 12409 12410 HDC hdc = BeginPaint(hwnd, &ps); 12411 12412 if(openglMode == OpenGlOptions.no) { 12413 12414 HDC hdcMem = CreateCompatibleDC(hdc); 12415 HBITMAP hbmOld = SelectObject(hdcMem, buffer); 12416 12417 GetObject(buffer, bm.sizeof, &bm); 12418 12419 // FIXME: only BitBlt the invalidated rectangle, not the whole thing 12420 if(resizability == Resizability.automaticallyScaleIfPossible) 12421 StretchBlt(hdc, 0, 0, this._width, this._height, hdcMem, 0, 0, bm.bmWidth, bm.bmHeight, SRCCOPY); 12422 else 12423 BitBlt(hdc, 0, 0, bm.bmWidth, bm.bmHeight, hdcMem, 0, 0, SRCCOPY); 12424 //BitBlt(hdc, ps.rcPaint.left, ps.rcPaint.top, ps.rcPaint.right - ps.rcPaint.left, ps.rcPaint.top - ps.rcPaint.bottom, hdcMem, 0, 0, SRCCOPY); 12425 12426 SelectObject(hdcMem, hbmOld); 12427 DeleteDC(hdcMem); 12428 EndPaint(hwnd, &ps); 12429 } else { 12430 EndPaint(hwnd, &ps); 12431 version(without_opengl) {} else 12432 redrawOpenGlSceneSoon(); 12433 } 12434 } break; 12435 default: 12436 return DefWindowProc(hwnd, msg, wParam, lParam); 12437 } 12438 return 0; 12439 12440 } 12441 catch(Throwable t) { 12442 sdpyPrintDebugString(t.toString); 12443 return 0; 12444 } 12445 } 12446 } 12447 12448 mixin template NativeImageImplementation() { 12449 HBITMAP handle; 12450 ubyte* rawData; 12451 12452 final: 12453 12454 Color getPixel(int x, int y) { 12455 auto itemsPerLine = ((cast(int) width * 3 + 3) / 4) * 4; 12456 // remember, bmps are upside down 12457 auto offset = itemsPerLine * (height - y - 1) + x * 3; 12458 12459 Color c; 12460 if(enableAlpha) 12461 c.a = rawData[offset + 3]; 12462 else 12463 c.a = 255; 12464 c.b = rawData[offset + 0]; 12465 c.g = rawData[offset + 1]; 12466 c.r = rawData[offset + 2]; 12467 c.unPremultiply(); 12468 return c; 12469 } 12470 12471 void setPixel(int x, int y, Color c) { 12472 auto itemsPerLine = ((cast(int) width * 3 + 3) / 4) * 4; 12473 // remember, bmps are upside down 12474 auto offset = itemsPerLine * (height - y - 1) + x * 3; 12475 12476 if(enableAlpha) 12477 c.premultiply(); 12478 12479 rawData[offset + 0] = c.b; 12480 rawData[offset + 1] = c.g; 12481 rawData[offset + 2] = c.r; 12482 if(enableAlpha) 12483 rawData[offset + 3] = c.a; 12484 } 12485 12486 void convertToRgbaBytes(ubyte[] where) { 12487 assert(where.length == this.width * this.height * 4); 12488 12489 auto itemsPerLine = ((cast(int) width * 3 + 3) / 4) * 4; 12490 int idx = 0; 12491 int offset = itemsPerLine * (height - 1); 12492 // remember, bmps are upside down 12493 for(int y = height - 1; y >= 0; y--) { 12494 auto offsetStart = offset; 12495 for(int x = 0; x < width; x++) { 12496 where[idx + 0] = rawData[offset + 2]; // r 12497 where[idx + 1] = rawData[offset + 1]; // g 12498 where[idx + 2] = rawData[offset + 0]; // b 12499 if(enableAlpha) { 12500 where[idx + 3] = rawData[offset + 3]; // a 12501 unPremultiplyRgba(where[idx .. idx + 4]); 12502 offset++; 12503 } else 12504 where[idx + 3] = 255; // a 12505 idx += 4; 12506 offset += 3; 12507 } 12508 12509 offset = offsetStart - itemsPerLine; 12510 } 12511 } 12512 12513 void setFromRgbaBytes(in ubyte[] what) { 12514 assert(what.length == this.width * this.height * 4); 12515 12516 auto itemsPerLine = enableAlpha ? (width * 4) : (((cast(int) width * 3 + 3) / 4) * 4); 12517 int idx = 0; 12518 int offset = itemsPerLine * (height - 1); 12519 // remember, bmps are upside down 12520 for(int y = height - 1; y >= 0; y--) { 12521 auto offsetStart = offset; 12522 for(int x = 0; x < width; x++) { 12523 if(enableAlpha) { 12524 auto a = what[idx + 3]; 12525 12526 rawData[offset + 2] = (a * what[idx + 0]) / 255; // r 12527 rawData[offset + 1] = (a * what[idx + 1]) / 255; // g 12528 rawData[offset + 0] = (a * what[idx + 2]) / 255; // b 12529 rawData[offset + 3] = a; // a 12530 //premultiplyBgra(rawData[offset .. offset + 4]); 12531 offset++; 12532 } else { 12533 rawData[offset + 2] = what[idx + 0]; // r 12534 rawData[offset + 1] = what[idx + 1]; // g 12535 rawData[offset + 0] = what[idx + 2]; // b 12536 } 12537 idx += 4; 12538 offset += 3; 12539 } 12540 12541 offset = offsetStart - itemsPerLine; 12542 } 12543 } 12544 12545 12546 void createImage(int width, int height, bool forcexshm=false, bool enableAlpha = false) { 12547 BITMAPINFO infoheader; 12548 infoheader.bmiHeader.biSize = infoheader.bmiHeader.sizeof; 12549 infoheader.bmiHeader.biWidth = width; 12550 infoheader.bmiHeader.biHeight = height; 12551 infoheader.bmiHeader.biPlanes = 1; 12552 infoheader.bmiHeader.biBitCount = enableAlpha ? 32: 24; 12553 infoheader.bmiHeader.biCompression = BI_RGB; 12554 12555 handle = CreateDIBSection( 12556 null, 12557 &infoheader, 12558 DIB_RGB_COLORS, 12559 cast(void**) &rawData, 12560 null, 12561 0); 12562 if(handle is null) 12563 throw new WindowsApiException("create image failed", GetLastError()); 12564 12565 } 12566 12567 void dispose() { 12568 DeleteObject(handle); 12569 } 12570 } 12571 12572 enum KEY_ESCAPE = 27; 12573 } 12574 version(X11) { 12575 /// This is the default font used. You might change this before doing anything else with 12576 /// the library if you want to try something else. Surround that in `static if(UsingSimpledisplayX11)` 12577 /// for cross-platform compatibility. 12578 //__gshared string xfontstr = "-*-dejavu sans-medium-r-*-*-12-*-*-*-*-*-*-*"; 12579 //__gshared string xfontstr = "-*-dejavu sans-medium-r-*-*-12-*-*-*-*-*-*-*"; 12580 __gshared string xfontstr = "-*-lucida-medium-r-normal-sans-12-*-*-*-*-*-*-*"; 12581 //__gshared string xfontstr = "-*-fixed-medium-r-*-*-14-*-*-*-*-*-*-*"; 12582 12583 alias int delegate(XEvent) NativeEventHandler; 12584 alias Window NativeWindowHandle; 12585 12586 enum KEY_ESCAPE = 9; 12587 12588 mixin template NativeScreenPainterImplementation() { 12589 Display* display; 12590 Drawable d; 12591 Drawable destiny; 12592 12593 // FIXME: should the gc be static too so it isn't recreated every time draw is called? 12594 GC gc; 12595 12596 __gshared bool fontAttempted; 12597 12598 __gshared XFontStruct* defaultfont; 12599 __gshared XFontSet defaultfontset; 12600 12601 XFontStruct* font; 12602 XFontSet fontset; 12603 12604 void create(PaintingHandle window) { 12605 this.display = XDisplayConnection.get(); 12606 12607 Drawable buffer = None; 12608 if(auto sw = cast(SimpleWindow) this.window) { 12609 buffer = sw.impl.buffer; 12610 this.destiny = cast(Drawable) window; 12611 } else { 12612 buffer = cast(Drawable) window; 12613 this.destiny = None; 12614 } 12615 12616 this.d = cast(Drawable) buffer; 12617 12618 auto dgc = DefaultGC(display, DefaultScreen(display)); 12619 12620 this.gc = XCreateGC(display, d, 0, null); 12621 12622 XCopyGC(display, dgc, 0xffffffff, this.gc); 12623 12624 ensureDefaultFontLoaded(); 12625 12626 font = defaultfont; 12627 fontset = defaultfontset; 12628 12629 if(font) { 12630 XSetFont(display, gc, font.fid); 12631 } 12632 } 12633 12634 static void ensureDefaultFontLoaded() { 12635 if(!fontAttempted) { 12636 auto display = XDisplayConnection.get; 12637 auto font = XLoadQueryFont(display, xfontstr.ptr); 12638 // if the user font choice fails, fixed is pretty reliable (required by X to start!) and not bad either 12639 if(font is null) { 12640 xfontstr = "-*-fixed-medium-r-*-*-13-*-*-*-*-*-*-*"; 12641 font = XLoadQueryFont(display, xfontstr.ptr); 12642 } 12643 12644 char** lol; 12645 int lol2; 12646 char* lol3; 12647 auto fontset = XCreateFontSet(display, xfontstr.ptr, &lol, &lol2, &lol3); 12648 12649 fontAttempted = true; 12650 12651 defaultfont = font; 12652 defaultfontset = fontset; 12653 } 12654 } 12655 12656 arsd.color.Rectangle _clipRectangle; 12657 void setClipRectangle(int x, int y, int width, int height) { 12658 auto old = _clipRectangle; 12659 _clipRectangle = arsd.color.Rectangle(Point(x, y), Size(width, height)); 12660 if(old == _clipRectangle) 12661 return; 12662 12663 if(width == 0 || height == 0) { 12664 XSetClipMask(display, gc, None); 12665 12666 if(xrenderPicturePainter) { 12667 12668 XRectangle[1] rects; 12669 rects[0] = XRectangle(short.min, short.min, short.max, short.max); 12670 XRenderSetPictureClipRectangles(display, xrenderPicturePainter, 0, 0, rects.ptr, cast(int) rects.length); 12671 } 12672 12673 version(with_xft) { 12674 if(xftFont is null || xftDraw is null) 12675 return; 12676 XftDrawSetClip(xftDraw, null); 12677 } 12678 } else { 12679 XRectangle[1] rects; 12680 rects[0] = XRectangle(cast(short)(x), cast(short)(y), cast(short) width, cast(short) height); 12681 XSetClipRectangles(XDisplayConnection.get, gc, 0, 0, rects.ptr, 1, 0); 12682 12683 if(xrenderPicturePainter) 12684 XRenderSetPictureClipRectangles(display, xrenderPicturePainter, 0, 0, rects.ptr, cast(int) rects.length); 12685 12686 version(with_xft) { 12687 if(xftFont is null || xftDraw is null) 12688 return; 12689 XftDrawSetClipRectangles(xftDraw, 0, 0, rects.ptr, 1); 12690 } 12691 } 12692 } 12693 12694 version(with_xft) { 12695 XftFont* xftFont; 12696 XftDraw* xftDraw; 12697 12698 XftColor xftColor; 12699 12700 void updateXftColor() { 12701 if(xftFont is null) 12702 return; 12703 12704 // not bothering with XftColorFree since p sure i don't need it on 24 bit displays.... 12705 XRenderColor colorIn = XRenderColor(_outlineColor.r * 255, _outlineColor.g * 255, _outlineColor.b * 255, _outlineColor.a * 255); 12706 12707 XftColorAllocValue( 12708 display, 12709 DefaultVisual(display, DefaultScreen(display)), 12710 DefaultColormap(display, 0), 12711 &colorIn, 12712 &xftColor 12713 ); 12714 } 12715 } 12716 12717 private OperatingSystemFont _activeFont; 12718 void setFont(OperatingSystemFont font) { 12719 _activeFont = font; 12720 version(with_xft) { 12721 if(font && font.isXft && font.xftFont) 12722 this.xftFont = font.xftFont; 12723 else 12724 this.xftFont = null; 12725 12726 if(this.xftFont) { 12727 if(xftDraw is null) { 12728 xftDraw = XftDrawCreate( 12729 display, 12730 d, 12731 DefaultVisual(display, DefaultScreen(display)), 12732 DefaultColormap(display, 0) 12733 ); 12734 12735 updateXftColor(); 12736 } 12737 12738 return; 12739 } 12740 } 12741 12742 if(font && font.font) { 12743 this.font = font.font; 12744 this.fontset = font.fontset; 12745 XSetFont(display, gc, font.font.fid); 12746 } else { 12747 this.font = defaultfont; 12748 this.fontset = defaultfontset; 12749 } 12750 12751 } 12752 12753 private Picture xrenderPicturePainter; 12754 12755 bool manualInvalidations; 12756 void invalidateRect(Rectangle invalidRect) { 12757 // FIXME if manualInvalidations 12758 } 12759 12760 void dispose() { 12761 this.rasterOp = RasterOp.normal; 12762 12763 if(xrenderPicturePainter) { 12764 XRenderFreePicture(display, xrenderPicturePainter); 12765 xrenderPicturePainter = None; 12766 } 12767 12768 // FIXME: this.window.width/height is probably wrong 12769 12770 // src x,y then dest x, y 12771 if(destiny != None) { 12772 // FIXME: if manual invalidations we can actually only copy some of the area. 12773 // if(manualInvalidations) 12774 XSetClipMask(display, gc, None); 12775 XCopyArea(display, d, destiny, gc, 0, 0, this.window.width, this.window.height, 0, 0); 12776 } 12777 12778 XFreeGC(display, gc); 12779 12780 version(with_xft) 12781 if(xftDraw) { 12782 XftDrawDestroy(xftDraw); 12783 xftDraw = null; 12784 } 12785 12786 /+ 12787 // this should prolly legit never be used since if it destroys the font handle from a OperatingSystemFont, it also ruins a reusable resource. 12788 if(font && font !is defaultfont) { 12789 XFreeFont(display, font); 12790 font = null; 12791 } 12792 if(fontset && fontset !is defaultfontset) { 12793 XFreeFontSet(display, fontset); 12794 fontset = null; 12795 } 12796 +/ 12797 XFlush(display); 12798 12799 if(window.paintingFinishedDg !is null) 12800 window.paintingFinishedDg()(); 12801 } 12802 12803 bool backgroundIsNotTransparent = true; 12804 bool foregroundIsNotTransparent = true; 12805 12806 bool _penInitialized = false; 12807 Pen _activePen; 12808 12809 Color _outlineColor; 12810 Color _fillColor; 12811 12812 @property void pen(Pen p) { 12813 if(_penInitialized && p == _activePen) { 12814 return; 12815 } 12816 _penInitialized = true; 12817 _activePen = p; 12818 _outlineColor = p.color; 12819 12820 int style; 12821 12822 byte dashLength; 12823 12824 final switch(p.style) { 12825 case Pen.Style.Solid: 12826 style = 0 /*LineSolid*/; 12827 break; 12828 case Pen.Style.Dashed: 12829 style = 1 /*LineOnOffDash*/; 12830 dashLength = 4; 12831 break; 12832 case Pen.Style.Dotted: 12833 style = 1 /*LineOnOffDash*/; 12834 dashLength = 1; 12835 break; 12836 } 12837 12838 XSetLineAttributes(display, gc, p.width, style, style == 0 ? 3 : 0, 0); 12839 if(dashLength) 12840 XSetDashes(display, gc, 0, &dashLength, 1); 12841 12842 if(p.color.a == 0) { 12843 foregroundIsNotTransparent = false; 12844 return; 12845 } 12846 12847 foregroundIsNotTransparent = true; 12848 12849 XSetForeground(display, gc, colorToX(p.color, display)); 12850 12851 version(with_xft) 12852 updateXftColor(); 12853 } 12854 12855 RasterOp _currentRasterOp; 12856 bool _currentRasterOpInitialized = false; 12857 @property void rasterOp(RasterOp op) { 12858 if(_currentRasterOpInitialized && _currentRasterOp == op) 12859 return; 12860 _currentRasterOp = op; 12861 _currentRasterOpInitialized = true; 12862 int mode; 12863 final switch(op) { 12864 case RasterOp.normal: 12865 mode = GXcopy; 12866 break; 12867 case RasterOp.xor: 12868 mode = GXxor; 12869 break; 12870 } 12871 XSetFunction(display, gc, mode); 12872 } 12873 12874 12875 bool _fillColorInitialized = false; 12876 12877 @property void fillColor(Color c) { 12878 if(_fillColorInitialized && _fillColor == c) 12879 return; // already good, no need to waste time calling it 12880 _fillColor = c; 12881 _fillColorInitialized = true; 12882 if(c.a == 0) { 12883 backgroundIsNotTransparent = false; 12884 return; 12885 } 12886 12887 backgroundIsNotTransparent = true; 12888 12889 XSetBackground(display, gc, colorToX(c, display)); 12890 12891 } 12892 12893 void swapColors() { 12894 auto tmp = _fillColor; 12895 fillColor = _outlineColor; 12896 auto newPen = _activePen; 12897 newPen.color = tmp; 12898 pen(newPen); 12899 } 12900 12901 uint colorToX(Color c, Display* display) { 12902 auto visual = DefaultVisual(display, DefaultScreen(display)); 12903 import core.bitop; 12904 uint color = 0; 12905 { 12906 auto startBit = bsf(visual.red_mask); 12907 auto lastBit = bsr(visual.red_mask); 12908 auto r = cast(uint) c.r; 12909 r >>= 7 - (lastBit - startBit); 12910 r <<= startBit; 12911 color |= r; 12912 } 12913 { 12914 auto startBit = bsf(visual.green_mask); 12915 auto lastBit = bsr(visual.green_mask); 12916 auto g = cast(uint) c.g; 12917 g >>= 7 - (lastBit - startBit); 12918 g <<= startBit; 12919 color |= g; 12920 } 12921 { 12922 auto startBit = bsf(visual.blue_mask); 12923 auto lastBit = bsr(visual.blue_mask); 12924 auto b = cast(uint) c.b; 12925 b >>= 7 - (lastBit - startBit); 12926 b <<= startBit; 12927 color |= b; 12928 } 12929 12930 12931 12932 return color; 12933 } 12934 12935 void drawImage(int x, int y, Image i, int ix, int iy, int w, int h) { 12936 // source x, source y 12937 if(ix >= i.width) return; 12938 if(iy >= i.height) return; 12939 if(ix + w > i.width) w = i.width - ix; 12940 if(iy + h > i.height) h = i.height - iy; 12941 if(i.usingXshm) 12942 XShmPutImage(display, d, gc, i.handle, ix, iy, x, y, w, h, false); 12943 else 12944 XPutImage(display, d, gc, i.handle, ix, iy, x, y, w, h); 12945 } 12946 12947 void drawPixmap(Sprite s, int x, int y, int ix, int iy, int w, int h) { 12948 if(s.enableAlpha) { 12949 // the Sprite must be created first, meaning if we're here, XRender is already loaded 12950 if(this.xrenderPicturePainter == None) { 12951 XRenderPictureAttributes attrs; 12952 // FIXME: I can prolly reuse this as long as the pixmap itself is valid. 12953 xrenderPicturePainter = XRenderCreatePicture(display, d, Sprite.RGB24, 0, &attrs); 12954 12955 // need to initialize the clip 12956 XRectangle[1] rects; 12957 rects[0] = XRectangle(cast(short)(_clipRectangle.left), cast(short)(_clipRectangle.top), cast(short) _clipRectangle.width, cast(short) _clipRectangle.height); 12958 12959 if(_clipRectangle != Rectangle.init) 12960 XRenderSetPictureClipRectangles(display, xrenderPicturePainter, 0, 0, rects.ptr, cast(int) rects.length); 12961 } 12962 12963 XRenderComposite( 12964 display, 12965 3, // PicOpOver 12966 s.xrenderPicture, 12967 None, 12968 this.xrenderPicturePainter, 12969 ix, 12970 iy, 12971 0, 12972 0, 12973 x, 12974 y, 12975 w ? w : s.width, 12976 h ? h : s.height 12977 ); 12978 } else { 12979 XCopyArea(display, s.handle, d, gc, ix, iy, w ? w : s.width, h ? h : s.height, x, y); 12980 } 12981 } 12982 12983 int fontHeight() { 12984 version(with_xft) 12985 if(xftFont !is null) 12986 return xftFont.height; 12987 if(font) 12988 return font.max_bounds.ascent + font.max_bounds.descent; 12989 return 12; // pretty common default... 12990 } 12991 12992 int textWidth(in char[] line) { 12993 version(with_xft) 12994 if(xftFont) { 12995 if(line.length == 0) 12996 return 0; 12997 XGlyphInfo extents; 12998 XftTextExtentsUtf8(display, xftFont, line.ptr, cast(int) line.length, &extents); 12999 return extents.width; 13000 } 13001 13002 if(fontset) { 13003 if(line.length == 0) 13004 return 0; 13005 XRectangle rect; 13006 Xutf8TextExtents(fontset, line.ptr, cast(int) line.length, null, &rect); 13007 13008 return rect.width; 13009 } 13010 13011 if(font) 13012 // FIXME: unicode 13013 return XTextWidth( font, line.ptr, cast(int) line.length); 13014 else 13015 return fontHeight / 2 * cast(int) line.length; // if no font is loaded, it is prolly Fixed, which is a 2:1 ratio 13016 } 13017 13018 Size textSize(in char[] text) { 13019 auto maxWidth = 0; 13020 auto lineHeight = fontHeight; 13021 int h = text.length ? 0 : lineHeight + 4; // if text is empty, it still gives the line height 13022 foreach(line; text.split('\n')) { 13023 int textWidth = this.textWidth(line); 13024 if(textWidth > maxWidth) 13025 maxWidth = textWidth; 13026 h += lineHeight + 4; 13027 } 13028 return Size(maxWidth, h); 13029 } 13030 13031 void drawText(in int x, in int y, in int x2, in int y2, in char[] originalText, in uint alignment) { 13032 const(char)[] text; 13033 version(with_xft) 13034 if(xftFont) { 13035 text = originalText; 13036 goto loaded; 13037 } 13038 13039 if(fontset) 13040 text = originalText; 13041 else { 13042 text.reserve(originalText.length); 13043 // the first 256 unicode codepoints are the same as ascii and latin-1, which is what X expects, so we can keep all those 13044 // then strip the rest so there isn't garbage 13045 foreach(dchar ch; originalText) 13046 if(ch < 256) 13047 text ~= cast(ubyte) ch; 13048 else 13049 text ~= 191; // FIXME: using a random character (upside down question mark) to fill the space 13050 } 13051 loaded: 13052 if(text.length == 0) 13053 return; 13054 13055 // FIXME: should we clip it to the bounding box? 13056 int textHeight = fontHeight; 13057 13058 auto lines = text.split('\n'); 13059 13060 const lineHeight = textHeight; 13061 textHeight *= lines.length; 13062 13063 int cy = y; 13064 13065 if(alignment & TextAlignment.VerticalBottom) { 13066 if(y2 <= 0) 13067 return; 13068 auto h = y2 - y; 13069 if(h > textHeight) { 13070 cy += h - textHeight; 13071 cy -= lineHeight / 2; 13072 } 13073 } else if(alignment & TextAlignment.VerticalCenter) { 13074 if(y2 <= 0) 13075 return; 13076 auto h = y2 - y; 13077 if(textHeight < h) { 13078 cy += (h - textHeight) / 2; 13079 //cy -= lineHeight / 4; 13080 } 13081 } 13082 13083 foreach(line; text.split('\n')) { 13084 int textWidth = this.textWidth(line); 13085 13086 int px = x, py = cy; 13087 13088 if(alignment & TextAlignment.Center) { 13089 if(x2 <= 0) 13090 return; 13091 auto w = x2 - x; 13092 if(w > textWidth) 13093 px += (w - textWidth) / 2; 13094 } else if(alignment & TextAlignment.Right) { 13095 if(x2 <= 0) 13096 return; 13097 auto pos = x2 - textWidth; 13098 if(pos > x) 13099 px = pos; 13100 } 13101 13102 version(with_xft) 13103 if(xftFont) { 13104 XftDrawStringUtf8(xftDraw, &xftColor, xftFont, px, py + xftFont.ascent, line.ptr, cast(int) line.length); 13105 13106 goto carry_on; 13107 } 13108 13109 if(fontset) 13110 Xutf8DrawString(display, d, fontset, gc, px, py + (font ? font.max_bounds.ascent : lineHeight), line.ptr, cast(int) line.length); 13111 else 13112 XDrawString(display, d, gc, px, py + (font ? font.max_bounds.ascent : lineHeight), line.ptr, cast(int) line.length); 13113 carry_on: 13114 cy += lineHeight + 4; 13115 } 13116 } 13117 13118 void drawPixel(int x, int y) { 13119 XDrawPoint(display, d, gc, x, y); 13120 } 13121 13122 // The basic shapes, outlined 13123 13124 void drawLine(int x1, int y1, int x2, int y2) { 13125 if(foregroundIsNotTransparent) 13126 XDrawLine(display, d, gc, x1, y1, x2, y2); 13127 } 13128 13129 void drawRectangle(int x, int y, int width, int height) { 13130 if(backgroundIsNotTransparent) { 13131 swapColors(); 13132 XFillRectangle(display, d, gc, x+1, y+1, width-2, height-2); // Need to ensure pixels are only drawn once... 13133 swapColors(); 13134 } 13135 // 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 13136 if(foregroundIsNotTransparent) 13137 XDrawRectangle(display, d, gc, x + _activePen.width / 2, y + _activePen.width / 2, width - 1 - _activePen.width / 2, height - 1 - _activePen.width / 2); 13138 } 13139 13140 /// Arguments are the points of the bounding rectangle 13141 void drawEllipse(int x1, int y1, int x2, int y2) { 13142 drawArc(x1, y1, x2 - x1, y2 - y1, 0, 360 * 64); 13143 } 13144 13145 // NOTE: start and finish are in units of degrees * 64 13146 void drawArc(int x1, int y1, int width, int height, int start, int finish) { 13147 if(backgroundIsNotTransparent) { 13148 swapColors(); 13149 XFillArc(display, d, gc, x1, y1, width, height, start, finish); 13150 swapColors(); 13151 } 13152 if(foregroundIsNotTransparent) { 13153 XDrawArc(display, d, gc, x1, y1, width, height, start, finish); 13154 13155 // Windows draws the straight lines on the edges too so FIXME sort of 13156 } 13157 } 13158 13159 void drawPolygon(Point[] vertexes) { 13160 XPoint[16] pointsBuffer; 13161 XPoint[] points; 13162 if(vertexes.length <= pointsBuffer.length) 13163 points = pointsBuffer[0 .. vertexes.length]; 13164 else 13165 points.length = vertexes.length; 13166 13167 foreach(i, p; vertexes) { 13168 points[i].x = cast(short) p.x; 13169 points[i].y = cast(short) p.y; 13170 } 13171 13172 if(backgroundIsNotTransparent) { 13173 swapColors(); 13174 XFillPolygon(display, d, gc, points.ptr, cast(int) points.length, PolygonShape.Complex, CoordMode.CoordModeOrigin); 13175 swapColors(); 13176 } 13177 if(foregroundIsNotTransparent) { 13178 XDrawLines(display, d, gc, points.ptr, cast(int) points.length, CoordMode.CoordModeOrigin); 13179 } 13180 } 13181 } 13182 13183 /* XRender { */ 13184 13185 struct XRenderColor { 13186 ushort red; 13187 ushort green; 13188 ushort blue; 13189 ushort alpha; 13190 } 13191 13192 alias Picture = XID; 13193 alias PictFormat = XID; 13194 13195 struct XGlyphInfo { 13196 ushort width; 13197 ushort height; 13198 short x; 13199 short y; 13200 short xOff; 13201 short yOff; 13202 } 13203 13204 struct XRenderDirectFormat { 13205 short red; 13206 short redMask; 13207 short green; 13208 short greenMask; 13209 short blue; 13210 short blueMask; 13211 short alpha; 13212 short alphaMask; 13213 } 13214 13215 struct XRenderPictFormat { 13216 PictFormat id; 13217 int type; 13218 int depth; 13219 XRenderDirectFormat direct; 13220 Colormap colormap; 13221 } 13222 13223 enum PictFormatID = (1 << 0); 13224 enum PictFormatType = (1 << 1); 13225 enum PictFormatDepth = (1 << 2); 13226 enum PictFormatRed = (1 << 3); 13227 enum PictFormatRedMask =(1 << 4); 13228 enum PictFormatGreen = (1 << 5); 13229 enum PictFormatGreenMask=(1 << 6); 13230 enum PictFormatBlue = (1 << 7); 13231 enum PictFormatBlueMask =(1 << 8); 13232 enum PictFormatAlpha = (1 << 9); 13233 enum PictFormatAlphaMask=(1 << 10); 13234 enum PictFormatColormap =(1 << 11); 13235 13236 struct XRenderPictureAttributes { 13237 int repeat; 13238 Picture alpha_map; 13239 int alpha_x_origin; 13240 int alpha_y_origin; 13241 int clip_x_origin; 13242 int clip_y_origin; 13243 Pixmap clip_mask; 13244 Bool graphics_exposures; 13245 int subwindow_mode; 13246 int poly_edge; 13247 int poly_mode; 13248 Atom dither; 13249 Bool component_alpha; 13250 } 13251 13252 alias int XFixed; 13253 13254 struct XPointFixed { 13255 XFixed x, y; 13256 } 13257 13258 struct XCircle { 13259 XFixed x; 13260 XFixed y; 13261 XFixed radius; 13262 } 13263 13264 struct XTransform { 13265 XFixed[3][3] matrix; 13266 } 13267 13268 struct XFilters { 13269 int nfilter; 13270 char **filter; 13271 int nalias; 13272 short *alias_; 13273 } 13274 13275 struct XIndexValue { 13276 c_ulong pixel; 13277 ushort red, green, blue, alpha; 13278 } 13279 13280 struct XAnimCursor { 13281 Cursor cursor; 13282 c_ulong delay; 13283 } 13284 13285 struct XLinearGradient { 13286 XPointFixed p1; 13287 XPointFixed p2; 13288 } 13289 13290 struct XRadialGradient { 13291 XCircle inner; 13292 XCircle outer; 13293 } 13294 13295 struct XConicalGradient { 13296 XPointFixed center; 13297 XFixed angle; /* in degrees */ 13298 } 13299 13300 enum PictStandardARGB32 = 0; 13301 enum PictStandardRGB24 = 1; 13302 enum PictStandardA8 = 2; 13303 enum PictStandardA4 = 3; 13304 enum PictStandardA1 = 4; 13305 enum PictStandardNUM = 5; 13306 13307 interface XRender { 13308 extern(C) @nogc: 13309 13310 Bool XRenderQueryExtension (Display *dpy, int *event_basep, int *error_basep); 13311 13312 Status XRenderQueryVersion (Display *dpy, 13313 int *major_versionp, 13314 int *minor_versionp); 13315 13316 Status XRenderQueryFormats (Display *dpy); 13317 13318 int XRenderQuerySubpixelOrder (Display *dpy, int screen); 13319 13320 Bool XRenderSetSubpixelOrder (Display *dpy, int screen, int subpixel); 13321 13322 XRenderPictFormat * 13323 XRenderFindVisualFormat (Display *dpy, const Visual *visual); 13324 13325 XRenderPictFormat * 13326 XRenderFindFormat (Display *dpy, 13327 c_ulong mask, 13328 const XRenderPictFormat *templ, 13329 int count); 13330 XRenderPictFormat * 13331 XRenderFindStandardFormat (Display *dpy, 13332 int format); 13333 13334 XIndexValue * 13335 XRenderQueryPictIndexValues(Display *dpy, 13336 const XRenderPictFormat *format, 13337 int *num); 13338 13339 Picture XRenderCreatePicture( 13340 Display *dpy, 13341 Drawable drawable, 13342 const XRenderPictFormat *format, 13343 c_ulong valuemask, 13344 const XRenderPictureAttributes *attributes); 13345 13346 void XRenderChangePicture (Display *dpy, 13347 Picture picture, 13348 c_ulong valuemask, 13349 const XRenderPictureAttributes *attributes); 13350 13351 void 13352 XRenderSetPictureClipRectangles (Display *dpy, 13353 Picture picture, 13354 int xOrigin, 13355 int yOrigin, 13356 const XRectangle *rects, 13357 int n); 13358 13359 void 13360 XRenderSetPictureClipRegion (Display *dpy, 13361 Picture picture, 13362 Region r); 13363 13364 void 13365 XRenderSetPictureTransform (Display *dpy, 13366 Picture picture, 13367 XTransform *transform); 13368 13369 void 13370 XRenderFreePicture (Display *dpy, 13371 Picture picture); 13372 13373 void 13374 XRenderComposite (Display *dpy, 13375 int op, 13376 Picture src, 13377 Picture mask, 13378 Picture dst, 13379 int src_x, 13380 int src_y, 13381 int mask_x, 13382 int mask_y, 13383 int dst_x, 13384 int dst_y, 13385 uint width, 13386 uint height); 13387 13388 13389 Picture XRenderCreateSolidFill (Display *dpy, 13390 const XRenderColor *color); 13391 13392 Picture XRenderCreateLinearGradient (Display *dpy, 13393 const XLinearGradient *gradient, 13394 const XFixed *stops, 13395 const XRenderColor *colors, 13396 int nstops); 13397 13398 Picture XRenderCreateRadialGradient (Display *dpy, 13399 const XRadialGradient *gradient, 13400 const XFixed *stops, 13401 const XRenderColor *colors, 13402 int nstops); 13403 13404 Picture XRenderCreateConicalGradient (Display *dpy, 13405 const XConicalGradient *gradient, 13406 const XFixed *stops, 13407 const XRenderColor *colors, 13408 int nstops); 13409 13410 13411 13412 Cursor 13413 XRenderCreateCursor (Display *dpy, 13414 Picture source, 13415 uint x, 13416 uint y); 13417 13418 XFilters * 13419 XRenderQueryFilters (Display *dpy, Drawable drawable); 13420 13421 void 13422 XRenderSetPictureFilter (Display *dpy, 13423 Picture picture, 13424 const char *filter, 13425 XFixed *params, 13426 int nparams); 13427 13428 Cursor 13429 XRenderCreateAnimCursor (Display *dpy, 13430 int ncursor, 13431 XAnimCursor *cursors); 13432 } 13433 13434 __gshared bool XRenderLibrarySuccessfullyLoaded = true; 13435 mixin DynamicLoad!(XRender, "Xrender", 1, XRenderLibrarySuccessfullyLoaded) XRenderLibrary; 13436 13437 /* XRender } */ 13438 13439 /* Xrandr { */ 13440 13441 struct XRRMonitorInfo { 13442 Atom name; 13443 Bool primary; 13444 Bool automatic; 13445 int noutput; 13446 int x; 13447 int y; 13448 int width; 13449 int height; 13450 int mwidth; 13451 int mheight; 13452 /*RROutput*/ void *outputs; 13453 } 13454 13455 struct XRRScreenChangeNotifyEvent { 13456 int type; /* event base */ 13457 c_ulong serial; /* # of last request processed by server */ 13458 Bool send_event; /* true if this came from a SendEvent request */ 13459 Display *display; /* Display the event was read from */ 13460 Window window; /* window which selected for this event */ 13461 Window root; /* Root window for changed screen */ 13462 Time timestamp; /* when the screen change occurred */ 13463 Time config_timestamp; /* when the last configuration change */ 13464 ushort/*SizeID*/ size_index; 13465 ushort/*SubpixelOrder*/ subpixel_order; 13466 ushort/*Rotation*/ rotation; 13467 int width; 13468 int height; 13469 int mwidth; 13470 int mheight; 13471 } 13472 13473 enum RRScreenChangeNotify = 0; 13474 13475 enum RRScreenChangeNotifyMask = 1; 13476 13477 __gshared int xrrEventBase = -1; 13478 13479 13480 interface XRandr { 13481 extern(C) @nogc: 13482 Bool XRRQueryExtension (Display *dpy, int *event_base_return, int *error_base_return); 13483 Status XRRQueryVersion (Display *dpy, int *major_version_return, int *minor_version_return); 13484 13485 XRRMonitorInfo * XRRGetMonitors(Display *dpy, Window window, Bool get_active, int *nmonitors); 13486 void XRRFreeMonitors(XRRMonitorInfo *monitors); 13487 13488 void XRRSelectInput(Display *dpy, Window window, int mask); 13489 } 13490 13491 __gshared bool XRandrLibrarySuccessfullyLoaded = true; 13492 mixin DynamicLoad!(XRandr, "Xrandr", 2, XRandrLibrarySuccessfullyLoaded) XRandrLibrary; 13493 /* Xrandr } */ 13494 13495 /* Xft { */ 13496 13497 // actually freetype 13498 alias void FT_Face; 13499 13500 // actually fontconfig 13501 private alias FcBool = int; 13502 alias void FcCharSet; 13503 alias void FcPattern; 13504 alias void FcResult; 13505 enum FcEndian { FcEndianBig, FcEndianLittle } 13506 struct FcFontSet { 13507 int nfont; 13508 int sfont; 13509 FcPattern** fonts; 13510 } 13511 13512 // actually XRegion 13513 struct BOX { 13514 short x1, x2, y1, y2; 13515 } 13516 struct _XRegion { 13517 c_long size; 13518 c_long numRects; 13519 BOX* rects; 13520 BOX extents; 13521 } 13522 13523 alias Region = _XRegion*; 13524 13525 // ok actually Xft 13526 13527 struct XftFontInfo; 13528 13529 struct XftFont { 13530 int ascent; 13531 int descent; 13532 int height; 13533 int max_advance_width; 13534 FcCharSet* charset; 13535 FcPattern* pattern; 13536 } 13537 13538 struct XftDraw; 13539 13540 struct XftColor { 13541 c_ulong pixel; 13542 XRenderColor color; 13543 } 13544 13545 struct XftCharSpec { 13546 dchar ucs4; 13547 short x; 13548 short y; 13549 } 13550 13551 struct XftCharFontSpec { 13552 XftFont *font; 13553 dchar ucs4; 13554 short x; 13555 short y; 13556 } 13557 13558 struct XftGlyphSpec { 13559 uint glyph; 13560 short x; 13561 short y; 13562 } 13563 13564 struct XftGlyphFontSpec { 13565 XftFont *font; 13566 uint glyph; 13567 short x; 13568 short y; 13569 } 13570 13571 interface Xft { 13572 extern(C) @nogc pure: 13573 13574 Bool XftColorAllocName (Display *dpy, 13575 const Visual *visual, 13576 Colormap cmap, 13577 const char *name, 13578 XftColor *result); 13579 13580 Bool XftColorAllocValue (Display *dpy, 13581 Visual *visual, 13582 Colormap cmap, 13583 const XRenderColor *color, 13584 XftColor *result); 13585 13586 void XftColorFree (Display *dpy, 13587 Visual *visual, 13588 Colormap cmap, 13589 XftColor *color); 13590 13591 Bool XftDefaultHasRender (Display *dpy); 13592 13593 Bool XftDefaultSet (Display *dpy, FcPattern *defaults); 13594 13595 void XftDefaultSubstitute (Display *dpy, int screen, FcPattern *pattern); 13596 13597 XftDraw * XftDrawCreate (Display *dpy, 13598 Drawable drawable, 13599 Visual *visual, 13600 Colormap colormap); 13601 13602 XftDraw * XftDrawCreateBitmap (Display *dpy, 13603 Pixmap bitmap); 13604 13605 XftDraw * XftDrawCreateAlpha (Display *dpy, 13606 Pixmap pixmap, 13607 int depth); 13608 13609 void XftDrawChange (XftDraw *draw, 13610 Drawable drawable); 13611 13612 Display * XftDrawDisplay (XftDraw *draw); 13613 13614 Drawable XftDrawDrawable (XftDraw *draw); 13615 13616 Colormap XftDrawColormap (XftDraw *draw); 13617 13618 Visual * XftDrawVisual (XftDraw *draw); 13619 13620 void XftDrawDestroy (XftDraw *draw); 13621 13622 Picture XftDrawPicture (XftDraw *draw); 13623 13624 Picture XftDrawSrcPicture (XftDraw *draw, const XftColor *color); 13625 13626 void XftDrawGlyphs (XftDraw *draw, 13627 const XftColor *color, 13628 XftFont *pub, 13629 int x, 13630 int y, 13631 const uint *glyphs, 13632 int nglyphs); 13633 13634 void XftDrawString8 (XftDraw *draw, 13635 const XftColor *color, 13636 XftFont *pub, 13637 int x, 13638 int y, 13639 const char *string, 13640 int len); 13641 13642 void XftDrawString16 (XftDraw *draw, 13643 const XftColor *color, 13644 XftFont *pub, 13645 int x, 13646 int y, 13647 const wchar *string, 13648 int len); 13649 13650 void XftDrawString32 (XftDraw *draw, 13651 const XftColor *color, 13652 XftFont *pub, 13653 int x, 13654 int y, 13655 const dchar *string, 13656 int len); 13657 13658 void XftDrawStringUtf8 (XftDraw *draw, 13659 const XftColor *color, 13660 XftFont *pub, 13661 int x, 13662 int y, 13663 const char *string, 13664 int len); 13665 void XftDrawStringUtf16 (XftDraw *draw, 13666 const XftColor *color, 13667 XftFont *pub, 13668 int x, 13669 int y, 13670 const char *string, 13671 FcEndian endian, 13672 int len); 13673 13674 void XftDrawCharSpec (XftDraw *draw, 13675 const XftColor *color, 13676 XftFont *pub, 13677 const XftCharSpec *chars, 13678 int len); 13679 13680 void XftDrawCharFontSpec (XftDraw *draw, 13681 const XftColor *color, 13682 const XftCharFontSpec *chars, 13683 int len); 13684 13685 void XftDrawGlyphSpec (XftDraw *draw, 13686 const XftColor *color, 13687 XftFont *pub, 13688 const XftGlyphSpec *glyphs, 13689 int len); 13690 13691 void XftDrawGlyphFontSpec (XftDraw *draw, 13692 const XftColor *color, 13693 const XftGlyphFontSpec *glyphs, 13694 int len); 13695 13696 void XftDrawRect (XftDraw *draw, 13697 const XftColor *color, 13698 int x, 13699 int y, 13700 uint width, 13701 uint height); 13702 13703 Bool XftDrawSetClip (XftDraw *draw, 13704 Region r); 13705 13706 13707 Bool XftDrawSetClipRectangles (XftDraw *draw, 13708 int xOrigin, 13709 int yOrigin, 13710 const XRectangle *rects, 13711 int n); 13712 13713 void XftDrawSetSubwindowMode (XftDraw *draw, 13714 int mode); 13715 13716 void XftGlyphExtents (Display *dpy, 13717 XftFont *pub, 13718 const uint *glyphs, 13719 int nglyphs, 13720 XGlyphInfo *extents); 13721 13722 void XftTextExtents8 (Display *dpy, 13723 XftFont *pub, 13724 const char *string, 13725 int len, 13726 XGlyphInfo *extents); 13727 13728 void XftTextExtents16 (Display *dpy, 13729 XftFont *pub, 13730 const wchar *string, 13731 int len, 13732 XGlyphInfo *extents); 13733 13734 void XftTextExtents32 (Display *dpy, 13735 XftFont *pub, 13736 const dchar *string, 13737 int len, 13738 XGlyphInfo *extents); 13739 13740 void XftTextExtentsUtf8 (Display *dpy, 13741 XftFont *pub, 13742 const char *string, 13743 int len, 13744 XGlyphInfo *extents); 13745 13746 void XftTextExtentsUtf16 (Display *dpy, 13747 XftFont *pub, 13748 const char *string, 13749 FcEndian endian, 13750 int len, 13751 XGlyphInfo *extents); 13752 13753 FcPattern * XftFontMatch (Display *dpy, 13754 int screen, 13755 const FcPattern *pattern, 13756 FcResult *result); 13757 13758 XftFont * XftFontOpen (Display *dpy, int screen, ...); 13759 13760 XftFont * XftFontOpenName (Display *dpy, int screen, const char *name); 13761 13762 XftFont * XftFontOpenXlfd (Display *dpy, int screen, const char *xlfd); 13763 13764 FT_Face XftLockFace (XftFont *pub); 13765 13766 void XftUnlockFace (XftFont *pub); 13767 13768 XftFontInfo * XftFontInfoCreate (Display *dpy, const FcPattern *pattern); 13769 13770 void XftFontInfoDestroy (Display *dpy, XftFontInfo *fi); 13771 13772 dchar XftFontInfoHash (const XftFontInfo *fi); 13773 13774 FcBool XftFontInfoEqual (const XftFontInfo *a, const XftFontInfo *b); 13775 13776 XftFont * XftFontOpenInfo (Display *dpy, 13777 FcPattern *pattern, 13778 XftFontInfo *fi); 13779 13780 XftFont * XftFontOpenPattern (Display *dpy, FcPattern *pattern); 13781 13782 XftFont * XftFontCopy (Display *dpy, XftFont *pub); 13783 13784 void XftFontClose (Display *dpy, XftFont *pub); 13785 13786 FcBool XftInitFtLibrary(); 13787 void XftFontLoadGlyphs (Display *dpy, 13788 XftFont *pub, 13789 FcBool need_bitmaps, 13790 const uint *glyphs, 13791 int nglyph); 13792 13793 void XftFontUnloadGlyphs (Display *dpy, 13794 XftFont *pub, 13795 const uint *glyphs, 13796 int nglyph); 13797 13798 FcBool XftFontCheckGlyph (Display *dpy, 13799 XftFont *pub, 13800 FcBool need_bitmaps, 13801 uint glyph, 13802 uint *missing, 13803 int *nmissing); 13804 13805 FcBool XftCharExists (Display *dpy, 13806 XftFont *pub, 13807 dchar ucs4); 13808 13809 uint XftCharIndex (Display *dpy, 13810 XftFont *pub, 13811 dchar ucs4); 13812 FcBool XftInit (const char *config); 13813 13814 int XftGetVersion (); 13815 13816 FcFontSet * XftListFonts (Display *dpy, 13817 int screen, 13818 ...); 13819 13820 FcPattern *XftNameParse (const char *name); 13821 13822 void XftGlyphRender (Display *dpy, 13823 int op, 13824 Picture src, 13825 XftFont *pub, 13826 Picture dst, 13827 int srcx, 13828 int srcy, 13829 int x, 13830 int y, 13831 const uint *glyphs, 13832 int nglyphs); 13833 13834 void XftGlyphSpecRender (Display *dpy, 13835 int op, 13836 Picture src, 13837 XftFont *pub, 13838 Picture dst, 13839 int srcx, 13840 int srcy, 13841 const XftGlyphSpec *glyphs, 13842 int nglyphs); 13843 13844 void XftCharSpecRender (Display *dpy, 13845 int op, 13846 Picture src, 13847 XftFont *pub, 13848 Picture dst, 13849 int srcx, 13850 int srcy, 13851 const XftCharSpec *chars, 13852 int len); 13853 void XftGlyphFontSpecRender (Display *dpy, 13854 int op, 13855 Picture src, 13856 Picture dst, 13857 int srcx, 13858 int srcy, 13859 const XftGlyphFontSpec *glyphs, 13860 int nglyphs); 13861 13862 void XftCharFontSpecRender (Display *dpy, 13863 int op, 13864 Picture src, 13865 Picture dst, 13866 int srcx, 13867 int srcy, 13868 const XftCharFontSpec *chars, 13869 int len); 13870 13871 void XftTextRender8 (Display *dpy, 13872 int op, 13873 Picture src, 13874 XftFont *pub, 13875 Picture dst, 13876 int srcx, 13877 int srcy, 13878 int x, 13879 int y, 13880 const char *string, 13881 int len); 13882 void XftTextRender16 (Display *dpy, 13883 int op, 13884 Picture src, 13885 XftFont *pub, 13886 Picture dst, 13887 int srcx, 13888 int srcy, 13889 int x, 13890 int y, 13891 const wchar *string, 13892 int len); 13893 13894 void XftTextRender16BE (Display *dpy, 13895 int op, 13896 Picture src, 13897 XftFont *pub, 13898 Picture dst, 13899 int srcx, 13900 int srcy, 13901 int x, 13902 int y, 13903 const char *string, 13904 int len); 13905 13906 void XftTextRender16LE (Display *dpy, 13907 int op, 13908 Picture src, 13909 XftFont *pub, 13910 Picture dst, 13911 int srcx, 13912 int srcy, 13913 int x, 13914 int y, 13915 const char *string, 13916 int len); 13917 13918 void XftTextRender32 (Display *dpy, 13919 int op, 13920 Picture src, 13921 XftFont *pub, 13922 Picture dst, 13923 int srcx, 13924 int srcy, 13925 int x, 13926 int y, 13927 const dchar *string, 13928 int len); 13929 13930 void XftTextRender32BE (Display *dpy, 13931 int op, 13932 Picture src, 13933 XftFont *pub, 13934 Picture dst, 13935 int srcx, 13936 int srcy, 13937 int x, 13938 int y, 13939 const char *string, 13940 int len); 13941 13942 void XftTextRender32LE (Display *dpy, 13943 int op, 13944 Picture src, 13945 XftFont *pub, 13946 Picture dst, 13947 int srcx, 13948 int srcy, 13949 int x, 13950 int y, 13951 const char *string, 13952 int len); 13953 13954 void XftTextRenderUtf8 (Display *dpy, 13955 int op, 13956 Picture src, 13957 XftFont *pub, 13958 Picture dst, 13959 int srcx, 13960 int srcy, 13961 int x, 13962 int y, 13963 const char *string, 13964 int len); 13965 13966 void XftTextRenderUtf16 (Display *dpy, 13967 int op, 13968 Picture src, 13969 XftFont *pub, 13970 Picture dst, 13971 int srcx, 13972 int srcy, 13973 int x, 13974 int y, 13975 const char *string, 13976 FcEndian endian, 13977 int len); 13978 FcPattern * XftXlfdParse (const char *xlfd_orig, Bool ignore_scalable, Bool complete); 13979 13980 } 13981 13982 interface FontConfig { 13983 extern(C) @nogc pure: 13984 int FcPatternGetString(const FcPattern *p, const char *object, int n, char ** s); 13985 void FcFontSetDestroy(FcFontSet*); 13986 char* FcNameUnparse(const FcPattern *); 13987 } 13988 13989 mixin DynamicLoad!(Xft, "Xft", 2, librariesSuccessfullyLoaded) XftLibrary; 13990 mixin DynamicLoad!(FontConfig, "fontconfig", 1, librariesSuccessfullyLoaded) FontConfigLibrary; 13991 13992 13993 /* Xft } */ 13994 13995 class XDisconnectException : Exception { 13996 bool userRequested; 13997 this(bool userRequested = true) { 13998 this.userRequested = userRequested; 13999 super("X disconnected"); 14000 } 14001 } 14002 14003 /++ 14004 Platform-specific for X11. Traps errors for the duration of `dg`. Avoid calling this from inside a call to this. 14005 14006 Please note that it returns 14007 +/ 14008 XErrorEvent[] trapXErrors(scope void delegate() dg) { 14009 14010 static XErrorEvent[] errorBuffer; 14011 14012 static extern(C) int handler (Display* dpy, XErrorEvent* evt) nothrow { 14013 errorBuffer ~= *evt; 14014 return 0; 14015 } 14016 14017 auto savedErrorHandler = XSetErrorHandler(&handler); 14018 14019 try { 14020 dg(); 14021 } finally { 14022 XSync(XDisplayConnection.get, 0/*False*/); 14023 XSetErrorHandler(savedErrorHandler); 14024 } 14025 14026 auto bfr = errorBuffer; 14027 errorBuffer = null; 14028 14029 return bfr; 14030 } 14031 14032 /// Platform-specific for X11. A singleton class (well, all its methods are actually static... so more like a namespace) wrapping a `Display*`. 14033 class XDisplayConnection { 14034 private __gshared Display* display; 14035 private __gshared XIM xim; 14036 private __gshared char* displayName; 14037 14038 private __gshared int connectionSequence_; 14039 private __gshared bool isLocal_; 14040 14041 /// use this for lazy caching when reconnection 14042 static int connectionSequenceNumber() { return connectionSequence_; } 14043 14044 /++ 14045 Guesses if the connection appears to be local. 14046 14047 History: 14048 Added June 3, 2021 14049 +/ 14050 static @property bool isLocal() nothrow @trusted @nogc { 14051 return isLocal_; 14052 } 14053 14054 /// Attempts recreation of state, may require application assistance 14055 /// You MUST call this OUTSIDE the event loop. Let the exception kill the loop, 14056 /// then call this, and if successful, reenter the loop. 14057 static void discardAndRecreate(string newDisplayString = null) { 14058 if(insideXEventLoop) 14059 throw new Error("You MUST call discardAndRecreate from OUTSIDE the event loop"); 14060 14061 // 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 14062 auto chnenhm = CapableOfHandlingNativeEvent.nativeHandleMapping.dup; 14063 14064 foreach(handle; chnenhm) { 14065 handle.discardConnectionState(); 14066 } 14067 14068 discardState(); 14069 14070 if(newDisplayString !is null) 14071 setDisplayName(newDisplayString); 14072 14073 auto display = get(); 14074 14075 foreach(handle; chnenhm) { 14076 handle.recreateAfterDisconnect(); 14077 } 14078 } 14079 14080 private __gshared EventMask rootEventMask; 14081 14082 /++ 14083 Requests the specified input from the root window on the connection, in addition to any other request. 14084 14085 14086 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. 14087 14088 $(WARNING it calls XSelectInput itself, which will override any other root window input you have!) 14089 +/ 14090 static void addRootInput(EventMask mask) { 14091 auto old = rootEventMask; 14092 rootEventMask |= mask; 14093 get(); // to ensure display connected 14094 if(display !is null && rootEventMask != old) 14095 XSelectInput(display, RootWindow(display, DefaultScreen(display)), rootEventMask); 14096 } 14097 14098 static void discardState() { 14099 freeImages(); 14100 14101 foreach(atomPtr; interredAtoms) 14102 *atomPtr = 0; 14103 interredAtoms = null; 14104 interredAtoms.assumeSafeAppend(); 14105 14106 ScreenPainterImplementation.fontAttempted = false; 14107 ScreenPainterImplementation.defaultfont = null; 14108 ScreenPainterImplementation.defaultfontset = null; 14109 14110 Image.impl.xshmQueryCompleted = false; 14111 Image.impl._xshmAvailable = false; 14112 14113 SimpleWindow.nativeMapping = null; 14114 CapableOfHandlingNativeEvent.nativeHandleMapping = null; 14115 // GlobalHotkeyManager 14116 14117 display = null; 14118 xim = null; 14119 } 14120 14121 // Do you want to know why do we need all this horrible-looking code? See comment at the bottom. 14122 private static void createXIM () { 14123 import core.stdc.locale : setlocale, LC_ALL; 14124 import core.stdc.stdio : stderr, fprintf; 14125 import core.stdc.stdlib : free; 14126 import core.stdc.string : strdup; 14127 14128 static immutable string[3] mtry = [ "", "@im=local", "@im=" ]; 14129 14130 auto olocale = strdup(setlocale(LC_ALL, null)); 14131 setlocale(LC_ALL, (sdx_isUTF8Locale ? "" : "en_US.UTF-8")); 14132 scope(exit) { setlocale(LC_ALL, olocale); free(olocale); } 14133 14134 //fprintf(stderr, "opening IM...\n"); 14135 foreach (string s; mtry) { 14136 XSetLocaleModifiers(s.ptr); // it's safe, as `s` is string literal 14137 if ((xim = XOpenIM(display, null, null, null)) !is null) return; 14138 } 14139 fprintf(stderr, "createXIM: XOpenIM failed!\n"); 14140 } 14141 14142 // for X11 we will keep all XShm-allocated images in this list, so we can free 'em on connection closing. 14143 // we'll use glibc malloc()/free(), 'cause `unregisterImage()` can be called from object dtor. 14144 static struct ImgList { 14145 size_t img; // class; hide it from GC 14146 ImgList* next; 14147 } 14148 14149 static __gshared ImgList* imglist = null; 14150 static __gshared bool imglistLocked = false; // true: don't register and unregister images 14151 14152 static void registerImage (Image img) { 14153 if (!imglistLocked && img !is null) { 14154 import core.stdc.stdlib : malloc; 14155 auto it = cast(ImgList*)malloc(ImgList.sizeof); 14156 assert(it !is null); // do proper checks 14157 it.img = cast(size_t)cast(void*)img; 14158 it.next = imglist; 14159 imglist = it; 14160 version(sdpy_debug_xshm) { import core.stdc.stdio : printf; printf("registering image %p\n", cast(void*)img); } 14161 } 14162 } 14163 14164 static void unregisterImage (Image img) { 14165 if (!imglistLocked && img !is null) { 14166 import core.stdc.stdlib : free; 14167 ImgList* prev = null; 14168 ImgList* cur = imglist; 14169 while (cur !is null) { 14170 if (cur.img == cast(size_t)cast(void*)img) break; // i found her! 14171 prev = cur; 14172 cur = cur.next; 14173 } 14174 if (cur !is null) { 14175 if (prev is null) imglist = cur.next; else prev.next = cur.next; 14176 free(cur); 14177 version(sdpy_debug_xshm) { import core.stdc.stdio : printf; printf("unregistering image %p\n", cast(void*)img); } 14178 } else { 14179 version(sdpy_debug_xshm) { import core.stdc.stdio : printf; printf("trying to unregister unknown image %p\n", cast(void*)img); } 14180 } 14181 } 14182 } 14183 14184 static void freeImages () { // needed for discardAndRecreate 14185 imglistLocked = true; 14186 scope(exit) imglistLocked = false; 14187 ImgList* cur = imglist; 14188 ImgList* next = null; 14189 while (cur !is null) { 14190 import core.stdc.stdlib : free; 14191 next = cur.next; 14192 version(sdpy_debug_xshm) { import core.stdc.stdio : printf; printf("disposing image %p\n", cast(void*)cur.img); } 14193 (cast(Image)cast(void*)cur.img).dispose(); 14194 free(cur); 14195 cur = next; 14196 } 14197 imglist = null; 14198 } 14199 14200 /// can be used to override normal handling of display name 14201 /// from environment and/or command line 14202 static setDisplayName(string newDisplayName) { 14203 displayName = cast(char*) (newDisplayName ~ '\0'); 14204 } 14205 14206 /// resets to the default display string 14207 static resetDisplayName() { 14208 displayName = null; 14209 } 14210 14211 /// 14212 static Display* get() { 14213 if(display is null) { 14214 if(!librariesSuccessfullyLoaded) 14215 throw new Exception("Unable to load X11 client libraries"); 14216 display = XOpenDisplay(displayName); 14217 14218 isLocal_ = false; 14219 14220 connectionSequence_++; 14221 if(display is null) 14222 throw new Exception("Unable to open X display"); 14223 14224 auto str = display.display_name; 14225 // this is a bit of a hack but like if it looks like a unix socket we assume it is local 14226 // and otherwise it probably isn't 14227 if(str is null || (str[0] != ':' && str[0] != '/')) 14228 isLocal_ = false; 14229 else 14230 isLocal_ = true; 14231 14232 debug(sdpy_x_errors) { 14233 XSetErrorHandler(&adrlogger); 14234 XSynchronize(display, true); 14235 14236 extern(C) int wtf() { 14237 if(errorHappened) { 14238 asm { int 3; } 14239 errorHappened = false; 14240 } 14241 return 0; 14242 } 14243 XSetAfterFunction(display, &wtf); 14244 } 14245 14246 14247 XSetIOErrorHandler(&x11ioerrCB); 14248 Bool sup; 14249 XkbSetDetectableAutoRepeat(display, 1, &sup); // so we will not receive KeyRelease until key is really released 14250 createXIM(); 14251 version(with_eventloop) { 14252 import arsd.eventloop; 14253 addFileEventListeners(display.fd, &eventListener, null, null); 14254 } 14255 } 14256 14257 return display; 14258 } 14259 14260 extern(C) 14261 static int x11ioerrCB(Display* dpy) { 14262 throw new XDisconnectException(false); 14263 } 14264 14265 version(with_eventloop) { 14266 import arsd.eventloop; 14267 static void eventListener(OsFileHandle fd) { 14268 //this.mtLock(); 14269 //scope(exit) this.mtUnlock(); 14270 while(XPending(display)) 14271 doXNextEvent(display); 14272 } 14273 } 14274 14275 // close connection on program exit -- we need this to properly free all images 14276 static ~this () { 14277 // the gui thread must clean up after itself or else Xlib might deadlock 14278 // using this flag on any thread destruction is the easiest way i know of 14279 // (shared static this is run by the LAST thread to exit, which may not be 14280 // the gui thread, and normal static this run by ALL threads, so we gotta check.) 14281 if(thisIsGuiThread) 14282 close(); 14283 } 14284 14285 /// 14286 static void close() { 14287 if(display is null) 14288 return; 14289 14290 version(with_eventloop) { 14291 import arsd.eventloop; 14292 removeFileEventListeners(display.fd); 14293 } 14294 14295 // now remove all registered images to prevent shared memory leaks 14296 freeImages(); 14297 14298 // tbh I don't know why it is doing this but like if this happens to run 14299 // from the other thread there's frequent hanging inside here. 14300 if(thisIsGuiThread) 14301 XCloseDisplay(display); 14302 display = null; 14303 } 14304 } 14305 14306 mixin template NativeImageImplementation() { 14307 XImage* handle; 14308 ubyte* rawData; 14309 14310 XShmSegmentInfo shminfo; 14311 bool premultiply = true; 14312 14313 __gshared bool xshmQueryCompleted; 14314 __gshared bool _xshmAvailable; 14315 public static @property bool xshmAvailable() { 14316 if(!xshmQueryCompleted) { 14317 int i1, i2, i3; 14318 xshmQueryCompleted = true; 14319 14320 if(!XDisplayConnection.isLocal) 14321 _xshmAvailable = false; 14322 else 14323 _xshmAvailable = XQueryExtension(XDisplayConnection.get(), "MIT-SHM", &i1, &i2, &i3) != 0; 14324 } 14325 return _xshmAvailable; 14326 } 14327 14328 bool usingXshm; 14329 final: 14330 14331 private __gshared bool xshmfailed; 14332 14333 void createImage(int width, int height, bool forcexshm=false, bool enableAlpha = false) { 14334 auto display = XDisplayConnection.get(); 14335 assert(display !is null); 14336 auto screen = DefaultScreen(display); 14337 14338 // it will only use shared memory for somewhat largish images, 14339 // since otherwise we risk wasting shared memory handles on a lot of little ones 14340 if (xshmAvailable && (forcexshm || (width > 100 && height > 100))) { 14341 14342 14343 // it is possible for the query extension to return true, the DISPLAY check to pass, yet 14344 // the actual use still fails. For example, if the program is in a container and permission denied 14345 // on shared memory, or if it is a local thing forwarded to a remote server, etc. 14346 // 14347 // If it does fail, we need to detect it now, abort the xshm and fall back to core protocol. 14348 14349 14350 // synchronize so preexisting buffers are clear 14351 XSync(display, false); 14352 xshmfailed = false; 14353 14354 auto oldErrorHandler = XSetErrorHandler(&XShmErrorHandler); 14355 14356 14357 usingXshm = true; 14358 handle = XShmCreateImage( 14359 display, 14360 DefaultVisual(display, screen), 14361 enableAlpha ? 32: 24, 14362 ImageFormat.ZPixmap, 14363 null, 14364 &shminfo, 14365 width, height); 14366 if(handle is null) 14367 goto abortXshm1; 14368 14369 if(handle.bytes_per_line != 4 * width) 14370 goto abortXshm2; 14371 14372 shminfo.shmid = shmget(IPC_PRIVATE, handle.bytes_per_line * height, IPC_CREAT | 511 /* 0777 */); 14373 if(shminfo.shmid < 0) 14374 goto abortXshm3; 14375 handle.data = shminfo.shmaddr = rawData = cast(ubyte*) shmat(shminfo.shmid, null, 0); 14376 if(rawData == cast(ubyte*) -1) 14377 goto abortXshm4; 14378 shminfo.readOnly = 0; 14379 XShmAttach(display, &shminfo); 14380 14381 // and now to the final error check to ensure it actually worked. 14382 XSync(display, false); 14383 if(xshmfailed) 14384 goto abortXshm5; 14385 14386 XSetErrorHandler(oldErrorHandler); 14387 14388 XDisplayConnection.registerImage(this); 14389 // if I don't flush here there's a chance the dtor will run before the 14390 // ctor and lead to a bad value X error. While this hurts the efficiency 14391 // it is local anyway so prolly better to keep it simple 14392 XFlush(display); 14393 14394 return; 14395 14396 abortXshm5: 14397 shmdt(shminfo.shmaddr); 14398 rawData = null; 14399 14400 abortXshm4: 14401 shmctl(shminfo.shmid, IPC_RMID, null); 14402 14403 abortXshm3: 14404 // nothing needed, the shmget failed so there's nothing to free 14405 14406 abortXshm2: 14407 XDestroyImage(handle); 14408 handle = null; 14409 14410 abortXshm1: 14411 XSetErrorHandler(oldErrorHandler); 14412 usingXshm = false; 14413 handle = null; 14414 14415 shminfo = typeof(shminfo).init; 14416 14417 _xshmAvailable = false; // don't try again in the future 14418 14419 // writeln("fallingback"); 14420 14421 goto fallback; 14422 14423 } else { 14424 fallback: 14425 14426 if (forcexshm) throw new Exception("can't create XShm Image"); 14427 // This actually needs to be malloc to avoid a double free error when XDestroyImage is called 14428 import core.stdc.stdlib : malloc; 14429 rawData = cast(ubyte*) malloc(width * height * 4); 14430 14431 handle = XCreateImage( 14432 display, 14433 DefaultVisual(display, screen), 14434 enableAlpha ? 32 : 24, // bpp 14435 ImageFormat.ZPixmap, 14436 0, // offset 14437 rawData, 14438 width, height, 14439 enableAlpha ? 32 : 8 /* FIXME */, 4 * width); // padding, bytes per line 14440 } 14441 } 14442 14443 void dispose() { 14444 // note: this calls free(rawData) for us 14445 if(handle) { 14446 if (usingXshm) { 14447 XDisplayConnection.unregisterImage(this); 14448 if (XDisplayConnection.get()) XShmDetach(XDisplayConnection.get(), &shminfo); 14449 } 14450 XDestroyImage(handle); 14451 if(usingXshm) { 14452 shmdt(shminfo.shmaddr); 14453 shmctl(shminfo.shmid, IPC_RMID, null); 14454 } 14455 handle = null; 14456 } 14457 } 14458 14459 Color getPixel(int x, int y) { 14460 auto offset = (y * width + x) * 4; 14461 Color c; 14462 c.a = enableAlpha ? rawData[offset + 3] : 255; 14463 c.b = rawData[offset + 0]; 14464 c.g = rawData[offset + 1]; 14465 c.r = rawData[offset + 2]; 14466 if(enableAlpha && premultiply) 14467 c.unPremultiply; 14468 return c; 14469 } 14470 14471 void setPixel(int x, int y, Color c) { 14472 if(enableAlpha && premultiply) 14473 c.premultiply(); 14474 auto offset = (y * width + x) * 4; 14475 rawData[offset + 0] = c.b; 14476 rawData[offset + 1] = c.g; 14477 rawData[offset + 2] = c.r; 14478 if(enableAlpha) 14479 rawData[offset + 3] = c.a; 14480 } 14481 14482 void convertToRgbaBytes(ubyte[] where) { 14483 assert(where.length == this.width * this.height * 4); 14484 14485 // if rawData had a length.... 14486 //assert(rawData.length == where.length); 14487 for(int idx = 0; idx < where.length; idx += 4) { 14488 where[idx + 0] = rawData[idx + 2]; // r 14489 where[idx + 1] = rawData[idx + 1]; // g 14490 where[idx + 2] = rawData[idx + 0]; // b 14491 where[idx + 3] = enableAlpha ? rawData[idx + 3] : 255; // a 14492 14493 if(enableAlpha && premultiply) 14494 unPremultiplyRgba(where[idx .. idx + 4]); 14495 } 14496 } 14497 14498 void setFromRgbaBytes(in ubyte[] where) { 14499 assert(where.length == this.width * this.height * 4); 14500 14501 // if rawData had a length.... 14502 //assert(rawData.length == where.length); 14503 for(int idx = 0; idx < where.length; idx += 4) { 14504 rawData[idx + 2] = where[idx + 0]; // r 14505 rawData[idx + 1] = where[idx + 1]; // g 14506 rawData[idx + 0] = where[idx + 2]; // b 14507 if(enableAlpha) { 14508 rawData[idx + 3] = where[idx + 3]; // a 14509 if(premultiply) 14510 premultiplyBgra(rawData[idx .. idx + 4]); 14511 } 14512 } 14513 } 14514 14515 } 14516 14517 mixin template NativeSimpleWindowImplementation() { 14518 GC gc; 14519 Window window; 14520 Display* display; 14521 14522 Pixmap buffer; 14523 int bufferw, bufferh; // size of the buffer; can be bigger than window 14524 XIC xic; // input context 14525 int curHidden = 0; // counter 14526 Cursor blankCurPtr = 0; 14527 int cursorSequenceNumber = 0; 14528 int warpEventCount = 0; // number of mouse movement events to eat 14529 14530 __gshared X11SetSelectionHandler[Atom] setSelectionHandlers; 14531 X11GetSelectionHandler[Atom] getSelectionHandlers; 14532 14533 version(without_opengl) {} else 14534 GLXContext glc; 14535 14536 private void fixFixedSize(bool forced=false) (int width, int height) { 14537 if (forced || this.resizability == Resizability.fixedSize) { 14538 //{ import core.stdc.stdio; printf("fixing size to: %dx%d\n", width, height); } 14539 XSizeHints sh; 14540 static if (!forced) { 14541 c_long spr; 14542 XGetWMNormalHints(display, window, &sh, &spr); 14543 sh.flags |= PMaxSize | PMinSize; 14544 } else { 14545 sh.flags = PMaxSize | PMinSize; 14546 } 14547 sh.min_width = width; 14548 sh.min_height = height; 14549 sh.max_width = width; 14550 sh.max_height = height; 14551 XSetWMNormalHints(display, window, &sh); 14552 //XFlush(display); 14553 } 14554 } 14555 14556 ScreenPainter getPainter(bool manualInvalidations) { 14557 return ScreenPainter(this, window, manualInvalidations); 14558 } 14559 14560 void move(int x, int y) { 14561 XMoveWindow(display, window, x, y); 14562 } 14563 14564 void resize(int w, int h) { 14565 if (w < 1) w = 1; 14566 if (h < 1) h = 1; 14567 XResizeWindow(display, window, w, h); 14568 14569 // calling this now to avoid waiting for the server to 14570 // acknowledge the resize; draws without returning to the 14571 // event loop will thus actually work. the server's event 14572 // btw might overrule this and resize it again 14573 recordX11Resize(display, this, w, h); 14574 14575 updateOpenglViewportIfNeeded(w, h); 14576 } 14577 14578 void moveResize (int x, int y, int w, int h) { 14579 if (w < 1) w = 1; 14580 if (h < 1) h = 1; 14581 XMoveResizeWindow(display, window, x, y, w, h); 14582 updateOpenglViewportIfNeeded(w, h); 14583 } 14584 14585 void hideCursor () { 14586 if (curHidden++ == 0) { 14587 if (!blankCurPtr || cursorSequenceNumber != XDisplayConnection.connectionSequenceNumber) { 14588 static const(char)[1] cmbmp = 0; 14589 XColor blackcolor = { 0, 0, 0, 0, 0, 0 }; 14590 Pixmap pm = XCreateBitmapFromData(display, window, cmbmp.ptr, 1, 1); 14591 blankCurPtr = XCreatePixmapCursor(display, pm, pm, &blackcolor, &blackcolor, 0, 0); 14592 cursorSequenceNumber = XDisplayConnection.connectionSequenceNumber; 14593 XFreePixmap(display, pm); 14594 } 14595 XDefineCursor(display, window, blankCurPtr); 14596 } 14597 } 14598 14599 void showCursor () { 14600 if (--curHidden == 0) XUndefineCursor(display, window); 14601 } 14602 14603 void warpMouse (int x, int y) { 14604 // here i will send dummy "ignore next mouse motion" event, 14605 // 'cause `XWarpPointer()` sends synthesised mouse motion, 14606 // and we don't need to report it to the user (as warping is 14607 // used when the user needs movement deltas). 14608 //XClientMessageEvent xclient; 14609 XEvent e; 14610 e.xclient.type = EventType.ClientMessage; 14611 e.xclient.window = window; 14612 e.xclient.message_type = GetAtom!("_X11SDPY_INSMME_FLAG_EVENT_", true)(display); // let's hope nobody else will use such stupid name ;-) 14613 e.xclient.format = 32; 14614 e.xclient.data.l[0] = 0; 14615 debug(x11sdpy_warp_debug) { import core.stdc.stdio : printf; printf("X11: sending \"INSMME\"...\n"); } 14616 //{ 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]); } 14617 XSendEvent(display, window, false, EventMask.NoEventMask, /*cast(XEvent*)&xclient*/&e); 14618 // now warp pointer... 14619 debug(x11sdpy_warp_debug) { import core.stdc.stdio : printf; printf("X11: sending \"warp\"...\n"); } 14620 XWarpPointer(display, None, window, 0, 0, 0, 0, x, y); 14621 // ...and flush 14622 debug(x11sdpy_warp_debug) { import core.stdc.stdio : printf; printf("X11: flushing...\n"); } 14623 XFlush(display); 14624 } 14625 14626 void sendDummyEvent () { 14627 // here i will send dummy event to ping event queue 14628 XEvent e; 14629 e.xclient.type = EventType.ClientMessage; 14630 e.xclient.window = window; 14631 e.xclient.message_type = GetAtom!("_X11SDPY_DUMMY_EVENT_", true)(display); // let's hope nobody else will use such stupid name ;-) 14632 e.xclient.format = 32; 14633 e.xclient.data.l[0] = 0; 14634 XSendEvent(display, window, false, EventMask.NoEventMask, /*cast(XEvent*)&xclient*/&e); 14635 XFlush(display); 14636 } 14637 14638 void setTitle(string title) { 14639 if (title.ptr is null) title = ""; 14640 auto XA_UTF8 = XInternAtom(display, "UTF8_STRING".ptr, false); 14641 auto XA_NETWM_NAME = XInternAtom(display, "_NET_WM_NAME".ptr, false); 14642 XTextProperty windowName; 14643 windowName.value = title.ptr; 14644 windowName.encoding = XA_UTF8; //XA_STRING; 14645 windowName.format = 8; 14646 windowName.nitems = cast(uint)title.length; 14647 XSetWMName(display, window, &windowName); 14648 char[1024] namebuf = 0; 14649 auto maxlen = namebuf.length-1; 14650 if (maxlen > title.length) maxlen = title.length; 14651 namebuf[0..maxlen] = title[0..maxlen]; 14652 XStoreName(display, window, namebuf.ptr); 14653 XChangeProperty(display, window, XA_NETWM_NAME, XA_UTF8, 8, PropModeReplace, title.ptr, cast(uint)title.length); 14654 flushGui(); // without this OpenGL windows has a LONG delay before changing title 14655 } 14656 14657 string[] getTitles() { 14658 auto XA_UTF8 = XInternAtom(display, "UTF8_STRING".ptr, false); 14659 auto XA_NETWM_NAME = XInternAtom(display, "_NET_WM_NAME".ptr, false); 14660 XTextProperty textProp; 14661 if (XGetTextProperty(display, window, &textProp, XA_NETWM_NAME) != 0 || XGetWMName(display, window, &textProp) != 0) { 14662 if ((textProp.encoding == XA_UTF8 || textProp.encoding == XA_STRING) && textProp.format == 8) { 14663 return textProp.value[0 .. textProp.nitems].idup.split('\0'); 14664 } else 14665 return []; 14666 } else 14667 return null; 14668 } 14669 14670 string getTitle() { 14671 auto titles = getTitles(); 14672 return titles.length ? titles[0] : null; 14673 } 14674 14675 void setMinSize (int minwidth, int minheight) { 14676 import core.stdc.config : c_long; 14677 if (minwidth < 1) minwidth = 1; 14678 if (minheight < 1) minheight = 1; 14679 XSizeHints sh; 14680 c_long spr; 14681 XGetWMNormalHints(display, window, &sh, &spr); 14682 sh.min_width = minwidth; 14683 sh.min_height = minheight; 14684 sh.flags |= PMinSize; 14685 XSetWMNormalHints(display, window, &sh); 14686 flushGui(); 14687 } 14688 14689 void setMaxSize (int maxwidth, int maxheight) { 14690 import core.stdc.config : c_long; 14691 if (maxwidth < 1) maxwidth = 1; 14692 if (maxheight < 1) maxheight = 1; 14693 XSizeHints sh; 14694 c_long spr; 14695 XGetWMNormalHints(display, window, &sh, &spr); 14696 sh.max_width = maxwidth; 14697 sh.max_height = maxheight; 14698 sh.flags |= PMaxSize; 14699 XSetWMNormalHints(display, window, &sh); 14700 flushGui(); 14701 } 14702 14703 void setResizeGranularity (int granx, int grany) { 14704 import core.stdc.config : c_long; 14705 if (granx < 1) granx = 1; 14706 if (grany < 1) grany = 1; 14707 XSizeHints sh; 14708 c_long spr; 14709 XGetWMNormalHints(display, window, &sh, &spr); 14710 sh.width_inc = granx; 14711 sh.height_inc = grany; 14712 sh.flags |= PResizeInc; 14713 XSetWMNormalHints(display, window, &sh); 14714 flushGui(); 14715 } 14716 14717 void setOpacity (uint opacity) { 14718 arch_ulong o = opacity; 14719 if (opacity == uint.max) 14720 XDeleteProperty(display, window, XInternAtom(display, "_NET_WM_WINDOW_OPACITY".ptr, false)); 14721 else 14722 XChangeProperty(display, window, XInternAtom(display, "_NET_WM_WINDOW_OPACITY".ptr, false), 14723 XA_CARDINAL, 32, PropModeReplace, &o, 1); 14724 } 14725 14726 void createWindow(int width, int height, string title, in OpenGlOptions opengl, SimpleWindow parent) { 14727 version(without_opengl) {} else if(opengl == OpenGlOptions.yes && !openGlLibrariesSuccessfullyLoaded) throw new Exception("OpenGL libraries did not load"); 14728 display = XDisplayConnection.get(); 14729 auto screen = DefaultScreen(display); 14730 14731 bool overrideRedirect = false; 14732 if(windowType == WindowTypes.dropdownMenu || windowType == WindowTypes.popupMenu || windowType == WindowTypes.notification)// || windowType == WindowTypes.nestedChild) 14733 overrideRedirect = true; 14734 14735 version(without_opengl) {} 14736 else { 14737 if(opengl == OpenGlOptions.yes) { 14738 GLXFBConfig fbconf = null; 14739 XVisualInfo* vi = null; 14740 bool useLegacy = false; 14741 static if (SdpyIsUsingIVGLBinds) {if (glbindGetProcAddress("glHint") is null) assert(0, "GL: error loading OpenGL"); } // loads all necessary functions 14742 if (sdpyOpenGLContextVersion != 0 && glXCreateContextAttribsARB_present()) { 14743 int[23] visualAttribs = [ 14744 GLX_X_RENDERABLE , 1/*True*/, 14745 GLX_DRAWABLE_TYPE, GLX_WINDOW_BIT, 14746 GLX_RENDER_TYPE , GLX_RGBA_BIT, 14747 GLX_X_VISUAL_TYPE, GLX_TRUE_COLOR, 14748 GLX_RED_SIZE , 8, 14749 GLX_GREEN_SIZE , 8, 14750 GLX_BLUE_SIZE , 8, 14751 GLX_ALPHA_SIZE , 8, 14752 GLX_DEPTH_SIZE , 24, 14753 GLX_STENCIL_SIZE , 8, 14754 GLX_DOUBLEBUFFER , 1/*True*/, 14755 0/*None*/, 14756 ]; 14757 int fbcount; 14758 GLXFBConfig* fbc = glXChooseFBConfig(display, screen, visualAttribs.ptr, &fbcount); 14759 if (fbcount == 0) { 14760 useLegacy = true; // try to do at least something 14761 } else { 14762 // pick the FB config/visual with the most samples per pixel 14763 int bestidx = -1, bestns = -1; 14764 foreach (int fbi; 0..fbcount) { 14765 int sb, samples; 14766 glXGetFBConfigAttrib(display, fbc[fbi], GLX_SAMPLE_BUFFERS, &sb); 14767 glXGetFBConfigAttrib(display, fbc[fbi], GLX_SAMPLES, &samples); 14768 if (bestidx < 0 || sb && samples > bestns) { bestidx = fbi; bestns = samples; } 14769 } 14770 //{ import core.stdc.stdio; printf("found gl visual with %d samples\n", bestns); } 14771 fbconf = fbc[bestidx]; 14772 // Be sure to free the FBConfig list allocated by glXChooseFBConfig() 14773 XFree(fbc); 14774 vi = cast(XVisualInfo*)glXGetVisualFromFBConfig(display, fbconf); 14775 } 14776 } 14777 if (vi is null || useLegacy) { 14778 static immutable GLint[5] attrs = [ GLX_RGBA, GLX_DEPTH_SIZE, 24, GLX_DOUBLEBUFFER, None ]; 14779 vi = cast(XVisualInfo*)glXChooseVisual(display, 0, attrs.ptr); 14780 useLegacy = true; 14781 } 14782 if (vi is null) throw new Exception("no open gl visual found"); 14783 14784 XSetWindowAttributes swa; 14785 auto root = RootWindow(display, screen); 14786 swa.colormap = XCreateColormap(display, root, vi.visual, AllocNone); 14787 14788 swa.override_redirect = overrideRedirect; 14789 14790 window = XCreateWindow(display, (windowType != WindowTypes.nestedChild || parent is null) ? root : parent.impl.window, 14791 0, 0, width, height, 14792 0, vi.depth, 1 /* InputOutput */, vi.visual, CWColormap | CWOverrideRedirect, &swa); 14793 14794 // now try to use `glXCreateContextAttribsARB()` if it's here 14795 if (!useLegacy) { 14796 // request fairly advanced context, even with stencil buffer! 14797 int[9] contextAttribs = [ 14798 GLX_CONTEXT_MAJOR_VERSION_ARB, (sdpyOpenGLContextVersion>>8), 14799 GLX_CONTEXT_MINOR_VERSION_ARB, (sdpyOpenGLContextVersion&0xff), 14800 /*GLX_CONTEXT_PROFILE_MASK_ARB*/0x9126, (sdpyOpenGLContextCompatible ? /*GLX_CONTEXT_COMPATIBILITY_PROFILE_BIT_ARB*/0x02 : /*GLX_CONTEXT_CORE_PROFILE_BIT_ARB*/ 0x01), 14801 // for modern context, set "forward compatibility" flag too 14802 (sdpyOpenGLContextCompatible ? None : /*GLX_CONTEXT_FLAGS_ARB*/ 0x2094), /*GLX_CONTEXT_FORWARD_COMPATIBLE_BIT_ARB*/ 0x02, 14803 0/*None*/, 14804 ]; 14805 glc = glXCreateContextAttribsARB(display, fbconf, null, 1/*True*/, contextAttribs.ptr); 14806 if (glc is null && sdpyOpenGLContextAllowFallback) { 14807 sdpyOpenGLContextVersion = 0; 14808 glc = glXCreateContext(display, vi, null, /*GL_TRUE*/1); 14809 } 14810 //{ import core.stdc.stdio; printf("using modern ogl v%d.%d\n", contextAttribs[1], contextAttribs[3]); } 14811 } else { 14812 // fallback to old GLX call 14813 if (sdpyOpenGLContextAllowFallback || sdpyOpenGLContextVersion == 0) { 14814 sdpyOpenGLContextVersion = 0; 14815 glc = glXCreateContext(display, vi, null, /*GL_TRUE*/1); 14816 } 14817 } 14818 // sync to ensure any errors generated are processed 14819 XSync(display, 0/*False*/); 14820 //{ import core.stdc.stdio; printf("ogl is here\n"); } 14821 if(glc is null) 14822 throw new Exception("glc"); 14823 } 14824 } 14825 14826 if(opengl == OpenGlOptions.no) { 14827 14828 XSetWindowAttributes swa; 14829 swa.background_pixel = WhitePixel(display, screen); 14830 swa.border_pixel = BlackPixel(display, screen); 14831 swa.override_redirect = overrideRedirect; 14832 auto root = RootWindow(display, screen); 14833 swa.colormap = XCreateColormap(display, root, DefaultVisual(display, screen), AllocNone); 14834 14835 window = XCreateWindow(display, (windowType != WindowTypes.nestedChild || parent is null) ? root : parent.impl.window, 14836 0, 0, width, height, 14837 // I'm commenting that CWBackPixel thing just because it actually causes flicker for no apparent benefit. 14838 0, CopyFromParent, 1 /* InputOutput */, cast(Visual*) CopyFromParent, CWColormap /*| CWBackPixel*/ | CWBorderPixel | CWOverrideRedirect, &swa); 14839 14840 14841 14842 /* 14843 window = XCreateSimpleWindow( 14844 display, 14845 parent is null ? RootWindow(display, screen) : parent.impl.window, 14846 0, 0, // x, y 14847 width, height, 14848 1, // border width 14849 BlackPixel(display, screen), // border 14850 WhitePixel(display, screen)); // background 14851 */ 14852 14853 buffer = XCreatePixmap(display, cast(Drawable) window, width, height, DefaultDepthOfDisplay(display)); 14854 bufferw = width; 14855 bufferh = height; 14856 14857 gc = DefaultGC(display, screen); 14858 14859 // clear out the buffer to get us started... 14860 XSetForeground(display, gc, WhitePixel(display, screen)); 14861 XFillRectangle(display, cast(Drawable) buffer, gc, 0, 0, width, height); 14862 XSetForeground(display, gc, BlackPixel(display, screen)); 14863 } 14864 14865 // input context 14866 //TODO: create this only for top-level windows, and reuse that? 14867 populateXic(); 14868 14869 if (sdpyWindowClassStr is null) loadBinNameToWindowClassName(); 14870 if (sdpyWindowClassStr is null) sdpyWindowClass = "DSimpleWindow"; 14871 // window class 14872 if (sdpyWindowClassStr !is null && sdpyWindowClassStr[0]) { 14873 //{ import core.stdc.stdio; printf("winclass: [%s]\n", sdpyWindowClassStr); } 14874 XClassHint klass; 14875 XWMHints wh; 14876 if(this.customizationFlags & WindowFlags.managesChildWindowFocus) { 14877 wh.input = true; 14878 wh.flags |= InputHint; 14879 } 14880 XSizeHints size; 14881 klass.res_name = sdpyWindowClassStr; 14882 klass.res_class = sdpyWindowClassStr; 14883 XSetWMProperties(display, window, null, null, null, 0, &size, &wh, &klass); 14884 } 14885 14886 setTitle(title); 14887 SimpleWindow.nativeMapping[window] = this; 14888 CapableOfHandlingNativeEvent.nativeHandleMapping[window] = this; 14889 14890 // This gives our window a close button 14891 if (windowType != WindowTypes.eventOnly) { 14892 Atom[2] atoms = [GetAtom!"WM_DELETE_WINDOW"(display), GetAtom!"WM_TAKE_FOCUS"(display)]; 14893 int useAtoms; 14894 if(this.customizationFlags & WindowFlags.managesChildWindowFocus) { 14895 useAtoms = 2; 14896 } else { 14897 useAtoms = 1; 14898 } 14899 assert(useAtoms <= atoms.length); 14900 XSetWMProtocols(display, window, atoms.ptr, useAtoms); 14901 } 14902 14903 // FIXME: windowType and customizationFlags 14904 Atom[8] wsatoms; // here, due to goto 14905 int wmsacount = 0; // here, due to goto 14906 14907 try 14908 final switch(windowType) { 14909 case WindowTypes.normal: 14910 setNetWMWindowType(GetAtom!"_NET_WM_WINDOW_TYPE_NORMAL"(display)); 14911 break; 14912 case WindowTypes.undecorated: 14913 motifHideDecorations(); 14914 setNetWMWindowType(GetAtom!"_NET_WM_WINDOW_TYPE_NORMAL"(display)); 14915 break; 14916 case WindowTypes.eventOnly: 14917 _hidden = true; 14918 XSelectInput(display, window, EventMask.StructureNotifyMask); // without this, we won't get destroy notification 14919 goto hiddenWindow; 14920 //break; 14921 case WindowTypes.nestedChild: 14922 // handled in XCreateWindow calls 14923 break; 14924 14925 case WindowTypes.dropdownMenu: 14926 motifHideDecorations(); 14927 setNetWMWindowType(GetAtom!"_NET_WM_WINDOW_TYPE_DROPDOWN_MENU"(display)); 14928 customizationFlags |= WindowFlags.skipTaskbar | WindowFlags.alwaysOnTop; 14929 break; 14930 case WindowTypes.popupMenu: 14931 motifHideDecorations(); 14932 setNetWMWindowType(GetAtom!"_NET_WM_WINDOW_TYPE_POPUP_MENU"(display)); 14933 customizationFlags |= WindowFlags.skipTaskbar | WindowFlags.alwaysOnTop; 14934 break; 14935 case WindowTypes.notification: 14936 motifHideDecorations(); 14937 setNetWMWindowType(GetAtom!"_NET_WM_WINDOW_TYPE_NOTIFICATION"(display)); 14938 customizationFlags |= WindowFlags.skipTaskbar | WindowFlags.alwaysOnTop; 14939 break; 14940 case WindowTypes.minimallyWrapped: 14941 assert(0, "don't create a minimallyWrapped thing explicitly!"); 14942 /+ 14943 case WindowTypes.menu: 14944 atoms[0] = GetAtom!"_NET_WM_WINDOW_TYPE_MENU"(display); 14945 motifHideDecorations(); 14946 break; 14947 case WindowTypes.desktop: 14948 atoms[0] = GetAtom!"_NET_WM_WINDOW_TYPE_DESKTOP"(display); 14949 break; 14950 case WindowTypes.dock: 14951 atoms[0] = GetAtom!"_NET_WM_WINDOW_TYPE_DOCK"(display); 14952 break; 14953 case WindowTypes.toolbar: 14954 atoms[0] = GetAtom!"_NET_WM_WINDOW_TYPE_TOOLBAR"(display); 14955 break; 14956 case WindowTypes.menu: 14957 atoms[0] = GetAtom!"_NET_WM_WINDOW_TYPE_MENU"(display); 14958 break; 14959 case WindowTypes.utility: 14960 atoms[0] = GetAtom!"_NET_WM_WINDOW_TYPE_UTILITY"(display); 14961 break; 14962 case WindowTypes.splash: 14963 atoms[0] = GetAtom!"_NET_WM_WINDOW_TYPE_SPLASH"(display); 14964 break; 14965 case WindowTypes.dialog: 14966 atoms[0] = GetAtom!"_NET_WM_WINDOW_TYPE_DIALOG"(display); 14967 break; 14968 case WindowTypes.tooltip: 14969 atoms[0] = GetAtom!"_NET_WM_WINDOW_TYPE_TOOLTIP"(display); 14970 break; 14971 case WindowTypes.notification: 14972 atoms[0] = GetAtom!"_NET_WM_WINDOW_TYPE_NOTIFICATION"(display); 14973 break; 14974 case WindowTypes.combo: 14975 atoms[0] = GetAtom!"_NET_WM_WINDOW_TYPE_COMBO"(display); 14976 break; 14977 case WindowTypes.dnd: 14978 atoms[0] = GetAtom!"_NET_WM_WINDOW_TYPE_DND"(display); 14979 break; 14980 +/ 14981 } 14982 catch(Exception e) { 14983 // XInternAtom failed, prolly a WM 14984 // that doesn't support these things 14985 } 14986 14987 if (customizationFlags&WindowFlags.skipTaskbar) wsatoms[wmsacount++] = GetAtom!("_NET_WM_STATE_SKIP_TASKBAR", true)(display); 14988 // the two following flags may be ignored by WM 14989 if (customizationFlags&WindowFlags.alwaysOnTop) wsatoms[wmsacount++] = GetAtom!("_NET_WM_STATE_ABOVE", true)(display); 14990 if (customizationFlags&WindowFlags.alwaysOnBottom) wsatoms[wmsacount++] = GetAtom!("_NET_WM_STATE_BELOW", true)(display); 14991 14992 if (wmsacount != 0) XChangeProperty(display, window, GetAtom!("_NET_WM_STATE", true)(display), XA_ATOM, 32 /* bits */,0 /*PropModeReplace*/, wsatoms.ptr, wmsacount); 14993 14994 if (this.resizability == Resizability.fixedSize || (opengl == OpenGlOptions.no && this.resizability != Resizability.allowResizing)) fixFixedSize!true(width, height); 14995 14996 // What would be ideal here is if they only were 14997 // selected if there was actually an event handler 14998 // for them... 14999 15000 selectDefaultInput((customizationFlags & WindowFlags.alwaysRequestMouseMotionEvents)?true:false); 15001 15002 hiddenWindow: 15003 15004 // set the pid property for lookup later by window managers 15005 // a standard convenience 15006 import core.sys.posix.unistd; 15007 arch_ulong pid = getpid(); 15008 15009 XChangeProperty( 15010 display, 15011 impl.window, 15012 GetAtom!("_NET_WM_PID", true)(display), 15013 XA_CARDINAL, 15014 32 /* bits */, 15015 0 /*PropModeReplace*/, 15016 &pid, 15017 1); 15018 15019 if(isTransient && parent) { // customizationFlags & WindowFlags.transient) { 15020 if(parent is null) assert(0); 15021 XChangeProperty( 15022 display, 15023 impl.window, 15024 GetAtom!("WM_TRANSIENT_FOR", true)(display), 15025 XA_WINDOW, 15026 32 /* bits */, 15027 0 /*PropModeReplace*/, 15028 &parent.impl.window, 15029 1); 15030 15031 } 15032 15033 if(windowType != WindowTypes.eventOnly && (customizationFlags&WindowFlags.dontAutoShow) == 0) { 15034 XMapWindow(display, window); 15035 } else { 15036 _hidden = true; 15037 } 15038 } 15039 15040 void populateXic() { 15041 if (XDisplayConnection.xim !is null) { 15042 xic = XCreateIC(XDisplayConnection.xim, 15043 /*XNInputStyle*/"inputStyle".ptr, XIMPreeditNothing|XIMStatusNothing, 15044 /*XNClientWindow*/"clientWindow".ptr, window, 15045 /*XNFocusWindow*/"focusWindow".ptr, window, 15046 null); 15047 if (xic is null) { 15048 import core.stdc.stdio : stderr, fprintf; 15049 fprintf(stderr, "XCreateIC failed for window %u\n", cast(uint)window); 15050 } 15051 } 15052 } 15053 15054 void selectDefaultInput(bool forceIncludeMouseMotion) { 15055 auto mask = EventMask.ExposureMask | 15056 EventMask.KeyPressMask | 15057 EventMask.KeyReleaseMask | 15058 EventMask.PropertyChangeMask | 15059 EventMask.FocusChangeMask | 15060 EventMask.StructureNotifyMask | 15061 EventMask.SubstructureNotifyMask | 15062 EventMask.VisibilityChangeMask 15063 | EventMask.ButtonPressMask 15064 | EventMask.ButtonReleaseMask 15065 ; 15066 15067 // xshm is our shortcut for local connections 15068 if(XDisplayConnection.isLocal || forceIncludeMouseMotion) 15069 mask |= EventMask.PointerMotionMask; 15070 else 15071 mask |= EventMask.ButtonMotionMask; 15072 15073 XSelectInput(display, window, mask); 15074 } 15075 15076 15077 void setNetWMWindowType(Atom type) { 15078 Atom[2] atoms; 15079 15080 atoms[0] = type; 15081 // generic fallback 15082 atoms[1] = GetAtom!"_NET_WM_WINDOW_TYPE_NORMAL"(display); 15083 15084 XChangeProperty( 15085 display, 15086 impl.window, 15087 GetAtom!"_NET_WM_WINDOW_TYPE"(display), 15088 XA_ATOM, 15089 32 /* bits */, 15090 0 /*PropModeReplace*/, 15091 atoms.ptr, 15092 cast(int) atoms.length); 15093 } 15094 15095 void motifHideDecorations(bool hide = true) { 15096 MwmHints hints; 15097 hints.flags = MWM_HINTS_DECORATIONS; 15098 hints.decorations = hide ? 0 : 1; 15099 15100 XChangeProperty( 15101 display, 15102 impl.window, 15103 GetAtom!"_MOTIF_WM_HINTS"(display), 15104 GetAtom!"_MOTIF_WM_HINTS"(display), 15105 32 /* bits */, 15106 0 /*PropModeReplace*/, 15107 &hints, 15108 hints.sizeof / 4); 15109 } 15110 15111 /*k8: unused 15112 void createOpenGlContext() { 15113 15114 } 15115 */ 15116 15117 void closeWindow() { 15118 // I can't close this or a child window closing will 15119 // break events for everyone. So I'm just leaking it right 15120 // now and that is probably perfectly fine... 15121 version(none) 15122 if (customEventFDRead != -1) { 15123 import core.sys.posix.unistd : close; 15124 auto same = customEventFDRead == customEventFDWrite; 15125 15126 close(customEventFDRead); 15127 if(!same) 15128 close(customEventFDWrite); 15129 customEventFDRead = -1; 15130 customEventFDWrite = -1; 15131 } 15132 15133 version(without_opengl) {} else 15134 if(glc !is null) { 15135 glXDestroyContext(display, glc); 15136 glc = null; 15137 } 15138 15139 if(buffer) 15140 XFreePixmap(display, buffer); 15141 bufferw = bufferh = 0; 15142 if (blankCurPtr && cursorSequenceNumber == XDisplayConnection.connectionSequenceNumber) XFreeCursor(display, blankCurPtr); 15143 XDestroyWindow(display, window); 15144 XFlush(display); 15145 } 15146 15147 void dispose() { 15148 } 15149 15150 bool destroyed = false; 15151 } 15152 15153 bool insideXEventLoop; 15154 } 15155 15156 version(X11) { 15157 15158 int mouseDoubleClickTimeout = 350; /// Double click timeout. X only, you probably shouldn't change this. 15159 15160 private class ResizeEvent { 15161 int width, height; 15162 } 15163 15164 void recordX11ResizeAsync(Display* display, SimpleWindow win, int width, int height) { 15165 if(win.windowType == WindowTypes.minimallyWrapped) 15166 return; 15167 15168 if(win.pendingResizeEvent is null) { 15169 win.pendingResizeEvent = new ResizeEvent(); 15170 win.addEventListener((ResizeEvent re) { 15171 recordX11Resize(XDisplayConnection.get, win, re.width, re.height); 15172 }); 15173 } 15174 win.pendingResizeEvent.width = width; 15175 win.pendingResizeEvent.height = height; 15176 if(!win.eventQueued!ResizeEvent) { 15177 win.postEvent(win.pendingResizeEvent); 15178 } 15179 } 15180 15181 void recordX11Resize(Display* display, SimpleWindow win, int width, int height) { 15182 if(win.windowType == WindowTypes.minimallyWrapped) 15183 return; 15184 if(win.closed) 15185 return; 15186 15187 if(width != win.width || height != win.height) { 15188 15189 // writeln("RESIZE: ", width, "x", height, " was ", win._width, "x", win._height, " window: ", win.windowType, "-", win.title, " ", win.window); 15190 win._width = width; 15191 win._height = height; 15192 15193 if(win.openglMode == OpenGlOptions.no) { 15194 // FIXME: could this be more efficient? 15195 15196 if (win.bufferw < width || win.bufferh < height) { 15197 //{ 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); } 15198 // grow the internal buffer to match the window... 15199 auto newPixmap = XCreatePixmap(display, cast(Drawable) win.window, width, height, DefaultDepthOfDisplay(display)); 15200 { 15201 GC xgc = XCreateGC(win.display, cast(Drawable)win.window, 0, null); 15202 XCopyGC(win.display, win.gc, 0xffffffff, xgc); 15203 scope(exit) XFreeGC(win.display, xgc); 15204 XSetClipMask(win.display, xgc, None); 15205 XSetForeground(win.display, xgc, 0); 15206 XFillRectangle(display, cast(Drawable)newPixmap, xgc, 0, 0, width, height); 15207 } 15208 XCopyArea(display, 15209 cast(Drawable) win.buffer, 15210 cast(Drawable) newPixmap, 15211 win.gc, 0, 0, 15212 win.bufferw < width ? win.bufferw : win.width, 15213 win.bufferh < height ? win.bufferh : win.height, 15214 0, 0); 15215 15216 XFreePixmap(display, win.buffer); 15217 win.buffer = newPixmap; 15218 win.bufferw = width; 15219 win.bufferh = height; 15220 } 15221 15222 // clear unused parts of the buffer 15223 if (win.bufferw > width || win.bufferh > height) { 15224 GC xgc = XCreateGC(win.display, cast(Drawable)win.window, 0, null); 15225 XCopyGC(win.display, win.gc, 0xffffffff, xgc); 15226 scope(exit) XFreeGC(win.display, xgc); 15227 XSetClipMask(win.display, xgc, None); 15228 XSetForeground(win.display, xgc, 0); 15229 immutable int maxw = (win.bufferw > width ? win.bufferw : width); 15230 immutable int maxh = (win.bufferh > height ? win.bufferh : height); 15231 XFillRectangle(win.display, cast(Drawable)win.buffer, xgc, width, 0, maxw, maxh); // let X11 do clipping 15232 XFillRectangle(win.display, cast(Drawable)win.buffer, xgc, 0, height, maxw, maxh); // let X11 do clipping 15233 } 15234 15235 } 15236 15237 win.updateOpenglViewportIfNeeded(width, height); 15238 15239 win.fixFixedSize(width, height); //k8: this does nothing on my FluxBox; wtf?! 15240 15241 if(win.resizability != Resizability.automaticallyScaleIfPossible) 15242 if(win.windowResized !is null) { 15243 XUnlockDisplay(display); 15244 scope(exit) XLockDisplay(display); 15245 win.windowResized(width, height); 15246 } 15247 } 15248 } 15249 15250 15251 /// Platform-specific, you might use it when doing a custom event loop. 15252 bool doXNextEvent(Display* display) { 15253 bool done; 15254 XEvent e; 15255 XNextEvent(display, &e); 15256 version(sddddd) { 15257 if(auto win = e.xany.window in CapableOfHandlingNativeEvent.nativeHandleMapping) { 15258 if(typeid(cast(Object) *win) == NotificationAreaIcon.classinfo) 15259 writeln("event for: ", e.xany.window, "; type is ", to!string(cast(EventType)e.type)); 15260 } 15261 } 15262 15263 // filter out compose events 15264 if (XFilterEvent(&e, None)) { 15265 //{ import core.stdc.stdio : printf; printf("XFilterEvent filtered!\n"); } 15266 //NOTE: we should ungrab keyboard here, but simpledisplay doesn't use keyboard grabbing (yet) 15267 return false; 15268 } 15269 // process keyboard mapping changes 15270 if (e.type == EventType.KeymapNotify) { 15271 //{ import core.stdc.stdio : printf; printf("KeymapNotify processed!\n"); } 15272 XRefreshKeyboardMapping(&e.xmapping); 15273 return false; 15274 } 15275 15276 version(with_eventloop) 15277 import arsd.eventloop; 15278 15279 if(SimpleWindow.handleNativeGlobalEvent !is null) { 15280 // see windows impl's comments 15281 XUnlockDisplay(display); 15282 scope(exit) XLockDisplay(display); 15283 auto ret = SimpleWindow.handleNativeGlobalEvent(e); 15284 if(ret == 0) 15285 return done; 15286 } 15287 15288 15289 if(auto win = e.xany.window in CapableOfHandlingNativeEvent.nativeHandleMapping) { 15290 if(win.getNativeEventHandler !is null) { 15291 XUnlockDisplay(display); 15292 scope(exit) XLockDisplay(display); 15293 auto ret = win.getNativeEventHandler()(e); 15294 if(ret == 0) 15295 return done; 15296 } 15297 } 15298 15299 if(xrrEventBase != -1 && e.type == xrrEventBase + RRScreenChangeNotify) { 15300 if(auto win = e.xany.window in SimpleWindow.nativeMapping) { 15301 // we get this because of the RRScreenChangeNotifyMask 15302 15303 // this isn't actually an ideal way to do it since it wastes time 15304 // but meh it is simple and it works. 15305 win.actualDpiLoadAttempted = false; 15306 SimpleWindow.xRandrInfoLoadAttemped = false; 15307 win.updateActualDpi(); // trigger a reload 15308 } 15309 } 15310 15311 switch(e.type) { 15312 case EventType.SelectionClear: 15313 if(auto win = e.xselectionclear.window in SimpleWindow.nativeMapping) { 15314 // FIXME so it is supposed to finish any in progress transfers... but idk... 15315 // writeln("SelectionClear"); 15316 SimpleWindow.impl.setSelectionHandlers.remove(e.xselectionclear.selection); 15317 } 15318 break; 15319 case EventType.SelectionRequest: 15320 if(auto win = e.xselectionrequest.owner in SimpleWindow.nativeMapping) 15321 if(auto ssh = e.xselectionrequest.selection in SimpleWindow.impl.setSelectionHandlers) { 15322 // printf("SelectionRequest %s\n", XGetAtomName(e.xselectionrequest.display, e.xselectionrequest.target)); 15323 XUnlockDisplay(display); 15324 scope(exit) XLockDisplay(display); 15325 (*ssh).handleRequest(e); 15326 } 15327 break; 15328 case EventType.PropertyNotify: 15329 // printf("PropertyNotify %s %d\n", XGetAtomName(e.xproperty.display, e.xproperty.atom), e.xproperty.state); 15330 15331 foreach(ssh; SimpleWindow.impl.setSelectionHandlers) { 15332 if(ssh.matchesIncr(e.xproperty.window, e.xproperty.atom) && e.xproperty.state == PropertyNotification.PropertyDelete) 15333 ssh.sendMoreIncr(&e.xproperty); 15334 } 15335 15336 15337 if(auto win = e.xproperty.window in SimpleWindow.nativeMapping) 15338 if(auto handler = e.xproperty.atom in win.getSelectionHandlers) { 15339 if(handler.matchesIncr(e.xproperty.window, e.xproperty.atom) && e.xproperty.state == PropertyNotification.PropertyNewValue) { 15340 Atom target; 15341 int format; 15342 arch_ulong bytesafter, length; 15343 void* value; 15344 15345 ubyte[] s; 15346 Atom targetToKeep; 15347 15348 XGetWindowProperty( 15349 e.xproperty.display, 15350 e.xproperty.window, 15351 e.xproperty.atom, 15352 0, 15353 100000 /* length */, 15354 true, /* erase it to signal we got it and want more */ 15355 0 /*AnyPropertyType*/, 15356 &target, &format, &length, &bytesafter, &value); 15357 15358 if(!targetToKeep) 15359 targetToKeep = target; 15360 15361 auto id = (cast(ubyte*) value)[0 .. length]; 15362 15363 handler.handleIncrData(targetToKeep, id); 15364 15365 XFree(value); 15366 } 15367 } 15368 break; 15369 case EventType.SelectionNotify: 15370 if(auto win = e.xselection.requestor in SimpleWindow.nativeMapping) 15371 if(auto handler = e.xproperty.atom in win.getSelectionHandlers) { 15372 if(e.xselection.property == None) { // || e.xselection.property == GetAtom!("NULL", true)(e.xselection.display)) { 15373 XUnlockDisplay(display); 15374 scope(exit) XLockDisplay(display); 15375 handler.handleData(None, null); 15376 } else { 15377 Atom target; 15378 int format; 15379 arch_ulong bytesafter, length; 15380 void* value; 15381 XGetWindowProperty( 15382 e.xselection.display, 15383 e.xselection.requestor, 15384 e.xselection.property, 15385 0, 15386 100000 /* length */, 15387 //false, /* don't erase it */ 15388 true, /* do erase it lol */ 15389 0 /*AnyPropertyType*/, 15390 &target, &format, &length, &bytesafter, &value); 15391 15392 // FIXME: I don't have to copy it now since it is in char[] instead of string 15393 15394 { 15395 XUnlockDisplay(display); 15396 scope(exit) XLockDisplay(display); 15397 15398 if(target == XA_ATOM) { 15399 // initial request, see what they are able to work with and request the best one 15400 // we can handle, if available 15401 15402 Atom[] answer = (cast(Atom*) value)[0 .. length]; 15403 Atom best = handler.findBestFormat(answer); 15404 15405 /+ 15406 writeln("got ", answer); 15407 foreach(a; answer) 15408 printf("%s\n", XGetAtomName(display, a)); 15409 writeln("best ", best); 15410 +/ 15411 15412 if(best != None) { 15413 // actually request the best format 15414 XConvertSelection(e.xselection.display, e.xselection.selection, best, GetAtom!("SDD_DATA", true)(display), e.xselection.requestor, 0 /*CurrentTime*/); 15415 } 15416 } else if(target == GetAtom!"INCR"(display)) { 15417 // incremental 15418 15419 handler.prepareIncremental(e.xselection.requestor, e.xselection.property); 15420 15421 // signal the sending program that we see 15422 // the incr and are ready to receive more. 15423 XDeleteProperty( 15424 e.xselection.display, 15425 e.xselection.requestor, 15426 e.xselection.property); 15427 } else { 15428 // unsupported type... maybe, forward 15429 handler.handleData(target, cast(ubyte[]) value[0 .. length]); 15430 } 15431 } 15432 XFree(value); 15433 /* 15434 XDeleteProperty( 15435 e.xselection.display, 15436 e.xselection.requestor, 15437 e.xselection.property); 15438 */ 15439 } 15440 } 15441 break; 15442 case EventType.ConfigureNotify: 15443 auto event = e.xconfigure; 15444 if(auto win = event.window in SimpleWindow.nativeMapping) { 15445 if(win.windowType == WindowTypes.minimallyWrapped) 15446 break; 15447 //version(sdddd) { writeln(" w=", event.width, "; h=", event.height); } 15448 15449 /+ 15450 The ICCCM says window managers must send a synthetic event when the window 15451 is moved but NOT when it is resized. In the resize case, an event is sent 15452 with position (0, 0) which can be wrong and break the dpi calculations. 15453 15454 So we only consider the synthetic events from the WM and otherwise 15455 need to wait for some other event to get the position which... sucks. 15456 15457 I'd rather not have windows changing their layout on mouse motion after 15458 switching monitors... might be forced to but for now just ignoring it. 15459 15460 Easiest way to switch monitors without sending a size position is by 15461 maximize or fullscreen in a setup like mine, but on most setups those 15462 work on the monitor it is already living on, so it should be ok most the 15463 time. 15464 +/ 15465 if(event.send_event) { 15466 win.screenPositionKnown = true; 15467 win.screenPositionX = event.x; 15468 win.screenPositionY = event.y; 15469 win.updateActualDpi(); 15470 } 15471 15472 win.updateIMEPopupLocation(); 15473 recordX11ResizeAsync(display, *win, event.width, event.height); 15474 } 15475 break; 15476 case EventType.Expose: 15477 if(auto win = e.xexpose.window in SimpleWindow.nativeMapping) { 15478 if(win.windowType == WindowTypes.minimallyWrapped) 15479 break; 15480 // if it is closing from a popup menu, it can get 15481 // an Expose event right by the end and trigger a 15482 // BadDrawable error ... we'll just check 15483 // closed to handle that. 15484 if((*win).closed) break; 15485 if((*win).openglMode == OpenGlOptions.no) { 15486 bool doCopy = true;// e.xexpose.count == 0; // the count is better if we copy all area but meh 15487 if (win.handleExpose !is null) doCopy = !win.handleExpose(e.xexpose.x, e.xexpose.y, e.xexpose.width, e.xexpose.height, e.xexpose.count); 15488 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); 15489 } else { 15490 // need to redraw the scene somehow 15491 if(e.xexpose.count == 0) { // only do the last one since redrawOpenGlSceneNow always does it all 15492 XUnlockDisplay(display); 15493 scope(exit) XLockDisplay(display); 15494 version(without_opengl) {} else 15495 win.redrawOpenGlSceneSoon(); 15496 } 15497 } 15498 } 15499 break; 15500 case EventType.FocusIn: 15501 case EventType.FocusOut: 15502 15503 if(auto win = e.xfocus.window in SimpleWindow.nativeMapping) { 15504 /+ 15505 15506 void info(string detail) { 15507 string s; 15508 // import std.conv; 15509 // import std.datetime; 15510 s ~= to!string(Clock.currTime); 15511 s ~= " "; 15512 s ~= e.type == EventType.FocusIn ? "in " : "out"; 15513 s ~= " "; 15514 s ~= win.windowType == WindowTypes.nestedChild ? "child " : "main "; 15515 s ~= e.xfocus.mode == NotifyModes.NotifyNormal ? " normal ": " grabbed "; 15516 s ~= detail; 15517 s ~= " "; 15518 15519 sdpyPrintDebugString(s); 15520 15521 } 15522 15523 switch(e.xfocus.detail) { 15524 case NotifyDetail.NotifyAncestor: info("Ancestor"); break; 15525 case NotifyDetail.NotifyVirtual: info("Virtual"); break; 15526 case NotifyDetail.NotifyInferior: info("Inferior"); break; 15527 case NotifyDetail.NotifyNonlinear: info("Nonlinear"); break; 15528 case NotifyDetail.NotifyNonlinearVirtual: info("nlinearvirtual"); break; 15529 case NotifyDetail.NotifyPointer: info("pointer"); break; 15530 case NotifyDetail.NotifyPointerRoot: info("pointerroot"); break; 15531 case NotifyDetail.NotifyDetailNone: info("none"); break; 15532 default: 15533 15534 } 15535 +/ 15536 15537 15538 if(e.xfocus.detail == NotifyDetail.NotifyPointer) 15539 break; // just ignore these they seem irrelevant 15540 15541 auto old = win._focused; 15542 win._focused = e.type == EventType.FocusIn; 15543 15544 // yes, we are losing the focus, but to our own child. that's actually kinda keeping it. 15545 if(e.type == EventType.FocusOut && e.xfocus.detail == NotifyDetail.NotifyInferior) 15546 win._focused = true; 15547 15548 if(win.demandingAttention) 15549 demandAttention(*win, false); 15550 15551 win.updateIMEFocused(); 15552 15553 if(old != win._focused && win.onFocusChange) { 15554 XUnlockDisplay(display); 15555 scope(exit) XLockDisplay(display); 15556 win.onFocusChange(win._focused); 15557 } 15558 } 15559 break; 15560 case EventType.VisibilityNotify: 15561 if(auto win = e.xfocus.window in SimpleWindow.nativeMapping) { 15562 if (e.xvisibility.state == VisibilityNotify.VisibilityFullyObscured) { 15563 if (win.visibilityChanged !is null) { 15564 XUnlockDisplay(display); 15565 scope(exit) XLockDisplay(display); 15566 win.visibilityChanged(false); 15567 } 15568 } else { 15569 if (win.visibilityChanged !is null) { 15570 XUnlockDisplay(display); 15571 scope(exit) XLockDisplay(display); 15572 win.visibilityChanged(true); 15573 } 15574 } 15575 } 15576 break; 15577 case EventType.ClientMessage: 15578 if (e.xclient.message_type == GetAtom!("_X11SDPY_INSMME_FLAG_EVENT_", true)(e.xany.display)) { 15579 // "ignore next mouse motion" event, increment ignore counter for teh window 15580 if (auto win = e.xclient.window in SimpleWindow.nativeMapping) { 15581 ++(*win).warpEventCount; 15582 debug(x11sdpy_warp_debug) { import core.stdc.stdio : printf; printf("X11: got \"INSMME\" message, new count=%d\n", (*win).warpEventCount); } 15583 } else { 15584 debug(x11sdpy_warp_debug) { import core.stdc.stdio : printf; printf("X11: got \"INSMME\" WTF?!!\n"); } 15585 } 15586 } else if(e.xclient.data.l[0] == GetAtom!"WM_DELETE_WINDOW"(e.xany.display)) { 15587 // user clicked the close button on the window manager 15588 if(auto win = e.xclient.window in SimpleWindow.nativeMapping) { 15589 XUnlockDisplay(display); 15590 scope(exit) XLockDisplay(display); 15591 if ((*win).closeQuery !is null) (*win).closeQuery(); else (*win).close(); 15592 } 15593 15594 } else if(e.xclient.data.l[0] == GetAtom!"WM_TAKE_FOCUS"(e.xany.display)) { 15595 // writeln("HAPPENED"); 15596 // user clicked the close button on the window manager 15597 if(auto win = e.xclient.window in SimpleWindow.nativeMapping) { 15598 XUnlockDisplay(display); 15599 scope(exit) XLockDisplay(display); 15600 15601 auto setTo = *win; 15602 15603 if(win.setRequestedInputFocus !is null) { 15604 auto s = win.setRequestedInputFocus(); 15605 if(s !is null) { 15606 setTo = s; 15607 } 15608 } 15609 15610 assert(setTo !is null); 15611 15612 // FIXME: so this is actually supposed to focus to a relevant child window if appropriate 15613 15614 XSetInputFocus(display, setTo.impl.window, RevertToParent, e.xclient.data.l[1]); 15615 } 15616 } else if(e.xclient.message_type == GetAtom!"MANAGER"(e.xany.display)) { 15617 foreach(nai; NotificationAreaIcon.activeIcons) 15618 nai.newManager(); 15619 } else if(auto win = e.xclient.window in SimpleWindow.nativeMapping) { 15620 15621 bool xDragWindow = true; 15622 if(xDragWindow && e.xclient.message_type == GetAtom!"XdndStatus"(e.xany.display)) { 15623 //XDefineCursor(display, xDragWindow.impl.window, 15624 //writeln("XdndStatus ", e.xclient.data.l); 15625 } 15626 if(auto dh = win.dropHandler) { 15627 15628 static Atom[3] xFormatsBuffer; 15629 static Atom[] xFormats; 15630 15631 void resetXFormats() { 15632 xFormatsBuffer[] = 0; 15633 xFormats = xFormatsBuffer[]; 15634 } 15635 15636 if(e.xclient.message_type == GetAtom!"XdndEnter"(e.xany.display)) { 15637 // on Windows it is supposed to return the effect you actually do FIXME 15638 15639 auto sourceWindow = e.xclient.data.l[0]; 15640 15641 xFormatsBuffer[0] = e.xclient.data.l[2]; 15642 xFormatsBuffer[1] = e.xclient.data.l[3]; 15643 xFormatsBuffer[2] = e.xclient.data.l[4]; 15644 15645 if(e.xclient.data.l[1] & 1) { 15646 // can just grab it all but like we don't necessarily need them... 15647 xFormats = cast(Atom[]) getX11PropertyData(sourceWindow, GetAtom!"XdndTypeList"(display), XA_ATOM); 15648 } else { 15649 int len; 15650 foreach(fmt; xFormatsBuffer) 15651 if(fmt) len++; 15652 xFormats = xFormatsBuffer[0 .. len]; 15653 } 15654 15655 auto pkg = DropPackage(*win, e.xclient.data.l[0], 0, xFormats); 15656 15657 dh.dragEnter(&pkg); 15658 } else if(e.xclient.message_type == GetAtom!"XdndPosition"(e.xany.display)) { 15659 15660 auto pack = e.xclient.data.l[2]; 15661 15662 auto result = dh.dragOver(Point((pack & 0xffff0000) >> 16, pack & 0xffff)); // FIXME: translate screen coordinates back to window coords 15663 15664 15665 XClientMessageEvent xclient; 15666 15667 xclient.type = EventType.ClientMessage; 15668 xclient.window = e.xclient.data.l[0]; 15669 xclient.message_type = GetAtom!"XdndStatus"(display); 15670 xclient.format = 32; 15671 xclient.data.l[0] = win.impl.window; 15672 xclient.data.l[1] = (result.action != DragAndDropAction.none) ? 1 : 0; // will accept 15673 auto r = result.consistentWithin; 15674 xclient.data.l[2] = ((cast(short) r.left) << 16) | (cast(short) r.top); 15675 xclient.data.l[3] = ((cast(short) r.width) << 16) | (cast(short) r.height); 15676 xclient.data.l[4] = dndActionAtom(e.xany.display, result.action); 15677 15678 XSendEvent( 15679 display, 15680 e.xclient.data.l[0], 15681 false, 15682 EventMask.NoEventMask, 15683 cast(XEvent*) &xclient 15684 ); 15685 15686 15687 } else if(e.xclient.message_type == GetAtom!"XdndLeave"(e.xany.display)) { 15688 //writeln("XdndLeave"); 15689 // drop cancelled. 15690 // data.l[0] is the source window 15691 dh.dragLeave(); 15692 15693 resetXFormats(); 15694 } else if(e.xclient.message_type == GetAtom!"XdndDrop"(e.xany.display)) { 15695 // drop happening, should fetch data, then send finished 15696 // writeln("XdndDrop"); 15697 15698 auto pkg = DropPackage(*win, e.xclient.data.l[0], e.xclient.data.l[2], xFormats); 15699 15700 dh.drop(&pkg); 15701 15702 resetXFormats(); 15703 } else if(e.xclient.message_type == GetAtom!"XdndFinished"(e.xany.display)) { 15704 // writeln("XdndFinished"); 15705 15706 dh.finish(); 15707 } 15708 15709 } 15710 } 15711 break; 15712 case EventType.MapNotify: 15713 if(auto win = e.xmap.window in SimpleWindow.nativeMapping) { 15714 (*win)._visible = true; 15715 if (!(*win)._visibleForTheFirstTimeCalled) { 15716 (*win)._visibleForTheFirstTimeCalled = true; 15717 if ((*win).visibleForTheFirstTime !is null) { 15718 XUnlockDisplay(display); 15719 scope(exit) XLockDisplay(display); 15720 (*win).visibleForTheFirstTime(); 15721 } 15722 } 15723 if ((*win).visibilityChanged !is null) { 15724 XUnlockDisplay(display); 15725 scope(exit) XLockDisplay(display); 15726 (*win).visibilityChanged(true); 15727 } 15728 } 15729 break; 15730 case EventType.UnmapNotify: 15731 if(auto win = e.xunmap.window in SimpleWindow.nativeMapping) { 15732 win._visible = false; 15733 if (win.visibilityChanged !is null) { 15734 XUnlockDisplay(display); 15735 scope(exit) XLockDisplay(display); 15736 win.visibilityChanged(false); 15737 } 15738 } 15739 break; 15740 case EventType.DestroyNotify: 15741 if(auto win = e.xdestroywindow.window in SimpleWindow.nativeMapping) { 15742 if(win.destroyed) 15743 break; // might get a notification both for itself and from its parent 15744 if (win.onDestroyed !is null) try { win.onDestroyed(); } catch (Exception e) {} // sorry 15745 win._closed = true; // just in case 15746 win.destroyed = true; 15747 if (win.xic !is null) { 15748 XDestroyIC(win.xic); 15749 win.xic = null; // just in case 15750 } 15751 SimpleWindow.nativeMapping.remove(e.xdestroywindow.window); 15752 bool anyImportant = false; 15753 foreach(SimpleWindow w; SimpleWindow.nativeMapping) 15754 if(w.beingOpenKeepsAppOpen) { 15755 anyImportant = true; 15756 break; 15757 } 15758 if(!anyImportant) { 15759 EventLoop.quitApplication(); 15760 done = true; 15761 } 15762 } 15763 auto window = e.xdestroywindow.window; 15764 if(window in CapableOfHandlingNativeEvent.nativeHandleMapping) 15765 CapableOfHandlingNativeEvent.nativeHandleMapping.remove(window); 15766 15767 version(with_eventloop) { 15768 if(done) exit(); 15769 } 15770 break; 15771 15772 case EventType.MotionNotify: 15773 MouseEvent mouse; 15774 auto event = e.xmotion; 15775 15776 mouse.type = MouseEventType.motion; 15777 mouse.x = event.x; 15778 mouse.y = event.y; 15779 mouse.modifierState = event.state; 15780 15781 mouse.timestamp = event.time; 15782 15783 if(auto win = e.xmotion.window in SimpleWindow.nativeMapping) { 15784 mouse.window = *win; 15785 if (win.warpEventCount > 0) { 15786 debug(x11sdpy_warp_debug) { import core.stdc.stdio : printf; printf("X11: got \"warp motion\" message, current count=%d\n", (*win).warpEventCount); } 15787 --(*win).warpEventCount; 15788 (*win).mdx(mouse); // so deltas will be correctly updated 15789 } else { 15790 win.warpEventCount = 0; // just in case 15791 (*win).mdx(mouse); 15792 if((*win).handleMouseEvent) { 15793 XUnlockDisplay(display); 15794 scope(exit) XLockDisplay(display); 15795 (*win).handleMouseEvent(mouse); 15796 } 15797 } 15798 } 15799 15800 version(with_eventloop) 15801 send(mouse); 15802 break; 15803 case EventType.ButtonPress: 15804 case EventType.ButtonRelease: 15805 MouseEvent mouse; 15806 auto event = e.xbutton; 15807 15808 mouse.timestamp = event.time; 15809 15810 mouse.type = cast(MouseEventType) (e.type == EventType.ButtonPress ? 1 : 2); 15811 mouse.x = event.x; 15812 mouse.y = event.y; 15813 15814 static Time lastMouseDownTime = 0; 15815 static int lastMouseDownButton = -1; 15816 15817 mouse.doubleClick = e.type == EventType.ButtonPress && event.button == lastMouseDownButton && (event.time - lastMouseDownTime) < mouseDoubleClickTimeout; 15818 if(e.type == EventType.ButtonPress) { 15819 lastMouseDownTime = event.time; 15820 lastMouseDownButton = event.button; 15821 } 15822 15823 switch(event.button) { 15824 case 1: mouse.button = MouseButton.left; break; // left 15825 case 2: mouse.button = MouseButton.middle; break; // middle 15826 case 3: mouse.button = MouseButton.right; break; // right 15827 case 4: mouse.button = MouseButton.wheelUp; break; // scroll up 15828 case 5: mouse.button = MouseButton.wheelDown; break; // scroll down 15829 case 6: break; // idk 15830 case 7: break; // idk 15831 case 8: mouse.button = MouseButton.backButton; break; 15832 case 9: mouse.button = MouseButton.forwardButton; break; 15833 default: 15834 } 15835 15836 // FIXME: double check this 15837 mouse.modifierState = event.state; 15838 15839 //mouse.modifierState = event.detail; 15840 15841 if(auto win = e.xbutton.window in SimpleWindow.nativeMapping) { 15842 mouse.window = *win; 15843 (*win).mdx(mouse); 15844 if((*win).handleMouseEvent) { 15845 XUnlockDisplay(display); 15846 scope(exit) XLockDisplay(display); 15847 (*win).handleMouseEvent(mouse); 15848 } 15849 } 15850 version(with_eventloop) 15851 send(mouse); 15852 break; 15853 15854 case EventType.KeyPress: 15855 case EventType.KeyRelease: 15856 //if (e.type == EventType.KeyPress) { import core.stdc.stdio : stderr, fprintf; fprintf(stderr, "X11 keyboard event!\n"); } 15857 KeyEvent ke; 15858 ke.pressed = e.type == EventType.KeyPress; 15859 ke.hardwareCode = cast(ubyte) e.xkey.keycode; 15860 15861 auto sym = XKeycodeToKeysym( 15862 XDisplayConnection.get(), 15863 e.xkey.keycode, 15864 0); 15865 15866 ke.key = cast(Key) sym;//e.xkey.keycode; 15867 15868 ke.modifierState = e.xkey.state; 15869 15870 // writefln("%x", sym); 15871 wchar_t[128] charbuf = void; // buffer for XwcLookupString; composed value can consist of many chars! 15872 int charbuflen = 0; // return value of XwcLookupString 15873 if (ke.pressed) { 15874 auto win = e.xkey.window in SimpleWindow.nativeMapping; 15875 if (win !is null && win.xic !is null) { 15876 //{ import core.stdc.stdio : printf; printf("using xic!\n"); } 15877 Status status; 15878 charbuflen = XwcLookupString(win.xic, &e.xkey, charbuf.ptr, cast(int)charbuf.length, &sym, &status); 15879 //{ import core.stdc.stdio : printf; printf("charbuflen=%d\n", charbuflen); } 15880 } else { 15881 //{ import core.stdc.stdio : printf; printf("NOT using xic!\n"); } 15882 // If XIM initialization failed, don't process intl chars. Sorry, boys and girls. 15883 char[16] buffer; 15884 auto res = XLookupString(&e.xkey, buffer.ptr, buffer.length, null, null); 15885 if (res && buffer[0] < 128) charbuf[charbuflen++] = cast(wchar_t)buffer[0]; 15886 } 15887 } 15888 15889 // if there's no char, subst one 15890 if (charbuflen == 0) { 15891 switch (sym) { 15892 case 0xff09: charbuf[charbuflen++] = '\t'; break; 15893 case 0xff8d: // keypad enter 15894 case 0xff0d: charbuf[charbuflen++] = '\n'; break; 15895 default : // ignore 15896 } 15897 } 15898 15899 if (auto win = e.xkey.window in SimpleWindow.nativeMapping) { 15900 ke.window = *win; 15901 15902 15903 if(win.inputProxy) 15904 win = &win.inputProxy; 15905 15906 // char events are separate since they are on Windows too 15907 // also, xcompose can generate long char sequences 15908 // don't send char events if Meta and/or Hyper is pressed 15909 // TODO: ctrl+char should only send control chars; not yet 15910 if ((e.xkey.state&ModifierState.ctrl) != 0) { 15911 if (charbuflen > 1 || charbuf[0] >= ' ') charbuflen = 0; 15912 } 15913 15914 dchar[32] charsComingBuffer; 15915 int charsComingPosition; 15916 dchar[] charsComing = charsComingBuffer[]; 15917 15918 if (ke.pressed && charbuflen > 0) { 15919 // FIXME: I think Windows sends these on releases... we should try to match that, but idk about repeats. 15920 foreach (immutable dchar ch; charbuf[0..charbuflen]) { 15921 if(charsComingPosition >= charsComing.length) 15922 charsComing.length = charsComingPosition + 8; 15923 15924 charsComing[charsComingPosition++] = ch; 15925 } 15926 15927 charsComing = charsComing[0 .. charsComingPosition]; 15928 } else { 15929 charsComing = null; 15930 } 15931 15932 ke.charsPossible = charsComing; 15933 15934 if (win.handleKeyEvent) { 15935 XUnlockDisplay(display); 15936 scope(exit) XLockDisplay(display); 15937 win.handleKeyEvent(ke); 15938 } 15939 15940 // Super and alt modifier keys never actually send the chars, they are assumed to be special. 15941 if ((e.xkey.state&(ModifierState.alt|ModifierState.windows)) == 0 && win.handleCharEvent) { 15942 XUnlockDisplay(display); 15943 scope(exit) XLockDisplay(display); 15944 foreach(ch; charsComing) 15945 win.handleCharEvent(ch); 15946 } 15947 } 15948 15949 version(with_eventloop) 15950 send(ke); 15951 break; 15952 default: 15953 } 15954 15955 return done; 15956 } 15957 } 15958 15959 /* *************************************** */ 15960 /* Done with simpledisplay stuff */ 15961 /* *************************************** */ 15962 15963 // Necessary C library bindings follow 15964 version(Windows) {} else 15965 version(X11) { 15966 15967 extern(C) int eventfd (uint initval, int flags) nothrow @trusted @nogc; 15968 15969 // X11 bindings needed here 15970 /* 15971 A little of this is from the bindings project on 15972 D Source and some of it is copy/paste from the C 15973 header. 15974 15975 The DSource listing consistently used D's long 15976 where C used long. That's wrong - C long is 32 bit, so 15977 it should be int in D. I changed that here. 15978 15979 Note: 15980 This isn't complete, just took what I needed for myself. 15981 */ 15982 15983 import core.stdc.stddef : wchar_t; 15984 15985 interface XLib { 15986 extern(C) nothrow @nogc { 15987 char* XResourceManagerString(Display*); 15988 void XrmInitialize(); 15989 XrmDatabase XrmGetStringDatabase(char* data); 15990 bool XrmGetResource(XrmDatabase, const char*, const char*, char**, XrmValue*); 15991 15992 Cursor XCreateFontCursor(Display*, uint shape); 15993 int XDefineCursor(Display* display, Window w, Cursor cursor); 15994 int XUndefineCursor(Display* display, Window w); 15995 15996 Pixmap XCreateBitmapFromData(Display* display, Drawable d, const(char)* data, uint width, uint height); 15997 Cursor XCreatePixmapCursor(Display* display, Pixmap source, Pixmap mask, XColor* foreground_color, XColor* background_color, uint x, uint y); 15998 int XFreeCursor(Display* display, Cursor cursor); 15999 16000 int XLookupString(XKeyEvent *event_struct, char *buffer_return, int bytes_buffer, KeySym *keysym_return, void *status_in_out); 16001 16002 int XwcLookupString(XIC ic, XKeyPressedEvent* event, wchar_t* buffer_return, int wchars_buffer, KeySym* keysym_return, Status* status_return); 16003 16004 XVaNestedList XVaCreateNestedList(int unused, ...); 16005 16006 char *XKeysymToString(KeySym keysym); 16007 KeySym XKeycodeToKeysym( 16008 Display* /* display */, 16009 KeyCode /* keycode */, 16010 int /* index */ 16011 ); 16012 16013 int XConvertSelection(Display *display, Atom selection, Atom target, Atom property, Window requestor, Time time); 16014 16015 int XFree(void*); 16016 int XDeleteProperty(Display *display, Window w, Atom property); 16017 16018 int XChangeProperty(Display *display, Window w, Atom property, Atom type, int format, int mode, scope const void *data, int nelements); 16019 16020 int XGetWindowProperty(Display *display, Window w, Atom property, arch_long 16021 long_offset, arch_long long_length, Bool del, Atom req_type, Atom 16022 *actual_type_return, int *actual_format_return, arch_ulong 16023 *nitems_return, arch_ulong *bytes_after_return, void** prop_return); 16024 Atom* XListProperties(Display *display, Window w, int *num_prop_return); 16025 Status XGetTextProperty(Display *display, Window w, XTextProperty *text_prop_return, Atom property); 16026 Status XQueryTree(Display *display, Window w, Window *root_return, Window *parent_return, Window **children_return, uint *nchildren_return); 16027 16028 int XSetSelectionOwner(Display *display, Atom selection, Window owner, Time time); 16029 16030 Window XGetSelectionOwner(Display *display, Atom selection); 16031 16032 XVisualInfo* XGetVisualInfo(Display*, c_long, XVisualInfo*, int*); 16033 16034 char** XListFonts(Display*, const char*, int, int*); 16035 void XFreeFontNames(char**); 16036 16037 Display* XOpenDisplay(const char*); 16038 int XCloseDisplay(Display*); 16039 16040 int function() XSynchronize(Display*, bool); 16041 int function() XSetAfterFunction(Display*, int function() proc); 16042 16043 Bool XQueryExtension(Display*, const char*, int*, int*, int*); 16044 16045 Bool XSupportsLocale(); 16046 char* XSetLocaleModifiers(const(char)* modifier_list); 16047 XOM XOpenOM(Display* display, _XrmHashBucketRec* rdb, const(char)* res_name, const(char)* res_class); 16048 Status XCloseOM(XOM om); 16049 16050 XIM XOpenIM(Display* dpy, _XrmHashBucketRec* rdb, const(char)* res_name, const(char)* res_class); 16051 Status XCloseIM(XIM im); 16052 16053 char* XGetIMValues(XIM im, ...) /*_X_SENTINEL(0)*/; 16054 char* XSetIMValues(XIM im, ...) /*_X_SENTINEL(0)*/; 16055 Display* XDisplayOfIM(XIM im); 16056 char* XLocaleOfIM(XIM im); 16057 XIC XCreateIC(XIM im, ...) /*_X_SENTINEL(0)*/; 16058 void XDestroyIC(XIC ic); 16059 void XSetICFocus(XIC ic); 16060 void XUnsetICFocus(XIC ic); 16061 //wchar_t* XwcResetIC(XIC ic); 16062 char* XmbResetIC(XIC ic); 16063 char* Xutf8ResetIC(XIC ic); 16064 char* XSetICValues(XIC ic, ...) /*_X_SENTINEL(0)*/; 16065 char* XGetICValues(XIC ic, ...) /*_X_SENTINEL(0)*/; 16066 XIM XIMOfIC(XIC ic); 16067 16068 uint XSendEvent(Display* display, Window w, Bool propagate, arch_long event_mask, XEvent* event_send); 16069 16070 16071 XFontStruct *XLoadQueryFont(Display *display, scope const char *name); 16072 int XFreeFont(Display *display, XFontStruct *font_struct); 16073 int XSetFont(Display* display, GC gc, Font font); 16074 int XTextWidth(XFontStruct*, scope const char*, int); 16075 16076 int XSetLineAttributes(Display *display, GC gc, uint line_width, int line_style, int cap_style, int join_style); 16077 int XSetDashes(Display *display, GC gc, int dash_offset, scope const byte* dash_list, int n); 16078 16079 Window XCreateSimpleWindow( 16080 Display* /* display */, 16081 Window /* parent */, 16082 int /* x */, 16083 int /* y */, 16084 uint /* width */, 16085 uint /* height */, 16086 uint /* border_width */, 16087 uint /* border */, 16088 uint /* background */ 16089 ); 16090 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); 16091 16092 int XReparentWindow(Display*, Window, Window, int, int); 16093 int XClearWindow(Display*, Window); 16094 int XMoveResizeWindow(Display*, Window, int, int, uint, uint); 16095 int XMoveWindow(Display*, Window, int, int); 16096 int XResizeWindow(Display *display, Window w, uint width, uint height); 16097 16098 Colormap XCreateColormap(Display *display, Window w, Visual *visual, int alloc); 16099 16100 Status XMatchVisualInfo(Display *display, int screen, int depth, int class_, XVisualInfo *vinfo_return); 16101 16102 Status XGetWindowAttributes(Display*, Window, XWindowAttributes*); 16103 16104 XImage *XCreateImage( 16105 Display* /* display */, 16106 Visual* /* visual */, 16107 uint /* depth */, 16108 int /* format */, 16109 int /* offset */, 16110 ubyte* /* data */, 16111 uint /* width */, 16112 uint /* height */, 16113 int /* bitmap_pad */, 16114 int /* bytes_per_line */ 16115 ); 16116 16117 Status XInitImage (XImage* image); 16118 16119 Atom XInternAtom( 16120 Display* /* display */, 16121 const char* /* atom_name */, 16122 Bool /* only_if_exists */ 16123 ); 16124 16125 Status XInternAtoms(Display*, const char**, int, Bool, Atom*); 16126 char* XGetAtomName(Display*, Atom); 16127 Status XGetAtomNames(Display*, Atom*, int count, char**); 16128 16129 int XPutImage( 16130 Display* /* display */, 16131 Drawable /* d */, 16132 GC /* gc */, 16133 XImage* /* image */, 16134 int /* src_x */, 16135 int /* src_y */, 16136 int /* dest_x */, 16137 int /* dest_y */, 16138 uint /* width */, 16139 uint /* height */ 16140 ); 16141 16142 XImage *XGetImage(Display *display, Drawable d, int x, int y, uint width, uint height, c_ulong plane_mask, int format); 16143 16144 16145 int XDestroyWindow( 16146 Display* /* display */, 16147 Window /* w */ 16148 ); 16149 16150 int XDestroyImage(XImage*); 16151 16152 int XSelectInput( 16153 Display* /* display */, 16154 Window /* w */, 16155 EventMask /* event_mask */ 16156 ); 16157 16158 int XMapWindow( 16159 Display* /* display */, 16160 Window /* w */ 16161 ); 16162 16163 Status XIconifyWindow(Display*, Window, int); 16164 int XMapRaised(Display*, Window); 16165 int XMapSubwindows(Display*, Window); 16166 16167 int XNextEvent( 16168 Display* /* display */, 16169 XEvent* /* event_return */ 16170 ); 16171 16172 int XMaskEvent(Display*, arch_long, XEvent*); 16173 16174 Bool XFilterEvent(XEvent *event, Window window); 16175 int XRefreshKeyboardMapping(XMappingEvent *event_map); 16176 16177 Status XSetWMProtocols( 16178 Display* /* display */, 16179 Window /* w */, 16180 Atom* /* protocols */, 16181 int /* count */ 16182 ); 16183 16184 void XSetWMNormalHints(Display *display, Window w, XSizeHints *hints); 16185 Status XGetWMNormalHints(Display *display, Window w, XSizeHints *hints, c_long* supplied_return); 16186 16187 16188 Status XInitThreads(); 16189 void XLockDisplay (Display* display); 16190 void XUnlockDisplay (Display* display); 16191 16192 void XSetWMProperties(Display*, Window, XTextProperty*, XTextProperty*, char**, int, XSizeHints*, XWMHints*, XClassHint*); 16193 16194 int XSetWindowBackground (Display* display, Window w, c_ulong background_pixel); 16195 int XSetWindowBackgroundPixmap (Display* display, Window w, Pixmap background_pixmap); 16196 //int XSetWindowBorder (Display* display, Window w, c_ulong border_pixel); 16197 //int XSetWindowBorderPixmap (Display* display, Window w, Pixmap border_pixmap); 16198 //int XSetWindowBorderWidth (Display* display, Window w, uint width); 16199 16200 16201 // check out Xft too: http://www.keithp.com/~keithp/render/Xft.tutorial 16202 int XDrawString(Display*, Drawable, GC, int, int, scope const char*, int); 16203 int XDrawLine(Display*, Drawable, GC, int, int, int, int); 16204 int XDrawRectangle(Display*, Drawable, GC, int, int, uint, uint); 16205 int XDrawArc(Display*, Drawable, GC, int, int, uint, uint, int, int); 16206 int XFillRectangle(Display*, Drawable, GC, int, int, uint, uint); 16207 int XFillArc(Display*, Drawable, GC, int, int, uint, uint, int, int); 16208 int XDrawPoint(Display*, Drawable, GC, int, int); 16209 int XSetForeground(Display*, GC, uint); 16210 int XSetBackground(Display*, GC, uint); 16211 16212 XFontSet XCreateFontSet(Display*, const char*, char***, int*, char**); 16213 void XFreeFontSet(Display*, XFontSet); 16214 void Xutf8DrawString(Display*, Drawable, XFontSet, GC, int, int, scope const char*, int); 16215 void Xutf8DrawText(Display*, Drawable, GC, int, int, XmbTextItem*, int); 16216 16217 int Xutf8TextExtents(XFontSet font_set, const char *, int num_bytes, XRectangle *overall_ink_return, XRectangle *overall_logical_return); 16218 16219 16220 //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); 16221 16222 void XDrawText(Display*, Drawable, GC, int, int, XTextItem*, int); 16223 int XSetFunction(Display*, GC, int); 16224 16225 GC XCreateGC(Display*, Drawable, uint, void*); 16226 int XCopyGC(Display*, GC, uint, GC); 16227 int XFreeGC(Display*, GC); 16228 16229 bool XCheckWindowEvent(Display*, Window, int, XEvent*); 16230 bool XCheckMaskEvent(Display*, int, XEvent*); 16231 16232 int XPending(Display*); 16233 int XEventsQueued(Display* display, int mode); 16234 16235 Pixmap XCreatePixmap(Display*, Drawable, uint, uint, uint); 16236 int XFreePixmap(Display*, Pixmap); 16237 int XCopyArea(Display*, Drawable, Drawable, GC, int, int, uint, uint, int, int); 16238 int XFlush(Display*); 16239 int XBell(Display*, int); 16240 int XSync(Display*, bool); 16241 16242 int XGrabKey (Display* display, int keycode, uint modifiers, Window grab_window, Bool owner_events, int pointer_mode, int keyboard_mode); 16243 int XUngrabKey (Display* display, int keycode, uint modifiers, Window grab_window); 16244 16245 int XGrabKeyboard(Display*, Window, Bool, int, int, Time); 16246 int XUngrabKeyboard(Display*, Time); 16247 16248 KeyCode XKeysymToKeycode (Display* display, KeySym keysym); 16249 16250 KeySym XStringToKeysym(const char *string); 16251 16252 Bool XCheckTypedEvent(Display *display, int event_type, XEvent *event_return); 16253 16254 Window XDefaultRootWindow(Display*); 16255 16256 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); 16257 16258 int XUngrabButton(Display *display, uint button, uint modifiers, Window grab_window); 16259 16260 int XDrawLines(Display*, Drawable, GC, XPoint*, int, CoordMode); 16261 int XFillPolygon(Display*, Drawable, GC, XPoint*, int, PolygonShape, CoordMode); 16262 16263 Status XAllocColor(Display*, Colormap, XColor*); 16264 16265 int XWithdrawWindow(Display*, Window, int); 16266 int XUnmapWindow(Display*, Window); 16267 int XLowerWindow(Display*, Window); 16268 int XRaiseWindow(Display*, Window); 16269 16270 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); 16271 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); 16272 16273 int XGetInputFocus(Display*, Window*, int*); 16274 int XSetInputFocus(Display*, Window, int, Time); 16275 16276 XErrorHandler XSetErrorHandler(XErrorHandler); 16277 16278 int XGetErrorText(Display*, int, char*, int); 16279 16280 Bool XkbSetDetectableAutoRepeat(Display* dpy, Bool detectable, Bool* supported); 16281 16282 16283 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); 16284 int XUngrabPointer(Display *display, Time time); 16285 int XChangeActivePointerGrab(Display *display, uint event_mask, Cursor cursor, Time time); 16286 16287 int XCopyPlane(Display*, Drawable, Drawable, GC, int, int, uint, uint, int, int, arch_ulong); 16288 16289 Status XGetGeometry(Display*, Drawable, Window*, int*, int*, uint*, uint*, uint*, uint*); 16290 int XSetClipMask(Display*, GC, Pixmap); 16291 int XSetClipOrigin(Display*, GC, int, int); 16292 16293 void XSetClipRectangles(Display*, GC, int, int, XRectangle*, int, int); 16294 16295 void XSetWMName(Display*, Window, XTextProperty*); 16296 Status XGetWMName(Display*, Window, XTextProperty*); 16297 int XStoreName(Display* display, Window w, const(char)* window_name); 16298 16299 XIOErrorHandler XSetIOErrorHandler (XIOErrorHandler handler); 16300 16301 } 16302 } 16303 16304 interface Xext { 16305 extern(C) nothrow @nogc { 16306 Status XShmAttach(Display*, XShmSegmentInfo*); 16307 Status XShmDetach(Display*, XShmSegmentInfo*); 16308 Status XShmPutImage( 16309 Display* /* dpy */, 16310 Drawable /* d */, 16311 GC /* gc */, 16312 XImage* /* image */, 16313 int /* src_x */, 16314 int /* src_y */, 16315 int /* dst_x */, 16316 int /* dst_y */, 16317 uint /* src_width */, 16318 uint /* src_height */, 16319 Bool /* send_event */ 16320 ); 16321 16322 Status XShmQueryExtension(Display*); 16323 16324 XImage *XShmCreateImage( 16325 Display* /* dpy */, 16326 Visual* /* visual */, 16327 uint /* depth */, 16328 int /* format */, 16329 char* /* data */, 16330 XShmSegmentInfo* /* shminfo */, 16331 uint /* width */, 16332 uint /* height */ 16333 ); 16334 16335 Pixmap XShmCreatePixmap( 16336 Display* /* dpy */, 16337 Drawable /* d */, 16338 char* /* data */, 16339 XShmSegmentInfo* /* shminfo */, 16340 uint /* width */, 16341 uint /* height */, 16342 uint /* depth */ 16343 ); 16344 16345 } 16346 } 16347 16348 // this requires -lXpm 16349 //int XpmCreatePixmapFromData(Display*, Drawable, scope const char**, Pixmap*, Pixmap*, void*); // FIXME: void* should be XpmAttributes 16350 16351 16352 mixin DynamicLoad!(XLib, "X11", 6, librariesSuccessfullyLoaded) xlib; 16353 mixin DynamicLoad!(Xext, "Xext", 6, librariesSuccessfullyLoaded) xext; 16354 shared static this() { 16355 xlib.loadDynamicLibrary(); 16356 xext.loadDynamicLibrary(); 16357 } 16358 16359 16360 extern(C) nothrow @nogc { 16361 16362 alias XrmDatabase = void*; 16363 struct XrmValue { 16364 uint size; 16365 void* addr; 16366 } 16367 16368 struct XVisualInfo { 16369 Visual* visual; 16370 VisualID visualid; 16371 int screen; 16372 uint depth; 16373 int c_class; 16374 c_ulong red_mask; 16375 c_ulong green_mask; 16376 c_ulong blue_mask; 16377 int colormap_size; 16378 int bits_per_rgb; 16379 } 16380 16381 enum VisualNoMask= 0x0; 16382 enum VisualIDMask= 0x1; 16383 enum VisualScreenMask=0x2; 16384 enum VisualDepthMask= 0x4; 16385 enum VisualClassMask= 0x8; 16386 enum VisualRedMaskMask=0x10; 16387 enum VisualGreenMaskMask=0x20; 16388 enum VisualBlueMaskMask=0x40; 16389 enum VisualColormapSizeMask=0x80; 16390 enum VisualBitsPerRGBMask=0x100; 16391 enum VisualAllMask= 0x1FF; 16392 16393 enum AnyKey = 0; 16394 enum AnyModifier = 1 << 15; 16395 16396 // XIM and other crap 16397 struct _XOM {} 16398 struct _XIM {} 16399 struct _XIC {} 16400 alias XOM = _XOM*; 16401 alias XIM = _XIM*; 16402 alias XIC = _XIC*; 16403 16404 alias XVaNestedList = void*; 16405 16406 alias XIMStyle = arch_ulong; 16407 enum : arch_ulong { 16408 XIMPreeditArea = 0x0001, 16409 XIMPreeditCallbacks = 0x0002, 16410 XIMPreeditPosition = 0x0004, 16411 XIMPreeditNothing = 0x0008, 16412 XIMPreeditNone = 0x0010, 16413 XIMStatusArea = 0x0100, 16414 XIMStatusCallbacks = 0x0200, 16415 XIMStatusNothing = 0x0400, 16416 XIMStatusNone = 0x0800, 16417 } 16418 16419 16420 /* X Shared Memory Extension functions */ 16421 //pragma(lib, "Xshm"); 16422 alias arch_ulong ShmSeg; 16423 struct XShmSegmentInfo { 16424 ShmSeg shmseg; 16425 int shmid; 16426 ubyte* shmaddr; 16427 Bool readOnly; 16428 } 16429 16430 // and the necessary OS functions 16431 int shmget(int, size_t, int); 16432 void* shmat(int, scope const void*, int); 16433 int shmdt(scope const void*); 16434 int shmctl (int shmid, int cmd, void* ptr /*struct shmid_ds *buf*/); 16435 16436 enum IPC_PRIVATE = 0; 16437 enum IPC_CREAT = 512; 16438 enum IPC_RMID = 0; 16439 16440 /* MIT-SHM end */ 16441 16442 16443 enum MappingType:int { 16444 MappingModifier =0, 16445 MappingKeyboard =1, 16446 MappingPointer =2 16447 } 16448 16449 /* ImageFormat -- PutImage, GetImage */ 16450 enum ImageFormat:int { 16451 XYBitmap =0, /* depth 1, XYFormat */ 16452 XYPixmap =1, /* depth == drawable depth */ 16453 ZPixmap =2 /* depth == drawable depth */ 16454 } 16455 16456 enum ModifierName:int { 16457 ShiftMapIndex =0, 16458 LockMapIndex =1, 16459 ControlMapIndex =2, 16460 Mod1MapIndex =3, 16461 Mod2MapIndex =4, 16462 Mod3MapIndex =5, 16463 Mod4MapIndex =6, 16464 Mod5MapIndex =7 16465 } 16466 16467 enum ButtonMask:int { 16468 Button1Mask =1<<8, 16469 Button2Mask =1<<9, 16470 Button3Mask =1<<10, 16471 Button4Mask =1<<11, 16472 Button5Mask =1<<12, 16473 AnyModifier =1<<15/* used in GrabButton, GrabKey */ 16474 } 16475 16476 enum KeyOrButtonMask:uint { 16477 ShiftMask =1<<0, 16478 LockMask =1<<1, 16479 ControlMask =1<<2, 16480 Mod1Mask =1<<3, 16481 Mod2Mask =1<<4, 16482 Mod3Mask =1<<5, 16483 Mod4Mask =1<<6, 16484 Mod5Mask =1<<7, 16485 Button1Mask =1<<8, 16486 Button2Mask =1<<9, 16487 Button3Mask =1<<10, 16488 Button4Mask =1<<11, 16489 Button5Mask =1<<12, 16490 AnyModifier =1<<15/* used in GrabButton, GrabKey */ 16491 } 16492 16493 enum ButtonName:int { 16494 Button1 =1, 16495 Button2 =2, 16496 Button3 =3, 16497 Button4 =4, 16498 Button5 =5 16499 } 16500 16501 /* Notify modes */ 16502 enum NotifyModes:int 16503 { 16504 NotifyNormal =0, 16505 NotifyGrab =1, 16506 NotifyUngrab =2, 16507 NotifyWhileGrabbed =3 16508 } 16509 enum NotifyHint = 1; /* for MotionNotify events */ 16510 16511 /* Notify detail */ 16512 enum NotifyDetail:int 16513 { 16514 NotifyAncestor =0, 16515 NotifyVirtual =1, 16516 NotifyInferior =2, 16517 NotifyNonlinear =3, 16518 NotifyNonlinearVirtual =4, 16519 NotifyPointer =5, 16520 NotifyPointerRoot =6, 16521 NotifyDetailNone =7 16522 } 16523 16524 /* Visibility notify */ 16525 16526 enum VisibilityNotify:int 16527 { 16528 VisibilityUnobscured =0, 16529 VisibilityPartiallyObscured =1, 16530 VisibilityFullyObscured =2 16531 } 16532 16533 16534 enum WindowStackingMethod:int 16535 { 16536 Above =0, 16537 Below =1, 16538 TopIf =2, 16539 BottomIf =3, 16540 Opposite =4 16541 } 16542 16543 /* Circulation request */ 16544 enum CirculationRequest:int 16545 { 16546 PlaceOnTop =0, 16547 PlaceOnBottom =1 16548 } 16549 16550 enum PropertyNotification:int 16551 { 16552 PropertyNewValue =0, 16553 PropertyDelete =1 16554 } 16555 16556 enum ColorMapNotification:int 16557 { 16558 ColormapUninstalled =0, 16559 ColormapInstalled =1 16560 } 16561 16562 16563 struct _XPrivate {} 16564 struct _XrmHashBucketRec {} 16565 16566 alias void* XPointer; 16567 alias void* XExtData; 16568 16569 version( X86_64 ) { 16570 alias ulong XID; 16571 alias ulong arch_ulong; 16572 alias long arch_long; 16573 } else version (AArch64) { 16574 alias ulong XID; 16575 alias ulong arch_ulong; 16576 alias long arch_long; 16577 } else { 16578 alias uint XID; 16579 alias uint arch_ulong; 16580 alias int arch_long; 16581 } 16582 16583 alias XID Window; 16584 alias XID Drawable; 16585 alias XID Pixmap; 16586 16587 alias arch_ulong Atom; 16588 alias int Bool; 16589 alias Display XDisplay; 16590 16591 alias int ByteOrder; 16592 alias arch_ulong Time; 16593 alias void ScreenFormat; 16594 16595 struct XImage { 16596 int width, height; /* size of image */ 16597 int xoffset; /* number of pixels offset in X direction */ 16598 ImageFormat format; /* XYBitmap, XYPixmap, ZPixmap */ 16599 void *data; /* pointer to image data */ 16600 ByteOrder byte_order; /* data byte order, LSBFirst, MSBFirst */ 16601 int bitmap_unit; /* quant. of scanline 8, 16, 32 */ 16602 int bitmap_bit_order; /* LSBFirst, MSBFirst */ 16603 int bitmap_pad; /* 8, 16, 32 either XY or ZPixmap */ 16604 int depth; /* depth of image */ 16605 int bytes_per_line; /* accelarator to next line */ 16606 int bits_per_pixel; /* bits per pixel (ZPixmap) */ 16607 arch_ulong red_mask; /* bits in z arrangment */ 16608 arch_ulong green_mask; 16609 arch_ulong blue_mask; 16610 XPointer obdata; /* hook for the object routines to hang on */ 16611 static struct F { /* image manipulation routines */ 16612 XImage* function( 16613 XDisplay* /* display */, 16614 Visual* /* visual */, 16615 uint /* depth */, 16616 int /* format */, 16617 int /* offset */, 16618 ubyte* /* data */, 16619 uint /* width */, 16620 uint /* height */, 16621 int /* bitmap_pad */, 16622 int /* bytes_per_line */) create_image; 16623 int function(XImage *) destroy_image; 16624 arch_ulong function(XImage *, int, int) get_pixel; 16625 int function(XImage *, int, int, arch_ulong) put_pixel; 16626 XImage* function(XImage *, int, int, uint, uint) sub_image; 16627 int function(XImage *, arch_long) add_pixel; 16628 } 16629 F f; 16630 } 16631 version(X86_64) static assert(XImage.sizeof == 136); 16632 else version(X86) static assert(XImage.sizeof == 88); 16633 16634 struct XCharStruct { 16635 short lbearing; /* origin to left edge of raster */ 16636 short rbearing; /* origin to right edge of raster */ 16637 short width; /* advance to next char's origin */ 16638 short ascent; /* baseline to top edge of raster */ 16639 short descent; /* baseline to bottom edge of raster */ 16640 ushort attributes; /* per char flags (not predefined) */ 16641 } 16642 16643 /* 16644 * To allow arbitrary information with fonts, there are additional properties 16645 * returned. 16646 */ 16647 struct XFontProp { 16648 Atom name; 16649 arch_ulong card32; 16650 } 16651 16652 alias Atom Font; 16653 16654 struct XFontStruct { 16655 XExtData *ext_data; /* Hook for extension to hang data */ 16656 Font fid; /* Font ID for this font */ 16657 uint direction; /* Direction the font is painted */ 16658 uint min_char_or_byte2; /* First character */ 16659 uint max_char_or_byte2; /* Last character */ 16660 uint min_byte1; /* First row that exists (for two-byte fonts) */ 16661 uint max_byte1; /* Last row that exists (for two-byte fonts) */ 16662 Bool all_chars_exist; /* Flag if all characters have nonzero size */ 16663 uint default_char; /* Char to print for undefined character */ 16664 int n_properties; /* How many properties there are */ 16665 XFontProp *properties; /* Pointer to array of additional properties*/ 16666 XCharStruct min_bounds; /* Minimum bounds over all existing char*/ 16667 XCharStruct max_bounds; /* Maximum bounds over all existing char*/ 16668 XCharStruct *per_char; /* first_char to last_char information */ 16669 int ascent; /* Max extent above baseline for spacing */ 16670 int descent; /* Max descent below baseline for spacing */ 16671 } 16672 16673 16674 /* 16675 * Definitions of specific events. 16676 */ 16677 struct XKeyEvent 16678 { 16679 int type; /* of event */ 16680 arch_ulong serial; /* # of last request processed by server */ 16681 Bool send_event; /* true if this came from a SendEvent request */ 16682 Display *display; /* Display the event was read from */ 16683 Window window; /* "event" window it is reported relative to */ 16684 Window root; /* root window that the event occurred on */ 16685 Window subwindow; /* child window */ 16686 Time time; /* milliseconds */ 16687 int x, y; /* pointer x, y coordinates in event window */ 16688 int x_root, y_root; /* coordinates relative to root */ 16689 KeyOrButtonMask state; /* key or button mask */ 16690 uint keycode; /* detail */ 16691 Bool same_screen; /* same screen flag */ 16692 } 16693 version(X86_64) static assert(XKeyEvent.sizeof == 96); 16694 alias XKeyEvent XKeyPressedEvent; 16695 alias XKeyEvent XKeyReleasedEvent; 16696 16697 struct XButtonEvent 16698 { 16699 int type; /* of event */ 16700 arch_ulong serial; /* # of last request processed by server */ 16701 Bool send_event; /* true if this came from a SendEvent request */ 16702 Display *display; /* Display the event was read from */ 16703 Window window; /* "event" window it is reported relative to */ 16704 Window root; /* root window that the event occurred on */ 16705 Window subwindow; /* child window */ 16706 Time time; /* milliseconds */ 16707 int x, y; /* pointer x, y coordinates in event window */ 16708 int x_root, y_root; /* coordinates relative to root */ 16709 KeyOrButtonMask state; /* key or button mask */ 16710 uint button; /* detail */ 16711 Bool same_screen; /* same screen flag */ 16712 } 16713 alias XButtonEvent XButtonPressedEvent; 16714 alias XButtonEvent XButtonReleasedEvent; 16715 16716 struct XMotionEvent{ 16717 int type; /* of event */ 16718 arch_ulong serial; /* # of last request processed by server */ 16719 Bool send_event; /* true if this came from a SendEvent request */ 16720 Display *display; /* Display the event was read from */ 16721 Window window; /* "event" window reported relative to */ 16722 Window root; /* root window that the event occurred on */ 16723 Window subwindow; /* child window */ 16724 Time time; /* milliseconds */ 16725 int x, y; /* pointer x, y coordinates in event window */ 16726 int x_root, y_root; /* coordinates relative to root */ 16727 KeyOrButtonMask state; /* key or button mask */ 16728 byte is_hint; /* detail */ 16729 Bool same_screen; /* same screen flag */ 16730 } 16731 alias XMotionEvent XPointerMovedEvent; 16732 16733 struct XCrossingEvent{ 16734 int type; /* of event */ 16735 arch_ulong serial; /* # of last request processed by server */ 16736 Bool send_event; /* true if this came from a SendEvent request */ 16737 Display *display; /* Display the event was read from */ 16738 Window window; /* "event" window reported relative to */ 16739 Window root; /* root window that the event occurred on */ 16740 Window subwindow; /* child window */ 16741 Time time; /* milliseconds */ 16742 int x, y; /* pointer x, y coordinates in event window */ 16743 int x_root, y_root; /* coordinates relative to root */ 16744 NotifyModes mode; /* NotifyNormal, NotifyGrab, NotifyUngrab */ 16745 NotifyDetail detail; 16746 /* 16747 * NotifyAncestor, NotifyVirtual, NotifyInferior, 16748 * NotifyNonlinear,NotifyNonlinearVirtual 16749 */ 16750 Bool same_screen; /* same screen flag */ 16751 Bool focus; /* Boolean focus */ 16752 KeyOrButtonMask state; /* key or button mask */ 16753 } 16754 alias XCrossingEvent XEnterWindowEvent; 16755 alias XCrossingEvent XLeaveWindowEvent; 16756 16757 struct XFocusChangeEvent{ 16758 int type; /* FocusIn or FocusOut */ 16759 arch_ulong serial; /* # of last request processed by server */ 16760 Bool send_event; /* true if this came from a SendEvent request */ 16761 Display *display; /* Display the event was read from */ 16762 Window window; /* window of event */ 16763 NotifyModes mode; /* NotifyNormal, NotifyWhileGrabbed, 16764 NotifyGrab, NotifyUngrab */ 16765 NotifyDetail detail; 16766 /* 16767 * NotifyAncestor, NotifyVirtual, NotifyInferior, 16768 * NotifyNonlinear,NotifyNonlinearVirtual, NotifyPointer, 16769 * NotifyPointerRoot, NotifyDetailNone 16770 */ 16771 } 16772 alias XFocusChangeEvent XFocusInEvent; 16773 alias XFocusChangeEvent XFocusOutEvent; 16774 16775 enum CWBackPixmap = (1L<<0); 16776 enum CWBackPixel = (1L<<1); 16777 enum CWBorderPixmap = (1L<<2); 16778 enum CWBorderPixel = (1L<<3); 16779 enum CWBitGravity = (1L<<4); 16780 enum CWWinGravity = (1L<<5); 16781 enum CWBackingStore = (1L<<6); 16782 enum CWBackingPlanes = (1L<<7); 16783 enum CWBackingPixel = (1L<<8); 16784 enum CWOverrideRedirect = (1L<<9); 16785 enum CWSaveUnder = (1L<<10); 16786 enum CWEventMask = (1L<<11); 16787 enum CWDontPropagate = (1L<<12); 16788 enum CWColormap = (1L<<13); 16789 enum CWCursor = (1L<<14); 16790 16791 struct XWindowAttributes { 16792 int x, y; /* location of window */ 16793 int width, height; /* width and height of window */ 16794 int border_width; /* border width of window */ 16795 int depth; /* depth of window */ 16796 Visual *visual; /* the associated visual structure */ 16797 Window root; /* root of screen containing window */ 16798 int class_; /* InputOutput, InputOnly*/ 16799 int bit_gravity; /* one of the bit gravity values */ 16800 int win_gravity; /* one of the window gravity values */ 16801 int backing_store; /* NotUseful, WhenMapped, Always */ 16802 arch_ulong backing_planes; /* planes to be preserved if possible */ 16803 arch_ulong backing_pixel; /* value to be used when restoring planes */ 16804 Bool save_under; /* boolean, should bits under be saved? */ 16805 Colormap colormap; /* color map to be associated with window */ 16806 Bool map_installed; /* boolean, is color map currently installed*/ 16807 int map_state; /* IsUnmapped, IsUnviewable, IsViewable */ 16808 arch_long all_event_masks; /* set of events all people have interest in*/ 16809 arch_long your_event_mask; /* my event mask */ 16810 arch_long do_not_propagate_mask; /* set of events that should not propagate */ 16811 Bool override_redirect; /* boolean value for override-redirect */ 16812 Screen *screen; /* back pointer to correct screen */ 16813 } 16814 16815 enum IsUnmapped = 0; 16816 enum IsUnviewable = 1; 16817 enum IsViewable = 2; 16818 16819 struct XSetWindowAttributes { 16820 Pixmap background_pixmap;/* background, None, or ParentRelative */ 16821 arch_ulong background_pixel;/* background pixel */ 16822 Pixmap border_pixmap; /* border of the window or CopyFromParent */ 16823 arch_ulong border_pixel;/* border pixel value */ 16824 int bit_gravity; /* one of bit gravity values */ 16825 int win_gravity; /* one of the window gravity values */ 16826 int backing_store; /* NotUseful, WhenMapped, Always */ 16827 arch_ulong backing_planes;/* planes to be preserved if possible */ 16828 arch_ulong backing_pixel;/* value to use in restoring planes */ 16829 Bool save_under; /* should bits under be saved? (popups) */ 16830 arch_long event_mask; /* set of events that should be saved */ 16831 arch_long do_not_propagate_mask;/* set of events that should not propagate */ 16832 Bool override_redirect; /* boolean value for override_redirect */ 16833 Colormap colormap; /* color map to be associated with window */ 16834 Cursor cursor; /* cursor to be displayed (or None) */ 16835 } 16836 16837 16838 alias int Status; 16839 16840 16841 enum EventMask:int 16842 { 16843 NoEventMask =0, 16844 KeyPressMask =1<<0, 16845 KeyReleaseMask =1<<1, 16846 ButtonPressMask =1<<2, 16847 ButtonReleaseMask =1<<3, 16848 EnterWindowMask =1<<4, 16849 LeaveWindowMask =1<<5, 16850 PointerMotionMask =1<<6, 16851 PointerMotionHintMask =1<<7, 16852 Button1MotionMask =1<<8, 16853 Button2MotionMask =1<<9, 16854 Button3MotionMask =1<<10, 16855 Button4MotionMask =1<<11, 16856 Button5MotionMask =1<<12, 16857 ButtonMotionMask =1<<13, 16858 KeymapStateMask =1<<14, 16859 ExposureMask =1<<15, 16860 VisibilityChangeMask =1<<16, 16861 StructureNotifyMask =1<<17, 16862 ResizeRedirectMask =1<<18, 16863 SubstructureNotifyMask =1<<19, 16864 SubstructureRedirectMask=1<<20, 16865 FocusChangeMask =1<<21, 16866 PropertyChangeMask =1<<22, 16867 ColormapChangeMask =1<<23, 16868 OwnerGrabButtonMask =1<<24 16869 } 16870 16871 struct MwmHints { 16872 c_ulong flags; 16873 c_ulong functions; 16874 c_ulong decorations; 16875 c_long input_mode; 16876 c_ulong status; 16877 } 16878 16879 enum { 16880 MWM_HINTS_FUNCTIONS = (1L << 0), 16881 MWM_HINTS_DECORATIONS = (1L << 1), 16882 16883 MWM_FUNC_ALL = (1L << 0), 16884 MWM_FUNC_RESIZE = (1L << 1), 16885 MWM_FUNC_MOVE = (1L << 2), 16886 MWM_FUNC_MINIMIZE = (1L << 3), 16887 MWM_FUNC_MAXIMIZE = (1L << 4), 16888 MWM_FUNC_CLOSE = (1L << 5), 16889 16890 MWM_DECOR_ALL = (1L << 0), 16891 MWM_DECOR_BORDER = (1L << 1), 16892 MWM_DECOR_RESIZEH = (1L << 2), 16893 MWM_DECOR_TITLE = (1L << 3), 16894 MWM_DECOR_MENU = (1L << 4), 16895 MWM_DECOR_MINIMIZE = (1L << 5), 16896 MWM_DECOR_MAXIMIZE = (1L << 6), 16897 } 16898 16899 import core.stdc.config : c_long, c_ulong; 16900 16901 /* Size hints mask bits */ 16902 16903 enum USPosition = (1L << 0) /* user specified x, y */; 16904 enum USSize = (1L << 1) /* user specified width, height */; 16905 enum PPosition = (1L << 2) /* program specified position */; 16906 enum PSize = (1L << 3) /* program specified size */; 16907 enum PMinSize = (1L << 4) /* program specified minimum size */; 16908 enum PMaxSize = (1L << 5) /* program specified maximum size */; 16909 enum PResizeInc = (1L << 6) /* program specified resize increments */; 16910 enum PAspect = (1L << 7) /* program specified min and max aspect ratios */; 16911 enum PBaseSize = (1L << 8); 16912 enum PWinGravity = (1L << 9); 16913 enum PAllHints = (PPosition|PSize| PMinSize|PMaxSize| PResizeInc|PAspect); 16914 struct XSizeHints { 16915 arch_long flags; /* marks which fields in this structure are defined */ 16916 int x, y; /* Obsolete */ 16917 int width, height; /* Obsolete */ 16918 int min_width, min_height; 16919 int max_width, max_height; 16920 int width_inc, height_inc; 16921 struct Aspect { 16922 int x; /* numerator */ 16923 int y; /* denominator */ 16924 } 16925 16926 Aspect min_aspect; 16927 Aspect max_aspect; 16928 int base_width, base_height; 16929 int win_gravity; 16930 /* this structure may be extended in the future */ 16931 } 16932 16933 16934 16935 enum EventType:int 16936 { 16937 KeyPress =2, 16938 KeyRelease =3, 16939 ButtonPress =4, 16940 ButtonRelease =5, 16941 MotionNotify =6, 16942 EnterNotify =7, 16943 LeaveNotify =8, 16944 FocusIn =9, 16945 FocusOut =10, 16946 KeymapNotify =11, 16947 Expose =12, 16948 GraphicsExpose =13, 16949 NoExpose =14, 16950 VisibilityNotify =15, 16951 CreateNotify =16, 16952 DestroyNotify =17, 16953 UnmapNotify =18, 16954 MapNotify =19, 16955 MapRequest =20, 16956 ReparentNotify =21, 16957 ConfigureNotify =22, 16958 ConfigureRequest =23, 16959 GravityNotify =24, 16960 ResizeRequest =25, 16961 CirculateNotify =26, 16962 CirculateRequest =27, 16963 PropertyNotify =28, 16964 SelectionClear =29, 16965 SelectionRequest =30, 16966 SelectionNotify =31, 16967 ColormapNotify =32, 16968 ClientMessage =33, 16969 MappingNotify =34, 16970 LASTEvent =35 /* must be bigger than any event # */ 16971 } 16972 /* generated on EnterWindow and FocusIn when KeyMapState selected */ 16973 struct XKeymapEvent 16974 { 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 Window window; 16980 byte[32] key_vector; 16981 } 16982 16983 struct XExposeEvent 16984 { 16985 int type; 16986 arch_ulong serial; /* # of last request processed by server */ 16987 Bool send_event; /* true if this came from a SendEvent request */ 16988 Display *display; /* Display the event was read from */ 16989 Window window; 16990 int x, y; 16991 int width, height; 16992 int count; /* if non-zero, at least this many more */ 16993 } 16994 16995 struct XGraphicsExposeEvent{ 16996 int type; 16997 arch_ulong serial; /* # of last request processed by server */ 16998 Bool send_event; /* true if this came from a SendEvent request */ 16999 Display *display; /* Display the event was read from */ 17000 Drawable drawable; 17001 int x, y; 17002 int width, height; 17003 int count; /* if non-zero, at least this many more */ 17004 int major_code; /* core is CopyArea or CopyPlane */ 17005 int minor_code; /* not defined in the core */ 17006 } 17007 17008 struct XNoExposeEvent{ 17009 int type; 17010 arch_ulong serial; /* # of last request processed by server */ 17011 Bool send_event; /* true if this came from a SendEvent request */ 17012 Display *display; /* Display the event was read from */ 17013 Drawable drawable; 17014 int major_code; /* core is CopyArea or CopyPlane */ 17015 int minor_code; /* not defined in the core */ 17016 } 17017 17018 struct XVisibilityEvent{ 17019 int type; 17020 arch_ulong serial; /* # of last request processed by server */ 17021 Bool send_event; /* true if this came from a SendEvent request */ 17022 Display *display; /* Display the event was read from */ 17023 Window window; 17024 VisibilityNotify state; /* Visibility state */ 17025 } 17026 17027 struct XCreateWindowEvent{ 17028 int type; 17029 arch_ulong serial; /* # of last request processed by server */ 17030 Bool send_event; /* true if this came from a SendEvent request */ 17031 Display *display; /* Display the event was read from */ 17032 Window parent; /* parent of the window */ 17033 Window window; /* window id of window created */ 17034 int x, y; /* window location */ 17035 int width, height; /* size of window */ 17036 int border_width; /* border width */ 17037 Bool override_redirect; /* creation should be overridden */ 17038 } 17039 17040 struct XDestroyWindowEvent 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 } 17049 17050 struct XUnmapEvent 17051 { 17052 int type; 17053 arch_ulong serial; /* # of last request processed by server */ 17054 Bool send_event; /* true if this came from a SendEvent request */ 17055 Display *display; /* Display the event was read from */ 17056 Window event; 17057 Window window; 17058 Bool from_configure; 17059 } 17060 17061 struct XMapEvent 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 Bool override_redirect; /* Boolean, is override set... */ 17070 } 17071 17072 struct XMapRequestEvent 17073 { 17074 int type; 17075 arch_ulong serial; /* # of last request processed by server */ 17076 Bool send_event; /* true if this came from a SendEvent request */ 17077 Display *display; /* Display the event was read from */ 17078 Window parent; 17079 Window window; 17080 } 17081 17082 struct XReparentEvent 17083 { 17084 int type; 17085 arch_ulong serial; /* # of last request processed by server */ 17086 Bool send_event; /* true if this came from a SendEvent request */ 17087 Display *display; /* Display the event was read from */ 17088 Window event; 17089 Window window; 17090 Window parent; 17091 int x, y; 17092 Bool override_redirect; 17093 } 17094 17095 struct XConfigureEvent 17096 { 17097 int type; 17098 arch_ulong serial; /* # of last request processed by server */ 17099 Bool send_event; /* true if this came from a SendEvent request */ 17100 Display *display; /* Display the event was read from */ 17101 Window event; 17102 Window window; 17103 int x, y; 17104 int width, height; 17105 int border_width; 17106 Window above; 17107 Bool override_redirect; 17108 } 17109 17110 struct XGravityEvent 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 event; 17117 Window window; 17118 int x, y; 17119 } 17120 17121 struct XResizeRequestEvent 17122 { 17123 int type; 17124 arch_ulong serial; /* # of last request processed by server */ 17125 Bool send_event; /* true if this came from a SendEvent request */ 17126 Display *display; /* Display the event was read from */ 17127 Window window; 17128 int width, height; 17129 } 17130 17131 struct XConfigureRequestEvent 17132 { 17133 int type; 17134 arch_ulong serial; /* # of last request processed by server */ 17135 Bool send_event; /* true if this came from a SendEvent request */ 17136 Display *display; /* Display the event was read from */ 17137 Window parent; 17138 Window window; 17139 int x, y; 17140 int width, height; 17141 int border_width; 17142 Window above; 17143 WindowStackingMethod detail; /* Above, Below, TopIf, BottomIf, Opposite */ 17144 arch_ulong value_mask; 17145 } 17146 17147 struct XCirculateEvent 17148 { 17149 int type; 17150 arch_ulong serial; /* # of last request processed by server */ 17151 Bool send_event; /* true if this came from a SendEvent request */ 17152 Display *display; /* Display the event was read from */ 17153 Window event; 17154 Window window; 17155 CirculationRequest place; /* PlaceOnTop, PlaceOnBottom */ 17156 } 17157 17158 struct XCirculateRequestEvent 17159 { 17160 int type; 17161 arch_ulong serial; /* # of last request processed by server */ 17162 Bool send_event; /* true if this came from a SendEvent request */ 17163 Display *display; /* Display the event was read from */ 17164 Window parent; 17165 Window window; 17166 CirculationRequest place; /* PlaceOnTop, PlaceOnBottom */ 17167 } 17168 17169 struct XPropertyEvent 17170 { 17171 int type; 17172 arch_ulong serial; /* # of last request processed by server */ 17173 Bool send_event; /* true if this came from a SendEvent request */ 17174 Display *display; /* Display the event was read from */ 17175 Window window; 17176 Atom atom; 17177 Time time; 17178 PropertyNotification state; /* NewValue, Deleted */ 17179 } 17180 17181 struct XSelectionClearEvent 17182 { 17183 int type; 17184 arch_ulong serial; /* # of last request processed by server */ 17185 Bool send_event; /* true if this came from a SendEvent request */ 17186 Display *display; /* Display the event was read from */ 17187 Window window; 17188 Atom selection; 17189 Time time; 17190 } 17191 17192 struct XSelectionRequestEvent 17193 { 17194 int type; 17195 arch_ulong serial; /* # of last request processed by server */ 17196 Bool send_event; /* true if this came from a SendEvent request */ 17197 Display *display; /* Display the event was read from */ 17198 Window owner; 17199 Window requestor; 17200 Atom selection; 17201 Atom target; 17202 Atom property; 17203 Time time; 17204 } 17205 17206 struct XSelectionEvent 17207 { 17208 int type; 17209 arch_ulong serial; /* # of last request processed by server */ 17210 Bool send_event; /* true if this came from a SendEvent request */ 17211 Display *display; /* Display the event was read from */ 17212 Window requestor; 17213 Atom selection; 17214 Atom target; 17215 Atom property; /* ATOM or None */ 17216 Time time; 17217 } 17218 version(X86_64) static assert(XSelectionClearEvent.sizeof == 56); 17219 17220 struct XColormapEvent 17221 { 17222 int type; 17223 arch_ulong serial; /* # of last request processed by server */ 17224 Bool send_event; /* true if this came from a SendEvent request */ 17225 Display *display; /* Display the event was read from */ 17226 Window window; 17227 Colormap colormap; /* COLORMAP or None */ 17228 Bool new_; /* C++ */ 17229 ColorMapNotification state; /* ColormapInstalled, ColormapUninstalled */ 17230 } 17231 version(X86_64) static assert(XColormapEvent.sizeof == 56); 17232 17233 struct XClientMessageEvent 17234 { 17235 int type; 17236 arch_ulong serial; /* # of last request processed by server */ 17237 Bool send_event; /* true if this came from a SendEvent request */ 17238 Display *display; /* Display the event was read from */ 17239 Window window; 17240 Atom message_type; 17241 int format; 17242 union Data{ 17243 byte[20] b; 17244 short[10] s; 17245 arch_ulong[5] l; 17246 } 17247 Data data; 17248 17249 } 17250 version(X86_64) static assert(XClientMessageEvent.sizeof == 96); 17251 17252 struct XMappingEvent 17253 { 17254 int type; 17255 arch_ulong serial; /* # of last request processed by server */ 17256 Bool send_event; /* true if this came from a SendEvent request */ 17257 Display *display; /* Display the event was read from */ 17258 Window window; /* unused */ 17259 MappingType request; /* one of MappingModifier, MappingKeyboard, 17260 MappingPointer */ 17261 int first_keycode; /* first keycode */ 17262 int count; /* defines range of change w. first_keycode*/ 17263 } 17264 17265 struct XErrorEvent 17266 { 17267 int type; 17268 Display *display; /* Display the event was read from */ 17269 XID resourceid; /* resource id */ 17270 arch_ulong serial; /* serial number of failed request */ 17271 ubyte error_code; /* error code of failed request */ 17272 ubyte request_code; /* Major op-code of failed request */ 17273 ubyte minor_code; /* Minor op-code of failed request */ 17274 } 17275 17276 struct XAnyEvent 17277 { 17278 int type; 17279 arch_ulong serial; /* # of last request processed by server */ 17280 Bool send_event; /* true if this came from a SendEvent request */ 17281 Display *display;/* Display the event was read from */ 17282 Window window; /* window on which event was requested in event mask */ 17283 } 17284 17285 union XEvent{ 17286 int type; /* must not be changed; first element */ 17287 XAnyEvent xany; 17288 XKeyEvent xkey; 17289 XButtonEvent xbutton; 17290 XMotionEvent xmotion; 17291 XCrossingEvent xcrossing; 17292 XFocusChangeEvent xfocus; 17293 XExposeEvent xexpose; 17294 XGraphicsExposeEvent xgraphicsexpose; 17295 XNoExposeEvent xnoexpose; 17296 XVisibilityEvent xvisibility; 17297 XCreateWindowEvent xcreatewindow; 17298 XDestroyWindowEvent xdestroywindow; 17299 XUnmapEvent xunmap; 17300 XMapEvent xmap; 17301 XMapRequestEvent xmaprequest; 17302 XReparentEvent xreparent; 17303 XConfigureEvent xconfigure; 17304 XGravityEvent xgravity; 17305 XResizeRequestEvent xresizerequest; 17306 XConfigureRequestEvent xconfigurerequest; 17307 XCirculateEvent xcirculate; 17308 XCirculateRequestEvent xcirculaterequest; 17309 XPropertyEvent xproperty; 17310 XSelectionClearEvent xselectionclear; 17311 XSelectionRequestEvent xselectionrequest; 17312 XSelectionEvent xselection; 17313 XColormapEvent xcolormap; 17314 XClientMessageEvent xclient; 17315 XMappingEvent xmapping; 17316 XErrorEvent xerror; 17317 XKeymapEvent xkeymap; 17318 arch_ulong[24] pad; 17319 } 17320 17321 17322 struct Display { 17323 XExtData *ext_data; /* hook for extension to hang data */ 17324 _XPrivate *private1; 17325 int fd; /* Network socket. */ 17326 int private2; 17327 int proto_major_version;/* major version of server's X protocol */ 17328 int proto_minor_version;/* minor version of servers X protocol */ 17329 char *vendor; /* vendor of the server hardware */ 17330 XID private3; 17331 XID private4; 17332 XID private5; 17333 int private6; 17334 XID function(Display*)resource_alloc;/* allocator function */ 17335 ByteOrder byte_order; /* screen byte order, LSBFirst, MSBFirst */ 17336 int bitmap_unit; /* padding and data requirements */ 17337 int bitmap_pad; /* padding requirements on bitmaps */ 17338 ByteOrder bitmap_bit_order; /* LeastSignificant or MostSignificant */ 17339 int nformats; /* number of pixmap formats in list */ 17340 ScreenFormat *pixmap_format; /* pixmap format list */ 17341 int private8; 17342 int release; /* release of the server */ 17343 _XPrivate *private9; 17344 _XPrivate *private10; 17345 int qlen; /* Length of input event queue */ 17346 arch_ulong last_request_read; /* seq number of last event read */ 17347 arch_ulong request; /* sequence number of last request. */ 17348 XPointer private11; 17349 XPointer private12; 17350 XPointer private13; 17351 XPointer private14; 17352 uint max_request_size; /* maximum number 32 bit words in request*/ 17353 _XrmHashBucketRec *db; 17354 int function (Display*)private15; 17355 char *display_name; /* "host:display" string used on this connect*/ 17356 int default_screen; /* default screen for operations */ 17357 int nscreens; /* number of screens on this server*/ 17358 Screen *screens; /* pointer to list of screens */ 17359 arch_ulong motion_buffer; /* size of motion buffer */ 17360 arch_ulong private16; 17361 int min_keycode; /* minimum defined keycode */ 17362 int max_keycode; /* maximum defined keycode */ 17363 XPointer private17; 17364 XPointer private18; 17365 int private19; 17366 byte *xdefaults; /* contents of defaults from server */ 17367 /* there is more to this structure, but it is private to Xlib */ 17368 } 17369 17370 // I got these numbers from a C program as a sanity test 17371 version(X86_64) { 17372 static assert(Display.sizeof == 296); 17373 static assert(XPointer.sizeof == 8); 17374 static assert(XErrorEvent.sizeof == 40); 17375 static assert(XAnyEvent.sizeof == 40); 17376 static assert(XMappingEvent.sizeof == 56); 17377 static assert(XEvent.sizeof == 192); 17378 } else version (AArch64) { 17379 // omit check for aarch64 17380 } else { 17381 static assert(Display.sizeof == 176); 17382 static assert(XPointer.sizeof == 4); 17383 static assert(XEvent.sizeof == 96); 17384 } 17385 17386 struct Depth 17387 { 17388 int depth; /* this depth (Z) of the depth */ 17389 int nvisuals; /* number of Visual types at this depth */ 17390 Visual *visuals; /* list of visuals possible at this depth */ 17391 } 17392 17393 alias void* GC; 17394 alias c_ulong VisualID; 17395 alias XID Colormap; 17396 alias XID Cursor; 17397 alias XID KeySym; 17398 alias uint KeyCode; 17399 enum None = 0; 17400 } 17401 17402 version(without_opengl) {} 17403 else { 17404 extern(C) nothrow @nogc { 17405 17406 17407 static if(!SdpyIsUsingIVGLBinds) { 17408 enum GLX_USE_GL= 1; /* support GLX rendering */ 17409 enum GLX_BUFFER_SIZE= 2; /* depth of the color buffer */ 17410 enum GLX_LEVEL= 3; /* level in plane stacking */ 17411 enum GLX_RGBA= 4; /* true if RGBA mode */ 17412 enum GLX_DOUBLEBUFFER= 5; /* double buffering supported */ 17413 enum GLX_STEREO= 6; /* stereo buffering supported */ 17414 enum GLX_AUX_BUFFERS= 7; /* number of aux buffers */ 17415 enum GLX_RED_SIZE= 8; /* number of red component bits */ 17416 enum GLX_GREEN_SIZE= 9; /* number of green component bits */ 17417 enum GLX_BLUE_SIZE= 10; /* number of blue component bits */ 17418 enum GLX_ALPHA_SIZE= 11; /* number of alpha component bits */ 17419 enum GLX_DEPTH_SIZE= 12; /* number of depth bits */ 17420 enum GLX_STENCIL_SIZE= 13; /* number of stencil bits */ 17421 enum GLX_ACCUM_RED_SIZE= 14; /* number of red accum bits */ 17422 enum GLX_ACCUM_GREEN_SIZE= 15; /* number of green accum bits */ 17423 enum GLX_ACCUM_BLUE_SIZE= 16; /* number of blue accum bits */ 17424 enum GLX_ACCUM_ALPHA_SIZE= 17; /* number of alpha accum bits */ 17425 17426 17427 //XVisualInfo* glXChooseVisual(Display *dpy, int screen, in int *attrib_list); 17428 17429 17430 17431 enum GL_TRUE = 1; 17432 enum GL_FALSE = 0; 17433 } 17434 17435 alias XID GLXContextID; 17436 alias XID GLXPixmap; 17437 alias XID GLXDrawable; 17438 alias XID GLXPbuffer; 17439 alias XID GLXWindow; 17440 alias XID GLXFBConfigID; 17441 alias void* GLXContext; 17442 17443 } 17444 } 17445 17446 enum AllocNone = 0; 17447 17448 extern(C) { 17449 /* WARNING, this type not in Xlib spec */ 17450 extern(C) alias XIOErrorHandler = int function (Display* display); 17451 } 17452 17453 extern(C) nothrow 17454 alias XErrorHandler = int function(Display*, XErrorEvent*); 17455 17456 extern(C) nothrow @nogc { 17457 struct Screen{ 17458 XExtData *ext_data; /* hook for extension to hang data */ 17459 Display *display; /* back pointer to display structure */ 17460 Window root; /* Root window id. */ 17461 int width, height; /* width and height of screen */ 17462 int mwidth, mheight; /* width and height of in millimeters */ 17463 int ndepths; /* number of depths possible */ 17464 Depth *depths; /* list of allowable depths on the screen */ 17465 int root_depth; /* bits per pixel */ 17466 Visual *root_visual; /* root visual */ 17467 GC default_gc; /* GC for the root root visual */ 17468 Colormap cmap; /* default color map */ 17469 uint white_pixel; 17470 uint black_pixel; /* White and Black pixel values */ 17471 int max_maps, min_maps; /* max and min color maps */ 17472 int backing_store; /* Never, WhenMapped, Always */ 17473 bool save_unders; 17474 int root_input_mask; /* initial root input mask */ 17475 } 17476 17477 struct Visual 17478 { 17479 XExtData *ext_data; /* hook for extension to hang data */ 17480 VisualID visualid; /* visual id of this visual */ 17481 int class_; /* class of screen (monochrome, etc.) */ 17482 c_ulong red_mask, green_mask, blue_mask; /* mask values */ 17483 int bits_per_rgb; /* log base 2 of distinct color values */ 17484 int map_entries; /* color map entries */ 17485 } 17486 17487 alias Display* _XPrivDisplay; 17488 17489 extern(D) Screen* ScreenOfDisplay(Display* dpy, int scr) { 17490 assert(dpy !is null); 17491 return &dpy.screens[scr]; 17492 } 17493 17494 extern(D) Window RootWindow(Display *dpy,int scr) { 17495 return ScreenOfDisplay(dpy,scr).root; 17496 } 17497 17498 struct XWMHints { 17499 arch_long flags; 17500 Bool input; 17501 int initial_state; 17502 Pixmap icon_pixmap; 17503 Window icon_window; 17504 int icon_x, icon_y; 17505 Pixmap icon_mask; 17506 XID window_group; 17507 } 17508 17509 struct XClassHint { 17510 char* res_name; 17511 char* res_class; 17512 } 17513 17514 extern(D) int DefaultScreen(Display *dpy) { 17515 return dpy.default_screen; 17516 } 17517 17518 extern(D) int DefaultDepth(Display* dpy, int scr) { return ScreenOfDisplay(dpy, scr).root_depth; } 17519 extern(D) int DisplayWidth(Display* dpy, int scr) { return ScreenOfDisplay(dpy, scr).width; } 17520 extern(D) int DisplayHeight(Display* dpy, int scr) { return ScreenOfDisplay(dpy, scr).height; } 17521 extern(D) int DisplayWidthMM(Display* dpy, int scr) { return ScreenOfDisplay(dpy, scr).mwidth; } 17522 extern(D) int DisplayHeightMM(Display* dpy, int scr) { return ScreenOfDisplay(dpy, scr).mheight; } 17523 extern(D) auto DefaultColormap(Display* dpy, int scr) { return ScreenOfDisplay(dpy, scr).cmap; } 17524 17525 extern(D) int ConnectionNumber(Display* dpy) { return dpy.fd; } 17526 17527 enum int AnyPropertyType = 0; 17528 enum int Success = 0; 17529 17530 enum int RevertToNone = None; 17531 enum int PointerRoot = 1; 17532 enum Time CurrentTime = 0; 17533 enum int RevertToPointerRoot = PointerRoot; 17534 enum int RevertToParent = 2; 17535 17536 extern(D) int DefaultDepthOfDisplay(Display* dpy) { 17537 return ScreenOfDisplay(dpy, DefaultScreen(dpy)).root_depth; 17538 } 17539 17540 extern(D) Visual* DefaultVisual(Display *dpy,int scr) { 17541 return ScreenOfDisplay(dpy,scr).root_visual; 17542 } 17543 17544 extern(D) GC DefaultGC(Display *dpy,int scr) { 17545 return ScreenOfDisplay(dpy,scr).default_gc; 17546 } 17547 17548 extern(D) uint BlackPixel(Display *dpy,int scr) { 17549 return ScreenOfDisplay(dpy,scr).black_pixel; 17550 } 17551 17552 extern(D) uint WhitePixel(Display *dpy,int scr) { 17553 return ScreenOfDisplay(dpy,scr).white_pixel; 17554 } 17555 17556 alias void* XFontSet; // i think 17557 struct XmbTextItem { 17558 char* chars; 17559 int nchars; 17560 int delta; 17561 XFontSet font_set; 17562 } 17563 17564 struct XTextItem { 17565 char* chars; 17566 int nchars; 17567 int delta; 17568 Font font; 17569 } 17570 17571 enum { 17572 GXclear = 0x0, /* 0 */ 17573 GXand = 0x1, /* src AND dst */ 17574 GXandReverse = 0x2, /* src AND NOT dst */ 17575 GXcopy = 0x3, /* src */ 17576 GXandInverted = 0x4, /* NOT src AND dst */ 17577 GXnoop = 0x5, /* dst */ 17578 GXxor = 0x6, /* src XOR dst */ 17579 GXor = 0x7, /* src OR dst */ 17580 GXnor = 0x8, /* NOT src AND NOT dst */ 17581 GXequiv = 0x9, /* NOT src XOR dst */ 17582 GXinvert = 0xa, /* NOT dst */ 17583 GXorReverse = 0xb, /* src OR NOT dst */ 17584 GXcopyInverted = 0xc, /* NOT src */ 17585 GXorInverted = 0xd, /* NOT src OR dst */ 17586 GXnand = 0xe, /* NOT src OR NOT dst */ 17587 GXset = 0xf, /* 1 */ 17588 } 17589 enum QueueMode : int { 17590 QueuedAlready, 17591 QueuedAfterReading, 17592 QueuedAfterFlush 17593 } 17594 17595 enum GrabMode { GrabModeSync = 0, GrabModeAsync = 1 } 17596 17597 struct XPoint { 17598 short x; 17599 short y; 17600 } 17601 17602 enum CoordMode:int { 17603 CoordModeOrigin = 0, 17604 CoordModePrevious = 1 17605 } 17606 17607 enum PolygonShape:int { 17608 Complex = 0, 17609 Nonconvex = 1, 17610 Convex = 2 17611 } 17612 17613 struct XTextProperty { 17614 const(char)* value; /* same as Property routines */ 17615 Atom encoding; /* prop type */ 17616 int format; /* prop data format: 8, 16, or 32 */ 17617 arch_ulong nitems; /* number of data items in value */ 17618 } 17619 17620 version( X86_64 ) { 17621 static assert(XTextProperty.sizeof == 32); 17622 } 17623 17624 17625 struct XGCValues { 17626 int function_; /* logical operation */ 17627 arch_ulong plane_mask;/* plane mask */ 17628 arch_ulong foreground;/* foreground pixel */ 17629 arch_ulong background;/* background pixel */ 17630 int line_width; /* line width */ 17631 int line_style; /* LineSolid, LineOnOffDash, LineDoubleDash */ 17632 int cap_style; /* CapNotLast, CapButt, 17633 CapRound, CapProjecting */ 17634 int join_style; /* JoinMiter, JoinRound, JoinBevel */ 17635 int fill_style; /* FillSolid, FillTiled, 17636 FillStippled, FillOpaeueStippled */ 17637 int fill_rule; /* EvenOddRule, WindingRule */ 17638 int arc_mode; /* ArcChord, ArcPieSlice */ 17639 Pixmap tile; /* tile pixmap for tiling operations */ 17640 Pixmap stipple; /* stipple 1 plane pixmap for stipping */ 17641 int ts_x_origin; /* offset for tile or stipple operations */ 17642 int ts_y_origin; 17643 Font font; /* default text font for text operations */ 17644 int subwindow_mode; /* ClipByChildren, IncludeInferiors */ 17645 Bool graphics_exposures;/* boolean, should exposures be generated */ 17646 int clip_x_origin; /* origin for clipping */ 17647 int clip_y_origin; 17648 Pixmap clip_mask; /* bitmap clipping; other calls for rects */ 17649 int dash_offset; /* patterned/dashed line information */ 17650 char dashes; 17651 } 17652 17653 struct XColor { 17654 arch_ulong pixel; 17655 ushort red, green, blue; 17656 byte flags; 17657 byte pad; 17658 } 17659 17660 struct XRectangle { 17661 short x; 17662 short y; 17663 ushort width; 17664 ushort height; 17665 } 17666 17667 enum ClipByChildren = 0; 17668 enum IncludeInferiors = 1; 17669 17670 enum Atom XA_PRIMARY = 1; 17671 enum Atom XA_SECONDARY = 2; 17672 enum Atom XA_STRING = 31; 17673 enum Atom XA_CARDINAL = 6; 17674 enum Atom XA_WM_NAME = 39; 17675 enum Atom XA_ATOM = 4; 17676 enum Atom XA_WINDOW = 33; 17677 enum Atom XA_WM_HINTS = 35; 17678 enum int PropModeAppend = 2; 17679 enum int PropModeReplace = 0; 17680 enum int PropModePrepend = 1; 17681 17682 enum int CopyFromParent = 0; 17683 enum int InputOutput = 1; 17684 17685 // XWMHints 17686 enum InputHint = 1 << 0; 17687 enum StateHint = 1 << 1; 17688 enum IconPixmapHint = (1L << 2); 17689 enum IconWindowHint = (1L << 3); 17690 enum IconPositionHint = (1L << 4); 17691 enum IconMaskHint = (1L << 5); 17692 enum WindowGroupHint = (1L << 6); 17693 enum AllHints = (InputHint|StateHint|IconPixmapHint|IconWindowHint|IconPositionHint|IconMaskHint|WindowGroupHint); 17694 enum XUrgencyHint = (1L << 8); 17695 17696 // GC Components 17697 enum GCFunction = (1L<<0); 17698 enum GCPlaneMask = (1L<<1); 17699 enum GCForeground = (1L<<2); 17700 enum GCBackground = (1L<<3); 17701 enum GCLineWidth = (1L<<4); 17702 enum GCLineStyle = (1L<<5); 17703 enum GCCapStyle = (1L<<6); 17704 enum GCJoinStyle = (1L<<7); 17705 enum GCFillStyle = (1L<<8); 17706 enum GCFillRule = (1L<<9); 17707 enum GCTile = (1L<<10); 17708 enum GCStipple = (1L<<11); 17709 enum GCTileStipXOrigin = (1L<<12); 17710 enum GCTileStipYOrigin = (1L<<13); 17711 enum GCFont = (1L<<14); 17712 enum GCSubwindowMode = (1L<<15); 17713 enum GCGraphicsExposures= (1L<<16); 17714 enum GCClipXOrigin = (1L<<17); 17715 enum GCClipYOrigin = (1L<<18); 17716 enum GCClipMask = (1L<<19); 17717 enum GCDashOffset = (1L<<20); 17718 enum GCDashList = (1L<<21); 17719 enum GCArcMode = (1L<<22); 17720 enum GCLastBit = 22; 17721 17722 17723 enum int WithdrawnState = 0; 17724 enum int NormalState = 1; 17725 enum int IconicState = 3; 17726 17727 } 17728 } else version (OSXCocoa) { 17729 17730 /+ 17731 DON'T FORGET TO MARK THE CLASSES `extern`!! can cause "unrecognized selector sent to class" errors if you do. 17732 +/ 17733 17734 private __gshared AppDelegate globalAppDelegate; 17735 17736 extern(Objective-C) 17737 class AppDelegate : NSObject, NSApplicationDelegate { 17738 override static AppDelegate alloc() @selector("alloc"); 17739 17740 17741 void sdpyCustomEventWakeup(NSid arg) @selector("sdpyCustomEventWakeup:") { 17742 SimpleWindow.processAllCustomEvents(); 17743 } 17744 17745 override void applicationWillFinishLaunching(NSNotification notification) @selector("applicationWillFinishLaunching:") { 17746 immutable style = NSWindowStyleMask.resizable | 17747 NSWindowStyleMask.closable | 17748 NSWindowStyleMask.miniaturizable | 17749 NSWindowStyleMask.titled; 17750 17751 NSMenu mainMenu = NSMenu.alloc.init(MacString("Main").borrow); 17752 17753 { 17754 auto item = mainMenu.addItem(MacString("Test").borrow, null, MacString("").borrow); 17755 auto menu = NSMenu.alloc.init(MacString("Test2").borrow); 17756 mainMenu.setSubmenu(menu, item); 17757 17758 auto newItem = menu.addItem(MacString("Quit").borrow, sel_registerName("terminate:"), MacString("q").borrow); 17759 newItem.target = NSApp; 17760 auto newItem2 = menu.addItem(MacString("Disabled").borrow, sel_registerName("doesnotexist:"), MacString("x").borrow); 17761 newItem2.target = NSApp; 17762 } 17763 17764 { 17765 auto item = mainMenu.addItem(MacString("Test3").borrow, null, MacString("").borrow); 17766 auto menu = NSMenu.alloc.init(MacString("Test4").borrow); // this is the title actually used 17767 mainMenu.setSubmenu(menu, item); 17768 17769 auto newItem = menu.addItem(MacString("Quit2").borrow, sel_registerName("stop:"), MacString("s").borrow); 17770 menu.addItem(MacString("Pulse").borrow, sel_registerName("simpledisplay_pulse:"), MacString("p").borrow); 17771 } 17772 17773 17774 NSApp.menu = mainMenu; 17775 17776 17777 // auto controller = ViewController.alloc.init; 17778 17779 // auto timer = NSTimer.schedule(1.0, cast(NSid) view, sel_registerName("simpledisplay_pulse:"), null, true); 17780 17781 /+ 17782 this.window = window; 17783 this.controller = controller; 17784 +/ 17785 } 17786 17787 override void applicationDidFinishLaunching(NSNotification notification) @selector("applicationDidFinishLaunching:") { 17788 NSApplication.shared_.activateIgnoringOtherApps(true); 17789 } 17790 override bool applicationShouldTerminateAfterLastWindowClosed(NSNotification notification) @selector("applicationShouldTerminateAfterLastWindowClosed:") { 17791 return true; 17792 } 17793 } 17794 17795 extern(Objective-C) 17796 class SDWindowDelegate : NSObject, NSWindowDelegate { 17797 override static SDWindowDelegate alloc() @selector("alloc"); 17798 override SDWindowDelegate init() @selector("init"); 17799 17800 SimpleWindow simpleWindow; 17801 17802 override NSSize windowWillResize(NSWindow sender, NSSize frameSize) @selector("windowWillResize:toSize:") { 17803 if(simpleWindow.windowResized) { 17804 // FIXME: automaticallyScaleIfPossible behaviors 17805 17806 simpleWindow._width = cast(int) frameSize.width; 17807 simpleWindow._height = cast(int) frameSize.height; 17808 17809 simpleWindow.view.setFrameSize(frameSize); 17810 17811 /+ 17812 auto size = simpleWindow.view.frame.size; 17813 writeln(cast(int) size.width, "x", cast(int) size.height); 17814 +/ 17815 17816 simpleWindow.createNewDrawingContext(simpleWindow._width, simpleWindow._height); 17817 17818 simpleWindow.windowResized(simpleWindow._width, simpleWindow._height); 17819 17820 // simpleWindow.view.setNeedsDisplay(true); 17821 } 17822 17823 return frameSize; 17824 } 17825 17826 /+ 17827 override void windowDidResize(NSNotification notification) @selector("windowDidResize:") { 17828 if(simpleWindow.windowResized) { 17829 auto window = simpleWindow.window; 17830 auto rect = window.contentRectForFrameRect(window.frame); 17831 import std.stdio; writeln(window.frame.size); 17832 simpleWindow.windowResized(cast(int) rect.size.width, cast(int) rect.size.height); 17833 } 17834 } 17835 +/ 17836 } 17837 17838 extern(Objective-C) 17839 class SDGraphicsView : NSView { 17840 SimpleWindow simpleWindow; 17841 17842 override static SDGraphicsView alloc() @selector("alloc"); 17843 override SDGraphicsView init() @selector("init") { 17844 super.init(); 17845 return this; 17846 } 17847 17848 override void drawRect(NSRect rect) @selector("drawRect:") { 17849 auto curCtx = NSGraphicsContext.currentContext.graphicsPort; 17850 auto cgImage = CGBitmapContextCreateImage(simpleWindow.drawingContext); 17851 auto size = CGSize(CGBitmapContextGetWidth(simpleWindow.drawingContext), CGBitmapContextGetHeight(simpleWindow.drawingContext)); 17852 CGContextDrawImage(curCtx, CGRect(CGPoint(0, 0), size), cgImage); 17853 CGImageRelease(cgImage); 17854 } 17855 17856 private void mouseHelper(NSEvent event, MouseEventType type, MouseButton button) { 17857 MouseEvent me; 17858 me.type = type; 17859 17860 auto pos = event.locationInWindow; 17861 17862 me.x = cast(int) pos.x; 17863 me.y = cast(int) (simpleWindow.height - pos.y); 17864 17865 me.dx = 0; // FIXME 17866 me.dy = 0; // FIXME 17867 17868 me.button = button; 17869 me.modifierState = cast(uint) event.modifierFlags; 17870 me.window = simpleWindow; 17871 17872 me.doubleClick = false; 17873 17874 if(simpleWindow && simpleWindow.handleMouseEvent) 17875 simpleWindow.handleMouseEvent(me); 17876 } 17877 17878 override void mouseDown(NSEvent event) @selector("mouseDown:") { 17879 // writeln(event.pressedMouseButtons); 17880 17881 mouseHelper(event, MouseEventType.buttonPressed, MouseButton.left); 17882 } 17883 override void mouseDragged(NSEvent event) @selector("mouseDragged:") { 17884 mouseHelper(event, MouseEventType.motion, MouseButton.left); 17885 } 17886 override void mouseUp(NSEvent event) @selector("mouseUp:") { 17887 mouseHelper(event, MouseEventType.buttonReleased, MouseButton.left); 17888 } 17889 override void mouseMoved(NSEvent event) @selector("mouseMoved:") { 17890 mouseHelper(event, MouseEventType.motion, MouseButton.left); // button wrong prolly 17891 } 17892 /+ 17893 // FIXME 17894 override void mouseEntered(NSEvent event) @selector("mouseEntered:") { 17895 mouseHelper(event, MouseEventType.buttonPressed, MouseButton.left); 17896 } 17897 override void mouseExited(NSEvent event) @selector("mouseExited:") { 17898 mouseHelper(event, MouseEventType.buttonPressed, MouseButton.left); 17899 } 17900 +/ 17901 17902 override void rightMouseDown(NSEvent event) @selector("rightMouseDown:") { 17903 mouseHelper(event, MouseEventType.buttonPressed, MouseButton.right); 17904 } 17905 override void rightMouseDragged(NSEvent event) @selector("rightMouseDragged:") { 17906 mouseHelper(event, MouseEventType.motion, MouseButton.right); 17907 } 17908 override void rightMouseUp(NSEvent event) @selector("rightMouseUp:") { 17909 mouseHelper(event, MouseEventType.buttonReleased, MouseButton.right); 17910 } 17911 17912 override void otherMouseDown(NSEvent event) @selector("otherMouseDown:") { 17913 mouseHelper(event, MouseEventType.buttonPressed, MouseButton.middle); 17914 } 17915 override void otherMouseDragged(NSEvent event) @selector("otherMouseDragged:") { 17916 mouseHelper(event, MouseEventType.motion, MouseButton.middle); 17917 } 17918 override void otherMouseUp(NSEvent event) @selector("otherMouseUp:") { 17919 mouseHelper(event, MouseEventType.buttonReleased, MouseButton.middle); 17920 } 17921 17922 override void scrollWheel(NSEvent event) @selector("scrollWheel:") { 17923 import std.stdio; 17924 writeln(event.deltaY); 17925 } 17926 17927 override void keyDown(NSEvent event) @selector("keyDown:") { 17928 // the event may have multiple characters, and we send them all at once. 17929 if (simpleWindow.handleCharEvent) { 17930 auto chars = DeifiedNSString(event.characters); 17931 foreach (dchar dc; chars.str) 17932 simpleWindow.handleCharEvent(dc); 17933 } 17934 17935 keyHelper(event, true); 17936 } 17937 17938 override void keyUp(NSEvent event) @selector("keyUp:") { 17939 keyHelper(event, false); 17940 } 17941 17942 private void keyHelper(NSEvent event, bool pressed) { 17943 if(simpleWindow.handleKeyEvent) { 17944 KeyEvent ev; 17945 ev.key = cast(Key) event.keyCode;// (event.specialKey ? event.specialKey : event.keyCode); 17946 ev.pressed = pressed; 17947 ev.hardwareCode = cast(ubyte) event.keyCode; 17948 ev.modifierState = cast(uint) event.modifierFlags; 17949 ev.window = simpleWindow; 17950 17951 simpleWindow.handleKeyEvent(ev); 17952 } 17953 } 17954 17955 override bool isFlipped() @selector("isFlipped") { 17956 return true; 17957 } 17958 override bool acceptsFirstResponder() @selector("acceptsFirstResponder") { 17959 return true; 17960 } 17961 17962 void simpledisplay_pulse(NSTimer timer) @selector("simpledisplay_pulse:") { 17963 if(simpleWindow && simpleWindow.handlePulse) 17964 simpleWindow.handlePulse(); 17965 /+ 17966 setNeedsDisplay = true; 17967 +/ 17968 } 17969 } 17970 17971 private: 17972 alias const(void)* CFStringRef; 17973 alias const(void)* CFAllocatorRef; 17974 alias const(void)* CFTypeRef; 17975 alias const(void)* CGColorSpaceRef; 17976 alias const(void)* CGImageRef; 17977 alias ulong CGBitmapInfo; 17978 alias NSGraphicsContext CGContextRef; 17979 17980 alias NSPoint CGPoint; 17981 alias NSSize CGSize; 17982 alias NSRect CGRect; 17983 17984 struct CGAffineTransform { 17985 double a, b, c, d, tx, ty; 17986 } 17987 17988 enum NSApplicationActivationPolicyRegular = 0; 17989 enum NSBackingStoreBuffered = 2; 17990 enum kCFStringEncodingUTF8 = 0x08000100; 17991 17992 enum : size_t { 17993 NSBorderlessWindowMask = 0, 17994 NSTitledWindowMask = 1 << 0, 17995 NSClosableWindowMask = 1 << 1, 17996 NSMiniaturizableWindowMask = 1 << 2, 17997 NSResizableWindowMask = 1 << 3, 17998 NSTexturedBackgroundWindowMask = 1 << 8 17999 } 18000 18001 enum : ulong { 18002 kCGImageAlphaNone, 18003 kCGImageAlphaPremultipliedLast, 18004 kCGImageAlphaPremultipliedFirst, 18005 kCGImageAlphaLast, 18006 kCGImageAlphaFirst, 18007 kCGImageAlphaNoneSkipLast, 18008 kCGImageAlphaNoneSkipFirst 18009 } 18010 enum : ulong { 18011 kCGBitmapAlphaInfoMask = 0x1F, 18012 kCGBitmapFloatComponents = (1 << 8), 18013 kCGBitmapByteOrderMask = 0x7000, 18014 kCGBitmapByteOrderDefault = (0 << 12), 18015 kCGBitmapByteOrder16Little = (1 << 12), 18016 kCGBitmapByteOrder32Little = (2 << 12), 18017 kCGBitmapByteOrder16Big = (3 << 12), 18018 kCGBitmapByteOrder32Big = (4 << 12) 18019 } 18020 enum CGPathDrawingMode { 18021 kCGPathFill, 18022 kCGPathEOFill, 18023 kCGPathStroke, 18024 kCGPathFillStroke, 18025 kCGPathEOFillStroke 18026 } 18027 enum objc_AssociationPolicy : size_t { 18028 OBJC_ASSOCIATION_ASSIGN = 0, 18029 OBJC_ASSOCIATION_RETAIN_NONATOMIC = 1, 18030 OBJC_ASSOCIATION_COPY_NONATOMIC = 3, 18031 OBJC_ASSOCIATION_RETAIN = 0x301, //01401, 18032 OBJC_ASSOCIATION_COPY = 0x303 //01403 18033 } 18034 18035 extern(C) { 18036 CGContextRef CGBitmapContextCreate(void* data, size_t width, size_t height, size_t bitsPerComponent, size_t bytesPerRow, CGColorSpaceRef colorspace, CGBitmapInfo bitmapInfo); 18037 void CGContextRelease(CGContextRef c); 18038 ubyte* CGBitmapContextGetData(CGContextRef c); 18039 CGImageRef CGBitmapContextCreateImage(CGContextRef c); 18040 size_t CGBitmapContextGetWidth(CGContextRef c); 18041 size_t CGBitmapContextGetHeight(CGContextRef c); 18042 18043 CGColorSpaceRef CGColorSpaceCreateDeviceRGB(); 18044 void CGColorSpaceRelease(CGColorSpaceRef cs); 18045 18046 void CGContextSetRGBStrokeColor(CGContextRef c, double red, double green, double blue, double alpha); 18047 void CGContextSetRGBFillColor(CGContextRef c, double red, double green, double blue, double alpha); 18048 void CGContextDrawImage(CGContextRef c, CGRect rect, CGImageRef image); 18049 void CGContextShowTextAtPoint(CGContextRef c, double x, double y, const(char)* str, size_t length); 18050 void CGContextStrokeLineSegments(CGContextRef c, const(CGPoint)* points, size_t count); 18051 18052 void CGContextBeginPath(CGContextRef c); 18053 void CGContextDrawPath(CGContextRef c, CGPathDrawingMode mode); 18054 void CGContextAddEllipseInRect(CGContextRef c, CGRect rect); 18055 void CGContextAddArc(CGContextRef c, double x, double y, double radius, double startAngle, double endAngle, long clockwise); 18056 void CGContextAddRect(CGContextRef c, CGRect rect); 18057 void CGContextAddLines(CGContextRef c, const(CGPoint)* points, size_t count); 18058 void CGContextSaveGState(CGContextRef c); 18059 void CGContextRestoreGState(CGContextRef c); 18060 void CGContextSelectFont(CGContextRef c, const(char)* name, double size, ulong textEncoding); 18061 CGAffineTransform CGContextGetTextMatrix(CGContextRef c); 18062 void CGContextSetTextMatrix(CGContextRef c, CGAffineTransform t); 18063 18064 void CGImageRelease(CGImageRef image); 18065 } 18066 } else static assert(0, "Unsupported operating system"); 18067 18068 18069 version(OSXCocoa) { 18070 // I don't know anything about the Mac, but a couple years ago, KennyTM on the newsgroup wrote this for me 18071 // 18072 // http://forum.dlang.org/thread/innr0v$1deh$1@digitalmars.com?page=4#post-int88l:24uaf:241:40digitalmars.com 18073 // https://github.com/kennytm/simpledisplay.d/blob/osx/simpledisplay.d 18074 // 18075 // and it is about time I merged it in here. It is available with -version=OSXCocoa until someone tests it for me! 18076 // Probably won't even fully compile right now 18077 18078 private enum double PI = 3.14159265358979323; 18079 18080 alias NSWindow NativeWindowHandle; 18081 alias void delegate(NSid) NativeEventHandler; 18082 18083 enum KEY_ESCAPE = 27; 18084 18085 mixin template NativeImageImplementation() { 18086 CGContextRef context; 18087 ubyte* rawData; 18088 18089 final: 18090 18091 void convertToRgbaBytes(ubyte[] where) { 18092 assert(where.length == this.width * this.height * 4); 18093 18094 // if rawData had a length.... 18095 //assert(rawData.length == where.length); 18096 for(long idx = 0; idx < where.length; idx += 4) { 18097 auto alpha = rawData[idx + 3]; 18098 if(alpha == 255) { 18099 where[idx + 0] = rawData[idx + 0]; // r 18100 where[idx + 1] = rawData[idx + 1]; // g 18101 where[idx + 2] = rawData[idx + 2]; // b 18102 where[idx + 3] = rawData[idx + 3]; // a 18103 } else { 18104 where[idx + 0] = cast(ubyte)(rawData[idx + 0] * 255 / alpha); // r 18105 where[idx + 1] = cast(ubyte)(rawData[idx + 1] * 255 / alpha); // g 18106 where[idx + 2] = cast(ubyte)(rawData[idx + 2] * 255 / alpha); // b 18107 where[idx + 3] = rawData[idx + 3]; // a 18108 18109 } 18110 } 18111 } 18112 18113 void setFromRgbaBytes(in ubyte[] where) { 18114 // FIXME: this is probably wrong 18115 assert(where.length == this.width * this.height * 4); 18116 18117 // if rawData had a length.... 18118 //assert(rawData.length == where.length); 18119 for(long idx = 0; idx < where.length; idx += 4) { 18120 auto alpha = where[idx + 3]; 18121 if(alpha == 255) { 18122 rawData[idx + 0] = where[idx + 0]; // r 18123 rawData[idx + 1] = where[idx + 1]; // g 18124 rawData[idx + 2] = where[idx + 2]; // b 18125 rawData[idx + 3] = where[idx + 3]; // a 18126 } else if(alpha == 0) { 18127 rawData[idx + 0] = 0; 18128 rawData[idx + 1] = 0; 18129 rawData[idx + 2] = 0; 18130 rawData[idx + 3] = 0; 18131 } else { 18132 rawData[idx + 0] = cast(ubyte)(where[idx + 0] * 255 / alpha); // r 18133 rawData[idx + 1] = cast(ubyte)(where[idx + 1] * 255 / alpha); // g 18134 rawData[idx + 2] = cast(ubyte)(where[idx + 2] * 255 / alpha); // b 18135 rawData[idx + 3] = where[idx + 3]; // a 18136 } 18137 } 18138 } 18139 18140 18141 void createImage(int width, int height, bool forcexshm=false, bool ignored = false) { 18142 auto colorSpace = CGColorSpaceCreateDeviceRGB(); 18143 context = CGBitmapContextCreate(null, width, height, 8, 4*width, colorSpace, kCGImageAlphaPremultipliedLast |kCGBitmapByteOrder32Big); 18144 CGColorSpaceRelease(colorSpace); 18145 rawData = CGBitmapContextGetData(context); 18146 } 18147 void dispose() { 18148 CGContextRelease(context); 18149 } 18150 18151 void setPixel(int x, int y, Color c) { 18152 auto offset = (y * width + x) * 4; 18153 if (c.a == 255) { 18154 rawData[offset + 0] = c.r; 18155 rawData[offset + 1] = c.g; 18156 rawData[offset + 2] = c.b; 18157 rawData[offset + 3] = c.a; 18158 } else { 18159 rawData[offset + 0] = cast(ubyte)(c.r*c.a/255); 18160 rawData[offset + 1] = cast(ubyte)(c.g*c.a/255); 18161 rawData[offset + 2] = cast(ubyte)(c.b*c.a/255); 18162 rawData[offset + 3] = c.a; 18163 } 18164 } 18165 } 18166 18167 mixin template NativeScreenPainterImplementation() { 18168 CGContextRef context; 18169 ubyte[4] _outlineComponents; 18170 NSView view; 18171 18172 void create(PaintingHandle window) { 18173 // this.destiny = window; 18174 if(auto sw = cast(SimpleWindow) this.window) { 18175 context = sw.drawingContext; 18176 view = sw.view; 18177 } else { 18178 throw new NotYetImplementedException(); 18179 } 18180 } 18181 18182 void dispose() { 18183 view.setNeedsDisplay(true); 18184 } 18185 18186 bool manualInvalidations; 18187 void invalidateRect(Rectangle invalidRect) { } 18188 18189 // NotYetImplementedException 18190 Size textSize(in char[] txt) { return Size(32, 16); /*throw new NotYetImplementedException();*/ } 18191 void rasterOp(RasterOp op) {} 18192 Pen _activePen; 18193 Color _fillColor; 18194 Rectangle _clipRectangle; 18195 void setClipRectangle(int, int, int, int) {} 18196 void setFont(OperatingSystemFont) {} 18197 int fontHeight() { return 14; } 18198 18199 // end 18200 18201 void pen(Pen pen) { 18202 _activePen = pen; 18203 auto color = pen.color; // FIXME 18204 double alphaComponent = color.a/255.0f; 18205 CGContextSetRGBStrokeColor(context, 18206 color.r/255.0f, color.g/255.0f, color.b/255.0f, alphaComponent); 18207 18208 if (color.a != 255) { 18209 _outlineComponents[0] = cast(ubyte)(color.r*color.a/255); 18210 _outlineComponents[1] = cast(ubyte)(color.g*color.a/255); 18211 _outlineComponents[2] = cast(ubyte)(color.b*color.a/255); 18212 _outlineComponents[3] = color.a; 18213 } else { 18214 _outlineComponents[0] = color.r; 18215 _outlineComponents[1] = color.g; 18216 _outlineComponents[2] = color.b; 18217 _outlineComponents[3] = color.a; 18218 } 18219 } 18220 18221 @property void fillColor(Color color) { 18222 CGContextSetRGBFillColor(context, 18223 color.r/255.0f, color.g/255.0f, color.b/255.0f, color.a/255.0f); 18224 } 18225 18226 void drawImage(int x, int y, Image image, int ulx, int upy, int width, int height) { 18227 // NotYetImplementedException for upper left/width/height 18228 auto cgImage = CGBitmapContextCreateImage(image.context); 18229 auto size = CGSize(CGBitmapContextGetWidth(image.context), CGBitmapContextGetHeight(image.context)); 18230 CGContextDrawImage(context, CGRect(CGPoint(x, y), size), cgImage); 18231 CGImageRelease(cgImage); 18232 } 18233 18234 void drawPixmap(Sprite s, int x, int y, int ix, int iy, int w, int h) { 18235 // FIXME: is this efficient? 18236 auto cgImage = CGBitmapContextCreateImage(s.handle); 18237 auto size = CGSize(CGBitmapContextGetWidth(s.handle), CGBitmapContextGetHeight(s.handle)); 18238 CGContextDrawImage(context, CGRect(CGPoint(x, y), size), cgImage); 18239 CGImageRelease(cgImage); 18240 } 18241 18242 18243 void drawText(int x, int y, int x2, int y2, in char[] text, uint alignment) { 18244 // FIXME: alignment 18245 if (_outlineComponents[3] != 0) { 18246 CGContextSaveGState(context); 18247 auto invAlpha = 1.0f/_outlineComponents[3]; 18248 CGContextSetRGBFillColor(context, _outlineComponents[0]*invAlpha, 18249 _outlineComponents[1]*invAlpha, 18250 _outlineComponents[2]*invAlpha, 18251 _outlineComponents[3]/255.0f); 18252 CGContextShowTextAtPoint(context, x, y + 12 /* this is cuz this picks baseline but i want bounding box */, text.ptr, text.length); 18253 // auto cfstr = cast(NSid)createCFString(text); 18254 // objc_msgSend(cfstr, sel_registerName("drawAtPoint:withAttributes:"), 18255 // NSPoint(x, y), null); 18256 // CFRelease(cfstr); 18257 CGContextRestoreGState(context); 18258 } 18259 } 18260 18261 void drawPixel(int x, int y) { 18262 auto rawData = CGBitmapContextGetData(context); 18263 auto width = CGBitmapContextGetWidth(context); 18264 auto height = CGBitmapContextGetHeight(context); 18265 auto offset = ((height - y - 1) * width + x) * 4; 18266 rawData[offset .. offset+4] = _outlineComponents; 18267 } 18268 18269 void drawLine(int x1, int y1, int x2, int y2) { 18270 CGPoint[2] linePoints; 18271 linePoints[0] = CGPoint(x1, y1); 18272 linePoints[1] = CGPoint(x2, y2); 18273 CGContextStrokeLineSegments(context, linePoints.ptr, linePoints.length); 18274 } 18275 18276 void drawRectangle(int x, int y, int width, int height) { 18277 CGContextBeginPath(context); 18278 auto rect = CGRect(CGPoint(x, y), CGSize(width, height)); 18279 CGContextAddRect(context, rect); 18280 CGContextDrawPath(context, CGPathDrawingMode.kCGPathFillStroke); 18281 } 18282 18283 void drawEllipse(int x1, int y1, int x2, int y2) { 18284 CGContextBeginPath(context); 18285 auto rect = CGRect(CGPoint(x1, y1), CGSize(x2-x1, y2-y1)); 18286 CGContextAddEllipseInRect(context, rect); 18287 CGContextDrawPath(context, CGPathDrawingMode.kCGPathFillStroke); 18288 } 18289 18290 void drawArc(int x1, int y1, int width, int height, int start, int finish) { 18291 // @@@BUG@@@ Does not support elliptic arc (width != height). 18292 CGContextBeginPath(context); 18293 CGContextAddArc(context, x1+width*0.5f, y1+height*0.5f, width, 18294 start*PI/(180*64), finish*PI/(180*64), 0); 18295 CGContextDrawPath(context, CGPathDrawingMode.kCGPathFillStroke); 18296 } 18297 18298 void drawPolygon(Point[] intPoints) { 18299 CGContextBeginPath(context); 18300 CGPoint[16] pointsBuffer; 18301 CGPoint[] points; 18302 if(intPoints.length <= pointsBuffer.length) 18303 points = pointsBuffer[0 .. intPoints.length]; 18304 else 18305 points = new CGPoint[](intPoints.length); 18306 18307 foreach(idx, pt; intPoints) 18308 points[idx] = CGPoint(pt.x, pt.y); 18309 18310 CGContextAddLines(context, points.ptr, points.length); 18311 CGContextDrawPath(context, CGPathDrawingMode.kCGPathFillStroke); 18312 } 18313 } 18314 18315 private bool appInitialized = false; 18316 void initializeApp() { 18317 if(appInitialized) 18318 return; 18319 synchronized { 18320 if(appInitialized) 18321 return; 18322 18323 auto app = NSApp(); // ensure the is initialized 18324 18325 auto dg = AppDelegate.alloc; 18326 globalAppDelegate = dg; 18327 NSApp.delegate_ = dg; 18328 18329 NSApp.setActivationPolicy(NSApplicationActivationPolicy.regular); 18330 18331 appInitialized = true; 18332 } 18333 } 18334 18335 mixin template NativeSimpleWindowImplementation() { 18336 void createWindow(int width, int height, string title, OpenGlOptions opengl, SimpleWindow parent) { 18337 initializeApp(); 18338 18339 auto contentRect = NSRect(NSPoint(0, 0), NSSize(width, height)); 18340 18341 auto window = NSWindow.alloc.initWithContentRect( 18342 contentRect, 18343 NSWindowStyleMask.resizable | NSWindowStyleMask.closable | NSWindowStyleMask.miniaturizable | NSWindowStyleMask.titled, 18344 NSBackingStoreType.buffered, 18345 true 18346 ); 18347 18348 SimpleWindow.nativeMapping[cast(void*) window] = this; 18349 18350 window.title = MacString(title).borrow; 18351 18352 auto dg = SDWindowDelegate.alloc.init; 18353 dg.simpleWindow = this; 18354 window.delegate_ = dg; 18355 18356 auto view = SDGraphicsView.alloc.init; 18357 assert(view !is null); 18358 window.contentView = view; 18359 this.view = view; 18360 view.simpleWindow = this; 18361 18362 window.center(); 18363 18364 window.makeKeyAndOrderFront(null); 18365 18366 // no need to make a bitmap on mac since everything is double buffered already 18367 18368 // create area to draw on. 18369 createNewDrawingContext(width, height); 18370 18371 window.setBackgroundColor(NSColor.whiteColor); 18372 } 18373 18374 void createNewDrawingContext(int width, int height) { 18375 // FIXME need to preserve info from the old context too i think... maybe. or at least setNeedsDisplay 18376 if(this.drawingContext) 18377 CGContextRelease(this.drawingContext); 18378 auto colorSpace = CGColorSpaceCreateDeviceRGB(); 18379 this.drawingContext = CGBitmapContextCreate(null, width, height, 8, 4*width, colorSpace, kCGImageAlphaPremultipliedLast |kCGBitmapByteOrder32Big); 18380 CGColorSpaceRelease(colorSpace); 18381 CGContextSelectFont(drawingContext, "Lucida Grande", 12.0f, 1); 18382 auto matrix = CGContextGetTextMatrix(drawingContext); 18383 matrix.c = -matrix.c; 18384 matrix.d = -matrix.d; 18385 CGContextSetTextMatrix(drawingContext, matrix); 18386 18387 } 18388 18389 void dispose() { 18390 closeWindow(); 18391 // window.release(); // closing the window does this automatically i think 18392 } 18393 void closeWindow() { 18394 if(timer) 18395 timer.invalidate(); 18396 window.close(); 18397 } 18398 18399 ScreenPainter getPainter(bool manualInvalidations) { 18400 return ScreenPainter(this, this.window, manualInvalidations); 18401 } 18402 18403 NSWindow window; 18404 NSTimer timer; 18405 NSView view; 18406 CGContextRef drawingContext; 18407 } 18408 } 18409 18410 version(without_opengl) {} else 18411 extern(System) nothrow @nogc { 18412 //enum uint GL_VERSION = 0x1F02; 18413 //const(char)* glGetString (/*GLenum*/uint); 18414 version(X11) { 18415 static if (!SdpyIsUsingIVGLBinds) { 18416 18417 enum GLX_X_RENDERABLE = 0x8012; 18418 enum GLX_DRAWABLE_TYPE = 0x8010; 18419 enum GLX_RENDER_TYPE = 0x8011; 18420 enum GLX_X_VISUAL_TYPE = 0x22; 18421 enum GLX_TRUE_COLOR = 0x8002; 18422 enum GLX_WINDOW_BIT = 0x00000001; 18423 enum GLX_RGBA_BIT = 0x00000001; 18424 enum GLX_COLOR_INDEX_BIT = 0x00000002; 18425 enum GLX_SAMPLE_BUFFERS = 0x186a0; 18426 enum GLX_SAMPLES = 0x186a1; 18427 enum GLX_CONTEXT_MAJOR_VERSION_ARB = 0x2091; 18428 enum GLX_CONTEXT_MINOR_VERSION_ARB = 0x2092; 18429 } 18430 18431 // GLX_EXT_swap_control 18432 alias glXSwapIntervalEXT = void function (Display* dpy, /*GLXDrawable*/Drawable drawable, int interval); 18433 private __gshared glXSwapIntervalEXT _glx_swapInterval_fn = null; 18434 18435 //k8: ugly code to prevent warnings when sdpy is compiled into .a 18436 extern(System) { 18437 alias glXCreateContextAttribsARB_fna = GLXContext function (Display *dpy, GLXFBConfig config, GLXContext share_context, /*Bool*/int direct, const(int)* attrib_list); 18438 } 18439 private __gshared /*glXCreateContextAttribsARB_fna*/void* glXCreateContextAttribsARBFn = cast(void*)1; //HACK! 18440 18441 // this made public so we don't have to get it again and again 18442 public bool glXCreateContextAttribsARB_present () { 18443 if (glXCreateContextAttribsARBFn is cast(void*)1) { 18444 // get it 18445 glXCreateContextAttribsARBFn = cast(void*)glbindGetProcAddress("glXCreateContextAttribsARB"); 18446 //{ import core.stdc.stdio; printf("checking glXCreateContextAttribsARB: %shere\n", (glXCreateContextAttribsARBFn !is null ? "".ptr : "not ".ptr)); } 18447 } 18448 return (glXCreateContextAttribsARBFn !is null); 18449 } 18450 18451 // this made public so we don't have to get it again and again 18452 public GLXContext glXCreateContextAttribsARB (Display *dpy, GLXFBConfig config, GLXContext share_context, /*Bool*/int direct, const(int)* attrib_list) { 18453 if (!glXCreateContextAttribsARB_present()) assert(0, "glXCreateContextAttribsARB is not present"); 18454 return (cast(glXCreateContextAttribsARB_fna)glXCreateContextAttribsARBFn)(dpy, config, share_context, direct, attrib_list); 18455 } 18456 18457 // extern(C) private __gshared int function(int) glXSwapIntervalSGI; // seems totally redundant to the tohers 18458 extern(C) private __gshared int function(int) glXSwapIntervalMESA; 18459 18460 void glxSetVSync (Display* dpy, /*GLXDrawable*/Drawable drawable, bool wait) { 18461 if (cast(void*)_glx_swapInterval_fn is cast(void*)1) return; 18462 if (_glx_swapInterval_fn is null) { 18463 _glx_swapInterval_fn = cast(glXSwapIntervalEXT)glXGetProcAddress("glXSwapIntervalEXT"); 18464 if (_glx_swapInterval_fn is null) { 18465 _glx_swapInterval_fn = cast(glXSwapIntervalEXT)1; 18466 return; 18467 } 18468 version(sdddd) { debug writeln("glXSwapIntervalEXT found!"); } 18469 } 18470 18471 if(glXSwapIntervalMESA is null) { 18472 // it seems to require both to actually take effect on many computers 18473 // idk why 18474 glXSwapIntervalMESA = cast(typeof(glXSwapIntervalMESA)) glXGetProcAddress("glXSwapIntervalMESA"); 18475 if(glXSwapIntervalMESA is null) 18476 glXSwapIntervalMESA = cast(typeof(glXSwapIntervalMESA)) 1; 18477 } 18478 18479 if(cast(void*) glXSwapIntervalMESA > cast(void*) 1) 18480 glXSwapIntervalMESA(wait ? 1 : 0); 18481 18482 _glx_swapInterval_fn(dpy, drawable, (wait ? 1 : 0)); 18483 } 18484 } else version(Windows) { 18485 static if (!SdpyIsUsingIVGLBinds) { 18486 enum GL_TRUE = 1; 18487 enum GL_FALSE = 0; 18488 18489 public void* glbindGetProcAddress (const(char)* name) { 18490 void* res = wglGetProcAddress(name); 18491 if (res is null) { 18492 /+ 18493 //{ import core.stdc.stdio; printf("GL: '%s' not found (0)\n", name); } 18494 import core.sys.windows.windef, core.sys.windows.winbase; 18495 __gshared HINSTANCE dll = null; 18496 if (dll is null) { 18497 dll = LoadLibraryA("opengl32.dll"); 18498 if (dll is null) return null; // <32, but idc 18499 } 18500 res = GetProcAddress(dll, name); 18501 +/ 18502 res = GetProcAddress(gl.libHandle, name); 18503 } 18504 //{ import core.stdc.stdio; printf(" GL: '%s' is 0x%08x\n", name, cast(uint)res); } 18505 return res; 18506 } 18507 } 18508 18509 18510 private __gshared extern(System) BOOL function(int) wglSwapIntervalEXT; 18511 void wglSetVSync(bool wait) { 18512 if(wglSwapIntervalEXT is null) { 18513 wglSwapIntervalEXT = cast(typeof(wglSwapIntervalEXT)) wglGetProcAddress("wglSwapIntervalEXT"); 18514 if(wglSwapIntervalEXT is null) 18515 wglSwapIntervalEXT = cast(typeof(wglSwapIntervalEXT)) 1; 18516 } 18517 if(cast(void*) wglSwapIntervalEXT is cast(void*) 1) 18518 return; 18519 18520 wglSwapIntervalEXT(wait ? 1 : 0); 18521 } 18522 18523 enum WGL_CONTEXT_MAJOR_VERSION_ARB = 0x2091; 18524 enum WGL_CONTEXT_MINOR_VERSION_ARB = 0x2092; 18525 enum WGL_CONTEXT_LAYER_PLANE_ARB = 0x2093; 18526 enum WGL_CONTEXT_FLAGS_ARB = 0x2094; 18527 enum WGL_CONTEXT_PROFILE_MASK_ARB = 0x9126; 18528 18529 enum WGL_CONTEXT_DEBUG_BIT_ARB = 0x0001; 18530 enum WGL_CONTEXT_FORWARD_COMPATIBLE_BIT_ARB = 0x0002; 18531 18532 enum WGL_CONTEXT_CORE_PROFILE_BIT_ARB = 0x00000001; 18533 enum WGL_CONTEXT_COMPATIBILITY_PROFILE_BIT_ARB = 0x00000002; 18534 18535 alias wglCreateContextAttribsARB_fna = HGLRC function (HDC hDC, HGLRC hShareContext, const(int)* attribList); 18536 __gshared wglCreateContextAttribsARB_fna wglCreateContextAttribsARB = null; 18537 18538 void wglInitOtherFunctions () { 18539 if (wglCreateContextAttribsARB is null) { 18540 wglCreateContextAttribsARB = cast(wglCreateContextAttribsARB_fna)glbindGetProcAddress("wglCreateContextAttribsARB"); 18541 } 18542 } 18543 } 18544 18545 static if (!SdpyIsUsingIVGLBinds) { 18546 18547 interface GL { 18548 extern(System) @nogc nothrow: 18549 18550 void glGetIntegerv(int, void*); 18551 void glMatrixMode(int); 18552 void glPushMatrix(); 18553 void glLoadIdentity(); 18554 void glOrtho(double, double, double, double, double, double); 18555 void glFrustum(double, double, double, double, double, double); 18556 18557 void glPopMatrix(); 18558 void glEnable(int); 18559 void glDisable(int); 18560 void glClear(int); 18561 void glBegin(int); 18562 void glVertex2f(float, float); 18563 void glVertex3f(float, float, float); 18564 void glEnd(); 18565 void glColor3b(byte, byte, byte); 18566 void glColor3ub(ubyte, ubyte, ubyte); 18567 void glColor4b(byte, byte, byte, byte); 18568 void glColor4ub(ubyte, ubyte, ubyte, ubyte); 18569 void glColor3i(int, int, int); 18570 void glColor3ui(uint, uint, uint); 18571 void glColor4i(int, int, int, int); 18572 void glColor4ui(uint, uint, uint, uint); 18573 void glColor3f(float, float, float); 18574 void glColor4f(float, float, float, float); 18575 void glTranslatef(float, float, float); 18576 void glScalef(float, float, float); 18577 version(X11) { 18578 void glSecondaryColor3b(byte, byte, byte); 18579 void glSecondaryColor3ub(ubyte, ubyte, ubyte); 18580 void glSecondaryColor3i(int, int, int); 18581 void glSecondaryColor3ui(uint, uint, uint); 18582 void glSecondaryColor3f(float, float, float); 18583 } 18584 18585 void glDrawElements(int, int, int, void*); 18586 18587 void glRotatef(float, float, float, float); 18588 18589 uint glGetError(); 18590 18591 void glDeleteTextures(int, uint*); 18592 18593 18594 void glRasterPos2i(int, int); 18595 void glDrawPixels(int, int, uint, uint, void*); 18596 void glClearColor(float, float, float, float); 18597 18598 18599 void glPixelStorei(uint, int); 18600 18601 void glGenTextures(uint, uint*); 18602 void glBindTexture(int, int); 18603 void glTexParameteri(uint, uint, int); 18604 void glTexParameterf(uint/*GLenum*/ target, uint/*GLenum*/ pname, float param); 18605 void glTexImage2D(int, int, int, int, int, int, int, int, scope const void*); 18606 void glTexSubImage2D(uint/*GLenum*/ target, int level, int xoffset, int yoffset, 18607 /*GLsizei*/int width, /*GLsizei*/int height, 18608 uint/*GLenum*/ format, uint/*GLenum*/ type, scope const void* pixels); 18609 void glTexEnvf(uint/*GLenum*/ target, uint/*GLenum*/ pname, float param); 18610 18611 void glLineWidth(int); 18612 18613 18614 void glTexCoord2f(float, float); 18615 void glVertex2i(int, int); 18616 void glBlendFunc (int, int); 18617 void glDepthFunc (int); 18618 void glViewport(int, int, int, int); 18619 18620 void glClearDepth(double); 18621 18622 void glReadBuffer(uint); 18623 void glReadPixels(int, int, int, int, int, int, void*); 18624 18625 void glFlush(); 18626 void glFinish(); 18627 18628 version(Windows) { 18629 BOOL wglCopyContext(HGLRC, HGLRC, UINT); 18630 HGLRC wglCreateContext(HDC); 18631 HGLRC wglCreateLayerContext(HDC, int); 18632 BOOL wglDeleteContext(HGLRC); 18633 BOOL wglDescribeLayerPlane(HDC, int, int, UINT, LPLAYERPLANEDESCRIPTOR); 18634 HGLRC wglGetCurrentContext(); 18635 HDC wglGetCurrentDC(); 18636 int wglGetLayerPaletteEntries(HDC, int, int, int, COLORREF*); 18637 PROC wglGetProcAddress(LPCSTR); 18638 BOOL wglMakeCurrent(HDC, HGLRC); 18639 BOOL wglRealizeLayerPalette(HDC, int, BOOL); 18640 int wglSetLayerPaletteEntries(HDC, int, int, int, const(COLORREF)*); 18641 BOOL wglShareLists(HGLRC, HGLRC); 18642 BOOL wglSwapLayerBuffers(HDC, UINT); 18643 BOOL wglUseFontBitmapsA(HDC, DWORD, DWORD, DWORD); 18644 BOOL wglUseFontBitmapsW(HDC, DWORD, DWORD, DWORD); 18645 BOOL wglUseFontOutlinesA(HDC, DWORD, DWORD, DWORD, FLOAT, FLOAT, int, LPGLYPHMETRICSFLOAT); 18646 BOOL wglUseFontOutlinesW(HDC, DWORD, DWORD, DWORD, FLOAT, FLOAT, int, LPGLYPHMETRICSFLOAT); 18647 } 18648 18649 } 18650 18651 interface GL3 { 18652 extern(System) @nogc nothrow: 18653 18654 void glGenVertexArrays(GLsizei, GLuint*); 18655 void glBindVertexArray(GLuint); 18656 void glDeleteVertexArrays(GLsizei, const(GLuint)*); 18657 void glGenerateMipmap(GLenum); 18658 void glBufferSubData(GLenum, GLintptr, GLsizeiptr, const(GLvoid)*); 18659 void glStencilMask(GLuint); 18660 void glStencilFunc(GLenum, GLint, GLuint); 18661 void glGetShaderInfoLog(GLuint, GLsizei, GLsizei*, GLchar*); 18662 void glGetProgramInfoLog(GLuint, GLsizei, GLsizei*, GLchar*); 18663 GLuint glCreateProgram(); 18664 GLuint glCreateShader(GLenum); 18665 void glShaderSource(GLuint, GLsizei, const(GLchar*)*, const(GLint)*); 18666 void glCompileShader(GLuint); 18667 void glGetShaderiv(GLuint, GLenum, GLint*); 18668 void glAttachShader(GLuint, GLuint); 18669 void glBindAttribLocation(GLuint, GLuint, const(GLchar)*); 18670 void glLinkProgram(GLuint); 18671 void glGetProgramiv(GLuint, GLenum, GLint*); 18672 void glDeleteProgram(GLuint); 18673 void glDeleteShader(GLuint); 18674 GLint glGetUniformLocation(GLuint, const(GLchar)*); 18675 void glGenBuffers(GLsizei, GLuint*); 18676 18677 void glUniform1f(GLint location, GLfloat v0); 18678 void glUniform2f(GLint location, GLfloat v0, GLfloat v1); 18679 void glUniform3f(GLint location, GLfloat v0, GLfloat v1, GLfloat v2); 18680 void glUniform4f(GLint location, GLfloat v0, GLfloat v1, GLfloat v2, GLfloat v3); 18681 void glUniform1i(GLint location, GLint v0); 18682 void glUniform2i(GLint location, GLint v0, GLint v1); 18683 void glUniform3i(GLint location, GLint v0, GLint v1, GLint v2); 18684 void glUniform4i(GLint location, GLint v0, GLint v1, GLint v2, GLint v3); 18685 void glUniform1ui(GLint location, GLuint v0); 18686 void glUniform2ui(GLint location, GLuint v0, GLuint v1); 18687 void glUniform3ui(GLint location, GLuint v0, GLuint v1, GLuint v2); 18688 void glUniform4ui(GLint location, GLuint v0, GLuint v1, GLuint v2, GLuint v3); 18689 void glUniform1fv(GLint location, GLsizei count, const GLfloat *value); 18690 void glUniform2fv(GLint location, GLsizei count, const GLfloat *value); 18691 void glUniform3fv(GLint location, GLsizei count, const GLfloat *value); 18692 void glUniform4fv(GLint location, GLsizei count, const GLfloat *value); 18693 void glUniform1iv(GLint location, GLsizei count, const GLint *value); 18694 void glUniform2iv(GLint location, GLsizei count, const GLint *value); 18695 void glUniform3iv(GLint location, GLsizei count, const GLint *value); 18696 void glUniform4iv(GLint location, GLsizei count, const GLint *value); 18697 void glUniform1uiv(GLint location, GLsizei count, const GLuint *value); 18698 void glUniform2uiv(GLint location, GLsizei count, const GLuint *value); 18699 void glUniform3uiv(GLint location, GLsizei count, const GLuint *value); 18700 void glUniform4uiv(GLint location, GLsizei count, const GLuint *value); 18701 void glUniformMatrix2fv(GLint location, GLsizei count, GLboolean transpose, const GLfloat *value); 18702 void glUniformMatrix3fv(GLint location, GLsizei count, GLboolean transpose, const GLfloat *value); 18703 void glUniformMatrix4fv(GLint location, GLsizei count, GLboolean transpose, const GLfloat *value); 18704 void glUniformMatrix2x3fv(GLint location, GLsizei count, GLboolean transpose, const GLfloat *value); 18705 void glUniformMatrix3x2fv(GLint location, GLsizei count, GLboolean transpose, const GLfloat *value); 18706 void glUniformMatrix2x4fv(GLint location, GLsizei count, GLboolean transpose, const GLfloat *value); 18707 void glUniformMatrix4x2fv(GLint location, GLsizei count, GLboolean transpose, const GLfloat *value); 18708 void glUniformMatrix3x4fv(GLint location, GLsizei count, GLboolean transpose, const GLfloat *value); 18709 void glUniformMatrix4x3fv(GLint location, GLsizei count, GLboolean transpose, const GLfloat *value); 18710 18711 void glColorMask(GLboolean, GLboolean, GLboolean, GLboolean); 18712 void glStencilOpSeparate(GLenum, GLenum, GLenum, GLenum); 18713 void glDrawArrays(GLenum, GLint, GLsizei); 18714 void glStencilOp(GLenum, GLenum, GLenum); 18715 void glUseProgram(GLuint); 18716 void glCullFace(GLenum); 18717 void glFrontFace(GLenum); 18718 void glActiveTexture(GLenum); 18719 void glBindBuffer(GLenum, GLuint); 18720 void glBufferData(GLenum, GLsizeiptr, const(void)*, GLenum); 18721 void glEnableVertexAttribArray(GLuint); 18722 void glVertexAttribPointer(GLuint, GLint, GLenum, GLboolean, GLsizei, const(void)*); 18723 void glUniform1i(GLint, GLint); 18724 void glUniform2fv(GLint, GLsizei, const(GLfloat)*); 18725 void glDisableVertexAttribArray(GLuint); 18726 void glDeleteBuffers(GLsizei, const(GLuint)*); 18727 void glBlendFuncSeparate(GLenum, GLenum, GLenum, GLenum); 18728 void glLogicOp (GLenum opcode); 18729 void glFramebufferTexture2D (GLenum target, GLenum attachment, GLenum textarget, GLuint texture, GLint level); 18730 void glDeleteFramebuffers (GLsizei n, const(GLuint)* framebuffers); 18731 void glGenFramebuffers (GLsizei n, GLuint* framebuffers); 18732 GLenum glCheckFramebufferStatus (GLenum target); 18733 void glBindFramebuffer (GLenum target, GLuint framebuffer); 18734 } 18735 18736 interface GL4 { 18737 extern(System) @nogc nothrow: 18738 18739 void glTextureSubImage2D(uint texture, int level, int xoffset, int yoffset, 18740 /*GLsizei*/int width, /*GLsizei*/int height, 18741 uint/*GLenum*/ format, uint/*GLenum*/ type, scope const void* pixels); 18742 } 18743 18744 interface GLU { 18745 extern(System) @nogc nothrow: 18746 18747 void gluLookAt(double, double, double, double, double, double, double, double, double); 18748 void gluPerspective(double, double, double, double); 18749 18750 char* gluErrorString(uint); 18751 } 18752 18753 18754 enum GL_RED = 0x1903; 18755 enum GL_ALPHA = 0x1906; 18756 18757 enum uint GL_FRONT = 0x0404; 18758 18759 enum uint GL_BLEND = 0x0be2; 18760 enum uint GL_LEQUAL = 0x0203; 18761 18762 18763 enum uint GL_RGB = 0x1907; 18764 enum uint GL_BGRA = 0x80e1; 18765 enum uint GL_RGBA = 0x1908; 18766 enum uint GL_TEXTURE_2D = 0x0DE1; 18767 enum uint GL_TEXTURE_MIN_FILTER = 0x2801; 18768 enum uint GL_NEAREST = 0x2600; 18769 enum uint GL_LINEAR = 0x2601; 18770 enum uint GL_TEXTURE_MAG_FILTER = 0x2800; 18771 enum uint GL_TEXTURE_WRAP_S = 0x2802; 18772 enum uint GL_TEXTURE_WRAP_T = 0x2803; 18773 enum uint GL_REPEAT = 0x2901; 18774 enum uint GL_CLAMP = 0x2900; 18775 enum uint GL_CLAMP_TO_EDGE = 0x812F; 18776 enum uint GL_CLAMP_TO_BORDER = 0x812D; 18777 enum uint GL_DECAL = 0x2101; 18778 enum uint GL_MODULATE = 0x2100; 18779 enum uint GL_TEXTURE_ENV = 0x2300; 18780 enum uint GL_TEXTURE_ENV_MODE = 0x2200; 18781 enum uint GL_REPLACE = 0x1E01; 18782 enum uint GL_LIGHTING = 0x0B50; 18783 enum uint GL_DITHER = 0x0BD0; 18784 18785 enum uint GL_NO_ERROR = 0; 18786 18787 18788 18789 enum int GL_VIEWPORT = 0x0BA2; 18790 enum int GL_MODELVIEW = 0x1700; 18791 enum int GL_TEXTURE = 0x1702; 18792 enum int GL_PROJECTION = 0x1701; 18793 enum int GL_DEPTH_TEST = 0x0B71; 18794 18795 enum int GL_COLOR_BUFFER_BIT = 0x00004000; 18796 enum int GL_ACCUM_BUFFER_BIT = 0x00000200; 18797 enum int GL_DEPTH_BUFFER_BIT = 0x00000100; 18798 enum uint GL_STENCIL_BUFFER_BIT = 0x00000400; 18799 18800 enum int GL_POINTS = 0x0000; 18801 enum int GL_LINES = 0x0001; 18802 enum int GL_LINE_LOOP = 0x0002; 18803 enum int GL_LINE_STRIP = 0x0003; 18804 enum int GL_TRIANGLES = 0x0004; 18805 enum int GL_TRIANGLE_STRIP = 5; 18806 enum int GL_TRIANGLE_FAN = 6; 18807 enum int GL_QUADS = 7; 18808 enum int GL_QUAD_STRIP = 8; 18809 enum int GL_POLYGON = 9; 18810 18811 alias GLvoid = void; 18812 alias GLboolean = ubyte; 18813 alias GLint = int; 18814 alias GLuint = uint; 18815 alias GLenum = uint; 18816 alias GLchar = char; 18817 alias GLsizei = int; 18818 alias GLfloat = float; 18819 alias GLintptr = size_t; 18820 alias GLsizeiptr = ptrdiff_t; 18821 18822 18823 enum uint GL_INVALID_ENUM = 0x0500; 18824 18825 enum uint GL_ZERO = 0; 18826 enum uint GL_ONE = 1; 18827 18828 enum uint GL_BYTE = 0x1400; 18829 enum uint GL_UNSIGNED_BYTE = 0x1401; 18830 enum uint GL_SHORT = 0x1402; 18831 enum uint GL_UNSIGNED_SHORT = 0x1403; 18832 enum uint GL_INT = 0x1404; 18833 enum uint GL_UNSIGNED_INT = 0x1405; 18834 enum uint GL_FLOAT = 0x1406; 18835 enum uint GL_2_BYTES = 0x1407; 18836 enum uint GL_3_BYTES = 0x1408; 18837 enum uint GL_4_BYTES = 0x1409; 18838 enum uint GL_DOUBLE = 0x140A; 18839 18840 enum uint GL_STREAM_DRAW = 0x88E0; 18841 18842 enum uint GL_CCW = 0x0901; 18843 18844 enum uint GL_STENCIL_TEST = 0x0B90; 18845 enum uint GL_SCISSOR_TEST = 0x0C11; 18846 18847 enum uint GL_EQUAL = 0x0202; 18848 enum uint GL_NOTEQUAL = 0x0205; 18849 18850 enum uint GL_ALWAYS = 0x0207; 18851 enum uint GL_KEEP = 0x1E00; 18852 18853 enum uint GL_INCR = 0x1E02; 18854 18855 enum uint GL_INCR_WRAP = 0x8507; 18856 enum uint GL_DECR_WRAP = 0x8508; 18857 18858 enum uint GL_CULL_FACE = 0x0B44; 18859 enum uint GL_BACK = 0x0405; 18860 18861 enum uint GL_FRAGMENT_SHADER = 0x8B30; 18862 enum uint GL_VERTEX_SHADER = 0x8B31; 18863 18864 enum uint GL_COMPILE_STATUS = 0x8B81; 18865 enum uint GL_LINK_STATUS = 0x8B82; 18866 18867 enum uint GL_ELEMENT_ARRAY_BUFFER = 0x8893; 18868 18869 enum uint GL_STATIC_DRAW = 0x88E4; 18870 18871 enum uint GL_UNPACK_ALIGNMENT = 0x0CF5; 18872 enum uint GL_UNPACK_ROW_LENGTH = 0x0CF2; 18873 enum uint GL_UNPACK_SKIP_PIXELS = 0x0CF4; 18874 enum uint GL_UNPACK_SKIP_ROWS = 0x0CF3; 18875 18876 enum uint GL_GENERATE_MIPMAP = 0x8191; 18877 enum uint GL_LINEAR_MIPMAP_LINEAR = 0x2703; 18878 18879 enum uint GL_TEXTURE0 = 0x84C0U; 18880 enum uint GL_TEXTURE1 = 0x84C1U; 18881 18882 enum uint GL_ARRAY_BUFFER = 0x8892; 18883 18884 enum uint GL_SRC_COLOR = 0x0300; 18885 enum uint GL_ONE_MINUS_SRC_COLOR = 0x0301; 18886 enum uint GL_SRC_ALPHA = 0x0302; 18887 enum uint GL_ONE_MINUS_SRC_ALPHA = 0x0303; 18888 enum uint GL_DST_ALPHA = 0x0304; 18889 enum uint GL_ONE_MINUS_DST_ALPHA = 0x0305; 18890 enum uint GL_DST_COLOR = 0x0306; 18891 enum uint GL_ONE_MINUS_DST_COLOR = 0x0307; 18892 enum uint GL_SRC_ALPHA_SATURATE = 0x0308; 18893 18894 enum uint GL_INVERT = 0x150AU; 18895 18896 enum uint GL_DEPTH_STENCIL = 0x84F9U; 18897 enum uint GL_UNSIGNED_INT_24_8 = 0x84FAU; 18898 18899 enum uint GL_FRAMEBUFFER = 0x8D40U; 18900 enum uint GL_COLOR_ATTACHMENT0 = 0x8CE0U; 18901 enum uint GL_DEPTH_STENCIL_ATTACHMENT = 0x821AU; 18902 18903 enum uint GL_FRAMEBUFFER_COMPLETE = 0x8CD5U; 18904 enum uint GL_FRAMEBUFFER_INCOMPLETE_ATTACHMENT = 0x8CD6U; 18905 enum uint GL_FRAMEBUFFER_INCOMPLETE_MISSING_ATTACHMENT = 0x8CD7U; 18906 enum uint GL_FRAMEBUFFER_INCOMPLETE_DIMENSIONS = 0x8CD9U; 18907 enum uint GL_FRAMEBUFFER_UNSUPPORTED = 0x8CDDU; 18908 18909 enum uint GL_COLOR_LOGIC_OP = 0x0BF2U; 18910 enum uint GL_CLEAR = 0x1500U; 18911 enum uint GL_COPY = 0x1503U; 18912 enum uint GL_XOR = 0x1506U; 18913 18914 enum uint GL_FRAMEBUFFER_BINDING = 0x8CA6U; 18915 18916 enum uint GL_TEXTURE_LOD_BIAS = 0x8501; 18917 18918 } 18919 } 18920 18921 /++ 18922 History: 18923 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. 18924 +/ 18925 __gshared bool gluSuccessfullyLoaded = true; 18926 18927 version(without_opengl) {} else { 18928 static if(!SdpyIsUsingIVGLBinds) { 18929 version(Windows) { 18930 mixin DynamicLoad!(GL, "opengl32", 1, openGlLibrariesSuccessfullyLoaded) gl; 18931 mixin DynamicLoad!(GLU, "glu32", 1, gluSuccessfullyLoaded) glu; 18932 } else { 18933 mixin DynamicLoad!(GL, "GL", 1, openGlLibrariesSuccessfullyLoaded) gl; 18934 mixin DynamicLoad!(GLU, "GLU", 3, gluSuccessfullyLoaded) glu; 18935 } 18936 mixin DynamicLoadSupplementalOpenGL!(GL3) gl3; 18937 18938 18939 shared static this() { 18940 gl.loadDynamicLibrary(); 18941 18942 // FIXME: this is NOT actually required and should NOT fail if it is not loaded 18943 // unless those functions are actually used 18944 // go to mark b openGlLibrariesSuccessfullyLoaded = false; 18945 glu.loadDynamicLibrary(); 18946 } 18947 } 18948 } 18949 18950 /++ 18951 Convenience method for converting D arrays to opengl buffer data 18952 18953 I would LOVE to overload it with the original glBufferData, but D won't 18954 let me since glBufferData is a function pointer :( 18955 18956 Added: August 25, 2020 (version 8.5) 18957 +/ 18958 version(without_opengl) {} else 18959 void glBufferDataSlice(GLenum target, const(void[]) data, GLenum usage) { 18960 glBufferData(target, data.length, data.ptr, usage); 18961 } 18962 18963 /+ 18964 /++ 18965 A matrix for simple uses that easily integrates with [OpenGlShader]. 18966 18967 Might not be useful to you since it only as some simple functions and 18968 probably isn't that fast. 18969 18970 Note it uses an inline static array for its storage, so copying it 18971 may be expensive. 18972 +/ 18973 struct BasicMatrix(int columns, int rows, T = float) { 18974 import core.stdc.math; 18975 18976 T[columns * rows] data = 0.0; 18977 18978 /++ 18979 Basic operations that operate *in place*. 18980 +/ 18981 void translate() { 18982 18983 } 18984 18985 /// ditto 18986 void scale() { 18987 18988 } 18989 18990 /// ditto 18991 void rotate() { 18992 18993 } 18994 18995 /++ 18996 18997 +/ 18998 static if(columns == rows) 18999 static BasicMatrix identity() { 19000 BasicMatrix m; 19001 foreach(i; 0 .. columns) 19002 data[0 + i + i * columns] = 1.0; 19003 return m; 19004 } 19005 19006 static BasicMatrix ortho() { 19007 return BasicMatrix.init; 19008 } 19009 } 19010 +/ 19011 19012 /++ 19013 Convenience class for using opengl shaders. 19014 19015 Ensure that you've loaded opengl 3+ and set your active 19016 context before trying to use this. 19017 19018 Added: August 25, 2020 (version 8.5) 19019 +/ 19020 version(without_opengl) {} else 19021 final class OpenGlShader { 19022 private int shaderProgram_; 19023 private @property void shaderProgram(int a) { 19024 shaderProgram_ = a; 19025 } 19026 /// Get the program ID for use in OpenGL functions. 19027 public @property int shaderProgram() { 19028 return shaderProgram_; 19029 } 19030 19031 /++ 19032 19033 +/ 19034 static struct Source { 19035 uint type; /// GL_FRAGMENT_SHADER, GL_VERTEX_SHADER, etc. 19036 string code; /// 19037 } 19038 19039 /++ 19040 Helper method to just compile some shader code and check for errors 19041 while you do glCreateShader, etc. on the outside yourself. 19042 19043 This just does `glShaderSource` and `glCompileShader` for the given code. 19044 19045 If you the OpenGlShader class constructor, you never need to call this yourself. 19046 +/ 19047 static void compile(int sid, Source code) { 19048 const(char)*[1] buffer; 19049 int[1] lengthBuffer; 19050 19051 buffer[0] = code.code.ptr; 19052 lengthBuffer[0] = cast(int) code.code.length; 19053 19054 glShaderSource(sid, cast(int) buffer.length, buffer.ptr, lengthBuffer.ptr); 19055 glCompileShader(sid); 19056 19057 int success; 19058 glGetShaderiv(sid, GL_COMPILE_STATUS, &success); 19059 if(!success) { 19060 char[512] info; 19061 int len; 19062 glGetShaderInfoLog(sid, info.length, &len, info.ptr); 19063 19064 throw new Exception("Shader compile failure: " ~ cast(immutable) info[0 .. len]); 19065 } 19066 } 19067 19068 /++ 19069 Calls `glLinkProgram` and throws if error a occurs. 19070 19071 If you the OpenGlShader class constructor, you never need to call this yourself. 19072 +/ 19073 static void link(int shaderProgram) { 19074 glLinkProgram(shaderProgram); 19075 int success; 19076 glGetProgramiv(shaderProgram, GL_LINK_STATUS, &success); 19077 if(!success) { 19078 char[512] info; 19079 int len; 19080 glGetProgramInfoLog(shaderProgram, info.length, &len, info.ptr); 19081 19082 throw new Exception("Shader link failure: " ~ cast(immutable) info[0 .. len]); 19083 } 19084 } 19085 19086 /++ 19087 Constructs the shader object by calling `glCreateProgram`, then 19088 compiling each given [Source], and finally, linking them together. 19089 19090 Throws: on compile or link failure. 19091 +/ 19092 this(Source[] codes...) { 19093 shaderProgram = glCreateProgram(); 19094 19095 int[16] shadersBufferStack; 19096 19097 int[] shadersBuffer = codes.length <= shadersBufferStack.length ? 19098 shadersBufferStack[0 .. codes.length] : 19099 new int[](codes.length); 19100 19101 foreach(idx, code; codes) { 19102 shadersBuffer[idx] = glCreateShader(code.type); 19103 19104 compile(shadersBuffer[idx], code); 19105 19106 glAttachShader(shaderProgram, shadersBuffer[idx]); 19107 } 19108 19109 link(shaderProgram); 19110 19111 foreach(s; shadersBuffer) 19112 glDeleteShader(s); 19113 } 19114 19115 /// Calls `glUseProgram(this.shaderProgram)` 19116 void use() { 19117 glUseProgram(this.shaderProgram); 19118 } 19119 19120 /// Deletes the program. 19121 void delete_() { 19122 glDeleteProgram(shaderProgram); 19123 shaderProgram = 0; 19124 } 19125 19126 /++ 19127 [OpenGlShader.uniforms].name gives you one of these. 19128 19129 You can get the id out of it or just assign 19130 +/ 19131 static struct Uniform { 19132 /// the id passed to glUniform* 19133 int id; 19134 19135 /// Assigns the 4 floats. You will probably have to call this via the .opAssign name 19136 void opAssign(float x, float y, float z, float w) { 19137 if(id != -1) 19138 glUniform4f(id, x, y, z, w); 19139 } 19140 19141 void opAssign(float x) { 19142 if(id != -1) 19143 glUniform1f(id, x); 19144 } 19145 19146 void opAssign(float x, float y) { 19147 if(id != -1) 19148 glUniform2f(id, x, y); 19149 } 19150 19151 void opAssign(T)(T t) { 19152 t.glUniform(id); 19153 } 19154 } 19155 19156 static struct UniformsHelper { 19157 OpenGlShader _shader; 19158 19159 @property Uniform opDispatch(string name)() { 19160 auto i = glGetUniformLocation(_shader.shaderProgram, name.ptr); 19161 // FIXME: decide what to do here; the exception is liable to be swallowed by the event syste 19162 //if(i == -1) 19163 //throw new Exception("Could not find uniform " ~ name); 19164 return Uniform(i); 19165 } 19166 19167 @property void opDispatch(string name, T)(T t) { 19168 Uniform f = this.opDispatch!name; 19169 t.glUniform(f); 19170 } 19171 } 19172 19173 /++ 19174 Gives access to the uniforms through dot access. 19175 `OpenGlShader.Uniform = shader.uniforms.foo; // calls glGetUniformLocation(this, "foo"); 19176 +/ 19177 @property UniformsHelper uniforms() { return UniformsHelper(this); } 19178 } 19179 19180 version(without_opengl) {} else { 19181 /++ 19182 A static container of experimental types and value constructors for opengl 3+ shaders. 19183 19184 19185 You can declare variables like: 19186 19187 ``` 19188 OGL.vec3f something; 19189 ``` 19190 19191 But generally it would be used with [OpenGlShader]'s uniform helpers like 19192 19193 ``` 19194 shader.uniforms.mouse = OGL.vec(mouseX, mouseY); // or OGL.vec2f if you want to be more specific 19195 ``` 19196 19197 This is still extremely experimental, not very useful at this point, and thus subject to change at random. 19198 19199 19200 History: 19201 Added December 7, 2021. Not yet stable. 19202 +/ 19203 final class OGL { 19204 static: 19205 19206 private template typeFromSpecifier(string specifier) { 19207 static if(specifier == "f") 19208 alias typeFromSpecifier = GLfloat; 19209 else static if(specifier == "i") 19210 alias typeFromSpecifier = GLint; 19211 else static if(specifier == "ui") 19212 alias typeFromSpecifier = GLuint; 19213 else static assert(0, "I don't know this ogl type suffix " ~ specifier); 19214 } 19215 19216 private template CommonType(T...) { 19217 static if(T.length == 1) 19218 alias CommonType = T[0]; 19219 else static if(is(typeof(true ? T[0].init : T[1].init) C)) 19220 alias CommonType = CommonType!(C, T[2 .. $]); 19221 } 19222 19223 private template typesToSpecifier(T...) { 19224 static if(is(CommonType!T == float)) 19225 enum typesToSpecifier = "f"; 19226 else static if(is(CommonType!T == int)) 19227 enum typesToSpecifier = "i"; 19228 else static if(is(CommonType!T == uint)) 19229 enum typesToSpecifier = "ui"; 19230 else static assert(0, "I can't find a gl type suffix for common type " ~ CommonType!T.stringof); 19231 } 19232 19233 private template genNames(size_t dim, size_t dim2 = 0) { 19234 string helper() { 19235 string s; 19236 if(dim2) { 19237 s ~= "type["~(dim + '0')~"]["~(dim2 + '0')~"] matrix;"; 19238 } else { 19239 if(dim > 0) s ~= "type x = 0;"; 19240 if(dim > 1) s ~= "type y = 0;"; 19241 if(dim > 2) s ~= "type z = 0;"; 19242 if(dim > 3) s ~= "type w = 0;"; 19243 } 19244 return s; 19245 } 19246 19247 enum genNames = helper(); 19248 } 19249 19250 // there's vec, arrays of vec, mat, and arrays of mat 19251 template opDispatch(string name) 19252 if(name.length > 4 && (name[0 .. 3] == "vec" || name[0 .. 3] == "mat")) 19253 { 19254 static if(name[4] == 'x') { 19255 enum dimX = cast(int) (name[3] - '0'); 19256 static assert(dimX > 0 && dimX <= 4, "Bad dimension for OGL X type " ~ name[3]); 19257 19258 enum dimY = cast(int) (name[5] - '0'); 19259 static assert(dimY > 0 && dimY <= 4, "Bad dimension for OGL Y type " ~ name[5]); 19260 19261 enum isArray = name[$ - 1] == 'v'; 19262 enum typeSpecifier = isArray ? name[6 .. $ - 1] : name[6 .. $]; 19263 alias type = typeFromSpecifier!typeSpecifier; 19264 } else { 19265 enum dim = cast(int) (name[3] - '0'); 19266 static assert(dim > 0 && dim <= 4, "Bad dimension for OGL type " ~ name[3]); 19267 enum isArray = name[$ - 1] == 'v'; 19268 enum typeSpecifier = isArray ? name[4 .. $ - 1] : name[4 .. $]; 19269 alias type = typeFromSpecifier!typeSpecifier; 19270 } 19271 19272 align(1) 19273 struct opDispatch { 19274 align(1): 19275 static if(name[4] == 'x') 19276 mixin(genNames!(dimX, dimY)); 19277 else 19278 mixin(genNames!dim); 19279 19280 private void glUniform(OpenGlShader.Uniform assignTo) { 19281 glUniform(assignTo.id); 19282 } 19283 private void glUniform(int assignTo) { 19284 static if(name[4] == 'x') { 19285 // FIXME 19286 pragma(msg, "This matrix uniform helper has never been tested!!!!"); 19287 mixin("glUniformMatrix" ~ name[3 .. $] ~ "v")(assignTo, dimX * dimY, false, this.matrix.ptr); 19288 } else 19289 mixin("glUniform" ~ name[3 .. $])(assignTo, this.tupleof); 19290 } 19291 } 19292 } 19293 19294 auto vec(T...)(T members) { 19295 return typeof(this).opDispatch!("vec" ~ toInternal!string(cast(int) T.length)~ typesToSpecifier!T)(members); 19296 } 19297 } 19298 } 19299 19300 version(linux) { 19301 version(with_eventloop) {} else { 19302 private int epollFd = -1; 19303 void prepareEventLoop() { 19304 if(epollFd != -1) 19305 return; // already initialized, no need to do it again 19306 import ep = core.sys.linux.epoll; 19307 19308 epollFd = ep.epoll_create1(ep.EPOLL_CLOEXEC); 19309 if(epollFd == -1) 19310 throw new Exception("epoll create failure"); 19311 } 19312 } 19313 } else version(Posix) { 19314 void prepareEventLoop() {} 19315 } 19316 19317 version(X11) { 19318 import core.stdc.locale : LC_ALL; // rdmd fix 19319 __gshared bool sdx_isUTF8Locale; 19320 19321 // This whole crap is used to initialize X11 locale, so that you can use XIM methods later. 19322 // Yes, there are people with non-utf locale (it's me, Ketmar!), but XIM (composing) will 19323 // not work right if app/X11 locale is not utf. This sux. That's why all that "utf detection" 19324 // anal magic is here. I (Ketmar) hope you like it. 19325 // We will use `sdx_isUTF8Locale` on XIM creation to enforce UTF-8 locale, so XCompose will 19326 // always return correct unicode symbols. The detection is here 'cause user can change locale 19327 // later. 19328 19329 // NOTE: IT IS VERY IMPORTANT THAT THIS BE THE LAST STATIC CTOR OF THE FILE since it tests librariesSuccessfullyLoaded 19330 shared static this () { 19331 if(!librariesSuccessfullyLoaded) 19332 return; 19333 19334 import core.stdc.locale : setlocale, LC_ALL, LC_CTYPE; 19335 19336 // this doesn't hurt; it may add some locking, but the speed is still 19337 // allows doing 60 FPS videogames; also, ignore the result, as most 19338 // users will probably won't do mulththreaded X11 anyway (and I (ketmar) 19339 // never seen this failing). 19340 if (XInitThreads() == 0) { import core.stdc.stdio; fprintf(stderr, "XInitThreads() failed!\n"); } 19341 19342 setlocale(LC_ALL, ""); 19343 // check if out locale is UTF-8 19344 auto lct = setlocale(LC_CTYPE, null); 19345 if (lct is null) { 19346 sdx_isUTF8Locale = false; 19347 } else { 19348 for (size_t idx = 0; lct[idx] && lct[idx+1] && lct[idx+2]; ++idx) { 19349 if ((lct[idx+0] == 'u' || lct[idx+0] == 'U') && 19350 (lct[idx+1] == 't' || lct[idx+1] == 'T') && 19351 (lct[idx+2] == 'f' || lct[idx+2] == 'F')) 19352 { 19353 sdx_isUTF8Locale = true; 19354 break; 19355 } 19356 } 19357 } 19358 //{ import core.stdc.stdio : stderr, fprintf; fprintf(stderr, "UTF8: %s\n", sdx_isUTF8Locale ? "tan".ptr : "ona".ptr); } 19359 } 19360 } 19361 19362 class ExperimentalTextComponent2 { 19363 /+ 19364 Stage 1: get it working monospace 19365 Stage 2: use proportional font 19366 Stage 3: allow changes in inline style 19367 Stage 4: allow new fonts and sizes in the middle 19368 Stage 5: optimize gap buffer 19369 Stage 6: optimize layout 19370 Stage 7: word wrap 19371 Stage 8: justification 19372 Stage 9: editing, selection, etc. 19373 19374 Operations: 19375 insert text 19376 overstrike text 19377 select 19378 cut 19379 modify 19380 +/ 19381 19382 /++ 19383 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. 19384 +/ 19385 this(SimpleWindow window) { 19386 this.window = window; 19387 } 19388 19389 private SimpleWindow window; 19390 19391 19392 /++ 19393 When you render a [ComponentInFlow], it returns an arbitrary number of these interfaces 19394 representing the internal parts. The first pass is focused on the x parameter, then the 19395 renderer is responsible for going back to the parts in the current line and calling 19396 adjustDownForAscent to change the y params. 19397 +/ 19398 static interface ComponentRenderHelper { 19399 19400 /+ 19401 When you do an edit, possibly stuff on the same line previously need to move (to adjust 19402 the baseline), stuff subsequent needs to move (adjust x) and possibly stuff below needs 19403 to move (adjust y to make room for new line) until you get back to the same position, 19404 then you can stop - if one thing is unchanged, nothing after it is changed too. 19405 19406 Word wrap might change this as if can rewrap tons of stuff, but the same idea applies, 19407 once you reach something that is unchanged, you can stop. 19408 +/ 19409 19410 void adjustDownForAscent(int amount); // at the end of the line it needs to do these 19411 19412 int ascent() const; 19413 int descent() const; 19414 19415 int advance() const; 19416 19417 bool endsWithExplititLineBreak() const; 19418 } 19419 19420 static interface RenderResult { 19421 /++ 19422 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. 19423 +/ 19424 void popFront(); 19425 @property bool empty() const; 19426 @property ComponentRenderHelper front() const; 19427 19428 void repositionForNextLine(Point baseline, int availableWidth); 19429 } 19430 19431 static interface ComponentInFlow { 19432 void draw(ScreenPainter painter); 19433 //RenderResult render(Point baseline, int availableWidth); // FIXME: it needs to be able to say "my cache is good, nothing different" 19434 19435 bool startsWithExplicitLineBreak() const; 19436 } 19437 19438 static class TextFlowComponent : ComponentInFlow { 19439 bool startsWithExplicitLineBreak() const { return false; } // FIXME: if it is block this can return true 19440 19441 Color foreground; 19442 Color background; 19443 19444 OperatingSystemFont font; // should NEVER be null 19445 19446 ubyte attributes; // underline, strike through, display on new block 19447 19448 version(Windows) 19449 const(wchar)[] content; 19450 else 19451 const(char)[] content; // this should NEVER have a newline, except at the end 19452 19453 RenderedComponent[] rendered; // entirely controlled by [rerender] 19454 19455 // could prolly put some spacing around it too like margin / padding 19456 19457 this(Color f, Color b, OperatingSystemFont font, ubyte attr, const(char)[] c) 19458 in { assert(font !is null); 19459 assert(!font.isNull); } 19460 do 19461 { 19462 this.foreground = f; 19463 this.background = b; 19464 this.font = font; 19465 19466 this.attributes = attr; 19467 version(Windows) { 19468 auto conversionFlags = 0;//WindowsStringConversionFlags.convertNewLines; 19469 auto sz = sizeOfConvertedWstring(c, conversionFlags); 19470 auto buffer = new wchar[](sz); 19471 this.content = makeWindowsString(c, buffer, conversionFlags); 19472 } else { 19473 this.content = c.dup; 19474 } 19475 } 19476 19477 void draw(ScreenPainter painter) { 19478 painter.setFont(this.font); 19479 painter.outlineColor = this.foreground; 19480 painter.fillColor = Color.transparent; 19481 foreach(rendered; this.rendered) { 19482 // the component works in term of baseline, 19483 // but the painter works in term of upper left bounding box 19484 // so need to translate that 19485 19486 if(this.background.a) { 19487 painter.fillColor = this.background; 19488 painter.outlineColor = this.background; 19489 19490 painter.drawRectangle(Point(rendered.startX, rendered.startY - this.font.ascent), Size(rendered.width, this.font.height)); 19491 19492 painter.outlineColor = this.foreground; 19493 painter.fillColor = Color.transparent; 19494 } 19495 19496 painter.drawText(Point(rendered.startX, rendered.startY - this.font.ascent), rendered.slice); 19497 19498 // FIXME: strike through, underline, highlight selection, etc. 19499 } 19500 } 19501 } 19502 19503 // I could split the parts into words on render 19504 // for easier word-wrap, each one being an unbreakable "inline-block" 19505 private TextFlowComponent[] parts; 19506 private int needsRerenderFrom; 19507 19508 void addPart(Color f, Color b, OperatingSystemFont font, ubyte attr, const(char)[] c) { 19509 // FIXME: needsRerenderFrom. Basically if the bounding box and baseline is the same as the previous thing, it can prolly just stop. 19510 parts ~= new TextFlowComponent(f, b, font, attr, c); 19511 } 19512 19513 static struct RenderedComponent { 19514 int startX; 19515 int startY; 19516 short width; 19517 // 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! 19518 // for individual chars in here you've gotta process on demand 19519 version(Windows) 19520 const(wchar)[] slice; 19521 else 19522 const(char)[] slice; 19523 } 19524 19525 19526 void rerender(Rectangle boundingBox) { 19527 Point baseline = boundingBox.upperLeft; 19528 19529 this.boundingBox.left = boundingBox.left; 19530 this.boundingBox.top = boundingBox.top; 19531 19532 auto remainingParts = parts; 19533 19534 int largestX; 19535 19536 19537 foreach(part; parts) 19538 part.font.prepareContext(window); 19539 scope(exit) 19540 foreach(part; parts) 19541 part.font.releaseContext(); 19542 19543 calculateNextLine: 19544 19545 int nextLineHeight = 0; 19546 int nextBiggestDescent = 0; 19547 19548 foreach(part; remainingParts) { 19549 auto height = part.font.ascent; 19550 if(height > nextLineHeight) 19551 nextLineHeight = height; 19552 if(part.font.descent > nextBiggestDescent) 19553 nextBiggestDescent = part.font.descent; 19554 if(part.content.length && part.content[$-1] == '\n') 19555 break; 19556 } 19557 19558 baseline.y += nextLineHeight; 19559 auto lineStart = baseline; 19560 19561 while(remainingParts.length) { 19562 remainingParts[0].rendered = null; 19563 19564 bool eol; 19565 if(remainingParts[0].content.length && remainingParts[0].content[$-1] == '\n') 19566 eol = true; 19567 19568 // FIXME: word wrap 19569 auto font = remainingParts[0].font; 19570 auto slice = remainingParts[0].content[0 .. $ - (eol ? 1 : 0)]; 19571 auto width = font.stringWidth(slice, window); 19572 remainingParts[0].rendered ~= RenderedComponent(baseline.x, baseline.y, cast(short) width, slice); 19573 19574 remainingParts = remainingParts[1 .. $]; 19575 baseline.x += width; 19576 19577 if(eol) { 19578 baseline.y += nextBiggestDescent; 19579 if(baseline.x > largestX) 19580 largestX = baseline.x; 19581 baseline.x = lineStart.x; 19582 goto calculateNextLine; 19583 } 19584 } 19585 19586 if(baseline.x > largestX) 19587 largestX = baseline.x; 19588 19589 this.boundingBox.right = largestX; 19590 this.boundingBox.bottom = baseline.y; 19591 } 19592 19593 // you must call rerender first! 19594 void draw(ScreenPainter painter) { 19595 foreach(part; parts) { 19596 part.draw(painter); 19597 } 19598 } 19599 19600 struct IdentifyResult { 19601 TextFlowComponent part; 19602 int charIndexInPart; 19603 int totalCharIndex = -1; // if this is -1, it just means the end 19604 19605 Rectangle boundingBox; 19606 } 19607 19608 IdentifyResult identify(Point pt, bool exact = false) { 19609 if(parts.length == 0) 19610 return IdentifyResult(null, 0); 19611 19612 if(pt.y < boundingBox.top) { 19613 if(exact) 19614 return IdentifyResult(null, 1); 19615 return IdentifyResult(parts[0], 0); 19616 } 19617 if(pt.y > boundingBox.bottom) { 19618 if(exact) 19619 return IdentifyResult(null, 2); 19620 return IdentifyResult(parts[$-1], cast(int) parts[$-1].content.length); 19621 } 19622 19623 int tci = 0; 19624 19625 // I should probably like binary search this or something... 19626 foreach(ref part; parts) { 19627 foreach(rendered; part.rendered) { 19628 auto rect = Rectangle(rendered.startX, rendered.startY - part.font.ascent, rendered.startX + rendered.width, rendered.startY + part.font.descent); 19629 if(rect.contains(pt)) { 19630 auto x = pt.x - rendered.startX; 19631 auto estimatedIdx = x / part.font.averageWidth; 19632 19633 if(estimatedIdx < 0) 19634 estimatedIdx = 0; 19635 19636 if(estimatedIdx > rendered.slice.length) 19637 estimatedIdx = cast(int) rendered.slice.length; 19638 19639 int idx; 19640 int x1, x2; 19641 if(part.font.isMonospace) { 19642 auto w = part.font.averageWidth; 19643 if(!exact && x > (estimatedIdx + 1) * w) 19644 return IdentifyResult(null, 4); 19645 idx = estimatedIdx; 19646 x1 = idx * w; 19647 x2 = (idx + 1) * w; 19648 } else { 19649 idx = estimatedIdx; 19650 19651 part.font.prepareContext(window); 19652 scope(exit) part.font.releaseContext(); 19653 19654 // int iterations; 19655 19656 while(true) { 19657 // iterations++; 19658 x1 = idx ? part.font.stringWidth(rendered.slice[0 .. idx - 1]) : 0; 19659 x2 = part.font.stringWidth(rendered.slice[0 .. idx]); // should be the maximum since `averageWidth` kinda lies. 19660 19661 x1 += rendered.startX; 19662 x2 += rendered.startX; 19663 19664 if(pt.x < x1) { 19665 if(idx == 0) { 19666 if(exact) 19667 return IdentifyResult(null, 6); 19668 else 19669 break; 19670 } 19671 idx--; 19672 } else if(pt.x > x2) { 19673 idx++; 19674 if(idx > rendered.slice.length) { 19675 if(exact) 19676 return IdentifyResult(null, 5); 19677 else 19678 break; 19679 } 19680 } else if(pt.x >= x1 && pt.x <= x2) { 19681 if(idx) 19682 idx--; // point it at the original index 19683 break; // we fit 19684 } 19685 } 19686 19687 // writeln(iterations) 19688 } 19689 19690 19691 return IdentifyResult(part, idx, tci + idx, Rectangle(x1, rect.top, x2, rect.bottom)); // FIXME: utf-8? 19692 } 19693 } 19694 tci += cast(int) part.content.length; // FIXME: utf-8? 19695 } 19696 return IdentifyResult(null, 3); 19697 } 19698 19699 Rectangle boundingBox; // only set after [rerender] 19700 19701 // text will be positioned around the exclusion zone 19702 static struct ExclusionZone { 19703 19704 } 19705 19706 ExclusionZone[] exclusionZones; 19707 } 19708 19709 19710 // Don't use this yet. When I'm happy with it, I will move it to the 19711 // regular module namespace. 19712 mixin template ExperimentalTextComponent() { 19713 19714 static: 19715 19716 alias Rectangle = arsd.color.Rectangle; 19717 19718 struct ForegroundColor { 19719 Color color; 19720 alias color this; 19721 19722 this(Color c) { 19723 color = c; 19724 } 19725 19726 this(int r, int g, int b, int a = 255) { 19727 color = Color(r, g, b, a); 19728 } 19729 19730 static ForegroundColor opDispatch(string s)() if(__traits(compiles, ForegroundColor(mixin("Color." ~ s)))) { 19731 return ForegroundColor(mixin("Color." ~ s)); 19732 } 19733 } 19734 19735 struct BackgroundColor { 19736 Color color; 19737 alias color this; 19738 19739 this(Color c) { 19740 color = c; 19741 } 19742 19743 this(int r, int g, int b, int a = 255) { 19744 color = Color(r, g, b, a); 19745 } 19746 19747 static BackgroundColor opDispatch(string s)() if(__traits(compiles, BackgroundColor(mixin("Color." ~ s)))) { 19748 return BackgroundColor(mixin("Color." ~ s)); 19749 } 19750 } 19751 19752 static class InlineElement { 19753 string text; 19754 19755 BlockElement containingBlock; 19756 19757 Color color = Color.black; 19758 Color backgroundColor = Color.transparent; 19759 ushort styles; 19760 19761 string font; 19762 int fontSize; 19763 19764 int lineHeight; 19765 19766 void* identifier; 19767 19768 Rectangle boundingBox; 19769 int[] letterXs; // FIXME: maybe i should do bounding boxes for every character 19770 19771 bool isMergeCompatible(InlineElement other) { 19772 return 19773 containingBlock is other.containingBlock && 19774 color == other.color && 19775 backgroundColor == other.backgroundColor && 19776 styles == other.styles && 19777 font == other.font && 19778 fontSize == other.fontSize && 19779 lineHeight == other.lineHeight && 19780 true; 19781 } 19782 19783 int xOfIndex(size_t index) { 19784 if(index < letterXs.length) 19785 return letterXs[index]; 19786 else 19787 return boundingBox.right; 19788 } 19789 19790 InlineElement clone() { 19791 auto ie = new InlineElement(); 19792 ie.tupleof = this.tupleof; 19793 return ie; 19794 } 19795 19796 InlineElement getPreviousInlineElement() { 19797 InlineElement prev = null; 19798 foreach(ie; this.containingBlock.parts) { 19799 if(ie is this) 19800 break; 19801 prev = ie; 19802 } 19803 if(prev is null) { 19804 BlockElement pb; 19805 BlockElement cb = this.containingBlock; 19806 moar: 19807 foreach(ie; this.containingBlock.containingLayout.blocks) { 19808 if(ie is cb) 19809 break; 19810 pb = ie; 19811 } 19812 if(pb is null) 19813 return null; 19814 if(pb.parts.length == 0) { 19815 cb = pb; 19816 goto moar; 19817 } 19818 19819 prev = pb.parts[$-1]; 19820 19821 } 19822 return prev; 19823 } 19824 19825 InlineElement getNextInlineElement() { 19826 InlineElement next = null; 19827 foreach(idx, ie; this.containingBlock.parts) { 19828 if(ie is this) { 19829 if(idx + 1 < this.containingBlock.parts.length) 19830 next = this.containingBlock.parts[idx + 1]; 19831 break; 19832 } 19833 } 19834 if(next is null) { 19835 BlockElement n; 19836 foreach(idx, ie; this.containingBlock.containingLayout.blocks) { 19837 if(ie is this.containingBlock) { 19838 if(idx + 1 < this.containingBlock.containingLayout.blocks.length) 19839 n = this.containingBlock.containingLayout.blocks[idx + 1]; 19840 break; 19841 } 19842 } 19843 if(n is null) 19844 return null; 19845 19846 if(n.parts.length) 19847 next = n.parts[0]; 19848 else {} // FIXME 19849 19850 } 19851 return next; 19852 } 19853 19854 } 19855 19856 // Block elements are used entirely for positioning inline elements, 19857 // which are the things that are actually drawn. 19858 class BlockElement { 19859 InlineElement[] parts; 19860 uint alignment; 19861 19862 int whiteSpace; // pre, pre-wrap, wrap 19863 19864 TextLayout containingLayout; 19865 19866 // inputs 19867 Point where; 19868 Size minimumSize; 19869 Size maximumSize; 19870 Rectangle[] excludedBoxes; // like if you want it to write around a floated image or something. Coordinates are relative to the bounding box. 19871 void* identifier; 19872 19873 Rectangle margin; 19874 Rectangle padding; 19875 19876 // outputs 19877 Rectangle[] boundingBoxes; 19878 } 19879 19880 struct TextIdentifyResult { 19881 InlineElement element; 19882 int offset; 19883 19884 private TextIdentifyResult fixupNewline() { 19885 if(element !is null && offset < element.text.length && element.text[offset] == '\n') { 19886 offset--; 19887 } else if(element !is null && offset == element.text.length && element.text.length > 1 && element.text[$-1] == '\n') { 19888 offset--; 19889 } 19890 return this; 19891 } 19892 } 19893 19894 class TextLayout { 19895 BlockElement[] blocks; 19896 Rectangle boundingBox_; 19897 Rectangle boundingBox() { return boundingBox_; } 19898 void boundingBox(Rectangle r) { 19899 if(r != boundingBox_) { 19900 boundingBox_ = r; 19901 layoutInvalidated = true; 19902 } 19903 } 19904 19905 Rectangle contentBoundingBox() { 19906 Rectangle r; 19907 foreach(block; blocks) 19908 foreach(ie; block.parts) { 19909 if(ie.boundingBox.right > r.right) 19910 r.right = ie.boundingBox.right; 19911 if(ie.boundingBox.bottom > r.bottom) 19912 r.bottom = ie.boundingBox.bottom; 19913 } 19914 return r; 19915 } 19916 19917 BlockElement[] getBlocks() { 19918 return blocks; 19919 } 19920 19921 InlineElement[] getTexts() { 19922 InlineElement[] elements; 19923 foreach(block; blocks) 19924 elements ~= block.parts; 19925 return elements; 19926 } 19927 19928 string getPlainText() { 19929 string text; 19930 foreach(block; blocks) 19931 foreach(part; block.parts) 19932 text ~= part.text; 19933 return text; 19934 } 19935 19936 string getHtml() { 19937 return null; // FIXME 19938 } 19939 19940 this(Rectangle boundingBox) { 19941 this.boundingBox = boundingBox; 19942 } 19943 19944 BlockElement addBlock(InlineElement after = null, Rectangle margin = Rectangle(0, 0, 0, 0), Rectangle padding = Rectangle(0, 0, 0, 0)) { 19945 auto be = new BlockElement(); 19946 be.containingLayout = this; 19947 if(after is null) 19948 blocks ~= be; 19949 else { 19950 foreach(idx, b; blocks) { 19951 if(b is after.containingBlock) { 19952 blocks = blocks[0 .. idx + 1] ~ be ~ blocks[idx + 1 .. $]; 19953 break; 19954 } 19955 } 19956 } 19957 return be; 19958 } 19959 19960 void clear() { 19961 blocks = null; 19962 selectionStart = selectionEnd = caret = Caret.init; 19963 } 19964 19965 void addText(Args...)(Args args) { 19966 if(blocks.length == 0) 19967 addBlock(); 19968 19969 InlineElement ie = new InlineElement(); 19970 foreach(idx, arg; args) { 19971 static if(is(typeof(arg) == ForegroundColor)) 19972 ie.color = arg; 19973 else static if(is(typeof(arg) == TextFormat)) { 19974 if(arg & 0x8000) // ~TextFormat.something turns it off 19975 ie.styles &= arg; 19976 else 19977 ie.styles |= arg; 19978 } else static if(is(typeof(arg) == string)) { 19979 static if(idx == 0 && args.length > 1) 19980 static assert(0, "Put styles before the string."); 19981 size_t lastLineIndex; 19982 foreach(cidx, char a; arg) { 19983 if(a == '\n') { 19984 ie.text = arg[lastLineIndex .. cidx + 1]; 19985 lastLineIndex = cidx + 1; 19986 ie.containingBlock = blocks[$-1]; 19987 blocks[$-1].parts ~= ie.clone; 19988 ie.text = null; 19989 } else { 19990 19991 } 19992 } 19993 19994 ie.text = arg[lastLineIndex .. $]; 19995 ie.containingBlock = blocks[$-1]; 19996 blocks[$-1].parts ~= ie.clone; 19997 caret = Caret(this, blocks[$-1].parts[$-1], cast(int) blocks[$-1].parts[$-1].text.length); 19998 } 19999 } 20000 20001 invalidateLayout(); 20002 } 20003 20004 void tryMerge(InlineElement into, InlineElement what) { 20005 if(!into.isMergeCompatible(what)) { 20006 return; // cannot merge, different configs 20007 } 20008 20009 // cool, can merge, bring text together... 20010 into.text ~= what.text; 20011 20012 // and remove what 20013 for(size_t a = 0; a < what.containingBlock.parts.length; a++) { 20014 if(what.containingBlock.parts[a] is what) { 20015 for(size_t i = a; i < what.containingBlock.parts.length - 1; i++) 20016 what.containingBlock.parts[i] = what.containingBlock.parts[i + 1]; 20017 what.containingBlock.parts = what.containingBlock.parts[0 .. $-1]; 20018 20019 } 20020 } 20021 20022 // FIXME: ensure no other carets have a reference to it 20023 } 20024 20025 /// exact = true means return null if no match. otherwise, get the closest one that makes sense for a mouse click. 20026 TextIdentifyResult identify(int x, int y, bool exact = false) { 20027 TextIdentifyResult inexactMatch; 20028 foreach(block; blocks) { 20029 foreach(part; block.parts) { 20030 if(x >= part.boundingBox.left && x < part.boundingBox.right && y >= part.boundingBox.top && y < part.boundingBox.bottom) { 20031 20032 // FIXME binary search 20033 int tidx; 20034 int lastX; 20035 foreach_reverse(idxo, lx; part.letterXs) { 20036 int idx = cast(int) idxo; 20037 if(lx <= x) { 20038 if(lastX && lastX - x < x - lx) 20039 tidx = idx + 1; 20040 else 20041 tidx = idx; 20042 break; 20043 } 20044 lastX = lx; 20045 } 20046 20047 return TextIdentifyResult(part, tidx).fixupNewline; 20048 } else if(!exact) { 20049 // we're not in the box, but are we on the same line? 20050 if(y >= part.boundingBox.top && y < part.boundingBox.bottom) 20051 inexactMatch = TextIdentifyResult(part, x == 0 ? 0 : cast(int) part.text.length); 20052 } 20053 } 20054 } 20055 20056 if(!exact && inexactMatch is TextIdentifyResult.init && blocks.length && blocks[$-1].parts.length) 20057 return TextIdentifyResult(blocks[$-1].parts[$-1], cast(int) blocks[$-1].parts[$-1].text.length).fixupNewline; 20058 20059 return exact ? TextIdentifyResult.init : inexactMatch.fixupNewline; 20060 } 20061 20062 void moveCaretToPixelCoordinates(int x, int y) { 20063 auto result = identify(x, y); 20064 caret.inlineElement = result.element; 20065 caret.offset = result.offset; 20066 } 20067 20068 void selectToPixelCoordinates(int x, int y) { 20069 auto result = identify(x, y); 20070 20071 if(y < caretLastDrawnY1) { 20072 // on a previous line, carat is selectionEnd 20073 selectionEnd = caret; 20074 20075 selectionStart = Caret(this, result.element, result.offset); 20076 } else if(y > caretLastDrawnY2) { 20077 // on a later line 20078 selectionStart = caret; 20079 20080 selectionEnd = Caret(this, result.element, result.offset); 20081 } else { 20082 // on the same line... 20083 if(x <= caretLastDrawnX) { 20084 selectionEnd = caret; 20085 selectionStart = Caret(this, result.element, result.offset); 20086 } else { 20087 selectionStart = caret; 20088 selectionEnd = Caret(this, result.element, result.offset); 20089 } 20090 20091 } 20092 } 20093 20094 20095 /// Call this if the inputs change. It will reflow everything 20096 void redoLayout(ScreenPainter painter) { 20097 //painter.setClipRectangle(boundingBox); 20098 auto pos = Point(boundingBox.left, boundingBox.top); 20099 20100 int lastHeight; 20101 void nl() { 20102 pos.x = boundingBox.left; 20103 pos.y += lastHeight; 20104 } 20105 foreach(block; blocks) { 20106 nl(); 20107 foreach(part; block.parts) { 20108 part.letterXs = null; 20109 20110 auto size = painter.textSize(part.text); 20111 version(Windows) 20112 if(part.text.length && part.text[$-1] == '\n') 20113 size.height /= 2; // windows counts the new line at the end, but we don't want that 20114 20115 part.boundingBox = Rectangle(pos.x, pos.y, pos.x + size.width, pos.y + size.height); 20116 20117 foreach(idx, char c; part.text) { 20118 // FIXME: unicode 20119 part.letterXs ~= painter.textSize(part.text[0 .. idx]).width + pos.x; 20120 } 20121 20122 pos.x += size.width; 20123 if(pos.x >= boundingBox.right) { 20124 pos.y += size.height; 20125 pos.x = boundingBox.left; 20126 lastHeight = 0; 20127 } else { 20128 lastHeight = size.height; 20129 } 20130 20131 if(part.text.length && part.text[$-1] == '\n') 20132 nl(); 20133 } 20134 } 20135 20136 layoutInvalidated = false; 20137 } 20138 20139 bool layoutInvalidated = true; 20140 void invalidateLayout() { 20141 layoutInvalidated = true; 20142 } 20143 20144 // FIXME: caret can remain sometimes when inserting 20145 // FIXME: inserting at the beginning once you already have something can eff it up. 20146 void drawInto(ScreenPainter painter, bool focused = false) { 20147 if(layoutInvalidated) 20148 redoLayout(painter); 20149 foreach(block; blocks) { 20150 foreach(part; block.parts) { 20151 painter.outlineColor = part.color; 20152 painter.fillColor = part.backgroundColor; 20153 20154 auto pos = part.boundingBox.upperLeft; 20155 auto size = part.boundingBox.size; 20156 20157 painter.drawText(pos, part.text); 20158 if(part.styles & TextFormat.underline) 20159 painter.drawLine(Point(pos.x, pos.y + size.height - 4), Point(pos.x + size.width, pos.y + size.height - 4)); 20160 if(part.styles & TextFormat.strikethrough) 20161 painter.drawLine(Point(pos.x, pos.y + size.height/2), Point(pos.x + size.width, pos.y + size.height/2)); 20162 } 20163 } 20164 20165 // on every redraw, I will force the caret to be 20166 // redrawn too, in order to eliminate perceived lag 20167 // when moving around with the mouse. 20168 eraseCaret(painter); 20169 20170 if(focused) { 20171 highlightSelection(painter); 20172 drawCaret(painter); 20173 } 20174 } 20175 20176 Color selectionXorColor = Color(255, 255, 127); 20177 20178 void highlightSelection(ScreenPainter painter) { 20179 if(selectionStart is selectionEnd) 20180 return; // no selection 20181 20182 if(selectionStart.inlineElement is null) return; 20183 if(selectionEnd.inlineElement is null) return; 20184 20185 assert(selectionStart.inlineElement !is null); 20186 assert(selectionEnd.inlineElement !is null); 20187 20188 painter.rasterOp = RasterOp.xor; 20189 painter.outlineColor = Color.transparent; 20190 painter.fillColor = selectionXorColor; 20191 20192 auto at = selectionStart.inlineElement; 20193 auto atOffset = selectionStart.offset; 20194 bool done; 20195 while(at) { 20196 auto box = at.boundingBox; 20197 if(atOffset < at.letterXs.length) 20198 box.left = at.letterXs[atOffset]; 20199 20200 if(at is selectionEnd.inlineElement) { 20201 if(selectionEnd.offset < at.letterXs.length) 20202 box.right = at.letterXs[selectionEnd.offset]; 20203 done = true; 20204 } 20205 20206 painter.drawRectangle(box.upperLeft, box.width, box.height); 20207 20208 if(done) 20209 break; 20210 20211 at = at.getNextInlineElement(); 20212 atOffset = 0; 20213 } 20214 } 20215 20216 int caretLastDrawnX, caretLastDrawnY1, caretLastDrawnY2; 20217 bool caretShowingOnScreen = false; 20218 void drawCaret(ScreenPainter painter) { 20219 //painter.setClipRectangle(boundingBox); 20220 int x, y1, y2; 20221 if(caret.inlineElement is null) { 20222 x = boundingBox.left; 20223 y1 = boundingBox.top + 2; 20224 y2 = boundingBox.top + painter.fontHeight; 20225 } else { 20226 x = caret.inlineElement.xOfIndex(caret.offset); 20227 y1 = caret.inlineElement.boundingBox.top + 2; 20228 y2 = caret.inlineElement.boundingBox.bottom - 2; 20229 } 20230 20231 if(caretShowingOnScreen && (x != caretLastDrawnX || y1 != caretLastDrawnY1 || y2 != caretLastDrawnY2)) 20232 eraseCaret(painter); 20233 20234 painter.pen = Pen(Color.white, 1); 20235 painter.rasterOp = RasterOp.xor; 20236 painter.drawLine( 20237 Point(x, y1), 20238 Point(x, y2) 20239 ); 20240 painter.rasterOp = RasterOp.normal; 20241 caretShowingOnScreen = !caretShowingOnScreen; 20242 20243 if(caretShowingOnScreen) { 20244 caretLastDrawnX = x; 20245 caretLastDrawnY1 = y1; 20246 caretLastDrawnY2 = y2; 20247 } 20248 } 20249 20250 Rectangle caretBoundingBox() { 20251 int x, y1, y2; 20252 if(caret.inlineElement is null) { 20253 x = boundingBox.left; 20254 y1 = boundingBox.top + 2; 20255 y2 = boundingBox.top + 16; 20256 } else { 20257 x = caret.inlineElement.xOfIndex(caret.offset); 20258 y1 = caret.inlineElement.boundingBox.top + 2; 20259 y2 = caret.inlineElement.boundingBox.bottom - 2; 20260 } 20261 20262 return Rectangle(x, y1, x + 1, y2); 20263 } 20264 20265 void eraseCaret(ScreenPainter painter) { 20266 //painter.setClipRectangle(boundingBox); 20267 if(!caretShowingOnScreen) return; 20268 painter.pen = Pen(Color.white, 1); 20269 painter.rasterOp = RasterOp.xor; 20270 painter.drawLine( 20271 Point(caretLastDrawnX, caretLastDrawnY1), 20272 Point(caretLastDrawnX, caretLastDrawnY2) 20273 ); 20274 20275 caretShowingOnScreen = false; 20276 painter.rasterOp = RasterOp.normal; 20277 } 20278 20279 /// Caret movement api 20280 /// These should give the user a logical result based on what they see on screen... 20281 /// thus they locate predominately by *pixels* not char index. (These will generally coincide with monospace fonts tho!) 20282 void moveUp() { 20283 if(caret.inlineElement is null) return; 20284 auto x = caret.inlineElement.xOfIndex(caret.offset); 20285 auto y = caret.inlineElement.boundingBox.top + 2; 20286 20287 y -= caret.inlineElement.boundingBox.bottom - caret.inlineElement.boundingBox.top; 20288 if(y < 0) 20289 return; 20290 20291 auto i = identify(x, y); 20292 20293 if(i.element) { 20294 caret.inlineElement = i.element; 20295 caret.offset = i.offset; 20296 } 20297 } 20298 void moveDown() { 20299 if(caret.inlineElement is null) return; 20300 auto x = caret.inlineElement.xOfIndex(caret.offset); 20301 auto y = caret.inlineElement.boundingBox.bottom - 2; 20302 20303 y += caret.inlineElement.boundingBox.bottom - caret.inlineElement.boundingBox.top; 20304 20305 auto i = identify(x, y); 20306 if(i.element) { 20307 caret.inlineElement = i.element; 20308 caret.offset = i.offset; 20309 } 20310 } 20311 void moveLeft() { 20312 if(caret.inlineElement is null) return; 20313 if(caret.offset) 20314 caret.offset--; 20315 else { 20316 auto p = caret.inlineElement.getPreviousInlineElement(); 20317 if(p) { 20318 caret.inlineElement = p; 20319 if(p.text.length && p.text[$-1] == '\n') 20320 caret.offset = cast(int) p.text.length - 1; 20321 else 20322 caret.offset = cast(int) p.text.length; 20323 } 20324 } 20325 } 20326 void moveRight() { 20327 if(caret.inlineElement is null) return; 20328 if(caret.offset < caret.inlineElement.text.length && caret.inlineElement.text[caret.offset] != '\n') { 20329 caret.offset++; 20330 } else { 20331 auto p = caret.inlineElement.getNextInlineElement(); 20332 if(p) { 20333 caret.inlineElement = p; 20334 caret.offset = 0; 20335 } 20336 } 20337 } 20338 void moveHome() { 20339 if(caret.inlineElement is null) return; 20340 auto x = 0; 20341 auto y = caret.inlineElement.boundingBox.top + 2; 20342 20343 auto i = identify(x, y); 20344 20345 if(i.element) { 20346 caret.inlineElement = i.element; 20347 caret.offset = i.offset; 20348 } 20349 } 20350 void moveEnd() { 20351 if(caret.inlineElement is null) return; 20352 auto x = int.max; 20353 auto y = caret.inlineElement.boundingBox.top + 2; 20354 20355 auto i = identify(x, y); 20356 20357 if(i.element) { 20358 caret.inlineElement = i.element; 20359 caret.offset = i.offset; 20360 } 20361 20362 } 20363 void movePageUp(ref Caret caret) {} 20364 void movePageDown(ref Caret caret) {} 20365 20366 void moveDocumentStart(ref Caret caret) { 20367 if(blocks.length && blocks[0].parts.length) 20368 caret = Caret(this, blocks[0].parts[0], 0); 20369 else 20370 caret = Caret.init; 20371 } 20372 20373 void moveDocumentEnd(ref Caret caret) { 20374 if(blocks.length) { 20375 auto parts = blocks[$-1].parts; 20376 if(parts.length) { 20377 caret = Caret(this, parts[$-1], cast(int) parts[$-1].text.length); 20378 } else { 20379 caret = Caret.init; 20380 } 20381 } else 20382 caret = Caret.init; 20383 } 20384 20385 void deleteSelection() { 20386 if(selectionStart is selectionEnd) 20387 return; 20388 20389 if(selectionStart.inlineElement is null) return; 20390 if(selectionEnd.inlineElement is null) return; 20391 20392 assert(selectionStart.inlineElement !is null); 20393 assert(selectionEnd.inlineElement !is null); 20394 20395 auto at = selectionStart.inlineElement; 20396 20397 if(selectionEnd.inlineElement is at) { 20398 // same element, need to chop out 20399 at.text = at.text[0 .. selectionStart.offset] ~ at.text[selectionEnd.offset .. $]; 20400 at.letterXs = at.letterXs[0 .. selectionStart.offset] ~ at.letterXs[selectionEnd.offset .. $]; 20401 selectionEnd.offset -= selectionEnd.offset - selectionStart.offset; 20402 } else { 20403 // different elements, we can do it with slicing 20404 at.text = at.text[0 .. selectionStart.offset]; 20405 if(selectionStart.offset < at.letterXs.length) 20406 at.letterXs = at.letterXs[0 .. selectionStart.offset]; 20407 20408 at = at.getNextInlineElement(); 20409 20410 while(at) { 20411 if(at is selectionEnd.inlineElement) { 20412 at.text = at.text[selectionEnd.offset .. $]; 20413 if(selectionEnd.offset < at.letterXs.length) 20414 at.letterXs = at.letterXs[selectionEnd.offset .. $]; 20415 selectionEnd.offset = 0; 20416 break; 20417 } else { 20418 auto cfd = at; 20419 cfd.text = null; // delete the whole thing 20420 20421 at = at.getNextInlineElement(); 20422 20423 if(cfd.text.length == 0) { 20424 // and remove cfd 20425 for(size_t a = 0; a < cfd.containingBlock.parts.length; a++) { 20426 if(cfd.containingBlock.parts[a] is cfd) { 20427 for(size_t i = a; i < cfd.containingBlock.parts.length - 1; i++) 20428 cfd.containingBlock.parts[i] = cfd.containingBlock.parts[i + 1]; 20429 cfd.containingBlock.parts = cfd.containingBlock.parts[0 .. $-1]; 20430 20431 } 20432 } 20433 } 20434 } 20435 } 20436 } 20437 20438 caret = selectionEnd; 20439 selectNone(); 20440 20441 invalidateLayout(); 20442 20443 } 20444 20445 /// Plain text editing api. These work at the current caret inside the selected inline element. 20446 void insert(in char[] text) { 20447 foreach(dchar ch; text) 20448 insert(ch); 20449 } 20450 /// ditto 20451 void insert(dchar ch) { 20452 20453 bool selectionDeleted = false; 20454 if(selectionStart !is selectionEnd) { 20455 deleteSelection(); 20456 selectionDeleted = true; 20457 } 20458 20459 if(ch == 127) { 20460 delete_(); 20461 return; 20462 } 20463 if(ch == 8) { 20464 if(!selectionDeleted) 20465 backspace(); 20466 return; 20467 } 20468 20469 invalidateLayout(); 20470 20471 if(ch == 13) ch = 10; 20472 auto e = caret.inlineElement; 20473 if(e is null) { 20474 addText("" ~ cast(char) ch) ; // FIXME 20475 return; 20476 } 20477 20478 if(caret.offset == e.text.length) { 20479 e.text ~= cast(char) ch; // FIXME 20480 caret.offset++; 20481 if(ch == 10) { 20482 auto c = caret.inlineElement.clone; 20483 c.text = null; 20484 c.letterXs = null; 20485 insertPartAfter(c,e); 20486 caret = Caret(this, c, 0); 20487 } 20488 } else { 20489 // FIXME cast char sucks 20490 if(ch == 10) { 20491 auto c = caret.inlineElement.clone; 20492 c.text = e.text[caret.offset .. $]; 20493 if(caret.offset < c.letterXs.length) 20494 c.letterXs = e.letterXs[caret.offset .. $]; // FIXME boundingBox 20495 e.text = e.text[0 .. caret.offset] ~ cast(char) ch; 20496 if(caret.offset <= e.letterXs.length) { 20497 e.letterXs = e.letterXs[0 .. caret.offset] ~ 0; // FIXME bounding box 20498 } 20499 insertPartAfter(c,e); 20500 caret = Caret(this, c, 0); 20501 } else { 20502 e.text = e.text[0 .. caret.offset] ~ cast(char) ch ~ e.text[caret.offset .. $]; 20503 caret.offset++; 20504 } 20505 } 20506 } 20507 20508 void insertPartAfter(InlineElement what, InlineElement where) { 20509 foreach(idx, p; where.containingBlock.parts) { 20510 if(p is where) { 20511 if(idx + 1 == where.containingBlock.parts.length) 20512 where.containingBlock.parts ~= what; 20513 else 20514 where.containingBlock.parts = where.containingBlock.parts[0 .. idx + 1] ~ what ~ where.containingBlock.parts[idx + 1 .. $]; 20515 return; 20516 } 20517 } 20518 } 20519 20520 void cleanupStructures() { 20521 for(size_t i = 0; i < blocks.length; i++) { 20522 auto block = blocks[i]; 20523 for(size_t a = 0; a < block.parts.length; a++) { 20524 auto part = block.parts[a]; 20525 if(part.text.length == 0) { 20526 for(size_t b = a; b < block.parts.length - 1; b++) 20527 block.parts[b] = block.parts[b+1]; 20528 block.parts = block.parts[0 .. $-1]; 20529 } 20530 } 20531 if(block.parts.length == 0) { 20532 for(size_t a = i; a < blocks.length - 1; a++) 20533 blocks[a] = blocks[a+1]; 20534 blocks = blocks[0 .. $-1]; 20535 } 20536 } 20537 } 20538 20539 void backspace() { 20540 try_again: 20541 auto e = caret.inlineElement; 20542 if(e is null) 20543 return; 20544 if(caret.offset == 0) { 20545 auto prev = e.getPreviousInlineElement(); 20546 if(prev is null) 20547 return; 20548 auto newOffset = cast(int) prev.text.length; 20549 tryMerge(prev, e); 20550 caret.inlineElement = prev; 20551 caret.offset = prev is null ? 0 : newOffset; 20552 20553 goto try_again; 20554 } else if(caret.offset == e.text.length) { 20555 e.text = e.text[0 .. $-1]; 20556 caret.offset--; 20557 } else { 20558 e.text = e.text[0 .. caret.offset - 1] ~ e.text[caret.offset .. $]; 20559 caret.offset--; 20560 } 20561 //cleanupStructures(); 20562 20563 invalidateLayout(); 20564 } 20565 void delete_() { 20566 if(selectionStart !is selectionEnd) 20567 deleteSelection(); 20568 else { 20569 auto before = caret; 20570 moveRight(); 20571 if(caret != before) { 20572 backspace(); 20573 } 20574 } 20575 20576 invalidateLayout(); 20577 } 20578 void overstrike() {} 20579 20580 /// Selection API. See also: caret movement. 20581 void selectAll() { 20582 moveDocumentStart(selectionStart); 20583 moveDocumentEnd(selectionEnd); 20584 } 20585 bool selectNone() { 20586 if(selectionStart != selectionEnd) { 20587 selectionStart = selectionEnd = Caret.init; 20588 return true; 20589 } 20590 return false; 20591 } 20592 20593 /// Rich text editing api. These allow you to manipulate the meta data of the current element and add new elements. 20594 /// They will modify the current selection if there is one and will splice one in if needed. 20595 void changeAttributes() {} 20596 20597 20598 /// Text search api. They manipulate the selection and/or caret. 20599 void findText(string text) {} 20600 void findIndex(size_t textIndex) {} 20601 20602 // sample event handlers 20603 20604 void handleEvent(KeyEvent event) { 20605 //if(event.type == KeyEvent.Type.KeyPressed) { 20606 20607 //} 20608 } 20609 20610 void handleEvent(dchar ch) { 20611 20612 } 20613 20614 void handleEvent(MouseEvent event) { 20615 20616 } 20617 20618 bool contentEditable; // can it be edited? 20619 bool contentCaretable; // is there a caret/cursor that moves around in there? 20620 bool contentSelectable; // selectable? 20621 20622 Caret caret; 20623 Caret selectionStart; 20624 Caret selectionEnd; 20625 20626 bool insertMode; 20627 } 20628 20629 struct Caret { 20630 TextLayout layout; 20631 InlineElement inlineElement; 20632 int offset; 20633 } 20634 20635 enum TextFormat : ushort { 20636 // decorations 20637 underline = 1, 20638 strikethrough = 2, 20639 20640 // font selectors 20641 20642 bold = 0x4000 | 1, // weight 700 20643 light = 0x4000 | 2, // weight 300 20644 veryBoldOrLight = 0x4000 | 4, // weight 100 with light, weight 900 with bold 20645 // bold | light is really invalid but should give weight 500 20646 // veryBoldOrLight without one of the others should just give the default for the font; it should be ignored. 20647 20648 italic = 0x4000 | 8, 20649 smallcaps = 0x4000 | 16, 20650 } 20651 20652 void* findFont(string family, int weight, TextFormat formats) { 20653 return null; 20654 } 20655 20656 } 20657 20658 /++ 20659 $(PITFALL This is not yet stable and may break in future versions without notice.) 20660 20661 History: 20662 Added February 19, 2021 20663 +/ 20664 /// Group: drag_and_drop 20665 interface DropHandler { 20666 /++ 20667 Called when the drag enters the handler's area. 20668 +/ 20669 DragAndDropAction dragEnter(DropPackage*); 20670 /++ 20671 Called when the drag leaves the handler's area or is 20672 cancelled. You should free your resources when this is called. 20673 +/ 20674 void dragLeave(); 20675 /++ 20676 Called continually as the drag moves over the handler's area. 20677 20678 Returns: feedback to the dragger 20679 +/ 20680 DropParameters dragOver(Point pt); 20681 /++ 20682 The user dropped the data and you should process it now. You can 20683 access the data through the given [DropPackage]. 20684 +/ 20685 void drop(scope DropPackage*); 20686 /++ 20687 Called when the drop is complete. You should free whatever temporary 20688 resources you were using. It is often reasonable to simply forward 20689 this call to [dragLeave]. 20690 +/ 20691 void finish(); 20692 20693 /++ 20694 Parameters returned by [DropHandler.drop]. 20695 +/ 20696 static struct DropParameters { 20697 /++ 20698 Acceptable action over this area. 20699 +/ 20700 DragAndDropAction action; 20701 /++ 20702 Rectangle, in client coordinates, where the dragger can expect the same result during this drag session and thus need not ask again. 20703 20704 If you leave this as Rectangle.init, the dragger will continue to ask and this can waste resources. 20705 +/ 20706 Rectangle consistentWithin; 20707 } 20708 } 20709 20710 /++ 20711 History: 20712 Added February 19, 2021 20713 +/ 20714 /// Group: drag_and_drop 20715 enum DragAndDropAction { 20716 none = 0, 20717 copy, 20718 move, 20719 link, 20720 ask, 20721 custom 20722 } 20723 20724 /++ 20725 An opaque structure representing dropped data. It contains 20726 private, platform-specific data that your `drop` function 20727 should simply forward. 20728 20729 $(PITFALL This is not yet stable and may break in future versions without notice.) 20730 20731 History: 20732 Added February 19, 2021 20733 +/ 20734 /// Group: drag_and_drop 20735 struct DropPackage { 20736 /++ 20737 Lists the available formats as magic numbers. You should compare these 20738 against looked-up formats (see [DraggableData.getFormatId]) you know you support and can 20739 understand the passed data. 20740 +/ 20741 DraggableData.FormatId[] availableFormats() { 20742 version(X11) { 20743 return xFormats; 20744 } else version(Windows) { 20745 if(pDataObj is null) 20746 return null; 20747 20748 typeof(return) ret; 20749 20750 IEnumFORMATETC ef; 20751 if(pDataObj.EnumFormatEtc(DATADIR.DATADIR_GET, &ef) == S_OK) { 20752 FORMATETC fmt; 20753 ULONG fetched; 20754 while(ef.Next(1, &fmt, &fetched) == S_OK) { 20755 if(fetched == 0) 20756 break; 20757 20758 if(fmt.lindex != -1) 20759 continue; 20760 if(fmt.dwAspect != DVASPECT.DVASPECT_CONTENT) 20761 continue; 20762 if(!(fmt.tymed & TYMED.TYMED_HGLOBAL)) 20763 continue; 20764 20765 ret ~= fmt.cfFormat; 20766 } 20767 } 20768 20769 return ret; 20770 } else throw new NotYetImplementedException(); 20771 } 20772 20773 /++ 20774 Gets data from the drop and optionally accepts it. 20775 20776 Returns: 20777 void because the data is fed asynchronously through the `dg` parameter. 20778 20779 Params: 20780 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. 20781 20782 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. 20783 20784 Calling `getData` again after accepting a drop is not permitted. 20785 20786 format = the format you want, from [availableFormats]. Use [DraggableData.getFormatId] to convert from a MIME string or well-known standard format. 20787 20788 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. 20789 20790 Throws: 20791 if `format` was not compatible with the [availableFormats] or if the drop has already been accepted. 20792 20793 History: 20794 Included in first release of [DropPackage]. 20795 +/ 20796 void getData(DragAndDropAction acceptedAction, DraggableData.FormatId format, void delegate(scope ubyte[] data) dg) { 20797 version(X11) { 20798 20799 auto display = XDisplayConnection.get(); 20800 auto selectionAtom = GetAtom!"XdndSelection"(display); 20801 auto best = format; 20802 20803 static class X11GetSelectionHandler_Drop : X11GetSelectionHandler { 20804 20805 XDisplay* display; 20806 Atom selectionAtom; 20807 DraggableData.FormatId best; 20808 DraggableData.FormatId format; 20809 void delegate(scope ubyte[] data) dg; 20810 DragAndDropAction acceptedAction; 20811 Window sourceWindow; 20812 SimpleWindow win; 20813 this(XDisplay* display, SimpleWindow win, Window sourceWindow, DraggableData.FormatId format, Atom selectionAtom, DraggableData.FormatId best, void delegate(scope ubyte[] data) dg, DragAndDropAction acceptedAction) { 20814 this.display = display; 20815 this.win = win; 20816 this.sourceWindow = sourceWindow; 20817 this.format = format; 20818 this.selectionAtom = selectionAtom; 20819 this.best = best; 20820 this.dg = dg; 20821 this.acceptedAction = acceptedAction; 20822 } 20823 20824 20825 mixin X11GetSelectionHandler_Basics; 20826 20827 void handleData(Atom target, in ubyte[] data) { 20828 //if(target == GetAtom!"UTF8_STRING"(XDisplayConnection.get) || target == XA_STRING || target == GetAtom!"text/plain"(XDisplayConnection.get)) 20829 20830 dg(cast(ubyte[]) data); 20831 20832 if(acceptedAction != DragAndDropAction.none) { 20833 auto display = XDisplayConnection.get; 20834 20835 XClientMessageEvent xclient; 20836 20837 xclient.type = EventType.ClientMessage; 20838 xclient.window = sourceWindow; 20839 xclient.message_type = GetAtom!"XdndFinished"(display); 20840 xclient.format = 32; 20841 xclient.data.l[0] = win.impl.window; 20842 xclient.data.l[1] = 1; // drop successful 20843 xclient.data.l[2] = dndActionAtom(display, acceptedAction); 20844 20845 XSendEvent( 20846 display, 20847 sourceWindow, 20848 false, 20849 EventMask.NoEventMask, 20850 cast(XEvent*) &xclient 20851 ); 20852 20853 XFlush(display); 20854 } 20855 } 20856 20857 Atom findBestFormat(Atom[] answer) { 20858 Atom best = None; 20859 foreach(option; answer) { 20860 if(option == format) { 20861 best = option; 20862 break; 20863 } 20864 /* 20865 if(option == GetAtom!"UTF8_STRING"(XDisplayConnection.get)) { 20866 best = option; 20867 break; 20868 } else if(option == XA_STRING) { 20869 best = option; 20870 } else if(option == GetAtom!"text/plain"(XDisplayConnection.get)) { 20871 best = option; 20872 } 20873 */ 20874 } 20875 return best; 20876 } 20877 } 20878 20879 win.impl.getSelectionHandlers[selectionAtom] = new X11GetSelectionHandler_Drop(display, win, sourceWindow, format, selectionAtom, best, dg, acceptedAction); 20880 20881 XConvertSelection(display, selectionAtom, best, GetAtom!("SDD_DATA", true)(display), win.impl.window, dataTimestamp); 20882 20883 } else version(Windows) { 20884 20885 // clean up like DragLeave 20886 // pass effect back up 20887 20888 FORMATETC t; 20889 assert(format >= 0 && format <= ushort.max); 20890 t.cfFormat = cast(ushort) format; 20891 t.lindex = -1; 20892 t.dwAspect = DVASPECT.DVASPECT_CONTENT; 20893 t.tymed = TYMED.TYMED_HGLOBAL; 20894 20895 STGMEDIUM m; 20896 20897 if(pDataObj.GetData(&t, &m) != S_OK) { 20898 // fail 20899 } else { 20900 // succeed, take the data and clean up 20901 20902 // FIXME: ensure it is legit HGLOBAL 20903 auto handle = m.hGlobal; 20904 20905 if(handle) { 20906 auto sz = GlobalSize(handle); 20907 if(auto ptr = cast(ubyte*) GlobalLock(handle)) { 20908 scope(exit) GlobalUnlock(handle); 20909 scope(exit) GlobalFree(handle); 20910 20911 auto data = ptr[0 .. sz]; 20912 20913 dg(data); 20914 } 20915 } 20916 } 20917 } 20918 } 20919 20920 private: 20921 20922 version(X11) { 20923 SimpleWindow win; 20924 Window sourceWindow; 20925 Time dataTimestamp; 20926 20927 Atom[] xFormats; 20928 } 20929 version(Windows) { 20930 IDataObject pDataObj; 20931 } 20932 } 20933 20934 /++ 20935 A generic helper base class for making a drop handler with a preference list of custom types. 20936 This is the base for [TextDropHandler] and [FilesDropHandler] and you can use it for your own 20937 droppers too. 20938 20939 It assumes the whole window it used, but you can subclass to change that. 20940 20941 $(PITFALL This is not yet stable and may break in future versions without notice.) 20942 20943 History: 20944 Added February 19, 2021 20945 +/ 20946 /// Group: drag_and_drop 20947 class GenericDropHandlerBase : DropHandler { 20948 // no fancy state here so no need to do anything here 20949 void finish() { } 20950 void dragLeave() { } 20951 20952 private DragAndDropAction acceptedAction; 20953 private DraggableData.FormatId acceptedFormat; 20954 private void delegate(scope ubyte[]) acceptedHandler; 20955 20956 struct FormatHandler { 20957 DraggableData.FormatId format; 20958 void delegate(scope ubyte[]) handler; 20959 } 20960 20961 protected abstract FormatHandler[] formatHandlers(); 20962 20963 DragAndDropAction dragEnter(DropPackage* pkg) { 20964 debug(sdpy_dnd) { foreach(fmt; pkg.availableFormats()) writeln(fmt, " ", DraggableData.getFormatName(fmt)); } 20965 foreach(fmt; formatHandlers()) 20966 foreach(f; pkg.availableFormats()) 20967 if(f == fmt.format) { 20968 acceptedFormat = f; 20969 acceptedHandler = fmt.handler; 20970 return acceptedAction = DragAndDropAction.copy; 20971 } 20972 return acceptedAction = DragAndDropAction.none; 20973 } 20974 DropParameters dragOver(Point pt) { 20975 return DropParameters(acceptedAction); 20976 } 20977 20978 void drop(scope DropPackage* dropPackage) { 20979 if(!acceptedFormat || acceptedHandler is null) { 20980 debug(sdpy_dnd) { writeln("drop called w/ handler ", acceptedHandler, " and format ", acceptedFormat); } 20981 return; // prolly shouldn't happen anyway... 20982 } 20983 20984 dropPackage.getData(acceptedAction, acceptedFormat, acceptedHandler); 20985 } 20986 } 20987 20988 /++ 20989 A simple handler for making your window accept drops of plain text. 20990 20991 $(PITFALL This is not yet stable and may break in future versions without notice.) 20992 20993 History: 20994 Added February 22, 2021 20995 +/ 20996 /// Group: drag_and_drop 20997 class TextDropHandler : GenericDropHandlerBase { 20998 private void delegate(in char[] text) dg; 20999 21000 /++ 21001 21002 +/ 21003 this(void delegate(in char[] text) dg) { 21004 this.dg = dg; 21005 } 21006 21007 protected override FormatHandler[] formatHandlers() { 21008 version(X11) 21009 return [ 21010 FormatHandler(GetAtom!"UTF8_STRING"(XDisplayConnection.get), &translator), 21011 FormatHandler(GetAtom!"text/plain;charset=utf-8"(XDisplayConnection.get), &translator), 21012 ]; 21013 else version(Windows) 21014 return [ 21015 FormatHandler(CF_UNICODETEXT, &translator), 21016 ]; 21017 else throw new NotYetImplementedException(); 21018 } 21019 21020 private void translator(scope ubyte[] data) { 21021 version(X11) 21022 dg(cast(char[]) data); 21023 else version(Windows) 21024 dg(makeUtf8StringFromWindowsString(cast(wchar[]) data)); 21025 } 21026 } 21027 21028 /++ 21029 A simple handler for making your window accept drops of files, issued to you as file names. 21030 21031 $(PITFALL This is not yet stable and may break in future versions without notice.) 21032 21033 History: 21034 Added February 22, 2021 21035 +/ 21036 /// Group: drag_and_drop 21037 21038 class FilesDropHandler : GenericDropHandlerBase { 21039 private void delegate(in char[][]) dg; 21040 21041 /++ 21042 21043 +/ 21044 this(void delegate(in char[][] fileNames) dg) { 21045 this.dg = dg; 21046 } 21047 21048 protected override FormatHandler[] formatHandlers() { 21049 version(X11) 21050 return [ 21051 FormatHandler(GetAtom!"text/uri-list"(XDisplayConnection.get), &translator), 21052 ]; 21053 else version(Windows) 21054 return [ 21055 FormatHandler(CF_HDROP, &translator), 21056 ]; 21057 else throw new NotYetImplementedException(); 21058 } 21059 21060 private void translator(scope ubyte[] data) { 21061 version(X11) { 21062 char[] listString = cast(char[]) data; 21063 char[][16] buffer; 21064 int count; 21065 char[][] result = buffer[]; 21066 21067 void commit(char[] s) { 21068 if(count == result.length) 21069 result.length += 16; 21070 if(s.length > 7 && s[0 ..7] == "file://") 21071 s = s[7 .. $]; // FIXME: also may need to trim out the host and do some entity decoding 21072 result[count++] = s; 21073 } 21074 21075 size_t last; 21076 foreach(idx, char c; listString) { 21077 if(c == '\n') { 21078 commit(listString[last .. idx - 1]); // a \r 21079 last = idx + 1; // a \n 21080 } 21081 } 21082 21083 if(last < listString.length) { 21084 commit(listString[last .. $]); 21085 } 21086 21087 // FIXME: they are uris now, should I translate it to local file names? 21088 // of course the host name is supposed to be there cuz of X rokking... 21089 21090 dg(result[0 .. count]); 21091 } else version(Windows) { 21092 21093 static struct DROPFILES { 21094 DWORD pFiles; 21095 POINT pt; 21096 BOOL fNC; 21097 BOOL fWide; 21098 } 21099 21100 21101 const(char)[][16] buffer; 21102 int count; 21103 const(char)[][] result = buffer[]; 21104 size_t last; 21105 21106 void commitA(in char[] stuff) { 21107 if(count == result.length) 21108 result.length += 16; 21109 result[count++] = stuff; 21110 } 21111 21112 void commitW(in wchar[] stuff) { 21113 commitA(makeUtf8StringFromWindowsString(stuff)); 21114 } 21115 21116 void magic(T)(T chars) { 21117 size_t idx; 21118 while(chars[idx]) { 21119 last = idx; 21120 while(chars[idx]) { 21121 idx++; 21122 } 21123 static if(is(T == char*)) 21124 commitA(chars[last .. idx]); 21125 else 21126 commitW(chars[last .. idx]); 21127 idx++; 21128 } 21129 } 21130 21131 auto df = cast(DROPFILES*) data.ptr; 21132 if(df.fWide) { 21133 wchar* chars = cast(wchar*) (data.ptr + df.pFiles); 21134 magic(chars); 21135 } else { 21136 char* chars = cast(char*) (data.ptr + df.pFiles); 21137 magic(chars); 21138 } 21139 dg(result[0 .. count]); 21140 } 21141 else throw new NotYetImplementedException(); 21142 } 21143 } 21144 21145 /++ 21146 Interface to describe data being dragged. See also [draggable] helper function. 21147 21148 $(PITFALL This is not yet stable and may break in future versions without notice.) 21149 21150 History: 21151 Added February 19, 2021 21152 +/ 21153 interface DraggableData { 21154 version(X11) 21155 alias FormatId = Atom; 21156 else 21157 alias FormatId = uint; 21158 /++ 21159 Gets the platform-specific FormatId associated with the given named format. 21160 21161 This may be a MIME type, but may also be other various strings defined by the 21162 programs you want to interoperate with. 21163 21164 FIXME: sdpy needs to offer data adapter things that look for compatible formats 21165 and convert it to some particular type for you. 21166 +/ 21167 static FormatId getFormatId(string name)() { 21168 version(X11) 21169 return GetAtom!name(XDisplayConnection.get); 21170 else version(Windows) { 21171 static UINT cache; 21172 if(!cache) 21173 cache = RegisterClipboardFormatA(name); 21174 return cache; 21175 } else 21176 throw new NotYetImplementedException(); 21177 } 21178 21179 /++ 21180 Looks up a string to represent the name for the given format, if there is one. 21181 21182 You should avoid using this function because it is slow. It is provided more for 21183 debugging than for primary use. 21184 +/ 21185 static string getFormatName(FormatId format) { 21186 version(X11) { 21187 if(format == 0) 21188 return "None"; 21189 else 21190 return getAtomName(format, XDisplayConnection.get); 21191 } else version(Windows) { 21192 switch(format) { 21193 case CF_UNICODETEXT: return "CF_UNICODETEXT"; 21194 case CF_DIBV5: return "CF_DIBV5"; 21195 case CF_RIFF: return "CF_RIFF"; 21196 case CF_WAVE: return "CF_WAVE"; 21197 case CF_HDROP: return "CF_HDROP"; 21198 default: 21199 char[1024] name; 21200 auto count = GetClipboardFormatNameA(format, name.ptr, name.length); 21201 return name[0 .. count].idup; 21202 } 21203 } else throw new NotYetImplementedException(); 21204 } 21205 21206 FormatId[] availableFormats(); 21207 // Return the slice of data you filled, empty slice if done. 21208 // this is to support the incremental thing 21209 ubyte[] getData(FormatId format, return scope ubyte[] data); 21210 21211 size_t dataLength(FormatId format); 21212 } 21213 21214 /++ 21215 $(PITFALL This is not yet stable and may break in future versions without notice.) 21216 21217 History: 21218 Added February 19, 2021 21219 +/ 21220 DraggableData draggable(string s) { 21221 version(X11) 21222 return new class X11SetSelectionHandler_Text, DraggableData { 21223 this() { 21224 super(s); 21225 } 21226 21227 override FormatId[] availableFormats() { 21228 return X11SetSelectionHandler_Text.availableFormats(); 21229 } 21230 21231 override ubyte[] getData(FormatId format, return scope ubyte[] data) { 21232 return X11SetSelectionHandler_Text.getData(format, data); 21233 } 21234 21235 size_t dataLength(FormatId format) { 21236 return s.length; 21237 } 21238 }; 21239 else version(Windows) 21240 return new class DraggableData { 21241 FormatId[] availableFormats() { 21242 return [CF_UNICODETEXT]; 21243 } 21244 21245 ubyte[] getData(FormatId format, return scope ubyte[] data) { 21246 return cast(ubyte[]) makeWindowsString(s, cast(wchar[]) data, WindowsStringConversionFlags.convertNewLines | WindowsStringConversionFlags.zeroTerminate); 21247 } 21248 21249 size_t dataLength(FormatId format) { 21250 return sizeOfConvertedWstring(s, WindowsStringConversionFlags.convertNewLines | WindowsStringConversionFlags.zeroTerminate) * wchar.sizeof; 21251 } 21252 }; 21253 else 21254 throw new NotYetImplementedException(); 21255 } 21256 21257 /++ 21258 $(PITFALL This is not yet stable and may break in future versions without notice.) 21259 21260 History: 21261 Added February 19, 2021 21262 +/ 21263 /// Group: drag_and_drop 21264 int doDragDrop(SimpleWindow window, DraggableData handler, DragAndDropAction action = DragAndDropAction.copy) 21265 in { 21266 assert(window !is null); 21267 assert(handler !is null); 21268 } 21269 do 21270 { 21271 version(X11) { 21272 auto sh = cast(X11SetSelectionHandler) handler; 21273 if(sh is null) { 21274 // gotta make my own adapter. 21275 sh = new class X11SetSelectionHandler { 21276 mixin X11SetSelectionHandler_Basics; 21277 21278 Atom[] availableFormats() { return handler.availableFormats(); } 21279 ubyte[] getData(Atom format, return scope ubyte[] data) { 21280 return handler.getData(format, data); 21281 } 21282 21283 // since the drop selection is only ever used once it isn't important 21284 // to reset it. 21285 void done() {} 21286 }; 21287 } 21288 return doDragDropX11(window, sh, action); 21289 } else version(Windows) { 21290 return doDragDropWindows(window, handler, action); 21291 } else throw new NotYetImplementedException(); 21292 } 21293 21294 version(Windows) 21295 private int doDragDropWindows(SimpleWindow window, DraggableData handler, DragAndDropAction action = DragAndDropAction.copy) { 21296 IDataObject obj = new class IDataObject { 21297 ULONG refCount; 21298 ULONG AddRef() { 21299 return ++refCount; 21300 } 21301 ULONG Release() { 21302 return --refCount; 21303 } 21304 HRESULT QueryInterface(const (IID)*riid, LPVOID *ppv) { 21305 if (IID_IUnknown == *riid) { 21306 *ppv = cast(void*) cast(IUnknown) this; 21307 } 21308 else if (IID_IDataObject == *riid) { 21309 *ppv = cast(void*) cast(IDataObject) this; 21310 } 21311 else { 21312 *ppv = null; 21313 return E_NOINTERFACE; 21314 } 21315 21316 AddRef(); 21317 return NOERROR; 21318 } 21319 21320 HRESULT DAdvise(FORMATETC* pformatetc, DWORD advf, IAdviseSink pAdvSink, DWORD* pdwConnection) { 21321 // writeln("Advise"); 21322 return E_NOTIMPL; 21323 } 21324 HRESULT DUnadvise(DWORD dwConnection) { 21325 return E_NOTIMPL; 21326 } 21327 HRESULT EnumDAdvise(IEnumSTATDATA* ppenumAdvise) { 21328 // writeln("EnumDAdvise"); 21329 return OLE_E_ADVISENOTSUPPORTED; 21330 } 21331 // tell what formats it supports 21332 21333 FORMATETC[] types; 21334 this() { 21335 FORMATETC t; 21336 foreach(ty; handler.availableFormats()) { 21337 assert(ty <= ushort.max && ty >= 0); 21338 t.cfFormat = cast(ushort) ty; 21339 t.lindex = -1; 21340 t.dwAspect = DVASPECT.DVASPECT_CONTENT; 21341 t.tymed = TYMED.TYMED_HGLOBAL; 21342 } 21343 types ~= t; 21344 } 21345 HRESULT EnumFormatEtc(DWORD dwDirection, IEnumFORMATETC* ppenumFormatEtc) { 21346 if(dwDirection == DATADIR.DATADIR_GET) { 21347 *ppenumFormatEtc = new class IEnumFORMATETC { 21348 ULONG refCount; 21349 ULONG AddRef() { 21350 return ++refCount; 21351 } 21352 ULONG Release() { 21353 return --refCount; 21354 } 21355 HRESULT QueryInterface(const (IID)*riid, LPVOID *ppv) { 21356 if (IID_IUnknown == *riid) { 21357 *ppv = cast(void*) cast(IUnknown) this; 21358 } 21359 else if (IID_IEnumFORMATETC == *riid) { 21360 *ppv = cast(void*) cast(IEnumFORMATETC) this; 21361 } 21362 else { 21363 *ppv = null; 21364 return E_NOINTERFACE; 21365 } 21366 21367 AddRef(); 21368 return NOERROR; 21369 } 21370 21371 21372 int pos; 21373 this() { 21374 pos = 0; 21375 } 21376 21377 HRESULT Clone(IEnumFORMATETC* ppenum) { 21378 // writeln("clone"); 21379 return E_NOTIMPL; // FIXME 21380 } 21381 21382 // Caller is responsible for freeing memory 21383 HRESULT Next(ULONG celt, FORMATETC* rgelt, ULONG* pceltFetched) { 21384 // fetched may be null if celt is one 21385 if(celt != 1) 21386 return E_NOTIMPL; // FIXME 21387 21388 if(celt + pos > types.length) 21389 return S_FALSE; 21390 21391 *rgelt = types[pos++]; 21392 21393 if(pceltFetched !is null) 21394 *pceltFetched = 1; 21395 21396 // writeln("ok celt ", celt); 21397 return S_OK; 21398 } 21399 21400 HRESULT Reset() { 21401 pos = 0; 21402 return S_OK; 21403 } 21404 21405 HRESULT Skip(ULONG celt) { 21406 if(celt + pos <= types.length) { 21407 pos += celt; 21408 return S_OK; 21409 } 21410 return S_FALSE; 21411 } 21412 }; 21413 21414 return S_OK; 21415 } else 21416 return E_NOTIMPL; 21417 } 21418 // given a format, return the format you'd prefer to use cuz it is identical 21419 HRESULT GetCanonicalFormatEtc(FORMATETC* pformatectIn, FORMATETC* pformatetcOut) { 21420 // FIXME: prolly could be better but meh 21421 // writeln("gcf: ", *pformatectIn); 21422 *pformatetcOut = *pformatectIn; 21423 return S_OK; 21424 } 21425 HRESULT GetData(FORMATETC* pformatetcIn, STGMEDIUM* pmedium) { 21426 foreach(ty; types) { 21427 if(ty == *pformatetcIn) { 21428 auto format = ty.cfFormat; 21429 // writeln("A: ", *pformatetcIn, "\nB: ", ty); 21430 STGMEDIUM medium; 21431 medium.tymed = TYMED.TYMED_HGLOBAL; 21432 21433 auto sz = handler.dataLength(format); 21434 auto handle = GlobalAlloc(GMEM_MOVEABLE, sz); 21435 if(handle is null) throw new WindowsApiException("GlobalAlloc", GetLastError()); 21436 if(auto data = cast(wchar*) GlobalLock(handle)) { 21437 auto slice = data[0 .. sz]; 21438 scope(exit) 21439 GlobalUnlock(handle); 21440 21441 handler.getData(format, cast(ubyte[]) slice[]); 21442 } 21443 21444 21445 medium.hGlobal = handle; // FIXME 21446 *pmedium = medium; 21447 return S_OK; 21448 } 21449 } 21450 return DV_E_FORMATETC; 21451 } 21452 HRESULT GetDataHere(FORMATETC* pformatetcIn, STGMEDIUM* pmedium) { 21453 // writeln("GDH: ", *pformatetcIn); 21454 return E_NOTIMPL; // FIXME 21455 } 21456 HRESULT QueryGetData(FORMATETC* pformatetc) { 21457 auto search = *pformatetc; 21458 search.tymed &= TYMED.TYMED_HGLOBAL; 21459 foreach(ty; types) 21460 if(ty == search) { 21461 // writeln("QueryGetData ", search, " ", types[0]); 21462 return S_OK; 21463 } 21464 if(pformatetc.cfFormat==CF_UNICODETEXT) { 21465 //writeln("QueryGetData FALSE ", search, " ", types[0]); 21466 } 21467 return S_FALSE; 21468 } 21469 HRESULT SetData(FORMATETC* pformatetc, STGMEDIUM* pmedium, BOOL fRelease) { 21470 // writeln("SetData: "); 21471 return E_NOTIMPL; 21472 } 21473 }; 21474 21475 21476 IDropSource src = new class IDropSource { 21477 ULONG refCount; 21478 ULONG AddRef() { 21479 return ++refCount; 21480 } 21481 ULONG Release() { 21482 return --refCount; 21483 } 21484 HRESULT QueryInterface(const (IID)*riid, LPVOID *ppv) { 21485 if (IID_IUnknown == *riid) { 21486 *ppv = cast(void*) cast(IUnknown) this; 21487 } 21488 else if (IID_IDropSource == *riid) { 21489 *ppv = cast(void*) cast(IDropSource) this; 21490 } 21491 else { 21492 *ppv = null; 21493 return E_NOINTERFACE; 21494 } 21495 21496 AddRef(); 21497 return NOERROR; 21498 } 21499 21500 int QueryContinueDrag(int fEscapePressed, uint grfKeyState) { 21501 if(fEscapePressed) 21502 return DRAGDROP_S_CANCEL; 21503 if(!(grfKeyState & MK_LBUTTON)) 21504 return DRAGDROP_S_DROP; 21505 return S_OK; 21506 } 21507 21508 int GiveFeedback(uint dwEffect) { 21509 return DRAGDROP_S_USEDEFAULTCURSORS; 21510 } 21511 }; 21512 21513 DWORD effect; 21514 21515 if(action == DragAndDropAction.none) assert(0, "Don't drag something with a none effect."); 21516 21517 DROPEFFECT de = win32DragAndDropAction(action); 21518 21519 // I'm not as concerned about the GC here since DoDragDrop blocks so the stack frame still sane the whole time 21520 // but still prolly a FIXME 21521 21522 auto ret = DoDragDrop(obj, src, de, &effect); 21523 /+ 21524 if(ret == DRAGDROP_S_DROP) 21525 writeln("drop ", effect); 21526 else if(ret == DRAGDROP_S_CANCEL) 21527 writeln("cancel"); 21528 else if(ret == S_OK) 21529 writeln("ok"); 21530 else writeln(ret); 21531 +/ 21532 21533 return ret; 21534 } 21535 21536 version(Windows) 21537 DROPEFFECT win32DragAndDropAction(DragAndDropAction action) { 21538 DROPEFFECT de; 21539 21540 with(DragAndDropAction) 21541 with(DROPEFFECT) 21542 final switch(action) { 21543 case none: de = DROPEFFECT_NONE; break; 21544 case copy: de = DROPEFFECT_COPY; break; 21545 case move: de = DROPEFFECT_MOVE; break; 21546 case link: de = DROPEFFECT_LINK; break; 21547 case ask: throw new Exception("ask not implemented yet"); 21548 case custom: throw new Exception("custom not implemented yet"); 21549 } 21550 21551 return de; 21552 } 21553 21554 21555 /++ 21556 History: 21557 Added February 19, 2021 21558 +/ 21559 /// Group: drag_and_drop 21560 void enableDragAndDrop(SimpleWindow window, DropHandler handler) { 21561 version(X11) { 21562 auto display = XDisplayConnection.get; 21563 21564 Atom atom = 5; // right??? 21565 21566 XChangeProperty( 21567 display, 21568 window.impl.window, 21569 GetAtom!"XdndAware"(display), 21570 XA_ATOM, 21571 32 /* bits */, 21572 PropModeReplace, 21573 &atom, 21574 1); 21575 21576 window.dropHandler = handler; 21577 } else version(Windows) { 21578 21579 initDnd(); 21580 21581 auto dropTarget = new class (handler) IDropTarget { 21582 DropHandler handler; 21583 this(DropHandler handler) { 21584 this.handler = handler; 21585 } 21586 ULONG refCount; 21587 ULONG AddRef() { 21588 return ++refCount; 21589 } 21590 ULONG Release() { 21591 return --refCount; 21592 } 21593 HRESULT QueryInterface(const (IID)*riid, LPVOID *ppv) { 21594 if (IID_IUnknown == *riid) { 21595 *ppv = cast(void*) cast(IUnknown) this; 21596 } 21597 else if (IID_IDropTarget == *riid) { 21598 *ppv = cast(void*) cast(IDropTarget) this; 21599 } 21600 else { 21601 *ppv = null; 21602 return E_NOINTERFACE; 21603 } 21604 21605 AddRef(); 21606 return NOERROR; 21607 } 21608 21609 21610 // /////////////////// 21611 21612 HRESULT DragEnter(IDataObject pDataObj, DWORD grfKeyState, POINTL pt, DWORD* pdwEffect) { 21613 DropPackage dropPackage = DropPackage(pDataObj); 21614 *pdwEffect = win32DragAndDropAction(handler.dragEnter(&dropPackage)); 21615 return S_OK; // https://docs.microsoft.com/en-us/windows/win32/api/oleidl/nf-oleidl-idroptarget-dragenter 21616 } 21617 21618 HRESULT DragLeave() { 21619 handler.dragLeave(); 21620 // release the IDataObject if needed 21621 return S_OK; 21622 } 21623 21624 HRESULT DragOver(DWORD grfKeyState, POINTL pt, DWORD* pdwEffect) { 21625 auto res = handler.dragOver(Point(pt.x, pt.y)); // FIXME: translate screen coordinates back to window coordinates 21626 21627 *pdwEffect = win32DragAndDropAction(res.action); 21628 // same as DragEnter basically 21629 return S_OK; 21630 } 21631 21632 HRESULT Drop(IDataObject pDataObj, DWORD grfKeyState, POINTL pt, DWORD* pdwEffect) { 21633 DropPackage pkg = DropPackage(pDataObj); 21634 handler.drop(&pkg); 21635 21636 return S_OK; 21637 } 21638 }; 21639 // Windows can hold on to the handler and try to call it 21640 // during which time the GC can't see it. so important to 21641 // manually manage this. At some point i'll FIXME and make 21642 // all my com instances manually managed since they supposed 21643 // to respect the refcount. 21644 import core.memory; 21645 GC.addRoot(cast(void*) dropTarget); 21646 21647 if(RegisterDragDrop(window.impl.hwnd, dropTarget) != S_OK) 21648 throw new WindowsApiException("RegisterDragDrop", GetLastError()); 21649 21650 window.dropHandler = handler; 21651 } else throw new NotYetImplementedException(); 21652 } 21653 21654 21655 21656 static if(UsingSimpledisplayX11) { 21657 21658 enum _NET_WM_STATE_ADD = 1; 21659 enum _NET_WM_STATE_REMOVE = 0; 21660 enum _NET_WM_STATE_TOGGLE = 2; 21661 21662 /// X-specific. Use [SimpleWindow.requestAttention] instead for most cases. 21663 void demandAttention(SimpleWindow window, bool needs = true) { 21664 demandAttention(window.impl.window, needs); 21665 } 21666 21667 /// ditto 21668 void demandAttention(Window window, bool needs = true) { 21669 setNetWmStateAtom(window, GetAtom!("_NET_WM_STATE_DEMANDS_ATTENTION", false)(XDisplayConnection.get), needs); 21670 } 21671 21672 void setNetWmStateAtom(Window window, Atom atom, bool set = true, Atom atom2 = None) { 21673 auto display = XDisplayConnection.get(); 21674 if(atom == None) 21675 return; // non-failure error 21676 //auto atom2 = GetAtom!"_NET_WM_STATE_SHADED"(display); 21677 21678 XClientMessageEvent xclient; 21679 21680 xclient.type = EventType.ClientMessage; 21681 xclient.window = window; 21682 xclient.message_type = GetAtom!"_NET_WM_STATE"(display); 21683 xclient.format = 32; 21684 xclient.data.l[0] = set ? _NET_WM_STATE_ADD : _NET_WM_STATE_REMOVE; 21685 xclient.data.l[1] = atom; 21686 xclient.data.l[2] = atom2; 21687 xclient.data.l[3] = 1; 21688 // [3] == source. 0 == unknown, 1 == app, 2 == else 21689 21690 XSendEvent( 21691 display, 21692 RootWindow(display, DefaultScreen(display)), 21693 false, 21694 EventMask.SubstructureRedirectMask | EventMask.SubstructureNotifyMask, 21695 cast(XEvent*) &xclient 21696 ); 21697 21698 /+ 21699 XChangeProperty( 21700 display, 21701 window.impl.window, 21702 GetAtom!"_NET_WM_STATE"(display), 21703 XA_ATOM, 21704 32 /* bits */, 21705 PropModeAppend, 21706 &atom, 21707 1); 21708 +/ 21709 } 21710 21711 private Atom dndActionAtom(Display* display, DragAndDropAction action) { 21712 Atom actionAtom; 21713 with(DragAndDropAction) 21714 final switch(action) { 21715 case none: actionAtom = None; break; 21716 case copy: actionAtom = GetAtom!"XdndActionCopy"(display); break; 21717 case move: actionAtom = GetAtom!"XdndActionMove"(display); break; 21718 case link: actionAtom = GetAtom!"XdndActionLink"(display); break; 21719 case ask: actionAtom = GetAtom!"XdndActionAsk"(display); break; 21720 case custom: actionAtom = GetAtom!"XdndActionCustom"(display); break; 21721 } 21722 21723 return actionAtom; 21724 } 21725 21726 private int doDragDropX11(SimpleWindow window, X11SetSelectionHandler handler, DragAndDropAction action) { 21727 // FIXME: I need to show user feedback somehow. 21728 auto display = XDisplayConnection.get; 21729 21730 auto actionAtom = dndActionAtom(display, action); 21731 assert(actionAtom, "Don't use action none to accept a drop"); 21732 21733 setX11Selection!"XdndSelection"(window, handler, null); 21734 21735 auto oldKeyHandler = window.handleKeyEvent; 21736 scope(exit) window.handleKeyEvent = oldKeyHandler; 21737 21738 auto oldCharHandler = window.handleCharEvent; 21739 scope(exit) window.handleCharEvent = oldCharHandler; 21740 21741 auto oldMouseHandler = window.handleMouseEvent; 21742 scope(exit) window.handleMouseEvent = oldMouseHandler; 21743 21744 Window[Window] eligibility; // 0 == not eligible, otherwise it is the window id of an eligible child 21745 21746 import core.sys.posix.sys.time; 21747 timeval tv; 21748 gettimeofday(&tv, null); 21749 21750 Time dataTimestamp = tv.tv_sec * 1000 + tv.tv_usec / 1000; 21751 21752 Time lastMouseTimestamp; 21753 21754 bool dnding = true; 21755 Window lastIn = None; 21756 21757 void leave() { 21758 if(lastIn == None) 21759 return; 21760 21761 XEvent ev; 21762 ev.xclient.type = EventType.ClientMessage; 21763 ev.xclient.window = lastIn; 21764 ev.xclient.message_type = GetAtom!("XdndLeave", true)(display); 21765 ev.xclient.format = 32; 21766 ev.xclient.data.l[0] = window.impl.window; 21767 21768 XSendEvent(XDisplayConnection.get, lastIn, false, EventMask.NoEventMask, &ev); 21769 XFlush(display); 21770 21771 lastIn = None; 21772 } 21773 21774 void enter(Window w) { 21775 assert(lastIn == None); 21776 21777 lastIn = w; 21778 21779 XEvent ev; 21780 ev.xclient.type = EventType.ClientMessage; 21781 ev.xclient.window = lastIn; 21782 ev.xclient.message_type = GetAtom!("XdndEnter", true)(display); 21783 ev.xclient.format = 32; 21784 ev.xclient.data.l[0] = window.impl.window; 21785 ev.xclient.data.l[1] = (5 << 24) | 0; // version 5, no more sources. FIXME source types 21786 21787 auto types = handler.availableFormats(); 21788 assert(types.length > 0); 21789 21790 ev.xclient.data.l[2] = types[0]; 21791 if(types.length > 1) 21792 ev.xclient.data.l[3] = types[1]; 21793 if(types.length > 2) 21794 ev.xclient.data.l[4] = types[2]; 21795 21796 // FIXME: other types?!?!? and make sure we skip TARGETS 21797 21798 XSendEvent(XDisplayConnection.get, lastIn, false, EventMask.NoEventMask, &ev); 21799 XFlush(display); 21800 } 21801 21802 void position(int rootX, int rootY) { 21803 assert(lastIn != None); 21804 21805 XEvent ev; 21806 ev.xclient.type = EventType.ClientMessage; 21807 ev.xclient.window = lastIn; 21808 ev.xclient.message_type = GetAtom!("XdndPosition", true)(display); 21809 ev.xclient.format = 32; 21810 ev.xclient.data.l[0] = window.impl.window; 21811 ev.xclient.data.l[1] = 0; // reserved 21812 ev.xclient.data.l[2] = (rootX << 16) | rootY; 21813 ev.xclient.data.l[3] = dataTimestamp; 21814 ev.xclient.data.l[4] = actionAtom; 21815 21816 XSendEvent(XDisplayConnection.get, lastIn, false, EventMask.NoEventMask, &ev); 21817 XFlush(display); 21818 21819 } 21820 21821 void drop() { 21822 XEvent ev; 21823 ev.xclient.type = EventType.ClientMessage; 21824 ev.xclient.window = lastIn; 21825 ev.xclient.message_type = GetAtom!("XdndDrop", true)(display); 21826 ev.xclient.format = 32; 21827 ev.xclient.data.l[0] = window.impl.window; 21828 ev.xclient.data.l[1] = 0; // reserved 21829 ev.xclient.data.l[2] = dataTimestamp; 21830 21831 XSendEvent(XDisplayConnection.get, lastIn, false, EventMask.NoEventMask, &ev); 21832 XFlush(display); 21833 21834 lastIn = None; 21835 dnding = false; 21836 } 21837 21838 // fyi nativeEventHandler can return 0 if it handles it, or otherwise it goes back to the normal handler 21839 // but idk if i should... 21840 21841 window.setEventHandlers( 21842 delegate(KeyEvent ev) { 21843 if(ev.pressed == true && ev.key == Key.Escape) { 21844 // cancel 21845 dnding = false; 21846 } 21847 }, 21848 delegate(MouseEvent ev) { 21849 if(ev.timestamp < lastMouseTimestamp) 21850 return; 21851 21852 lastMouseTimestamp = ev.timestamp; 21853 21854 if(ev.type == MouseEventType.motion) { 21855 auto display = XDisplayConnection.get; 21856 auto root = RootWindow(display, DefaultScreen(display)); 21857 21858 Window topWindow; 21859 int rootX, rootY; 21860 21861 XTranslateCoordinates(display, window.impl.window, root, ev.x, ev.y, &rootX, &rootY, &topWindow); 21862 21863 if(topWindow == None) 21864 return; 21865 21866 top: 21867 if(auto result = topWindow in eligibility) { 21868 auto dropWindow = *result; 21869 if(dropWindow == None) { 21870 leave(); 21871 return; 21872 } 21873 21874 if(dropWindow != lastIn) { 21875 leave(); 21876 enter(dropWindow); 21877 position(rootX, rootY); 21878 } else { 21879 position(rootX, rootY); 21880 } 21881 } else { 21882 // determine eligibility 21883 auto data = cast(Atom[]) getX11PropertyData(topWindow, GetAtom!"XdndAware"(display), XA_ATOM); 21884 if(data.length == 1) { 21885 // in case there is no WM or it isn't reparenting 21886 eligibility[topWindow] = (data[0] == 5) ? topWindow : None; // FIXME I'm supposed to handle older versions too but meh 21887 } else { 21888 21889 Window tryScanChildren(Window search, int maxRecurse) { 21890 // could be reparenting window manager, so gotta check the next few children too 21891 Window child; 21892 int x; 21893 int y; 21894 XTranslateCoordinates(display, window.impl.window, search, ev.x, ev.y, &x, &y, &child); 21895 21896 if(child == None) 21897 return None; 21898 auto data = cast(Atom[]) getX11PropertyData(child, GetAtom!"XdndAware"(display), XA_ATOM); 21899 if(data.length == 1) { 21900 return (data[0] == 5) ? child : None; // FIXME I'm supposed to handle older versions too but meh 21901 } else { 21902 if(maxRecurse) 21903 return tryScanChildren(child, maxRecurse - 1); 21904 else 21905 return None; 21906 } 21907 21908 } 21909 21910 // if a WM puts more than 3 layers on it, like wtf is it doing, screw that. 21911 auto topResult = tryScanChildren(topWindow, 3); 21912 // it is easy to have a false negative due to the mouse going over a WM 21913 // child window like the close button if separate from the frame... so I 21914 // can't really cache negatives, :( 21915 if(topResult != None) { 21916 eligibility[topWindow] = topResult; 21917 goto top; // reload to do the positioning iff eligibility changed lest we endless loop 21918 } 21919 } 21920 21921 } 21922 21923 } else if(ev.type == MouseEventType.buttonReleased) { 21924 drop(); 21925 dnding = false; 21926 } 21927 } 21928 ); 21929 21930 window.grabInput(); 21931 scope(exit) 21932 window.releaseInputGrab(); 21933 21934 21935 EventLoop.get.run(() => dnding); 21936 21937 return 0; 21938 } 21939 21940 /// X-specific 21941 TrueColorImage getWindowNetWmIcon(Window window) { 21942 try { 21943 auto display = XDisplayConnection.get; 21944 21945 auto data = getX11PropertyData (window, GetAtom!"_NET_WM_ICON"(display), XA_CARDINAL); 21946 21947 if (data.length > arch_ulong.sizeof * 2) { 21948 auto meta = cast(arch_ulong[]) (data[0 .. arch_ulong.sizeof * 2]); 21949 // these are an array of rgba images that we have to convert into pixmaps ourself 21950 21951 int width = cast(int) meta[0]; 21952 int height = cast(int) meta[1]; 21953 21954 auto bytes = cast(ubyte[]) (data[arch_ulong.sizeof * 2 .. $]); 21955 21956 static if(arch_ulong.sizeof == 4) { 21957 bytes = bytes[0 .. width * height * 4]; 21958 alias imageData = bytes; 21959 } else static if(arch_ulong.sizeof == 8) { 21960 bytes = bytes[0 .. width * height * 8]; 21961 auto imageData = new ubyte[](4 * width * height); 21962 } else static assert(0); 21963 21964 21965 21966 // this returns ARGB. Remember it is little-endian so 21967 // we have BGRA 21968 // our thing uses RGBA, which in little endian, is ABGR 21969 for(int idx = 0, idx2 = 0; idx < bytes.length; idx += arch_ulong.sizeof, idx2 += 4) { 21970 auto r = bytes[idx + 2]; 21971 auto g = bytes[idx + 1]; 21972 auto b = bytes[idx + 0]; 21973 auto a = bytes[idx + 3]; 21974 21975 imageData[idx2 + 0] = r; 21976 imageData[idx2 + 1] = g; 21977 imageData[idx2 + 2] = b; 21978 imageData[idx2 + 3] = a; 21979 } 21980 21981 return new TrueColorImage(width, height, imageData); 21982 } 21983 21984 return null; 21985 } catch(Exception e) { 21986 return null; 21987 } 21988 } 21989 21990 } /* UsingSimpledisplayX11 */ 21991 21992 21993 void loadBinNameToWindowClassName () { 21994 import core.stdc.stdlib : realloc; 21995 version(linux) { 21996 // args[0] MAY be empty, so we'll just use this 21997 import core.sys.posix.unistd : readlink; 21998 char[1024] ebuf = void; // 1KB should be enough for everyone! 21999 auto len = readlink("/proc/self/exe", ebuf.ptr, ebuf.length); 22000 if (len < 1) return; 22001 } else /*version(Windows)*/ { 22002 import core.runtime : Runtime; 22003 if (Runtime.args.length == 0 || Runtime.args[0].length == 0) return; 22004 auto ebuf = Runtime.args[0]; 22005 auto len = ebuf.length; 22006 } 22007 auto pos = len; 22008 while (pos > 0 && ebuf[pos-1] != '/') --pos; 22009 sdpyWindowClassStr = cast(char*)realloc(sdpyWindowClassStr, len-pos+1); 22010 if (sdpyWindowClassStr is null) return; // oops 22011 sdpyWindowClassStr[0..len-pos+1] = 0; // just in case 22012 sdpyWindowClassStr[0..len-pos] = ebuf[pos..len]; 22013 } 22014 22015 /++ 22016 An interface representing a font that is drawn with custom facilities. 22017 22018 You might want [OperatingSystemFont] instead, which represents 22019 a font loaded and drawn by functions native to the operating system. 22020 22021 WARNING: I might still change this. 22022 +/ 22023 interface DrawableFont : MeasurableFont { 22024 /++ 22025 Please note the point is upperLeft, NOT baseline! This is the point of a bounding box of the string. 22026 22027 Implementations must use the painter's fillColor to draw a rectangle behind the string, 22028 then use the outlineColor to draw the string. It might alpha composite if there's a transparent 22029 fill color, but that's up to the implementation. 22030 +/ 22031 void drawString(ScreenPainter painter, Point upperLeft, in char[] text); 22032 22033 /++ 22034 Requests that the given string is added to the image cache. You should only do this rarely, but 22035 if you have a string that you know will be used over and over again, adding it to a cache can 22036 improve things (assuming the implementation actually has a cache; it is also valid for an implementation 22037 to implement this as a do-nothing method). 22038 +/ 22039 void cacheString(SimpleWindow window, Color foreground, Color background, string text); 22040 } 22041 22042 /++ 22043 Loads a true type font using [arsd.ttf] that can be drawn as images on windows 22044 through a [ScreenPainter]. That module must be compiled in if you choose to use this function. 22045 22046 You should also consider [OperatingSystemFont], which loads and draws a font with 22047 facilities native to the user's operating system. You might also consider 22048 [arsd.ttf.OpenGlLimitedFont] or using [arsd.nanovega] if you are making some kind 22049 of game, as they have their own ways to draw text too. 22050 22051 Be warned: this can be slow, especially on remote connections to the X server, since 22052 it needs to create and transfer bitmaps instead of just text. The [DrawableFont] interface 22053 offers [DrawableFont.cacheString] which can help with this, sometimes. You might want to 22054 experiment in your specific case. 22055 22056 Please note that the return type of [DrawableFont] also includes an implementation of 22057 [MeasurableFont]. 22058 +/ 22059 DrawableFont arsdTtfFont()(in ubyte[] data, int size) { 22060 import arsd.ttf; 22061 static class ArsdTtfFont : DrawableFont { 22062 TtfFont font; 22063 int size; 22064 this(in ubyte[] data, int size) { 22065 font = TtfFont(data); 22066 this.size = size; 22067 22068 22069 auto scale = stbtt_ScaleForPixelHeight(&font.font, size); 22070 int ascent_, descent_, line_gap; 22071 stbtt_GetFontVMetrics(&font.font, &ascent_, &descent_, &line_gap); 22072 22073 int advance, lsb; 22074 stbtt_GetCodepointHMetrics(&font.font, 'x', &advance, &lsb); 22075 xWidth = cast(int) (advance * scale); 22076 stbtt_GetCodepointHMetrics(&font.font, 'M', &advance, &lsb); 22077 MWidth = cast(int) (advance * scale); 22078 } 22079 22080 private int ascent_; 22081 private int descent_; 22082 private int xWidth; 22083 private int MWidth; 22084 22085 bool isMonospace() { 22086 return xWidth == MWidth; 22087 } 22088 int averageWidth() { 22089 return xWidth; 22090 } 22091 int height() { 22092 return size; 22093 } 22094 int ascent() { 22095 return ascent_; 22096 } 22097 int descent() { 22098 return descent_; 22099 } 22100 22101 int stringWidth(scope const(char)[] s, SimpleWindow window = null) { 22102 int width, height; 22103 font.getStringSize(s, size, width, height); 22104 return width; 22105 } 22106 22107 22108 22109 Sprite[string] cache; 22110 22111 void cacheString(SimpleWindow window, Color foreground, Color background, string text) { 22112 auto sprite = new Sprite(window, stringToImage(foreground, background, text)); 22113 cache[text] = sprite; 22114 } 22115 22116 Image stringToImage(Color fg, Color bg, in char[] text) { 22117 int width, height; 22118 auto data = font.renderString(text, size, width, height); 22119 auto image = new TrueColorImage(width, height); 22120 int pos = 0; 22121 foreach(y; 0 .. height) 22122 foreach(x; 0 .. width) { 22123 fg.a = data[0]; 22124 bg.a = 255; 22125 auto color = alphaBlend(fg, bg); 22126 image.imageData.bytes[pos++] = color.r; 22127 image.imageData.bytes[pos++] = color.g; 22128 image.imageData.bytes[pos++] = color.b; 22129 image.imageData.bytes[pos++] = data[0]; 22130 data = data[1 .. $]; 22131 } 22132 assert(data.length == 0); 22133 22134 return Image.fromMemoryImage(image); 22135 } 22136 22137 void drawString(ScreenPainter painter, Point upperLeft, in char[] text) { 22138 Sprite sprite = (text in cache) ? *(text in cache) : null; 22139 22140 auto fg = painter.impl._outlineColor; 22141 auto bg = painter.impl._fillColor; 22142 22143 if(sprite !is null) { 22144 auto w = cast(SimpleWindow) painter.window; 22145 assert(w !is null); 22146 22147 sprite.drawAt(painter, upperLeft); 22148 } else { 22149 painter.drawImage(upperLeft, stringToImage(fg, bg, text)); 22150 } 22151 } 22152 } 22153 22154 return new ArsdTtfFont(data, size); 22155 } 22156 22157 class NotYetImplementedException : Exception { 22158 this(string file = __FILE__, size_t line = __LINE__) { 22159 super("Not yet implemented", file, line); 22160 } 22161 } 22162 22163 /// 22164 __gshared bool librariesSuccessfullyLoaded = true; 22165 /// 22166 __gshared bool openGlLibrariesSuccessfullyLoaded = true; 22167 22168 private mixin template DynamicLoadSupplementalOpenGL(Iface) { 22169 mixin(staticForeachReplacement!Iface); 22170 22171 void loadDynamicLibrary() @nogc { 22172 (cast(void function() @nogc) &loadDynamicLibraryForReal)(); 22173 } 22174 22175 void loadDynamicLibraryForReal() { 22176 foreach(name; __traits(derivedMembers, Iface)) { 22177 mixin("alias tmp = " ~ name ~ ";"); 22178 tmp = cast(typeof(tmp)) glbindGetProcAddress(name); 22179 if(tmp is null) throw new Exception("load failure of function " ~ name ~ " from supplemental OpenGL"); 22180 } 22181 } 22182 } 22183 22184 private const(char)[] staticForeachReplacement(Iface)() pure { 22185 /* 22186 // just this for gdc 9.... 22187 // when i drop support for it and switch to gdc10, we can put this original back for a slight compile time ram decrease 22188 22189 static foreach(name; __traits(derivedMembers, Iface)) 22190 mixin("__gshared typeof(&__traits(getMember, Iface, name)) " ~ name ~ ";"); 22191 */ 22192 22193 char[] code = new char[](__traits(derivedMembers, Iface).length * 64); 22194 size_t pos; 22195 22196 void append(in char[] what) { 22197 if(pos + what.length > code.length) 22198 code.length = (code.length * 3) / 2; 22199 code[pos .. pos + what.length] = what[]; 22200 pos += what.length; 22201 } 22202 22203 foreach(name; __traits(derivedMembers, Iface)) { 22204 append(`__gshared typeof(&__traits(getMember, Iface, "`); 22205 append(name); 22206 append(`")) `); 22207 append(name); 22208 append(";"); 22209 } 22210 22211 return code[0 .. pos]; 22212 } 22213 22214 private mixin template DynamicLoad(Iface, string library, int majorVersion, alias success) { 22215 mixin(staticForeachReplacement!Iface); 22216 22217 private __gshared void* libHandle; 22218 private __gshared bool attempted; 22219 22220 void loadDynamicLibrary() @nogc { 22221 (cast(void function() @nogc) &loadDynamicLibraryForReal)(); 22222 } 22223 22224 bool loadAttempted() { 22225 return attempted; 22226 } 22227 bool loadSuccessful() { 22228 return libHandle !is null; 22229 } 22230 22231 void loadDynamicLibraryForReal() { 22232 attempted = true; 22233 version(Posix) { 22234 import core.sys.posix.dlfcn; 22235 version(OSX) { 22236 version(X11) 22237 libHandle = dlopen("/usr/X11/lib/lib" ~ library ~ ".dylib", RTLD_NOW); 22238 else 22239 libHandle = dlopen(library ~ ".dylib", RTLD_NOW); 22240 } else { 22241 libHandle = dlopen("lib" ~ library ~ ".so", RTLD_NOW); 22242 if(libHandle is null) 22243 libHandle = dlopen(("lib" ~ library ~ ".so." ~ toInternal!string(majorVersion) ~ "\0").ptr, RTLD_NOW); 22244 } 22245 22246 static void* loadsym(void* l, const char* name) { 22247 import core.stdc.stdlib; 22248 if(l is null) 22249 return &abort; 22250 return dlsym(l, name); 22251 } 22252 } else version(Windows) { 22253 import core.sys.windows.winbase; 22254 libHandle = LoadLibrary(library ~ ".dll"); 22255 static void* loadsym(void* l, const char* name) { 22256 import core.stdc.stdlib; 22257 if(l is null) 22258 return &abort; 22259 return GetProcAddress(l, name); 22260 } 22261 } 22262 if(libHandle is null) { 22263 success = false; 22264 //throw new Exception("load failure of library " ~ library); 22265 } 22266 foreach(name; __traits(derivedMembers, Iface)) { 22267 mixin("alias tmp = " ~ name ~ ";"); 22268 tmp = cast(typeof(tmp)) loadsym(libHandle, name); 22269 if(tmp is null) throw new Exception("load failure of function " ~ name ~ " from " ~ library); 22270 } 22271 } 22272 22273 void unloadDynamicLibrary() { 22274 version(Posix) { 22275 import core.sys.posix.dlfcn; 22276 dlclose(libHandle); 22277 } else version(Windows) { 22278 import core.sys.windows.winbase; 22279 FreeLibrary(libHandle); 22280 } 22281 foreach(name; __traits(derivedMembers, Iface)) 22282 mixin(name ~ " = null;"); 22283 } 22284 } 22285 22286 /+ 22287 The GC can be called from any thread, and a lot of cleanup must be done 22288 on the gui thread. Since the GC can interrupt any locks - including being 22289 triggered inside a critical section - it is vital to avoid deadlocks to get 22290 these functions called from the right place. 22291 22292 If the buffer overflows, things are going to get leaked. I'm kinda ok with that 22293 right now. 22294 22295 The cleanup function is run when the event loop gets around to it, which is just 22296 whenever there's something there after it has been woken up for other work. It does 22297 NOT wake up the loop itself - can't risk doing that from inside the GC in another thread. 22298 (Well actually it might be ok but i don't wanna mess with it right now.) 22299 +/ 22300 private struct CleanupQueue { 22301 import core.stdc.stdlib; 22302 22303 void queue(alias func, T...)(T args) { 22304 static struct Args { 22305 T args; 22306 } 22307 static struct RealJob { 22308 Job j; 22309 Args a; 22310 } 22311 static void call(Job* data) { 22312 auto rj = cast(RealJob*) data; 22313 func(rj.a.args); 22314 } 22315 22316 RealJob* thing = cast(RealJob*) malloc(RealJob.sizeof); 22317 thing.j.call = &call; 22318 thing.a.args = args; 22319 22320 buffer[tail++] = cast(Job*) thing; 22321 22322 // FIXME: set overflowed 22323 } 22324 22325 void process() { 22326 const tail = this.tail; 22327 22328 while(tail != head) { 22329 Job* job = cast(Job*) buffer[head++]; 22330 job.call(job); 22331 free(job); 22332 } 22333 22334 if(overflowed) 22335 throw new Exception("cleanup overflowed"); 22336 } 22337 22338 private: 22339 22340 ubyte tail; // must ONLY be written by queue 22341 ubyte head; // must ONLY be written by process 22342 bool overflowed; 22343 22344 static struct Job { 22345 void function(Job*) call; 22346 } 22347 22348 void*[256] buffer; 22349 } 22350 private __gshared CleanupQueue cleanupQueue; 22351 22352 version(X11) 22353 /++ 22354 Returns the custom scaling factor read out of environment["ARSD_SCALING_FACTOR"]. 22355 22356 $(WARNING 22357 This function is exempted from stability guarantees. 22358 ) 22359 +/ 22360 float customScalingFactorForMonitor(int monitorNumber) { 22361 import core.stdc.stdlib; 22362 auto val = getenv("ARSD_SCALING_FACTOR"); 22363 22364 // FIXME: maybe we should assume a default nbased on the dpi thing if this isn't given 22365 if(val is null) 22366 return 1.0; 22367 22368 char[16] buffer = 0; 22369 int pos; 22370 22371 const(char)* at = val; 22372 22373 foreach(item; 0 .. monitorNumber + 1) { 22374 if(*at == 0) 22375 break; // reuse the last number when we at the end of the string 22376 pos = 0; 22377 while(pos + 1 < buffer.length && *at && *at != ';') { 22378 buffer[pos++] = *at; 22379 at++; 22380 } 22381 if(*at) 22382 at++; // skip the semicolon 22383 buffer[pos] = 0; 22384 } 22385 22386 //sdpyPrintDebugString(buffer[0 .. pos]); 22387 22388 import core.stdc.math; 22389 auto f = atof(buffer.ptr); 22390 22391 if(f <= 0.0 || isnan(f) || isinf(f)) 22392 return 1.0; 22393 22394 return f; 22395 } 22396 22397 void guiAbortProcess(string msg) { 22398 import core.stdc.stdlib; 22399 version(Windows) { 22400 WCharzBuffer t = WCharzBuffer(msg); 22401 MessageBoxW(null, t.ptr, "Program Termination"w.ptr, 0); 22402 } else { 22403 import core.stdc.stdio; 22404 fwrite(msg.ptr, 1, msg.length, stderr); 22405 msg = "\n"; 22406 fwrite(msg.ptr, 1, msg.length, stderr); 22407 fflush(stderr); 22408 } 22409 22410 abort(); 22411 } 22412 22413 private int minInternal(int a, int b) { 22414 return (a < b) ? a : b; 22415 } 22416 22417 private alias scriptable = arsd_jsvar_compatible;