1 // https://dpaste.dzfl.pl/7a77355acaec 2 3 /+ 4 To share some stuff between two opengl threads: 5 windows 6 https://www.khronos.org/opengl/wiki/OpenGL_and_multithreading 7 linux 8 https://stackoverflow.com/questions/18879520/sharing-opengl-objects-between-contexts-on-linux 9 +/ 10 11 12 // Search for: FIXME: leaks if multithreaded gc 13 14 // https://freedesktop.org/wiki/Specifications/XDND/ 15 16 // https://docs.microsoft.com/en-us/windows/win32/dataxchg/html-clipboard-format 17 18 // https://www.x.org/releases/X11R7.7/doc/libXext/dbelib.html 19 // https://www.x.org/releases/X11R7.6/doc/libXext/synclib.html 20 21 22 // on Mac with X11: -L-L/usr/X11/lib 23 24 /+ 25 26 * I might need to set modal hints too _NET_WM_STATE_MODAL and make sure that TRANSIENT_FOR legit works 27 28 Progress bar in taskbar 29 - i can probably just set a property on the window... 30 it sets that prop to an integer 0 .. 100. Taskbar 31 deletes it or window deletes it when it is handled. 32 - prolly display it as a nice little line at the bottom. 33 34 35 from gtk: 36 37 #define PROGRESS_HINT "_NET_WM_XAPP_PROGRESS" 38 #define PROGRESS_PULSE_HINT "_NET_WM_XAPP_PROGRESS_PULSE" 39 40 >+ if (cardinal > 0) 41 >+ { 42 >+ XChangeProperty (GDK_DISPLAY_XDISPLAY (display), 43 >+ xid, 44 >+ gdk_x11_get_xatom_by_name_for_display (display, atom_name), 45 >+ XA_CARDINAL, 32, 46 >+ PropModeReplace, 47 >+ (guchar *) &cardinal, 1); 48 >+ } 49 >+ else 50 >+ { 51 >+ XDeleteProperty (GDK_DISPLAY_XDISPLAY (display), 52 >+ xid, 53 >+ gdk_x11_get_xatom_by_name_for_display (display, atom_name)); 54 >+ } 55 56 from Windows: 57 58 see: https://docs.microsoft.com/en-us/windows/win32/api/shobjidl_core/nn-shobjidl_core-itaskbarlist3 59 60 interface 61 CoCreateInstance( CLSID_TaskbarList, nullptr, CLSCTX_ALL, __uuidof(ITaskbarList3), (LPVOID*)&m_pTL3 ); 62 auto msg = RegisterWindowMessage(TEXT(“TaskbarButtonCreated”)); 63 listen for msg, return TRUE 64 interface->SetProgressState(hwnd, TBPF_NORMAL); 65 interface->SetProgressValue(hwnd, 40, 100); 66 67 68 My new notification system. 69 - use a unix socket? or a x property? or a udp port? 70 - could of course also get on the dbus train but ugh. 71 - it could also reply with the info as a string for easy remote examination. 72 73 +/ 74 75 /* 76 Event Loop would be nices: 77 78 * add on idle - runs when nothing else happens 79 * which can specify how long to yield for 80 * send messages without a recipient window 81 * setTimeout 82 * setInterval 83 */ 84 85 /* 86 Classic games I want to add: 87 * my tetris clone 88 * pac man 89 */ 90 91 /* 92 Text layout needs a lot of work. Plain drawText is useful but too 93 limited. It will need some kind of text context thing which it will 94 update and you can pass it on and get more details out of it. 95 96 It will need a bounding box, a current cursor location that is updated 97 as drawing continues, and various changable facts (which can also be 98 changed on the painter i guess) like font, color, size, background, 99 etc. 100 101 We can also fetch the caret location from it somehow. 102 103 Should prolly be an overload of drawText 104 105 blink taskbar / demand attention cross platform. FlashWindow and demandAttention 106 107 WS_EX_NOACTIVATE 108 WS_CHILD - owner and owned vs parent and child. Does X have something similar? 109 full screen windows. Can just set the atom on X. Windows will be harder. 110 111 moving windows. resizing windows. 112 113 hide cursor, capture cursor, change cursor. 114 115 REMEMBER: simpledisplay does NOT have to do everything! It just needs to make 116 sure the pieces are there to do its job easily and make other jobs possible. 117 */ 118 119 /++ 120 simpledisplay.d (often abbreviated to "sdpy") provides basic cross-platform GUI-related functionality, 121 including creating windows, drawing on them, working with the clipboard, 122 timers, OpenGL, and more. However, it does NOT provide high level GUI 123 widgets. See my minigui.d, an extension to this module, for that 124 functionality. 125 126 simpledisplay provides cross-platform wrapping for Windows and Linux 127 (and perhaps other OSes that use X11), but also does not prevent you 128 from using the underlying facilities if you need them. It has a goal 129 of working efficiently over a remote X link (at least as far as Xlib 130 reasonably allows.) 131 132 simpledisplay depends on [arsd.color|color.d], which should be available from the 133 same place where you got this file. Other than that, however, it has 134 very few dependencies and ones that don't come with the OS and/or the 135 compiler are all opt-in. 136 137 simpledisplay.d's home base is on my arsd repo on Github. The file is: 138 https://github.com/adamdruppe/arsd/blob/master/simpledisplay.d 139 140 simpledisplay is basically stable. I plan to refactor the internals, 141 and may add new features and fix bugs, but It do not expect to 142 significantly change the API. It has been stable a few years already now. 143 144 Installation_instructions: 145 146 `simpledisplay.d` does not have any dependencies outside the 147 operating system and `color.d`, so it should just work most the 148 time, but there are a few caveats on some systems: 149 150 On Win32, you can pass `-L/subsystem:windows` if you don't want a 151 console to be automatically allocated. 152 153 Please note when compiling on Win64, you need to explicitly list 154 `-Lgdi32.lib -Luser32.lib` on the build command. If you want the Windows 155 subsystem too, use `-L/subsystem:windows -L/entry:mainCRTStartup`. 156 157 If using ldc instead of dmd, use `-L/entry:wmainCRTStartup` instead of `mainCRTStartup`; 158 note the "w". 159 160 I provided a `mixin EnableWindowsSubsystem;` helper to do those linker flags for you, 161 but you still need to use dmd -m32mscoff or -m64 (which dub does by default too fyi). 162 See [EnableWindowsSubsystem] for more information. 163 164 $(PITFALL 165 With the Windows subsystem, there is no console, so standard writeln will throw! 166 You can use [sdpyPrintDebugString] instead of stdio writeln instead which will 167 create a console as needed. 168 ) 169 170 On Mac, when compiling with X11, you need XQuartz and -L-L/usr/X11R6/lib passed to dmd. If using the Cocoa implementation on Mac, you need to pass `-L-framework -LCocoa` to dmd. For OpenGL, add `-L-framework -LOpenGL` to the build command. 171 172 On Ubuntu, you might need to install X11 development libraries to 173 successfully link. 174 175 $(CONSOLE 176 $ sudo apt-get install libglc-dev 177 $ sudo apt-get install libx11-dev 178 ) 179 180 181 Jump_list: 182 183 Don't worry, you don't have to read this whole documentation file! 184 185 Check out the [#event-example] and [#Pong-example] to get started quickly. 186 187 The main classes you may want to create are [SimpleWindow], [Timer], 188 [Image], and [Sprite]. 189 190 The main functions you'll want are [setClipboardText] and [getClipboardText]. 191 192 There are also platform-specific functions available such as [XDisplayConnection] 193 and [GetAtom] for X11, among others. 194 195 See the examples and topics list below to learn more. 196 197 $(WARNING 198 There should only be one GUI thread per application, 199 and all windows should be created in it and your 200 event loop should run there. 201 202 To do otherwise is undefined behavior and has no 203 cross platform guarantees. 204 ) 205 206 $(H2 About this documentation) 207 208 The goal here is to give some complete programs as overview examples first, then a look at each major feature with working examples first, then, finally, the inline class and method list will follow. 209 210 Scan for headers for a topic - $(B they will visually stand out) - you're interested in to get started quickly and feel free to copy and paste any example as a starting point for your program. I encourage you to learn the library by experimenting with the examples! 211 212 All examples are provided with no copyright restrictions whatsoever. You do not need to credit me or carry any kind of notice with the source if you copy and paste from them. 213 214 To get started, download `simpledisplay.d` and `color.d` to a working directory. Copy an example info a file called `example.d` and compile using the command given at the top of each example. 215 216 If you need help, email me: destructionator@gmail.com or IRC us, #d on Freenode (I am destructionator or adam_d_ruppe there). If you learn something that isn't documented, I appreciate pull requests on github to this file. 217 218 At points, I will talk about implementation details in the documentation. These are sometimes 219 subject to change, but nevertheless useful to understand what is really going on. You can learn 220 more about some of the referenced things by searching the web for info about using them from C. 221 You can always look at the source of simpledisplay.d too for the most authoritative source on 222 its specific implementation. If you disagree with how I did something, please contact me so we 223 can discuss it! 224 225 $(H2 Using with fibers) 226 227 simpledisplay can be used with [core.thread.Fiber], but be warned many of the functions can use a significant amount of stack space. I recommend at least 64 KB stack for each fiber (just set through the second argument to Fiber's constructor). 228 229 $(H2 Topics) 230 231 $(H3 $(ID topic-windows) Windows) 232 The [SimpleWindow] class is simpledisplay's flagship feature. It represents a single 233 window on the user's screen. 234 235 You may create multiple windows, if the underlying platform supports it. You may check 236 `static if(multipleWindowsSupported)` at compile time, or catch exceptions thrown by 237 SimpleWindow's constructor at runtime to handle those cases. 238 239 A single running event loop will handle as many windows as needed. 240 241 $(H3 $(ID topic-event-loops) Event loops) 242 The simpledisplay event loop is designed to handle common cases easily while being extensible for more advanced cases, or replaceable by other libraries. 243 244 The most common scenario is creating a window, then calling [SimpleWindow.eventLoop|window.eventLoop] when setup is complete. You can pass several handlers to the `eventLoop` method right there: 245 246 --- 247 // dmd example.d simpledisplay.d color.d 248 import arsd.simpledisplay; 249 void main() { 250 auto window = new SimpleWindow(200, 200); 251 window.eventLoop(0, 252 delegate (dchar) { /* got a character key press */ } 253 ); 254 } 255 --- 256 257 $(TIP If you get a compile error saying "I can't use this event handler", the most common thing in my experience is passing a function instead of a delegate. The simple solution is to use the `delegate` keyword, like I did in the example above.) 258 259 On Linux, the event loop is implemented with the `epoll` system call for efficiency an extensibility to other files. On Windows, it runs a traditional `GetMessage` + `DispatchMessage` loop, with a call to `SleepEx` in each iteration to allow the thread to enter an alertable wait state regularly, primarily so Overlapped I/O callbacks will get a chance to run. 260 261 On Linux, simpledisplay also supports my (deprecated) [arsd.eventloop] module. Compile your program, including the eventloop.d file, with the `-version=with_eventloop` switch. 262 263 It should be possible to integrate simpledisplay with vibe.d as well, though I haven't tried. 264 265 You can also run the event loop independently of a window, with [EventLoop.run|EventLoop.get.run], though since it will automatically terminate when there are no open windows, you will want to have one anyway. 266 267 $(H3 $(ID topic-notification-areas) Notification area (aka systray) icons) 268 Notification area icons are currently implemented on X11 and Windows. On X11, it defaults to using `libnotify` to show bubbles, if available, and will do a custom bubble window if not. You can `version=without_libnotify` to avoid this run-time dependency, if you like. 269 270 See the [NotificationAreaIcon] class. 271 272 $(H3 $(ID topic-input-handling) Input handling) 273 There are event handlers for low-level keyboard and mouse events, and higher level handlers for character events. 274 275 See [SimpleWindow.handleCharEvent], [SimpleWindow.handleKeyEvent], [SimpleWindow.handleMouseEvent]. 276 277 $(H3 $(ID topic-2d-drawing) 2d Drawing) 278 To draw on your window, use the [SimpleWindow.draw] method. It returns a [ScreenPainter] structure with drawing methods. 279 280 Important: `ScreenPainter` double-buffers and will not actually update the window until its destructor is run. Always ensure the painter instance goes out-of-scope before proceeding. You can do this by calling it inside an event handler, a timer callback, or an small scope inside main. For example: 281 282 --- 283 // dmd example.d simpledisplay.d color.d 284 import arsd.simpledisplay; 285 void main() { 286 auto window = new SimpleWindow(200, 200); 287 { // introduce sub-scope 288 auto painter = window.draw(); // begin drawing 289 /* draw here */ 290 painter.outlineColor = Color.red; 291 painter.fillColor = Color.black; 292 painter.drawRectangle(Point(0, 0), 200, 200); 293 } // end scope, calling `painter`'s destructor, drawing to the screen. 294 window.eventLoop(0); // handle events 295 } 296 --- 297 298 Painting is done based on two color properties, a pen and a brush. 299 300 At this time, the 2d drawing does not support alpha blending, except for the [Sprite] class. If you need that, use a 2d OpenGL context instead. 301 302 FIXME Add example of 2d opengl drawing here. 303 $(H3 $(ID topic-3d-drawing) 3d Drawing (or 2d with OpenGL)) 304 simpledisplay can create OpenGL contexts on your window. It works quite differently than 2d drawing. 305 306 Note that it is still possible to draw 2d on top of an OpenGL window, using the `draw` method, though I don't recommend it. 307 308 To start, you create a [SimpleWindow] with OpenGL enabled by passing the argument [OpenGlOptions.yes] to the constructor. 309 310 Next, you set [SimpleWindow.redrawOpenGlScene|window.redrawOpenGlScene] to a delegate which draws your frame. 311 312 To force a redraw of the scene, call [SimpleWindow.redrawOpenGlSceneNow|window.redrawOpenGlSceneNow()] or to queue a redraw after processing the next batch of pending events, use [SimpleWindow.redrawOpenGlSceneSoon|window.redrawOpenGlSceneSoon]. 313 314 simpledisplay supports both old-style `glBegin` and newer-style shader-based code all through its built-in bindings. See the next section of the docs to see a shader-based program. 315 316 This example program will draw a rectangle on your window using old-style OpenGL with a pulsating color: 317 318 --- 319 import arsd.simpledisplay; 320 321 void main() { 322 auto window = new SimpleWindow(800, 600, "opengl 1", OpenGlOptions.yes, Resizability.allowResizing); 323 324 float otherColor = 0.0; 325 float colorDelta = 0.05; 326 327 window.redrawOpenGlScene = delegate() { 328 glLoadIdentity(); 329 glBegin(GL_QUADS); 330 331 glColor3f(1.0, otherColor, 0); 332 glVertex3f(-0.8, -0.8, 0); 333 334 glColor3f(1.0, otherColor, 1.0); 335 glVertex3f(0.8, -0.8, 0); 336 337 glColor3f(0, 1.0, otherColor); 338 glVertex3f(0.8, 0.8, 0); 339 340 glColor3f(otherColor, 0, 1.0); 341 glVertex3f(-0.8, 0.8, 0); 342 343 glEnd(); 344 }; 345 346 window.eventLoop(50, () { 347 otherColor += colorDelta; 348 if(otherColor > 1.0) { 349 otherColor = 1.0; 350 colorDelta = -0.05; 351 } 352 if(otherColor < 0) { 353 otherColor = 0; 354 colorDelta = 0.05; 355 } 356 // at the end of the timer, we have to request a redraw 357 // or we won't see the changes. 358 window.redrawOpenGlSceneSoon(); 359 }); 360 } 361 --- 362 363 My [arsd.game] module has some helpers for using old-style opengl to make 2D windows too. See: [arsd.game.create2dWindow]. 364 $(H3 $(ID topic-modern-opengl) Modern OpenGL) 365 simpledisplay's opengl support, by default, is for "legacy" opengl. To use "modern" functions, you must opt-into them with a little more setup. But the library provides helpers for this too. 366 367 This example program shows how you can set up a shader to draw a rectangle: 368 369 --- 370 import arsd.simpledisplay; 371 372 // based on https://learnopengl.com/Getting-started/Hello-Triangle 373 374 void main() { 375 // First thing we do, before creating the window, is declare what version we want. 376 setOpenGLContextVersion(3, 3); 377 // turning off legacy compat is required to use version 3.3 and newer 378 openGLContextCompatible = false; 379 380 uint VAO; 381 OpenGlShader shader; 382 383 // then we can create the window. 384 auto window = new SimpleWindow(800, 600, "opengl 3", OpenGlOptions.yes, Resizability.allowResizing); 385 386 // additional setup needs to be done when it is visible, simpledisplay offers a property 387 // for exactly that: 388 window.visibleForTheFirstTime = delegate() { 389 // now with the window loaded, we can start loading the modern opengl functions. 390 391 // you MUST set the context first. 392 window.setAsCurrentOpenGlContext; 393 // then load the remainder of the library 394 gl3.loadDynamicLibrary(); 395 396 // now you can create the shaders, etc. 397 shader = new OpenGlShader( 398 OpenGlShader.Source(GL_VERTEX_SHADER, ` 399 #version 330 core 400 layout (location = 0) in vec3 aPos; 401 void main() { 402 gl_Position = vec4(aPos.x, aPos.y, aPos.z, 1.0); 403 } 404 `), 405 OpenGlShader.Source(GL_FRAGMENT_SHADER, ` 406 #version 330 core 407 out vec4 FragColor; 408 uniform vec4 mycolor; 409 void main() { 410 FragColor = mycolor; 411 } 412 `), 413 ); 414 415 // and do whatever other setup you want. 416 417 float[] vertices = [ 418 0.5f, 0.5f, 0.0f, // top right 419 0.5f, -0.5f, 0.0f, // bottom right 420 -0.5f, -0.5f, 0.0f, // bottom left 421 -0.5f, 0.5f, 0.0f // top left 422 ]; 423 uint[] indices = [ // note that we start from 0! 424 0, 1, 3, // first Triangle 425 1, 2, 3 // second Triangle 426 ]; 427 uint VBO, EBO; 428 glGenVertexArrays(1, &VAO); 429 // bind the Vertex Array Object first, then bind and set vertex buffer(s), and then configure vertex attributes(s). 430 glBindVertexArray(VAO); 431 432 glGenBuffers(1, &VBO); 433 glGenBuffers(1, &EBO); 434 435 glBindBuffer(GL_ARRAY_BUFFER, VBO); 436 glBufferDataSlice(GL_ARRAY_BUFFER, vertices, GL_STATIC_DRAW); 437 438 glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, EBO); 439 glBufferDataSlice(GL_ELEMENT_ARRAY_BUFFER, indices, GL_STATIC_DRAW); 440 441 glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, 3 * float.sizeof, null); 442 glEnableVertexAttribArray(0); 443 444 // the library will set the initial viewport and trigger our first draw, 445 // so these next two lines are NOT needed. they are just here as comments 446 // to show what would happen next. 447 448 // glViewport(0, 0, window.width, window.height); 449 // window.redrawOpenGlSceneNow(); 450 }; 451 452 // this delegate is called any time the window needs to be redrawn or if you call `window.redrawOpenGlSceneNow;` 453 // it is our render method. 454 window.redrawOpenGlScene = delegate() { 455 glClearColor(0.2f, 0.3f, 0.3f, 1.0f); 456 glClear(GL_COLOR_BUFFER_BIT); 457 458 glUseProgram(shader.shaderProgram); 459 460 // the shader helper class has methods to set uniforms too 461 shader.uniforms.mycolor.opAssign(1.0, 1.0, 0, 1.0); 462 463 glBindVertexArray(VAO); 464 glDrawElements(GL_TRIANGLES, 6, GL_UNSIGNED_INT, null); 465 }; 466 467 window.eventLoop(0); 468 } 469 --- 470 471 This program only draws the image once because that's all that is necessary, since it is static. If you want to do animation, you might set a pulse timer (which would be a fixed max fps, not necessarily consistent) or use a render loop in a separate thread. 472 473 $(H3 $(ID vulkan) Vulkan) 474 475 See a couple examples ported from GLFW to simpledisplay using the erupted vulkan bindings: 476 477 https://github.com/adamdruppe/VulkanizeDSdpy 478 479 https://github.com/adamdruppe/VulkanSdpyDemo/tree/demo 480 481 $(H3 $(ID topic-images) Displaying images) 482 You can also load PNG images using [arsd.png]. 483 484 --- 485 // dmd example.d simpledisplay.d color.d png.d 486 import arsd.simpledisplay; 487 import arsd.png; 488 489 void main() { 490 auto image = Image.fromMemoryImage(readPng("image.png")); 491 displayImage(image); 492 } 493 --- 494 495 Compile with `dmd example.d simpledisplay.d png.d`. 496 497 If you find an image file which is a valid png that [arsd.png] fails to load, please let me know. In the mean time of fixing the bug, you can probably convert the file into an easier-to-load format. Be sure to turn OFF png interlacing, as that isn't supported. Other things to try would be making the image smaller, or trying 24 bit truecolor mode with an alpha channel. 498 499 $(H3 $(ID topic-sprites) Sprites) 500 The [Sprite] class is used to make images on the display server for fast blitting to screen. This is especially important to use to support fast drawing of repeated images on a remote X11 link. 501 502 [Sprite] is also the only facility that currently supports alpha blending without using OpenGL . 503 504 $(H3 $(ID topic-clipboard) Clipboard) 505 The free functions [getClipboardText] and [setClipboardText] consist of simpledisplay's cross-platform clipboard support at this time. 506 507 It also has helpers for handling X-specific events. 508 509 $(H3 $(ID topic-dnd) Drag and Drop) 510 See [enableDragAndDrop] and [draggable]. 511 512 $(H3 $(ID topic-timers) Timers) 513 There are two timers in simpledisplay: one is the pulse timeout you can set on the call to `window.eventLoop`, and the other is a customizable class, [Timer]. 514 515 The pulse timeout is used by setting a non-zero interval as the first argument to `eventLoop` function and adding a zero-argument delegate to handle the pulse. 516 517 --- 518 import arsd.simpledisplay; 519 520 void main() { 521 auto window = new SimpleWindow(400, 400); 522 // every 100 ms, it will draw a random line 523 // on the window. 524 window.eventLoop(100, { 525 auto painter = window.draw(); 526 527 import std.random; 528 // random color 529 painter.outlineColor = Color(uniform(0, 256), uniform(0, 256), uniform(0, 256)); 530 // random line 531 painter.drawLine( 532 Point(uniform(0, window.width), uniform(0, window.height)), 533 Point(uniform(0, window.width), uniform(0, window.height))); 534 535 }); 536 } 537 --- 538 539 The `Timer` class works similarly, but is created separately from the event loop. (It still fires through the event loop, though.) You may make as many instances of `Timer` as you wish. 540 541 The pulse timer and instances of the [Timer] class may be combined at will. 542 543 --- 544 import arsd.simpledisplay; 545 546 void main() { 547 auto window = new SimpleWindow(400, 400); 548 auto timer = new Timer(1000, delegate { 549 auto painter = window.draw(); 550 painter.clear(); 551 }); 552 553 window.eventLoop(0); 554 } 555 --- 556 557 Timers are currently only implemented on Windows, using `SetTimer` and Linux, using `timerfd_create`. These deliver timeout messages through your application event loop. 558 559 $(H3 $(ID topic-os-helpers) OS-specific helpers) 560 simpledisplay carries a lot of code to help implement itself without extra dependencies, and much of this code is available for you too, so you may extend the functionality yourself. 561 562 See also: `xwindows.d` from my github. 563 564 $(H3 $(ID topic-os-extension) Extending with OS-specific functionality) 565 `handleNativeEvent` and `handleNativeGlobalEvent`. 566 567 $(H3 $(ID topic-integration) Integration with other libraries) 568 Integration with a third-party event loop is possible. 569 570 On Linux, you might want to support both terminal input and GUI input. You can do this by using simpledisplay together with eventloop.d and terminal.d. 571 572 $(H3 $(ID topic-guis) GUI widgets) 573 simpledisplay does not provide GUI widgets such as text areas, buttons, checkboxes, etc. It only gives basic windows, the ability to draw on it, receive input from it, and access native information for extension. You may write your own gui widgets with these, but you don't have to because I already did for you! 574 575 Download `minigui.d` from my github repository and add it to your project. minigui builds these things on top of simpledisplay and offers its own Window class (and subclasses) to use that wrap SimpleWindow, adding a new event and drawing model that is hookable by subwidgets, represented by their own classes. 576 577 Migrating to minigui from simpledisplay is often easy though, because they both use the same ScreenPainter API, and the same simpledisplay events are available, if you want them. (Though you may like using the minigui model, especially if you are familiar with writing web apps in the browser with Javascript.) 578 579 minigui still needs a lot of work to be finished at this time, but it already offers a number of useful classes. 580 581 $(H2 Platform-specific tips and tricks) 582 583 X_tips: 584 585 On X11, if you set an environment variable, `ARSD_SCALING_FACTOR`, you can control the per-monitor DPI scaling returned to the application. The format is `ARSD_SCALING_FACTOR=2;1`, for example, to set 2x scaling on your first monitor and 1x scaling on your second monitor. Support for this was added on March 22, 2022, the dub 10.7 release. 586 587 Windows_tips: 588 589 You can add icons or manifest files to your exe using a resource file. 590 591 To create a Windows .ico file, use the gimp or something. I'll write a helper 592 program later. 593 594 Create `yourapp.rc`: 595 596 ```rc 597 1 ICON filename.ico 598 CREATEPROCESS_MANIFEST_RESOURCE_ID RT_MANIFEST "YourApp.exe.manifest" 599 ``` 600 601 And `yourapp.exe.manifest`: 602 603 ```xml 604 <?xml version="1.0" encoding="UTF-8" standalone="yes"?> 605 <assembly xmlns="urn:schemas-microsoft-com:asm.v1" manifestVersion="1.0"> 606 <assemblyIdentity 607 version="1.0.0.0" 608 processorArchitecture="*" 609 name="CompanyName.ProductName.YourApplication" 610 type="win32" 611 /> 612 <description>Your application description here.</description> 613 <dependency> 614 <dependentAssembly> 615 <assemblyIdentity 616 type="win32" 617 name="Microsoft.Windows.Common-Controls" 618 version="6.0.0.0" 619 processorArchitecture="*" 620 publicKeyToken="6595b64144ccf1df" 621 language="*" 622 /> 623 </dependentAssembly> 624 </dependency> 625 <application xmlns="urn:schemas-microsoft-com:asm.v3"> 626 <windowsSettings> 627 <dpiAware xmlns="http://schemas.microsoft.com/SMI/2005/WindowsSettings">true/pm</dpiAware> <!-- old style --> 628 <dpiAwareness xmlns="http://schemas.microsoft.com/SMI/2016/WindowsSettings">PerMonitorV2</dpiAwareness> <!-- new style --> 629 <!-- Un-comment the line below to enable GDI-scaling in this project. This will enable text --> 630 <!-- to render crisply in DPI-unaware contexts --> 631 <!--<gdiScaling xmlns="http://schemas.microsoft.com/SMI/2017/WindowsSettings">true</gdiScaling>--> 632 </windowsSettings> 633 </application> 634 </assembly> 635 ``` 636 637 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`. 638 639 Doing this lets you opt into various new things since Windows XP. 640 641 See: https://docs.microsoft.com/en-us/windows/win32/SbsCs/application-manifests 642 643 $(H2 Tips) 644 645 $(H3 Name conflicts) 646 647 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: 648 649 --- 650 static import sdpy = arsd.simpledisplay; 651 import arsd.simpledisplay : SimpleWindow; 652 653 void main() { 654 auto window = new SimpleWindow(); 655 sdpy.EventLoop.get.run(); 656 } 657 --- 658 659 $(H2 $(ID developer-notes) Developer notes) 660 661 I don't have a Mac, so that code isn't maintained. I would like to have a Cocoa 662 implementation though. 663 664 The NativeSimpleWindowImplementation and NativeScreenPainterImplementation both 665 suck. If I was rewriting it, I wouldn't do it that way again. 666 667 This file must not have any more required dependencies. If you need bindings, add 668 them right to this file. Once it gets into druntime and is there for a while, remove 669 bindings from here to avoid conflicts (or put them in an appropriate version block 670 so it continues to just work on old dmd), but wait a couple releases before making the 671 transition so this module remains usable with older versions of dmd. 672 673 You may have optional dependencies if needed by putting them in version blocks or 674 template functions. You may also extend the module with other modules with UFCS without 675 actually editing this - that is nice to do if you can. 676 677 Try to make functions work the same way across operating systems. I typically make 678 it thinly wrap Windows, then emulate that on Linux. 679 680 A goal of this is to keep a gui hello world to less than 250 KB. This means avoiding 681 Phobos! So try to avoid it. 682 683 See more comments throughout the source. 684 685 I realize this file is fairly large, but over half that is just bindings at the bottom 686 or documentation at the top. Some of the classes are a bit big too, but hopefully easy 687 to understand. I suggest you jump around the source by looking for a particular 688 declaration you're interested in, like `class SimpleWindow` using your editor's search 689 function, then look at one piece at a time. 690 691 Authors: Adam D. Ruppe with the help of others. If you need help, please email me with 692 destructionator@gmail.com or find me on IRC. Our channel is #d on Freenode and you can 693 ping me, adam_d_ruppe, and I'll usually see it if I'm around. 694 695 I live in the eastern United States, so I will most likely not be around at night in 696 that US east timezone. 697 698 License: Copyright Adam D. Ruppe, 2011-2021. Released under the Boost Software License. 699 700 Building documentation: use my adrdox generator, `dub run adrdox`. 701 702 Examples: 703 704 $(DIV $(ID Event-example)) 705 $(H3 $(ID event-example) Event example) 706 This program creates a window and draws events inside them as they 707 happen, scrolling the text in the window as needed. Run this program 708 and experiment to get a feel for where basic input events take place 709 in the library. 710 711 --- 712 // dmd example.d simpledisplay.d color.d 713 import arsd.simpledisplay; 714 import std.conv; 715 716 void main() { 717 auto window = new SimpleWindow(Size(500, 500), "Event example - simpledisplay.d"); 718 719 int y = 0; 720 721 void addLine(string text) { 722 auto painter = window.draw(); 723 724 if(y + painter.fontHeight >= window.height) { 725 painter.scrollArea(Point(0, 0), window.width, window.height, 0, painter.fontHeight); 726 y -= painter.fontHeight; 727 } 728 729 painter.outlineColor = Color.red; 730 painter.fillColor = Color.black; 731 painter.drawRectangle(Point(0, y), window.width, painter.fontHeight); 732 733 painter.outlineColor = Color.white; 734 735 painter.drawText(Point(10, y), text); 736 737 y += painter.fontHeight; 738 } 739 740 window.eventLoop(1000, 741 () { 742 addLine("Timer went off!"); 743 }, 744 (KeyEvent event) { 745 addLine(to!string(event)); 746 }, 747 (MouseEvent event) { 748 addLine(to!string(event)); 749 }, 750 (dchar ch) { 751 addLine(to!string(ch)); 752 } 753 ); 754 } 755 --- 756 757 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. 758 759 $(COMMENT 760 This program displays a pie chart. Clicking on a color will increase its share of the pie. 761 762 --- 763 764 --- 765 ) 766 767 History: 768 Initial release in April 2011. 769 770 simpledisplay was stand alone until about 2015. It then added a dependency on [arsd.color] and changed its name to `arsd.simpledisplay`. 771 772 On March 4, 2023 (dub v11.0), it started importing [arsd.core] as well, making that a build-time requirement. 773 +/ 774 module arsd.simpledisplay; 775 776 import arsd.core; 777 778 // FIXME: tetris demo 779 // FIXME: space invaders demo 780 // FIXME: asteroids demo 781 782 /++ $(ID Pong-example) 783 $(H3 Pong) 784 785 This program creates a little Pong-like game. Player one is controlled 786 with the keyboard. Player two is controlled with the mouse. It demos 787 the pulse timer, event handling, and some basic drawing. 788 +/ 789 version(demos) 790 unittest { 791 // dmd example.d simpledisplay.d color.d 792 import arsd.simpledisplay; 793 794 enum paddleMovementSpeed = 8; 795 enum paddleHeight = 48; 796 797 void main() { 798 auto window = new SimpleWindow(600, 400, "Pong game!"); 799 800 int playerOnePosition, playerTwoPosition; 801 int playerOneMovement, playerTwoMovement; 802 int playerOneScore, playerTwoScore; 803 804 int ballX, ballY; 805 int ballDx, ballDy; 806 807 void serve() { 808 import std.random; 809 810 ballX = window.width / 2; 811 ballY = window.height / 2; 812 ballDx = uniform(-4, 4) * 3; 813 ballDy = uniform(-4, 4) * 3; 814 if(ballDx == 0) 815 ballDx = uniform(0, 2) == 0 ? 3 : -3; 816 } 817 818 serve(); 819 820 window.eventLoop(50, // set a 50 ms timer pulls 821 // This runs once per timer pulse 822 delegate () { 823 auto painter = window.draw(); 824 825 painter.clear(); 826 827 // Update everyone's motion 828 playerOnePosition += playerOneMovement; 829 playerTwoPosition += playerTwoMovement; 830 831 ballX += ballDx; 832 ballY += ballDy; 833 834 // Bounce off the top and bottom edges of the window 835 if(ballY + 7 >= window.height) 836 ballDy = -ballDy; 837 if(ballY - 8 <= 0) 838 ballDy = -ballDy; 839 840 // Bounce off the paddle, if it is in position 841 if(ballX - 8 <= 16) { 842 if(ballY + 7 > playerOnePosition && ballY - 8 < playerOnePosition + paddleHeight) { 843 ballDx = -ballDx + 1; // add some speed to keep it interesting 844 ballDy += playerOneMovement; // and y movement based on your controls too 845 ballX = 24; // move it past the paddle so it doesn't wiggle inside 846 } else { 847 // Missed it 848 playerTwoScore ++; 849 serve(); 850 } 851 } 852 853 if(ballX + 7 >= window.width - 16) { // do the same thing but for player 1 854 if(ballY + 7 > playerTwoPosition && ballY - 8 < playerTwoPosition + paddleHeight) { 855 ballDx = -ballDx - 1; 856 ballDy += playerTwoMovement; 857 ballX = window.width - 24; 858 } else { 859 // Missed it 860 playerOneScore ++; 861 serve(); 862 } 863 } 864 865 // Draw the paddles 866 painter.outlineColor = Color.black; 867 painter.drawLine(Point(16, playerOnePosition), Point(16, playerOnePosition + paddleHeight)); 868 painter.drawLine(Point(window.width - 16, playerTwoPosition), Point(window.width - 16, playerTwoPosition + paddleHeight)); 869 870 // Draw the ball 871 painter.fillColor = Color.red; 872 painter.outlineColor = Color.yellow; 873 painter.drawEllipse(Point(ballX - 8, ballY - 8), Point(ballX + 7, ballY + 7)); 874 875 // Draw the score 876 painter.outlineColor = Color.blue; 877 import std.conv; 878 painter.drawText(Point(64, 4), to!string(playerOneScore)); 879 painter.drawText(Point(window.width - 64, 4), to!string(playerTwoScore)); 880 881 }, 882 delegate (KeyEvent event) { 883 // Player 1's controls are the arrow keys on the keyboard 884 if(event.key == Key.Down) 885 playerOneMovement = event.pressed ? paddleMovementSpeed : 0; 886 if(event.key == Key.Up) 887 playerOneMovement = event.pressed ? -paddleMovementSpeed : 0; 888 889 }, 890 delegate (MouseEvent event) { 891 // Player 2's controls are mouse movement while the left button is held down 892 if(event.type == MouseEventType.motion && (event.modifierState & ModifierState.leftButtonDown)) { 893 if(event.dy > 0) 894 playerTwoMovement = paddleMovementSpeed; 895 else if(event.dy < 0) 896 playerTwoMovement = -paddleMovementSpeed; 897 } else { 898 playerTwoMovement = 0; 899 } 900 } 901 ); 902 } 903 } 904 905 /++ $(H3 $(ID example-minesweeper) Minesweeper) 906 907 This minesweeper demo shows how we can implement another classic 908 game with simpledisplay and shows some mouse input and basic output 909 code. 910 +/ 911 version(demos) 912 unittest { 913 import arsd.simpledisplay; 914 915 enum GameSquare { 916 mine = 0, 917 clear, 918 m1, m2, m3, m4, m5, m6, m7, m8 919 } 920 921 enum UserSquare { 922 unknown, 923 revealed, 924 flagged, 925 questioned 926 } 927 928 enum GameState { 929 inProgress, 930 lose, 931 win 932 } 933 934 GameSquare[] board; 935 UserSquare[] userState; 936 GameState gameState; 937 int boardWidth; 938 int boardHeight; 939 940 bool isMine(int x, int y) { 941 if(x < 0 || y < 0 || x >= boardWidth || y >= boardHeight) 942 return false; 943 return board[y * boardWidth + x] == GameSquare.mine; 944 } 945 946 GameState reveal(int x, int y) { 947 if(board[y * boardWidth + x] == GameSquare.clear) { 948 floodFill(userState, boardWidth, boardHeight, 949 UserSquare.unknown, UserSquare.revealed, 950 x, y, 951 (x, y) { 952 if(board[y * boardWidth + x] == GameSquare.clear) 953 return true; 954 else { 955 userState[y * boardWidth + x] = UserSquare.revealed; 956 return false; 957 } 958 }); 959 } else { 960 userState[y * boardWidth + x] = UserSquare.revealed; 961 if(isMine(x, y)) 962 return GameState.lose; 963 } 964 965 foreach(state; userState) { 966 if(state == UserSquare.unknown || state == UserSquare.questioned) 967 return GameState.inProgress; 968 } 969 970 return GameState.win; 971 } 972 973 void initializeBoard(int width, int height, int numberOfMines) { 974 boardWidth = width; 975 boardHeight = height; 976 board.length = width * height; 977 978 userState.length = width * height; 979 userState[] = UserSquare.unknown; 980 981 import std.algorithm, std.random, std.range; 982 983 board[] = GameSquare.clear; 984 985 foreach(minePosition; randomSample(iota(0, board.length), numberOfMines)) 986 board[minePosition] = GameSquare.mine; 987 988 int x; 989 int y; 990 foreach(idx, ref square; board) { 991 if(square == GameSquare.clear) { 992 int danger = 0; 993 danger += isMine(x-1, y-1)?1:0; 994 danger += isMine(x-1, y)?1:0; 995 danger += isMine(x-1, y+1)?1:0; 996 danger += isMine(x, y-1)?1:0; 997 danger += isMine(x, y+1)?1:0; 998 danger += isMine(x+1, y-1)?1:0; 999 danger += isMine(x+1, y)?1:0; 1000 danger += isMine(x+1, y+1)?1:0; 1001 1002 square = cast(GameSquare) (danger + 1); 1003 } 1004 1005 x++; 1006 if(x == width) { 1007 x = 0; 1008 y++; 1009 } 1010 } 1011 } 1012 1013 void redraw(SimpleWindow window) { 1014 import std.conv; 1015 1016 auto painter = window.draw(); 1017 1018 painter.clear(); 1019 1020 final switch(gameState) with(GameState) { 1021 case inProgress: 1022 break; 1023 case win: 1024 painter.fillColor = Color.green; 1025 painter.drawRectangle(Point(0, 0), window.width, window.height); 1026 return; 1027 case lose: 1028 painter.fillColor = Color.red; 1029 painter.drawRectangle(Point(0, 0), window.width, window.height); 1030 return; 1031 } 1032 1033 int x = 0; 1034 int y = 0; 1035 1036 foreach(idx, square; board) { 1037 auto state = userState[idx]; 1038 1039 final switch(state) with(UserSquare) { 1040 case unknown: 1041 painter.outlineColor = Color.black; 1042 painter.fillColor = Color(128,128,128); 1043 1044 painter.drawRectangle( 1045 Point(x * 20, y * 20), 1046 20, 20 1047 ); 1048 break; 1049 case revealed: 1050 if(square == GameSquare.clear) { 1051 painter.outlineColor = Color.white; 1052 painter.fillColor = Color.white; 1053 1054 painter.drawRectangle( 1055 Point(x * 20, y * 20), 1056 20, 20 1057 ); 1058 } else { 1059 painter.outlineColor = Color.black; 1060 painter.fillColor = Color.white; 1061 1062 painter.drawText( 1063 Point(x * 20, y * 20), 1064 to!string(square)[1..2], 1065 Point(x * 20 + 20, y * 20 + 20), 1066 TextAlignment.Center | TextAlignment.VerticalCenter); 1067 } 1068 break; 1069 case flagged: 1070 painter.outlineColor = Color.black; 1071 painter.fillColor = Color.red; 1072 painter.drawRectangle( 1073 Point(x * 20, y * 20), 1074 20, 20 1075 ); 1076 break; 1077 case questioned: 1078 painter.outlineColor = Color.black; 1079 painter.fillColor = Color.yellow; 1080 painter.drawRectangle( 1081 Point(x * 20, y * 20), 1082 20, 20 1083 ); 1084 break; 1085 } 1086 1087 x++; 1088 if(x == boardWidth) { 1089 x = 0; 1090 y++; 1091 } 1092 } 1093 1094 } 1095 1096 void main() { 1097 auto window = new SimpleWindow(200, 200); 1098 1099 initializeBoard(10, 10, 10); 1100 1101 redraw(window); 1102 window.eventLoop(0, 1103 delegate (MouseEvent me) { 1104 if(me.type != MouseEventType.buttonPressed) 1105 return; 1106 auto x = me.x / 20; 1107 auto y = me.y / 20; 1108 if(x >= 0 && x < boardWidth && y >= 0 && y < boardHeight) { 1109 if(me.button == MouseButton.left) { 1110 gameState = reveal(x, y); 1111 } else { 1112 userState[y*boardWidth+x] = UserSquare.flagged; 1113 } 1114 redraw(window); 1115 } 1116 } 1117 ); 1118 } 1119 } 1120 1121 import arsd.core; 1122 1123 // FIXME: tetris demo 1124 // FIXME: space invaders demo 1125 // FIXME: asteroids demo 1126 1127 version(OSX) version(DigitalMars) version=OSXCocoa; 1128 1129 1130 version(OSXCocoa) { 1131 version=without_opengl; 1132 version=allow_unimplemented_features; 1133 // version=OSXCocoa; 1134 // pragma(linkerDirective, "-framework Cocoa"); 1135 } 1136 1137 version(without_opengl) { 1138 enum SdpyIsUsingIVGLBinds = false; 1139 } else /*version(Posix)*/ { 1140 static if (__traits(compiles, (){import iv.glbinds;})) { 1141 enum SdpyIsUsingIVGLBinds = true; 1142 public import iv.glbinds; 1143 //pragma(msg, "SDPY: using iv.glbinds"); 1144 } else { 1145 enum SdpyIsUsingIVGLBinds = false; 1146 } 1147 //} else { 1148 // enum SdpyIsUsingIVGLBinds = false; 1149 } 1150 1151 1152 version(Windows) { 1153 //import core.sys.windows.windows; 1154 import core.sys.windows.winnls; 1155 import core.sys.windows.windef; 1156 import core.sys.windows.basetyps; 1157 import core.sys.windows.winbase; 1158 import core.sys.windows.winuser; 1159 import core.sys.windows.shellapi; 1160 import core.sys.windows.wingdi; 1161 static import gdi = core.sys.windows.wingdi; // so i 1162 1163 pragma(lib, "gdi32"); 1164 pragma(lib, "user32"); 1165 1166 // for AlphaBlend... a breaking change.... 1167 version(CRuntime_DigitalMars) { } else 1168 pragma(lib, "msimg32"); 1169 } else version (linux) { 1170 //k8: this is hack for rdmd. sorry. 1171 static import core.sys.linux.epoll; 1172 static import core.sys.linux.timerfd; 1173 } 1174 1175 1176 // FIXME: icons on Windows don't look quite right, I think the transparency mask is off. 1177 1178 // http://wiki.dlang.org/Simpledisplay.d 1179 1180 // see : http://www.sbin.org/doc/Xlib/chapt_09.html section on Keyboard Preferences re: scroll lock led 1181 1182 // Cool stuff: I want right alt and scroll lock to do different stuff for personal use. maybe even right ctrl 1183 // but can i control the scroll lock led 1184 1185 1186 // Note: if you are using Image on X, you might want to do: 1187 /* 1188 static if(UsingSimpledisplayX11) { 1189 if(!Image.impl.xshmAvailable) { 1190 // the images will use the slower XPutImage, you might 1191 // want to consider an alternative method to get better speed 1192 } 1193 } 1194 1195 If the shared memory extension is available though, simpledisplay uses it 1196 for a significant speed boost whenever you draw large Images. 1197 */ 1198 1199 // 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. 1200 1201 // WARNING: if you are using with_eventloop, don't forget to call XFlush(XDisplayConnection.get()); before calling loop()! 1202 1203 /* 1204 Biggest FIXME: 1205 make sure the key event numbers match between X and Windows OR provide symbolic constants on each system 1206 1207 clean up opengl contexts when their windows close 1208 1209 fix resizing the bitmaps/pixmaps 1210 */ 1211 1212 // BTW on Windows: 1213 // -L/SUBSYSTEM:WINDOWS:5.0 1214 // to dmd will make a nice windows binary w/o a console if you want that. 1215 1216 /* 1217 Stuff to add: 1218 1219 use multibyte functions everywhere we can 1220 1221 OpenGL windows 1222 more event stuff 1223 extremely basic windows w/ no decoration for tooltips, splash screens, etc. 1224 1225 1226 resizeEvent 1227 and make the windows non-resizable by default, 1228 or perhaps stretched (if I can find something in X like StretchBlt) 1229 1230 take a screenshot function! 1231 1232 Pens and brushes? 1233 Maybe a global event loop? 1234 1235 Mouse deltas 1236 Key items 1237 */ 1238 1239 /* 1240 From MSDN: 1241 1242 You can also use the GET_X_LPARAM or GET_Y_LPARAM macro to extract the x- or y-coordinate. 1243 1244 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. 1245 1246 */ 1247 1248 version(linux) { 1249 version = X11; 1250 version(without_libnotify) { 1251 // we cool 1252 } 1253 else 1254 version = libnotify; 1255 } 1256 1257 version(libnotify) { 1258 pragma(lib, "dl"); 1259 import core.sys.posix.dlfcn; 1260 1261 void delegate()[int] libnotify_action_delegates; 1262 int libnotify_action_delegates_count; 1263 extern(C) static void libnotify_action_callback_sdpy(void* notification, char* action, void* user_data) { 1264 auto idx = cast(int) user_data; 1265 if(auto dgptr = idx in libnotify_action_delegates) { 1266 (*dgptr)(); 1267 libnotify_action_delegates.remove(idx); 1268 } 1269 } 1270 1271 struct C_DynamicLibrary { 1272 void* handle; 1273 this(string name) { 1274 handle = dlopen((name ~ "\0").ptr, RTLD_NOW); 1275 if(handle is null) 1276 throw new Exception("dlopen"); 1277 } 1278 1279 void close() { 1280 dlclose(handle); 1281 } 1282 1283 ~this() { 1284 // close 1285 } 1286 1287 // FIXME: this looks up by name every time.... 1288 template call(string func, Ret, Args...) { 1289 extern(C) Ret function(Args) fptr; 1290 typeof(fptr) call() { 1291 fptr = cast(typeof(fptr)) dlsym(handle, func); 1292 return fptr; 1293 } 1294 } 1295 } 1296 1297 C_DynamicLibrary* libnotify; 1298 } 1299 1300 version(OSX) { 1301 version(OSXCocoa) {} 1302 else { version = X11; } 1303 } 1304 //version = OSXCocoa; // this was written by KennyTM 1305 version(FreeBSD) 1306 version = X11; 1307 version(Solaris) 1308 version = X11; 1309 1310 version(X11) { 1311 version(without_xft) {} 1312 else version=with_xft; 1313 } 1314 1315 void featureNotImplemented()() { 1316 version(allow_unimplemented_features) 1317 throw new NotYetImplementedException(); 1318 else 1319 static assert(0); 1320 } 1321 1322 // these are so the static asserts don't trigger unless you want to 1323 // add support to it for an OS 1324 version(Windows) 1325 version = with_timer; 1326 version(linux) 1327 version = with_timer; 1328 version(OSXCocoa) 1329 version = with_timer; 1330 1331 version(with_timer) 1332 enum bool SimpledisplayTimerAvailable = true; 1333 else 1334 enum bool SimpledisplayTimerAvailable = false; 1335 1336 /// 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. 1337 version(Windows) 1338 enum bool UsingSimpledisplayWindows = true; 1339 else 1340 enum bool UsingSimpledisplayWindows = false; 1341 1342 /// 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. 1343 version(X11) 1344 enum bool UsingSimpledisplayX11 = true; 1345 else 1346 enum bool UsingSimpledisplayX11 = false; 1347 1348 /// 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. 1349 version(OSXCocoa) 1350 enum bool UsingSimpledisplayCocoa = true; 1351 else 1352 enum bool UsingSimpledisplayCocoa = false; 1353 1354 /// Does this platform support multiple windows? If not, trying to create another will cause it to throw an exception. 1355 version(Windows) 1356 enum multipleWindowsSupported = true; 1357 else version(X11) 1358 enum multipleWindowsSupported = true; 1359 else version(OSXCocoa) 1360 enum multipleWindowsSupported = true; 1361 else 1362 static assert(0); 1363 1364 version(without_opengl) 1365 enum bool OpenGlEnabled = false; 1366 else 1367 enum bool OpenGlEnabled = true; 1368 1369 /++ 1370 Adds the necessary pragmas to your application to use the Windows gui subsystem. 1371 If you mix this in above your `main` function, you no longer need to use the linker 1372 flags explicitly. It does the necessary version blocks for various compilers and runtimes. 1373 1374 It does nothing if not compiling for Windows, so you need not version it out yourself. 1375 1376 Please note that Windows gui subsystem applications must NOT use std.stdio's stdout and 1377 stderr writeln. It will fail and throw an exception. 1378 1379 This will NOT work with plain `dmd` on Windows; you must use `dmd -m32mscoff` or `dmd -m64`. 1380 1381 History: 1382 Added November 24, 2021 (dub v10.4) 1383 +/ 1384 mixin template EnableWindowsSubsystem() { 1385 version(Windows) 1386 version(CRuntime_Microsoft) { 1387 pragma(linkerDirective, "/subsystem:windows"); 1388 version(LDC) 1389 pragma(linkerDirective, "/entry:wmainCRTStartup"); 1390 else 1391 pragma(linkerDirective, "/entry:mainCRTStartup"); 1392 } 1393 } 1394 1395 1396 /++ 1397 After selecting a type from [WindowTypes], you may further customize 1398 its behavior by setting one or more of these flags. 1399 1400 1401 The different window types have different meanings of `normal`. If the 1402 window type already is a good match for what you want to do, you should 1403 just use [WindowFlags.normal], the default, which will do the right thing 1404 for your users. 1405 1406 The window flags will not always be honored by the operating system 1407 and window managers; they are hints, not commands. 1408 +/ 1409 enum WindowFlags : int { 1410 normal = 0, /// 1411 skipTaskbar = 1, /// 1412 alwaysOnTop = 2, /// 1413 alwaysOnBottom = 4, /// 1414 cannotBeActivated = 8, /// 1415 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. 1416 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. 1417 /++ 1418 Sets the window as a short-lived child of its parent, but unlike an ordinary child, 1419 it is still a top-level window. This should NOT be set separately for most window types. 1420 1421 A transient window will not keep the application open if its main window closes. 1422 1423 $(PITFALL This may not be correctly implemented and its behavior is subject to change.) 1424 1425 1426 From the ICCM: 1427 1428 $(BLOCKQUOTE 1429 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. 1430 1431 $(CITE https://tronche.com/gui/x/icccm/sec-4.html) 1432 ) 1433 1434 So if you are using a window type that already describes this like [WindowTypes.dropdownMenu] etc., you should not use this flag. 1435 1436 History: 1437 Added February 23, 2021 but not yet stabilized. 1438 +/ 1439 transient = 64, 1440 /++ 1441 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. 1442 1443 This is currently only used for the WM_TAKE_FOCUS protocol on X11 at this time. 1444 1445 History: 1446 Added April 1, 2022 1447 +/ 1448 managesChildWindowFocus = 128, 1449 1450 dontAutoShow = 0x1000_0000, /// Don't automatically show window after creation; you will have to call `show()` manually. 1451 } 1452 1453 /++ 1454 When creating a window, you can pass a type to SimpleWindow's constructor, 1455 then further customize the window by changing `WindowFlags`. 1456 1457 1458 You should mostly only need [normal], [undecorated], and [eventOnly] for normal 1459 use. The others are there to build a foundation for a higher level GUI toolkit, 1460 but are themselves not as high level as you might think from their names. 1461 1462 This list is based on the EMWH spec for X11. 1463 http://standards.freedesktop.org/wm-spec/1.4/ar01s05.html#idm139704063786896 1464 +/ 1465 enum WindowTypes : int { 1466 /// An ordinary application window. 1467 normal, 1468 /// 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. 1469 undecorated, 1470 /// 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. 1471 eventOnly, 1472 /// A drop down menu, such as from a menu bar 1473 dropdownMenu, 1474 /// A popup menu, such as from a right click 1475 popupMenu, 1476 /// A popup bubble notification 1477 notification, 1478 /* 1479 menu, /// a tearable menu bar 1480 splashScreen, /// a loading splash screen for your application 1481 tooltip, /// A tiny window showing temporary help text or something. 1482 comboBoxDropdown, 1483 dialog, 1484 toolbar 1485 */ 1486 /// a child nested inside the parent. You must pass a parent window to the ctor 1487 nestedChild, 1488 1489 /++ 1490 The type you get when you pass in an existing browser handle, which means most 1491 of simpledisplay's fancy things will not be done since they were never set up. 1492 1493 Using this to the main SimpleWindow constructor explicitly will trigger an assertion 1494 failure; you should use the existing handle constructor. 1495 1496 History: 1497 Added November 17, 2022 (previously it would have type `normal`) 1498 +/ 1499 minimallyWrapped 1500 } 1501 1502 1503 private __gshared ushort sdpyOpenGLContextVersion = 0; // default: use legacy call 1504 private __gshared bool sdpyOpenGLContextCompatible = true; // default: allow "deprecated" features 1505 private __gshared char* sdpyWindowClassStr = null; 1506 private __gshared bool sdpyOpenGLContextAllowFallback = false; 1507 1508 /** 1509 Set OpenGL context version to use. This has no effect on non-OpenGL windows. 1510 You may want to change context version if you want to use advanced shaders or 1511 other modern OpenGL techinques. This setting doesn't affect already created 1512 windows. You may use version 2.1 as your default, which should be supported 1513 by any box since 2006, so seems to be a reasonable choice. 1514 1515 Note that by default version is set to `0`, which forces SimpleDisplay to use 1516 old context creation code without any version specified. This is the safest 1517 way to init OpenGL, but it may not give you access to advanced features. 1518 1519 See available OpenGL versions here: https://en.wikipedia.org/wiki/OpenGL 1520 */ 1521 void setOpenGLContextVersion() (ubyte hi, ubyte lo) { sdpyOpenGLContextVersion = cast(ushort)(hi<<8|lo); } 1522 1523 /** 1524 Set OpenGL context mode. Modern (3.0+) OpenGL versions deprecated old fixed 1525 pipeline functions, and without "compatible" mode you won't be able to use 1526 your old non-shader-based code with such contexts. By default SimpleDisplay 1527 creates compatible context, so you can gradually upgrade your OpenGL code if 1528 you want to (or leave it as is, as it should "just work"). 1529 */ 1530 @property void openGLContextCompatible() (bool v) { sdpyOpenGLContextCompatible = v; } 1531 1532 /** 1533 Set to `true` to allow creating OpenGL context with lower version than requested 1534 instead of throwing. If fallback was activated (or legacy OpenGL was requested), 1535 `openGLContextFallbackActivated()` will return `true`. 1536 */ 1537 @property void openGLContextAllowFallback() (bool v) { sdpyOpenGLContextAllowFallback = v; } 1538 1539 /** 1540 After creating OpenGL window, you can check this to see if you got only "legacy" OpenGL context. 1541 */ 1542 @property bool openGLContextFallbackActivated() () { return (sdpyOpenGLContextVersion == 0); } 1543 1544 /++ 1545 History: 1546 Added April 24, 2023 (dub v11.0) 1547 +/ 1548 version(without_opengl) {} else 1549 auto openGLCurrentContext() { 1550 version(Windows) 1551 return wglGetCurrentContext(); 1552 else 1553 return glXGetCurrentContext(); 1554 } 1555 1556 1557 /** 1558 Set window class name for all following `new SimpleWindow()` calls. 1559 1560 WARNING! For Windows, you should set your class name before creating any 1561 window, and NEVER change it after that! 1562 */ 1563 void sdpyWindowClass (const(char)[] v) { 1564 import core.stdc.stdlib : realloc; 1565 if (v.length == 0) v = "SimpleDisplayWindow"; 1566 sdpyWindowClassStr = cast(char*)realloc(sdpyWindowClassStr, v.length+1); 1567 if (sdpyWindowClassStr is null) return; // oops 1568 sdpyWindowClassStr[0..v.length+1] = 0; 1569 sdpyWindowClassStr[0..v.length] = v[]; 1570 } 1571 1572 /** 1573 Get current window class name. 1574 */ 1575 string sdpyWindowClass () { 1576 if (sdpyWindowClassStr is null) return null; 1577 foreach (immutable idx; 0..size_t.max-1) { 1578 if (sdpyWindowClassStr[idx] == 0) return sdpyWindowClassStr[0..idx].idup; 1579 } 1580 return null; 1581 } 1582 1583 /++ 1584 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. 1585 1586 If you want per-monitor dpi values, check [SimpleWindow.actualDpi], but you can fall back to this if it returns 0. 1587 +/ 1588 float[2] getDpi() { 1589 float[2] dpi; 1590 version(Windows) { 1591 HDC screen = GetDC(null); 1592 dpi[0] = GetDeviceCaps(screen, LOGPIXELSX); 1593 dpi[1] = GetDeviceCaps(screen, LOGPIXELSY); 1594 } else version(X11) { 1595 auto display = XDisplayConnection.get; 1596 auto screen = DefaultScreen(display); 1597 1598 void fallback() { 1599 /+ 1600 // 25.4 millimeters in an inch... 1601 dpi[0] = cast(float) DisplayWidth(display, screen) / DisplayWidthMM(display, screen) * 25.4; 1602 dpi[1] = cast(float) DisplayHeight(display, screen) / DisplayHeightMM(display, screen) * 25.4; 1603 +/ 1604 1605 // the physical size isn't actually as important as the logical size since this is 1606 // all about scaling really 1607 dpi[0] = 96; 1608 dpi[1] = 96; 1609 } 1610 1611 auto xft = getXftDpi(); 1612 if(xft is float.init) 1613 fallback(); 1614 else { 1615 dpi[0] = xft; 1616 dpi[1] = xft; 1617 } 1618 } 1619 1620 return dpi; 1621 } 1622 1623 version(X11) 1624 float getXftDpi() { 1625 auto display = XDisplayConnection.get; 1626 1627 char* resourceString = XResourceManagerString(display); 1628 XrmInitialize(); 1629 1630 if (resourceString) { 1631 auto db = XrmGetStringDatabase(resourceString); 1632 XrmValue value; 1633 char* type; 1634 if (XrmGetResource(db, "Xft.dpi", "String", &type, &value) == true) { 1635 if (value.addr) { 1636 import core.stdc.stdlib; 1637 return atof(cast(char*) value.addr); 1638 } 1639 } 1640 } 1641 1642 return float.init; 1643 } 1644 1645 /++ 1646 Implementation used by [SimpleWindow.takeScreenshot]. 1647 1648 Params: 1649 handle = the native window handle. If `NativeWindowHandle.init`, it will attempt to get the whole screen. 1650 width = the width of the image you wish to capture. If 0, it will attempt to capture the full width of the target. 1651 height = the height of the image you wish to capture. If 0, it will attempt to capture the full height of the target. 1652 x = the x-offset of the image to capture, from the left. 1653 y = the y-offset of the image to capture, from the top. 1654 1655 History: 1656 Added on March 14, 2021 1657 1658 Documented public on September 23, 2021 with full support for null params (dub 10.3) 1659 1660 +/ 1661 TrueColorImage trueColorImageFromNativeHandle(PaintingHandle handle, int width = 0, int height = 0, int x = 0, int y = 0) { 1662 TrueColorImage got; 1663 version(X11) { 1664 auto display = XDisplayConnection.get; 1665 if(handle == 0) 1666 handle = RootWindow(display, DefaultScreen(display)); 1667 1668 if(width == 0 || height == 0) { 1669 Window root; 1670 int xpos, ypos; 1671 uint widthret, heightret, borderret, depthret; 1672 XGetGeometry(display, handle, &root, &xpos, &ypos, &widthret, &heightret, &borderret, &depthret); 1673 1674 if(width == 0) 1675 width = widthret; 1676 if(height == 0) 1677 height = heightret; 1678 } 1679 1680 auto image = XGetImage(display, handle, x, y, width, height, (cast(c_ulong) ~0) /*AllPlanes*/, ImageFormat.ZPixmap); 1681 1682 // https://github.com/adamdruppe/arsd/issues/98 1683 1684 auto i = new Image(image); 1685 got = i.toTrueColorImage(); 1686 1687 XDestroyImage(image); 1688 } else version(Windows) { 1689 auto hdc = GetDC(handle); 1690 scope(exit) ReleaseDC(handle, hdc); 1691 1692 if(width == 0 || height == 0) { 1693 BITMAP bmHeader; 1694 auto bm = GetCurrentObject(hdc, OBJ_BITMAP); 1695 GetObject(bm, BITMAP.sizeof, &bmHeader); 1696 if(width == 0) 1697 width = bmHeader.bmWidth; 1698 if(height == 0) 1699 height = bmHeader.bmHeight; 1700 } 1701 1702 auto i = new Image(width, height); 1703 HDC hdcMem = CreateCompatibleDC(hdc); 1704 HBITMAP hbmOld = SelectObject(hdcMem, i.handle); 1705 BitBlt(hdcMem, x, y, width, height, hdc, 0, 0, SRCCOPY); 1706 SelectObject(hdcMem, hbmOld); 1707 DeleteDC(hdcMem); 1708 1709 got = i.toTrueColorImage(); 1710 } else featureNotImplemented(); 1711 1712 return got; 1713 } 1714 1715 version(Windows) extern(Windows) private alias SetProcessDpiAwarenessContext_t = BOOL function(HANDLE); 1716 version(Windows) extern(Windows) private __gshared UINT function(HWND) GetDpiForWindow; 1717 version(Windows) extern(Windows) private __gshared BOOL function(UINT, UINT, PVOID, UINT, UINT) SystemParametersInfoForDpi; 1718 1719 version(Windows) 1720 shared static this() { 1721 auto lib = LoadLibrary("User32.dll"); 1722 if(lib is null) 1723 return; 1724 //scope(exit) 1725 //FreeLibrary(lib); 1726 1727 SetProcessDpiAwarenessContext_t SetProcessDpiAwarenessContext = cast(SetProcessDpiAwarenessContext_t) GetProcAddress(lib, "SetProcessDpiAwarenessContext"); 1728 1729 if(SetProcessDpiAwarenessContext is null) 1730 return; 1731 1732 enum DPI_AWARENESS_CONTEXT_PER_MONITOR_AWARE_V2 = cast(HANDLE) -4; 1733 if(!SetProcessDpiAwarenessContext(DPI_AWARENESS_CONTEXT_PER_MONITOR_AWARE_V2)) { 1734 //writeln(GetLastError()); 1735 } 1736 1737 GetDpiForWindow = cast(typeof(GetDpiForWindow)) GetProcAddress(lib, "GetDpiForWindow"); 1738 SystemParametersInfoForDpi = cast(typeof(SystemParametersInfoForDpi)) GetProcAddress(lib, "SystemParametersInfoForDpi"); 1739 } 1740 1741 /++ 1742 Blocking mode for event loop calls associated with a window instance. 1743 1744 History: 1745 Added December 8, 2021 (dub v10.5). Prior to that, all calls to 1746 `window.eventLoop` were the same as calls to `EventLoop.get.run`; that 1747 is, all would block until the application quit. 1748 1749 That behavior can still be achieved here with `untilApplicationQuits`, 1750 or explicitly calling the top-level `EventLoop.get.run` function. 1751 +/ 1752 enum BlockingMode { 1753 /++ 1754 The event loop call will block until the whole application is ready 1755 to quit if it is the only one running, but if it is nested inside 1756 another one, it will only block until the window you're calling it on 1757 closes. 1758 +/ 1759 automatic = 0x00, 1760 /++ 1761 The event loop call will only return when the whole application 1762 is ready to quit. This usually means all windows have been closed. 1763 1764 This is appropriate for your main application event loop. 1765 +/ 1766 untilApplicationQuits = 0x01, 1767 /++ 1768 The event loop will return when the window you're calling it on 1769 closes. If there are other windows still open, they may be destroyed 1770 unless you have another event loop running later. 1771 1772 This might be appropriate for a modal dialog box loop. Remember that 1773 other windows are still processing input though, so you can end up 1774 with a lengthy call stack if this happens in a loop, similar to a 1775 recursive function (well, it literally is a recursive function, just 1776 not an obvious looking one). 1777 +/ 1778 untilWindowCloses = 0x02, 1779 /++ 1780 If an event loop is already running, this call will immediately 1781 return, allowing the existing loop to handle it. If not, this call 1782 will block until the condition you bitwise-or into the flag. 1783 1784 The default is to block until the application quits, same as with 1785 the `automatic` setting (since if it were nested, which triggers until 1786 window closes in automatic, this flag would instead not block at all), 1787 but if you used `BlockingMode.onlyIfNotNested | BlockingMode.untilWindowCloses`, 1788 it will only nest until the window closes. You might want that if you are 1789 going to open two windows simultaneously and want closing just one of them 1790 to trigger the event loop return. 1791 +/ 1792 onlyIfNotNested = 0x10, 1793 } 1794 1795 /++ 1796 The flagship window class. 1797 1798 1799 SimpleWindow tries to make ordinary windows very easy to create and use without locking you 1800 out of more advanced or complex features of the underlying windowing system. 1801 1802 For many applications, you can simply call `new SimpleWindow(some_width, some_height, "some title")` 1803 and get a suitable window to work with. 1804 1805 From there, you can opt into additional features, like custom resizability and OpenGL support 1806 with the next two constructor arguments. Or, if you need even more, you can set a window type 1807 and customization flags with the final two constructor arguments. 1808 1809 If none of that works for you, you can also create a window using native function calls, then 1810 wrap the window in a SimpleWindow instance by calling `new SimpleWindow(native_handle)`. Remember, 1811 though, if you do this, managing the window is still your own responsibility! Notably, you 1812 will need to destroy it yourself. 1813 +/ 1814 class SimpleWindow : CapableOfHandlingNativeEvent, CapableOfBeingDrawnUpon { 1815 1816 /++ 1817 Copies the window's current state into a [TrueColorImage]. 1818 1819 Be warned: this can be a very slow operation 1820 1821 History: 1822 Actually implemented on March 14, 2021 1823 +/ 1824 TrueColorImage takeScreenshot() { 1825 version(Windows) 1826 return trueColorImageFromNativeHandle(impl.hwnd, _width, _height); 1827 else version(OSXCocoa) 1828 throw new NotYetImplementedException(); 1829 else 1830 return trueColorImageFromNativeHandle(impl.window, _width, _height); 1831 } 1832 1833 /++ 1834 Returns the actual logical DPI for the window on its current display monitor. If the window 1835 straddles monitors, it will return the value of one or the other in a platform-defined manner. 1836 1837 Please note this function may return zero if it doesn't know the answer! 1838 1839 1840 On Windows, it returns the dpi per monitor if the operating system supports it (Windows 10), 1841 or a system dpi value if not, which will live-update if the OS supports it (Windows 8 and up). 1842 1843 On X, it reads the xrandr extension to determine monitor positions and sizes. On some systems, 1844 this is not provided, meaning it will return 0. Otherwise, it will determine which monitor the 1845 window primarily resides on by checking the center point of the window against the monitor map. 1846 1847 Returns: 1848 0 if unknown. Otherwise, a rounded value of dots per inch reported by the monitor. It 1849 assumes the X and Y dpi are the same. 1850 1851 History: 1852 Added November 26, 2021 (dub v10.4) 1853 1854 It said "physical dpi" in the description prior to July 29, 2022, but the behavior was 1855 always a logical value on Windows and usually one on Linux too, so now the docs reflect 1856 that. 1857 1858 Bugs: 1859 Probably plenty. I haven't done a lot of tests on this. I know it doesn't automatically 1860 just work on linux; you need to set ARSD_SCALING_FACTOR as an environment variable to 1861 set it. Set ARSD_SCALING_FACTOR=1;1.5 for example to set it to 1x on the primary monitor 1862 and 1.5 on the secondary monitor. 1863 1864 The local dpi is not necessarily related to the physical dpi of the monitor. The name 1865 is a historical misnomer - the real thing of interest is the scale factor and due to 1866 compatibility concerns the scale would modify dpi values to trick applications. But since 1867 that's the terminology common out there, I used it too. 1868 1869 See_Also: 1870 [getDpi] gives the value provided for the default monitor. Not necessarily the same 1871 as this since the window many be on a different monitor, but it is a reasonable fallback 1872 to use if `actualDpi` returns 0. 1873 1874 [onDpiChanged] is changed when `actualDpi` has changed. 1875 +/ 1876 int actualDpi() { 1877 version(X11) bool useFallbackDpi = false; 1878 if(!actualDpiLoadAttempted) { 1879 // FIXME: do the actual monitor we are on 1880 // and on X this is a good chance to load the monitor map. 1881 version(Windows) { 1882 if(GetDpiForWindow) 1883 actualDpi_ = GetDpiForWindow(impl.hwnd); 1884 } else version(X11) { 1885 if(!xRandrInfoLoadAttemped) { 1886 xRandrInfoLoadAttemped = true; 1887 if(!XRandrLibrary.attempted) { 1888 XRandrLibrary.loadDynamicLibrary(); 1889 } 1890 1891 if(XRandrLibrary.loadSuccessful) { 1892 auto display = XDisplayConnection.get; 1893 int scratch; 1894 int major, minor; 1895 if(!XRRQueryExtension(display, &xrrEventBase, &scratch)) 1896 goto fallback; 1897 1898 XRRQueryVersion(display, &major, &minor); 1899 if(major <= 1 && minor < 5) 1900 goto fallback; 1901 1902 int count; 1903 XRRMonitorInfo *monitors = XRRGetMonitors(display, RootWindow(display, DefaultScreen(display)), true, &count); 1904 if(monitors is null) 1905 goto fallback; 1906 scope(exit) XRRFreeMonitors(monitors); 1907 1908 MonitorInfo.info = MonitorInfo.info[0 .. 0]; 1909 MonitorInfo.info.assumeSafeAppend(); 1910 foreach(idx, monitor; monitors[0 .. count]) { 1911 MonitorInfo.info ~= MonitorInfo( 1912 Rectangle(Point(monitor.x, monitor.y), Size(monitor.width, monitor.height)), 1913 Size(monitor.mwidth, monitor.mheight), 1914 cast(int) (customScalingFactorForMonitor(cast(int) idx) * getDpi()[0]) 1915 ); 1916 1917 /+ 1918 if(monitor.mwidth == 0 || monitor.mheight == 0) 1919 // unknown physical size, just guess 96 to avoid divide by zero 1920 MonitorInfo.info ~= MonitorInfo( 1921 Rectangle(Point(monitor.x, monitor.y), Size(monitor.width, monitor.height)), 1922 Size(monitor.mwidth, monitor.mheight), 1923 96 1924 ); 1925 else 1926 // and actual thing 1927 MonitorInfo.info ~= MonitorInfo( 1928 Rectangle(Point(monitor.x, monitor.y), Size(monitor.width, monitor.height)), 1929 Size(monitor.mwidth, monitor.mheight), 1930 minInternal( 1931 // millimeter to int then rounding up. 1932 cast(int)(monitor.width * 25.4 / monitor.mwidth + 0.5), 1933 cast(int)(monitor.height * 25.4 / monitor.mheight + 0.5) 1934 ) 1935 ); 1936 +/ 1937 } 1938 // writeln("Here", MonitorInfo.info); 1939 } 1940 } 1941 1942 if(XRandrLibrary.loadSuccessful) { 1943 updateActualDpi(true); 1944 // writeln("updated"); 1945 1946 if(!requestedInput) { 1947 // this is what requests live updates should the configuration change 1948 // each time you select input, it sends an initial event, so very important 1949 // to not get into a loop of selecting input, getting event, updating data, 1950 // and reselecting input... 1951 requestedInput = true; 1952 XRRSelectInput(display, impl.window, RRScreenChangeNotifyMask); 1953 // writeln("requested input"); 1954 } 1955 } else { 1956 fallback: 1957 // make sure we disable events that aren't coming 1958 xrrEventBase = -1; 1959 // best guess... respect the custom scaling user command to some extent at least though 1960 useFallbackDpi = true; 1961 } 1962 } else version(OSXCocoa) { 1963 actualDpi_ = cast(int)(96 * customScalingFactorForMonitor(0)); // FIXME 1964 } 1965 actualDpiLoadAttempted = true; 1966 } else version(X11) if(MonitorInfo.info.length == 0) { 1967 useFallbackDpi = true; 1968 } 1969 1970 version(X11) 1971 if(useFallbackDpi) 1972 actualDpi_ = cast(int) (getDpi()[0] * customScalingFactorForMonitor(0)); 1973 1974 return actualDpi_; 1975 } 1976 1977 private int actualDpi_; 1978 private bool actualDpiLoadAttempted; 1979 1980 version(X11) private { 1981 bool requestedInput; 1982 static bool xRandrInfoLoadAttemped; 1983 struct MonitorInfo { 1984 Rectangle position; 1985 Size size; 1986 int dpi; 1987 1988 static MonitorInfo[] info; 1989 } 1990 bool screenPositionKnown; 1991 int screenPositionX; 1992 int screenPositionY; 1993 void updateActualDpi(bool loadingNow = false) { 1994 if(!loadingNow && !actualDpiLoadAttempted) 1995 actualDpi(); // just to make it do the load 1996 foreach(idx, m; MonitorInfo.info) { 1997 if(m.position.contains(Point(screenPositionX + this.width / 2, screenPositionY + this.height / 2))) { 1998 bool changed = actualDpi_ && actualDpi_ != m.dpi; 1999 actualDpi_ = m.dpi; 2000 // writeln("monitor ", idx); 2001 if(changed && onDpiChanged) 2002 onDpiChanged(); 2003 break; 2004 } 2005 } 2006 } 2007 } 2008 2009 /++ 2010 Sent when the window is moved to a new DPI context, for example, when it is dragged between monitors 2011 or if the window is moved to a new remote connection or a monitor is hot-swapped. 2012 2013 History: 2014 Added November 26, 2021 (dub v10.4) 2015 2016 See_Also: 2017 [actualDpi] 2018 +/ 2019 void delegate() onDpiChanged; 2020 2021 version(X11) { 2022 void recreateAfterDisconnect() { 2023 if(!stateDiscarded) return; 2024 2025 if(_parent !is null && _parent.stateDiscarded) 2026 _parent.recreateAfterDisconnect(); 2027 2028 bool wasHidden = hidden; 2029 2030 activeScreenPainter = null; // should already be done but just to confirm 2031 2032 actualDpi_ = 0; 2033 actualDpiLoadAttempted = false; 2034 xRandrInfoLoadAttemped = false; 2035 2036 impl.createWindow(_width, _height, _title, openglMode, _parent); 2037 2038 if(auto dh = dropHandler) { 2039 dropHandler = null; 2040 enableDragAndDrop(this, dh); 2041 } 2042 2043 if(recreateAdditionalConnectionState) 2044 recreateAdditionalConnectionState(); 2045 2046 hidden = wasHidden; 2047 stateDiscarded = false; 2048 } 2049 2050 bool stateDiscarded; 2051 void discardConnectionState() { 2052 if(XDisplayConnection.display) 2053 impl.dispose(); // if display is already null, it is hopeless to try to destroy stuff on it anyway 2054 if(discardAdditionalConnectionState) 2055 discardAdditionalConnectionState(); 2056 stateDiscarded = true; 2057 } 2058 2059 void delegate() discardAdditionalConnectionState; 2060 void delegate() recreateAdditionalConnectionState; 2061 2062 } 2063 2064 private DropHandler dropHandler; 2065 2066 SimpleWindow _parent; 2067 bool beingOpenKeepsAppOpen = true; 2068 /++ 2069 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. 2070 2071 The constructor tries to have sane default arguments, so for many cases, you only need to provide a few of them. 2072 2073 Params: 2074 2075 width = the width of the window's client area, in pixels 2076 height = the height of the window's client area, in pixels 2077 title = the title of the window (seen in the title bar, taskbar, etc.). You can change it after construction with the [SimpleWindow.title] property. 2078 opengl = [OpenGlOptions] are yes and no. If yes, it creates an OpenGL context on the window. 2079 resizable = [Resizability] has three options: 2080 $(P `allowResizing`, which allows the window to be resized by the user. The `windowResized` delegate will be called when the size is changed.) 2081 $(P `fixedSize` will not allow the user to resize the window.) 2082 $(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.) 2083 windowType = The type of window you want to make. 2084 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. 2085 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". 2086 +/ 2087 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) { 2088 claimGuiThread(); 2089 version(sdpy_thread_checks) assert(thisIsGuiThread); 2090 this._width = this._virtualWidth = width; 2091 this._height = this._virtualHeight = height; 2092 this.openglMode = opengl; 2093 version(X11) { 2094 // auto scale not implemented except with opengl and even there it is kinda weird 2095 if(resizable == Resizability.automaticallyScaleIfPossible && opengl == OpenGlOptions.no) 2096 resizable = Resizability.fixedSize; 2097 } 2098 this.resizability = resizable; 2099 this.windowType = windowType; 2100 this.customizationFlags = customizationFlags; 2101 this._title = (title is null ? "D Application" : title); 2102 this._parent = parent; 2103 impl.createWindow(width, height, this._title, opengl, parent); 2104 2105 if(windowType == WindowTypes.dropdownMenu || windowType == WindowTypes.popupMenu || windowType == WindowTypes.nestedChild || (customizationFlags & WindowFlags.transient)) 2106 beingOpenKeepsAppOpen = false; 2107 } 2108 2109 /// ditto 2110 this(int width, int height, string title, Resizability resizable, OpenGlOptions opengl = OpenGlOptions.no, WindowTypes windowType = WindowTypes.normal, int customizationFlags = WindowFlags.normal, SimpleWindow parent = null) { 2111 this(width, height, title, opengl, resizable, windowType, customizationFlags, parent); 2112 } 2113 2114 /// Same as above, except using the `Size` struct instead of separate width and height. 2115 this(Size size, string title = null, OpenGlOptions opengl = OpenGlOptions.no, Resizability resizable = Resizability.automaticallyScaleIfPossible) { 2116 this(size.width, size.height, title, opengl, resizable); 2117 } 2118 2119 /// ditto 2120 this(Size size, string title, Resizability resizable, OpenGlOptions opengl = OpenGlOptions.no) { 2121 this(size, title, opengl, resizable); 2122 } 2123 2124 2125 /++ 2126 Creates a window based on the given [Image]. It's client area 2127 width and height is equal to the image. (A window's client area 2128 is the drawable space inside; it excludes the title bar, etc.) 2129 2130 Windows based on images will not be resizable and do not use OpenGL. 2131 2132 It will draw the image in upon creation, but this will be overwritten 2133 upon any draws, including the initial window visible event. 2134 2135 You probably do not want to use this and it may be removed from 2136 the library eventually, or I might change it to be a "permanent" 2137 background image; one that is automatically drawn on it before any 2138 other drawing event. idk. 2139 +/ 2140 this(Image image, string title = null) { 2141 this(image.width, image.height, title); 2142 this.image = image; 2143 } 2144 2145 /++ 2146 Wraps a native window handle with very little additional processing - notably no destruction 2147 this is incomplete so don't use it for much right now. The purpose of this is to make native 2148 windows created through the low level API (so you can use platform-specific options and 2149 other details SimpleWindow does not expose) available to the event loop wrappers. 2150 +/ 2151 this(NativeWindowHandle nativeWindow) { 2152 windowType = WindowTypes.minimallyWrapped; 2153 version(Windows) 2154 impl.hwnd = nativeWindow; 2155 else version(X11) { 2156 impl.window = nativeWindow; 2157 if(nativeWindow) 2158 display = XDisplayConnection.get(); // get initial display to not segfault 2159 } else version(OSXCocoa) { 2160 if(nativeWindow !is NullWindow) throw new NotYetImplementedException(); 2161 } else featureNotImplemented(); 2162 // FIXME: set the size correctly 2163 _width = 1; 2164 _height = 1; 2165 if(nativeWindow) 2166 nativeMapping[cast(void*) nativeWindow] = this; 2167 2168 beingOpenKeepsAppOpen = false; 2169 2170 if(nativeWindow) 2171 CapableOfHandlingNativeEvent.nativeHandleMapping[nativeWindow] = this; 2172 _suppressDestruction = true; // so it doesn't try to close 2173 } 2174 2175 /++ 2176 Used iff [WindowFlags.managesChildWindowFocus] is set when the window is created. 2177 The delegate will be called when the window manager asks you to take focus. 2178 2179 This is currently only used for the WM_TAKE_FOCUS protocol on X11 at this time. 2180 2181 History: 2182 Added April 1, 2022 (dub v10.8) 2183 +/ 2184 SimpleWindow delegate() setRequestedInputFocus; 2185 2186 /// Experimental, do not use yet 2187 /++ 2188 Grabs exclusive input from the user until you release it with 2189 [releaseInputGrab]. 2190 2191 2192 Note: it is extremely rude to do this without good reason. 2193 Reasons may include doing some kind of mouse drag operation 2194 or popping up a temporary menu that should get events and will 2195 be dismissed at ease by the user clicking away. 2196 2197 Params: 2198 keyboard = do you want to grab keyboard input? 2199 mouse = grab mouse input? 2200 confine = confine the mouse cursor to inside this window? 2201 2202 History: 2203 Prior to March 11, 2021, grabbing the keyboard would always also 2204 set the X input focus. Now, it only focuses if it is a non-transient 2205 window and otherwise manages the input direction internally. 2206 2207 This means spurious focus/blur events will no longer be sent and the 2208 application will not steal focus from other applications (which the 2209 window manager may have rejected anyway). 2210 +/ 2211 void grabInput(bool keyboard = true, bool mouse = true, bool confine = false) { 2212 static if(UsingSimpledisplayX11) { 2213 XSync(XDisplayConnection.get, 0); 2214 if(keyboard) { 2215 if(isTransient && _parent) { 2216 /* 2217 FIXME: 2218 setting the keyboard focus is not actually that helpful, what I more likely want 2219 is the events from the parent window to be sent over here if we're transient. 2220 */ 2221 2222 _parent.inputProxy = this; 2223 } else { 2224 2225 SimpleWindow setTo; 2226 if(setRequestedInputFocus !is null) 2227 setTo = setRequestedInputFocus(); 2228 if(setTo is null) 2229 setTo = this; 2230 XSetInputFocus(XDisplayConnection.get, setTo.impl.window, RevertToParent, CurrentTime); 2231 } 2232 } 2233 if(mouse) { 2234 if(auto res = XGrabPointer(XDisplayConnection.get, this.impl.window, false /* owner_events */, 2235 EventMask.PointerMotionMask // FIXME: not efficient 2236 | EventMask.ButtonPressMask 2237 | EventMask.ButtonReleaseMask 2238 /* event mask */, GrabMode.GrabModeAsync, GrabMode.GrabModeAsync, confine ? this.impl.window : None, None, CurrentTime) 2239 ) 2240 { 2241 XSync(XDisplayConnection.get, 0); 2242 import core.stdc.stdio; 2243 printf("Grab input failed %d\n", res); 2244 //throw new Exception("Grab input failed"); 2245 } else { 2246 // cool 2247 } 2248 } 2249 2250 } else version(Windows) { 2251 // FIXME: keyboard? 2252 SetCapture(impl.hwnd); 2253 if(confine) { 2254 RECT rcClip; 2255 //RECT rcOldClip; 2256 //GetClipCursor(&rcOldClip); 2257 GetWindowRect(hwnd, &rcClip); 2258 ClipCursor(&rcClip); 2259 } 2260 } else version(OSXCocoa) { 2261 // throw new NotYetImplementedException(); 2262 } else static assert(0); 2263 } 2264 2265 private Point imePopupLocation = Point(0, 0); 2266 2267 /++ 2268 Sets the location for the IME (input method editor) to pop up when the user activates it. 2269 2270 Bugs: 2271 Not implemented outside X11. 2272 +/ 2273 void setIMEPopupLocation(Point location) { 2274 static if(UsingSimpledisplayX11) { 2275 imePopupLocation = location; 2276 updateIMEPopupLocation(); 2277 } else { 2278 // this is non-fatal at this point... but still wanna find it when i search for NotYetImplementedException at least 2279 // throw new NotYetImplementedException(); 2280 } 2281 } 2282 2283 /// ditto 2284 void setIMEPopupLocation(int x, int y) { 2285 return setIMEPopupLocation(Point(x, y)); 2286 } 2287 2288 // we need to remind XIM of where we wanted to place the IME whenever the window moves 2289 // so this function gets called in setIMEPopupLocation as well as whenever the window 2290 // receives a ConfigureNotify event 2291 private void updateIMEPopupLocation() { 2292 static if(UsingSimpledisplayX11) { 2293 if (xic is null) { 2294 return; 2295 } 2296 2297 XPoint nspot; 2298 nspot.x = cast(short) imePopupLocation.x; 2299 nspot.y = cast(short) imePopupLocation.y; 2300 XVaNestedList preeditAttr = XVaCreateNestedList(0, /*XNSpotLocation*/"spotLocation".ptr, &nspot, null); 2301 XSetICValues(xic, /*XNPreeditAttributes*/"preeditAttributes".ptr, preeditAttr, null); 2302 XFree(preeditAttr); 2303 } 2304 } 2305 2306 private bool imeFocused = true; 2307 2308 /++ 2309 Tells the IME whether or not an input field is currently focused in the window. 2310 2311 Bugs: 2312 Not implemented outside X11. 2313 +/ 2314 void setIMEFocused(bool value) { 2315 imeFocused = value; 2316 updateIMEFocused(); 2317 } 2318 2319 // used to focus/unfocus the IC if necessary when the window gains/loses focus 2320 private void updateIMEFocused() { 2321 static if(UsingSimpledisplayX11) { 2322 if (xic is null) { 2323 return; 2324 } 2325 2326 if (focused && imeFocused) { 2327 XSetICFocus(xic); 2328 } else { 2329 XUnsetICFocus(xic); 2330 } 2331 } 2332 } 2333 2334 /++ 2335 Returns the native window. 2336 2337 History: 2338 Added November 5, 2021 (dub v10.4). Prior to that, you'd have 2339 to access it through the `impl` member (which is semi-supported 2340 but platform specific and here it is simple enough to offer an accessor). 2341 2342 Bugs: 2343 Not implemented outside Windows or X11. 2344 +/ 2345 NativeWindowHandle nativeWindowHandle() { 2346 version(X11) 2347 return impl.window; 2348 else version(Windows) 2349 return impl.hwnd; 2350 else 2351 throw new NotYetImplementedException(); 2352 } 2353 2354 private bool isTransient() { 2355 with(WindowTypes) 2356 final switch(windowType) { 2357 case normal, undecorated, eventOnly: 2358 case nestedChild, minimallyWrapped: 2359 return (customizationFlags & WindowFlags.transient) ? true : false; 2360 case dropdownMenu, popupMenu, notification: 2361 return true; 2362 } 2363 } 2364 2365 private SimpleWindow inputProxy; 2366 2367 /++ 2368 Releases the grab acquired by [grabInput]. 2369 +/ 2370 void releaseInputGrab() { 2371 static if(UsingSimpledisplayX11) { 2372 XUngrabPointer(XDisplayConnection.get, CurrentTime); 2373 if(_parent) 2374 _parent.inputProxy = null; 2375 } else version(Windows) { 2376 ReleaseCapture(); 2377 ClipCursor(null); 2378 } else version(OSXCocoa) { 2379 // throw new NotYetImplementedException(); 2380 } else static assert(0); 2381 } 2382 2383 /++ 2384 Sets the input focus to this window. 2385 2386 You shouldn't call this very often - please let the user control the input focus. 2387 +/ 2388 void focus() { 2389 static if(UsingSimpledisplayX11) { 2390 SimpleWindow setTo; 2391 if(setRequestedInputFocus !is null) 2392 setTo = setRequestedInputFocus(); 2393 if(setTo is null) 2394 setTo = this; 2395 XSetInputFocus(XDisplayConnection.get, setTo.impl.window, RevertToParent, CurrentTime); 2396 } else version(Windows) { 2397 SetFocus(this.impl.hwnd); 2398 } else version(OSXCocoa) { 2399 throw new NotYetImplementedException(); 2400 } else static assert(0); 2401 } 2402 2403 /++ 2404 Requests attention from the user for this window. 2405 2406 2407 The typical result of this function is to change the color 2408 of the taskbar icon, though it may be tweaked on specific 2409 platforms. 2410 2411 It is meant to unobtrusively tell the user that something 2412 relevant to them happened in the background and they should 2413 check the window when they get a chance. Upon receiving the 2414 keyboard focus, the window will automatically return to its 2415 natural state. 2416 2417 If the window already has the keyboard focus, this function 2418 may do nothing, because the user is presumed to already be 2419 giving the window attention. 2420 2421 Implementation_note: 2422 2423 `requestAttention` uses the _NET_WM_STATE_DEMANDS_ATTENTION 2424 atom on X11 and the FlashWindow function on Windows. 2425 +/ 2426 void requestAttention() { 2427 if(_focused) 2428 return; 2429 2430 version(Windows) { 2431 FLASHWINFO info; 2432 info.cbSize = info.sizeof; 2433 info.hwnd = impl.hwnd; 2434 info.dwFlags = FLASHW_TRAY; 2435 info.uCount = 1; 2436 2437 FlashWindowEx(&info); 2438 2439 } else version(X11) { 2440 demandingAttention = true; 2441 demandAttention(this, true); 2442 } else version(OSXCocoa) { 2443 throw new NotYetImplementedException(); 2444 } else static assert(0); 2445 } 2446 2447 private bool _focused; 2448 2449 version(X11) private bool demandingAttention; 2450 2451 /// This will be called when WM wants to close your window (i.e. user clicked "close" icon, for example). 2452 /// You'll have to call `close()` manually if you set this delegate. 2453 void delegate () closeQuery; 2454 2455 /// This will be called when window visibility was changed. 2456 void delegate (bool becomesVisible) visibilityChanged; 2457 2458 /// This will be called when window becomes visible for the first time. 2459 /// You can do OpenGL initialization here. Note that in X11 you can't call 2460 /// [setAsCurrentOpenGlContext] right after window creation, or X11 may 2461 /// fail to send reparent and map events (hit that with proprietary NVidia drivers). 2462 /// So you need to wait until this is called and call setAsCurrentOpenGlContext in there, then do the OpenGL initialization. 2463 private bool _visibleForTheFirstTimeCalled; 2464 void delegate () visibleForTheFirstTime; 2465 2466 /// Returns true if the window has been closed. 2467 final @property bool closed() { return _closed; } 2468 2469 private final @property bool notClosed() { return !_closed; } 2470 2471 /// Returns true if the window is focused. 2472 final @property bool focused() { return _focused; } 2473 2474 private bool _visible; 2475 /// Returns true if the window is visible (mapped). 2476 final @property bool visible() { return _visible; } 2477 2478 /// Closes the window. If there are no more open windows, the event loop will terminate. 2479 void close() { 2480 if (!_closed) { 2481 runInGuiThread( { 2482 if(_closed) return; // another thread got to it first. this isn't a big deal, it just means our message was queued 2483 if (onClosing !is null) onClosing(); 2484 impl.closeWindow(); 2485 _closed = true; 2486 } ); 2487 } 2488 } 2489 2490 /++ 2491 `close` is one of the few methods that can be called from other threads. This `shared` overload reflects that. 2492 2493 History: 2494 Overload added on March 7, 2021. 2495 +/ 2496 void close() shared { 2497 (cast() this).close(); 2498 } 2499 2500 /++ 2501 2502 +/ 2503 void maximize() { 2504 version(Windows) 2505 ShowWindow(impl.hwnd, SW_MAXIMIZE); 2506 else version(X11) { 2507 setNetWmStateAtom(this.impl.window, GetAtom!("_NET_WM_STATE_MAXIMIZED_VERT", false)(XDisplayConnection.get), true, GetAtom!("_NET_WM_STATE_MAXIMIZED_HORZ", false)(XDisplayConnection.get)); 2508 2509 // also note _NET_WM_STATE_FULLSCREEN 2510 } 2511 2512 } 2513 2514 private bool _fullscreen; 2515 version(Windows) 2516 private WINDOWPLACEMENT g_wpPrev; 2517 2518 /// not fully implemented but planned for a future release 2519 void fullscreen(bool yes) { 2520 version(Windows) { 2521 g_wpPrev.length = WINDOWPLACEMENT.sizeof; 2522 DWORD dwStyle = GetWindowLong(hwnd, GWL_STYLE); 2523 if (dwStyle & WS_OVERLAPPEDWINDOW) { 2524 MONITORINFO mi; 2525 mi.cbSize = MONITORINFO.sizeof; 2526 if (GetWindowPlacement(hwnd, &g_wpPrev) && 2527 GetMonitorInfo(MonitorFromWindow(hwnd, 2528 MONITOR_DEFAULTTOPRIMARY), &mi)) { 2529 SetWindowLong(hwnd, GWL_STYLE, 2530 dwStyle & ~WS_OVERLAPPEDWINDOW); 2531 SetWindowPos(hwnd, HWND_TOP, 2532 mi.rcMonitor.left, mi.rcMonitor.top, 2533 mi.rcMonitor.right - mi.rcMonitor.left, 2534 mi.rcMonitor.bottom - mi.rcMonitor.top, 2535 SWP_NOOWNERZORDER | SWP_FRAMECHANGED); 2536 } 2537 } else { 2538 SetWindowLong(hwnd, GWL_STYLE, 2539 dwStyle | WS_OVERLAPPEDWINDOW); 2540 SetWindowPlacement(hwnd, &g_wpPrev); 2541 SetWindowPos(hwnd, null, 0, 0, 0, 0, 2542 SWP_NOMOVE | SWP_NOSIZE | SWP_NOZORDER | 2543 SWP_NOOWNERZORDER | SWP_FRAMECHANGED); 2544 } 2545 2546 } else version(X11) { 2547 setNetWmStateAtom(this.impl.window, GetAtom!("_NET_WM_STATE_FULLSCREEN", false)(XDisplayConnection.get), yes); 2548 } 2549 2550 _fullscreen = yes; 2551 2552 } 2553 2554 bool fullscreen() { 2555 return _fullscreen; 2556 } 2557 2558 /++ 2559 Note: only implemented on Windows. No-op on other platforms. You may want to use [hide] instead. 2560 2561 +/ 2562 void minimize() { 2563 version(Windows) 2564 ShowWindow(impl.hwnd, SW_MINIMIZE); 2565 //else version(X11) 2566 //setNetWmStateAtom(this, GetAtom!("_NET_WM_STATE_MINIMIZED", false)(XDisplayConnection.get), true); 2567 } 2568 2569 /// Alias for `hidden = false` 2570 void show() { 2571 hidden = false; 2572 } 2573 2574 /// Alias for `hidden = true` 2575 void hide() { 2576 hidden = true; 2577 } 2578 2579 /// Hide cursor when it enters the window. 2580 void hideCursor() { 2581 version(OSXCocoa) throw new NotYetImplementedException(); else 2582 if (!_closed) impl.hideCursor(); 2583 } 2584 2585 /// Don't hide cursor when it enters the window. 2586 void showCursor() { 2587 version(OSXCocoa) throw new NotYetImplementedException(); else 2588 if (!_closed) impl.showCursor(); 2589 } 2590 2591 /** "Warp" mouse pointer to coordinates relative to window top-left corner. Return "success" flag. 2592 * 2593 * Please remember that the cursor is a shared resource that should usually be left to the user's 2594 * control. Try to think for other approaches before using this function. 2595 * 2596 * Note: "warping" pointer will not send any synthesised mouse events, so you probably doesn't want 2597 * to use it to move mouse pointer to some active GUI area, for example, as your window won't 2598 * receive "mouse moved here" event. 2599 */ 2600 bool warpMouse (int x, int y) { 2601 version(X11) { 2602 if (!_closed) { impl.warpMouse(x, y); return true; } 2603 } else version(Windows) { 2604 if (!_closed) { 2605 POINT point; 2606 point.x = x; 2607 point.y = y; 2608 if(ClientToScreen(impl.hwnd, &point)) { 2609 SetCursorPos(point.x, point.y); 2610 return true; 2611 } 2612 } 2613 } 2614 return false; 2615 } 2616 2617 /// Send dummy window event to ping event loop. Required to process NotificationIcon on X11, for example. 2618 void sendDummyEvent () { 2619 version(X11) { 2620 if (!_closed) { impl.sendDummyEvent(); } 2621 } 2622 } 2623 2624 /// Set window minimal size. 2625 void setMinSize (int minwidth, int minheight) { 2626 version(OSXCocoa) throw new NotYetImplementedException(); else 2627 if (!_closed) impl.setMinSize(minwidth, minheight); 2628 } 2629 2630 /// Set window maximal size. 2631 void setMaxSize (int maxwidth, int maxheight) { 2632 version(OSXCocoa) throw new NotYetImplementedException(); else 2633 if (!_closed) impl.setMaxSize(maxwidth, maxheight); 2634 } 2635 2636 /// Set window resize step (window size will be changed with the given granularity on supported platforms). 2637 /// Currently only supported on X11. 2638 void setResizeGranularity (int granx, int grany) { 2639 version(OSXCocoa) throw new NotYetImplementedException(); else 2640 if (!_closed) impl.setResizeGranularity(granx, grany); 2641 } 2642 2643 /// Move window. 2644 void move(int x, int y) { 2645 version(OSXCocoa) throw new NotYetImplementedException(); else 2646 if (!_closed) impl.move(x, y); 2647 } 2648 2649 /// ditto 2650 void move(Point p) { 2651 version(OSXCocoa) throw new NotYetImplementedException(); else 2652 if (!_closed) impl.move(p.x, p.y); 2653 } 2654 2655 /++ 2656 Resize window. 2657 2658 Note that the width and height of the window are NOT instantly 2659 updated - it waits for the window manager to approve the resize 2660 request, which means you must return to the event loop before the 2661 width and height are actually changed. 2662 +/ 2663 void resize(int w, int h) { 2664 if(!_closed && _fullscreen) fullscreen = false; 2665 version(OSXCocoa) throw new NotYetImplementedException(); else 2666 if (!_closed) impl.resize(w, h); 2667 } 2668 2669 /// Move and resize window (this can be faster and more visually pleasant than doing it separately). 2670 void moveResize (int x, int y, int w, int h) { 2671 if(!_closed && _fullscreen) fullscreen = false; 2672 version(OSXCocoa) throw new NotYetImplementedException(); else 2673 if (!_closed) impl.moveResize(x, y, w, h); 2674 } 2675 2676 private bool _hidden; 2677 2678 /// Returns true if the window is hidden. 2679 final @property bool hidden() { 2680 return _hidden; 2681 } 2682 2683 /// Shows or hides the window based on the bool argument. 2684 final @property void hidden(bool b) { 2685 _hidden = b; 2686 version(Windows) { 2687 ShowWindow(impl.hwnd, b ? SW_HIDE : SW_SHOW); 2688 } else version(X11) { 2689 if(b) 2690 //XUnmapWindow(impl.display, impl.window); 2691 XWithdrawWindow(impl.display, impl.window, DefaultScreen(impl.display)); 2692 else 2693 XMapWindow(impl.display, impl.window); 2694 } else version(OSXCocoa) { 2695 // throw new NotYetImplementedException(); 2696 } else static assert(0); 2697 } 2698 2699 /// Sets the window opacity. On X11 this requires a compositor to be running. On windows the WindowFlags.extraComposite must be set at window creation. 2700 void opacity(double opacity) @property 2701 in { 2702 assert(opacity >= 0 && opacity <= 1); 2703 } do { 2704 version (Windows) { 2705 impl.setOpacity(cast(ubyte)(255 * opacity)); 2706 } else version (X11) { 2707 impl.setOpacity(cast(uint)(uint.max * opacity)); 2708 } else throw new NotYetImplementedException(); 2709 } 2710 2711 /++ 2712 Sets your event handlers, without entering the event loop. Useful if you 2713 have multiple windows - set the handlers on each window, then only do 2714 [eventLoop] on your main window or call `EventLoop.get.run();`. 2715 2716 This assigns the given handlers to [handleKeyEvent], [handleCharEvent], 2717 [handlePulse], and [handleMouseEvent] automatically based on the provide 2718 delegate signatures. 2719 +/ 2720 void setEventHandlers(T...)(T eventHandlers) { 2721 // FIXME: add more events 2722 foreach(handler; eventHandlers) { 2723 static if(__traits(compiles, handleKeyEvent = handler)) { 2724 handleKeyEvent = handler; 2725 } else static if(__traits(compiles, handleCharEvent = handler)) { 2726 handleCharEvent = handler; 2727 } else static if(__traits(compiles, handlePulse = handler)) { 2728 handlePulse = handler; 2729 } else static if(__traits(compiles, handleMouseEvent = handler)) { 2730 handleMouseEvent = handler; 2731 } else static assert(0, "I can't use this event handler " ~ typeof(handler).stringof ~ "\nHave you tried using the delegate keyword?"); 2732 } 2733 } 2734 2735 /++ 2736 The event loop automatically returns when the window is closed 2737 pulseTimeout is given in milliseconds. If pulseTimeout == 0, no 2738 pulse timer is created. The event loop will block until an event 2739 arrives or the pulse timer goes off. 2740 2741 The given `eventHandlers` are passed to [setEventHandlers], which in turn 2742 assigns them to [handleKeyEvent], [handleCharEvent], [handlePulse], and 2743 [handleMouseEvent], based on the signature of delegates you provide. 2744 2745 Give one with no parameters to set a timer pulse handler. Give one that 2746 takes [KeyEvent] for a key handler, [MouseEvent], for a mouse handler, 2747 and one that takes `dchar` for a char event handler. You can use as many 2748 or as few handlers as you need for your application. 2749 2750 Bugs: 2751 2752 $(PITFALL 2753 You should always have one event loop live for your application. 2754 If you make two windows in sequence, the second call to eventLoop 2755 might fail: 2756 2757 --- 2758 // don't do this! 2759 auto window = new SimpleWindow(); 2760 window.eventLoop(0); 2761 2762 auto window2 = new SimpleWindow(); 2763 window2.eventLoop(0); // problematic! might crash 2764 --- 2765 2766 simpledisplay's current implementation assumes that final cleanup is 2767 done when the event loop refcount reaches zero. So after the first 2768 eventLoop returns, when there isn't already another one active, it assumes 2769 the program will exit soon and cleans up. 2770 2771 This is arguably a bug that it doesn't reinitialize, and I'll probably change 2772 it eventually, but in the mean time, there's an easy solution: 2773 2774 --- 2775 // do this 2776 EventLoop mainEventLoop = EventLoop.get; // just add this line 2777 2778 auto window = new SimpleWindow(); 2779 window.eventLoop(0); 2780 2781 auto window2 = new SimpleWindow(); 2782 window2.eventLoop(0); // perfectly fine since mainEventLoop still alive 2783 --- 2784 2785 By adding a top-level reference to the event loop, it ensures the final cleanup 2786 is not performed until it goes out of scope too, letting the individual window loops 2787 work without trouble despite the bug. 2788 ) 2789 2790 History: 2791 The overload without `pulseTimeout` was added on December 8, 2021. 2792 2793 On December 9, 2021, the default blocking mode (which is now configurable 2794 because [eventLoopWithBlockingMode] was added) switched from 2795 [BlockingMode.untilApplicationQuits] over to [BlockingMode.automatic]. This 2796 should almost never be noticeable to you since the typical simpledisplay 2797 paradigm has been (and I still recommend) to have one `eventLoop` call. 2798 2799 See_Also: 2800 [eventLoopWithBlockingMode] 2801 +/ 2802 final int eventLoop(T...)( 2803 long pulseTimeout, /// set to zero if you don't want a pulse. 2804 T eventHandlers) /// delegate list like std.concurrency.receive 2805 { 2806 return eventLoopWithBlockingMode(BlockingMode.automatic, pulseTimeout, eventHandlers); 2807 } 2808 2809 /// ditto 2810 final int eventLoop(T...)(T eventHandlers) if(T.length == 0 || is(T[0] == delegate)) 2811 { 2812 return eventLoopWithBlockingMode(BlockingMode.automatic, 0, eventHandlers); 2813 } 2814 2815 /++ 2816 This is the function [eventLoop] forwards to. It, in turn, forwards to `EventLoop.get.run`. 2817 2818 History: 2819 Added December 8, 2021 (dub v10.5) 2820 2821 Previously, this implementation was right inside [eventLoop], but when I wanted 2822 to add the new [BlockingMode] parameter, the compiler got in a trouble loop so I 2823 just renamed it instead of adding as an overload. Besides, the new name makes it 2824 easier to remember the order and avoids ambiguity between two int-like params anyway. 2825 2826 See_Also: 2827 [SimpleWindow.eventLoop], [EventLoop] 2828 2829 Bugs: 2830 The blocking mode is not implemented on OSX Cocoa nor on the (deprecated) arsd.eventloop. 2831 +/ 2832 final int eventLoopWithBlockingMode(T...)( 2833 BlockingMode blockingMode, /// when you want this function to block until 2834 long pulseTimeout, /// set to zero if you don't want a pulse. 2835 T eventHandlers) /// delegate list like std.concurrency.receive 2836 { 2837 setEventHandlers(eventHandlers); 2838 2839 version(with_eventloop) { 2840 // delegates event loop to my other module 2841 version(X11) 2842 XFlush(display); 2843 2844 import arsd.eventloop; 2845 auto handle = setInterval(handlePulse, cast(int) pulseTimeout); 2846 scope(exit) clearInterval(handle); 2847 2848 loop(); 2849 return 0; 2850 } else version(OSXCocoa) { 2851 // FIXME 2852 if (handlePulse !is null && pulseTimeout != 0) { 2853 timer = NSTimer.schedule(pulseTimeout*1e-3, 2854 cast(NSid) view, sel_registerName("simpledisplay_pulse:"), 2855 null, true); 2856 } 2857 2858 view.setNeedsDisplay(true); 2859 2860 NSApp.run(); 2861 return 0; 2862 } else { 2863 EventLoop el = EventLoop(pulseTimeout, handlePulse); 2864 2865 if((blockingMode & BlockingMode.onlyIfNotNested) && el.impl.refcount > 1) 2866 return 0; 2867 2868 return el.run( 2869 ((blockingMode & 0x0f) == BlockingMode.untilApplicationQuits) ? 2870 null : 2871 &this.notClosed 2872 ); 2873 } 2874 } 2875 2876 /++ 2877 This lets you draw on the window (or its backing buffer) using basic 2878 2D primitives. 2879 2880 Be sure to call this in a limited scope because your changes will not 2881 actually appear on the window until ScreenPainter's destructor runs. 2882 2883 Returns: an instance of [ScreenPainter], which has the drawing methods 2884 on it to draw on this window. 2885 2886 Params: 2887 manualInvalidations = if you set this to true, you will need to 2888 set the invalid rectangle on the painter yourself. If false, it 2889 assumes the whole window has been redrawn each time you draw. 2890 2891 Only invalidated rectangles are blitted back to the window when 2892 the destructor runs. Doing this yourself can reduce flickering 2893 of child windows. 2894 2895 History: 2896 The `manualInvalidations` parameter overload was added on 2897 December 30, 2021 (dub v10.5) 2898 +/ 2899 ScreenPainter draw() { 2900 return draw(false); 2901 } 2902 /// ditto 2903 ScreenPainter draw(bool manualInvalidations) { 2904 return impl.getPainter(manualInvalidations); 2905 } 2906 2907 // This is here to implement the interface we use for various native handlers. 2908 NativeEventHandler getNativeEventHandler() { return handleNativeEvent; } 2909 2910 // maps native window handles to SimpleWindow instances, if there are any 2911 // you shouldn't need this, but it is public in case you do in a native event handler or something 2912 // mac uses void* cuz NSObject opHash won't pick up in typeinfo 2913 version(OSXCocoa) 2914 public __gshared SimpleWindow[void*] nativeMapping; 2915 else 2916 public __gshared SimpleWindow[NativeWindowHandle] nativeMapping; 2917 2918 // the size the user requested in the constructor, in automatic scale modes it always pretends to be this size 2919 private int _virtualWidth; 2920 private int _virtualHeight; 2921 2922 /// Width of the window's drawable client area, in pixels. 2923 @scriptable 2924 final @property int width() const pure nothrow @safe @nogc { 2925 if(resizability == Resizability.automaticallyScaleIfPossible) 2926 return _virtualWidth; 2927 else 2928 return _width; 2929 } 2930 2931 /// Height of the window's drawable client area, in pixels. 2932 @scriptable 2933 final @property int height() const pure nothrow @safe @nogc { 2934 if(resizability == Resizability.automaticallyScaleIfPossible) 2935 return _virtualHeight; 2936 else 2937 return _height; 2938 } 2939 2940 /++ 2941 Returns the actual size of the window, bypassing the logical 2942 illusions of [Resizability.automaticallyScaleIfPossible]. 2943 2944 History: 2945 Added November 11, 2022 (dub v10.10) 2946 +/ 2947 final @property Size actualWindowSize() const pure nothrow @safe @nogc { 2948 return Size(_width, _height); 2949 } 2950 2951 2952 private int _width; 2953 private int _height; 2954 2955 // HACK: making the best of some copy constructor woes with refcounting 2956 private ScreenPainterImplementation* activeScreenPainter_; 2957 2958 protected ScreenPainterImplementation* activeScreenPainter() { return activeScreenPainter_; } 2959 protected void activeScreenPainter(ScreenPainterImplementation* i) { activeScreenPainter_ = i; } 2960 2961 private OpenGlOptions openglMode; 2962 private Resizability resizability; 2963 private WindowTypes windowType; 2964 private int customizationFlags; 2965 2966 /// `true` if OpenGL was initialized for this window. 2967 @property bool isOpenGL () const pure nothrow @safe @nogc { 2968 version(without_opengl) 2969 return false; 2970 else 2971 return (openglMode == OpenGlOptions.yes); 2972 } 2973 @property Resizability resizingMode () const pure nothrow @safe @nogc { return resizability; } /// Original resizability. 2974 @property WindowTypes type () const pure nothrow @safe @nogc { return windowType; } /// Original window type. 2975 @property int customFlags () const pure nothrow @safe @nogc { return customizationFlags; } /// Original customization flags. 2976 2977 /// "Lock" this window handle, to do multithreaded synchronization. You probably won't need 2978 /// to call this, as it's not recommended to share window between threads. 2979 void mtLock () { 2980 version(X11) { 2981 XLockDisplay(this.display); 2982 } 2983 } 2984 2985 /// "Unlock" this window handle, to do multithreaded synchronization. You probably won't need 2986 /// to call this, as it's not recommended to share window between threads. 2987 void mtUnlock () { 2988 version(X11) { 2989 XUnlockDisplay(this.display); 2990 } 2991 } 2992 2993 /// Emit a beep to get user's attention. 2994 void beep () { 2995 version(X11) { 2996 XBell(this.display, 100); 2997 } else version(Windows) { 2998 MessageBeep(0xFFFFFFFF); 2999 } 3000 } 3001 3002 3003 3004 version(without_opengl) {} else { 3005 3006 /// 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`. 3007 void delegate() redrawOpenGlScene; 3008 3009 /// This will allow you to change OpenGL vsync state. 3010 final @property void vsync (bool wait) { 3011 if (this._closed) return; // window may be closed, but timer is still firing; avoid GLXBadDrawable error 3012 version(X11) { 3013 setAsCurrentOpenGlContext(); 3014 glxSetVSync(display, impl.window, wait); 3015 } else version(Windows) { 3016 setAsCurrentOpenGlContext(); 3017 wglSetVSync(wait); 3018 } 3019 } 3020 3021 /// Set this to `false` if you don't need to do `glFinish()` after `swapOpenGlBuffers()`. 3022 /// Note that at least NVidia proprietary driver may segfault if you will modify texture fast 3023 /// enough without waiting 'em to finish their frame business. 3024 bool useGLFinish = true; 3025 3026 // FIXME: it should schedule it for the end of the current iteration of the event loop... 3027 /// 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. 3028 void redrawOpenGlSceneNow() { 3029 version(X11) if (!this._visible) return; // no need to do this if window is invisible 3030 if (this._closed) return; // window may be closed, but timer is still firing; avoid GLXBadDrawable error 3031 if(redrawOpenGlScene is null) 3032 return; 3033 3034 this.mtLock(); 3035 scope(exit) this.mtUnlock(); 3036 3037 this.setAsCurrentOpenGlContext(); 3038 3039 redrawOpenGlScene(); 3040 3041 this.swapOpenGlBuffers(); 3042 // 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. 3043 if (useGLFinish) glFinish(); 3044 } 3045 3046 private bool redrawOpenGlSceneSoonSet = false; 3047 private static class RedrawOpenGlSceneEvent { 3048 SimpleWindow w; 3049 this(SimpleWindow w) { this.w = w; } 3050 } 3051 private RedrawOpenGlSceneEvent redrawOpenGlSceneEvent; 3052 /++ 3053 Queues an opengl redraw as soon as the other pending events are cleared. 3054 +/ 3055 void redrawOpenGlSceneSoon() { 3056 if(redrawOpenGlScene is null) 3057 return; 3058 3059 if(!redrawOpenGlSceneSoonSet) { 3060 redrawOpenGlSceneEvent = new RedrawOpenGlSceneEvent(this); 3061 this.addEventListener((RedrawOpenGlSceneEvent e) { e.w.redrawOpenGlSceneNow(); }); 3062 redrawOpenGlSceneSoonSet = true; 3063 } 3064 this.postEvent(redrawOpenGlSceneEvent, true); 3065 } 3066 3067 3068 /// Makes all gl* functions target this window until changed. This is only valid if you passed `OpenGlOptions.yes` to the constructor. 3069 void setAsCurrentOpenGlContext() { 3070 assert(openglMode == OpenGlOptions.yes); 3071 version(X11) { 3072 if(glXMakeCurrent(display, impl.window, impl.glc) == 0) 3073 throw new Exception("glXMakeCurrent"); 3074 } else version(Windows) { 3075 static if (SdpyIsUsingIVGLBinds) import iv.glbinds; // override druntime windows imports 3076 if (!wglMakeCurrent(ghDC, ghRC)) 3077 throw new Exception("wglMakeCurrent " ~ toInternal!int(GetLastError())); // let windows users suffer too 3078 } 3079 } 3080 3081 /// Makes all gl* functions target this window until changed. This is only valid if you passed `OpenGlOptions.yes` to the constructor. 3082 /// This doesn't throw, returning success flag instead. 3083 bool setAsCurrentOpenGlContextNT() nothrow { 3084 assert(openglMode == OpenGlOptions.yes); 3085 version(X11) { 3086 return (glXMakeCurrent(display, impl.window, impl.glc) != 0); 3087 } else version(Windows) { 3088 static if (SdpyIsUsingIVGLBinds) import iv.glbinds; // override druntime windows imports 3089 return wglMakeCurrent(ghDC, ghRC) ? true : false; 3090 } 3091 } 3092 3093 /// 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. 3094 /// This doesn't throw, returning success flag instead. 3095 bool releaseCurrentOpenGlContext() nothrow { 3096 assert(openglMode == OpenGlOptions.yes); 3097 version(X11) { 3098 return (glXMakeCurrent(display, 0, null) != 0); 3099 } else version(Windows) { 3100 static if (SdpyIsUsingIVGLBinds) import iv.glbinds; // override druntime windows imports 3101 return wglMakeCurrent(ghDC, null) ? true : false; 3102 } 3103 } 3104 3105 /++ 3106 simpledisplay always uses double buffering, usually automatically. This 3107 manually swaps the OpenGL buffers. 3108 3109 3110 You should not need to call this yourself because simpledisplay will do it 3111 for you after calling your `redrawOpenGlScene`. 3112 3113 Remember that this may throw an exception, which you can catch in a multithreaded 3114 application to keep your thread from dying from an unhandled exception. 3115 +/ 3116 void swapOpenGlBuffers() { 3117 assert(openglMode == OpenGlOptions.yes); 3118 version(X11) { 3119 if (!this._visible) return; // no need to do this if window is invisible 3120 if (this._closed) return; // window may be closed, but timer is still firing; avoid GLXBadDrawable error 3121 glXSwapBuffers(display, impl.window); 3122 } else version(Windows) { 3123 SwapBuffers(ghDC); 3124 } 3125 } 3126 } 3127 3128 /++ 3129 Set the window title, which is visible on the window manager title bar, operating system taskbar, etc. 3130 3131 3132 --- 3133 auto window = new SimpleWindow(100, 100, "First title"); 3134 window.title = "A new title"; 3135 --- 3136 3137 You may call this function at any time. 3138 +/ 3139 @property void title(string title) { 3140 _title = title; 3141 version(OSXCocoa) throw new NotYetImplementedException(); else 3142 impl.setTitle(title); 3143 } 3144 3145 private string _title; 3146 3147 /// Gets the title 3148 @property string title() { 3149 if(_title is null) 3150 _title = getRealTitle(); 3151 return _title; 3152 } 3153 3154 /++ 3155 Get the title as set by the window manager. 3156 May not match what you attempted to set. 3157 +/ 3158 string getRealTitle() { 3159 static if(is(typeof(impl.getTitle()))) 3160 return impl.getTitle(); 3161 else 3162 return null; 3163 } 3164 3165 // don't use this generally it is not yet really released 3166 version(X11) 3167 @property Image secret_icon() { 3168 return secret_icon_inner; 3169 } 3170 private Image secret_icon_inner; 3171 3172 3173 /// Set the icon that is seen in the title bar or taskbar, etc., for the user. If passed `null`, does nothing. 3174 @property void icon(MemoryImage icon) { 3175 if(icon is null) 3176 return; 3177 auto tci = icon.getAsTrueColorImage(); 3178 version(Windows) { 3179 winIcon = new WindowsIcon(icon); 3180 SendMessageA(impl.hwnd, 0x0080 /*WM_SETICON*/, 0 /*ICON_SMALL*/, cast(LPARAM) winIcon.hIcon); // there is also 1 == ICON_BIG 3181 } else version(X11) { 3182 secret_icon_inner = Image.fromMemoryImage(icon); 3183 // FIXME: ensure this is correct 3184 auto display = XDisplayConnection.get; 3185 arch_ulong[] buffer; 3186 buffer ~= icon.width; 3187 buffer ~= icon.height; 3188 foreach(c; tci.imageData.colors) { 3189 arch_ulong b; 3190 b |= c.a << 24; 3191 b |= c.r << 16; 3192 b |= c.g << 8; 3193 b |= c.b; 3194 buffer ~= b; 3195 } 3196 3197 XChangeProperty( 3198 display, 3199 impl.window, 3200 GetAtom!("_NET_WM_ICON", true)(display), 3201 GetAtom!"CARDINAL"(display), 3202 32 /* bits */, 3203 0 /*PropModeReplace*/, 3204 buffer.ptr, 3205 cast(int) buffer.length); 3206 } else version(OSXCocoa) { 3207 throw new NotYetImplementedException(); 3208 } else static assert(0); 3209 } 3210 3211 version(Windows) 3212 private WindowsIcon winIcon; 3213 3214 bool _suppressDestruction; 3215 3216 ~this() { 3217 if(!thisIsGuiThread) return; // FIXME: leaks if multithreaded gc 3218 if(_suppressDestruction) 3219 return; 3220 impl.dispose(); 3221 } 3222 3223 private bool _closed; 3224 3225 // the idea here is to draw something temporary on top of the main picture e.g. a blinking cursor 3226 /* 3227 ScreenPainter drawTransiently() { 3228 return impl.getPainter(); 3229 } 3230 */ 3231 3232 /// Draws an image on the window. This is meant to provide quick look 3233 /// of a static image generated elsewhere. 3234 @property void image(Image i) { 3235 /+ 3236 version(Windows) { 3237 BITMAP bm; 3238 HDC hdc = GetDC(hwnd); 3239 HDC hdcMem = CreateCompatibleDC(hdc); 3240 HBITMAP hbmOld = SelectObject(hdcMem, i.handle); 3241 3242 GetObject(i.handle, bm.sizeof, &bm); 3243 3244 BitBlt(hdc, 0, 0, bm.bmWidth, bm.bmHeight, hdcMem, 0, 0, SRCCOPY); 3245 3246 SelectObject(hdcMem, hbmOld); 3247 DeleteDC(hdcMem); 3248 ReleaseDC(hwnd, hdc); 3249 3250 /* 3251 RECT r; 3252 r.right = i.width; 3253 r.bottom = i.height; 3254 InvalidateRect(hwnd, &r, false); 3255 */ 3256 } else 3257 version(X11) { 3258 if(!destroyed) { 3259 if(i.usingXshm) 3260 XShmPutImage(display, cast(Drawable) window, gc, i.handle, 0, 0, 0, 0, i.width, i.height, false); 3261 else 3262 XPutImage(display, cast(Drawable) window, gc, i.handle, 0, 0, 0, 0, i.width, i.height); 3263 } 3264 } else 3265 version(OSXCocoa) { 3266 draw().drawImage(Point(0, 0), i); 3267 setNeedsDisplay(view, true); 3268 } else static assert(0); 3269 +/ 3270 auto painter = this.draw; 3271 painter.drawImage(Point(0, 0), i); 3272 } 3273 3274 /++ 3275 Changes the cursor for the window. If the cursor is hidden via [hideCursor], this has no effect. 3276 3277 --- 3278 window.cursor = GenericCursor.Help; 3279 // now the window mouse cursor is set to a generic help 3280 --- 3281 3282 +/ 3283 @property void cursor(MouseCursor cursor) { 3284 version(OSXCocoa) 3285 {} // featureNotImplemented(); 3286 else 3287 if(this.impl.curHidden <= 0) { 3288 static if(UsingSimpledisplayX11) { 3289 auto ch = cursor.cursorHandle; 3290 XDefineCursor(XDisplayConnection.get(), this.impl.window, ch); 3291 } else version(Windows) { 3292 auto ch = cursor.cursorHandle; 3293 impl.currentCursor = ch; 3294 SetCursor(ch); // redraw without waiting for mouse movement to update 3295 } else featureNotImplemented(); 3296 } 3297 3298 } 3299 3300 /// What follows are the event handlers. These are set automatically 3301 /// by the eventLoop function, but are still public so you can change 3302 /// them later. wasPressed == true means key down. false == key up. 3303 3304 /// Handles a low-level keyboard event. Settable through setEventHandlers. 3305 void delegate(KeyEvent ke) handleKeyEvent; 3306 3307 /// Handles a higher level keyboard event - c is the character just pressed. Settable through setEventHandlers. 3308 void delegate(dchar c) handleCharEvent; 3309 3310 /// Handles a timer pulse. Settable through setEventHandlers. 3311 void delegate() handlePulse; 3312 3313 /// Called when the focus changes, param is if we have it (true) or are losing it (false). 3314 void delegate(bool) onFocusChange; 3315 3316 /** Called inside `close()` method. Our window is still alive, and we can free various resources. 3317 * Sometimes it is easier to setup the delegate instead of subclassing. */ 3318 void delegate() onClosing; 3319 3320 /** Called when we received destroy notification. At this stage we cannot do much with our window 3321 * (as it is already dead, and it's native handle cannot be used), but we still can do some 3322 * last minute cleanup. */ 3323 void delegate() onDestroyed; 3324 3325 static if (UsingSimpledisplayX11) 3326 /** Called when Expose event comes. See Xlib manual to understand the arguments. 3327 * Return `false` if you want Simpledisplay to copy backbuffer, or `true` if you did it yourself. 3328 * You will probably never need to setup this handler, it is for very low-level stuff. 3329 * 3330 * WARNING! Xlib is multithread-locked when this handles is called! */ 3331 bool delegate(int x, int y, int width, int height, int eventsLeft) handleExpose; 3332 3333 //version(Windows) 3334 //bool delegate(WPARAM wParam, LPARAM lParam) handleWM_PAINT; 3335 3336 private { 3337 int lastMouseX = int.min; 3338 int lastMouseY = int.min; 3339 void mdx(ref MouseEvent ev) { 3340 if(lastMouseX == int.min || lastMouseY == int.min) { 3341 ev.dx = 0; 3342 ev.dy = 0; 3343 } else { 3344 ev.dx = ev.x - lastMouseX; 3345 ev.dy = ev.y - lastMouseY; 3346 } 3347 3348 lastMouseX = ev.x; 3349 lastMouseY = ev.y; 3350 } 3351 } 3352 3353 /// Mouse event handler. Settable through setEventHandlers. 3354 void delegate(MouseEvent) handleMouseEvent; 3355 3356 /// use to redraw child widgets if you use system apis to add stuff 3357 void delegate() paintingFinished; 3358 3359 void delegate() paintingFinishedDg() { 3360 return paintingFinished; 3361 } 3362 3363 /// handle a resize, after it happens. You must construct the window with Resizability.allowResizing 3364 /// for this to ever happen. 3365 void delegate(int width, int height) windowResized; 3366 3367 /++ 3368 Platform specific - handle any native message this window gets. 3369 3370 Note: this is called *in addition to* other event handlers, unless you either: 3371 3372 1) On X11, return 0 indicating that you handled it. Any other return value is simply discarded. 3373 3374 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. 3375 3376 On Windows, your delegate takes the form of `int delegate(HWND hwnd, UINT message, WPARAM wParam, LPARAM lParam, out int mustReturn)`. 3377 3378 On X, it takes the form of `int delegate(XEvent)`. 3379 3380 History: 3381 In ancient versions, this was `static`. If you want a global hook, use [handleNativeGlobalEvent] instead. 3382 3383 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. 3384 +/ 3385 NativeEventHandler handleNativeEvent_; 3386 3387 @property NativeEventHandler handleNativeEvent() nothrow pure @nogc const @safe { 3388 return handleNativeEvent_; 3389 } 3390 @property void handleNativeEvent(NativeEventHandler neh) nothrow pure @nogc @safe { 3391 handleNativeEvent_ = neh; 3392 } 3393 3394 version(Windows) 3395 // compatibility shim with the old deprecated way 3396 // in this one, if you return 0, it means you must return. otherwise the ret value is ignored. 3397 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) { 3398 handleNativeEvent_ = delegate int(HWND h, UINT m, WPARAM w, LPARAM l, out int r) { 3399 auto ret = dg(h, m, w, l); 3400 if(ret == 0) 3401 r = 1; 3402 return ret; 3403 }; 3404 } 3405 3406 /// This is the same as handleNativeEvent, but static so it can hook ALL events in the loop. 3407 /// If you used to use handleNativeEvent depending on it being static, just change it to use 3408 /// this instead and it will work the same way. 3409 __gshared NativeEventHandler handleNativeGlobalEvent; 3410 3411 // private: 3412 /// The native implementation is available, but you shouldn't use it unless you are 3413 /// familiar with the underlying operating system, don't mind depending on it, and 3414 /// know simpledisplay.d's internals too. It is virtually private; you can hopefully 3415 /// do what you need to do with handleNativeEvent instead. 3416 /// 3417 /// This is likely to eventually change to be just a struct holding platform-specific 3418 /// handles instead of a template mixin at some point because I'm not happy with the 3419 /// code duplication here (ironically). 3420 mixin NativeSimpleWindowImplementation!() impl; 3421 3422 /** 3423 This is in-process one-way (from anything to window) event sending mechanics. 3424 It is thread-safe, so it can be used in multi-threaded applications to send, 3425 for example, "wake up and repaint" events when thread completed some operation. 3426 This will allow to avoid using timer pulse to check events with synchronization, 3427 'cause event handler will be called in UI thread. You can stop guessing which 3428 pulse frequency will be enough for your app. 3429 Note that events handlers may be called in arbitrary order, i.e. last registered 3430 handler can be called first, and vice versa. 3431 */ 3432 public: 3433 /** Is our custom event queue empty? Can be used in simple cases to prevent 3434 * "spamming" window with events it can't cope with. 3435 * It is safe to call this from non-UI threads. 3436 */ 3437 @property bool eventQueueEmpty() () { 3438 synchronized(this) { 3439 foreach (const ref o; eventQueue[0..eventQueueUsed]) if (!o.doProcess) return false; 3440 } 3441 return true; 3442 } 3443 3444 /** Does our custom event queue contains at least one with the given type? 3445 * Can be used in simple cases to prevent "spamming" window with events 3446 * it can't cope with. 3447 * It is safe to call this from non-UI threads. 3448 */ 3449 @property bool eventQueued(ET:Object) () { 3450 synchronized(this) { 3451 foreach (const ref o; eventQueue[0..eventQueueUsed]) { 3452 if (!o.doProcess) { 3453 if (cast(ET)(o.evt)) return true; 3454 } 3455 } 3456 } 3457 return false; 3458 } 3459 3460 /++ 3461 Event listeners added with [addEventListener] have their exceptions swallowed by the event loop. This delegate can handle them again before it proceeds. 3462 3463 History: 3464 Added May 12, 2021 3465 +/ 3466 void delegate(Exception e) nothrow eventUncaughtException; 3467 3468 /** Add listener for custom event. Can be used like this: 3469 * 3470 * --------------------- 3471 * auto eid = win.addEventListener((MyStruct evt) { ... }); 3472 * ... 3473 * win.removeEventListener(eid); 3474 * --------------------- 3475 * 3476 * Returns: 0 on failure (should never happen, so ignore it) 3477 * 3478 * $(WARNING Don't use this method in object destructors!) 3479 * 3480 * $(WARNING It is better to register all event handlers and don't remove 'em, 3481 * 'cause if event handler id counter will overflow, you won't be able 3482 * to register any more events.) 3483 */ 3484 uint addEventListener(ET:Object) (void delegate (ET) dg) { 3485 if (dg is null) return 0; // ignore empty handlers 3486 synchronized(this) { 3487 //FIXME: abort on overflow? 3488 if (++lastUsedHandlerId == 0) { --lastUsedHandlerId; return 0; } // alas, can't register more events. at all. 3489 EventHandlerEntry e; 3490 e.dg = delegate (Object o) { 3491 if (auto co = cast(ET)o) { 3492 try { 3493 dg(co); 3494 } catch (Exception e) { 3495 // sorry! 3496 if(eventUncaughtException) 3497 eventUncaughtException(e); 3498 } 3499 return true; 3500 } 3501 return false; 3502 }; 3503 e.id = lastUsedHandlerId; 3504 auto optr = eventHandlers.ptr; 3505 eventHandlers ~= e; 3506 if (eventHandlers.ptr !is optr) { 3507 import core.memory : GC; 3508 if (eventHandlers.ptr is GC.addrOf(eventHandlers.ptr)) GC.setAttr(eventHandlers.ptr, GC.BlkAttr.NO_INTERIOR); 3509 } 3510 return lastUsedHandlerId; 3511 } 3512 } 3513 3514 /// Remove event listener. It is safe to pass invalid event id here. 3515 /// $(WARNING Don't use this method in object destructors!) 3516 void removeEventListener() (uint id) { 3517 if (id == 0 || id > lastUsedHandlerId) return; 3518 synchronized(this) { 3519 foreach (immutable idx; 0..eventHandlers.length) { 3520 if (eventHandlers[idx].id == id) { 3521 foreach (immutable c; idx+1..eventHandlers.length) eventHandlers[c-1] = eventHandlers[c]; 3522 eventHandlers[$-1].dg = null; 3523 eventHandlers.length -= 1; 3524 eventHandlers.assumeSafeAppend; 3525 return; 3526 } 3527 } 3528 } 3529 } 3530 3531 /// Post event to queue. It is safe to call this from non-UI threads. 3532 /// If `timeoutmsecs` is greater than zero, the event will be delayed for at least `timeoutmsecs` milliseconds. 3533 /// if `replace` is `true`, replace all existing events typed `ET` with the new one (if `evt` is empty, remove 'em all) 3534 /// Returns `true` if event was queued. Always returns `false` if `evt` is null. 3535 bool postTimeout(ET:Object) (ET evt, uint timeoutmsecs, bool replace=false) { 3536 if (this.closed) return false; // closed windows can't handle events 3537 3538 // remove all events of type `ET` 3539 void removeAllET () { 3540 uint eidx = 0, ec = eventQueueUsed; 3541 auto eptr = eventQueue.ptr; 3542 while (eidx < ec) { 3543 if (eptr.doProcess) { ++eidx; ++eptr; continue; } 3544 if (cast(ET)eptr.evt !is null) { 3545 // i found her! 3546 if (inCustomEventProcessor) { 3547 // if we're in custom event processing loop, processor will clear it for us 3548 eptr.evt = null; 3549 ++eidx; 3550 ++eptr; 3551 } else { 3552 foreach (immutable c; eidx+1..ec) eventQueue.ptr[c-1] = eventQueue.ptr[c]; 3553 ec = --eventQueueUsed; 3554 // clear last event (it is already copied) 3555 eventQueue.ptr[ec].evt = null; 3556 } 3557 } else { 3558 ++eidx; 3559 ++eptr; 3560 } 3561 } 3562 } 3563 3564 if (evt is null) { 3565 if (replace) { synchronized(this) removeAllET(); } 3566 // ignore empty events, they can't be handled anyway 3567 return false; 3568 } 3569 3570 // add events even if no event FD/event object created yet 3571 synchronized(this) { 3572 if (replace) removeAllET(); 3573 if (eventQueueUsed == uint.max) return false; // just in case 3574 if (eventQueueUsed < eventQueue.length) { 3575 eventQueue[eventQueueUsed++] = QueuedEvent(evt, timeoutmsecs); 3576 } else { 3577 if (eventQueue.capacity == eventQueue.length) { 3578 // need to reallocate; do a trick to ensure that old array is cleared 3579 auto oarr = eventQueue; 3580 eventQueue ~= QueuedEvent(evt, timeoutmsecs); 3581 // just in case, do yet another check 3582 if (oarr.length != 0 && oarr.ptr !is eventQueue.ptr) foreach (ref e; oarr[0..eventQueueUsed]) e.evt = null; 3583 import core.memory : GC; 3584 if (eventQueue.ptr is GC.addrOf(eventQueue.ptr)) GC.setAttr(eventQueue.ptr, GC.BlkAttr.NO_INTERIOR); 3585 } else { 3586 auto optr = eventQueue.ptr; 3587 eventQueue ~= QueuedEvent(evt, timeoutmsecs); 3588 assert(eventQueue.ptr is optr); 3589 } 3590 ++eventQueueUsed; 3591 assert(eventQueueUsed == eventQueue.length); 3592 } 3593 if (!eventWakeUp()) { 3594 // can't wake up event processor, so there is no reason to keep the event 3595 assert(eventQueueUsed > 0); 3596 eventQueue[--eventQueueUsed].evt = null; 3597 return false; 3598 } 3599 return true; 3600 } 3601 } 3602 3603 /// Post event to queue. It is safe to call this from non-UI threads. 3604 /// if `replace` is `true`, replace all existing events typed `ET` with the new one (if `evt` is empty, remove 'em all) 3605 /// Returns `true` if event was queued. Always returns `false` if `evt` is null. 3606 bool postEvent(ET:Object) (ET evt, bool replace=false) { 3607 return postTimeout!ET(evt, 0, replace); 3608 } 3609 3610 private: 3611 private import core.time : MonoTime; 3612 3613 version(Posix) { 3614 __gshared int customEventFDRead = -1; 3615 __gshared int customEventFDWrite = -1; 3616 __gshared int customSignalFD = -1; 3617 } else version(Windows) { 3618 __gshared HANDLE customEventH = null; 3619 } 3620 3621 // wake up event processor 3622 static bool eventWakeUp () { 3623 version(X11) { 3624 import core.sys.posix.unistd : write; 3625 ulong n = 1; 3626 if (customEventFDWrite >= 0) write(customEventFDWrite, &n, n.sizeof); 3627 return true; 3628 } else version(Windows) { 3629 if (customEventH !is null) SetEvent(customEventH); 3630 return true; 3631 } else version(OSXCocoa) { 3632 if(globalAppDelegate) 3633 globalAppDelegate.performSelectorOnMainThread(sel_registerName("sdpyCustomEventWakeup:"), null, false); 3634 return true; 3635 } else { 3636 // not implemented for other OSes 3637 return false; 3638 } 3639 } 3640 3641 static struct QueuedEvent { 3642 Object evt; 3643 bool timed = false; 3644 MonoTime hittime = MonoTime.zero; 3645 bool doProcess = false; // process event at the current iteration (internal flag) 3646 3647 this (Object aevt, uint toutmsecs) { 3648 evt = aevt; 3649 if (toutmsecs > 0) { 3650 import core.time : msecs; 3651 timed = true; 3652 hittime = MonoTime.currTime+toutmsecs.msecs; 3653 } 3654 } 3655 } 3656 3657 alias CustomEventHandler = bool delegate (Object o) nothrow; 3658 static struct EventHandlerEntry { 3659 CustomEventHandler dg; 3660 uint id; 3661 } 3662 3663 uint lastUsedHandlerId; 3664 EventHandlerEntry[] eventHandlers; 3665 QueuedEvent[] eventQueue = null; 3666 uint eventQueueUsed = 0; // to avoid `.assumeSafeAppend` and length changes 3667 bool inCustomEventProcessor = false; // required to properly remove events 3668 3669 // process queued events and call custom event handlers 3670 // this will not process events posted from called handlers (such events are postponed for the next iteration) 3671 void processCustomEvents () { 3672 bool hasSomethingToDo = false; 3673 uint ecount; 3674 bool ocep; 3675 synchronized(this) { 3676 ocep = inCustomEventProcessor; 3677 inCustomEventProcessor = true; 3678 ecount = eventQueueUsed; // user may want to post new events from an event handler; process 'em on next iteration 3679 auto ctt = MonoTime.currTime; 3680 bool hasEmpty = false; 3681 // mark events to process (this is required for `eventQueued()`) 3682 foreach (ref qe; eventQueue[0..ecount]) { 3683 if (qe.evt is null) { hasEmpty = true; continue; } 3684 if (qe.timed) { 3685 qe.doProcess = (qe.hittime <= ctt); 3686 } else { 3687 qe.doProcess = true; 3688 } 3689 hasSomethingToDo = (hasSomethingToDo || qe.doProcess); 3690 } 3691 if (!hasSomethingToDo) { 3692 // remove empty events 3693 if (hasEmpty) { 3694 uint eidx = 0, ec = eventQueueUsed; 3695 auto eptr = eventQueue.ptr; 3696 while (eidx < ec) { 3697 if (eptr.evt is null) { 3698 foreach (immutable c; eidx+1..ec) eventQueue.ptr[c-1] = eventQueue.ptr[c]; 3699 ec = --eventQueueUsed; 3700 eventQueue.ptr[ec].evt = null; // make GC life easier 3701 } else { 3702 ++eidx; 3703 ++eptr; 3704 } 3705 } 3706 } 3707 inCustomEventProcessor = ocep; 3708 return; 3709 } 3710 } 3711 // process marked events 3712 uint efree = 0; // non-processed events will be put at this index 3713 EventHandlerEntry[] eh; 3714 Object evt; 3715 foreach (immutable eidx; 0..ecount) { 3716 synchronized(this) { 3717 if (!eventQueue[eidx].doProcess) { 3718 // skip this event 3719 assert(efree <= eidx); 3720 if (efree != eidx) { 3721 // copy this event to queue start 3722 eventQueue[efree] = eventQueue[eidx]; 3723 eventQueue[eidx].evt = null; // just in case 3724 } 3725 ++efree; 3726 continue; 3727 } 3728 evt = eventQueue[eidx].evt; 3729 eventQueue[eidx].evt = null; // in case event handler will hit GC 3730 if (evt is null) continue; // just in case 3731 // try all handlers; this can be slow, but meh... 3732 eh = eventHandlers; 3733 } 3734 foreach (ref evhan; eh) if (evhan.dg !is null) evhan.dg(evt); 3735 evt = null; 3736 eh = null; 3737 } 3738 synchronized(this) { 3739 // move all unprocessed events to queue top; efree holds first "free index" 3740 foreach (immutable eidx; ecount..eventQueueUsed) { 3741 assert(efree <= eidx); 3742 if (efree != eidx) eventQueue[efree] = eventQueue[eidx]; 3743 ++efree; 3744 } 3745 eventQueueUsed = efree; 3746 // wake up event processor on next event loop iteration if we have more queued events 3747 // also, remove empty events 3748 bool awaken = false; 3749 uint eidx = 0, ec = eventQueueUsed; 3750 auto eptr = eventQueue.ptr; 3751 while (eidx < ec) { 3752 if (eptr.evt is null) { 3753 foreach (immutable c; eidx+1..ec) eventQueue.ptr[c-1] = eventQueue.ptr[c]; 3754 ec = --eventQueueUsed; 3755 eventQueue.ptr[ec].evt = null; // make GC life easier 3756 } else { 3757 if (!awaken && !eptr.timed) { eventWakeUp(); awaken = true; } 3758 ++eidx; 3759 ++eptr; 3760 } 3761 } 3762 inCustomEventProcessor = ocep; 3763 } 3764 } 3765 3766 // for all windows in nativeMapping 3767 package static void processAllCustomEvents () { 3768 3769 cleanupQueue.process(); 3770 3771 justCommunication.processCustomEvents(); 3772 3773 foreach (SimpleWindow sw; SimpleWindow.nativeMapping.byValue) { 3774 if (sw is null || sw.closed) continue; 3775 sw.processCustomEvents(); 3776 } 3777 3778 runPendingRunInGuiThreadDelegates(); 3779 } 3780 3781 // 0: infinite (i.e. no scheduled events in queue) 3782 uint eventQueueTimeoutMSecs () { 3783 synchronized(this) { 3784 if (eventQueueUsed == 0) return 0; 3785 if (inCustomEventProcessor) assert(0, "WUTAFUUUUUUU..."); // the thing that should not be. ABSOLUTELY! (c) 3786 uint res = int.max; 3787 auto ctt = MonoTime.currTime; 3788 foreach (const ref qe; eventQueue[0..eventQueueUsed]) { 3789 if (qe.evt is null) assert(0, "WUTAFUUUUUUU..."); // the thing that should not be. ABSOLUTELY! (c) 3790 if (qe.doProcess) continue; // just in case 3791 if (!qe.timed) return 1; // minimal 3792 if (qe.hittime <= ctt) return 1; // minimal 3793 auto tms = (qe.hittime-ctt).total!"msecs"; 3794 if (tms < 1) tms = 1; // safety net 3795 if (tms >= int.max) tms = int.max-1; // and another safety net 3796 if (res > tms) res = cast(uint)tms; 3797 } 3798 return (res >= int.max ? 0 : res); 3799 } 3800 } 3801 3802 // for all windows in nativeMapping 3803 static uint eventAllQueueTimeoutMSecs () { 3804 uint res = uint.max; 3805 foreach (SimpleWindow sw; SimpleWindow.nativeMapping.byValue) { 3806 if (sw is null || sw.closed) continue; 3807 uint to = sw.eventQueueTimeoutMSecs(); 3808 if (to && to < res) { 3809 res = to; 3810 if (to == 1) break; // can't have less than this 3811 } 3812 } 3813 return (res >= int.max ? 0 : res); 3814 } 3815 3816 version(X11) { 3817 ResizeEvent pendingResizeEvent; 3818 } 3819 3820 /++ 3821 When in opengl mode and automatically resizing, it will set the opengl viewport to stretch. 3822 3823 If you work with multiple opengl contexts and/or threads, this might be more trouble than it is 3824 worth so you can disable it by setting this to `true`. 3825 3826 History: 3827 Added November 13, 2022. 3828 +/ 3829 public bool suppressAutoOpenglViewport = false; 3830 private void updateOpenglViewportIfNeeded(int width, int height) { 3831 if(suppressAutoOpenglViewport) return; 3832 3833 version(without_opengl) {} else 3834 if(openglMode == OpenGlOptions.yes && resizability == Resizability.automaticallyScaleIfPossible) { 3835 // writeln(width, " ", height); 3836 setAsCurrentOpenGlContextNT(); 3837 glViewport(0, 0, width, height); 3838 } 3839 } 3840 } 3841 3842 version(OSXCocoa) 3843 enum NSWindow NullWindow = null; 3844 else 3845 enum NullWindow = NativeWindowHandle.init; 3846 3847 /++ 3848 Magic pseudo-window for just posting events to a global queue. 3849 3850 Not entirely supported, I might delete it at any time. 3851 3852 Added Nov 5, 2021. 3853 +/ 3854 __gshared SimpleWindow justCommunication = new SimpleWindow(NullWindow); 3855 3856 /* Drag and drop support { */ 3857 version(X11) { 3858 3859 } else version(Windows) { 3860 import core.sys.windows.uuid; 3861 import core.sys.windows.ole2; 3862 import core.sys.windows.oleidl; 3863 import core.sys.windows.objidl; 3864 import core.sys.windows.wtypes; 3865 3866 pragma(lib, "ole32"); 3867 void initDnd() { 3868 auto err = OleInitialize(null); 3869 if(err != S_OK && err != S_FALSE) 3870 throw new Exception("init");//err); 3871 } 3872 } 3873 /* } End drag and drop support */ 3874 3875 3876 /// Represents a mouse cursor (aka the mouse pointer, the image seen on screen that indicates where the mouse is pointing). 3877 /// See [GenericCursor]. 3878 class MouseCursor { 3879 int osId; 3880 bool isStockCursor; 3881 private this(int osId) { 3882 this.osId = osId; 3883 this.isStockCursor = true; 3884 } 3885 3886 // https://msdn.microsoft.com/en-us/library/windows/desktop/ms648385(v=vs.85).aspx 3887 this(int xHotSpot, int yHotSpot, ubyte[] andMask, ubyte[] xorMask) {} 3888 3889 version(Windows) { 3890 HCURSOR cursor_; 3891 HCURSOR cursorHandle() { 3892 if(cursor_ is null) 3893 cursor_ = LoadCursor(null, MAKEINTRESOURCE(osId)); 3894 return cursor_; 3895 } 3896 3897 } else static if(UsingSimpledisplayX11) { 3898 Cursor cursor_ = None; 3899 int xDisplaySequence; 3900 3901 Cursor cursorHandle() { 3902 if(this.osId == None) 3903 return None; 3904 3905 // we need to reload if we on a new X connection 3906 if(cursor_ == None || XDisplayConnection.connectionSequenceNumber != xDisplaySequence) { 3907 cursor_ = XCreateFontCursor(XDisplayConnection.get(), this.osId); 3908 xDisplaySequence = XDisplayConnection.connectionSequenceNumber; 3909 } 3910 return cursor_; 3911 } 3912 } 3913 } 3914 3915 // https://developer.mozilla.org/en-US/docs/Web/CSS/cursor 3916 // https://tronche.com/gui/x/xlib/appendix/b/ 3917 // https://msdn.microsoft.com/en-us/library/windows/desktop/ms648391(v=vs.85).aspx 3918 /// 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. 3919 enum GenericCursorType { 3920 Default, /// The default arrow pointer. 3921 Wait, /// A cursor indicating something is loading and the user must wait. 3922 Hand, /// A pointing finger, like the one used hovering over hyperlinks in a web browser. 3923 Help, /// A cursor indicating the user can get help about the pointer location. 3924 Cross, /// A crosshair. 3925 Text, /// An i-beam shape, typically used to indicate text selection is possible. 3926 Move, /// Pointer indicating movement is possible. May also be used as SizeAll. 3927 UpArrow, /// An arrow pointing straight up. 3928 Progress, /// The hourglass and arrow, indicating the computer is working but the user can still work. Not great results on X11. 3929 NotAllowed, /// Indicates the current operation is not allowed. Not great results on X11. 3930 SizeNesw, /// Arrow pointing northeast and southwest (lower-left corner resize indicator). 3931 SizeNs, /// Arrow pointing north and south (upper/lower edge resize indicator). 3932 SizeNwse, /// Arrow pointing northwest and southeast (upper-left corner resize indicator). 3933 SizeWe, /// Arrow pointing west and east (left/right edge resize indicator). 3934 3935 } 3936 3937 /* 3938 X_plus == css cell == Windows ? 3939 */ 3940 3941 /// You get one by `GenericCursor.SomeTime`. See [GenericCursorType] for a list of types. 3942 static struct GenericCursor { 3943 static: 3944 /// 3945 MouseCursor opDispatch(string str)() if(__traits(hasMember, GenericCursorType, str)) { 3946 static MouseCursor mc; 3947 3948 auto type = __traits(getMember, GenericCursorType, str); 3949 3950 if(mc is null) { 3951 3952 version(Windows) { 3953 int osId; 3954 final switch(type) { 3955 case GenericCursorType.Default: osId = IDC_ARROW; break; 3956 case GenericCursorType.Wait: osId = IDC_WAIT; break; 3957 case GenericCursorType.Hand: osId = IDC_HAND; break; 3958 case GenericCursorType.Help: osId = IDC_HELP; break; 3959 case GenericCursorType.Cross: osId = IDC_CROSS; break; 3960 case GenericCursorType.Text: osId = IDC_IBEAM; break; 3961 case GenericCursorType.Move: osId = IDC_SIZEALL; break; 3962 case GenericCursorType.UpArrow: osId = IDC_UPARROW; break; 3963 case GenericCursorType.Progress: osId = IDC_APPSTARTING; break; 3964 case GenericCursorType.NotAllowed: osId = IDC_NO; break; 3965 case GenericCursorType.SizeNesw: osId = IDC_SIZENESW; break; 3966 case GenericCursorType.SizeNs: osId = IDC_SIZENS; break; 3967 case GenericCursorType.SizeNwse: osId = IDC_SIZENWSE; break; 3968 case GenericCursorType.SizeWe: osId = IDC_SIZEWE; break; 3969 } 3970 } else static if(UsingSimpledisplayX11) { 3971 int osId; 3972 final switch(type) { 3973 case GenericCursorType.Default: osId = None; break; 3974 case GenericCursorType.Wait: osId = 150 /* XC_watch */; break; 3975 case GenericCursorType.Hand: osId = 60 /* XC_hand2 */; break; 3976 case GenericCursorType.Help: osId = 92 /* XC_question_arrow */; break; 3977 case GenericCursorType.Cross: osId = 34 /* XC_crosshair */; break; 3978 case GenericCursorType.Text: osId = 152 /* XC_xterm */; break; 3979 case GenericCursorType.Move: osId = 52 /* XC_fleur */; break; 3980 case GenericCursorType.UpArrow: osId = 22 /* XC_center_ptr */; break; 3981 case GenericCursorType.Progress: osId = 150 /* XC_watch, best i can do i think */; break; 3982 3983 case GenericCursorType.NotAllowed: osId = 24 /* XC_circle. not great */; break; 3984 case GenericCursorType.SizeNesw: osId = 12 /* XC_bottom_left_corner */ ; break; 3985 case GenericCursorType.SizeNs: osId = 116 /* XC_sb_v_double_arrow */; break; 3986 case GenericCursorType.SizeNwse: osId = 14 /* XC_bottom_right_corner */; break; 3987 case GenericCursorType.SizeWe: osId = 108 /* XC_sb_h_double_arrow */; break; 3988 } 3989 3990 } else { 3991 int osId; 3992 // featureNotImplemented(); 3993 } 3994 3995 mc = new MouseCursor(osId); 3996 } 3997 return mc; 3998 } 3999 } 4000 4001 4002 /++ 4003 If you want to get more control over the event loop, you can use this. 4004 4005 Typically though, you can just call [SimpleWindow.eventLoop] which forwards 4006 to `EventLoop.get.run`. 4007 +/ 4008 struct EventLoop { 4009 @disable this(); 4010 4011 /// Gets a reference to an existing event loop 4012 static EventLoop get() { 4013 return EventLoop(0, null); 4014 } 4015 4016 static void quitApplication() { 4017 EventLoop.get().exit(); 4018 } 4019 4020 private __gshared static Object monitor = new Object(); // deliberate CTFE usage here fyi 4021 4022 /// Construct an application-global event loop for yourself 4023 /// See_Also: [SimpleWindow.setEventHandlers] 4024 this(long pulseTimeout, void delegate() handlePulse) { 4025 synchronized(monitor) { 4026 if(impl is null) { 4027 claimGuiThread(); 4028 version(sdpy_thread_checks) assert(thisIsGuiThread); 4029 impl = new EventLoopImpl(pulseTimeout, handlePulse); 4030 } else { 4031 if(pulseTimeout) { 4032 impl.pulseTimeout = pulseTimeout; 4033 impl.handlePulse = handlePulse; 4034 } 4035 } 4036 impl.refcount++; 4037 } 4038 } 4039 4040 ~this() { 4041 if(impl is null) 4042 return; 4043 impl.refcount--; 4044 if(impl.refcount == 0) { 4045 impl.dispose(); 4046 if(thisIsGuiThread) 4047 guiThreadFinalize(); 4048 } 4049 4050 } 4051 4052 this(this) { 4053 if(impl is null) 4054 return; 4055 impl.refcount++; 4056 } 4057 4058 /// Runs the event loop until the whileCondition, if present, returns false 4059 int run(bool delegate() whileCondition = null) { 4060 assert(impl !is null); 4061 impl.notExited = true; 4062 return impl.run(whileCondition); 4063 } 4064 4065 /// Exits the event loop 4066 void exit() { 4067 assert(impl !is null); 4068 impl.notExited = false; 4069 } 4070 4071 version(linux) 4072 ref void delegate(int) signalHandler() { 4073 assert(impl !is null); 4074 return impl.signalHandler; 4075 } 4076 4077 __gshared static EventLoopImpl* impl; 4078 } 4079 4080 version(linux) 4081 void delegate(int, int) globalHupHandler; 4082 4083 version(Posix) 4084 void makeNonBlocking(int fd) { 4085 import fcntl = core.sys.posix.fcntl; 4086 auto flags = fcntl.fcntl(fd, fcntl.F_GETFL, 0); 4087 if(flags == -1) 4088 throw new Exception("fcntl get"); 4089 flags |= fcntl.O_NONBLOCK; 4090 auto s = fcntl.fcntl(fd, fcntl.F_SETFL, flags); 4091 if(s == -1) 4092 throw new Exception("fcntl set"); 4093 } 4094 4095 struct EventLoopImpl { 4096 int refcount; 4097 4098 bool notExited = true; 4099 4100 version(linux) { 4101 static import ep = core.sys.linux.epoll; 4102 static import unix = core.sys.posix.unistd; 4103 static import err = core.stdc.errno; 4104 import core.sys.linux.timerfd; 4105 4106 void delegate(int) signalHandler; 4107 } 4108 4109 version(X11) { 4110 int pulseFd = -1; 4111 version(linux) ep.epoll_event[16] events = void; 4112 } else version(Windows) { 4113 Timer pulser; 4114 HANDLE[] handles; 4115 } 4116 4117 4118 /// "Lock" this window handle, to do multithreaded synchronization. You probably won't need 4119 /// to call this, as it's not recommended to share window between threads. 4120 void mtLock () { 4121 version(X11) { 4122 XLockDisplay(this.display); 4123 } 4124 } 4125 4126 version(X11) 4127 auto display() { return XDisplayConnection.get; } 4128 4129 /// "Unlock" this window handle, to do multithreaded synchronization. You probably won't need 4130 /// to call this, as it's not recommended to share window between threads. 4131 void mtUnlock () { 4132 version(X11) { 4133 XUnlockDisplay(this.display); 4134 } 4135 } 4136 4137 version(with_eventloop) 4138 void initialize(long pulseTimeout) {} 4139 else 4140 void initialize(long pulseTimeout) { 4141 version(Windows) { 4142 if(pulseTimeout && handlePulse !is null) 4143 pulser = new Timer(cast(int) pulseTimeout, handlePulse); 4144 4145 if (customEventH is null) { 4146 customEventH = CreateEvent(null, FALSE/*autoreset*/, FALSE/*initial state*/, null); 4147 if (customEventH !is null) { 4148 handles ~= customEventH; 4149 } else { 4150 // this is something that should not be; better be safe than sorry 4151 throw new Exception("can't create eventfd for custom event processing"); 4152 } 4153 } 4154 4155 SimpleWindow.processAllCustomEvents(); // process events added before event object creation 4156 } 4157 4158 version(linux) { 4159 prepareEventLoop(); 4160 { 4161 auto display = XDisplayConnection.get; 4162 // adding Xlib file 4163 ep.epoll_event ev = void; 4164 { import core.stdc.string : memset; memset(&ev, 0, ev.sizeof); } // this makes valgrind happy 4165 ev.events = ep.EPOLLIN; 4166 ev.data.fd = display.fd; 4167 if(ep.epoll_ctl(epollFd, ep.EPOLL_CTL_ADD, display.fd, &ev) == -1) 4168 throw new Exception("add x fd");// ~ to!string(epollFd)); 4169 displayFd = display.fd; 4170 } 4171 4172 if(pulseTimeout && handlePulse !is null) { 4173 pulseFd = timerfd_create(CLOCK_MONOTONIC, 0); 4174 if(pulseFd == -1) 4175 throw new Exception("pulse timer create failed"); 4176 4177 itimerspec value; 4178 value.it_value.tv_sec = cast(int) (pulseTimeout / 1000); 4179 value.it_value.tv_nsec = (pulseTimeout % 1000) * 1000_000; 4180 4181 value.it_interval.tv_sec = cast(int) (pulseTimeout / 1000); 4182 value.it_interval.tv_nsec = (pulseTimeout % 1000) * 1000_000; 4183 4184 if(timerfd_settime(pulseFd, 0, &value, null) == -1) 4185 throw new Exception("couldn't make pulse timer"); 4186 4187 ep.epoll_event ev = void; 4188 { import core.stdc.string : memset; memset(&ev, 0, ev.sizeof); } // this makes valgrind happy 4189 ev.events = ep.EPOLLIN; 4190 ev.data.fd = pulseFd; 4191 ep.epoll_ctl(epollFd, ep.EPOLL_CTL_ADD, pulseFd, &ev); 4192 } 4193 4194 // eventfd for custom events 4195 if (customEventFDWrite == -1) { 4196 customEventFDWrite = eventfd(0, 0); 4197 customEventFDRead = customEventFDWrite; 4198 if (customEventFDRead >= 0) { 4199 ep.epoll_event ev = void; 4200 { import core.stdc.string : memset; memset(&ev, 0, ev.sizeof); } // this makes valgrind happy 4201 ev.events = ep.EPOLLIN; 4202 ev.data.fd = customEventFDRead; 4203 ep.epoll_ctl(epollFd, ep.EPOLL_CTL_ADD, customEventFDRead, &ev); 4204 } else { 4205 // this is something that should not be; better be safe than sorry 4206 throw new Exception("can't create eventfd for custom event processing"); 4207 } 4208 } 4209 4210 if (customSignalFD == -1) { 4211 import core.sys.linux.sys.signalfd; 4212 4213 sigset_t sigset; 4214 auto err = sigemptyset(&sigset); 4215 assert(!err); 4216 err = sigaddset(&sigset, SIGINT); 4217 assert(!err); 4218 err = sigaddset(&sigset, SIGHUP); 4219 assert(!err); 4220 err = sigprocmask(SIG_BLOCK, &sigset, null); 4221 assert(!err); 4222 4223 customSignalFD = signalfd(-1, &sigset, SFD_NONBLOCK); 4224 assert(customSignalFD != -1); 4225 4226 ep.epoll_event ev = void; 4227 { import core.stdc.string : memset; memset(&ev, 0, ev.sizeof); } // this makes valgrind happy 4228 ev.events = ep.EPOLLIN; 4229 ev.data.fd = customSignalFD; 4230 ep.epoll_ctl(epollFd, ep.EPOLL_CTL_ADD, customSignalFD, &ev); 4231 } 4232 } else version(Posix) { 4233 prepareEventLoop(); 4234 if (customEventFDRead == -1) { 4235 int[2] bfr; 4236 import core.sys.posix.unistd; 4237 auto ret = pipe(bfr); 4238 if(ret == -1) throw new Exception("pipe"); 4239 customEventFDRead = bfr[0]; 4240 customEventFDWrite = bfr[1]; 4241 } 4242 4243 } 4244 4245 SimpleWindow.processAllCustomEvents(); // process events added before event FD creation 4246 4247 version(linux) { 4248 this.mtLock(); 4249 scope(exit) this.mtUnlock(); 4250 XPending(display); // no, really 4251 } 4252 4253 disposed = false; 4254 } 4255 4256 bool disposed = true; 4257 version(X11) 4258 int displayFd = -1; 4259 4260 version(with_eventloop) 4261 void dispose() {} 4262 else 4263 void dispose() { 4264 disposed = true; 4265 version(X11) { 4266 if(pulseFd != -1) { 4267 import unix = core.sys.posix.unistd; 4268 unix.close(pulseFd); 4269 pulseFd = -1; 4270 } 4271 4272 version(linux) 4273 if(displayFd != -1) { 4274 // 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 4275 ep.epoll_event ev = void; 4276 { import core.stdc.string : memset; memset(&ev, 0, ev.sizeof); } // this makes valgrind happy 4277 ev.events = ep.EPOLLIN; 4278 ev.data.fd = displayFd; 4279 ep.epoll_ctl(epollFd, ep.EPOLL_CTL_DEL, displayFd, &ev); 4280 displayFd = -1; 4281 } 4282 4283 } else version(Windows) { 4284 if(pulser !is null) { 4285 pulser.destroy(); 4286 pulser = null; 4287 } 4288 if (customEventH !is null) { 4289 CloseHandle(customEventH); 4290 customEventH = null; 4291 } 4292 } 4293 } 4294 4295 this(long pulseTimeout, void delegate() handlePulse) { 4296 this.pulseTimeout = pulseTimeout; 4297 this.handlePulse = handlePulse; 4298 initialize(pulseTimeout); 4299 } 4300 4301 private long pulseTimeout; 4302 void delegate() handlePulse; 4303 4304 ~this() { 4305 dispose(); 4306 } 4307 4308 version(Posix) 4309 ref int customEventFDRead() { return SimpleWindow.customEventFDRead; } 4310 version(Posix) 4311 ref int customEventFDWrite() { return SimpleWindow.customEventFDWrite; } 4312 version(linux) 4313 ref int customSignalFD() { return SimpleWindow.customSignalFD; } 4314 version(Windows) 4315 ref auto customEventH() { return SimpleWindow.customEventH; } 4316 4317 version(with_eventloop) { 4318 int loopHelper(bool delegate() whileCondition) { 4319 // FIXME: whileCondition 4320 import arsd.eventloop; 4321 loop(); 4322 return 0; 4323 } 4324 } else 4325 int loopHelper(bool delegate() whileCondition) { 4326 version(X11) { 4327 bool done = false; 4328 4329 XFlush(display); 4330 insideXEventLoop = true; 4331 scope(exit) insideXEventLoop = false; 4332 4333 version(linux) { 4334 while(!done && (whileCondition is null || whileCondition() == true) && notExited) { 4335 bool forceXPending = false; 4336 auto wto = SimpleWindow.eventAllQueueTimeoutMSecs(); 4337 // eh... some events may be queued for "squashing" (or "late delivery"), so we have to do the following magic 4338 { 4339 this.mtLock(); 4340 scope(exit) this.mtUnlock(); 4341 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 4342 } 4343 //{ import core.stdc.stdio; printf("*** wto=%d; force=%d\n", wto, (forceXPending ? 1 : 0)); } 4344 auto nfds = ep.epoll_wait(epollFd, events.ptr, events.length, (wto == 0 || wto >= int.max ? -1 : cast(int)wto)); 4345 if(nfds == -1) { 4346 if(err.errno == err.EINTR) { 4347 //if(forceXPending) goto xpending; 4348 continue; // interrupted by signal, just try again 4349 } 4350 throw new Exception("epoll wait failure"); 4351 } 4352 // writeln(nfds, " ", events[0].data.fd); 4353 4354 SimpleWindow.processAllCustomEvents(); // anyway 4355 //version(sdddd) { writeln("nfds=", nfds, "; [0]=", events[0].data.fd); } 4356 foreach(idx; 0 .. nfds) { 4357 if(done) break; 4358 auto fd = events[idx].data.fd; 4359 assert(fd != -1); // should never happen cuz the api doesn't do that but better to assert than assume. 4360 auto flags = events[idx].events; 4361 if(flags & ep.EPOLLIN) { 4362 if (fd == customSignalFD) { 4363 version(linux) { 4364 import core.sys.linux.sys.signalfd; 4365 import core.sys.posix.unistd : read; 4366 signalfd_siginfo info; 4367 read(customSignalFD, &info, info.sizeof); 4368 4369 auto sig = info.ssi_signo; 4370 4371 if(EventLoop.get.signalHandler !is null) { 4372 EventLoop.get.signalHandler()(sig); 4373 } else { 4374 EventLoop.get.exit(); 4375 } 4376 } 4377 } else if(fd == display.fd) { 4378 version(sdddd) { writeln("X EVENT PENDING!"); } 4379 this.mtLock(); 4380 scope(exit) this.mtUnlock(); 4381 while(!done && XPending(display)) { 4382 done = doXNextEvent(this.display); 4383 } 4384 forceXPending = false; 4385 } else if(fd == pulseFd) { 4386 long expirationCount; 4387 // 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... 4388 4389 handlePulse(); 4390 4391 // read just to clear the buffer so poll doesn't trigger again 4392 // BTW I read AFTER the pulse because if the pulse handler takes 4393 // a lot of time to execute, we don't want the app to get stuck 4394 // in a loop of timer hits without a chance to do anything else 4395 // 4396 // IOW handlePulse happens at most once per pulse interval. 4397 unix.read(pulseFd, &expirationCount, expirationCount.sizeof); 4398 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 4399 } else if (fd == customEventFDRead) { 4400 // we have some custom events; process 'em 4401 import core.sys.posix.unistd : read; 4402 ulong n; 4403 read(customEventFDRead, &n, n.sizeof); // reset counter value to zero again 4404 //{ import core.stdc.stdio; printf("custom event! count=%u\n", eventQueueUsed); } 4405 //SimpleWindow.processAllCustomEvents(); 4406 4407 forceXPending = true; 4408 } else { 4409 // some other timer 4410 version(sdddd) { writeln("unknown fd: ", fd); } 4411 4412 if(Timer* t = fd in Timer.mapping) 4413 (*t).trigger(); 4414 4415 if(PosixFdReader* pfr = fd in PosixFdReader.mapping) 4416 (*pfr).ready(flags); 4417 4418 // we don't know what the user did in this timer, so we need to assume that 4419 // there's X data to be flushed and potentially processed 4420 forceXPending = true; 4421 4422 // or i might add support for other FDs too 4423 // but for now it is just timer 4424 // (if you want other fds, use arsd.eventloop and compile with -version=with_eventloop), it offers a fuller api for arbitrary stuff. 4425 } 4426 } 4427 if(flags & ep.EPOLLHUP) { 4428 if(PosixFdReader* pfr = fd in PosixFdReader.mapping) 4429 (*pfr).hup(flags); 4430 if(globalHupHandler) 4431 globalHupHandler(fd, flags); 4432 } 4433 /+ 4434 } else { 4435 // not interested in OUT, we are just reading here. 4436 // 4437 // error or hup might also be reported 4438 // but it shouldn't here since we are only 4439 // using a few types of FD and Xlib will report 4440 // if it dies. 4441 // so instead of thoughtfully handling it, I'll 4442 // just throw. for now at least 4443 4444 throw new Exception("epoll did something else"); 4445 } 4446 +/ 4447 } 4448 // if we won't call `XPending()` here, libX may delay some internal event delivery. 4449 // i.e. we HAVE to repeatedly call `XPending()` even if libX fd wasn't signalled! 4450 xpending: 4451 if (!done && forceXPending) { 4452 this.mtLock(); 4453 scope(exit) this.mtUnlock(); 4454 //{ import core.stdc.stdio; printf("*** queued: %d\n", XEventsQueued(this.display, QueueMode.QueuedAlready)); } 4455 while(!done && XPending(display)) { 4456 done = doXNextEvent(this.display); 4457 } 4458 } 4459 } 4460 } else { 4461 // Generic fallback: yes to simple pulse support, 4462 // but NO timer support! 4463 4464 // FIXME: we could probably support the POSIX timer_create 4465 // signal-based option, but I'm in no rush to write it since 4466 // I prefer the fd-based functions. 4467 while (!done && (whileCondition is null || whileCondition() == true) && notExited) { 4468 4469 import core.sys.posix.poll; 4470 4471 pollfd[] pfds; 4472 pollfd[32] pfdsBuffer; 4473 auto len = PosixFdReader.mapping.length + 2; 4474 // FIXME: i should just reuse the buffer 4475 if(len < pfdsBuffer.length) 4476 pfds = pfdsBuffer[0 .. len]; 4477 else 4478 pfds = new pollfd[](len); 4479 4480 pfds[0].fd = display.fd; 4481 pfds[0].events = POLLIN; 4482 pfds[0].revents = 0; 4483 4484 int slot = 1; 4485 4486 if(customEventFDRead != -1) { 4487 pfds[slot].fd = customEventFDRead; 4488 pfds[slot].events = POLLIN; 4489 pfds[slot].revents = 0; 4490 4491 slot++; 4492 } 4493 4494 foreach(fd, obj; PosixFdReader.mapping) { 4495 if(!obj.enabled) continue; 4496 pfds[slot].fd = fd; 4497 pfds[slot].events = POLLIN; 4498 pfds[slot].revents = 0; 4499 4500 slot++; 4501 } 4502 4503 auto ret = poll(pfds.ptr, slot, pulseTimeout > 0 ? cast(int) pulseTimeout : -1); 4504 if(ret == -1) throw new Exception("poll"); 4505 4506 if(ret == 0) { 4507 // FIXME it may not necessarily time out if events keep coming 4508 if(handlePulse !is null) 4509 handlePulse(); 4510 } else { 4511 foreach(s; 0 .. slot) { 4512 if(pfds[s].revents == 0) continue; 4513 4514 if(pfds[s].fd == display.fd) { 4515 while(!done && XPending(display)) { 4516 this.mtLock(); 4517 scope(exit) this.mtUnlock(); 4518 done = doXNextEvent(this.display); 4519 } 4520 } else if(customEventFDRead != -1 && pfds[s].fd == customEventFDRead) { 4521 4522 import core.sys.posix.unistd : read; 4523 ulong n; 4524 read(customEventFDRead, &n, n.sizeof); 4525 SimpleWindow.processAllCustomEvents(); 4526 } else { 4527 auto obj = PosixFdReader.mapping[pfds[s].fd]; 4528 if(pfds[s].revents & POLLNVAL) { 4529 obj.dispose(); 4530 } else { 4531 obj.ready(pfds[s].revents); 4532 } 4533 } 4534 4535 ret--; 4536 if(ret == 0) break; 4537 } 4538 } 4539 } 4540 } 4541 } 4542 4543 version(Windows) { 4544 int ret = -1; 4545 MSG message; 4546 while(ret != 0 && (whileCondition is null || whileCondition() == true) && notExited) { 4547 eventLoopRound++; 4548 auto wto = SimpleWindow.eventAllQueueTimeoutMSecs(); 4549 auto waitResult = MsgWaitForMultipleObjectsEx( 4550 cast(int) handles.length, handles.ptr, 4551 (wto == 0 ? INFINITE : wto), /* timeout */ 4552 0x04FF, /* QS_ALLINPUT */ 4553 0x0002 /* MWMO_ALERTABLE */ | 0x0004 /* MWMO_INPUTAVAILABLE */); 4554 4555 SimpleWindow.processAllCustomEvents(); // anyway 4556 enum WAIT_OBJECT_0 = 0; 4557 if(waitResult >= WAIT_OBJECT_0 && waitResult < handles.length + WAIT_OBJECT_0) { 4558 auto h = handles[waitResult - WAIT_OBJECT_0]; 4559 if(auto e = h in WindowsHandleReader.mapping) { 4560 (*e).ready(); 4561 } 4562 } else if(waitResult == handles.length + WAIT_OBJECT_0) { 4563 // message ready 4564 int count; 4565 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 4566 ret = GetMessage(&message, null, 0, 0); 4567 if(ret == -1) 4568 throw new WindowsApiException("GetMessage", GetLastError()); 4569 TranslateMessage(&message); 4570 DispatchMessage(&message); 4571 4572 count++; 4573 if(count > 10) 4574 break; // take the opportunity to catch up on other events 4575 4576 if(ret == 0) { // WM_QUIT 4577 EventLoop.quitApplication(); 4578 break; 4579 } 4580 } 4581 } else if(waitResult == 0x000000C0L /* WAIT_IO_COMPLETION */) { 4582 SleepEx(0, true); // I call this to give it a chance to do stuff like async io 4583 } else if(waitResult == 258L /* WAIT_TIMEOUT */) { 4584 // timeout, should never happen since we aren't using it 4585 } else if(waitResult == 0xFFFFFFFF) { 4586 // failed 4587 throw new WindowsApiException("MsgWaitForMultipleObjectsEx", GetLastError()); 4588 } else { 4589 // idk.... 4590 } 4591 } 4592 4593 // return message.wParam; 4594 return 0; 4595 } else { 4596 return 0; 4597 } 4598 } 4599 4600 int run(bool delegate() whileCondition = null) { 4601 if(disposed) 4602 initialize(this.pulseTimeout); 4603 4604 version(X11) { 4605 try { 4606 return loopHelper(whileCondition); 4607 } catch(XDisconnectException e) { 4608 if(e.userRequested) { 4609 foreach(item; CapableOfHandlingNativeEvent.nativeHandleMapping) 4610 item.discardConnectionState(); 4611 XCloseDisplay(XDisplayConnection.display); 4612 } 4613 4614 XDisplayConnection.display = null; 4615 4616 this.dispose(); 4617 4618 throw e; 4619 } 4620 } else { 4621 return loopHelper(whileCondition); 4622 } 4623 } 4624 } 4625 4626 4627 /++ 4628 Provides an icon on the system notification area (also known as the system tray). 4629 4630 4631 If a notification area is not available with the NotificationIcon object is created, 4632 it will silently succeed and simply attempt to create one when an area becomes available. 4633 4634 4635 NotificationAreaIcon on Windows assumes you are on Windows Vista or later. Support for 4636 Windows XP was dropped on October 31, 2023. On the other hand, support for 32 bit transparency 4637 with true color was added at that time. I was just too lazy to write the fallback. 4638 4639 If this is an issue, let me know, it'd take about an hour to get it back in there, but I suggest 4640 you use arsd 10.x when targeting Windows XP. 4641 +/ 4642 version(OSXCocoa) {} else // NotYetImplementedException 4643 class NotificationAreaIcon : CapableOfHandlingNativeEvent { 4644 4645 version(X11) { 4646 void recreateAfterDisconnect() { 4647 stateDiscarded = false; 4648 clippixmap = None; 4649 throw new Exception("NOT IMPLEMENTED"); 4650 } 4651 4652 bool stateDiscarded; 4653 void discardConnectionState() { 4654 stateDiscarded = true; 4655 } 4656 } 4657 4658 4659 version(X11) { 4660 Image img; 4661 4662 NativeEventHandler getNativeEventHandler() { 4663 return delegate int(XEvent e) { 4664 switch(e.type) { 4665 case EventType.Expose: 4666 //case EventType.VisibilityNotify: 4667 redraw(); 4668 break; 4669 case EventType.ClientMessage: 4670 version(sddddd) { 4671 writeln("\t", e.xclient.message_type == GetAtom!("_XEMBED")(XDisplayConnection.get)); 4672 writeln("\t", e.xclient.format); 4673 writeln("\t", e.xclient.data.l); 4674 } 4675 break; 4676 case EventType.ButtonPress: 4677 auto event = e.xbutton; 4678 if (onClick !is null || onClickEx !is null) { 4679 MouseButton mb = cast(MouseButton)0; 4680 switch (event.button) { 4681 case 1: mb = MouseButton.left; break; // left 4682 case 2: mb = MouseButton.middle; break; // middle 4683 case 3: mb = MouseButton.right; break; // right 4684 case 4: mb = MouseButton.wheelUp; break; // scroll up 4685 case 5: mb = MouseButton.wheelDown; break; // scroll down 4686 case 6: break; // scroll left... 4687 case 7: break; // scroll right... 4688 case 8: mb = MouseButton.backButton; break; 4689 case 9: mb = MouseButton.forwardButton; break; 4690 default: 4691 } 4692 if (mb) { 4693 try { onClick()(mb); } catch (Exception) {} 4694 if (onClickEx !is null) try { onClickEx(event.x_root, event.y_root, mb, cast(ModifierState)event.state); } catch (Exception) {} 4695 } 4696 } 4697 break; 4698 case EventType.EnterNotify: 4699 if (onEnter !is null) { 4700 onEnter(e.xcrossing.x_root, e.xcrossing.y_root, cast(ModifierState)e.xcrossing.state); 4701 } 4702 break; 4703 case EventType.LeaveNotify: 4704 if (onLeave !is null) try { onLeave(); } catch (Exception) {} 4705 break; 4706 case EventType.DestroyNotify: 4707 active = false; 4708 CapableOfHandlingNativeEvent.nativeHandleMapping.remove(nativeHandle); 4709 break; 4710 case EventType.ConfigureNotify: 4711 auto event = e.xconfigure; 4712 this.width = event.width; 4713 this.height = event.height; 4714 // writeln(width, " x " , height, " @ ", event.x, " ", event.y); 4715 redraw(); 4716 break; 4717 default: return 1; 4718 } 4719 return 1; 4720 }; 4721 } 4722 4723 /* private */ void hideBalloon() { 4724 balloon.close(); 4725 version(with_timer) 4726 timer.destroy(); 4727 balloon = null; 4728 version(with_timer) 4729 timer = null; 4730 } 4731 4732 void redraw() { 4733 if (!active) return; 4734 4735 auto display = XDisplayConnection.get; 4736 GC gc; 4737 4738 // from https://stackoverflow.com/questions/10492275/how-to-upload-32-bit-image-to-server-side-pixmap 4739 4740 int gc_depth(int depth, Display *dpy, Window root, GC *gc) { 4741 Visual *visual; 4742 XVisualInfo vis_info; 4743 XSetWindowAttributes win_attr; 4744 c_ulong win_mask; 4745 4746 if(!XMatchVisualInfo(dpy, 0, depth, 4 /*TrueColor*/, &vis_info)) { 4747 assert(0); 4748 // return 1; 4749 } 4750 4751 visual = vis_info.visual; 4752 4753 win_attr.colormap = XCreateColormap(dpy, root, visual, AllocNone); 4754 win_attr.background_pixel = 0; 4755 win_attr.border_pixel = 0; 4756 4757 win_mask = CWBackPixel | CWColormap | CWBorderPixel; 4758 4759 *gc = XCreateGC(dpy, nativeHandle, 0, null); 4760 4761 return 0; 4762 } 4763 4764 if(useAlpha) 4765 gc_depth(32, display, RootWindow(display, DefaultScreen(display)), &gc); 4766 else 4767 gc = DefaultGC(display, DefaultScreen(display)); 4768 4769 XClearWindow(display, nativeHandle); 4770 4771 if(!useAlpha && img !is null) 4772 XSetClipMask(display, gc, clippixmap); 4773 4774 /+ 4775 XSetForeground(display, gc, 4776 cast(uint) 0 << 16 | 4777 cast(uint) 0 << 8 | 4778 cast(uint) 0); 4779 XFillRectangle(display, nativeHandle, gc, 0, 0, width, height); 4780 +/ 4781 4782 if (img is null) { 4783 XSetForeground(display, gc, 4784 cast(uint) 0 << 16 | 4785 cast(uint) 127 << 8 | 4786 cast(uint) 0); 4787 XFillArc(display, nativeHandle, 4788 gc, width / 4, height / 4, width * 2 / 4, height * 2 / 4, 0 * 64, 360 * 64); 4789 } else { 4790 int dx = 0; 4791 int dy = 0; 4792 if(width > img.width) 4793 dx = (width - img.width) / 2; 4794 if(height > img.height) 4795 dy = (height - img.height) / 2; 4796 // writeln(img.width, " ", img.height, " vs ", width, " ", height); 4797 XSetClipOrigin(display, gc, dx, dy); 4798 4799 int max(int a, int b) { 4800 if(a > b) return a; else return b; 4801 } 4802 4803 if (img.usingXshm) 4804 XShmPutImage(display, cast(Drawable)nativeHandle, gc, img.handle, 0, 0, dx, dy, max(img.width, width), max(img.height, height), false); 4805 else 4806 XPutImage(display, cast(Drawable)nativeHandle, gc, img.handle, 0, 0, dx, dy, max(img.width, width), max(img.height, height)); 4807 } 4808 XSetClipMask(display, gc, None); 4809 flushGui(); 4810 } 4811 4812 static Window getTrayOwner() { 4813 auto display = XDisplayConnection.get; 4814 auto i = cast(int) DefaultScreen(display); 4815 if(i < 10 && i >= 0) { 4816 static Atom atom; 4817 if(atom == None) 4818 atom = XInternAtom(display, cast(char*) ("_NET_SYSTEM_TRAY_S"~(cast(char) (i + '0')) ~ '\0').ptr, false); 4819 return XGetSelectionOwner(display, atom); 4820 } 4821 return None; 4822 } 4823 4824 static void sendTrayMessage(arch_long message, arch_long d1, arch_long d2, arch_long d3) { 4825 auto to = getTrayOwner(); 4826 auto display = XDisplayConnection.get; 4827 XEvent ev; 4828 ev.xclient.type = EventType.ClientMessage; 4829 ev.xclient.window = to; 4830 ev.xclient.message_type = GetAtom!("_NET_SYSTEM_TRAY_OPCODE", true)(display); 4831 ev.xclient.format = 32; 4832 ev.xclient.data.l[0] = CurrentTime; 4833 ev.xclient.data.l[1] = message; 4834 ev.xclient.data.l[2] = d1; 4835 ev.xclient.data.l[3] = d2; 4836 ev.xclient.data.l[4] = d3; 4837 4838 XSendEvent(XDisplayConnection.get, to, false, EventMask.NoEventMask, &ev); 4839 } 4840 4841 private static NotificationAreaIcon[] activeIcons; 4842 4843 // FIXME: possible leak with this stuff, should be able to clear it and stuff. 4844 private void newManager() { 4845 close(); 4846 createXWin(); 4847 4848 if(this.clippixmap) 4849 XFreePixmap(XDisplayConnection.get, clippixmap); 4850 if(this.originalMemoryImage) 4851 this.icon = this.originalMemoryImage; 4852 else if(this.img) 4853 this.icon = this.img; 4854 } 4855 4856 private bool useAlpha = false; 4857 4858 private void createXWin () { 4859 // create window 4860 auto display = XDisplayConnection.get; 4861 4862 // to check for MANAGER on root window to catch new/changed tray owners 4863 XDisplayConnection.addRootInput(EventMask.StructureNotifyMask); 4864 // so if a thing does appear, we can handle it 4865 foreach(ai; activeIcons) 4866 if(ai is this) 4867 goto alreadythere; 4868 activeIcons ~= this; 4869 alreadythere: 4870 4871 // and check for an existing tray 4872 auto trayOwner = getTrayOwner(); 4873 if(trayOwner == None) 4874 return; 4875 //throw new Exception("No notification area found"); 4876 4877 Visual* v = cast(Visual*) CopyFromParent; 4878 4879 // GNOME's default is 22x22 and KDE assumes all icons are going to match that then bitmap scales 4880 // from there. It is ugly and stupid but this gives the fewest artifacts. Good environments will send 4881 // a resize event later. 4882 width = 22; 4883 height = 22; 4884 4885 // if they system gave us a 32 bit visual we need to switch to it too 4886 int depth = 24; 4887 4888 auto visualProp = getX11PropertyData(trayOwner, GetAtom!("_NET_SYSTEM_TRAY_VISUAL", true)(display)); 4889 if(visualProp !is null) { 4890 c_ulong[] info = cast(c_ulong[]) visualProp; 4891 if(info.length == 1) { 4892 auto vid = info[0]; 4893 int returned; 4894 XVisualInfo t; 4895 t.visualid = vid; 4896 auto got = XGetVisualInfo(display, VisualIDMask, &t, &returned); 4897 if(got !is null) { 4898 if(returned == 1) { 4899 v = got.visual; 4900 depth = got.depth; 4901 // writeln("using special visual ", got.depth); 4902 // writeln(depth); 4903 } 4904 XFree(got); 4905 } 4906 } 4907 } 4908 4909 int CWFlags = CWBackPixel | CWBorderPixel | CWOverrideRedirect; 4910 XSetWindowAttributes attr; 4911 attr.background_pixel = 0; 4912 attr.border_pixel = 0; 4913 attr.override_redirect = 0; 4914 if(v !is cast(Visual*) CopyFromParent) { 4915 attr.colormap = XCreateColormap(display, RootWindow(display, DefaultScreen(display)), v, AllocNone); 4916 CWFlags |= CWColormap; 4917 if(depth == 32) 4918 useAlpha = true; 4919 else 4920 goto plain; 4921 } else { 4922 plain: 4923 attr.background_pixmap = 1 /* ParentRelative */; 4924 CWFlags |= CWBackPixmap; 4925 } 4926 4927 auto nativeWindow = XCreateWindow(display, RootWindow(display, DefaultScreen(display)), 0, 0, width, height, 0, depth, InputOutput, v, CWFlags, &attr); 4928 4929 assert(nativeWindow); 4930 4931 if(!useAlpha) 4932 XSetWindowBackgroundPixmap(display, nativeWindow, 1 /* ParentRelative */); 4933 4934 nativeHandle = nativeWindow; 4935 4936 ///+ 4937 arch_ulong[2] info; 4938 info[0] = 0; 4939 info[1] = 1; 4940 4941 string title = this.name is null ? "simpledisplay.d program" : this.name; 4942 auto XA_UTF8 = XInternAtom(display, "UTF8_STRING".ptr, false); 4943 auto XA_NETWM_NAME = XInternAtom(display, "_NET_WM_NAME".ptr, false); 4944 XChangeProperty(display, nativeWindow, XA_NETWM_NAME, XA_UTF8, 8, PropModeReplace, title.ptr, cast(uint)title.length); 4945 4946 XChangeProperty( 4947 display, 4948 nativeWindow, 4949 GetAtom!("_XEMBED_INFO", true)(display), 4950 GetAtom!("_XEMBED_INFO", true)(display), 4951 32 /* bits */, 4952 0 /*PropModeReplace*/, 4953 info.ptr, 4954 2); 4955 4956 import core.sys.posix.unistd; 4957 arch_ulong pid = getpid(); 4958 4959 XChangeProperty( 4960 display, 4961 nativeWindow, 4962 GetAtom!("_NET_WM_PID", true)(display), 4963 XA_CARDINAL, 4964 32 /* bits */, 4965 0 /*PropModeReplace*/, 4966 &pid, 4967 1); 4968 4969 updateNetWmIcon(); 4970 4971 if (sdpyWindowClassStr !is null && sdpyWindowClassStr[0]) { 4972 //{ import core.stdc.stdio; printf("winclass: [%s]\n", sdpyWindowClassStr); } 4973 XClassHint klass; 4974 XWMHints wh; 4975 XSizeHints size; 4976 klass.res_name = sdpyWindowClassStr; 4977 klass.res_class = sdpyWindowClassStr; 4978 XSetWMProperties(display, nativeWindow, null, null, null, 0, &size, &wh, &klass); 4979 } 4980 4981 // believe it or not, THIS is what xfce needed for the 9999 issue 4982 XSizeHints sh; 4983 c_long spr; 4984 XGetWMNormalHints(display, nativeWindow, &sh, &spr); 4985 sh.flags |= PMaxSize | PMinSize; 4986 // FIXME maybe nicer resizing 4987 sh.min_width = 16; 4988 sh.min_height = 16; 4989 sh.max_width = 22; 4990 sh.max_height = 22; 4991 XSetWMNormalHints(display, nativeWindow, &sh); 4992 4993 4994 //+/ 4995 4996 4997 XSelectInput(display, nativeWindow, 4998 EventMask.ButtonPressMask | EventMask.ExposureMask | EventMask.StructureNotifyMask | EventMask.VisibilityChangeMask | 4999 EventMask.EnterWindowMask | EventMask.LeaveWindowMask); 5000 5001 sendTrayMessage(SYSTEM_TRAY_REQUEST_DOCK, nativeWindow, 0, 0); 5002 // XMapWindow(display, nativeWindow); // to demo it w/o a tray 5003 5004 CapableOfHandlingNativeEvent.nativeHandleMapping[nativeWindow] = this; 5005 active = true; 5006 } 5007 5008 void updateNetWmIcon() { 5009 if(img is null) return; 5010 auto display = XDisplayConnection.get; 5011 // FIXME: ensure this is correct 5012 arch_ulong[] buffer; 5013 auto imgMi = img.toTrueColorImage; 5014 buffer ~= imgMi.width; 5015 buffer ~= imgMi.height; 5016 foreach(c; imgMi.imageData.colors) { 5017 arch_ulong b; 5018 b |= c.a << 24; 5019 b |= c.r << 16; 5020 b |= c.g << 8; 5021 b |= c.b; 5022 buffer ~= b; 5023 } 5024 5025 XChangeProperty( 5026 display, 5027 nativeHandle, 5028 GetAtom!"_NET_WM_ICON"(display), 5029 GetAtom!"CARDINAL"(display), 5030 32 /* bits */, 5031 0 /*PropModeReplace*/, 5032 buffer.ptr, 5033 cast(int) buffer.length); 5034 } 5035 5036 5037 5038 private SimpleWindow balloon; 5039 version(with_timer) 5040 private Timer timer; 5041 5042 private Window nativeHandle; 5043 private Pixmap clippixmap = None; 5044 private int width = 16; 5045 private int height = 16; 5046 private bool active = false; 5047 5048 void delegate (int x, int y, MouseButton button, ModifierState mods) onClickEx; /// x and y are globals (relative to root window). X11 only. 5049 void delegate (int x, int y, ModifierState mods) onEnter; /// x and y are global window coordinates. X11 only. 5050 void delegate () onLeave; /// X11 only. 5051 5052 @property bool closed () const pure nothrow @safe @nogc { return !active; } /// 5053 5054 /// X11 only. Get global window coordinates and size. This can be used to show various notifications. 5055 void getWindowRect (out int x, out int y, out int width, out int height) { 5056 if (!active) { width = 1; height = 1; return; } // 1: just in case 5057 Window dummyw; 5058 auto dpy = XDisplayConnection.get; 5059 //XWindowAttributes xwa; 5060 //XGetWindowAttributes(dpy, nativeHandle, &xwa); 5061 //XTranslateCoordinates(dpy, nativeHandle, RootWindow(dpy, DefaultScreen(dpy)), xwa.x, xwa.y, &x, &y, &dummyw); 5062 XTranslateCoordinates(dpy, nativeHandle, RootWindow(dpy, DefaultScreen(dpy)), x, y, &x, &y, &dummyw); 5063 width = this.width; 5064 height = this.height; 5065 } 5066 } 5067 5068 /+ 5069 What I actually want from this: 5070 5071 * set / change: icon, tooltip 5072 * handle: mouse click, right click 5073 * show: notification bubble. 5074 +/ 5075 5076 version(Windows) { 5077 WindowsIcon win32Icon; 5078 HWND hwnd; 5079 5080 NOTIFYICONDATAW data; 5081 5082 NativeEventHandler getNativeEventHandler() { 5083 return delegate int(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam, out int mustReturn) { 5084 if(msg == WM_USER) { 5085 auto event = LOWORD(lParam); 5086 auto iconId = HIWORD(lParam); 5087 //auto x = GET_X_LPARAM(wParam); 5088 //auto y = GET_Y_LPARAM(wParam); 5089 switch(event) { 5090 case WM_LBUTTONDOWN: 5091 onClick()(MouseButton.left); 5092 break; 5093 case WM_RBUTTONDOWN: 5094 onClick()(MouseButton.right); 5095 break; 5096 case WM_MBUTTONDOWN: 5097 onClick()(MouseButton.middle); 5098 break; 5099 case WM_MOUSEMOVE: 5100 // sent, we could use it. 5101 break; 5102 case WM_MOUSEWHEEL: 5103 // NOT SENT 5104 break; 5105 //case NIN_KEYSELECT: 5106 //case NIN_SELECT: 5107 //break; 5108 default: {} 5109 } 5110 } 5111 return 0; 5112 }; 5113 } 5114 5115 enum NIF_SHOWTIP = 0x00000080; 5116 5117 private static struct NOTIFYICONDATAW { 5118 DWORD cbSize; 5119 HWND hWnd; 5120 UINT uID; 5121 UINT uFlags; 5122 UINT uCallbackMessage; 5123 HICON hIcon; 5124 WCHAR[128] szTip; 5125 DWORD dwState; 5126 DWORD dwStateMask; 5127 WCHAR[256] szInfo; 5128 union { 5129 UINT uTimeout; 5130 UINT uVersion; 5131 } 5132 WCHAR[64] szInfoTitle; 5133 DWORD dwInfoFlags; 5134 GUID guidItem; 5135 HICON hBalloonIcon; 5136 } 5137 5138 } 5139 5140 /++ 5141 Note that on Windows, only left, right, and middle buttons are sent. 5142 Mouse wheel buttons are NOT set, so don't rely on those events if your 5143 program is meant to be used on Windows too. 5144 +/ 5145 this(string name, MemoryImage icon, void delegate(MouseButton button) onClick) { 5146 // The canonical constructor for Windows needs the MemoryImage, so it is here, 5147 // but on X, we need an Image, so its canonical ctor is there. They should 5148 // forward to each other though. 5149 version(X11) { 5150 this.name = name; 5151 this.onClick = onClick; 5152 createXWin(); 5153 this.icon = icon; 5154 } else version(Windows) { 5155 this.onClick = onClick; 5156 this.win32Icon = new WindowsIcon(icon); 5157 5158 HINSTANCE hInstance = cast(HINSTANCE) GetModuleHandle(null); 5159 5160 static bool registered = false; 5161 if(!registered) { 5162 WNDCLASSEX wc; 5163 wc.cbSize = wc.sizeof; 5164 wc.hInstance = hInstance; 5165 wc.lpfnWndProc = &WndProc; 5166 wc.lpszClassName = "arsd_simpledisplay_notification_icon"w.ptr; 5167 if(!RegisterClassExW(&wc)) 5168 throw new WindowsApiException("RegisterClass", GetLastError()); 5169 registered = true; 5170 } 5171 5172 this.hwnd = CreateWindowW("arsd_simpledisplay_notification_icon"w.ptr, "test"w.ptr /* name */, 0 /* dwStyle */, 0, 0, 0, 0, HWND_MESSAGE, null, hInstance, null); 5173 if(hwnd is null) 5174 throw new WindowsApiException("CreateWindow", GetLastError()); 5175 5176 data.cbSize = data.sizeof; 5177 data.hWnd = hwnd; 5178 data.uID = cast(uint) cast(void*) this; 5179 data.uFlags = NIF_MESSAGE | NIF_ICON | NIF_TIP | NIF_STATE | NIF_SHOWTIP /* use default tooltip, for now. */; 5180 // NIF_INFO means show balloon 5181 data.uCallbackMessage = WM_USER; 5182 data.hIcon = this.win32Icon.hIcon; 5183 data.szTip = ""; // FIXME 5184 data.dwState = 0; // NIS_HIDDEN; // windows vista 5185 data.dwStateMask = NIS_HIDDEN; // windows vista 5186 5187 data.uVersion = 4; // NOTIFYICON_VERSION_4; // Windows Vista and up 5188 5189 5190 Shell_NotifyIcon(NIM_ADD, cast(NOTIFYICONDATA*) &data); 5191 5192 CapableOfHandlingNativeEvent.nativeHandleMapping[this.hwnd] = this; 5193 } else version(OSXCocoa) { 5194 throw new NotYetImplementedException(); 5195 } else static assert(0); 5196 } 5197 5198 /// ditto 5199 this(string name, Image icon, void delegate(MouseButton button) onClick) { 5200 version(X11) { 5201 this.onClick = onClick; 5202 this.name = name; 5203 createXWin(); 5204 this.icon = icon; 5205 } else version(Windows) { 5206 this(name, icon is null ? null : icon.toTrueColorImage(), onClick); 5207 } else version(OSXCocoa) { 5208 throw new NotYetImplementedException(); 5209 } else static assert(0); 5210 } 5211 5212 version(X11) { 5213 /++ 5214 X-specific extension (for now at least) 5215 +/ 5216 this(string name, MemoryImage icon, void delegate(int x, int y, MouseButton button, ModifierState mods) onClickEx) { 5217 this.onClickEx = onClickEx; 5218 createXWin(); 5219 if (icon !is null) this.icon = icon; 5220 } 5221 5222 /// ditto 5223 this(string name, Image icon, void delegate(int x, int y, MouseButton button, ModifierState mods) onClickEx) { 5224 this.onClickEx = onClickEx; 5225 createXWin(); 5226 this.icon = icon; 5227 } 5228 } 5229 5230 private void delegate (MouseButton button) onClick_; 5231 5232 /// 5233 @property final void delegate(MouseButton) onClick() { 5234 if(onClick_ is null) 5235 onClick_ = delegate void(MouseButton) {}; 5236 return onClick_; 5237 } 5238 5239 /// ditto 5240 @property final void onClick(void delegate(MouseButton) handler) { 5241 // I made this a property setter so we can wrap smaller arg 5242 // delegates and just forward all to onClickEx or something. 5243 onClick_ = handler; 5244 } 5245 5246 5247 string name_; 5248 @property void name(string n) { 5249 name_ = n; 5250 } 5251 5252 @property string name() { 5253 return name_; 5254 } 5255 5256 private MemoryImage originalMemoryImage; 5257 5258 /// 5259 @property void icon(MemoryImage i) { 5260 version(X11) { 5261 this.originalMemoryImage = i; 5262 if (!active) return; 5263 if (i !is null) { 5264 this.img = Image.fromMemoryImage(i, useAlpha, false); 5265 if(!useAlpha) 5266 this.clippixmap = transparencyMaskFromMemoryImage(i, nativeHandle); 5267 // writeln("using pixmap ", clippixmap); 5268 updateNetWmIcon(); 5269 redraw(); 5270 } else { 5271 if (this.img !is null) { 5272 this.img = null; 5273 redraw(); 5274 } 5275 } 5276 } else version(Windows) { 5277 this.win32Icon = new WindowsIcon(i); 5278 5279 data.uFlags = NIF_ICON; 5280 data.hIcon = this.win32Icon.hIcon; 5281 5282 Shell_NotifyIcon(NIM_MODIFY, cast(NOTIFYICONDATA*) &data); 5283 } else version(OSXCocoa) { 5284 throw new NotYetImplementedException(); 5285 } else static assert(0); 5286 } 5287 5288 /// ditto 5289 @property void icon (Image i) { 5290 version(X11) { 5291 if (!active) return; 5292 if (i !is img) { 5293 originalMemoryImage = null; 5294 img = i; 5295 redraw(); 5296 } 5297 } else version(Windows) { 5298 this.icon(i is null ? null : i.toTrueColorImage()); 5299 } else version(OSXCocoa) { 5300 throw new NotYetImplementedException(); 5301 } else static assert(0); 5302 } 5303 5304 /++ 5305 Shows a balloon notification. You can only show one balloon at a time, if you call 5306 it twice while one is already up, the first balloon will be replaced. 5307 5308 5309 The user is free to block notifications and they will automatically disappear after 5310 a timeout period. 5311 5312 Params: 5313 title = Title of the notification. Must be 40 chars or less or the OS may truncate it. 5314 message = The message to pop up. Must be 220 chars or less or the OS may truncate it. 5315 icon = the icon to display with the notification. If null, it uses your existing icon. 5316 onclick = delegate called if the user clicks the balloon. (not yet implemented) 5317 timeout = your suggested timeout period. The operating system is free to ignore your suggestion. 5318 +/ 5319 void showBalloon(string title, string message, MemoryImage icon = null, void delegate() onclick = null, int timeout = 2_500) { 5320 bool useCustom = true; 5321 version(libnotify) { 5322 if(onclick is null) // libnotify impl doesn't support callbacks yet because it doesn't do a dbus message loop 5323 try { 5324 if(!active) return; 5325 5326 if(libnotify is null) { 5327 libnotify = new C_DynamicLibrary("libnotify.so"); 5328 libnotify.call!("notify_init", int, const char*)()((ApplicationName ~ "\0").ptr); 5329 } 5330 5331 auto n = libnotify.call!("notify_notification_new", void*, const char*, const char*, const char*)()((title~"\0").ptr, (message~"\0").ptr, null /* icon */); 5332 5333 libnotify.call!("notify_notification_set_timeout", void, void*, int)()(n, timeout); 5334 5335 if(onclick) { 5336 libnotify_action_delegates[libnotify_action_delegates_count] = onclick; 5337 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); 5338 libnotify_action_delegates_count++; 5339 } 5340 5341 // FIXME icon 5342 5343 // set hint image-data 5344 // set default action for onclick 5345 5346 void* error; 5347 libnotify.call!("notify_notification_show", bool, void*, void**)()(n, &error); 5348 5349 useCustom = false; 5350 } catch(Exception e) { 5351 5352 } 5353 } 5354 5355 version(X11) { 5356 if(useCustom) { 5357 if(!active) return; 5358 if(balloon) { 5359 hideBalloon(); 5360 } 5361 // I know there are two specs for this, but one is never 5362 // implemented by any window manager I have ever seen, and 5363 // the other is a bloated mess and too complicated for simpledisplay... 5364 // so doing my own little window instead. 5365 balloon = new SimpleWindow(380, 120, null, OpenGlOptions.no, Resizability.fixedSize, WindowTypes.notification, WindowFlags.dontAutoShow/*, window*/); 5366 5367 int x, y, width, height; 5368 getWindowRect(x, y, width, height); 5369 5370 int bx = x - balloon.width; 5371 int by = y - balloon.height; 5372 if(bx < 0) 5373 bx = x + width + balloon.width; 5374 if(by < 0) 5375 by = y + height; 5376 5377 // just in case, make sure it is actually on scren 5378 if(bx < 0) 5379 bx = 0; 5380 if(by < 0) 5381 by = 0; 5382 5383 balloon.move(bx, by); 5384 auto painter = balloon.draw(); 5385 painter.fillColor = Color(220, 220, 220); 5386 painter.outlineColor = Color.black; 5387 painter.drawRectangle(Point(0, 0), balloon.width, balloon.height); 5388 auto iconWidth = icon is null ? 0 : icon.width; 5389 if(icon) 5390 painter.drawImage(Point(4, 4), Image.fromMemoryImage(icon)); 5391 iconWidth += 6; // margin around the icon 5392 5393 // draw a close button 5394 painter.outlineColor = Color(44, 44, 44); 5395 painter.fillColor = Color(255, 255, 255); 5396 painter.drawRectangle(Point(balloon.width - 15, 3), 13, 13); 5397 painter.pen = Pen(Color.black, 3); 5398 painter.drawLine(Point(balloon.width - 14, 4), Point(balloon.width - 4, 14)); 5399 painter.drawLine(Point(balloon.width - 4, 4), Point(balloon.width - 14, 13)); 5400 painter.pen = Pen(Color.black, 1); 5401 painter.fillColor = Color(220, 220, 220); 5402 5403 // Draw the title and message 5404 painter.drawText(Point(4 + iconWidth, 4), title); 5405 painter.drawLine( 5406 Point(4 + iconWidth, 4 + painter.fontHeight + 1), 5407 Point(balloon.width - 4, 4 + painter.fontHeight + 1), 5408 ); 5409 painter.drawText(Point(4 + iconWidth, 4 + painter.fontHeight + 4), message); 5410 5411 balloon.setEventHandlers( 5412 (MouseEvent ev) { 5413 if(ev.type == MouseEventType.buttonPressed) { 5414 if(ev.x > balloon.width - 16 && ev.y < 16) 5415 hideBalloon(); 5416 else if(onclick) 5417 onclick(); 5418 } 5419 } 5420 ); 5421 balloon.show(); 5422 5423 version(with_timer) 5424 timer = new Timer(timeout, &hideBalloon); 5425 else {} // FIXME 5426 } 5427 } else version(Windows) { 5428 enum NIF_INFO = 0x00000010; 5429 5430 data.uFlags = NIF_INFO; 5431 5432 // FIXME: go back to the last valid unicode code point 5433 if(title.length > 40) 5434 title = title[0 .. 40]; 5435 if(message.length > 220) 5436 message = message[0 .. 220]; 5437 5438 enum NIIF_RESPECT_QUIET_TIME = 0x00000080; 5439 enum NIIF_LARGE_ICON = 0x00000020; 5440 enum NIIF_NOSOUND = 0x00000010; 5441 enum NIIF_USER = 0x00000004; 5442 enum NIIF_ERROR = 0x00000003; 5443 enum NIIF_WARNING = 0x00000002; 5444 enum NIIF_INFO = 0x00000001; 5445 enum NIIF_NONE = 0; 5446 5447 WCharzBuffer t = WCharzBuffer(title); 5448 WCharzBuffer m = WCharzBuffer(message); 5449 5450 t.copyInto(data.szInfoTitle); 5451 m.copyInto(data.szInfo); 5452 data.dwInfoFlags = NIIF_RESPECT_QUIET_TIME; 5453 5454 if(icon !is null) { 5455 auto i = new WindowsIcon(icon); 5456 data.hBalloonIcon = i.hIcon; 5457 data.dwInfoFlags |= NIIF_USER; 5458 } 5459 5460 Shell_NotifyIcon(NIM_MODIFY, cast(NOTIFYICONDATA*) &data); 5461 } else version(OSXCocoa) { 5462 throw new NotYetImplementedException(); 5463 } else static assert(0); 5464 } 5465 5466 /// 5467 //version(Windows) 5468 void show() { 5469 version(X11) { 5470 if(!hidden) 5471 return; 5472 sendTrayMessage(SYSTEM_TRAY_REQUEST_DOCK, nativeHandle, 0, 0); 5473 hidden = false; 5474 } else version(Windows) { 5475 data.uFlags = NIF_STATE; 5476 data.dwState = 0; // NIS_HIDDEN; // windows vista 5477 data.dwStateMask = NIS_HIDDEN; // windows vista 5478 Shell_NotifyIcon(NIM_MODIFY, cast(NOTIFYICONDATA*) &data); 5479 } else version(OSXCocoa) { 5480 throw new NotYetImplementedException(); 5481 } else static assert(0); 5482 } 5483 5484 version(X11) 5485 bool hidden = false; 5486 5487 /// 5488 //version(Windows) 5489 void hide() { 5490 version(X11) { 5491 if(hidden) 5492 return; 5493 hidden = true; 5494 XUnmapWindow(XDisplayConnection.get, nativeHandle); 5495 } else version(Windows) { 5496 data.uFlags = NIF_STATE; 5497 data.dwState = NIS_HIDDEN; // windows vista 5498 data.dwStateMask = NIS_HIDDEN; // windows vista 5499 Shell_NotifyIcon(NIM_MODIFY, cast(NOTIFYICONDATA*) &data); 5500 } else version(OSXCocoa) { 5501 throw new NotYetImplementedException(); 5502 } else static assert(0); 5503 } 5504 5505 /// 5506 void close () { 5507 version(X11) { 5508 if (active) { 5509 active = false; // event handler will set this too, but meh 5510 XUnmapWindow(XDisplayConnection.get, nativeHandle); // 'cause why not; let's be polite 5511 XDestroyWindow(XDisplayConnection.get, nativeHandle); 5512 flushGui(); 5513 } 5514 } else version(Windows) { 5515 Shell_NotifyIcon(NIM_DELETE, cast(NOTIFYICONDATA*) &data); 5516 } else version(OSXCocoa) { 5517 throw new NotYetImplementedException(); 5518 } else static assert(0); 5519 } 5520 5521 ~this() { 5522 if(!thisIsGuiThread) return; // FIXME: leaks if multithreaded gc 5523 version(X11) 5524 if(clippixmap != None) 5525 XFreePixmap(XDisplayConnection.get, clippixmap); 5526 close(); 5527 } 5528 } 5529 5530 version(X11) 5531 /// Call `XFreePixmap` on the return value. 5532 Pixmap transparencyMaskFromMemoryImage(MemoryImage i, Window window) { 5533 char[] data = new char[](i.width * i.height / 8 + 2); 5534 data[] = 0; 5535 5536 int bitOffset = 0; 5537 foreach(c; i.getAsTrueColorImage().imageData.colors) { // FIXME inefficient unnecessary conversion in palette cases 5538 ubyte v = c.a > 128 ? 1 : 0; 5539 data[bitOffset / 8] |= v << (bitOffset%8); 5540 bitOffset++; 5541 } 5542 auto handle = XCreateBitmapFromData(XDisplayConnection.get, cast(Drawable) window, data.ptr, i.width, i.height); 5543 return handle; 5544 } 5545 5546 5547 // basic functions to make timers 5548 /** 5549 A timer that will trigger your function on a given interval. 5550 5551 5552 You create a timer with an interval and a callback. It will continue 5553 to fire on the interval until it is destroyed. 5554 5555 There are currently no one-off timers (instead, just create one and 5556 destroy it when it is triggered) nor are there pause/resume functions - 5557 the timer must again be destroyed and recreated if you want to pause it. 5558 5559 --- 5560 auto timer = new Timer(50, { it happened!; }); 5561 timer.destroy(); 5562 --- 5563 5564 Timers can only be expected to fire when the event loop is running and only 5565 once per iteration through the event loop. 5566 5567 History: 5568 Prior to December 9, 2020, a timer pulse set too high with a handler too 5569 slow could lock up the event loop. It now guarantees other things will 5570 get a chance to run between timer calls, even if that means not keeping up 5571 with the requested interval. 5572 */ 5573 version(with_timer) { 5574 class Timer { 5575 // FIXME: needs pause and unpause 5576 // FIXME: I might add overloads for ones that take a count of 5577 // how many elapsed since last time (on Windows, it will divide 5578 // the ticks thing given, on Linux it is just available) and 5579 // maybe one that takes an instance of the Timer itself too 5580 /// Create a timer with a callback when it triggers. 5581 this(int intervalInMilliseconds, void delegate() onPulse) { 5582 assert(onPulse !is null); 5583 5584 this.intervalInMilliseconds = intervalInMilliseconds; 5585 this.onPulse = onPulse; 5586 5587 version(Windows) { 5588 /* 5589 handle = SetTimer(null, handle, intervalInMilliseconds, &timerCallback); 5590 if(handle == 0) 5591 throw new WindowsApiException("SetTimer", GetLastError()); 5592 */ 5593 5594 // thanks to Archival 998 for the WaitableTimer blocks 5595 handle = CreateWaitableTimer(null, false, null); 5596 long initialTime = -intervalInMilliseconds; 5597 if(handle is null || !SetWaitableTimer(handle, cast(LARGE_INTEGER*)&initialTime, intervalInMilliseconds, &timerCallback, handle, false)) 5598 throw new WindowsApiException("SetWaitableTimer", GetLastError()); 5599 5600 mapping[handle] = this; 5601 5602 } else version(linux) { 5603 static import ep = core.sys.linux.epoll; 5604 5605 import core.sys.linux.timerfd; 5606 5607 fd = timerfd_create(CLOCK_MONOTONIC, 0); 5608 if(fd == -1) 5609 throw new Exception("timer create failed"); 5610 5611 mapping[fd] = this; 5612 5613 itimerspec value = makeItimerspec(intervalInMilliseconds); 5614 5615 if(timerfd_settime(fd, 0, &value, null) == -1) 5616 throw new Exception("couldn't make pulse timer"); 5617 5618 version(with_eventloop) { 5619 import arsd.eventloop; 5620 addFileEventListeners(fd, &trigger, null, null); 5621 } else { 5622 prepareEventLoop(); 5623 5624 ep.epoll_event ev = void; 5625 ev.events = ep.EPOLLIN; 5626 ev.data.fd = fd; 5627 ep.epoll_ctl(epollFd, ep.EPOLL_CTL_ADD, fd, &ev); 5628 } 5629 } else featureNotImplemented(); 5630 } 5631 5632 private int intervalInMilliseconds; 5633 5634 // just cuz I sometimes call it this. 5635 alias dispose = destroy; 5636 5637 /// Stop and destroy the timer object. 5638 void destroy() { 5639 version(Windows) { 5640 staticDestroy(handle); 5641 handle = null; 5642 } else version(linux) { 5643 staticDestroy(fd); 5644 fd = -1; 5645 } else featureNotImplemented(); 5646 } 5647 5648 version(Windows) 5649 static void staticDestroy(HANDLE handle) { 5650 if(handle) { 5651 // KillTimer(null, handle); 5652 CancelWaitableTimer(cast(void*)handle); 5653 mapping.remove(handle); 5654 CloseHandle(handle); 5655 } 5656 } 5657 else version(linux) 5658 static void staticDestroy(int fd) { 5659 if(fd != -1) { 5660 import unix = core.sys.posix.unistd; 5661 static import ep = core.sys.linux.epoll; 5662 5663 version(with_eventloop) { 5664 import arsd.eventloop; 5665 removeFileEventListeners(fd); 5666 } else { 5667 ep.epoll_event ev = void; 5668 ev.events = ep.EPOLLIN; 5669 ev.data.fd = fd; 5670 5671 ep.epoll_ctl(epollFd, ep.EPOLL_CTL_DEL, fd, &ev); 5672 } 5673 unix.close(fd); 5674 mapping.remove(fd); 5675 } 5676 } 5677 5678 ~this() { 5679 version(Windows) { if(handle) 5680 cleanupQueue.queue!staticDestroy(handle); 5681 } else version(linux) { if(fd != -1) 5682 cleanupQueue.queue!staticDestroy(fd); 5683 } 5684 } 5685 5686 void changeTime(int intervalInMilliseconds) 5687 { 5688 this.intervalInMilliseconds = intervalInMilliseconds; 5689 version(Windows) 5690 { 5691 if(handle) 5692 { 5693 //handle = SetTimer(null, handle, intervalInMilliseconds, &timerCallback); 5694 long initialTime = -intervalInMilliseconds; 5695 if(handle is null || !SetWaitableTimer(handle, cast(LARGE_INTEGER*)&initialTime, intervalInMilliseconds, &timerCallback, handle, false)) 5696 throw new WindowsApiException("couldn't change pulse timer", GetLastError()); 5697 } 5698 } else version(linux) { 5699 import core.sys.linux.timerfd; 5700 5701 itimerspec value = makeItimerspec(intervalInMilliseconds); 5702 if(timerfd_settime(fd, 0, &value, null) == -1) { 5703 throw new Exception("couldn't change pulse timer"); 5704 } 5705 } else { 5706 assert(false, "Timer.changeTime(int) is not implemented for this platform"); 5707 } 5708 } 5709 5710 5711 private: 5712 5713 void delegate() onPulse; 5714 5715 int lastEventLoopRoundTriggered; 5716 5717 version(linux) { 5718 static auto makeItimerspec(int intervalInMilliseconds) { 5719 import core.sys.linux.timerfd; 5720 5721 itimerspec value; 5722 value.it_value.tv_sec = cast(int) (intervalInMilliseconds / 1000); 5723 value.it_value.tv_nsec = (intervalInMilliseconds % 1000) * 1000_000; 5724 5725 value.it_interval.tv_sec = cast(int) (intervalInMilliseconds / 1000); 5726 value.it_interval.tv_nsec = (intervalInMilliseconds % 1000) * 1000_000; 5727 5728 return value; 5729 } 5730 } 5731 5732 void trigger() { 5733 version(linux) { 5734 import unix = core.sys.posix.unistd; 5735 long val; 5736 unix.read(fd, &val, val.sizeof); // gotta clear the pipe 5737 } else version(Windows) { 5738 if(this.lastEventLoopRoundTriggered == eventLoopRound) 5739 return; // never try to actually run faster than the event loop 5740 lastEventLoopRoundTriggered = eventLoopRound; 5741 } else featureNotImplemented(); 5742 5743 onPulse(); 5744 } 5745 5746 version(Windows) 5747 void rearm() { 5748 5749 } 5750 5751 version(Windows) 5752 extern(Windows) 5753 //static void timerCallback(HWND, UINT, UINT_PTR timer, DWORD dwTime) nothrow { 5754 static void timerCallback(HANDLE timer, DWORD lowTime, DWORD hiTime) nothrow { 5755 if(Timer* t = timer in mapping) { 5756 try 5757 (*t).trigger(); 5758 catch(Exception e) { sdpy_abort(e); assert(0); } 5759 } 5760 } 5761 5762 version(Windows) { 5763 //UINT_PTR handle; 5764 //static Timer[UINT_PTR] mapping; 5765 HANDLE handle; 5766 __gshared Timer[HANDLE] mapping; 5767 } else version(linux) { 5768 int fd = -1; 5769 __gshared Timer[int] mapping; 5770 } else version(OSXCocoa) { 5771 } else static assert(0, "timer not supported"); 5772 } 5773 } 5774 5775 version(Windows) 5776 private int eventLoopRound; 5777 5778 version(Windows) 5779 /// 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 5780 class WindowsHandleReader { 5781 /// 5782 this(void delegate() onReady, HANDLE handle) { 5783 this.onReady = onReady; 5784 this.handle = handle; 5785 5786 mapping[handle] = this; 5787 5788 enable(); 5789 } 5790 5791 /// 5792 void enable() { 5793 auto el = EventLoop.get().impl; 5794 el.handles ~= handle; 5795 } 5796 5797 /// 5798 void disable() { 5799 auto el = EventLoop.get().impl; 5800 for(int i = 0; i < el.handles.length; i++) { 5801 if(el.handles[i] is handle) { 5802 el.handles[i] = el.handles[$-1]; 5803 el.handles = el.handles[0 .. $-1]; 5804 return; 5805 } 5806 } 5807 } 5808 5809 void dispose() { 5810 disable(); 5811 if(handle) 5812 mapping.remove(handle); 5813 handle = null; 5814 } 5815 5816 void ready() { 5817 if(onReady) 5818 onReady(); 5819 } 5820 5821 HANDLE handle; 5822 void delegate() onReady; 5823 5824 __gshared WindowsHandleReader[HANDLE] mapping; 5825 } 5826 5827 version(Posix) 5828 /// Lets you add files to the event loop for reading. Use at your own risk. 5829 class PosixFdReader { 5830 /// 5831 this(void delegate() onReady, int fd, bool captureReads = true, bool captureWrites = false) { 5832 this((int, bool, bool) { onReady(); }, fd, captureReads, captureWrites); 5833 } 5834 5835 /// 5836 this(void delegate(int) onReady, int fd, bool captureReads = true, bool captureWrites = false) { 5837 this((int fd, bool, bool) { onReady(fd); }, fd, captureReads, captureWrites); 5838 } 5839 5840 /// 5841 this(void delegate(int fd, bool read, bool write) onReady, int fd, bool captureReads = true, bool captureWrites = false) { 5842 this.onReady = onReady; 5843 this.fd = fd; 5844 this.captureWrites = captureWrites; 5845 this.captureReads = captureReads; 5846 5847 mapping[fd] = this; 5848 5849 version(with_eventloop) { 5850 import arsd.eventloop; 5851 addFileEventListeners(fd, &readyel); 5852 } else { 5853 enable(); 5854 } 5855 } 5856 5857 bool captureReads; 5858 bool captureWrites; 5859 5860 version(with_eventloop) {} else 5861 /// 5862 void enable() { 5863 prepareEventLoop(); 5864 5865 enabled = true; 5866 5867 version(linux) { 5868 static import ep = core.sys.linux.epoll; 5869 ep.epoll_event ev = void; 5870 ev.events = (captureReads ? ep.EPOLLIN : 0) | (captureWrites ? ep.EPOLLOUT : 0); 5871 // writeln("enable ", fd, " ", captureReads, " ", captureWrites); 5872 ev.data.fd = fd; 5873 ep.epoll_ctl(epollFd, ep.EPOLL_CTL_ADD, fd, &ev); 5874 } else { 5875 5876 } 5877 } 5878 5879 version(with_eventloop) {} else 5880 /// 5881 void disable() { 5882 prepareEventLoop(); 5883 5884 enabled = false; 5885 5886 version(linux) { 5887 static import ep = core.sys.linux.epoll; 5888 ep.epoll_event ev = void; 5889 ev.events = (captureReads ? ep.EPOLLIN : 0) | (captureWrites ? ep.EPOLLOUT : 0); 5890 // writeln("disable ", fd, " ", captureReads, " ", captureWrites); 5891 ev.data.fd = fd; 5892 ep.epoll_ctl(epollFd, ep.EPOLL_CTL_DEL, fd, &ev); 5893 } 5894 } 5895 5896 version(with_eventloop) {} else 5897 /// 5898 void dispose() { 5899 if(enabled) 5900 disable(); 5901 if(fd != -1) 5902 mapping.remove(fd); 5903 fd = -1; 5904 } 5905 5906 void delegate(int, bool, bool) onReady; 5907 5908 version(with_eventloop) 5909 void readyel() { 5910 onReady(fd, true, true); 5911 } 5912 5913 void ready(uint flags) { 5914 version(linux) { 5915 static import ep = core.sys.linux.epoll; 5916 onReady(fd, (flags & ep.EPOLLIN) ? true : false, (flags & ep.EPOLLOUT) ? true : false); 5917 } else { 5918 import core.sys.posix.poll; 5919 onReady(fd, (flags & POLLIN) ? true : false, (flags & POLLOUT) ? true : false); 5920 } 5921 } 5922 5923 void hup(uint flags) { 5924 if(onHup) 5925 onHup(); 5926 } 5927 5928 void delegate() onHup; 5929 5930 int fd = -1; 5931 private bool enabled; 5932 __gshared PosixFdReader[int] mapping; 5933 } 5934 5935 // basic functions to access the clipboard 5936 /+ 5937 5938 5939 http://msdn.microsoft.com/en-us/library/windows/desktop/ff729168%28v=vs.85%29.aspx 5940 http://msdn.microsoft.com/en-us/library/windows/desktop/ms649039%28v=vs.85%29.aspx 5941 http://msdn.microsoft.com/en-us/library/windows/desktop/ms649035%28v=vs.85%29.aspx 5942 http://msdn.microsoft.com/en-us/library/windows/desktop/ms649051%28v=vs.85%29.aspx 5943 http://msdn.microsoft.com/en-us/library/windows/desktop/ms649037%28v=vs.85%29.aspx 5944 http://msdn.microsoft.com/en-us/library/windows/desktop/ms649035%28v=vs.85%29.aspx 5945 http://msdn.microsoft.com/en-us/library/windows/desktop/ms649016%28v=vs.85%29.aspx 5946 5947 +/ 5948 5949 /++ 5950 this does a delegate because it is actually an async call on X... 5951 the receiver may never be called if the clipboard is empty or unavailable 5952 gets plain text from the clipboard. 5953 +/ 5954 void getClipboardText(SimpleWindow clipboardOwner, void delegate(in char[]) receiver) { 5955 version(Windows) { 5956 HWND hwndOwner = clipboardOwner ? clipboardOwner.impl.hwnd : null; 5957 if(OpenClipboard(hwndOwner) == 0) 5958 throw new WindowsApiException("OpenClipboard", GetLastError()); 5959 scope(exit) 5960 CloseClipboard(); 5961 // see: https://docs.microsoft.com/en-us/windows/win32/api/winuser/nf-winuser-getpriorityclipboardformat 5962 if(auto dataHandle = GetClipboardData(CF_UNICODETEXT)) { 5963 5964 if(auto data = cast(wchar*) GlobalLock(dataHandle)) { 5965 scope(exit) 5966 GlobalUnlock(dataHandle); 5967 5968 // FIXME: CR/LF conversions 5969 // FIXME: I might not have to copy it now that the receiver is in char[] instead of string 5970 int len = 0; 5971 auto d = data; 5972 while(*d) { 5973 d++; 5974 len++; 5975 } 5976 string s; 5977 s.reserve(len); 5978 foreach(dchar ch; data[0 .. len]) { 5979 s ~= ch; 5980 } 5981 receiver(s); 5982 } 5983 } 5984 } else version(X11) { 5985 getX11Selection!"CLIPBOARD"(clipboardOwner, receiver); 5986 } else version(OSXCocoa) { 5987 throw new NotYetImplementedException(); 5988 } else static assert(0); 5989 } 5990 5991 // FIXME: a clipboard listener might be cool btw 5992 5993 /++ 5994 this does a delegate because it is actually an async call on X... 5995 the receiver may never be called if the clipboard is empty or unavailable 5996 gets image from the clipboard. 5997 5998 templated because it introduces an optional dependency on arsd.bmp 5999 +/ 6000 void getClipboardImage()(SimpleWindow clipboardOwner, void delegate(MemoryImage) receiver) { 6001 version(Windows) { 6002 HWND hwndOwner = clipboardOwner ? clipboardOwner.impl.hwnd : null; 6003 if(OpenClipboard(hwndOwner) == 0) 6004 throw new WindowsApiException("OpenClipboard", GetLastError()); 6005 scope(exit) 6006 CloseClipboard(); 6007 if(auto dataHandle = GetClipboardData(CF_DIBV5)) { 6008 if(auto data = cast(ubyte*) GlobalLock(dataHandle)) { 6009 scope(exit) 6010 GlobalUnlock(dataHandle); 6011 6012 auto len = GlobalSize(dataHandle); 6013 6014 import arsd.bmp; 6015 auto img = readBmp(data[0 .. len], false); 6016 receiver(img); 6017 } 6018 } 6019 } else version(X11) { 6020 getX11Selection!"CLIPBOARD"(clipboardOwner, receiver); 6021 } else version(OSXCocoa) { 6022 throw new NotYetImplementedException(); 6023 } else static assert(0); 6024 } 6025 6026 /// Copies some text to the clipboard. 6027 void setClipboardText(SimpleWindow clipboardOwner, string text) { 6028 assert(clipboardOwner !is null); 6029 version(Windows) { 6030 if(OpenClipboard(clipboardOwner.impl.hwnd) == 0) 6031 throw new WindowsApiException("OpenClipboard", GetLastError()); 6032 scope(exit) 6033 CloseClipboard(); 6034 EmptyClipboard(); 6035 auto sz = sizeOfConvertedWstring(text, WindowsStringConversionFlags.convertNewLines | WindowsStringConversionFlags.zeroTerminate); 6036 auto handle = GlobalAlloc(GMEM_MOVEABLE, sz * 2); // zero terminated wchars 6037 if(handle is null) throw new WindowsApiException("GlobalAlloc", GetLastError()); 6038 if(auto data = cast(wchar*) GlobalLock(handle)) { 6039 auto slice = data[0 .. sz]; 6040 scope(failure) 6041 GlobalUnlock(handle); 6042 6043 auto str = makeWindowsString(text, slice, WindowsStringConversionFlags.convertNewLines | WindowsStringConversionFlags.zeroTerminate); 6044 6045 GlobalUnlock(handle); 6046 SetClipboardData(CF_UNICODETEXT, handle); 6047 } 6048 } else version(X11) { 6049 setX11Selection!"CLIPBOARD"(clipboardOwner, text); 6050 } else version(OSXCocoa) { 6051 throw new NotYetImplementedException(); 6052 } else static assert(0); 6053 } 6054 6055 void setClipboardImage()(SimpleWindow clipboardOwner, MemoryImage img) { 6056 assert(clipboardOwner !is null); 6057 version(Windows) { 6058 if(OpenClipboard(clipboardOwner.impl.hwnd) == 0) 6059 throw new WindowsApiException("OpenClipboard", GetLastError()); 6060 scope(exit) 6061 CloseClipboard(); 6062 EmptyClipboard(); 6063 6064 6065 import arsd.bmp; 6066 ubyte[] mdata; 6067 mdata.reserve(img.width * img.height); 6068 void sink(ubyte b) { 6069 mdata ~= b; 6070 } 6071 writeBmpIndirect(img, &sink, false); 6072 6073 auto handle = GlobalAlloc(GMEM_MOVEABLE, mdata.length); 6074 if(handle is null) throw new WindowsApiException("GlobalAlloc", GetLastError()); 6075 if(auto data = cast(ubyte*) GlobalLock(handle)) { 6076 auto slice = data[0 .. mdata.length]; 6077 scope(failure) 6078 GlobalUnlock(handle); 6079 6080 slice[] = mdata[]; 6081 6082 GlobalUnlock(handle); 6083 SetClipboardData(CF_DIB, handle); 6084 } 6085 } else version(X11) { 6086 static class X11SetSelectionHandler_Image : X11SetSelectionHandler { 6087 mixin X11SetSelectionHandler_Basics; 6088 private const(ubyte)[] mdata; 6089 private const(ubyte)[] mdata_original; 6090 this(MemoryImage img) { 6091 import arsd.bmp; 6092 6093 mdata.reserve(img.width * img.height); 6094 void sink(ubyte b) { 6095 mdata ~= b; 6096 } 6097 writeBmpIndirect(img, &sink, true); 6098 6099 mdata_original = mdata; 6100 } 6101 6102 Atom[] availableFormats() { 6103 auto display = XDisplayConnection.get; 6104 return [ 6105 GetAtom!"image/bmp"(display), 6106 GetAtom!"TARGETS"(display) 6107 ]; 6108 } 6109 6110 ubyte[] getData(Atom format, return scope ubyte[] data) { 6111 if(mdata.length < data.length) { 6112 data[0 .. mdata.length] = mdata[]; 6113 auto ret = data[0 .. mdata.length]; 6114 mdata = mdata[$..$]; 6115 return ret; 6116 } else { 6117 data[] = mdata[0 .. data.length]; 6118 mdata = mdata[data.length .. $]; 6119 return data[]; 6120 } 6121 } 6122 6123 void done() { 6124 mdata = mdata_original; 6125 } 6126 } 6127 6128 setX11Selection!"CLIPBOARD"(clipboardOwner, new X11SetSelectionHandler_Image(img)); 6129 } else version(OSXCocoa) { 6130 throw new NotYetImplementedException(); 6131 } else static assert(0); 6132 } 6133 6134 6135 version(X11) { 6136 // and the PRIMARY on X, be sure to put these in static if(UsingSimpledisplayX11) 6137 6138 private __gshared Atom*[] interredAtoms; // for discardAndRecreate 6139 6140 // FIXME: do a GetAtomUpfront too that just queues all at CT and combines it all. 6141 /// Platform-specific for X11. 6142 /// History: On February 21, 2021, I changed the default value of `create` to be true. 6143 @property Atom GetAtom(string name, bool create = true)(Display* display) { 6144 __gshared static Atom a; 6145 if(!a) { 6146 a = XInternAtom(display, name, !create); 6147 // FIXME: might need to synchronize this and attach it to the actual object 6148 interredAtoms ~= &a; 6149 } 6150 if(a == None) 6151 throw new Exception("XInternAtom " ~ name ~ " " ~ (create ? "true":"false")); 6152 return a; 6153 } 6154 6155 /// Platform-specific for X11 - gets atom names as a string. 6156 string getAtomName(Atom atom, Display* display) { 6157 auto got = XGetAtomName(display, atom); 6158 scope(exit) XFree(got); 6159 import core.stdc.string; 6160 string s = got[0 .. strlen(got)].idup; 6161 return s; 6162 } 6163 6164 /// Asserts ownership of PRIMARY and copies the text into a buffer that clients can request later. 6165 void setPrimarySelection(SimpleWindow window, string text) { 6166 setX11Selection!"PRIMARY"(window, text); 6167 } 6168 6169 /// Asserts ownership of SECONDARY and copies the text into a buffer that clients can request later. 6170 void setSecondarySelection(SimpleWindow window, string text) { 6171 setX11Selection!"SECONDARY"(window, text); 6172 } 6173 6174 interface X11SetSelectionHandler { 6175 // should include TARGETS right now 6176 Atom[] availableFormats(); 6177 // Return the slice of data you filled, empty slice if done. 6178 // this is to support the incremental thing 6179 ubyte[] getData(Atom format, return scope ubyte[] data); 6180 6181 void done(); 6182 6183 void handleRequest(XEvent); 6184 6185 bool matchesIncr(Window, Atom); 6186 void sendMoreIncr(XPropertyEvent*); 6187 } 6188 6189 mixin template X11SetSelectionHandler_Basics() { 6190 Window incrWindow; 6191 Atom incrAtom; 6192 Atom selectionAtom; 6193 Atom formatAtom; 6194 ubyte[] toSend; 6195 bool matchesIncr(Window w, Atom a) { 6196 return incrAtom && incrAtom == a && w == incrWindow; 6197 } 6198 void sendMoreIncr(XPropertyEvent* event) { 6199 auto display = XDisplayConnection.get; 6200 6201 XChangeProperty (display, 6202 incrWindow, 6203 incrAtom, 6204 formatAtom, 6205 8 /* bits */, PropModeReplace, 6206 toSend.ptr, cast(int) toSend.length); 6207 6208 if(toSend.length != 0) { 6209 toSend = this.getData(formatAtom, toSend[]); 6210 } else { 6211 this.done(); 6212 incrWindow = None; 6213 incrAtom = None; 6214 selectionAtom = None; 6215 formatAtom = None; 6216 toSend = null; 6217 } 6218 } 6219 void handleRequest(XEvent ev) { 6220 6221 auto display = XDisplayConnection.get; 6222 6223 XSelectionRequestEvent* event = &ev.xselectionrequest; 6224 XSelectionEvent selectionEvent; 6225 selectionEvent.type = EventType.SelectionNotify; 6226 selectionEvent.display = event.display; 6227 selectionEvent.requestor = event.requestor; 6228 selectionEvent.selection = event.selection; 6229 selectionEvent.time = event.time; 6230 selectionEvent.target = event.target; 6231 6232 bool supportedType() { 6233 foreach(t; this.availableFormats()) 6234 if(t == event.target) 6235 return true; 6236 return false; 6237 } 6238 6239 if(event.property == None) { 6240 selectionEvent.property = event.target; 6241 6242 XSendEvent(display, selectionEvent.requestor, false, 0, cast(XEvent*) &selectionEvent); 6243 XFlush(display); 6244 } if(event.target == GetAtom!"TARGETS"(display)) { 6245 /* respond with the supported types */ 6246 auto tlist = this.availableFormats(); 6247 XChangeProperty(display, event.requestor, event.property, XA_ATOM, 32, PropModeReplace, cast(void*)tlist.ptr, cast(int) tlist.length); 6248 selectionEvent.property = event.property; 6249 6250 XSendEvent(display, selectionEvent.requestor, false, 0, cast(XEvent*) &selectionEvent); 6251 XFlush(display); 6252 } else if(supportedType()) { 6253 auto buffer = new ubyte[](1024 * 64); 6254 auto toSend = this.getData(event.target, buffer[]); 6255 6256 if(toSend.length < 32 * 1024) { 6257 // small enough to send directly... 6258 selectionEvent.property = event.property; 6259 XChangeProperty (display, 6260 selectionEvent.requestor, 6261 selectionEvent.property, 6262 event.target, 6263 8 /* bits */, 0 /* PropModeReplace */, 6264 toSend.ptr, cast(int) toSend.length); 6265 6266 XSendEvent(display, selectionEvent.requestor, false, 0, cast(XEvent*) &selectionEvent); 6267 XFlush(display); 6268 } else { 6269 // large, let's send incrementally 6270 arch_ulong l = toSend.length; 6271 6272 // if I wanted other events from this window don't want to clear that out.... 6273 XWindowAttributes xwa; 6274 XGetWindowAttributes(display, selectionEvent.requestor, &xwa); 6275 6276 XSelectInput(display, selectionEvent.requestor, cast(EventMask) (xwa.your_event_mask | EventMask.PropertyChangeMask)); 6277 6278 incrWindow = event.requestor; 6279 incrAtom = event.property; 6280 formatAtom = event.target; 6281 selectionAtom = event.selection; 6282 this.toSend = toSend; 6283 6284 selectionEvent.property = event.property; 6285 XChangeProperty (display, 6286 selectionEvent.requestor, 6287 selectionEvent.property, 6288 GetAtom!"INCR"(display), 6289 32 /* bits */, PropModeReplace, 6290 &l, 1); 6291 6292 XSendEvent(display, selectionEvent.requestor, false, 0, cast(XEvent*) &selectionEvent); 6293 XFlush(display); 6294 } 6295 //if(after) 6296 //after(); 6297 } else { 6298 debug(sdpy_clip) { 6299 writeln("Unsupported data ", getAtomName(event.target, display)); 6300 } 6301 selectionEvent.property = None; // I don't know how to handle this type... 6302 XSendEvent(display, selectionEvent.requestor, false, EventMask.NoEventMask, cast(XEvent*) &selectionEvent); 6303 XFlush(display); 6304 } 6305 } 6306 } 6307 6308 class X11SetSelectionHandler_Text : X11SetSelectionHandler { 6309 mixin X11SetSelectionHandler_Basics; 6310 private const(ubyte)[] text; 6311 private const(ubyte)[] text_original; 6312 this(string text) { 6313 this.text = cast(const ubyte[]) text; 6314 this.text_original = this.text; 6315 } 6316 Atom[] availableFormats() { 6317 auto display = XDisplayConnection.get; 6318 return [ 6319 GetAtom!"UTF8_STRING"(display), 6320 GetAtom!"text/plain"(display), 6321 XA_STRING, 6322 GetAtom!"TARGETS"(display) 6323 ]; 6324 } 6325 6326 ubyte[] getData(Atom format, return scope ubyte[] data) { 6327 if(text.length < data.length) { 6328 data[0 .. text.length] = text[]; 6329 return data[0 .. text.length]; 6330 } else { 6331 data[] = text[0 .. data.length]; 6332 text = text[data.length .. $]; 6333 return data[]; 6334 } 6335 } 6336 6337 void done() { 6338 text = text_original; 6339 } 6340 } 6341 6342 /// 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?!) 6343 void setX11Selection(string atomName)(SimpleWindow window, string text, void delegate() after = null) { 6344 setX11Selection!atomName(window, new X11SetSelectionHandler_Text(text), after); 6345 } 6346 6347 void setX11Selection(string atomName)(SimpleWindow window, X11SetSelectionHandler data, void delegate() after = null) { 6348 assert(window !is null); 6349 6350 auto display = XDisplayConnection.get(); 6351 static if (atomName == "PRIMARY") Atom a = XA_PRIMARY; 6352 else static if (atomName == "SECONDARY") Atom a = XA_SECONDARY; 6353 else Atom a = GetAtom!atomName(display); 6354 6355 XSetSelectionOwner(display, a, window.impl.window, 0 /* CurrentTime */); 6356 6357 window.impl.setSelectionHandlers[a] = data; 6358 } 6359 6360 /// 6361 void getPrimarySelection(SimpleWindow window, void delegate(in char[]) handler) { 6362 getX11Selection!"PRIMARY"(window, handler); 6363 } 6364 6365 // added July 28, 2020 6366 // undocumented as experimental tho 6367 interface X11GetSelectionHandler { 6368 void handleData(Atom target, in ubyte[] data); 6369 Atom findBestFormat(Atom[] answer); 6370 6371 void prepareIncremental(Window, Atom); 6372 bool matchesIncr(Window, Atom); 6373 void handleIncrData(Atom, in ubyte[] data); 6374 } 6375 6376 mixin template X11GetSelectionHandler_Basics() { 6377 Window incrWindow; 6378 Atom incrAtom; 6379 6380 void prepareIncremental(Window w, Atom a) { 6381 incrWindow = w; 6382 incrAtom = a; 6383 } 6384 bool matchesIncr(Window w, Atom a) { 6385 return incrWindow == w && incrAtom == a; 6386 } 6387 6388 Atom incrFormatAtom; 6389 ubyte[] incrData; 6390 void handleIncrData(Atom format, in ubyte[] data) { 6391 incrFormatAtom = format; 6392 6393 if(data.length) 6394 incrData ~= data; 6395 else 6396 handleData(incrFormatAtom, incrData); 6397 6398 } 6399 } 6400 6401 /// 6402 void getX11Selection(string atomName)(SimpleWindow window, void delegate(in char[]) handler, Time timestamp = 0 /* CurrentTime */) { 6403 assert(window !is null); 6404 6405 auto display = XDisplayConnection.get(); 6406 auto atom = GetAtom!atomName(display); 6407 6408 static class X11GetSelectionHandler_Text : X11GetSelectionHandler { 6409 this(void delegate(in char[]) handler) { 6410 this.handler = handler; 6411 } 6412 6413 mixin X11GetSelectionHandler_Basics; 6414 6415 void delegate(in char[]) handler; 6416 6417 void handleData(Atom target, in ubyte[] data) { 6418 if(target == GetAtom!"UTF8_STRING"(XDisplayConnection.get) || target == XA_STRING || target == GetAtom!"text/plain"(XDisplayConnection.get)) 6419 handler(cast(const char[]) data); 6420 } 6421 6422 Atom findBestFormat(Atom[] answer) { 6423 Atom best = None; 6424 foreach(option; answer) { 6425 if(option == GetAtom!"UTF8_STRING"(XDisplayConnection.get)) { 6426 best = option; 6427 break; 6428 } else if(option == XA_STRING) { 6429 best = option; 6430 } else if(option == GetAtom!"text/plain"(XDisplayConnection.get)) { 6431 best = option; 6432 } 6433 } 6434 return best; 6435 } 6436 } 6437 6438 window.impl.getSelectionHandlers[atom] = new X11GetSelectionHandler_Text(handler); 6439 6440 auto target = GetAtom!"TARGETS"(display); 6441 6442 // SDD_DATA is "simpledisplay.d data" 6443 XConvertSelection(display, atom, target, GetAtom!("SDD_DATA", true)(display), window.impl.window, timestamp); 6444 } 6445 6446 /// Gets the image on the clipboard, if there is one. Added July 2020. 6447 void getX11Selection(string atomName)(SimpleWindow window, void delegate(MemoryImage) handler) { 6448 assert(window !is null); 6449 6450 auto display = XDisplayConnection.get(); 6451 auto atom = GetAtom!atomName(display); 6452 6453 static class X11GetSelectionHandler_Image : X11GetSelectionHandler { 6454 this(void delegate(MemoryImage) handler) { 6455 this.handler = handler; 6456 } 6457 6458 mixin X11GetSelectionHandler_Basics; 6459 6460 void delegate(MemoryImage) handler; 6461 6462 void handleData(Atom target, in ubyte[] data) { 6463 if(target == GetAtom!"image/bmp"(XDisplayConnection.get)) { 6464 import arsd.bmp; 6465 handler(readBmp(data)); 6466 } 6467 } 6468 6469 Atom findBestFormat(Atom[] answer) { 6470 Atom best = None; 6471 foreach(option; answer) { 6472 if(option == GetAtom!"image/bmp"(XDisplayConnection.get)) { 6473 best = option; 6474 } 6475 } 6476 return best; 6477 } 6478 6479 } 6480 6481 6482 window.impl.getSelectionHandlers[atom] = new X11GetSelectionHandler_Image(handler); 6483 6484 auto target = GetAtom!"TARGETS"(display); 6485 6486 // SDD_DATA is "simpledisplay.d data" 6487 XConvertSelection(display, atom, target, GetAtom!("SDD_DATA", true)(display), window.impl.window, 0 /*CurrentTime*/); 6488 } 6489 6490 6491 /// 6492 void[] getX11PropertyData(Window window, Atom property, Atom type = AnyPropertyType) { 6493 Atom actualType; 6494 int actualFormat; 6495 arch_ulong actualItems; 6496 arch_ulong bytesRemaining; 6497 void* data; 6498 6499 auto display = XDisplayConnection.get(); 6500 if(XGetWindowProperty(display, window, property, 0, 0x7fffffff, false, type, &actualType, &actualFormat, &actualItems, &bytesRemaining, &data) == Success) { 6501 if(actualFormat == 0) 6502 return null; 6503 else { 6504 int byteLength; 6505 if(actualFormat == 32) { 6506 // 32 means it is a C long... which is variable length 6507 actualFormat = cast(int) arch_long.sizeof * 8; 6508 } 6509 6510 // then it is just a bit count 6511 byteLength = cast(int) (actualItems * actualFormat / 8); 6512 6513 auto d = new ubyte[](byteLength); 6514 d[] = cast(ubyte[]) data[0 .. byteLength]; 6515 XFree(data); 6516 return d; 6517 } 6518 } 6519 return null; 6520 } 6521 6522 /* defined in the systray spec */ 6523 enum SYSTEM_TRAY_REQUEST_DOCK = 0; 6524 enum SYSTEM_TRAY_BEGIN_MESSAGE = 1; 6525 enum SYSTEM_TRAY_CANCEL_MESSAGE = 2; 6526 6527 6528 /** Global hotkey handler. Simpledisplay will usually create one for you, but if you want to use subclassing 6529 * instead of delegates, you can subclass this, and override `doHandle()` method. */ 6530 public class GlobalHotkey { 6531 KeyEvent key; 6532 void delegate () handler; 6533 6534 void doHandle () { if (handler !is null) handler(); } /// this will be called by hotkey manager 6535 6536 /// Create from initialzed KeyEvent object 6537 this (KeyEvent akey, void delegate () ahandler=null) { 6538 if (akey.key == 0 || !GlobalHotkeyManager.isGoodModifierMask(akey.modifierState)) throw new Exception("invalid global hotkey"); 6539 key = akey; 6540 handler = ahandler; 6541 } 6542 6543 /// Create from emacs-like key name ("C-M-Y", etc.) 6544 this (const(char)[] akey, void delegate () ahandler=null) { 6545 key = KeyEvent.parse(akey); 6546 if (key.key == 0 || !GlobalHotkeyManager.isGoodModifierMask(key.modifierState)) throw new Exception("invalid global hotkey"); 6547 handler = ahandler; 6548 } 6549 6550 } 6551 6552 private extern(C) int XGrabErrorHandler (Display* dpy, XErrorEvent* evt) nothrow @nogc { 6553 //conwriteln("failed to grab key"); 6554 GlobalHotkeyManager.ghfailed = true; 6555 return 0; 6556 } 6557 6558 private extern(C) int XShmErrorHandler (Display* dpy, XErrorEvent* evt) nothrow @nogc { 6559 Image.impl.xshmfailed = true; 6560 return 0; 6561 } 6562 6563 private __gshared int errorHappened; 6564 private extern(C) int adrlogger (Display* dpy, XErrorEvent* evt) nothrow @nogc { 6565 import core.stdc.stdio; 6566 char[265] buffer; 6567 XGetErrorText(dpy, evt.error_code, buffer.ptr, cast(int) buffer.length); 6568 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); 6569 errorHappened = true; 6570 return 0; 6571 } 6572 6573 /++ 6574 Global hotkey manager. It contains static methods to manage global hotkeys. 6575 6576 --- 6577 try { 6578 GlobalHotkeyManager.register("M-H-A", delegate () { hideShowWindows(); }); 6579 } catch (Exception e) { 6580 conwriteln("ERROR registering hotkey!"); 6581 } 6582 EventLoop.get.run(); 6583 --- 6584 6585 The key strings are based on Emacs. In practical terms, 6586 `M` means `alt` and `H` means the Windows logo key. `C` 6587 is `ctrl`. 6588 6589 $(WARNING 6590 This is X-specific right now. If you are on 6591 Windows, try [registerHotKey] instead. 6592 6593 We will probably merge these into a single 6594 interface later. 6595 ) 6596 +/ 6597 public class GlobalHotkeyManager : CapableOfHandlingNativeEvent { 6598 version(X11) { 6599 void recreateAfterDisconnect() { 6600 throw new Exception("NOT IMPLEMENTED"); 6601 } 6602 void discardConnectionState() { 6603 throw new Exception("NOT IMPLEMENTED"); 6604 } 6605 } 6606 6607 private static immutable uint[8] masklist = [ 0, 6608 KeyOrButtonMask.LockMask, 6609 KeyOrButtonMask.Mod2Mask, 6610 KeyOrButtonMask.Mod3Mask, 6611 KeyOrButtonMask.LockMask|KeyOrButtonMask.Mod2Mask, 6612 KeyOrButtonMask.LockMask|KeyOrButtonMask.Mod3Mask, 6613 KeyOrButtonMask.LockMask|KeyOrButtonMask.Mod2Mask|KeyOrButtonMask.Mod3Mask, 6614 KeyOrButtonMask.Mod2Mask|KeyOrButtonMask.Mod3Mask, 6615 ]; 6616 private __gshared GlobalHotkeyManager ghmanager; 6617 private __gshared bool ghfailed = false; 6618 6619 private static bool isGoodModifierMask (uint modmask) pure nothrow @safe @nogc { 6620 if (modmask == 0) return false; 6621 if (modmask&(KeyOrButtonMask.LockMask|KeyOrButtonMask.Mod2Mask|KeyOrButtonMask.Mod3Mask)) return false; 6622 if (modmask&~(KeyOrButtonMask.Mod5Mask-1)) return false; 6623 return true; 6624 } 6625 6626 private static uint cleanupModifiers (uint modmask) pure nothrow @safe @nogc { 6627 modmask &= ~(KeyOrButtonMask.LockMask|KeyOrButtonMask.Mod2Mask|KeyOrButtonMask.Mod3Mask); // remove caps, num, scroll 6628 modmask &= (KeyOrButtonMask.Mod5Mask-1); // and other modifiers 6629 return modmask; 6630 } 6631 6632 private static uint keyEvent2KeyCode() (scope auto ref const KeyEvent ke) { 6633 uint keycode = cast(uint)ke.key; 6634 auto dpy = XDisplayConnection.get; 6635 return XKeysymToKeycode(dpy, keycode); 6636 } 6637 6638 private static ulong keyCode2Hash() (uint keycode, uint modstate) pure nothrow @safe @nogc { return ((cast(ulong)modstate)<<32)|keycode; } 6639 6640 private __gshared GlobalHotkey[ulong] globalHotkeyList; 6641 6642 NativeEventHandler getNativeEventHandler () { 6643 return delegate int (XEvent e) { 6644 if (e.type != EventType.KeyPress) return 1; 6645 auto kev = cast(const(XKeyEvent)*)&e; 6646 auto hash = keyCode2Hash(e.xkey.keycode, cleanupModifiers(e.xkey.state)); 6647 if (auto ghkp = hash in globalHotkeyList) { 6648 try { 6649 ghkp.doHandle(); 6650 } catch (Exception e) { 6651 import core.stdc.stdio : stderr, fprintf; 6652 stderr.fprintf("HOTKEY HANDLER EXCEPTION: %.*s", cast(uint)e.msg.length, e.msg.ptr); 6653 } 6654 } 6655 return 1; 6656 }; 6657 } 6658 6659 private this () { 6660 auto dpy = XDisplayConnection.get; 6661 auto root = RootWindow(dpy, DefaultScreen(dpy)); 6662 CapableOfHandlingNativeEvent.nativeHandleMapping[root] = this; 6663 XDisplayConnection.addRootInput(EventMask.KeyPressMask); 6664 } 6665 6666 /// Register new global hotkey with initialized `GlobalHotkey` object. 6667 /// This function will throw if it failed to register hotkey (i.e. hotkey is invalid or already taken). 6668 static void register (GlobalHotkey gh) { 6669 if (gh is null) return; 6670 if (gh.key.key == 0 || !isGoodModifierMask(gh.key.modifierState)) throw new Exception("invalid global hotkey"); 6671 6672 auto dpy = XDisplayConnection.get; 6673 immutable keycode = keyEvent2KeyCode(gh.key); 6674 6675 auto hash = keyCode2Hash(keycode, gh.key.modifierState); 6676 if (hash in globalHotkeyList) throw new Exception("duplicate global hotkey"); 6677 if (ghmanager is null) ghmanager = new GlobalHotkeyManager(); 6678 XSync(dpy, 0/*False*/); 6679 6680 Window root = RootWindow(dpy, DefaultScreen(dpy)); 6681 XErrorHandler savedErrorHandler = XSetErrorHandler(&XGrabErrorHandler); 6682 ghfailed = false; 6683 foreach (immutable uint ormask; masklist[]) { 6684 XGrabKey(dpy, keycode, gh.key.modifierState|ormask, /*grab_window*/root, /*owner_events*/0/*False*/, GrabMode.GrabModeAsync, GrabMode.GrabModeAsync); 6685 } 6686 XSync(dpy, 0/*False*/); 6687 XSetErrorHandler(savedErrorHandler); 6688 6689 if (ghfailed) { 6690 savedErrorHandler = XSetErrorHandler(&XGrabErrorHandler); 6691 foreach (immutable uint ormask; masklist[]) XUngrabKey(dpy, keycode, gh.key.modifierState|ormask, /*grab_window*/root); 6692 XSync(dpy, 0/*False*/); 6693 XSetErrorHandler(savedErrorHandler); 6694 throw new Exception("cannot register global hotkey"); 6695 } 6696 6697 globalHotkeyList[hash] = gh; 6698 } 6699 6700 /// Ditto 6701 static void register (const(char)[] akey, void delegate () ahandler) { 6702 register(new GlobalHotkey(akey, ahandler)); 6703 } 6704 6705 private static void removeByHash (ulong hash) { 6706 if (auto ghp = hash in globalHotkeyList) { 6707 auto dpy = XDisplayConnection.get; 6708 immutable keycode = keyEvent2KeyCode(ghp.key); 6709 Window root = RootWindow(dpy, DefaultScreen(dpy)); 6710 XSync(dpy, 0/*False*/); 6711 XErrorHandler savedErrorHandler = XSetErrorHandler(&XGrabErrorHandler); 6712 foreach (immutable uint ormask; masklist[]) XUngrabKey(dpy, keycode, ghp.key.modifierState|ormask, /*grab_window*/root); 6713 XSync(dpy, 0/*False*/); 6714 XSetErrorHandler(savedErrorHandler); 6715 globalHotkeyList.remove(hash); 6716 } 6717 } 6718 6719 /// Register new global hotkey with previously used `GlobalHotkey` object. 6720 /// It is safe to unregister unknown or invalid hotkey. 6721 static void unregister (GlobalHotkey gh) { 6722 //TODO: add second AA for faster search? prolly doesn't worth it. 6723 if (gh is null) return; 6724 foreach (const ref kv; globalHotkeyList.byKeyValue) { 6725 if (kv.value is gh) { 6726 removeByHash(kv.key); 6727 return; 6728 } 6729 } 6730 } 6731 6732 /// Ditto. 6733 static void unregister (const(char)[] key) { 6734 auto kev = KeyEvent.parse(key); 6735 immutable keycode = keyEvent2KeyCode(kev); 6736 removeByHash(keyCode2Hash(keycode, kev.modifierState)); 6737 } 6738 } 6739 } 6740 6741 version(Windows) { 6742 /++ 6743 See [SyntheticInput.sendSyntheticInput] instead for cross-platform applications. 6744 6745 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). 6746 +/ 6747 void sendSyntheticInput(wstring s) { 6748 INPUT[] inputs; 6749 inputs.reserve(s.length * 2); 6750 6751 foreach(wchar c; s) { 6752 INPUT input; 6753 input.type = INPUT_KEYBOARD; 6754 input.ki.wScan = c; 6755 input.ki.dwFlags = KEYEVENTF_UNICODE; 6756 inputs ~= input; 6757 6758 input.ki.dwFlags |= KEYEVENTF_KEYUP; 6759 inputs ~= input; 6760 } 6761 6762 if(SendInput(cast(int) inputs.length, inputs.ptr, INPUT.sizeof) != inputs.length) { 6763 throw new WindowsApiException("SendInput", GetLastError()); 6764 } 6765 6766 } 6767 6768 6769 // global hotkey helper function 6770 6771 /// Platform-specific for Windows. Registers a global hotkey. Returns a registration ID. See [GlobalHotkeyManager] for Linux. Maybe some day I will merge these. 6772 int registerHotKey(SimpleWindow window, UINT modifiers, UINT vk, void delegate() handler) { 6773 __gshared int hotkeyId = 0; 6774 int id = ++hotkeyId; 6775 if(!RegisterHotKey(window.impl.hwnd, id, modifiers, vk)) 6776 throw new Exception("RegisterHotKey"); 6777 6778 __gshared void delegate()[WPARAM][HWND] handlers; 6779 6780 handlers[window.impl.hwnd][id] = handler; 6781 6782 int delegate(HWND, UINT, WPARAM, LPARAM, out int) oldHandler; 6783 6784 auto nativeEventHandler = delegate int(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam, out int mustReturn) { 6785 switch(msg) { 6786 // http://msdn.microsoft.com/en-us/library/windows/desktop/ms646279%28v=vs.85%29.aspx 6787 case WM_HOTKEY: 6788 if(auto list = hwnd in handlers) { 6789 if(auto h = wParam in *list) { 6790 (*h)(); 6791 return 0; 6792 } 6793 } 6794 goto default; 6795 default: 6796 } 6797 if(oldHandler) 6798 return oldHandler(hwnd, msg, wParam, lParam, mustReturn); 6799 return 1; // pass it on 6800 }; 6801 6802 if(window.handleNativeEvent.funcptr !is nativeEventHandler.funcptr) { 6803 oldHandler = window.handleNativeEvent; 6804 window.handleNativeEvent = nativeEventHandler; 6805 } 6806 6807 return id; 6808 } 6809 6810 /// Platform-specific for Windows. Unregisters a key. The id is the value returned by [registerHotKey]. 6811 void unregisterHotKey(SimpleWindow window, int id) { 6812 if(!UnregisterHotKey(window.impl.hwnd, id)) 6813 throw new WindowsApiException("UnregisterHotKey", GetLastError()); 6814 } 6815 } 6816 6817 version (X11) { 6818 pragma(lib, "dl"); 6819 import core.sys.posix.dlfcn; 6820 } 6821 6822 /++ 6823 Allows for sending synthetic input to the X server via the Xtst 6824 extension or on Windows using SendInput. 6825 6826 Please remember user input is meant to be user - don't use this 6827 if you have some other alternative! 6828 6829 History: 6830 Added May 17, 2020 with the X implementation. 6831 6832 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.) 6833 Bugs: 6834 All methods on OSX Cocoa will throw not yet implemented exceptions. 6835 +/ 6836 struct SyntheticInput { 6837 @disable this(); 6838 6839 private int* refcount; 6840 6841 version(X11) { 6842 private void* lib; 6843 6844 private extern(C) { 6845 void function(Display*, uint keycode, bool press, arch_ulong delay) XTestFakeKeyEvent; 6846 void function(Display*, uint button, bool press, arch_ulong delay) XTestFakeButtonEvent; 6847 } 6848 } 6849 6850 /// The dummy param must be 0. 6851 this(int dummy) { 6852 version(X11) { 6853 lib = dlopen("libXtst.so", RTLD_NOW); 6854 if(lib is null) 6855 throw new Exception("cannot load xtest lib extension"); 6856 scope(failure) 6857 dlclose(lib); 6858 6859 XTestFakeButtonEvent = cast(typeof(XTestFakeButtonEvent)) dlsym(lib, "XTestFakeButtonEvent"); 6860 XTestFakeKeyEvent = cast(typeof(XTestFakeKeyEvent)) dlsym(lib, "XTestFakeKeyEvent"); 6861 6862 if(XTestFakeKeyEvent is null) 6863 throw new Exception("No XTestFakeKeyEvent"); 6864 if(XTestFakeButtonEvent is null) 6865 throw new Exception("No XTestFakeButtonEvent"); 6866 } 6867 6868 refcount = new int; 6869 *refcount = 1; 6870 } 6871 6872 this(this) { 6873 if(refcount) 6874 *refcount += 1; 6875 } 6876 6877 ~this() { 6878 if(refcount) { 6879 *refcount -= 1; 6880 if(*refcount == 0) 6881 // I commented this because if I close the lib before 6882 // XCloseDisplay, it is liable to segfault... so just 6883 // gonna keep it loaded if it is loaded, no big deal 6884 // anyway. 6885 {} // dlclose(lib); 6886 } 6887 } 6888 6889 /++ 6890 Simulates typing a string into the keyboard. 6891 6892 Bugs: 6893 On X11, this ONLY works with basic ascii! On Windows, it can handle more. 6894 6895 Not implemented except on Windows and X11. 6896 +/ 6897 void sendSyntheticInput(string s) { 6898 version(Windows) { 6899 INPUT[] inputs; 6900 inputs.reserve(s.length * 2); 6901 6902 auto ei = GetMessageExtraInfo(); 6903 6904 foreach(wchar c; s) { 6905 INPUT input; 6906 input.type = INPUT_KEYBOARD; 6907 input.ki.wScan = c; 6908 input.ki.dwFlags = KEYEVENTF_UNICODE; 6909 input.ki.dwExtraInfo = ei; 6910 inputs ~= input; 6911 6912 input.ki.dwFlags |= KEYEVENTF_KEYUP; 6913 inputs ~= input; 6914 } 6915 6916 if(SendInput(cast(int) inputs.length, inputs.ptr, INPUT.sizeof) != inputs.length) { 6917 throw new WindowsApiException("SendInput", GetLastError()); 6918 } 6919 } else version(X11) { 6920 int delay = 0; 6921 foreach(ch; s) { 6922 pressKey(cast(Key) ch, true, delay); 6923 pressKey(cast(Key) ch, false, delay); 6924 delay += 5; 6925 } 6926 } else throw new NotYetImplementedException(); 6927 } 6928 6929 /++ 6930 Sends a fake press or release key 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 Bugs: 6935 The `delay` parameter is not implemented yet on Windows. 6936 6937 Not implemented except on Windows and X11. 6938 +/ 6939 void pressKey(Key key, bool pressed, int delay = 0) { 6940 version(Windows) { 6941 INPUT input; 6942 input.type = INPUT_KEYBOARD; 6943 input.ki.wVk = cast(ushort) key; 6944 6945 input.ki.dwFlags = pressed ? 0 : KEYEVENTF_KEYUP; 6946 input.ki.dwExtraInfo = GetMessageExtraInfo(); 6947 6948 if(SendInput(1, &input, INPUT.sizeof) != 1) { 6949 throw new WindowsApiException("SendInput", GetLastError()); 6950 } 6951 } else version(X11) { 6952 XTestFakeKeyEvent(XDisplayConnection.get, XKeysymToKeycode(XDisplayConnection.get, key), pressed, delay + pressed ? 0 : 5); 6953 } else throw new NotYetImplementedException(); 6954 } 6955 6956 /++ 6957 Sends a fake mouse button press or release event. 6958 6959 Please note you need to call [flushGui] or return to the event loop for this to actually be sent on X11. 6960 6961 `pressed` param must be `true` if button is `wheelUp` or `wheelDown`. 6962 6963 Bugs: 6964 The `delay` parameter is not implemented yet on Windows. 6965 6966 The backButton and forwardButton will throw NotYetImplementedException on Windows. 6967 6968 All arguments will throw NotYetImplementedException on OSX Cocoa. 6969 +/ 6970 void pressMouseButton(MouseButton button, bool pressed, int delay = 0) { 6971 version(Windows) { 6972 INPUT input; 6973 input.type = INPUT_MOUSE; 6974 input.mi.dwExtraInfo = GetMessageExtraInfo(); 6975 6976 // input.mi.mouseData for a wheel event 6977 6978 switch(button) { 6979 case MouseButton.left: input.mi.dwFlags = pressed ? MOUSEEVENTF_LEFTDOWN : MOUSEEVENTF_LEFTUP; break; 6980 case MouseButton.middle: input.mi.dwFlags = pressed ? MOUSEEVENTF_MIDDLEDOWN : MOUSEEVENTF_MIDDLEUP; break; 6981 case MouseButton.right: input.mi.dwFlags = pressed ? MOUSEEVENTF_RIGHTDOWN : MOUSEEVENTF_RIGHTUP; break; 6982 case MouseButton.wheelUp: 6983 case MouseButton.wheelDown: 6984 input.mi.dwFlags = MOUSEEVENTF_WHEEL; 6985 input.mi.mouseData = button == MouseButton.wheelUp ? 120 : -120; 6986 break; 6987 case MouseButton.backButton: throw new NotYetImplementedException(); 6988 case MouseButton.forwardButton: throw new NotYetImplementedException(); 6989 default: 6990 } 6991 6992 if(SendInput(1, &input, INPUT.sizeof) != 1) { 6993 throw new WindowsApiException("SendInput", GetLastError()); 6994 } 6995 } else version(X11) { 6996 int btn; 6997 6998 switch(button) { 6999 case MouseButton.left: btn = 1; break; 7000 case MouseButton.middle: btn = 2; break; 7001 case MouseButton.right: btn = 3; break; 7002 case MouseButton.wheelUp: btn = 4; break; 7003 case MouseButton.wheelDown: btn = 5; break; 7004 case MouseButton.backButton: btn = 8; break; 7005 case MouseButton.forwardButton: btn = 9; break; 7006 default: 7007 } 7008 7009 assert(btn); 7010 7011 XTestFakeButtonEvent(XDisplayConnection.get, btn, pressed, delay); 7012 } else throw new NotYetImplementedException(); 7013 } 7014 7015 /// 7016 static void moveMouseArrowBy(int dx, int dy) { 7017 version(Windows) { 7018 INPUT input; 7019 input.type = INPUT_MOUSE; 7020 input.mi.dwExtraInfo = GetMessageExtraInfo(); 7021 input.mi.dx = dx; 7022 input.mi.dy = dy; 7023 input.mi.dwFlags = MOUSEEVENTF_MOVE; 7024 7025 if(SendInput(1, &input, INPUT.sizeof) != 1) { 7026 throw new WindowsApiException("SendInput", GetLastError()); 7027 } 7028 } else version(X11) { 7029 auto disp = XDisplayConnection.get(); 7030 XWarpPointer(disp, None, None, 0, 0, 0, 0, dx, dy); 7031 XFlush(disp); 7032 } else throw new NotYetImplementedException(); 7033 } 7034 7035 /// 7036 static void moveMouseArrowTo(int x, int y) { 7037 version(Windows) { 7038 INPUT input; 7039 input.type = INPUT_MOUSE; 7040 input.mi.dwExtraInfo = GetMessageExtraInfo(); 7041 input.mi.dx = x; 7042 input.mi.dy = y; 7043 input.mi.dwFlags = MOUSEEVENTF_MOVE | MOUSEEVENTF_ABSOLUTE; 7044 7045 if(SendInput(1, &input, INPUT.sizeof) != 1) { 7046 throw new WindowsApiException("SendInput", GetLastError()); 7047 } 7048 } else version(X11) { 7049 auto disp = XDisplayConnection.get(); 7050 auto root = RootWindow(disp, DefaultScreen(disp)); 7051 XWarpPointer(disp, None, root, 0, 0, 0, 0, x, y); 7052 XFlush(disp); 7053 } else throw new NotYetImplementedException(); 7054 } 7055 } 7056 7057 7058 7059 /++ 7060 [ScreenPainter] operations can use different operations to combine the color with the color on screen. 7061 7062 See_Also: 7063 $(LIST 7064 *[ScreenPainter] 7065 *[ScreenPainter.rasterOp] 7066 ) 7067 +/ 7068 enum RasterOp { 7069 normal, /// Replaces the pixel. 7070 xor, /// Uses bitwise xor to draw. 7071 } 7072 7073 // being phobos-free keeps the size WAY down 7074 private const(char)* toStringz(string s) { return (s ~ '\0').ptr; } 7075 package(arsd) const(wchar)* toWStringz(wstring s) { return (s ~ '\0').ptr; } 7076 package(arsd) const(wchar)* toWStringz(string s) { 7077 wstring r; 7078 foreach(dchar c; s) 7079 r ~= c; 7080 r ~= '\0'; 7081 return r.ptr; 7082 } 7083 private string[] split(in void[] a, char c) { 7084 string[] ret; 7085 size_t previous = 0; 7086 foreach(i, char ch; cast(ubyte[]) a) { 7087 if(ch == c) { 7088 ret ~= cast(string) a[previous .. i]; 7089 previous = i + 1; 7090 } 7091 } 7092 if(previous != a.length) 7093 ret ~= cast(string) a[previous .. $]; 7094 return ret; 7095 } 7096 7097 version(without_opengl) { 7098 enum OpenGlOptions { 7099 no, 7100 } 7101 } else { 7102 /++ 7103 Determines if you want an OpenGL context created on the new window. 7104 7105 7106 See more: [#topics-3d|in the 3d topic]. 7107 7108 --- 7109 import arsd.simpledisplay; 7110 void main() { 7111 auto window = new SimpleWindow(500, 500, "OpenGL Test", OpenGlOptions.yes); 7112 7113 // Set up the matrix 7114 window.setAsCurrentOpenGlContext(); // make this window active 7115 7116 // This is called on each frame, we will draw our scene 7117 window.redrawOpenGlScene = delegate() { 7118 7119 }; 7120 7121 window.eventLoop(0); 7122 } 7123 --- 7124 +/ 7125 enum OpenGlOptions { 7126 no, /// No OpenGL context is created 7127 yes, /// Yes, create an OpenGL context 7128 } 7129 7130 version(X11) { 7131 static if (!SdpyIsUsingIVGLBinds) { 7132 7133 7134 struct __GLXFBConfigRec {} 7135 alias GLXFBConfig = __GLXFBConfigRec*; 7136 7137 //pragma(lib, "GL"); 7138 //pragma(lib, "GLU"); 7139 interface GLX { 7140 extern(C) nothrow @nogc { 7141 XVisualInfo* glXChooseVisual(Display *dpy, int screen, 7142 const int *attrib_list); 7143 7144 void glXCopyContext(Display *dpy, GLXContext src, 7145 GLXContext dst, arch_ulong mask); 7146 7147 GLXContext glXCreateContext(Display *dpy, XVisualInfo *vis, 7148 GLXContext share_list, Bool direct); 7149 7150 GLXPixmap glXCreateGLXPixmap(Display *dpy, XVisualInfo *vis, 7151 Pixmap pixmap); 7152 7153 void glXDestroyContext(Display *dpy, GLXContext ctx); 7154 7155 void glXDestroyGLXPixmap(Display *dpy, GLXPixmap pix); 7156 7157 int glXGetConfig(Display *dpy, XVisualInfo *vis, 7158 int attrib, int *value); 7159 7160 GLXContext glXGetCurrentContext(); 7161 7162 GLXDrawable glXGetCurrentDrawable(); 7163 7164 Bool glXIsDirect(Display *dpy, GLXContext ctx); 7165 7166 Bool glXMakeCurrent(Display *dpy, GLXDrawable drawable, 7167 GLXContext ctx); 7168 7169 Bool glXQueryExtension(Display *dpy, int *error_base, int *event_base); 7170 7171 Bool glXQueryVersion(Display *dpy, int *major, int *minor); 7172 7173 void glXSwapBuffers(Display *dpy, GLXDrawable drawable); 7174 7175 void glXUseXFont(Font font, int first, int count, int list_base); 7176 7177 void glXWaitGL(); 7178 7179 void glXWaitX(); 7180 7181 7182 GLXFBConfig* glXChooseFBConfig (Display*, int, int*, int*); 7183 int glXGetFBConfigAttrib (Display*, GLXFBConfig, int, int*); 7184 XVisualInfo* glXGetVisualFromFBConfig (Display*, GLXFBConfig); 7185 7186 char* glXQueryExtensionsString (Display*, int); 7187 void* glXGetProcAddress (const(char)*); 7188 7189 } 7190 } 7191 7192 version(OSX) 7193 mixin DynamicLoad!(GLX, "GL", 0, openGlLibrariesSuccessfullyLoaded) glx; 7194 else 7195 mixin DynamicLoad!(GLX, "GLX", 0, openGlLibrariesSuccessfullyLoaded) glx; 7196 shared static this() { 7197 glx.loadDynamicLibrary(); 7198 } 7199 7200 alias glbindGetProcAddress = glXGetProcAddress; 7201 } 7202 } else version(Windows) { 7203 /* it is done below by interface GL */ 7204 } else 7205 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."); 7206 } 7207 7208 deprecated("Sorry, I misspelled it in the first version! Use `Resizability` instead.") 7209 alias Resizablity = Resizability; 7210 7211 /// When you create a SimpleWindow, you can see its resizability to be one of these via the constructor... 7212 enum Resizability { 7213 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. 7214 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. 7215 /++ 7216 $(PITFALL 7217 Planned for the future but not implemented. 7218 ) 7219 7220 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. 7221 7222 History: 7223 Added November 11, 2022, but not yet implemented and may not be for some time. 7224 +/ 7225 /*@__future*/ allowResizingMaintainingAspectRatio, 7226 /++ 7227 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. 7228 7229 History: 7230 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. 7231 7232 Your programs should not be affected, as they will continue to function as if the user simply never resized the window at all. 7233 +/ 7234 automaticallyScaleIfPossible, 7235 } 7236 /// ditto 7237 alias Resizeability = Resizability; 7238 7239 7240 /++ 7241 Alignment for [ScreenPainter.drawText]. Left, Center, or Right may be combined with VerticalTop, VerticalCenter, or VerticalBottom via bitwise or. 7242 +/ 7243 enum TextAlignment : uint { 7244 Left = 0, /// 7245 Center = 1, /// 7246 Right = 2, /// 7247 7248 VerticalTop = 0, /// 7249 VerticalCenter = 4, /// 7250 VerticalBottom = 8, /// 7251 } 7252 7253 public import arsd.color; // no longer stand alone... :-( but i need a common type for this to work with images easily. 7254 alias Rectangle = arsd.color.Rectangle; 7255 7256 7257 /++ 7258 Keyboard press and release events. 7259 +/ 7260 struct KeyEvent { 7261 /// see table below. Always use the symbolic names, even for ASCII characters, since the actual numbers vary across platforms. See [Key] 7262 Key key; 7263 ubyte hardwareCode; /// A platform and hardware specific code for the key 7264 bool pressed; /// true if the key was just pressed, false if it was just released. note: released events aren't always sent... 7265 7266 deprecated("This never actually worked anyway, you should do a character event handler instead.") dchar character; 7267 7268 uint modifierState; /// see enum [ModifierState]. They are bitwise combined together. 7269 7270 SimpleWindow window; /// associated Window 7271 7272 /++ 7273 A view into the upcoming buffer holding coming character events that are sent if and only if neither 7274 the alt or super modifier keys are pressed (check this with `!(modifierState & (ModifierState.window | ModifierState.alt))` 7275 to predict if char events are actually coming.. 7276 7277 Only available on X systems since this information is not given ahead of time elsewhere. 7278 (Well, you COULD probably dig it up, but as far as I know right now, it isn't terribly pretty.) 7279 7280 I'm adding this because it is useful to the terminal emulator, but given its platform specificness 7281 and potential quirks I'd recommend avoiding it. 7282 7283 History: 7284 Added April 26, 2021 (dub v9.5) 7285 +/ 7286 version(X11) 7287 dchar[] charsPossible; 7288 7289 // convert key event to simplified string representation a-la emacs 7290 const(char)[] toStrBuf(bool growdest=false) (char[] dest) const nothrow @trusted { 7291 uint dpos = 0; 7292 void put (const(char)[] s...) nothrow @trusted { 7293 static if (growdest) { 7294 foreach (char ch; s) if (dpos < dest.length) dest.ptr[dpos++] = ch; else { dest ~= ch; ++dpos; } 7295 } else { 7296 foreach (char ch; s) if (dpos < dest.length) dest.ptr[dpos++] = ch; 7297 } 7298 } 7299 7300 void putMod (ModifierState mod, Key key, string text) nothrow @trusted { 7301 if ((this.modifierState&mod) != 0 && (this.pressed || this.key != key)) put(text); 7302 } 7303 7304 if (!this.key && !(this.modifierState&(ModifierState.ctrl|ModifierState.alt|ModifierState.shift|ModifierState.windows))) return null; 7305 7306 // put modifiers 7307 // releasing modifier keys can produce bizarre things like "Ctrl+Ctrl", so hack around it 7308 putMod(ModifierState.ctrl, Key.Ctrl, "Ctrl+"); 7309 putMod(ModifierState.alt, Key.Alt, "Alt+"); 7310 putMod(ModifierState.windows, Key.Shift, "Windows+"); 7311 putMod(ModifierState.shift, Key.Shift, "Shift+"); 7312 7313 if (this.key) { 7314 foreach (string kn; __traits(allMembers, Key)) { 7315 if (this.key == __traits(getMember, Key, kn)) { 7316 // HACK! 7317 static if (kn == "N0") put("0"); 7318 else static if (kn == "N1") put("1"); 7319 else static if (kn == "N2") put("2"); 7320 else static if (kn == "N3") put("3"); 7321 else static if (kn == "N4") put("4"); 7322 else static if (kn == "N5") put("5"); 7323 else static if (kn == "N6") put("6"); 7324 else static if (kn == "N7") put("7"); 7325 else static if (kn == "N8") put("8"); 7326 else static if (kn == "N9") put("9"); 7327 else put(kn); 7328 return dest[0..dpos]; 7329 } 7330 } 7331 put("Unknown"); 7332 } else { 7333 if (dpos && dest[dpos-1] == '+') --dpos; 7334 } 7335 return dest[0..dpos]; 7336 } 7337 7338 string toStr() () { return cast(string)toStrBuf!true(null); } // it is safe to cast here 7339 7340 /** Parse string into key name with modifiers. It accepts things like: 7341 * 7342 * C-H-1 -- emacs style (ctrl, and windows, and 1) 7343 * 7344 * Ctrl+Win+1 -- windows style 7345 * 7346 * Ctrl-Win-1 -- '-' is a valid delimiter too 7347 * 7348 * Ctrl Win 1 -- and space 7349 * 7350 * and even "Win + 1 + Ctrl". 7351 */ 7352 static KeyEvent parse (const(char)[] name, bool* ignoreModsOut=null, int* updown=null) nothrow @trusted @nogc { 7353 auto nanchor = name; // keep it anchored, 'cause `name` may have NO_INTERIOR set 7354 7355 // remove trailing spaces 7356 while (name.length && name[$-1] <= ' ') name = name[0..$-1]; 7357 7358 // tokens delimited by blank, '+', or '-' 7359 // null on eol 7360 const(char)[] getToken () nothrow @trusted @nogc { 7361 // remove leading spaces and delimiters 7362 while (name.length && (name[0] <= ' ' || name[0] == '+' || name[0] == '-')) name = name[1..$]; 7363 if (name.length == 0) return null; // oops, no more tokens 7364 // get token 7365 size_t epos = 0; 7366 while (epos < name.length && name[epos] > ' ' && name[epos] != '+' && name[epos] != '-') ++epos; 7367 assert(epos > 0 && epos <= name.length); 7368 auto res = name[0..epos]; 7369 name = name[epos..$]; 7370 return res; 7371 } 7372 7373 static bool strEquCI (const(char)[] s0, const(char)[] s1) pure nothrow @trusted @nogc { 7374 if (s0.length != s1.length) return false; 7375 foreach (immutable ci, char c0; s0) { 7376 if (c0 >= 'A' && c0 <= 'Z') c0 += 32; // poor man's tolower 7377 char c1 = s1[ci]; 7378 if (c1 >= 'A' && c1 <= 'Z') c1 += 32; // poor man's tolower 7379 if (c0 != c1) return false; 7380 } 7381 return true; 7382 } 7383 7384 if (ignoreModsOut !is null) *ignoreModsOut = false; 7385 if (updown !is null) *updown = -1; 7386 KeyEvent res; 7387 res.key = cast(Key)0; // just in case 7388 const(char)[] tk, tkn; // last token 7389 bool allowEmascStyle = true; 7390 bool ignoreModifiers = false; 7391 tokenloop: for (;;) { 7392 tk = tkn; 7393 tkn = getToken(); 7394 //k8: yay, i took "Bloody Mess" trait from Fallout! 7395 if (tkn.length != 0 && tk.length == 0) { tk = tkn; continue tokenloop; } 7396 if (tkn.length == 0 && tk.length == 0) break; // no more tokens 7397 if (allowEmascStyle && tkn.length != 0) { 7398 if (tk.length == 1) { 7399 char mdc = tk[0]; 7400 if (mdc >= 'a' && mdc <= 'z') mdc -= 32; // poor man's toupper() 7401 if (mdc == 'C' && (res.modifierState&ModifierState.ctrl) == 0) {res.modifierState |= ModifierState.ctrl; continue tokenloop; } 7402 if (mdc == 'M' && (res.modifierState&ModifierState.alt) == 0) { res.modifierState |= ModifierState.alt; continue tokenloop; } 7403 if (mdc == 'H' && (res.modifierState&ModifierState.windows) == 0) { res.modifierState |= ModifierState.windows; continue tokenloop; } 7404 if (mdc == 'S' && (res.modifierState&ModifierState.shift) == 0) { res.modifierState |= ModifierState.shift; continue tokenloop; } 7405 if (mdc == '*') { ignoreModifiers = true; continue tokenloop; } 7406 if (mdc == 'U' || mdc == 'R') { if (updown !is null) *updown = 0; continue tokenloop; } 7407 if (mdc == 'D' || mdc == 'P') { if (updown !is null) *updown = 1; continue tokenloop; } 7408 } 7409 } 7410 allowEmascStyle = false; 7411 if (strEquCI(tk, "Ctrl")) { res.modifierState |= ModifierState.ctrl; continue tokenloop; } 7412 if (strEquCI(tk, "Alt")) { res.modifierState |= ModifierState.alt; continue tokenloop; } 7413 if (strEquCI(tk, "Win") || strEquCI(tk, "Windows")) { res.modifierState |= ModifierState.windows; continue tokenloop; } 7414 if (strEquCI(tk, "Shift")) { res.modifierState |= ModifierState.shift; continue tokenloop; } 7415 if (strEquCI(tk, "Release")) { if (updown !is null) *updown = 0; continue tokenloop; } 7416 if (strEquCI(tk, "Press")) { if (updown !is null) *updown = 1; continue tokenloop; } 7417 if (tk == "*") { ignoreModifiers = true; continue tokenloop; } 7418 if (tk.length == 0) continue; 7419 // try key name 7420 if (res.key == 0) { 7421 // little hack 7422 if (tk.length == 1 && tk[0] >= '0' && tk[0] <= '9') { 7423 final switch (tk[0]) { 7424 case '0': tk = "N0"; break; 7425 case '1': tk = "N1"; break; 7426 case '2': tk = "N2"; break; 7427 case '3': tk = "N3"; break; 7428 case '4': tk = "N4"; break; 7429 case '5': tk = "N5"; break; 7430 case '6': tk = "N6"; break; 7431 case '7': tk = "N7"; break; 7432 case '8': tk = "N8"; break; 7433 case '9': tk = "N9"; break; 7434 } 7435 } 7436 foreach (string kn; __traits(allMembers, Key)) { 7437 if (strEquCI(tk, kn)) { res.key = __traits(getMember, Key, kn); continue tokenloop; } 7438 } 7439 } 7440 // unknown or duplicate key name, get out of here 7441 break; 7442 } 7443 if (ignoreModsOut !is null) *ignoreModsOut = ignoreModifiers; 7444 return res; // something 7445 } 7446 7447 bool opEquals() (const(char)[] name) const nothrow @trusted @nogc { 7448 enum modmask = (ModifierState.ctrl|ModifierState.alt|ModifierState.shift|ModifierState.windows); 7449 void doModKey (ref uint mask, ref Key kk, Key k, ModifierState mst) { 7450 if (kk == k) { mask |= mst; kk = cast(Key)0; } 7451 } 7452 bool ignoreMods; 7453 int updown; 7454 auto ke = KeyEvent.parse(name, &ignoreMods, &updown); 7455 if ((updown == 0 && this.pressed) || (updown == 1 && !this.pressed)) return false; 7456 if (this.key != ke.key) { 7457 // things like "ctrl+alt" are complicated 7458 uint tkm = this.modifierState&modmask; 7459 uint kkm = ke.modifierState&modmask; 7460 Key tk = this.key; 7461 // ke 7462 doModKey(kkm, ke.key, Key.Ctrl, ModifierState.ctrl); 7463 doModKey(kkm, ke.key, Key.Alt, ModifierState.alt); 7464 doModKey(kkm, ke.key, Key.Windows, ModifierState.windows); 7465 doModKey(kkm, ke.key, Key.Shift, ModifierState.shift); 7466 // this 7467 doModKey(tkm, tk, Key.Ctrl, ModifierState.ctrl); 7468 doModKey(tkm, tk, Key.Alt, ModifierState.alt); 7469 doModKey(tkm, tk, Key.Windows, ModifierState.windows); 7470 doModKey(tkm, tk, Key.Shift, ModifierState.shift); 7471 return (tk == ke.key && tkm == kkm); 7472 } 7473 return (ignoreMods || ((this.modifierState&modmask) == (ke.modifierState&modmask))); 7474 } 7475 } 7476 7477 /// Sets the application name. 7478 @property string ApplicationName(string name) { 7479 return _applicationName = name; 7480 } 7481 7482 string _applicationName; 7483 7484 /// ditto 7485 @property string ApplicationName() { 7486 if(_applicationName is null) { 7487 import core.runtime; 7488 return Runtime.args[0]; 7489 } 7490 return _applicationName; 7491 } 7492 7493 7494 /// Type of a [MouseEvent]. 7495 enum MouseEventType : int { 7496 motion = 0, /// The mouse moved inside the window 7497 buttonPressed = 1, /// A mouse button was pressed or the wheel was spun 7498 buttonReleased = 2, /// A mouse button was released 7499 } 7500 7501 // FIXME: mouse move should be distinct from presses+releases, so we can avoid subscribing to those events in X unnecessarily 7502 /++ 7503 Listen for this on your event listeners if you are interested in mouse action. 7504 7505 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. 7506 7507 Examples: 7508 7509 This will draw boxes on the window with the mouse as you hold the left button. 7510 --- 7511 import arsd.simpledisplay; 7512 7513 void main() { 7514 auto window = new SimpleWindow(); 7515 7516 window.eventLoop(0, 7517 (MouseEvent ev) { 7518 if(ev.modifierState & ModifierState.leftButtonDown) { 7519 auto painter = window.draw(); 7520 painter.fillColor = Color.red; 7521 painter.outlineColor = Color.black; 7522 painter.drawRectangle(Point(ev.x / 16 * 16, ev.y / 16 * 16), 16, 16); 7523 } 7524 } 7525 ); 7526 } 7527 --- 7528 +/ 7529 struct MouseEvent { 7530 MouseEventType type; /// movement, press, release, double click. See [MouseEventType] 7531 7532 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. 7533 int y; /// Current Y position of the cursor when the event fired. 7534 7535 int dx; /// Change in X position since last report 7536 int dy; /// Change in Y position since last report 7537 7538 MouseButton button; /// See [MouseButton] 7539 int modifierState; /// See [ModifierState] 7540 7541 version(X11) 7542 private Time timestamp; 7543 7544 /// Returns a linear representation of mouse button, 7545 /// for use with static arrays. Guaranteed to be >= 0 && <= 15 7546 /// 7547 /// Its implementation is based on range-limiting `core.bitop.bsf(button) + 1`. 7548 @property ubyte buttonLinear() const { 7549 import core.bitop; 7550 if(button == 0) 7551 return 0; 7552 return (bsf(button) + 1) & 0b1111; 7553 } 7554 7555 bool doubleClick; /// was it a double click? Only set on type == [MouseEventType.buttonPressed] 7556 7557 SimpleWindow window; /// The window in which the event happened. 7558 7559 Point globalCoordinates() { 7560 Point p; 7561 if(window is null) 7562 throw new Exception("wtf"); 7563 static if(UsingSimpledisplayX11) { 7564 Window child; 7565 XTranslateCoordinates( 7566 XDisplayConnection.get, 7567 window.impl.window, 7568 RootWindow(XDisplayConnection.get, DefaultScreen(XDisplayConnection.get)), 7569 x, y, &p.x, &p.y, &child); 7570 return p; 7571 } else version(Windows) { 7572 POINT[1] points; 7573 points[0].x = x; 7574 points[0].y = y; 7575 MapWindowPoints( 7576 window.impl.hwnd, 7577 null, 7578 points.ptr, 7579 points.length 7580 ); 7581 p.x = points[0].x; 7582 p.y = points[0].y; 7583 7584 return p; 7585 } else version(OSXCocoa) { 7586 throw new NotYetImplementedException(); 7587 } else static assert(0); 7588 } 7589 7590 bool opEquals() (const(char)[] str) pure nothrow @trusted @nogc { return equStr(this, str); } 7591 7592 /** 7593 can contain emacs-like modifier prefix 7594 case-insensitive names: 7595 lmbX/leftX 7596 rmbX/rightX 7597 mmbX/middleX 7598 wheelX 7599 motion (no prefix allowed) 7600 'X' is either "up" or "down" (or "-up"/"-down"); if omited, means "down" 7601 */ 7602 static bool equStr() (scope auto ref const MouseEvent event, const(char)[] str) pure nothrow @trusted @nogc { 7603 if (str.length == 0) return false; // just in case 7604 debug(arsd_mevent_strcmp) { import iv.cmdcon; conwriteln("str=<", str, ">"); } 7605 enum Flag : uint { Up = 0x8000_0000U, Down = 0x4000_0000U, Any = 0x1000_0000U } 7606 auto anchor = str; 7607 uint mods = 0; // uint.max == any 7608 // interesting bits in kmod 7609 uint kmodmask = 7610 ModifierState.shift| 7611 ModifierState.ctrl| 7612 ModifierState.alt| 7613 ModifierState.windows| 7614 ModifierState.leftButtonDown| 7615 ModifierState.middleButtonDown| 7616 ModifierState.rightButtonDown| 7617 0; 7618 uint lastButt = uint.max; // otherwise, bit 31 means "down" 7619 bool wasButtons = false; 7620 while (str.length) { 7621 if (str.ptr[0] <= ' ') { 7622 while (str.length && str.ptr[0] <= ' ') str = str[1..$]; 7623 continue; 7624 } 7625 // one-letter modifier? 7626 if (str.length >= 2 && str.ptr[1] == '-') { 7627 switch (str.ptr[0]) { 7628 case '*': // "any" modifier (cannot be undone) 7629 mods = mods.max; 7630 break; 7631 case 'C': case 'c': // emacs "ctrl" 7632 if (mods != mods.max) mods |= ModifierState.ctrl; 7633 break; 7634 case 'M': case 'm': // emacs "meta" 7635 if (mods != mods.max) mods |= ModifierState.alt; 7636 break; 7637 case 'S': case 's': // emacs "shift" 7638 if (mods != mods.max) mods |= ModifierState.shift; 7639 break; 7640 case 'H': case 'h': // emacs "hyper" (aka winkey) 7641 if (mods != mods.max) mods |= ModifierState.windows; 7642 break; 7643 default: 7644 return false; // unknown modifier 7645 } 7646 str = str[2..$]; 7647 continue; 7648 } 7649 // word 7650 char[16] buf = void; // locased 7651 auto wep = 0; 7652 while (str.length) { 7653 immutable char ch = str.ptr[0]; 7654 if (ch <= ' ' || ch == '-') break; 7655 str = str[1..$]; 7656 if (wep > buf.length) return false; // too long 7657 if (ch >= 'A' && ch <= 'Z') buf.ptr[wep++] = cast(char)(ch+32); // poor man tolower 7658 else if (ch >= 'a' && ch <= 'z') buf.ptr[wep++] = ch; 7659 else return false; // invalid char 7660 } 7661 if (wep == 0) return false; // just in case 7662 uint bnum; 7663 enum UpDown { None = -1, Up, Down, Any } 7664 auto updown = UpDown.None; // 0: up; 1: down 7665 switch (buf[0..wep]) { 7666 // left button 7667 case "lmbup": case "leftup": updown = UpDown.Up; goto case "lmb"; 7668 case "lmbdown": case "leftdown": updown = UpDown.Down; goto case "lmb"; 7669 case "lmbany": case "leftany": updown = UpDown.Any; goto case "lmb"; 7670 case "lmb": case "left": bnum = 0; break; 7671 // middle button 7672 case "mmbup": case "middleup": updown = UpDown.Up; goto case "mmb"; 7673 case "mmbdown": case "middledown": updown = UpDown.Down; goto case "mmb"; 7674 case "mmbany": case "middleany": updown = UpDown.Any; goto case "mmb"; 7675 case "mmb": case "middle": bnum = 1; break; 7676 // right button 7677 case "rmbup": case "rightup": updown = UpDown.Up; goto case "rmb"; 7678 case "rmbdown": case "rightdown": updown = UpDown.Down; goto case "rmb"; 7679 case "rmbany": case "rightany": updown = UpDown.Any; goto case "rmb"; 7680 case "rmb": case "right": bnum = 2; break; 7681 // wheel 7682 case "wheelup": updown = UpDown.Up; goto case "wheel"; 7683 case "wheeldown": updown = UpDown.Down; goto case "wheel"; 7684 case "wheelany": updown = UpDown.Any; goto case "wheel"; 7685 case "wheel": bnum = 3; break; 7686 // motion 7687 case "motion": bnum = 7; break; 7688 // unknown 7689 default: return false; 7690 } 7691 debug(arsd_mevent_strcmp) { import iv.cmdcon; conprintfln(" 0: mods=0x%08x; bnum=%u; updown=%s [%s]", mods, bnum, updown, str); } 7692 // parse possible "-up" or "-down" 7693 if (updown == UpDown.None && bnum < 7 && str.length > 0 && str.ptr[0] == '-') { 7694 wep = 0; 7695 foreach (immutable idx, immutable char ch; str[1..$]) { 7696 if (ch <= ' ' || ch == '-') break; 7697 assert(idx == wep); // for now; trick 7698 if (wep > buf.length) { wep = 0; break; } // too long 7699 if (ch >= 'A' && ch <= 'Z') buf.ptr[wep++] = cast(char)(ch+32); // poor man tolower 7700 else if (ch >= 'a' && ch <= 'z') buf.ptr[wep++] = ch; 7701 else { wep = 0; break; } // invalid char 7702 } 7703 if (wep == 2 && buf[0..wep] == "up") updown = UpDown.Up; 7704 else if (wep == 4 && buf[0..wep] == "down") updown = UpDown.Down; 7705 else if (wep == 3 && buf[0..wep] == "any") updown = UpDown.Any; 7706 // remove parsed part 7707 if (updown != UpDown.None) str = str[wep+1..$]; 7708 } 7709 if (updown == UpDown.None) { 7710 updown = UpDown.Down; 7711 } 7712 wasButtons = wasButtons || (bnum <= 2); 7713 //assert(updown != UpDown.None); 7714 debug(arsd_mevent_strcmp) { import iv.cmdcon; conprintfln(" 1: mods=0x%08x; bnum=%u; updown=%s [%s]", mods, bnum, updown, str); } 7715 // if we have a previous button, it goes to modifiers (unless it is a wheel or motion) 7716 if (lastButt != lastButt.max) { 7717 if ((lastButt&0xff) >= 3) return false; // wheel or motion 7718 if (mods != mods.max) { 7719 uint butbit = 0; 7720 final switch (lastButt&0x03) { 7721 case 0: butbit = ModifierState.leftButtonDown; break; 7722 case 1: butbit = ModifierState.middleButtonDown; break; 7723 case 2: butbit = ModifierState.rightButtonDown; break; 7724 } 7725 if (lastButt&Flag.Down) mods |= butbit; 7726 else if (lastButt&Flag.Up) mods &= ~butbit; 7727 else if (lastButt&Flag.Any) kmodmask &= ~butbit; 7728 } 7729 } 7730 // remember last button 7731 lastButt = bnum|(updown == UpDown.Up ? Flag.Up : updown == UpDown.Any ? Flag.Any : Flag.Down); 7732 } 7733 // no button -- nothing to do 7734 if (lastButt == lastButt.max) return false; 7735 // done parsing, check if something's left 7736 foreach (immutable char ch; str) if (ch > ' ') return false; // oops 7737 // remove action button from mask 7738 if ((lastButt&0xff) < 3) { 7739 final switch (lastButt&0x03) { 7740 case 0: kmodmask &= ~cast(uint)ModifierState.leftButtonDown; break; 7741 case 1: kmodmask &= ~cast(uint)ModifierState.middleButtonDown; break; 7742 case 2: kmodmask &= ~cast(uint)ModifierState.rightButtonDown; break; 7743 } 7744 } 7745 // special case: "Motion" means "ignore buttons" 7746 if ((lastButt&0xff) == 7 && !wasButtons) { 7747 debug(arsd_mevent_strcmp) { import iv.cmdcon; conwriteln(" *: special motion"); } 7748 kmodmask &= ~cast(uint)(ModifierState.leftButtonDown|ModifierState.middleButtonDown|ModifierState.rightButtonDown); 7749 } 7750 uint kmod = event.modifierState&kmodmask; 7751 debug(arsd_mevent_strcmp) { import iv.cmdcon; conprintfln(" *: mods=0x%08x; lastButt=0x%08x; kmod=0x%08x; type=%s", mods, lastButt, kmod, event.type); } 7752 // check modifier state 7753 if (mods != mods.max) { 7754 if (kmod != mods) return false; 7755 } 7756 // now check type 7757 if ((lastButt&0xff) == 7) { 7758 // motion 7759 if (event.type != MouseEventType.motion) return false; 7760 } else if ((lastButt&0xff) == 3) { 7761 // wheel 7762 if (lastButt&Flag.Up) return (event.type == MouseEventType.buttonPressed && event.button == MouseButton.wheelUp); 7763 if (lastButt&Flag.Down) return (event.type == MouseEventType.buttonPressed && event.button == MouseButton.wheelDown); 7764 if (lastButt&Flag.Any) return (event.type == MouseEventType.buttonPressed && (event.button == MouseButton.wheelUp || event.button == MouseButton.wheelUp)); 7765 return false; 7766 } else { 7767 // buttons 7768 if (((lastButt&Flag.Down) != 0 && event.type != MouseEventType.buttonPressed) || 7769 ((lastButt&Flag.Up) != 0 && event.type != MouseEventType.buttonReleased)) 7770 { 7771 return false; 7772 } 7773 // button number 7774 switch (lastButt&0x03) { 7775 case 0: if (event.button != MouseButton.left) return false; break; 7776 case 1: if (event.button != MouseButton.middle) return false; break; 7777 case 2: if (event.button != MouseButton.right) return false; break; 7778 default: return false; 7779 } 7780 } 7781 return true; 7782 } 7783 } 7784 7785 version(arsd_mevent_strcmp_test) unittest { 7786 MouseEvent event; 7787 event.type = MouseEventType.buttonPressed; 7788 event.button = MouseButton.left; 7789 event.modifierState = ModifierState.ctrl; 7790 assert(event == "C-LMB"); 7791 assert(event != "C-LMBUP"); 7792 assert(event != "C-LMB-UP"); 7793 assert(event != "C-S-LMB"); 7794 assert(event == "*-LMB"); 7795 assert(event != "*-LMB-UP"); 7796 7797 event.type = MouseEventType.buttonReleased; 7798 assert(event != "C-LMB"); 7799 assert(event == "C-LMBUP"); 7800 assert(event == "C-LMB-UP"); 7801 assert(event != "C-S-LMB"); 7802 assert(event != "*-LMB"); 7803 assert(event == "*-LMB-UP"); 7804 7805 event.button = MouseButton.right; 7806 event.modifierState |= ModifierState.shift; 7807 event.type = MouseEventType.buttonPressed; 7808 assert(event != "C-LMB"); 7809 assert(event != "C-LMBUP"); 7810 assert(event != "C-LMB-UP"); 7811 assert(event != "C-S-LMB"); 7812 assert(event != "*-LMB"); 7813 assert(event != "*-LMB-UP"); 7814 7815 assert(event != "C-RMB"); 7816 assert(event != "C-RMBUP"); 7817 assert(event != "C-RMB-UP"); 7818 assert(event == "C-S-RMB"); 7819 assert(event == "*-RMB"); 7820 assert(event != "*-RMB-UP"); 7821 } 7822 7823 /// This gives a few more options to drawing lines and such 7824 struct Pen { 7825 Color color; /// the foreground color 7826 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. 7827 Style style; /// See [Style] 7828 /+ 7829 // From X.h 7830 7831 #define LineSolid 0 7832 #define LineOnOffDash 1 7833 #define LineDoubleDash 2 7834 LineDou- The full path of the line is drawn, but the 7835 bleDash even dashes are filled differently from the 7836 odd dashes (see fill-style) with CapButt 7837 style used where even and odd dashes meet. 7838 7839 7840 7841 /* capStyle */ 7842 7843 #define CapNotLast 0 7844 #define CapButt 1 7845 #define CapRound 2 7846 #define CapProjecting 3 7847 7848 /* joinStyle */ 7849 7850 #define JoinMiter 0 7851 #define JoinRound 1 7852 #define JoinBevel 2 7853 7854 /* fillStyle */ 7855 7856 #define FillSolid 0 7857 #define FillTiled 1 7858 #define FillStippled 2 7859 #define FillOpaqueStippled 3 7860 7861 7862 +/ 7863 /// Style of lines drawn 7864 enum Style { 7865 Solid, /// a solid line 7866 Dashed, /// a dashed line 7867 Dotted, /// a dotted line 7868 } 7869 } 7870 7871 7872 /++ 7873 Represents an in-memory image in the format that the GUI expects, but with its raw data available to your program. 7874 7875 7876 On Windows, this means a device-independent bitmap. On X11, it is an XImage. 7877 7878 $(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.) 7879 7880 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. 7881 7882 If you intend to draw an image to screen several times, you will want to convert it into a [Sprite]. 7883 7884 $(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. 7885 7886 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! 7887 7888 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!) 7889 7890 Please call `destroy(image);` when you are done with it. The easiest way to do this is with scope: 7891 7892 --- 7893 auto image = new Image(256, 256); 7894 scope(exit) destroy(image); 7895 --- 7896 7897 As long as you don't hold on to it outside the scope. 7898 7899 I might change it to be an owned pointer at some point in the future. 7900 7901 ) 7902 7903 Drawing pixels on the image may be simple, using the `opIndexAssign` function, but 7904 you can also often get a fair amount of speedup by getting the raw data format and 7905 writing some custom code. 7906 7907 FIXME INSERT EXAMPLES HERE 7908 7909 7910 +/ 7911 final class Image { 7912 /// 7913 this(int width, int height, bool forcexshm=false, bool enableAlpha = false) { 7914 this.width = width; 7915 this.height = height; 7916 this.enableAlpha = enableAlpha; 7917 7918 impl.createImage(width, height, forcexshm, enableAlpha); 7919 } 7920 7921 /// 7922 this(Size size, bool forcexshm=false, bool enableAlpha = false) { 7923 this(size.width, size.height, forcexshm, enableAlpha); 7924 } 7925 7926 private bool suppressDestruction; 7927 7928 version(X11) 7929 this(XImage* handle) { 7930 this.handle = handle; 7931 this.rawData = cast(ubyte*) handle.data; 7932 this.width = handle.width; 7933 this.height = handle.height; 7934 this.enableAlpha = handle.depth == 32; 7935 suppressDestruction = true; 7936 } 7937 7938 ~this() { 7939 if(suppressDestruction) return; 7940 impl.dispose(); 7941 } 7942 7943 // these numbers are used for working with rawData itself, skipping putPixel and getPixel 7944 /// if you do the math yourself you might be able to optimize it. Call these functions only once and cache the value. 7945 pure const @system nothrow { 7946 /* 7947 To use these to draw a blue rectangle with size WxH at position X,Y... 7948 7949 // make certain that it will fit before we proceed 7950 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! 7951 7952 // gather all the values you'll need up front. These can be kept until the image changes size if you want 7953 // (though calculating them isn't really that expensive). 7954 auto nextLineAdjustment = img.adjustmentForNextLine(); 7955 auto offR = img.redByteOffset(); 7956 auto offB = img.blueByteOffset(); 7957 auto offG = img.greenByteOffset(); 7958 auto bpp = img.bytesPerPixel(); 7959 7960 auto data = img.getDataPointer(); 7961 7962 // figure out the starting byte offset 7963 auto offset = img.offsetForTopLeftPixel() + nextLineAdjustment*Y + bpp * X; 7964 7965 auto startOfLine = data + offset; // get our pointer lined up on the first pixel 7966 7967 // and now our drawing loop for the rectangle 7968 foreach(y; 0 .. H) { 7969 auto data = startOfLine; // we keep the start of line separately so moving to the next line is simple and portable 7970 foreach(x; 0 .. W) { 7971 // write our color 7972 data[offR] = 0; 7973 data[offG] = 0; 7974 data[offB] = 255; 7975 7976 data += bpp; // moving to the next pixel is just an addition... 7977 } 7978 startOfLine += nextLineAdjustment; 7979 } 7980 7981 7982 As you can see, the loop itself was very simple thanks to the calculations being moved outside. 7983 7984 FIXME: I wonder if I can make the pixel formats consistently 32 bit across platforms, so the color offsets 7985 can be made into a bitmask or something so we can write them as *uint... 7986 */ 7987 7988 /// 7989 int offsetForTopLeftPixel() { 7990 version(X11) { 7991 return 0; 7992 } else version(Windows) { 7993 if(enableAlpha) { 7994 return (width * 4) * (height - 1); 7995 } else { 7996 return (((cast(int) width * 3 + 3) / 4) * 4) * (height - 1); 7997 } 7998 } else version(OSXCocoa) { 7999 return 0 ; //throw new NotYetImplementedException(); 8000 } else static assert(0, "fill in this info for other OSes"); 8001 } 8002 8003 /// 8004 int offsetForPixel(int x, int y) { 8005 version(X11) { 8006 auto offset = (y * width + x) * 4; 8007 return offset; 8008 } else version(Windows) { 8009 if(enableAlpha) { 8010 auto itemsPerLine = width * 4; 8011 // remember, bmps are upside down 8012 auto offset = itemsPerLine * (height - y - 1) + x * 4; 8013 return offset; 8014 } else { 8015 auto itemsPerLine = ((cast(int) width * 3 + 3) / 4) * 4; 8016 // remember, bmps are upside down 8017 auto offset = itemsPerLine * (height - y - 1) + x * 3; 8018 return offset; 8019 } 8020 } else version(OSXCocoa) { 8021 return 0 ; //throw new NotYetImplementedException(); 8022 } else static assert(0, "fill in this info for other OSes"); 8023 } 8024 8025 /// 8026 int adjustmentForNextLine() { 8027 version(X11) { 8028 return width * 4; 8029 } else version(Windows) { 8030 // windows bmps are upside down, so the adjustment is actually negative 8031 if(enableAlpha) 8032 return - (cast(int) width * 4); 8033 else 8034 return -((cast(int) width * 3 + 3) / 4) * 4; 8035 } else version(OSXCocoa) { 8036 return 0 ; //throw new NotYetImplementedException(); 8037 } else static assert(0, "fill in this info for other OSes"); 8038 } 8039 8040 /// once you have the position of a pixel, use these to get to the proper color 8041 int redByteOffset() { 8042 version(X11) { 8043 return 2; 8044 } else version(Windows) { 8045 return 2; 8046 } else version(OSXCocoa) { 8047 return 0 ; //throw new NotYetImplementedException(); 8048 } else static assert(0, "fill in this info for other OSes"); 8049 } 8050 8051 /// 8052 int greenByteOffset() { 8053 version(X11) { 8054 return 1; 8055 } else version(Windows) { 8056 return 1; 8057 } else version(OSXCocoa) { 8058 return 0 ; //throw new NotYetImplementedException(); 8059 } else static assert(0, "fill in this info for other OSes"); 8060 } 8061 8062 /// 8063 int blueByteOffset() { 8064 version(X11) { 8065 return 0; 8066 } else version(Windows) { 8067 return 0; 8068 } else version(OSXCocoa) { 8069 return 0 ; //throw new NotYetImplementedException(); 8070 } else static assert(0, "fill in this info for other OSes"); 8071 } 8072 8073 /// Only valid if [enableAlpha] is true 8074 int alphaByteOffset() { 8075 version(X11) { 8076 return 3; 8077 } else version(Windows) { 8078 return 3; 8079 } else version(OSXCocoa) { 8080 return 3; //throw new NotYetImplementedException(); 8081 } else static assert(0, "fill in this info for other OSes"); 8082 } 8083 } 8084 8085 /// 8086 final void putPixel(int x, int y, Color c) { 8087 if(x < 0 || x >= width) 8088 return; 8089 if(y < 0 || y >= height) 8090 return; 8091 8092 impl.setPixel(x, y, c); 8093 } 8094 8095 /// 8096 final Color getPixel(int x, int y) { 8097 if(x < 0 || x >= width) 8098 return Color.transparent; 8099 if(y < 0 || y >= height) 8100 return Color.transparent; 8101 8102 version(OSXCocoa) throw new NotYetImplementedException(); else 8103 return impl.getPixel(x, y); 8104 } 8105 8106 /// 8107 final void opIndexAssign(Color c, int x, int y) { 8108 putPixel(x, y, c); 8109 } 8110 8111 /// 8112 TrueColorImage toTrueColorImage() { 8113 auto tci = new TrueColorImage(width, height); 8114 convertToRgbaBytes(tci.imageData.bytes); 8115 return tci; 8116 } 8117 8118 /// 8119 static Image fromMemoryImage(MemoryImage i, bool enableAlpha = false, bool premultiply = true) { 8120 auto tci = i.getAsTrueColorImage(); 8121 auto img = new Image(tci.width, tci.height, false, enableAlpha); 8122 static if(UsingSimpledisplayX11) 8123 img.premultiply = premultiply; 8124 img.setRgbaBytes(tci.imageData.bytes); 8125 return img; 8126 } 8127 8128 /// this is here for interop with arsd.image. where can be a TrueColorImage's data member 8129 /// if you pass in a buffer, it will put it right there. length must be width*height*4 already 8130 /// if you pass null, it will allocate a new one. 8131 ubyte[] getRgbaBytes(ubyte[] where = null) { 8132 if(where is null) 8133 where = new ubyte[this.width*this.height*4]; 8134 convertToRgbaBytes(where); 8135 return where; 8136 } 8137 8138 /// this is here for interop with arsd.image. from can be a TrueColorImage's data member 8139 void setRgbaBytes(in ubyte[] from ) { 8140 assert(from.length == this.width * this.height * 4); 8141 setFromRgbaBytes(from); 8142 } 8143 8144 // FIXME: make properly cross platform by getting rgba right 8145 8146 /// warning: this is not portable across platforms because the data format can change 8147 ubyte* getDataPointer() { 8148 return impl.rawData; 8149 } 8150 8151 /// for use with getDataPointer 8152 final int bytesPerLine() const pure @safe nothrow { 8153 version(Windows) 8154 return enableAlpha ? (width * 4) : (((cast(int) width * 3 + 3) / 4) * 4); 8155 else version(X11) 8156 return 4 * width; 8157 else version(OSXCocoa) 8158 return 4 * width; 8159 else static assert(0); 8160 } 8161 8162 /// for use with getDataPointer 8163 final int bytesPerPixel() const pure @safe nothrow { 8164 version(Windows) 8165 return enableAlpha ? 4 : 3; 8166 else version(X11) 8167 return 4; 8168 else version(OSXCocoa) 8169 return 4; 8170 else static assert(0); 8171 } 8172 8173 /// 8174 immutable int width; 8175 8176 /// 8177 immutable int height; 8178 8179 /// 8180 immutable bool enableAlpha; 8181 //private: 8182 mixin NativeImageImplementation!() impl; 8183 } 8184 8185 /++ 8186 A convenience function to pop up a window displaying the image. 8187 If you pass a win, it will draw the image in it. Otherwise, it will 8188 create a window with the size of the image and run its event loop, closing 8189 when a key is pressed. 8190 8191 History: 8192 `BlockingMode` parameter added on December 8, 2021. Previously, it would 8193 always block until the application quit which could cause bizarre behavior 8194 inside a more complex application. Now, the default is to block until 8195 this window closes if it is the only event loop running, and otherwise, 8196 not to block at all and just pop up the display window asynchronously. 8197 +/ 8198 void displayImage(Image image, SimpleWindow win = null, BlockingMode bm = BlockingMode.untilWindowCloses | BlockingMode.onlyIfNotNested) { 8199 if(win is null) { 8200 win = new SimpleWindow(image); 8201 { 8202 auto p = win.draw; 8203 p.drawImage(Point(0, 0), image); 8204 } 8205 win.eventLoopWithBlockingMode( 8206 bm, 0, 8207 (KeyEvent ev) { 8208 if (ev.pressed && (ev.key == Key.Escape || ev.key == Key.Space)) win.close(); 8209 } ); 8210 } else { 8211 win.image = image; 8212 } 8213 } 8214 8215 enum FontWeight : int { 8216 dontcare = 0, 8217 thin = 100, 8218 extralight = 200, 8219 light = 300, 8220 regular = 400, 8221 medium = 500, 8222 semibold = 600, 8223 bold = 700, 8224 extrabold = 800, 8225 heavy = 900 8226 } 8227 8228 /++ 8229 Interface with the common functionality for font measurements between [OperatingSystemFont] and [DrawableFont]. 8230 8231 History: 8232 Added October 24, 2022. The methods were already on [OperatingSystemFont] before that. 8233 +/ 8234 interface MeasurableFont { 8235 /++ 8236 Returns true if it is a monospace font, meaning each of the 8237 glyphs (at least the ascii characters) have matching width 8238 and no kerning, so you can determine the display width of some 8239 strings by simply multiplying the string width by [averageWidth]. 8240 8241 (Please note that multiply doesn't $(I actually) work in general, 8242 consider characters like tab and newline, but it does sometimes.) 8243 +/ 8244 bool isMonospace(); 8245 8246 /++ 8247 The average width of glyphs in the font, traditionally equal to the 8248 width of the lowercase x. Can be used to estimate bounding boxes, 8249 especially if the font [isMonospace]. 8250 8251 Given in pixels. 8252 +/ 8253 int averageWidth(); 8254 /++ 8255 The height of the bounding box of a line. 8256 +/ 8257 int height(); 8258 /++ 8259 The maximum ascent of a glyph above the baseline. 8260 8261 Given in pixels. 8262 +/ 8263 int ascent(); 8264 /++ 8265 The maximum descent of a glyph below the baseline. For example, how low the g might go. 8266 8267 Given in pixels. 8268 +/ 8269 int descent(); 8270 /++ 8271 The display width of the given string, and if you provide a window, it will use it to 8272 make the pixel count on screen more accurate too, but this shouldn't generally be necessary. 8273 8274 Given in pixels. 8275 +/ 8276 int stringWidth(scope const(char)[] s, SimpleWindow window = null); 8277 8278 } 8279 8280 // FIXME: i need a font cache and it needs to handle disconnects. 8281 8282 /++ 8283 Represents a font loaded off the operating system or the X server. 8284 8285 8286 While the api here is unified cross platform, the fonts are not necessarily 8287 available, even across machines of the same platform, so be sure to always check 8288 for null (using [isNull]) and have a fallback plan. 8289 8290 When you have a font you like, use [ScreenPainter.setFont] to load it for drawing. 8291 8292 Worst case, a null font will automatically fall back to the default font loaded 8293 for your system. 8294 +/ 8295 class OperatingSystemFont : MeasurableFont { 8296 // FIXME: when the X Connection is lost, these need to be invalidated! 8297 // that means I need to store the original stuff again to reconstruct it too. 8298 8299 version(X11) { 8300 XFontStruct* font; 8301 XFontSet fontset; 8302 8303 version(with_xft) { 8304 XftFont* xftFont; 8305 bool isXft; 8306 } 8307 } else version(Windows) { 8308 HFONT font; 8309 int width_; 8310 int height_; 8311 } else version(OSXCocoa) { 8312 NSFont font; 8313 } else static assert(0); 8314 8315 /++ 8316 Constructs the class and immediately calls [load]. 8317 +/ 8318 this(string name, int size = 0, FontWeight weight = FontWeight.dontcare, bool italic = false) { 8319 load(name, size, weight, italic); 8320 } 8321 8322 /++ 8323 Constructs the object, but does nothing. Call one of [load] or [loadDefault] to populate the object. 8324 8325 You can also call the platform-specific [loadXft], [loadCoreX], or [loadWin32] functions if appropriate for you. 8326 8327 History: 8328 Added January 24, 2021. 8329 +/ 8330 this() { 8331 // this space intentionally left blank 8332 } 8333 8334 /++ 8335 Constructs a copy of the given font object. 8336 8337 History: 8338 Added January 7, 2023. 8339 +/ 8340 this(OperatingSystemFont font) { 8341 if(font is null || font.loadedInfo is LoadedInfo.init) 8342 loadDefault(); 8343 else 8344 load(font.loadedInfo.tupleof); 8345 } 8346 8347 /++ 8348 Loads specifically with the Xft library - a freetype font from a fontconfig string. 8349 8350 History: 8351 Added November 13, 2020. 8352 +/ 8353 version(with_xft) 8354 bool loadXft(string name, int size = 0, FontWeight weight = FontWeight.dontcare, bool italic = false) { 8355 unload(); 8356 8357 if(!XftLibrary.attempted) { 8358 XftLibrary.loadDynamicLibrary(); 8359 } 8360 8361 if(!XftLibrary.loadSuccessful) 8362 return false; 8363 8364 auto display = XDisplayConnection.get; 8365 8366 char[256] nameBuffer = void; 8367 int nbp = 0; 8368 8369 void add(in char[] a) { 8370 nameBuffer[nbp .. nbp + a.length] = a[]; 8371 nbp += a.length; 8372 } 8373 add(name); 8374 8375 if(size) { 8376 add(":size="); 8377 add(toInternal!string(size)); 8378 } 8379 if(weight != FontWeight.dontcare) { 8380 add(":weight="); 8381 add(weightToString(weight)); 8382 } 8383 if(italic) 8384 add(":slant=100"); 8385 8386 nameBuffer[nbp] = 0; 8387 8388 this.xftFont = XftFontOpenName( 8389 display, 8390 DefaultScreen(display), 8391 nameBuffer.ptr 8392 ); 8393 8394 this.isXft = true; 8395 8396 if(xftFont !is null) { 8397 isMonospace_ = stringWidth("x") == stringWidth("M"); 8398 ascent_ = xftFont.ascent; 8399 descent_ = xftFont.descent; 8400 } 8401 8402 return !isNull(); 8403 } 8404 8405 /++ 8406 Lists available fonts from the system that match the given pattern, finding names that are suitable for passing to [OperatingSystemFont]'s constructor. 8407 8408 8409 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. 8410 8411 If `pattern` is null, it returns all available font families. 8412 8413 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. 8414 8415 The format of the pattern is platform-specific. 8416 8417 History: 8418 Added May 1, 2021 (dub v9.5) 8419 +/ 8420 static void listFonts(string pattern, bool delegate(in char[] name) handler) { 8421 version(Windows) { 8422 auto hdc = GetDC(null); 8423 scope(exit) ReleaseDC(null, hdc); 8424 LOGFONT logfont; 8425 static extern(Windows) int proc(const LOGFONT* lf, const TEXTMETRIC* tm, DWORD type, LPARAM p) { 8426 auto localHandler = *(cast(typeof(handler)*) p); 8427 return localHandler(lf.lfFaceName[].sliceCString) ? 1 : 0; 8428 } 8429 EnumFontFamiliesEx(hdc, &logfont, &proc, cast(LPARAM) &handler, 0); 8430 } else version(X11) { 8431 //import core.stdc.stdio; 8432 bool done = false; 8433 version(with_xft) { 8434 if(!XftLibrary.attempted) { 8435 XftLibrary.loadDynamicLibrary(); 8436 } 8437 8438 if(!XftLibrary.loadSuccessful) 8439 goto skipXft; 8440 8441 if(!FontConfigLibrary.attempted) 8442 FontConfigLibrary.loadDynamicLibrary(); 8443 if(!FontConfigLibrary.loadSuccessful) 8444 goto skipXft; 8445 8446 { 8447 auto got = XftListFonts(XDisplayConnection.get, 0, null, "family".ptr, "style".ptr, null); 8448 if(got is null) 8449 goto skipXft; 8450 scope(exit) FcFontSetDestroy(got); 8451 8452 auto fontPatterns = got.fonts[0 .. got.nfont]; 8453 foreach(candidate; fontPatterns) { 8454 char* where, whereStyle; 8455 8456 char* pmg = FcNameUnparse(candidate); 8457 8458 //FcPatternGetString(candidate, "family", 0, &where); 8459 //FcPatternGetString(candidate, "style", 0, &whereStyle); 8460 //if(where && whereStyle) { 8461 if(pmg) { 8462 if(!handler(pmg.sliceCString)) 8463 return; 8464 //printf("%s || %s %s\n", pmg, where, whereStyle); 8465 } 8466 } 8467 } 8468 } 8469 8470 skipXft: 8471 8472 if(pattern is null) 8473 pattern = "*"; 8474 8475 int count; 8476 auto coreFontsRaw = XListFonts(XDisplayConnection.get, pattern.toStringz, 10000 /* max return */, &count); 8477 scope(exit) XFreeFontNames(coreFontsRaw); 8478 8479 auto coreFonts = coreFontsRaw[0 .. count]; 8480 8481 foreach(font; coreFonts) { 8482 char[128] tmp; 8483 tmp[0 ..5] = "core:"; 8484 auto cf = font.sliceCString; 8485 if(5 + cf.length > tmp.length) 8486 assert(0, "a font name was too long, sorry i didn't bother implementing a fallback"); 8487 tmp[5 .. 5 + cf.length] = cf; 8488 if(!handler(tmp[0 .. 5 + cf.length])) 8489 return; 8490 } 8491 } 8492 } 8493 8494 /++ 8495 Returns the raw content of the ttf file, if possible. This allows you to use OperatingSystemFont 8496 to look up fonts that you then pass to things like [arsd.ttf.OpenGlLimitedFont] or [arsd.nanovega]. 8497 8498 Returns null if impossible. It is impossible if the loaded font is not a local TTF file or if the 8499 underlying system doesn't support returning the raw bytes. 8500 8501 History: 8502 Added September 10, 2021 (dub v10.3) 8503 +/ 8504 ubyte[] getTtfBytes() { 8505 if(isNull) 8506 return null; 8507 8508 version(Windows) { 8509 auto dc = GetDC(null); 8510 auto orig = SelectObject(dc, font); 8511 8512 scope(exit) { 8513 SelectObject(dc, orig); 8514 ReleaseDC(null, dc); 8515 } 8516 8517 auto res = GetFontData(dc, 0 /* whole file */, 0 /* offset */, null, 0); 8518 if(res == GDI_ERROR) 8519 return null; 8520 8521 ubyte[] buffer = new ubyte[](res); 8522 res = GetFontData(dc, 0 /* whole file */, 0 /* offset */, buffer.ptr, cast(DWORD) buffer.length); 8523 if(res == GDI_ERROR) 8524 return null; // wtf really tbh 8525 8526 return buffer; 8527 } else version(with_xft) { 8528 if(isXft && xftFont) { 8529 if(!FontConfigLibrary.attempted) 8530 FontConfigLibrary.loadDynamicLibrary(); 8531 if(!FontConfigLibrary.loadSuccessful) 8532 return null; 8533 8534 char* file; 8535 if (FcPatternGetString(xftFont.pattern, "file", 0, &file) == 0 /*FcResultMatch*/) { 8536 if (file !is null && file[0]) { 8537 import core.stdc.stdio; 8538 auto fp = fopen(file, "rb"); 8539 if(fp is null) 8540 return null; 8541 scope(exit) 8542 fclose(fp); 8543 fseek(fp, 0, SEEK_END); 8544 ubyte[] buffer = new ubyte[](ftell(fp)); 8545 fseek(fp, 0, SEEK_SET); 8546 8547 auto got = fread(buffer.ptr, 1, buffer.length, fp); 8548 if(got != buffer.length) 8549 return null; 8550 8551 return buffer; 8552 } 8553 } 8554 } 8555 return null; 8556 } else throw new NotYetImplementedException(); 8557 } 8558 8559 // see also: XftLockFace(font) which gives a FT_Face. from /usr/include/X11/Xft/Xft.h line 352 8560 8561 private string weightToString(FontWeight weight) { 8562 with(FontWeight) 8563 final switch(weight) { 8564 case dontcare: return "*"; 8565 case thin: return "extralight"; 8566 case extralight: return "extralight"; 8567 case light: return "light"; 8568 case regular: return "regular"; 8569 case medium: return "medium"; 8570 case semibold: return "demibold"; 8571 case bold: return "bold"; 8572 case extrabold: return "demibold"; 8573 case heavy: return "black"; 8574 } 8575 } 8576 8577 /++ 8578 Loads specifically a Core X font - rendered on the X server without antialiasing. Best performance. 8579 8580 History: 8581 Added November 13, 2020. Before then, this code was integrated in the [load] function. 8582 +/ 8583 version(X11) 8584 bool loadCoreX(string name, int size = 0, FontWeight weight = FontWeight.dontcare, bool italic = false) { 8585 unload(); 8586 8587 string xfontstr; 8588 8589 if(name.length > 3 && name[0 .. 3] == "-*-") { 8590 // this is kinda a disgusting hack but if the user sends an exact 8591 // string I'd like to honor it... 8592 xfontstr = name; 8593 } else { 8594 string weightstr = weightToString(weight); 8595 string sizestr; 8596 if(size == 0) 8597 sizestr = "*"; 8598 else 8599 sizestr = toInternal!string(size); 8600 xfontstr = "-*-"~name~"-"~weightstr~"-"~(italic ? "i" : "r")~"-*-*-"~sizestr~"-*-*-*-*-*-*-*\0"; 8601 } 8602 8603 // writeln(xfontstr); 8604 8605 auto display = XDisplayConnection.get; 8606 8607 font = XLoadQueryFont(display, xfontstr.ptr); 8608 if(font is null) 8609 return false; 8610 8611 char** lol; 8612 int lol2; 8613 char* lol3; 8614 fontset = XCreateFontSet(display, xfontstr.ptr, &lol, &lol2, &lol3); 8615 8616 prepareFontInfo(); 8617 8618 return !isNull(); 8619 } 8620 8621 version(X11) 8622 private void prepareFontInfo() { 8623 if(font !is null) { 8624 isMonospace_ = stringWidth("l") == stringWidth("M"); 8625 ascent_ = font.max_bounds.ascent; 8626 descent_ = font.max_bounds.descent; 8627 } 8628 } 8629 8630 version(OSXCocoa) 8631 private void prepareFontInfo() { 8632 if(font !is null) { 8633 isMonospace_ = font.isFixedPitch; 8634 ascent_ = cast(int) font.ascender; 8635 descent_ = cast(int) - font.descender; 8636 } 8637 } 8638 8639 8640 /++ 8641 Loads a Windows font. You probably want to use [load] instead to be more generic. 8642 8643 History: 8644 Added November 13, 2020. Before then, this code was integrated in the [load] function. 8645 +/ 8646 version(Windows) 8647 bool loadWin32(string name, int size = 0, FontWeight weight = FontWeight.dontcare, bool italic = false, HDC hdc = null) { 8648 unload(); 8649 8650 WCharzBuffer buffer = WCharzBuffer(name); 8651 font = CreateFont(size, 0, 0, 0, cast(int) weight, italic, 0, 0, 0, 0, 0, 0, 0, buffer.ptr); 8652 8653 prepareFontInfo(hdc); 8654 8655 return !isNull(); 8656 } 8657 8658 version(Windows) 8659 void prepareFontInfo(HDC hdc = null) { 8660 if(font is null) 8661 return; 8662 8663 TEXTMETRIC tm; 8664 auto dc = hdc ? hdc : GetDC(null); 8665 auto orig = SelectObject(dc, font); 8666 GetTextMetrics(dc, &tm); 8667 SelectObject(dc, orig); 8668 if(hdc is null) 8669 ReleaseDC(null, dc); 8670 8671 width_ = tm.tmAveCharWidth; 8672 height_ = tm.tmHeight; 8673 ascent_ = tm.tmAscent; 8674 descent_ = tm.tmDescent; 8675 // 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. 8676 isMonospace_ = (tm.tmPitchAndFamily & TMPF_FIXED_PITCH) == 0; 8677 } 8678 8679 8680 /++ 8681 `name` is a font name, but it can also be a more complicated string parsed in an OS-specific way. 8682 8683 On X, you may prefix a name with `core:` to bypass the freetype engine causing this function to forward to [loadCoreX]. Otherwise, 8684 it calls [loadXft] if the library is available. If the library or font is not available on Xft, it falls back on [loadCoreX]. 8685 8686 On Windows, it forwards directly to [loadWin32]. 8687 8688 Params: 8689 name = font name. This is looked up by the operating system and may be interpreted differently across platforms or user machines and their preferences. 8690 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. 8691 weight = approximate boldness, results may vary. 8692 italic = try to get a slanted version of the given font. 8693 8694 History: 8695 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. 8696 +/ 8697 bool load(string name, int size = 0, FontWeight weight = FontWeight.dontcare, bool italic = false) { 8698 this.loadedInfo = LoadedInfo(name, size, weight, italic); 8699 version(X11) { 8700 version(with_xft) { 8701 if(name.length > 5 && name[0 .. 5] == "core:") { 8702 goto core; 8703 } 8704 8705 if(loadXft(name, size, weight, italic)) 8706 return true; 8707 // if xft fails, fallback to core to avoid breaking 8708 // code that already depended on this. 8709 } 8710 8711 core: 8712 8713 if(name.length > 5 && name[0 .. 5] == "core:") { 8714 name = name[5 .. $]; 8715 } 8716 8717 return loadCoreX(name, size, weight, italic); 8718 } else version(Windows) { 8719 return loadWin32(name, size, weight, italic); 8720 } else version(OSXCocoa) { 8721 return loadCocoa(name, size, weight, italic); 8722 } else static assert(0); 8723 } 8724 8725 version(OSXCocoa) 8726 bool loadCocoa(string name, int size, FontWeight weight, bool italic) { 8727 unload(); 8728 8729 font = NSFont.fontWithName(MacString(name).borrow, size); // FIXME: weight and italic? 8730 prepareFontInfo(); 8731 8732 return !isNull(); 8733 } 8734 8735 private struct LoadedInfo { 8736 string name; 8737 int size; 8738 FontWeight weight; 8739 bool italic; 8740 } 8741 private LoadedInfo loadedInfo; 8742 8743 /// 8744 void unload() { 8745 if(isNull()) 8746 return; 8747 8748 version(X11) { 8749 auto display = XDisplayConnection.display; 8750 8751 if(display is null) 8752 return; 8753 8754 version(with_xft) { 8755 if(isXft) { 8756 if(xftFont) 8757 XftFontClose(display, xftFont); 8758 isXft = false; 8759 xftFont = null; 8760 return; 8761 } 8762 } 8763 8764 if(font && font !is ScreenPainterImplementation.defaultfont) 8765 XFreeFont(display, font); 8766 if(fontset && fontset !is ScreenPainterImplementation.defaultfontset) 8767 XFreeFontSet(display, fontset); 8768 8769 font = null; 8770 fontset = null; 8771 } else version(Windows) { 8772 DeleteObject(font); 8773 font = null; 8774 } else version(OSXCocoa) { 8775 font.release(); 8776 font = null; 8777 } else static assert(0); 8778 } 8779 8780 private bool isMonospace_; 8781 8782 /++ 8783 History: 8784 Added January 16, 2021 8785 +/ 8786 bool isMonospace() { 8787 return isMonospace_; 8788 } 8789 8790 /++ 8791 Returns the average width of the font, conventionally defined as the width of the lowercase 'x' character. 8792 8793 History: 8794 Added March 26, 2020 8795 Documented January 16, 2021 8796 +/ 8797 int averageWidth() { 8798 version(X11) { 8799 return stringWidth("x"); 8800 } version(OSXCocoa) { 8801 return stringWidth("x"); 8802 } else version(Windows) 8803 return width_; 8804 else assert(0); 8805 } 8806 8807 /++ 8808 Returns the width of the string as drawn on the specified window, or the default screen if the window is null. 8809 8810 History: 8811 Added January 16, 2021 8812 +/ 8813 int stringWidth(scope const(char)[] s, SimpleWindow window = null) { 8814 // FIXME: what about tab? 8815 if(isNull) 8816 return 0; 8817 8818 version(X11) { 8819 version(with_xft) 8820 if(isXft && xftFont !is null) { 8821 //return xftFont.max_advance_width; 8822 XGlyphInfo extents; 8823 XftTextExtentsUtf8(XDisplayConnection.get, xftFont, s.ptr, cast(int) s.length, &extents); 8824 // writeln(extents); 8825 return extents.xOff; 8826 } 8827 if(font is null) 8828 return 0; 8829 else if(fontset) { 8830 XRectangle rect; 8831 Xutf8TextExtents(fontset, s.ptr, cast(int) s.length, null, &rect); 8832 8833 return rect.width; 8834 } else { 8835 return XTextWidth(font, s.ptr, cast(int) s.length); 8836 } 8837 } else version(Windows) { 8838 WCharzBuffer buffer = WCharzBuffer(s); 8839 8840 return stringWidth(buffer.slice, window); 8841 } else version(OSXCocoa) { 8842 /+ 8843 int charCount = [string length]; 8844 CGGlyph glyphs[charCount]; 8845 CGRect rects[charCount]; 8846 8847 CTFontGetGlyphsForCharacters(theCTFont, (const unichar*)[string cStringUsingEncoding:NSUnicodeStringEncoding], glyphs, charCount); 8848 CTFontGetBoundingRectsForGlyphs(theCTFont, kCTFontDefaultOrientation, glyphs, rects, charCount); 8849 8850 int totalwidth = 0, maxheight = 0; 8851 for (int i=0; i < charCount; i++) 8852 { 8853 totalwidth += rects[i].size.width; 8854 maxheight = maxheight < rects[i].size.height ? rects[i].size.height : maxheight; 8855 } 8856 8857 dim = CGSizeMake(totalwidth, maxheight); 8858 +/ 8859 8860 return 16; // FIXME 8861 } 8862 else assert(0); 8863 } 8864 8865 version(Windows) 8866 /// ditto 8867 int stringWidth(scope const(wchar)[] s, SimpleWindow window = null) { 8868 if(isNull) 8869 return 0; 8870 version(Windows) { 8871 SIZE size; 8872 8873 prepareContext(window); 8874 scope(exit) releaseContext(); 8875 8876 GetTextExtentPoint32W(dc, s.ptr, cast(int) s.length, &size); 8877 8878 return size.cx; 8879 } else { 8880 // std.conv can do this easily but it is slow to import and i don't think it is worth it 8881 static assert(0, "not implemented yet"); 8882 //return stringWidth(s, window); 8883 } 8884 } 8885 8886 private { 8887 int prepRefcount; 8888 8889 version(Windows) { 8890 HDC dc; 8891 HANDLE orig; 8892 HWND hwnd; 8893 } 8894 } 8895 /++ 8896 [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. 8897 8898 History: 8899 Added January 23, 2021 8900 +/ 8901 void prepareContext(SimpleWindow window = null) { 8902 prepRefcount++; 8903 if(prepRefcount == 1) { 8904 version(Windows) { 8905 hwnd = window is null ? null : window.impl.hwnd; 8906 dc = GetDC(hwnd); 8907 orig = SelectObject(dc, font); 8908 } 8909 } 8910 } 8911 /// ditto 8912 void releaseContext() { 8913 prepRefcount--; 8914 if(prepRefcount == 0) { 8915 version(Windows) { 8916 SelectObject(dc, orig); 8917 ReleaseDC(hwnd, dc); 8918 hwnd = null; 8919 dc = null; 8920 orig = null; 8921 } 8922 } 8923 } 8924 8925 /+ 8926 FIXME: I think I need advance and kerning pair 8927 8928 int advance(dchar from, dchar to) { } // use dchar.init for first item in string 8929 +/ 8930 8931 /++ 8932 Returns the height of the font. 8933 8934 History: 8935 Added March 26, 2020 8936 Documented January 16, 2021 8937 +/ 8938 int height() { 8939 version(X11) { 8940 version(with_xft) 8941 if(isXft && xftFont !is null) { 8942 return xftFont.ascent + xftFont.descent; // i don't use height here because it doesn't include the baseline pixel 8943 } 8944 if(font is null) 8945 return 0; 8946 return font.max_bounds.ascent + font.max_bounds.descent; 8947 } else version(Windows) { 8948 return height_; 8949 } else version(OSXCocoa) { 8950 if(font is null) 8951 return 0; 8952 return cast(int) (font.ascender + font.descender + 0.9 /* to round up */); // font.capHeight 8953 } 8954 else assert(0); 8955 } 8956 8957 private int ascent_; 8958 private int descent_; 8959 8960 /++ 8961 Max ascent above the baseline. 8962 8963 History: 8964 Added January 22, 2021 8965 +/ 8966 int ascent() { 8967 return ascent_; 8968 } 8969 8970 /++ 8971 Max descent below the baseline. 8972 8973 History: 8974 Added January 22, 2021 8975 +/ 8976 int descent() { 8977 return descent_; 8978 } 8979 8980 /++ 8981 Loads the default font used by [ScreenPainter] if none others are loaded. 8982 8983 Returns: 8984 This method mutates the `this` object, but then returns `this` for 8985 easy chaining like: 8986 8987 --- 8988 auto font = foo.isNull ? foo : foo.loadDefault 8989 --- 8990 8991 History: 8992 Added previously, but left unimplemented until January 24, 2021. 8993 +/ 8994 OperatingSystemFont loadDefault() { 8995 unload(); 8996 8997 loadedInfo = LoadedInfo.init; 8998 8999 version(X11) { 9000 // another option would be https://tronche.com/gui/x/xlib/graphics/font-metrics/XQueryFont.html 9001 // but meh since sdpy does its own thing, this should be ok too 9002 9003 ScreenPainterImplementation.ensureDefaultFontLoaded(); 9004 this.font = ScreenPainterImplementation.defaultfont; 9005 this.fontset = ScreenPainterImplementation.defaultfontset; 9006 9007 prepareFontInfo(); 9008 return this; 9009 } else version(Windows) { 9010 ScreenPainterImplementation.ensureDefaultFontLoaded(); 9011 this.font = ScreenPainterImplementation.defaultGuiFont; 9012 9013 prepareFontInfo(); 9014 return this; 9015 } else version(OSXCocoa) { 9016 this.font = NSFont.systemFontOfSize(15); 9017 9018 prepareFontInfo(); 9019 9020 // import std.stdio; writeln("Load default: ", this.height()); 9021 return this; 9022 } else throw new NotYetImplementedException(); 9023 } 9024 9025 /// 9026 bool isNull() { 9027 version(with_xft) 9028 if(isXft) 9029 return xftFont is null; 9030 return font is null; 9031 } 9032 9033 /* Metrics */ 9034 /+ 9035 GetABCWidth 9036 GetKerningPairs 9037 9038 if I do it right, I can size it all here, and match 9039 what happens when I draw the full string with the OS functions. 9040 9041 subclasses might do the same thing while getting the glyphs on images 9042 struct GlyphInfo { 9043 int glyph; 9044 9045 size_t stringIdxStart; 9046 size_t stringIdxEnd; 9047 9048 Rectangle boundingBox; 9049 } 9050 GlyphInfo[] getCharBoxes() { 9051 // XftTextExtentsUtf8 9052 return null; 9053 9054 } 9055 +/ 9056 9057 ~this() { 9058 if(!thisIsGuiThread) return; // FIXME: leaks if multithreaded gc 9059 unload(); 9060 } 9061 } 9062 9063 version(Windows) 9064 private string sliceCString(const(wchar)[] w) { 9065 return makeUtf8StringFromWindowsString(cast(wchar*) w.ptr); 9066 } 9067 9068 private inout(char)[] sliceCString(inout(char)* s) { 9069 import core.stdc.string; 9070 auto len = strlen(s); 9071 return s[0 .. len]; 9072 } 9073 9074 version(OSXCocoa) 9075 alias PaintingHandle = NSObject; 9076 else 9077 alias PaintingHandle = NativeWindowHandle; 9078 9079 /** 9080 The 2D drawing proxy. You acquire one of these with [SimpleWindow.draw] rather 9081 than constructing it directly. Then, it is reference counted so you can pass it 9082 at around and when the last ref goes out of scope, the buffered drawing activities 9083 are all carried out. 9084 9085 9086 Most functions use the outlineColor instead of taking a color themselves. 9087 ScreenPainter is reference counted and draws its buffer to the screen when its 9088 final reference goes out of scope. 9089 */ 9090 struct ScreenPainter { 9091 CapableOfBeingDrawnUpon window; 9092 this(CapableOfBeingDrawnUpon window, PaintingHandle handle, bool manualInvalidations) { 9093 this.window = window; 9094 if(window.closed) 9095 return; // null painter is now allowed so no need to throw anymore, this likely happens at the end of a program anyway 9096 //currentClipRectangle = arsd.color.Rectangle(0, 0, window.width, window.height); 9097 currentClipRectangle = arsd.color.Rectangle(short.min, short.min, short.max, short.max); 9098 if(window.activeScreenPainter !is null) { 9099 impl = window.activeScreenPainter; 9100 if(impl.referenceCount == 0) { 9101 impl.window = window; 9102 impl.create(handle); 9103 } 9104 impl.manualInvalidations = manualInvalidations; 9105 impl.referenceCount++; 9106 // writeln("refcount ++ ", impl.referenceCount); 9107 } else { 9108 impl = new ScreenPainterImplementation; 9109 impl.window = window; 9110 impl.create(handle); 9111 impl.referenceCount = 1; 9112 impl.manualInvalidations = manualInvalidations; 9113 window.activeScreenPainter = impl; 9114 // writeln("constructed"); 9115 } 9116 9117 copyActiveOriginals(); 9118 } 9119 9120 /++ 9121 EXPERIMENTAL. subject to change. 9122 9123 When you draw a cursor, you can draw this to notify your window of where it is, 9124 for IME systems to use. 9125 +/ 9126 void notifyCursorPosition(int x, int y, int width, int height) { 9127 if(auto w = cast(SimpleWindow) window) { 9128 w.setIMEPopupLocation(x + _originX + width, y + _originY + height); 9129 } 9130 } 9131 9132 /++ 9133 If you are using manual invalidations, this informs the 9134 window system that a section needs to be redrawn. 9135 9136 If you didn't opt into manual invalidation, you don't 9137 have to call this. 9138 9139 History: 9140 Added December 30, 2021 (dub v10.5) 9141 +/ 9142 void invalidateRect(Rectangle rect) { 9143 if(impl is null) return; 9144 9145 // transform(rect) 9146 rect.left += _originX; 9147 rect.right += _originX; 9148 rect.top += _originY; 9149 rect.bottom += _originY; 9150 9151 impl.invalidateRect(rect); 9152 } 9153 9154 private Pen originalPen; 9155 private Color originalFillColor; 9156 private arsd.color.Rectangle originalClipRectangle; 9157 private OperatingSystemFont originalFont; 9158 void copyActiveOriginals() { 9159 if(impl is null) return; 9160 originalPen = impl._activePen; 9161 originalFillColor = impl._fillColor; 9162 originalClipRectangle = impl._clipRectangle; 9163 version(OSXCocoa) {} else 9164 originalFont = impl._activeFont; 9165 } 9166 9167 ~this() { 9168 if(impl is null) return; 9169 impl.referenceCount--; 9170 //writeln("refcount -- ", impl.referenceCount); 9171 if(impl.referenceCount == 0) { 9172 // writeln("destructed"); 9173 impl.dispose(); 9174 *window.activeScreenPainter = ScreenPainterImplementation.init; 9175 // writeln("paint finished"); 9176 } else { 9177 // there is still an active reference, reset stuff so the 9178 // next user doesn't get weirdness via the reference 9179 this.rasterOp = RasterOp.normal; 9180 pen = originalPen; 9181 fillColor = originalFillColor; 9182 if(originalFont) 9183 setFont(originalFont); 9184 impl.setClipRectangle(originalClipRectangle.left, originalClipRectangle.top, originalClipRectangle.width, originalClipRectangle.height); 9185 } 9186 } 9187 9188 this(this) { 9189 if(impl is null) return; 9190 impl.referenceCount++; 9191 //writeln("refcount ++ ", impl.referenceCount); 9192 9193 copyActiveOriginals(); 9194 } 9195 9196 private int _originX; 9197 private int _originY; 9198 @property int originX() { return _originX; } 9199 @property int originY() { return _originY; } 9200 @property int originX(int a) { 9201 _originX = a; 9202 return _originX; 9203 } 9204 @property int originY(int a) { 9205 _originY = a; 9206 return _originY; 9207 } 9208 arsd.color.Rectangle currentClipRectangle; // set BEFORE doing any transformations 9209 private void transform(ref Point p) { 9210 if(impl is null) return; 9211 p.x += _originX; 9212 p.y += _originY; 9213 } 9214 9215 // this needs to be checked BEFORE the originX/Y transformation 9216 private bool isClipped(Point p) { 9217 return !currentClipRectangle.contains(p); 9218 } 9219 private bool isClipped(Point p, int width, int height) { 9220 return !currentClipRectangle.overlaps(arsd.color.Rectangle(p, Size(width + 1, height + 1))); 9221 } 9222 private bool isClipped(Point p, Size s) { 9223 return !currentClipRectangle.overlaps(arsd.color.Rectangle(p, Size(s.width + 1, s.height + 1))); 9224 } 9225 private bool isClipped(Point p, Point p2) { 9226 // need to ensure the end points are actually included inside, so the +1 does that 9227 return !currentClipRectangle.overlaps(arsd.color.Rectangle(p, p2 + Point(1, 1))); 9228 } 9229 9230 9231 /++ 9232 Sets the clipping region for drawing. If width == 0 && height == 0, disabled clipping. 9233 9234 Returns: 9235 The old clip rectangle. 9236 9237 History: 9238 Return value was `void` prior to May 10, 2021. 9239 9240 +/ 9241 arsd.color.Rectangle setClipRectangle(Point pt, int width, int height) { 9242 if(impl is null) return currentClipRectangle; 9243 if(pt == currentClipRectangle.upperLeft && width == currentClipRectangle.width && height == currentClipRectangle.height) 9244 return currentClipRectangle; // no need to do anything 9245 auto old = currentClipRectangle; 9246 currentClipRectangle = arsd.color.Rectangle(pt, Size(width, height)); 9247 transform(pt); 9248 9249 impl.setClipRectangle(pt.x, pt.y, width, height); 9250 9251 return old; 9252 } 9253 9254 /// ditto 9255 arsd.color.Rectangle setClipRectangle(arsd.color.Rectangle rect) { 9256 if(impl is null) return currentClipRectangle; 9257 return setClipRectangle(rect.upperLeft, rect.width, rect.height); 9258 } 9259 9260 /// 9261 void setFont(OperatingSystemFont font) { 9262 if(impl is null) return; 9263 impl.setFont(font); 9264 } 9265 9266 /// 9267 int fontHeight() { 9268 if(impl is null) return 0; 9269 return impl.fontHeight(); 9270 } 9271 9272 private Pen activePen; 9273 9274 /// 9275 @property void pen(Pen p) { 9276 if(impl is null) return; 9277 activePen = p; 9278 impl.pen(p); 9279 } 9280 9281 /// 9282 @scriptable 9283 @property void outlineColor(Color c) { 9284 if(impl is null) return; 9285 if(activePen.color == c) 9286 return; 9287 activePen.color = c; 9288 impl.pen(activePen); 9289 } 9290 9291 /// 9292 @scriptable 9293 @property void fillColor(Color c) { 9294 if(impl is null) return; 9295 impl.fillColor(c); 9296 } 9297 9298 /// 9299 @property void rasterOp(RasterOp op) { 9300 if(impl is null) return; 9301 impl.rasterOp(op); 9302 } 9303 9304 9305 void updateDisplay() { 9306 // FIXME this should do what the dtor does 9307 } 9308 9309 /// 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) 9310 void scrollArea(Point upperLeft, int width, int height, int dx, int dy) { 9311 if(impl is null) return; 9312 if(isClipped(upperLeft, width, height)) return; 9313 transform(upperLeft); 9314 version(Windows) { 9315 // http://msdn.microsoft.com/en-us/library/windows/desktop/bb787589%28v=vs.85%29.aspx 9316 RECT scroll = RECT(upperLeft.x, upperLeft.y, upperLeft.x + width, upperLeft.y + height); 9317 RECT clip = scroll; 9318 RECT uncovered; 9319 HRGN hrgn; 9320 if(!ScrollDC(impl.hdc, -dx, -dy, &scroll, &clip, hrgn, &uncovered)) 9321 throw new WindowsApiException("ScrollDC", GetLastError()); 9322 9323 } else version(X11) { 9324 // FIXME: clip stuff outside this rectangle 9325 XCopyArea(impl.display, impl.d, impl.d, impl.gc, upperLeft.x, upperLeft.y, width, height, upperLeft.x - dx, upperLeft.y - dy); 9326 } else version(OSXCocoa) { 9327 throw new NotYetImplementedException(); 9328 } else static assert(0); 9329 } 9330 9331 /// 9332 void clear(Color color = Color.white()) { 9333 if(impl is null) return; 9334 fillColor = color; 9335 outlineColor = color; 9336 drawRectangle(Point(0, 0), window.width, window.height); 9337 } 9338 9339 /++ 9340 Draws a pixmap (represented by the [Sprite] class) on the drawable. 9341 9342 Params: 9343 upperLeft = point on the window where the upper left corner of the image will be drawn 9344 imageUpperLeft = point on the image to start the slice to draw 9345 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. 9346 History: 9347 The `imageUpperLeft` and `sliceSize` parameters were added on March 11, 2021 (dub v9.3.0) 9348 +/ 9349 void drawPixmap(Sprite s, Point upperLeft, Point imageUpperLeft = Point(0, 0), Size sliceSize = Size(0, 0)) { 9350 if(impl is null) return; 9351 if(isClipped(upperLeft, s.width, s.height)) return; 9352 transform(upperLeft); 9353 impl.drawPixmap(s, upperLeft.x, upperLeft.y, imageUpperLeft.x, imageUpperLeft.y, sliceSize.width, sliceSize.height); 9354 } 9355 9356 /// 9357 void drawImage(Point upperLeft, Image i, Point upperLeftOfImage = Point(0, 0), int w = 0, int h = 0) { 9358 if(impl is null) return; 9359 //if(isClipped(upperLeft, w, h)) return; // FIXME 9360 transform(upperLeft); 9361 if(w == 0 || w > i.width) 9362 w = i.width; 9363 if(h == 0 || h > i.height) 9364 h = i.height; 9365 if(upperLeftOfImage.x < 0) 9366 upperLeftOfImage.x = 0; 9367 if(upperLeftOfImage.y < 0) 9368 upperLeftOfImage.y = 0; 9369 9370 impl.drawImage(upperLeft.x, upperLeft.y, i, upperLeftOfImage.x, upperLeftOfImage.y, w, h); 9371 } 9372 9373 /// 9374 Size textSize(in char[] text) { 9375 if(impl is null) return Size(0, 0); 9376 return impl.textSize(text); 9377 } 9378 9379 /++ 9380 Draws a string in the window with the set font (see [setFont] to change it). 9381 9382 Params: 9383 upperLeft = the upper left point of the bounding box of the text 9384 text = the string to draw 9385 lowerRight = the lower right point of the bounding box of the text. If 0, 0, there is no lower right bound. 9386 alignment = A [arsd.docs.general_concepts#bitflags|combination] of [TextAlignment] flags 9387 +/ 9388 @scriptable 9389 void drawText(Point upperLeft, in char[] text, Point lowerRight = Point(0, 0), uint alignment = 0) { 9390 if(impl is null) return; 9391 if(lowerRight.x != 0 || lowerRight.y != 0) { 9392 if(isClipped(upperLeft, lowerRight)) return; 9393 transform(lowerRight); 9394 } else { 9395 if(isClipped(upperLeft, textSize(text))) return; 9396 } 9397 transform(upperLeft); 9398 impl.drawText(upperLeft.x, upperLeft.y, lowerRight.x, lowerRight.y, text, alignment); 9399 } 9400 9401 /++ 9402 Draws text using a custom font. 9403 9404 This is still MAJOR work in progress. 9405 9406 Creating a [DrawableFont] can be tricky and require additional dependencies. 9407 +/ 9408 void drawText(DrawableFont font, Point upperLeft, in char[] text) { 9409 if(impl is null) return; 9410 if(isClipped(upperLeft, Point(int.max, int.max))) return; 9411 transform(upperLeft); 9412 font.drawString(this, upperLeft, text); 9413 } 9414 9415 version(Windows) 9416 void drawText(Point upperLeft, scope const(wchar)[] text) { 9417 if(impl is null) return; 9418 if(isClipped(upperLeft, Point(int.max, int.max))) return; 9419 transform(upperLeft); 9420 9421 if(text.length && text[$-1] == '\n') 9422 text = text[0 .. $-1]; // tailing newlines are weird on windows... 9423 9424 TextOutW(impl.hdc, upperLeft.x, upperLeft.y, text.ptr, cast(int) text.length); 9425 } 9426 9427 static struct TextDrawingContext { 9428 Point boundingBoxUpperLeft; 9429 Point boundingBoxLowerRight; 9430 9431 Point currentLocation; 9432 9433 Point lastDrewUpperLeft; 9434 Point lastDrewLowerRight; 9435 9436 // how do i do right aligned rich text? 9437 // i kinda want to do a pre-made drawing then right align 9438 // draw the whole block. 9439 // 9440 // That's exactly the diff: inline vs block stuff. 9441 9442 // I need to get coordinates of an inline section out too, 9443 // not just a bounding box, but a series of bounding boxes 9444 // should be ok. Consider what's needed to detect a click 9445 // on a link in the middle of a paragraph breaking a line. 9446 // 9447 // Generally, we should be able to get the rectangles of 9448 // any portion we draw. 9449 // 9450 // It also needs to tell what text is left if it overflows 9451 // out of the box, so we can do stuff like float images around 9452 // it. It should not attempt to draw a letter that would be 9453 // clipped. 9454 // 9455 // I might also turn off word wrap stuff. 9456 } 9457 9458 void drawText(TextDrawingContext context, in char[] text, uint alignment = 0) { 9459 if(impl is null) return; 9460 // FIXME 9461 } 9462 9463 /// Drawing an individual pixel is slow. Avoid it if possible. 9464 void drawPixel(Point where) { 9465 if(impl is null) return; 9466 if(isClipped(where)) return; 9467 transform(where); 9468 impl.drawPixel(where.x, where.y); 9469 } 9470 9471 9472 /// Draws a pen using the current pen / outlineColor 9473 @scriptable 9474 void drawLine(Point starting, Point ending) { 9475 if(impl is null) return; 9476 if(isClipped(starting, ending)) return; 9477 transform(starting); 9478 transform(ending); 9479 impl.drawLine(starting.x, starting.y, ending.x, ending.y); 9480 } 9481 9482 /// Draws a rectangle using the current pen/outline color for the border and brush/fill color for the insides 9483 /// The outer lines, inclusive of x = 0, y = 0, x = width - 1, and y = height - 1 are drawn with the outlineColor 9484 /// The rest of the pixels are drawn with the fillColor. If fillColor is transparent, those pixels are not drawn. 9485 @scriptable 9486 void drawRectangle(Point upperLeft, int width, int height) { 9487 if(impl is null) return; 9488 if(isClipped(upperLeft, width, height)) return; 9489 transform(upperLeft); 9490 impl.drawRectangle(upperLeft.x, upperLeft.y, width, height); 9491 } 9492 9493 /// ditto 9494 void drawRectangle(Point upperLeft, Size size) { 9495 if(impl is null) return; 9496 if(isClipped(upperLeft, size.width, size.height)) return; 9497 transform(upperLeft); 9498 impl.drawRectangle(upperLeft.x, upperLeft.y, size.width, size.height); 9499 } 9500 9501 /// ditto 9502 void drawRectangle(Point upperLeft, Point lowerRightInclusive) { 9503 if(impl is null) return; 9504 if(isClipped(upperLeft, lowerRightInclusive + Point(1, 1))) return; 9505 transform(upperLeft); 9506 transform(lowerRightInclusive); 9507 impl.drawRectangle(upperLeft.x, upperLeft.y, 9508 lowerRightInclusive.x - upperLeft.x + 1, lowerRightInclusive.y - upperLeft.y + 1); 9509 } 9510 9511 // overload added on May 12, 2021 9512 /// ditto 9513 void drawRectangle(Rectangle rect) { 9514 drawRectangle(rect.upperLeft, rect.size); 9515 } 9516 9517 /// Arguments are the points of the bounding rectangle 9518 void drawEllipse(Point upperLeft, Point lowerRight) { 9519 if(impl is null) return; 9520 if(isClipped(upperLeft, lowerRight)) return; 9521 transform(upperLeft); 9522 transform(lowerRight); 9523 impl.drawEllipse(upperLeft.x, upperLeft.y, lowerRight.x, lowerRight.y); 9524 } 9525 9526 /++ 9527 start and finish are units of degrees * 64 9528 9529 History: 9530 The Windows implementation didn't match the Linux implementation until September 24, 2021. 9531 9532 They still don't exactly match in outlining the arc with straight lines (Windows does, Linux doesn't for now). 9533 +/ 9534 void drawArc(Point upperLeft, int width, int height, int start, int finish) { 9535 if(impl is null) return; 9536 // FIXME: not actually implemented 9537 if(isClipped(upperLeft, width, height)) return; 9538 transform(upperLeft); 9539 impl.drawArc(upperLeft.x, upperLeft.y, width, height, start, finish); 9540 } 9541 9542 /// this function draws a circle with the drawEllipse() function above, it requires the upper left point and the radius 9543 void drawCircle(Point upperLeft, int diameter) { 9544 drawEllipse(upperLeft, Point(upperLeft.x + diameter, upperLeft.y + diameter)); 9545 } 9546 9547 /// . 9548 void drawPolygon(Point[] vertexes) { 9549 if(impl is null) return; 9550 assert(vertexes.length); 9551 int minX = int.max, minY = int.max, maxX = int.min, maxY = int.min; 9552 foreach(ref vertex; vertexes) { 9553 if(vertex.x < minX) 9554 minX = vertex.x; 9555 if(vertex.y < minY) 9556 minY = vertex.y; 9557 if(vertex.x > maxX) 9558 maxX = vertex.x; 9559 if(vertex.y > maxY) 9560 maxY = vertex.y; 9561 transform(vertex); 9562 } 9563 if(isClipped(Point(minX, maxY), Point(maxX + 1, maxY + 1))) return; 9564 impl.drawPolygon(vertexes); 9565 } 9566 9567 /// ditto 9568 void drawPolygon(Point[] vertexes...) { 9569 if(impl is null) return; 9570 drawPolygon(vertexes); 9571 } 9572 9573 9574 // and do a draw/fill in a single call maybe. Windows can do it... but X can't, though it could do two calls. 9575 9576 //mixin NativeScreenPainterImplementation!() impl; 9577 9578 9579 // HACK: if I mixin the impl directly, it won't let me override the copy 9580 // constructor! The linker complains about there being multiple definitions. 9581 // I'll make the best of it and reference count it though. 9582 ScreenPainterImplementation* impl; 9583 } 9584 9585 // HACK: I need a pointer to the implementation so it's separate 9586 struct ScreenPainterImplementation { 9587 CapableOfBeingDrawnUpon window; 9588 int referenceCount; 9589 mixin NativeScreenPainterImplementation!(); 9590 } 9591 9592 // FIXME: i haven't actually tested the sprite class on MS Windows 9593 9594 /** 9595 Sprites are optimized for fast drawing on the screen, but slow for direct pixel 9596 access. They are best for drawing a relatively unchanging image repeatedly on the screen. 9597 9598 9599 On X11, this corresponds to an `XPixmap`. On Windows, it still uses a bitmap, 9600 though I'm not sure that's ideal and the implementation might change. 9601 9602 You create one by giving a window and an image. It optimizes for that window, 9603 and copies the image into it to use as the initial picture. Creating a sprite 9604 can be quite slow (especially over a network connection) so you should do it 9605 as little as possible and just hold on to your sprite handles after making them. 9606 simpledisplay does try to do its best though, using the XSHM extension if available, 9607 but you should still write your code as if it will always be slow. 9608 9609 Then you can use `sprite.drawAt(painter, point);` to draw it, which should be 9610 a fast operation - much faster than drawing the Image itself every time. 9611 9612 `Sprite` represents a scarce resource which should be freed when you 9613 are done with it. Use the `dispose` method to do this. Do not use a `Sprite` 9614 after it has been disposed. If you are unsure about this, don't take chances, 9615 just let the garbage collector do it for you. But ideally, you can manage its 9616 lifetime more efficiently. 9617 9618 $(NOTE `Sprite`, like the rest of simpledisplay's `ScreenPainter`, does not 9619 support alpha blending in its drawing at this time. That might change in the 9620 future, but if you need alpha blending right now, use OpenGL instead. See 9621 `gamehelpers.d` for a similar class to `Sprite` that uses OpenGL: `OpenGlTexture`.) 9622 9623 Update: on April 23, 2021, I finally added alpha blending support. You must opt 9624 in by setting the enableAlpha = true in the constructor. 9625 */ 9626 class Sprite : CapableOfBeingDrawnUpon { 9627 9628 /// 9629 ScreenPainter draw() { 9630 return ScreenPainter(this, handle, false); 9631 } 9632 9633 /++ 9634 Copies the sprite's current state into a [TrueColorImage]. 9635 9636 Be warned: this can be a very slow operation 9637 9638 History: 9639 Actually implemented on March 14, 2021 9640 +/ 9641 TrueColorImage takeScreenshot() { 9642 return trueColorImageFromNativeHandle(handle, width, height); 9643 } 9644 9645 void delegate() paintingFinishedDg() { return null; } 9646 bool closed() { return false; } 9647 ScreenPainterImplementation* activeScreenPainter_; 9648 protected ScreenPainterImplementation* activeScreenPainter() { return activeScreenPainter_; } 9649 protected void activeScreenPainter(ScreenPainterImplementation* i) { activeScreenPainter_ = i; } 9650 9651 version(Windows) 9652 private ubyte* rawData; 9653 // FIXME: sprites are lost when disconnecting from X! We need some way to invalidate them... 9654 // ditto on the XPicture stuff 9655 9656 version(X11) { 9657 private static XRenderPictFormat* RGB24; 9658 private static XRenderPictFormat* ARGB32; 9659 9660 private Picture xrenderPicture; 9661 } 9662 9663 version(X11) 9664 private static void requireXRender() { 9665 if(!XRenderLibrary.loadAttempted) { 9666 XRenderLibrary.loadDynamicLibrary(); 9667 } 9668 9669 if(!XRenderLibrary.loadSuccessful) 9670 throw new Exception("XRender library load failure"); 9671 9672 auto display = XDisplayConnection.get; 9673 9674 // FIXME: if we migrate X displays, these need to be changed 9675 if(RGB24 is null) 9676 RGB24 = XRenderFindStandardFormat(display, PictStandardRGB24); 9677 if(ARGB32 is null) 9678 ARGB32 = XRenderFindStandardFormat(display, PictStandardARGB32); 9679 } 9680 9681 protected this() {} 9682 9683 this(SimpleWindow win, int width, int height, bool enableAlpha = false) { 9684 this._width = width; 9685 this._height = height; 9686 this.enableAlpha = enableAlpha; 9687 9688 version(X11) { 9689 auto display = XDisplayConnection.get(); 9690 9691 if(enableAlpha) { 9692 requireXRender(); 9693 } 9694 9695 handle = XCreatePixmap(display, cast(Drawable) win.window, width, height, enableAlpha ? 32 : DefaultDepthOfDisplay(display)); 9696 9697 if(enableAlpha) { 9698 XRenderPictureAttributes attrs; 9699 xrenderPicture = XRenderCreatePicture(display, handle, ARGB32, 0, &attrs); 9700 } 9701 } else version(Windows) { 9702 version(CRuntime_DigitalMars) { 9703 //if(enableAlpha) 9704 //throw new Exception("Alpha support not available, try recompiling with -m32mscoff"); 9705 } 9706 9707 BITMAPINFO infoheader; 9708 infoheader.bmiHeader.biSize = infoheader.bmiHeader.sizeof; 9709 infoheader.bmiHeader.biWidth = width; 9710 infoheader.bmiHeader.biHeight = height; 9711 infoheader.bmiHeader.biPlanes = 1; 9712 infoheader.bmiHeader.biBitCount = enableAlpha ? 32 : 24; 9713 infoheader.bmiHeader.biCompression = BI_RGB; 9714 9715 // FIXME: this should prolly be a device dependent bitmap... 9716 handle = CreateDIBSection( 9717 null, 9718 &infoheader, 9719 DIB_RGB_COLORS, 9720 cast(void**) &rawData, 9721 null, 9722 0); 9723 9724 if(handle is null) 9725 throw new WindowsApiException("couldn't create pixmap", GetLastError()); 9726 } 9727 } 9728 9729 /// Makes a sprite based on the image with the initial contents from the Image 9730 this(SimpleWindow win, Image i) { 9731 this(win, i.width, i.height, i.enableAlpha); 9732 9733 version(X11) { 9734 auto display = XDisplayConnection.get(); 9735 auto gc = XCreateGC(display, this.handle, 0, null); 9736 scope(exit) XFreeGC(display, gc); 9737 if(i.usingXshm) 9738 XShmPutImage(display, cast(Drawable) handle, gc, i.handle, 0, 0, 0, 0, i.width, i.height, false); 9739 else 9740 XPutImage(display, cast(Drawable) handle, gc, i.handle, 0, 0, 0, 0, i.width, i.height); 9741 } else version(Windows) { 9742 auto itemsPerLine = enableAlpha ? (4 * width) : (((cast(int) width * 3 + 3) / 4) * 4); 9743 auto arrLength = itemsPerLine * height; 9744 rawData[0..arrLength] = i.rawData[0..arrLength]; 9745 } else version(OSXCocoa) { 9746 // FIXME: I have no idea if this is even any good 9747 auto colorSpace = CGColorSpaceCreateDeviceRGB(); 9748 handle = CGBitmapContextCreate(null, width, height, 8, 4*width, 9749 colorSpace, 9750 kCGImageAlphaPremultipliedLast 9751 |kCGBitmapByteOrder32Big); 9752 CGColorSpaceRelease(colorSpace); 9753 auto rawData = CGBitmapContextGetData(handle); 9754 9755 auto rdl = (width * height * 4); 9756 rawData[0 .. rdl] = i.rawData[0 .. rdl]; 9757 } else static assert(0); 9758 } 9759 9760 /++ 9761 Draws the image on the specified painter at the specified point. The point is the upper-left point where the image will be drawn. 9762 9763 Params: 9764 where = point on the window where the upper left corner of the image will be drawn 9765 imageUpperLeft = point on the image to start the slice to draw 9766 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. 9767 History: 9768 The `imageUpperLeft` and `sliceSize` parameters were added on March 11, 2021 (dub v9.3.0) 9769 +/ 9770 void drawAt(ScreenPainter painter, Point where, Point imageUpperLeft = Point(0, 0), Size sliceSize = Size(0, 0)) { 9771 painter.drawPixmap(this, where, imageUpperLeft, sliceSize); 9772 } 9773 9774 /// Call this when you're ready to get rid of it 9775 void dispose() { 9776 version(X11) { 9777 staticDispose(xrenderPicture, handle); 9778 xrenderPicture = None; 9779 handle = None; 9780 } else version(Windows) { 9781 staticDispose(handle); 9782 handle = null; 9783 } else version(OSXCocoa) { 9784 staticDispose(handle); 9785 handle = null; 9786 } else static assert(0); 9787 9788 } 9789 9790 version(X11) 9791 static void staticDispose(Picture xrenderPicture, Pixmap handle) { 9792 if(xrenderPicture) 9793 XRenderFreePicture(XDisplayConnection.get, xrenderPicture); 9794 if(handle) 9795 XFreePixmap(XDisplayConnection.get(), handle); 9796 } 9797 else version(Windows) 9798 static void staticDispose(HBITMAP handle) { 9799 if(handle) 9800 DeleteObject(handle); 9801 } 9802 else version(OSXCocoa) 9803 static void staticDispose(CGContextRef context) { 9804 if(context) 9805 CGContextRelease(context); 9806 } 9807 9808 ~this() { 9809 version(X11) { if(xrenderPicture || handle) 9810 cleanupQueue.queue!staticDispose(xrenderPicture, handle); 9811 } else version(Windows) { if(handle) 9812 cleanupQueue.queue!staticDispose(handle); 9813 } else version(OSXCocoa) { if(handle) 9814 cleanupQueue.queue!staticDispose(handle); 9815 } else static assert(0); 9816 } 9817 9818 /// 9819 final @property int width() { return _width; } 9820 9821 /// 9822 final @property int height() { return _height; } 9823 9824 /// 9825 static Sprite fromMemoryImage(SimpleWindow win, MemoryImage img, bool enableAlpha = false) { 9826 return new Sprite(win, Image.fromMemoryImage(img, enableAlpha)); 9827 } 9828 9829 auto nativeHandle() { 9830 return handle; 9831 } 9832 9833 private: 9834 9835 int _width; 9836 int _height; 9837 bool enableAlpha; 9838 version(X11) 9839 Pixmap handle; 9840 else version(Windows) 9841 HBITMAP handle; 9842 else version(OSXCocoa) 9843 CGContextRef handle; 9844 else static assert(0); 9845 } 9846 9847 /++ 9848 Represents a display-side gradient pseudo-image. Actually construct it with [LinearGradient], [RadialGradient], or [ConicalGradient]. 9849 9850 History: 9851 Added November 20, 2021 (dub v10.4) 9852 +/ 9853 version(OSXCocoa) {} else // NotYetImplementedException 9854 abstract class Gradient : Sprite { 9855 protected this(int w, int h) { 9856 version(X11) { 9857 Sprite.requireXRender(); 9858 9859 super(); 9860 enableAlpha = true; 9861 _width = w; 9862 _height = h; 9863 } else version(Windows) { 9864 super(null, w, h, true); // on Windows i'm just making a bitmap myself 9865 } 9866 } 9867 9868 version(Windows) 9869 final void forEachPixel(scope Color delegate(int x, int y) dg) { 9870 auto ptr = rawData; 9871 foreach(j; 0 .. _height) 9872 foreach(i; 0 .. _width) { 9873 auto color = dg(i, _height - j - 1); // cuz of upside down bitmap 9874 *rawData = (color.a * color.b) / 255; rawData++; 9875 *rawData = (color.a * color.g) / 255; rawData++; 9876 *rawData = (color.a * color.r) / 255; rawData++; 9877 *rawData = color.a; rawData++; 9878 } 9879 } 9880 9881 version(X11) 9882 protected void helper(scope Stop[] stops, scope Picture delegate(scope XFixed[] stopsPositions, scope XRenderColor[] colors) dg) { 9883 assert(stops.length > 0); 9884 assert(stops.length <= 16, "I got lazy with buffers"); 9885 9886 XFixed[16] stopsPositions = void; 9887 XRenderColor[16] colors = void; 9888 9889 foreach(idx, stop; stops) { 9890 stopsPositions[idx] = cast(int)(stop.percentage * ushort.max); 9891 auto c = stop.c; 9892 colors[idx] = XRenderColor( 9893 cast(ushort)(c.r * ushort.max / 255), 9894 cast(ushort)(c.g * ushort.max / 255), 9895 cast(ushort)(c.b * ushort.max / 255), 9896 cast(ushort)(c.a * ubyte.max) // max value here is fractional 9897 ); 9898 } 9899 9900 xrenderPicture = dg(stopsPositions, colors); 9901 } 9902 9903 /// 9904 static struct Stop { 9905 float percentage; /// between 0 and 1.0 9906 Color c; 9907 } 9908 } 9909 9910 /++ 9911 Creates a linear gradient between p1 and p2. 9912 9913 X ONLY RIGHT NOW 9914 9915 History: 9916 Added November 20, 2021 (dub v10.4) 9917 9918 Bugs: 9919 Not yet implemented on Windows. 9920 +/ 9921 version(OSXCocoa) {} else // NotYetImplementedException 9922 class LinearGradient : Gradient { 9923 /++ 9924 9925 +/ 9926 this(Point p1, Point p2, Stop[] stops...) { 9927 super(p2.x, p2.y); 9928 9929 version(X11) { 9930 XLinearGradient gradient; 9931 gradient.p1 = XPointFixed(p1.x * ushort.max, p1.y * ushort.max); 9932 gradient.p2 = XPointFixed(p2.x * ushort.max, p2.y * ushort.max); 9933 9934 helper(stops, (scope XFixed[] stopsPositions, scope XRenderColor[] colors) { 9935 return XRenderCreateLinearGradient( 9936 XDisplayConnection.get, 9937 &gradient, 9938 stopsPositions.ptr, 9939 colors.ptr, 9940 cast(int) stops.length); 9941 }); 9942 } else version(Windows) { 9943 // FIXME 9944 forEachPixel((int x, int y) { 9945 import core.stdc.math; 9946 9947 //sqrtf( 9948 9949 return Color.transparent; 9950 // return Color(x * 2, y * 2, 0, 128); // this result is so beautiful 9951 }); 9952 } 9953 } 9954 } 9955 9956 /++ 9957 A conical gradient goes from color to color around a circumference from a center point. 9958 9959 X ONLY RIGHT NOW 9960 9961 History: 9962 Added November 20, 2021 (dub v10.4) 9963 9964 Bugs: 9965 Not yet implemented on Windows. 9966 +/ 9967 version(OSXCocoa) {} else // NotYetImplementedException 9968 class ConicalGradient : Gradient { 9969 /++ 9970 9971 +/ 9972 this(Point center, float angleInDegrees, Stop[] stops...) { 9973 super(center.x * 2, center.y * 2); 9974 9975 version(X11) { 9976 XConicalGradient gradient; 9977 gradient.center = XPointFixed(center.x * ushort.max, center.y * ushort.max); 9978 gradient.angle = cast(int)(angleInDegrees * ushort.max); 9979 9980 helper(stops, (scope XFixed[] stopsPositions, scope XRenderColor[] colors) { 9981 return XRenderCreateConicalGradient( 9982 XDisplayConnection.get, 9983 &gradient, 9984 stopsPositions.ptr, 9985 colors.ptr, 9986 cast(int) stops.length); 9987 }); 9988 } else version(Windows) { 9989 // FIXME 9990 forEachPixel((int x, int y) { 9991 import core.stdc.math; 9992 9993 //sqrtf( 9994 9995 return Color.transparent; 9996 // return Color(x * 2, y * 2, 0, 128); // this result is so beautiful 9997 }); 9998 9999 } 10000 } 10001 } 10002 10003 /++ 10004 A radial gradient goes from color to color based on distance from the center. 10005 It is like rings of color. 10006 10007 X ONLY RIGHT NOW 10008 10009 10010 More specifically, you create two circles: an inner circle and an outer circle. 10011 The gradient is only drawn in the area outside the inner circle but inside the outer 10012 circle. The closest line between those two circles forms the line for the gradient 10013 and the stops are calculated the same as the [LinearGradient]. Then it just sweeps around. 10014 10015 History: 10016 Added November 20, 2021 (dub v10.4) 10017 10018 Bugs: 10019 Not yet implemented on Windows. 10020 +/ 10021 version(OSXCocoa) {} else // NotYetImplementedException 10022 class RadialGradient : Gradient { 10023 /++ 10024 10025 +/ 10026 this(Point innerCenter, float innerRadius, Point outerCenter, float outerRadius, Stop[] stops...) { 10027 super(cast(int)(outerCenter.x + outerRadius + 0.5), cast(int)(outerCenter.y + outerRadius + 0.5)); 10028 10029 version(X11) { 10030 XRadialGradient gradient; 10031 gradient.inner = XCircle(innerCenter.x * ushort.max, innerCenter.y * ushort.max, cast(int) (innerRadius * ushort.max)); 10032 gradient.outer = XCircle(outerCenter.x * ushort.max, outerCenter.y * ushort.max, cast(int) (outerRadius * ushort.max)); 10033 10034 helper(stops, (scope XFixed[] stopsPositions, scope XRenderColor[] colors) { 10035 return XRenderCreateRadialGradient( 10036 XDisplayConnection.get, 10037 &gradient, 10038 stopsPositions.ptr, 10039 colors.ptr, 10040 cast(int) stops.length); 10041 }); 10042 } else version(Windows) { 10043 // FIXME 10044 forEachPixel((int x, int y) { 10045 import core.stdc.math; 10046 10047 //sqrtf( 10048 10049 return Color.transparent; 10050 // return Color(x * 2, y * 2, 0, 128); // this result is so beautiful 10051 }); 10052 } 10053 } 10054 } 10055 10056 10057 10058 /+ 10059 NOT IMPLEMENTED 10060 10061 A display-stored image optimized for relatively quick drawing, like 10062 [Sprite], but this one supports alpha channel blending and does NOT 10063 support direct drawing upon it with a [ScreenPainter]. 10064 10065 You can think of it as an [arsd.game.OpenGlTexture] for usage with a 10066 plain [ScreenPainter]... sort of. 10067 10068 On X11, it requires the Xrender extension and library. This is available 10069 almost everywhere though. 10070 10071 History: 10072 Added November 14, 2020 but NOT ACTUALLY IMPLEMENTED 10073 +/ 10074 version(none) 10075 class AlphaSprite { 10076 /++ 10077 Copies the given image into it. 10078 +/ 10079 this(MemoryImage img) { 10080 10081 if(!XRenderLibrary.loadAttempted) { 10082 XRenderLibrary.loadDynamicLibrary(); 10083 10084 // FIXME: this needs to be reconstructed when the X server changes 10085 repopulateX(); 10086 } 10087 if(!XRenderLibrary.loadSuccessful) 10088 throw new Exception("XRender library load failure"); 10089 10090 // I probably need to put the alpha mask in a separate Picture 10091 // ugh 10092 // maybe the Sprite itself can have an alpha bitmask anyway 10093 10094 10095 auto display = XDisplayConnection.get(); 10096 pixmap = XCreatePixmap(display, cast(Drawable) win.window, width, height, DefaultDepthOfDisplay(display)); 10097 10098 10099 XRenderPictureAttributes attrs; 10100 10101 handle = XRenderCreatePicture( 10102 XDisplayConnection.get, 10103 pixmap, 10104 RGBA, 10105 0, 10106 &attrs 10107 ); 10108 10109 } 10110 10111 // maybe i'll use the create gradient functions too with static factories.. 10112 10113 void drawAt(ScreenPainter painter, Point where) { 10114 //painter.drawPixmap(this, where); 10115 10116 XRenderPictureAttributes attrs; 10117 10118 auto pic = XRenderCreatePicture( 10119 XDisplayConnection.get, 10120 painter.impl.d, 10121 RGB, 10122 0, 10123 &attrs 10124 ); 10125 10126 XRenderComposite( 10127 XDisplayConnection.get, 10128 3, // PictOpOver 10129 handle, 10130 None, 10131 pic, 10132 0, // src 10133 0, 10134 0, // mask 10135 0, 10136 10, // dest 10137 10, 10138 100, // width 10139 100 10140 ); 10141 10142 /+ 10143 XRenderFreePicture( 10144 XDisplayConnection.get, 10145 pic 10146 ); 10147 10148 XRenderFreePicture( 10149 XDisplayConnection.get, 10150 fill 10151 ); 10152 +/ 10153 // on Windows you can stretch but Xrender still can't :( 10154 } 10155 10156 static XRenderPictFormat* RGB; 10157 static XRenderPictFormat* RGBA; 10158 static void repopulateX() { 10159 auto display = XDisplayConnection.get; 10160 RGB = XRenderFindStandardFormat(display, PictStandardRGB24); 10161 RGBA = XRenderFindStandardFormat(display, PictStandardARGB32); 10162 } 10163 10164 XPixmap pixmap; 10165 Picture handle; 10166 } 10167 10168 /// 10169 interface CapableOfBeingDrawnUpon { 10170 /// 10171 ScreenPainter draw(); 10172 /// 10173 int width(); 10174 /// 10175 int height(); 10176 protected ScreenPainterImplementation* activeScreenPainter(); 10177 protected void activeScreenPainter(ScreenPainterImplementation*); 10178 bool closed(); 10179 10180 void delegate() paintingFinishedDg(); 10181 10182 /// Be warned: this can be a very slow operation 10183 TrueColorImage takeScreenshot(); 10184 } 10185 10186 /// 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]. 10187 void flushGui() { 10188 version(X11) { 10189 auto dpy = XDisplayConnection.get(); 10190 XLockDisplay(dpy); 10191 scope(exit) XUnlockDisplay(dpy); 10192 XFlush(dpy); 10193 } 10194 } 10195 10196 /++ 10197 Runs the given code in the GUI thread when its event loop 10198 is available, blocking until it completes. This allows you 10199 to create and manipulate windows from another thread without 10200 invoking undefined behavior. 10201 10202 If this is the gui thread, it runs the code immediately. 10203 10204 If no gui thread exists yet, the current thread is assumed 10205 to be it. Attempting to create windows or run the event loop 10206 in any other thread will cause an assertion failure. 10207 10208 10209 $(TIP 10210 Did you know you can use UFCS on delegate literals? 10211 10212 () { 10213 // code here 10214 }.runInGuiThread; 10215 ) 10216 10217 Returns: 10218 `true` if the function was called, `false` if it was not. 10219 The function may not be called because the gui thread had 10220 already terminated by the time you called this. 10221 10222 History: 10223 Added April 10, 2020 (v7.2.0) 10224 10225 Return value added and implementation tweaked to avoid locking 10226 at program termination on February 24, 2021 (v9.2.1). 10227 +/ 10228 bool runInGuiThread(scope void delegate() dg) @trusted { 10229 claimGuiThread(); 10230 10231 if(thisIsGuiThread) { 10232 dg(); 10233 return true; 10234 } 10235 10236 if(guiThreadTerminating) 10237 return false; 10238 10239 import core.sync.semaphore; 10240 static Semaphore sc; 10241 if(sc is null) 10242 sc = new Semaphore(); 10243 10244 static RunQueueMember* rqm; 10245 if(rqm is null) 10246 rqm = new RunQueueMember; 10247 rqm.dg = cast(typeof(rqm.dg)) dg; 10248 rqm.signal = sc; 10249 rqm.thrown = null; 10250 10251 synchronized(runInGuiThreadLock) { 10252 runInGuiThreadQueue ~= rqm; 10253 } 10254 10255 if(!SimpleWindow.eventWakeUp()) 10256 throw new Error("runInGuiThread impossible; eventWakeUp failed"); 10257 10258 rqm.signal.wait(); 10259 auto t = rqm.thrown; 10260 10261 if(t) 10262 throw t; 10263 10264 return true; 10265 } 10266 10267 // note it runs sync if this is the gui thread.... 10268 void runInGuiThreadAsync(void delegate() dg, void delegate(Exception) nothrow handleError = null) nothrow { 10269 claimGuiThread(); 10270 10271 try { 10272 10273 if(thisIsGuiThread) { 10274 dg(); 10275 return; 10276 } 10277 10278 if(guiThreadTerminating) 10279 return; 10280 10281 RunQueueMember* rqm = new RunQueueMember; 10282 rqm.dg = cast(typeof(rqm.dg)) dg; 10283 rqm.signal = null; 10284 rqm.thrown = null; 10285 10286 synchronized(runInGuiThreadLock) { 10287 runInGuiThreadQueue ~= rqm; 10288 } 10289 10290 if(!SimpleWindow.eventWakeUp()) 10291 throw new Error("runInGuiThread impossible; eventWakeUp failed"); 10292 } catch(Exception e) { 10293 if(handleError) 10294 handleError(e); 10295 } 10296 } 10297 10298 private void runPendingRunInGuiThreadDelegates() { 10299 more: 10300 RunQueueMember* next; 10301 synchronized(runInGuiThreadLock) { 10302 if(runInGuiThreadQueue.length) { 10303 next = runInGuiThreadQueue[0]; 10304 runInGuiThreadQueue = runInGuiThreadQueue[1 .. $]; 10305 } else { 10306 next = null; 10307 } 10308 } 10309 10310 if(next) { 10311 try { 10312 next.dg(); 10313 next.thrown = null; 10314 } catch(Throwable t) { 10315 next.thrown = t; 10316 } 10317 10318 if(next.signal) 10319 next.signal.notify(); 10320 10321 goto more; 10322 } 10323 } 10324 10325 private void claimGuiThread() nothrow { 10326 import core.atomic; 10327 if(cas(&guiThreadExists_, false, true)) 10328 thisIsGuiThread = true; 10329 } 10330 10331 private struct RunQueueMember { 10332 void delegate() dg; 10333 import core.sync.semaphore; 10334 Semaphore signal; 10335 Throwable thrown; 10336 } 10337 10338 private __gshared RunQueueMember*[] runInGuiThreadQueue; 10339 private __gshared Object runInGuiThreadLock = new Object; // intentional CTFE 10340 private bool thisIsGuiThread = false; 10341 private shared bool guiThreadExists_ = false; 10342 private shared bool guiThreadTerminating = false; 10343 10344 /++ 10345 Returns `true` if a gui thread exists, that is, a thread running the simpledisplay.d 10346 event loop. All windows must be exclusively created and managed by a single thread. 10347 10348 If no gui thread exists, simpledisplay.d will automatically adopt the current thread 10349 when you call one of its constructors. 10350 10351 If a gui thread exists, you should check [thisThreadRunningGui] to see if it is this 10352 one. If so, you can run gui functions on it. If not, don't. The helper functions 10353 [runInGuiThread] and [runInGuiThreadAsync] can be used to help you with this automatically. 10354 10355 The reason this function is available is in case you want to message pass between a gui 10356 thread and your current thread. If no gui thread exists or if this is the gui thread, 10357 you're liable to deadlock when trying to communicate since you'd end up talking to yourself. 10358 10359 History: 10360 Added December 3, 2021 (dub v10.5) 10361 +/ 10362 public bool guiThreadExists() { 10363 return guiThreadExists_; 10364 } 10365 10366 /++ 10367 Returns `true` if this thread is either running or set to be running the 10368 simpledisplay.d gui core event loop because it owns windows. 10369 10370 It is important to keep gui-related functionality in the right thread, so you will 10371 want to `runInGuiThread` when you call them (with some specific exceptions called 10372 out in those specific functions' documentation). Notably, all windows must be 10373 created and managed only from the gui thread. 10374 10375 Will return false if simpledisplay's other functions haven't been called 10376 yet; check [guiThreadExists] in addition to this. 10377 10378 History: 10379 Added December 3, 2021 (dub v10.5) 10380 +/ 10381 public bool thisThreadRunningGui() { 10382 return thisIsGuiThread; 10383 } 10384 10385 /++ 10386 Function to help temporarily print debugging info. It will bypass any stdout/err redirection 10387 and go to the controlling tty or console (attaching to the parent and/or allocating one as 10388 needed on Windows. Please note it may overwrite output from other programs in the parent and the 10389 allocated one will not survive if your program crashes. Use the `fileOverride` to print to a log 10390 file instead if you are in one of those situations). 10391 10392 It does not support outputting very many types; just strings and ints are likely to actually work. 10393 10394 It will perform very slowly and swallows any errors that may occur. Moreover, the specific output 10395 is unspecified meaning I can change it at any time. The only point of this function is to help 10396 in temporary use for printf-style debugging. It is NOT nogc, but you can use the `debug` keyword 10397 and the compiler will cheat for you. It is, however, formally nothrow and trusted to ease its use 10398 in those contexts. 10399 10400 $(WARNING 10401 I reserve the right to change this function at any time. You can use it if it helps you 10402 but do not rely on it for anything permanent. 10403 ) 10404 10405 History: 10406 Added December 3, 2021. Not formally supported under any stable tag. 10407 +/ 10408 void sdpyPrintDebugString(string fileOverride = null, T...)(T t) nothrow @trusted { 10409 try { 10410 version(Windows) { 10411 import core.sys.windows.wincon; 10412 if(!AttachConsole(ATTACH_PARENT_PROCESS)) 10413 AllocConsole(); 10414 const(char)* fn = "CONOUT$"; 10415 } else version(Posix) { 10416 const(char)* fn = "/dev/tty"; 10417 } else static assert(0, "Function not implemented for your system"); 10418 10419 if(fileOverride.length) 10420 fn = fileOverride.ptr; 10421 10422 import core.stdc.stdio; 10423 auto fp = fopen(fn, "wt"); 10424 if(fp is null) return; 10425 scope(exit) fclose(fp); 10426 10427 string str; 10428 foreach(item; t) { 10429 static if(is(typeof(item) : const(char)[])) 10430 str ~= item; 10431 else 10432 str ~= toInternal!string(item); 10433 str ~= " "; 10434 } 10435 str ~= "\n"; 10436 10437 fwrite(str.ptr, 1, str.length, fp); 10438 fflush(fp); 10439 } catch(Exception e) { 10440 // sorry no hope 10441 } 10442 } 10443 10444 private void guiThreadFinalize() { 10445 assert(thisIsGuiThread); 10446 10447 guiThreadTerminating = true; // don't add any more from this point on 10448 runPendingRunInGuiThreadDelegates(); 10449 } 10450 10451 /+ 10452 interface IPromise { 10453 void reportProgress(int current, int max, string message); 10454 10455 /+ // not formally in cuz of templates but still 10456 IPromise Then(); 10457 IPromise Catch(); 10458 IPromise Finally(); 10459 +/ 10460 } 10461 10462 /+ 10463 auto promise = async({ ... }); 10464 promise.Then(whatever). 10465 Then(whateverelse). 10466 Catch((exception) { }); 10467 10468 10469 A promise is run inside a fiber and it looks something like: 10470 10471 try { 10472 auto res = whatever(); 10473 auto res2 = whateverelse(res); 10474 } catch(Exception e) { 10475 { }(e); 10476 } 10477 10478 When a thing succeeds, it is passed as an arg to the next 10479 +/ 10480 class Promise(T) : IPromise { 10481 auto Then() { return null; } 10482 auto Catch() { return null; } 10483 auto Finally() { return null; } 10484 10485 // wait for it to resolve and return the value, or rethrow the error if that occurred. 10486 // cannot be called from the gui thread, but this is caught at runtime instead of compile time. 10487 T await(); 10488 } 10489 10490 interface Task { 10491 } 10492 10493 interface Resolvable(T) : Task { 10494 void run(); 10495 10496 void resolve(T); 10497 10498 Resolvable!T then(void delegate(T)); // returns a new promise 10499 Resolvable!T error(Throwable); // js catch 10500 Resolvable!T completed(); // js finally 10501 10502 } 10503 10504 /++ 10505 Runs `work` in a helper thread and sends its return value back to the main gui 10506 thread as the argument to `uponCompletion`. If `work` throws, the exception is 10507 sent to the `uponThrown` if given, or if null, rethrown from the event loop to 10508 kill the program. 10509 10510 You can call reportProgress(position, max, message) to update your parent window 10511 on your progress. 10512 10513 I should also use `shared` methods. FIXME 10514 10515 History: 10516 Added March 6, 2021 (dub version 9.3). 10517 +/ 10518 void runInWorkerThread(T)(T delegate(Task) work, void delegate(T) uponCompletion) { 10519 uponCompletion(work(null)); 10520 } 10521 10522 +/ 10523 10524 /// Used internal to dispatch events to various classes. 10525 interface CapableOfHandlingNativeEvent { 10526 NativeEventHandler getNativeEventHandler(); 10527 10528 /*private*//*protected*/ __gshared CapableOfHandlingNativeEvent[NativeWindowHandle] nativeHandleMapping; 10529 10530 version(X11) { 10531 // if this is impossible, you are allowed to just throw from it 10532 // Note: if you call it from another object, set a flag cuz the manger will call you again 10533 void recreateAfterDisconnect(); 10534 // discard any *connection specific* state, but keep enough that you 10535 // can be recreated if possible. discardConnectionState() is always called immediately 10536 // before recreateAfterDisconnect(), so you can set a flag there to decide if 10537 // you need initialization order 10538 void discardConnectionState(); 10539 } 10540 } 10541 10542 version(X11) 10543 /++ 10544 State of keys on mouse events, especially motion. 10545 10546 Do not trust the actual integer values in this, they are platform-specific. Always use the names. 10547 +/ 10548 enum ModifierState : uint { 10549 shift = 1, /// 10550 capsLock = 2, /// 10551 ctrl = 4, /// 10552 alt = 8, /// Not always available on Windows 10553 windows = 64, /// ditto 10554 numLock = 16, /// 10555 10556 leftButtonDown = 256, /// these aren't available on Windows for key events, so don't use them for that unless your app is X only. 10557 middleButtonDown = 512, /// ditto 10558 rightButtonDown = 1024, /// ditto 10559 } 10560 else version(Windows) 10561 /// ditto 10562 enum ModifierState : uint { 10563 shift = 4, /// 10564 ctrl = 8, /// 10565 10566 // i'm not sure if the next two are available 10567 alt = 256, /// not always available on Windows 10568 windows = 512, /// ditto 10569 10570 capsLock = 1024, /// 10571 numLock = 2048, /// 10572 10573 leftButtonDown = 1, /// not available on key events 10574 middleButtonDown = 16, /// ditto 10575 rightButtonDown = 2, /// ditto 10576 10577 backButtonDown = 0x20, /// not available on X 10578 forwardButtonDown = 0x40, /// ditto 10579 } 10580 else version(OSXCocoa) 10581 // FIXME FIXME NotYetImplementedException 10582 enum ModifierState : uint { 10583 shift = 1, /// 10584 capsLock = 2, /// 10585 ctrl = 4, /// 10586 alt = 8, /// Not always available on Windows 10587 windows = 64, /// ditto 10588 numLock = 16, /// 10589 10590 leftButtonDown = 256, /// these aren't available on Windows for key events, so don't use them for that unless your app is X only. 10591 middleButtonDown = 512, /// ditto 10592 rightButtonDown = 1024, /// ditto 10593 } 10594 10595 /// The names assume a right-handed mouse. These are bitwise combined on the events that use them. 10596 enum MouseButton : int { 10597 none = 0, 10598 left = 1, /// 10599 right = 2, /// 10600 middle = 4, /// 10601 wheelUp = 8, /// 10602 wheelDown = 16, /// 10603 backButton = 32, /// often found on the thumb and used for back in browsers 10604 forwardButton = 64, /// often found on the thumb and used for forward in browsers 10605 } 10606 10607 /// Corresponds to the values found in MouseEvent.buttonLinear, being equal to `core.bitop.bsf(button) + 1` 10608 enum MouseButtonLinear : ubyte { 10609 left = 1, /// 10610 right, /// 10611 middle, /// 10612 wheelUp, /// 10613 wheelDown, /// 10614 backButton, /// often found on the thumb and used for back in browsers 10615 forwardButton, /// often found on the thumb and used for forward in browsers 10616 } 10617 10618 version(X11) { 10619 // FIXME: match ASCII whenever we can. Most of it is already there, 10620 // but there's a few exceptions and mismatches with Windows 10621 10622 /// Do not trust the numeric values as they are platform-specific. Always use the symbolic name. 10623 enum Key { 10624 Escape = 0xff1b, /// 10625 F1 = 0xffbe, /// 10626 F2 = 0xffbf, /// 10627 F3 = 0xffc0, /// 10628 F4 = 0xffc1, /// 10629 F5 = 0xffc2, /// 10630 F6 = 0xffc3, /// 10631 F7 = 0xffc4, /// 10632 F8 = 0xffc5, /// 10633 F9 = 0xffc6, /// 10634 F10 = 0xffc7, /// 10635 F11 = 0xffc8, /// 10636 F12 = 0xffc9, /// 10637 PrintScreen = 0xff61, /// 10638 ScrollLock = 0xff14, /// 10639 Pause = 0xff13, /// 10640 Grave = 0x60, /// The $(BACKTICK) ~ key 10641 // number keys across the top of the keyboard 10642 N1 = 0x31, /// Number key atop the keyboard 10643 N2 = 0x32, /// 10644 N3 = 0x33, /// 10645 N4 = 0x34, /// 10646 N5 = 0x35, /// 10647 N6 = 0x36, /// 10648 N7 = 0x37, /// 10649 N8 = 0x38, /// 10650 N9 = 0x39, /// 10651 N0 = 0x30, /// 10652 Dash = 0x2d, /// 10653 Equals = 0x3d, /// 10654 Backslash = 0x5c, /// The \ | key 10655 Backspace = 0xff08, /// 10656 Insert = 0xff63, /// 10657 Home = 0xff50, /// 10658 PageUp = 0xff55, /// 10659 Delete = 0xffff, /// 10660 End = 0xff57, /// 10661 PageDown = 0xff56, /// 10662 Up = 0xff52, /// 10663 Down = 0xff54, /// 10664 Left = 0xff51, /// 10665 Right = 0xff53, /// 10666 10667 Tab = 0xff09, /// 10668 Q = 0x71, /// 10669 W = 0x77, /// 10670 E = 0x65, /// 10671 R = 0x72, /// 10672 T = 0x74, /// 10673 Y = 0x79, /// 10674 U = 0x75, /// 10675 I = 0x69, /// 10676 O = 0x6f, /// 10677 P = 0x70, /// 10678 LeftBracket = 0x5b, /// the [ { key 10679 RightBracket = 0x5d, /// the ] } key 10680 CapsLock = 0xffe5, /// 10681 A = 0x61, /// 10682 S = 0x73, /// 10683 D = 0x64, /// 10684 F = 0x66, /// 10685 G = 0x67, /// 10686 H = 0x68, /// 10687 J = 0x6a, /// 10688 K = 0x6b, /// 10689 L = 0x6c, /// 10690 Semicolon = 0x3b, /// 10691 Apostrophe = 0x27, /// 10692 Enter = 0xff0d, /// 10693 Shift = 0xffe1, /// 10694 Z = 0x7a, /// 10695 X = 0x78, /// 10696 C = 0x63, /// 10697 V = 0x76, /// 10698 B = 0x62, /// 10699 N = 0x6e, /// 10700 M = 0x6d, /// 10701 Comma = 0x2c, /// 10702 Period = 0x2e, /// 10703 Slash = 0x2f, /// the / ? key 10704 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 10705 Ctrl = 0xffe3, /// 10706 Windows = 0xffeb, /// 10707 Alt = 0xffe9, /// 10708 Space = 0x20, /// 10709 Alt_r = 0xffea, /// ditto of shift_r 10710 Windows_r = 0xffec, /// 10711 Menu = 0xff67, /// 10712 Ctrl_r = 0xffe4, /// 10713 10714 NumLock = 0xff7f, /// 10715 Divide = 0xffaf, /// The / key on the number pad 10716 Multiply = 0xffaa, /// The * key on the number pad 10717 Minus = 0xffad, /// The - key on the number pad 10718 Plus = 0xffab, /// The + key on the number pad 10719 PadEnter = 0xff8d, /// Numberpad enter key 10720 Pad1 = 0xff9c, /// Numberpad keys 10721 Pad2 = 0xff99, /// 10722 Pad3 = 0xff9b, /// 10723 Pad4 = 0xff96, /// 10724 Pad5 = 0xff9d, /// 10725 Pad6 = 0xff98, /// 10726 Pad7 = 0xff95, /// 10727 Pad8 = 0xff97, /// 10728 Pad9 = 0xff9a, /// 10729 Pad0 = 0xff9e, /// 10730 PadDot = 0xff9f, /// 10731 } 10732 } else version(Windows) { 10733 // the character here is for en-us layouts and for illustration only 10734 // if you actually want to get characters, wait for character events 10735 // (the argument to your event handler is simply a dchar) 10736 // those will be converted by the OS for the right locale. 10737 10738 enum Key { 10739 Escape = 0x1b, 10740 F1 = 0x70, 10741 F2 = 0x71, 10742 F3 = 0x72, 10743 F4 = 0x73, 10744 F5 = 0x74, 10745 F6 = 0x75, 10746 F7 = 0x76, 10747 F8 = 0x77, 10748 F9 = 0x78, 10749 F10 = 0x79, 10750 F11 = 0x7a, 10751 F12 = 0x7b, 10752 PrintScreen = 0x2c, 10753 ScrollLock = 0x91, 10754 Pause = 0x13, 10755 Grave = 0xc0, 10756 // number keys across the top of the keyboard 10757 N1 = 0x31, 10758 N2 = 0x32, 10759 N3 = 0x33, 10760 N4 = 0x34, 10761 N5 = 0x35, 10762 N6 = 0x36, 10763 N7 = 0x37, 10764 N8 = 0x38, 10765 N9 = 0x39, 10766 N0 = 0x30, 10767 Dash = 0xbd, 10768 Equals = 0xbb, 10769 Backslash = 0xdc, 10770 Backspace = 0x08, 10771 Insert = 0x2d, 10772 Home = 0x24, 10773 PageUp = 0x21, 10774 Delete = 0x2e, 10775 End = 0x23, 10776 PageDown = 0x22, 10777 Up = 0x26, 10778 Down = 0x28, 10779 Left = 0x25, 10780 Right = 0x27, 10781 10782 Tab = 0x09, 10783 Q = 0x51, 10784 W = 0x57, 10785 E = 0x45, 10786 R = 0x52, 10787 T = 0x54, 10788 Y = 0x59, 10789 U = 0x55, 10790 I = 0x49, 10791 O = 0x4f, 10792 P = 0x50, 10793 LeftBracket = 0xdb, 10794 RightBracket = 0xdd, 10795 CapsLock = 0x14, 10796 A = 0x41, 10797 S = 0x53, 10798 D = 0x44, 10799 F = 0x46, 10800 G = 0x47, 10801 H = 0x48, 10802 J = 0x4a, 10803 K = 0x4b, 10804 L = 0x4c, 10805 Semicolon = 0xba, 10806 Apostrophe = 0xde, 10807 Enter = 0x0d, 10808 Shift = 0x10, 10809 Z = 0x5a, 10810 X = 0x58, 10811 C = 0x43, 10812 V = 0x56, 10813 B = 0x42, 10814 N = 0x4e, 10815 M = 0x4d, 10816 Comma = 0xbc, 10817 Period = 0xbe, 10818 Slash = 0xbf, 10819 Shift_r = 0xa1, // FIXME Note: this isn't sent on all computers, sometimes it just sends Shift, so don't rely on it 10820 Ctrl = 0x11, 10821 Windows = 0x5b, 10822 Alt = -5, // FIXME 10823 Space = 0x20, 10824 Alt_r = 0xffea, // ditto of shift_r 10825 Windows_r = 0x5c, // ditto of shift_r 10826 Menu = 0x5d, 10827 Ctrl_r = 0xa3, // ditto of shift_r 10828 10829 NumLock = 0x90, 10830 Divide = 0x6f, 10831 Multiply = 0x6a, 10832 Minus = 0x6d, 10833 Plus = 0x6b, 10834 PadEnter = -8, // FIXME 10835 Pad1 = 0x61, 10836 Pad2 = 0x62, 10837 Pad3 = 0x63, 10838 Pad4 = 0x64, 10839 Pad5 = 0x65, 10840 Pad6 = 0x66, 10841 Pad7 = 0x67, 10842 Pad8 = 0x68, 10843 Pad9 = 0x69, 10844 Pad0 = 0x60, 10845 PadDot = 0x6e, 10846 } 10847 10848 // I'm keeping this around for reference purposes 10849 // ideally all these buttons will be listed for all platforms, 10850 // but now now I'm just focusing on my US keyboard 10851 version(none) 10852 enum Key { 10853 LBUTTON = 0x01, 10854 RBUTTON = 0x02, 10855 CANCEL = 0x03, 10856 MBUTTON = 0x04, 10857 //static if (_WIN32_WINNT > = 0x500) { 10858 XBUTTON1 = 0x05, 10859 XBUTTON2 = 0x06, 10860 //} 10861 BACK = 0x08, 10862 TAB = 0x09, 10863 CLEAR = 0x0C, 10864 RETURN = 0x0D, 10865 SHIFT = 0x10, 10866 CONTROL = 0x11, 10867 MENU = 0x12, 10868 PAUSE = 0x13, 10869 CAPITAL = 0x14, 10870 KANA = 0x15, 10871 HANGEUL = 0x15, 10872 HANGUL = 0x15, 10873 JUNJA = 0x17, 10874 FINAL = 0x18, 10875 HANJA = 0x19, 10876 KANJI = 0x19, 10877 ESCAPE = 0x1B, 10878 CONVERT = 0x1C, 10879 NONCONVERT = 0x1D, 10880 ACCEPT = 0x1E, 10881 MODECHANGE = 0x1F, 10882 SPACE = 0x20, 10883 PRIOR = 0x21, 10884 NEXT = 0x22, 10885 END = 0x23, 10886 HOME = 0x24, 10887 LEFT = 0x25, 10888 UP = 0x26, 10889 RIGHT = 0x27, 10890 DOWN = 0x28, 10891 SELECT = 0x29, 10892 PRINT = 0x2A, 10893 EXECUTE = 0x2B, 10894 SNAPSHOT = 0x2C, 10895 INSERT = 0x2D, 10896 DELETE = 0x2E, 10897 HELP = 0x2F, 10898 LWIN = 0x5B, 10899 RWIN = 0x5C, 10900 APPS = 0x5D, 10901 SLEEP = 0x5F, 10902 NUMPAD0 = 0x60, 10903 NUMPAD1 = 0x61, 10904 NUMPAD2 = 0x62, 10905 NUMPAD3 = 0x63, 10906 NUMPAD4 = 0x64, 10907 NUMPAD5 = 0x65, 10908 NUMPAD6 = 0x66, 10909 NUMPAD7 = 0x67, 10910 NUMPAD8 = 0x68, 10911 NUMPAD9 = 0x69, 10912 MULTIPLY = 0x6A, 10913 ADD = 0x6B, 10914 SEPARATOR = 0x6C, 10915 SUBTRACT = 0x6D, 10916 DECIMAL = 0x6E, 10917 DIVIDE = 0x6F, 10918 F1 = 0x70, 10919 F2 = 0x71, 10920 F3 = 0x72, 10921 F4 = 0x73, 10922 F5 = 0x74, 10923 F6 = 0x75, 10924 F7 = 0x76, 10925 F8 = 0x77, 10926 F9 = 0x78, 10927 F10 = 0x79, 10928 F11 = 0x7A, 10929 F12 = 0x7B, 10930 F13 = 0x7C, 10931 F14 = 0x7D, 10932 F15 = 0x7E, 10933 F16 = 0x7F, 10934 F17 = 0x80, 10935 F18 = 0x81, 10936 F19 = 0x82, 10937 F20 = 0x83, 10938 F21 = 0x84, 10939 F22 = 0x85, 10940 F23 = 0x86, 10941 F24 = 0x87, 10942 NUMLOCK = 0x90, 10943 SCROLL = 0x91, 10944 LSHIFT = 0xA0, 10945 RSHIFT = 0xA1, 10946 LCONTROL = 0xA2, 10947 RCONTROL = 0xA3, 10948 LMENU = 0xA4, 10949 RMENU = 0xA5, 10950 //static if (_WIN32_WINNT > = 0x500) { 10951 BROWSER_BACK = 0xA6, 10952 BROWSER_FORWARD = 0xA7, 10953 BROWSER_REFRESH = 0xA8, 10954 BROWSER_STOP = 0xA9, 10955 BROWSER_SEARCH = 0xAA, 10956 BROWSER_FAVORITES = 0xAB, 10957 BROWSER_HOME = 0xAC, 10958 VOLUME_MUTE = 0xAD, 10959 VOLUME_DOWN = 0xAE, 10960 VOLUME_UP = 0xAF, 10961 MEDIA_NEXT_TRACK = 0xB0, 10962 MEDIA_PREV_TRACK = 0xB1, 10963 MEDIA_STOP = 0xB2, 10964 MEDIA_PLAY_PAUSE = 0xB3, 10965 LAUNCH_MAIL = 0xB4, 10966 LAUNCH_MEDIA_SELECT = 0xB5, 10967 LAUNCH_APP1 = 0xB6, 10968 LAUNCH_APP2 = 0xB7, 10969 //} 10970 OEM_1 = 0xBA, 10971 //static if (_WIN32_WINNT > = 0x500) { 10972 OEM_PLUS = 0xBB, 10973 OEM_COMMA = 0xBC, 10974 OEM_MINUS = 0xBD, 10975 OEM_PERIOD = 0xBE, 10976 //} 10977 OEM_2 = 0xBF, 10978 OEM_3 = 0xC0, 10979 OEM_4 = 0xDB, 10980 OEM_5 = 0xDC, 10981 OEM_6 = 0xDD, 10982 OEM_7 = 0xDE, 10983 OEM_8 = 0xDF, 10984 //static if (_WIN32_WINNT > = 0x500) { 10985 OEM_102 = 0xE2, 10986 //} 10987 PROCESSKEY = 0xE5, 10988 //static if (_WIN32_WINNT > = 0x500) { 10989 PACKET = 0xE7, 10990 //} 10991 ATTN = 0xF6, 10992 CRSEL = 0xF7, 10993 EXSEL = 0xF8, 10994 EREOF = 0xF9, 10995 PLAY = 0xFA, 10996 ZOOM = 0xFB, 10997 NONAME = 0xFC, 10998 PA1 = 0xFD, 10999 OEM_CLEAR = 0xFE, 11000 } 11001 11002 } else version(OSXCocoa) { 11003 enum Key { 11004 Escape = 53, 11005 F1 = 122, 11006 F2 = 120, 11007 F3 = 99, 11008 F4 = 118, 11009 F5 = 96, 11010 F6 = 97, 11011 F7 = 98, 11012 F8 = 100, 11013 F9 = 101, 11014 F10 = 109, 11015 F11 = 103, 11016 F12 = 111, 11017 PrintScreen = 105, 11018 ScrollLock = 107, 11019 Pause = 113, 11020 Grave = 50, 11021 // number keys across the top of the keyboard 11022 N1 = 18, 11023 N2 = 19, 11024 N3 = 20, 11025 N4 = 21, 11026 N5 = 23, 11027 N6 = 22, 11028 N7 = 26, 11029 N8 = 28, 11030 N9 = 25, 11031 N0 = 29, 11032 Dash = 27, 11033 Equals = 24, 11034 Backslash = 42, 11035 Backspace = 51, 11036 Insert = 114, 11037 Home = 115, 11038 PageUp = 116, 11039 Delete = 117, 11040 End = 119, 11041 PageDown = 121, 11042 Up = 126, 11043 Down = 125, 11044 Left = 123, 11045 Right = 124, 11046 11047 Tab = 48, 11048 Q = 12, 11049 W = 13, 11050 E = 14, 11051 R = 15, 11052 T = 17, 11053 Y = 16, 11054 U = 32, 11055 I = 34, 11056 O = 31, 11057 P = 35, 11058 LeftBracket = 33, 11059 RightBracket = 30, 11060 CapsLock = 57, 11061 A = 0, 11062 S = 1, 11063 D = 2, 11064 F = 3, 11065 G = 5, 11066 H = 4, 11067 J = 38, 11068 K = 40, 11069 L = 37, 11070 Semicolon = 41, 11071 Apostrophe = 39, 11072 Enter = 36, 11073 Shift = 56, 11074 Z = 6, 11075 X = 7, 11076 C = 8, 11077 V = 9, 11078 B = 11, 11079 N = 45, 11080 M = 46, 11081 Comma = 43, 11082 Period = 47, 11083 Slash = 44, 11084 Shift_r = -4, // FIXME Note: this isn't sent on all computers, sometimes it just sends Shift, so don't rely on it 11085 Ctrl = 59, 11086 Windows = 55, 11087 Alt = 58, 11088 Space = 49, 11089 Alt_r = -3, // ditto of shift_r 11090 Windows_r = -2, 11091 Menu = 110, 11092 Ctrl_r = -1, 11093 11094 NumLock = 1, 11095 Divide = 75, 11096 Multiply = 67, 11097 Minus = 78, 11098 Plus = 69, 11099 PadEnter = 76, 11100 Pad1 = 83, 11101 Pad2 = 84, 11102 Pad3 = 85, 11103 Pad4 = 86, 11104 Pad5 = 87, 11105 Pad6 = 88, 11106 Pad7 = 89, 11107 Pad8 = 91, 11108 Pad9 = 92, 11109 Pad0 = 82, 11110 PadDot = 65, 11111 } 11112 11113 } 11114 11115 /* Additional utilities */ 11116 11117 11118 Color fromHsl(real h, real s, real l) { 11119 return arsd.color.fromHsl([h,s,l]); 11120 } 11121 11122 11123 11124 /* ********** What follows is the system-specific implementations *********/ 11125 version(Windows) { 11126 11127 11128 // helpers for making HICONs from MemoryImages 11129 class WindowsIcon { 11130 struct Win32Icon { 11131 align(1): 11132 uint biSize; 11133 int biWidth; 11134 int biHeight; 11135 ushort biPlanes; 11136 ushort biBitCount; 11137 uint biCompression; 11138 uint biSizeImage; 11139 int biXPelsPerMeter; 11140 int biYPelsPerMeter; 11141 uint biClrUsed; 11142 uint biClrImportant; 11143 // RGBQUAD[colorCount] biColors; 11144 /* Pixels: 11145 Uint8 pixels[] 11146 */ 11147 /* Mask: 11148 Uint8 mask[] 11149 */ 11150 } 11151 11152 ubyte[] fromMemoryImage(MemoryImage mi, out int icon_len, out int width, out int height) { 11153 11154 assert(mi.width <= 256, "image too wide"); 11155 assert(mi.height <= 256, "image too tall"); 11156 assert(mi.width % 8 == 0, "image not multiple of 8 width"); // i don't want padding nor do i want the and mask to get fancy 11157 assert(mi.height % 4 == 0, "image not multiple of 4 height"); 11158 11159 int icon_plen = mi.width * mi.height * 4; 11160 int icon_mlen = mi.width * mi.height / 8; 11161 11162 int colorCount = 0; 11163 icon_len = 40 + icon_plen + icon_mlen + cast(int) RGBQUAD.sizeof * colorCount; 11164 11165 ubyte[] memory = new ubyte[](Win32Icon.sizeof + icon_plen + icon_mlen); 11166 Win32Icon* icon_win32 = cast(Win32Icon*) memory.ptr; 11167 11168 auto data = memory[Win32Icon.sizeof .. $]; 11169 11170 width = mi.width; 11171 height = mi.height; 11172 11173 auto trueColorImage = mi.getAsTrueColorImage(); 11174 11175 icon_win32.biSize = 40; 11176 icon_win32.biWidth = mi.width; 11177 icon_win32.biHeight = mi.height*2; 11178 icon_win32.biPlanes = 1; 11179 icon_win32.biBitCount = 32; 11180 icon_win32.biSizeImage = icon_plen + icon_mlen; 11181 11182 int offset = 0; 11183 int andOff = icon_plen * 8; // the and offset is in bits 11184 11185 // leaving the and mask as the default 0 so the rgba alpha blend 11186 // does its thing instead 11187 for(int y = height - 1; y >= 0; y--) { 11188 int off2 = y * width * 4; 11189 foreach(x; 0 .. width) { 11190 data[offset + 2] = trueColorImage.imageData.bytes[off2 + 0]; 11191 data[offset + 1] = trueColorImage.imageData.bytes[off2 + 1]; 11192 data[offset + 0] = trueColorImage.imageData.bytes[off2 + 2]; 11193 data[offset + 3] = trueColorImage.imageData.bytes[off2 + 3]; 11194 11195 offset += 4; 11196 off2 += 4; 11197 } 11198 } 11199 11200 return memory; 11201 } 11202 11203 this(MemoryImage mi) { 11204 int icon_len, width, height; 11205 11206 auto icon_win32 = fromMemoryImage(mi, icon_len, width, height); 11207 11208 /* 11209 PNG* png = readPnpngData); 11210 PNGHeader pngh = getHeader(png); 11211 void* icon_win32; 11212 if(pngh.depth == 4) { 11213 auto i = new Win32Icon!(16); 11214 i.fromPNG(png, pngh, icon_len, width, height); 11215 icon_win32 = i; 11216 } 11217 else if(pngh.depth == 8) { 11218 auto i = new Win32Icon!(256); 11219 i.fromPNG(png, pngh, icon_len, width, height); 11220 icon_win32 = i; 11221 } else assert(0); 11222 */ 11223 11224 hIcon = CreateIconFromResourceEx(icon_win32.ptr, icon_len, true, 0x00030000, width, height, 0); 11225 11226 if(hIcon is null) throw new WindowsApiException("CreateIconFromResourceEx", GetLastError()); 11227 } 11228 11229 ~this() { 11230 if(!thisIsGuiThread) return; // FIXME: leaks if multithreaded gc 11231 DestroyIcon(hIcon); 11232 } 11233 11234 HICON hIcon; 11235 } 11236 11237 11238 11239 11240 11241 11242 alias int delegate(HWND, UINT, WPARAM, LPARAM, out int) NativeEventHandler; 11243 alias HWND NativeWindowHandle; 11244 11245 extern(Windows) 11246 LRESULT WndProc(HWND hWnd, UINT iMessage, WPARAM wParam, LPARAM lParam) nothrow { 11247 try { 11248 if(SimpleWindow.handleNativeGlobalEvent !is null) { 11249 // it returns zero if the message is handled, so we won't do anything more there 11250 // do I like that though? 11251 int mustReturn; 11252 auto ret = SimpleWindow.handleNativeGlobalEvent(hWnd, iMessage, wParam, lParam, mustReturn); 11253 if(mustReturn) 11254 return ret; 11255 } 11256 11257 if(auto window = hWnd in CapableOfHandlingNativeEvent.nativeHandleMapping) { 11258 if(window.getNativeEventHandler !is null) { 11259 int mustReturn; 11260 auto ret = window.getNativeEventHandler()(hWnd, iMessage, wParam, lParam, mustReturn); 11261 if(mustReturn) 11262 return ret; 11263 } 11264 if(auto w = cast(SimpleWindow) (*window)) 11265 return w.windowProcedure(hWnd, iMessage, wParam, lParam); 11266 else 11267 return DefWindowProc(hWnd, iMessage, wParam, lParam); 11268 } else { 11269 return DefWindowProc(hWnd, iMessage, wParam, lParam); 11270 } 11271 } catch (Exception e) { 11272 try { 11273 sdpy_abort(e); 11274 return 0; 11275 } catch(Exception e) { assert(0); } 11276 } 11277 } 11278 11279 void sdpy_abort(Throwable e) nothrow { 11280 try 11281 MessageBoxA(null, (e.toString() ~ "\0").ptr, "Exception caught in WndProc", 0); 11282 catch(Exception e) 11283 MessageBoxA(null, "Exception.toString threw too!", "Exception caught in WndProc", 0); 11284 ExitProcess(1); 11285 } 11286 11287 mixin template NativeScreenPainterImplementation() { 11288 HDC hdc; 11289 HWND hwnd; 11290 //HDC windowHdc; 11291 HBITMAP oldBmp; 11292 11293 void create(PaintingHandle window) { 11294 hwnd = window; 11295 11296 if(auto sw = cast(SimpleWindow) this.window) { 11297 // drawing on a window, double buffer 11298 auto windowHdc = GetDC(hwnd); 11299 11300 auto buffer = sw.impl.buffer; 11301 if(buffer is null) { 11302 hdc = windowHdc; 11303 windowDc = true; 11304 } else { 11305 hdc = CreateCompatibleDC(windowHdc); 11306 11307 ReleaseDC(hwnd, windowHdc); 11308 11309 oldBmp = SelectObject(hdc, buffer); 11310 } 11311 } else { 11312 // drawing on something else, draw directly 11313 hdc = CreateCompatibleDC(null); 11314 SelectObject(hdc, window); 11315 } 11316 11317 // X doesn't draw a text background, so neither should we 11318 SetBkMode(hdc, TRANSPARENT); 11319 11320 ensureDefaultFontLoaded(); 11321 11322 if(defaultGuiFont) { 11323 SelectObject(hdc, defaultGuiFont); 11324 // DeleteObject(defaultGuiFont); 11325 } 11326 } 11327 11328 static HFONT defaultGuiFont; 11329 static void ensureDefaultFontLoaded() { 11330 static bool triedDefaultGuiFont = false; 11331 if(!triedDefaultGuiFont) { 11332 NONCLIENTMETRICS params; 11333 params.cbSize = params.sizeof; 11334 if(SystemParametersInfo(SPI_GETNONCLIENTMETRICS, params.sizeof, ¶ms, 0)) { 11335 defaultGuiFont = CreateFontIndirect(¶ms.lfMessageFont); 11336 } 11337 triedDefaultGuiFont = true; 11338 } 11339 } 11340 11341 private OperatingSystemFont _activeFont; 11342 11343 void setFont(OperatingSystemFont font) { 11344 _activeFont = font; 11345 if(font && font.font) { 11346 if(SelectObject(hdc, font.font) == HGDI_ERROR) { 11347 // error... how to handle tho? 11348 } else { 11349 11350 } 11351 } 11352 else if(defaultGuiFont) 11353 SelectObject(hdc, defaultGuiFont); 11354 } 11355 11356 arsd.color.Rectangle _clipRectangle; 11357 11358 void setClipRectangle(int x, int y, int width, int height) { 11359 auto old = _clipRectangle; 11360 _clipRectangle = arsd.color.Rectangle(Point(x, y), Size(width, height)); 11361 if(old == _clipRectangle) 11362 return; 11363 11364 if(width == 0 || height == 0) { 11365 SelectClipRgn(hdc, null); 11366 } else { 11367 auto region = CreateRectRgn(x, y, x + width, y + height); 11368 SelectClipRgn(hdc, region); 11369 DeleteObject(region); 11370 } 11371 } 11372 11373 11374 // just because we can on Windows... 11375 //void create(Image image); 11376 11377 void invalidateRect(Rectangle invalidRect) { 11378 RECT rect; 11379 rect.left = invalidRect.left; 11380 rect.right = invalidRect.right; 11381 rect.top = invalidRect.top; 11382 rect.bottom = invalidRect.bottom; 11383 InvalidateRect(hwnd, &rect, false); 11384 } 11385 bool manualInvalidations; 11386 11387 void dispose() { 11388 // FIXME: this.window.width/height is probably wrong 11389 // BitBlt(windowHdc, 0, 0, this.window.width, this.window.height, hdc, 0, 0, SRCCOPY); 11390 // ReleaseDC(hwnd, windowHdc); 11391 11392 // FIXME: it shouldn't invalidate the whole thing in all cases... it would be ideal to do this right 11393 if(cast(SimpleWindow) this.window) { 11394 if(!manualInvalidations) 11395 InvalidateRect(hwnd, cast(RECT*)null, false); // no need to erase bg as the whole thing gets bitblt'd ove 11396 } 11397 11398 if(originalPen !is null) 11399 SelectObject(hdc, originalPen); 11400 if(currentPen !is null) 11401 DeleteObject(currentPen); 11402 if(originalBrush !is null) 11403 SelectObject(hdc, originalBrush); 11404 if(currentBrush !is null) 11405 DeleteObject(currentBrush); 11406 11407 SelectObject(hdc, oldBmp); 11408 11409 if(windowDc) 11410 ReleaseDC(hwnd, hdc); 11411 else 11412 DeleteDC(hdc); 11413 11414 if(window.paintingFinishedDg !is null) 11415 window.paintingFinishedDg()(); 11416 } 11417 11418 bool windowDc; 11419 HPEN originalPen; 11420 HPEN currentPen; 11421 11422 Pen _activePen; 11423 11424 Color _outlineColor; 11425 11426 @property void pen(Pen p) { 11427 _activePen = p; 11428 _outlineColor = p.color; 11429 11430 HPEN pen; 11431 if(p.color.a == 0) { 11432 pen = GetStockObject(NULL_PEN); 11433 } else { 11434 int style = PS_SOLID; 11435 final switch(p.style) { 11436 case Pen.Style.Solid: 11437 style = PS_SOLID; 11438 break; 11439 case Pen.Style.Dashed: 11440 style = PS_DASH; 11441 break; 11442 case Pen.Style.Dotted: 11443 style = PS_DOT; 11444 break; 11445 } 11446 pen = CreatePen(style, p.width, RGB(p.color.r, p.color.g, p.color.b)); 11447 } 11448 auto orig = SelectObject(hdc, pen); 11449 if(originalPen is null) 11450 originalPen = orig; 11451 11452 if(currentPen !is null) 11453 DeleteObject(currentPen); 11454 11455 currentPen = pen; 11456 11457 // the outline is like a foreground since it's done that way on X 11458 SetTextColor(hdc, RGB(p.color.r, p.color.g, p.color.b)); 11459 11460 } 11461 11462 @property void rasterOp(RasterOp op) { 11463 int mode; 11464 final switch(op) { 11465 case RasterOp.normal: 11466 mode = R2_COPYPEN; 11467 break; 11468 case RasterOp.xor: 11469 mode = R2_XORPEN; 11470 break; 11471 } 11472 SetROP2(hdc, mode); 11473 } 11474 11475 HBRUSH originalBrush; 11476 HBRUSH currentBrush; 11477 Color _fillColor = Color(1, 1, 1, 1); // what are the odds that they'd set this?? 11478 @property void fillColor(Color c) { 11479 if(c == _fillColor) 11480 return; 11481 _fillColor = c; 11482 HBRUSH brush; 11483 if(c.a == 0) { 11484 brush = GetStockObject(HOLLOW_BRUSH); 11485 } else { 11486 brush = CreateSolidBrush(RGB(c.r, c.g, c.b)); 11487 } 11488 auto orig = SelectObject(hdc, brush); 11489 if(originalBrush is null) 11490 originalBrush = orig; 11491 11492 if(currentBrush !is null) 11493 DeleteObject(currentBrush); 11494 11495 currentBrush = brush; 11496 11497 // background color is NOT set because X doesn't draw text backgrounds 11498 // SetBkColor(hdc, RGB(255, 255, 255)); 11499 } 11500 11501 void drawImage(int x, int y, Image i, int ix, int iy, int w, int h) { 11502 BITMAP bm; 11503 11504 HDC hdcMem = CreateCompatibleDC(hdc); 11505 HBITMAP hbmOld = SelectObject(hdcMem, i.handle); 11506 11507 GetObject(i.handle, bm.sizeof, &bm); 11508 11509 // or should I AlphaBlend!??!?! 11510 BitBlt(hdc, x, y, w /* bm.bmWidth */, /*bm.bmHeight*/ h, hdcMem, ix, iy, SRCCOPY); 11511 11512 SelectObject(hdcMem, hbmOld); 11513 DeleteDC(hdcMem); 11514 } 11515 11516 void drawPixmap(Sprite s, int x, int y, int ix, int iy, int w, int h) { 11517 BITMAP bm; 11518 11519 HDC hdcMem = CreateCompatibleDC(hdc); 11520 HBITMAP hbmOld = SelectObject(hdcMem, s.handle); 11521 11522 GetObject(s.handle, bm.sizeof, &bm); 11523 11524 version(CRuntime_DigitalMars) goto noalpha; 11525 11526 // or should I AlphaBlend!??!?! note it is supposed to be premultiplied http://www.fengyuan.com/article/alphablend.html 11527 if(s.enableAlpha) { 11528 auto dw = w ? w : bm.bmWidth; 11529 auto dh = h ? h : bm.bmHeight; 11530 BLENDFUNCTION bf; 11531 bf.BlendOp = AC_SRC_OVER; 11532 bf.SourceConstantAlpha = 255; 11533 bf.AlphaFormat = AC_SRC_ALPHA; 11534 AlphaBlend(hdc, x, y, dw, dh, hdcMem, ix, iy, dw, dh, bf); 11535 } else { 11536 noalpha: 11537 BitBlt(hdc, x, y, w ? w : bm.bmWidth, h ? h : bm.bmHeight, hdcMem, ix, iy, SRCCOPY); 11538 } 11539 11540 SelectObject(hdcMem, hbmOld); 11541 DeleteDC(hdcMem); 11542 } 11543 11544 Size textSize(scope const(char)[] text) { 11545 bool dummyX; 11546 if(text.length == 0) { 11547 text = " "; 11548 dummyX = true; 11549 } 11550 RECT rect; 11551 WCharzBuffer buffer = WCharzBuffer(text); 11552 DrawTextW(hdc, buffer.ptr, cast(int) buffer.length, &rect, DT_CALCRECT | DT_NOPREFIX); 11553 return Size(dummyX ? 0 : rect.right, rect.bottom); 11554 } 11555 11556 void drawText(int x, int y, int x2, int y2, scope const(char)[] text, uint alignment) { 11557 if(text.length && text[$-1] == '\n') 11558 text = text[0 .. $-1]; // tailing newlines are weird on windows... 11559 if(text.length && text[$-1] == '\r') 11560 text = text[0 .. $-1]; 11561 11562 WCharzBuffer buffer = WCharzBuffer(text, WindowsStringConversionFlags.convertNewLines); 11563 if(x2 == 0 && y2 == 0) { 11564 TextOutW(hdc, x, y, buffer.ptr, cast(int) buffer.length); 11565 } else { 11566 RECT rect; 11567 rect.left = x; 11568 rect.top = y; 11569 rect.right = x2; 11570 rect.bottom = y2; 11571 11572 uint mode = DT_LEFT; 11573 if(alignment & TextAlignment.Right) 11574 mode = DT_RIGHT; 11575 else if(alignment & TextAlignment.Center) 11576 mode = DT_CENTER; 11577 11578 // FIXME: vcenter on windows only works with single line, but I want it to work in all cases 11579 if(alignment & TextAlignment.VerticalCenter) 11580 mode |= DT_VCENTER | DT_SINGLELINE; 11581 11582 DrawTextW(hdc, buffer.ptr, cast(int) buffer.length, &rect, mode | DT_NOPREFIX); 11583 } 11584 11585 /* 11586 uint mode; 11587 11588 if(alignment & TextAlignment.Center) 11589 mode = TA_CENTER; 11590 11591 SetTextAlign(hdc, mode); 11592 */ 11593 } 11594 11595 int fontHeight() { 11596 TEXTMETRIC metric; 11597 if(GetTextMetricsW(hdc, &metric)) { 11598 return metric.tmHeight; 11599 } 11600 11601 return 16; // idk just guessing here, maybe we should throw 11602 } 11603 11604 void drawPixel(int x, int y) { 11605 SetPixel(hdc, x, y, RGB(_activePen.color.r, _activePen.color.g, _activePen.color.b)); 11606 } 11607 11608 // The basic shapes, outlined 11609 11610 void drawLine(int x1, int y1, int x2, int y2) { 11611 MoveToEx(hdc, x1, y1, null); 11612 LineTo(hdc, x2, y2); 11613 } 11614 11615 void drawRectangle(int x, int y, int width, int height) { 11616 // FIXME: with a wider pen this might not draw quite right. im not sure. 11617 gdi.Rectangle(hdc, x, y, x + width, y + height); 11618 } 11619 11620 /// Arguments are the points of the bounding rectangle 11621 void drawEllipse(int x1, int y1, int x2, int y2) { 11622 Ellipse(hdc, x1, y1, x2, y2); 11623 } 11624 11625 void drawArc(int x1, int y1, int width, int height, int start, int finish) { 11626 if((start % (360*64)) == (finish % (360*64))) 11627 drawEllipse(x1, y1, x1 + width, y1 + height); 11628 else { 11629 import core.stdc.math; 11630 float startAngle = cast(float) start / 64.0 / 180.0 * 3.14159265358979323; 11631 float endAngle = cast(float) finish / 64.0 / 180.0 * 3.14159265358979323; 11632 11633 auto c1 = cast(int) roundf(cos(startAngle) * width / 2 + x1 + width / 2); 11634 auto c2 = cast(int) roundf(-sin(startAngle) * height / 2 + y1 + height / 2); 11635 auto c3 = cast(int) roundf(cos(endAngle) * width / 2 + x1 + width / 2); 11636 auto c4 = cast(int) roundf(-sin(endAngle) * height / 2 + y1 + height / 2); 11637 11638 if(_activePen.color.a) 11639 Arc(hdc, x1, y1, x1 + width + 1, y1 + height + 1, c1, c2, c3, c4); 11640 if(_fillColor.a) 11641 Pie(hdc, x1, y1, x1 + width + 1, y1 + height + 1, c1, c2, c3, c4); 11642 } 11643 } 11644 11645 void drawPolygon(Point[] vertexes) { 11646 POINT[] points; 11647 points.length = vertexes.length; 11648 11649 foreach(i, p; vertexes) { 11650 points[i].x = p.x; 11651 points[i].y = p.y; 11652 } 11653 11654 Polygon(hdc, points.ptr, cast(int) points.length); 11655 } 11656 } 11657 11658 11659 // Mix this into the SimpleWindow class 11660 mixin template NativeSimpleWindowImplementation() { 11661 int curHidden = 0; // counter 11662 __gshared static bool[string] knownWinClasses; 11663 static bool altPressed = false; 11664 11665 HANDLE oldCursor; 11666 11667 void hideCursor () { 11668 if(curHidden == 0) 11669 oldCursor = SetCursor(null); 11670 ++curHidden; 11671 } 11672 11673 void showCursor () { 11674 --curHidden; 11675 if(curHidden == 0) { 11676 SetCursor(currentCursor is null ? oldCursor : currentCursor); // show it immediately without waiting for mouse movement 11677 } 11678 } 11679 11680 11681 int minWidth = 0, minHeight = 0, maxWidth = int.max, maxHeight = int.max; 11682 11683 void setMinSize (int minwidth, int minheight) { 11684 minWidth = minwidth; 11685 minHeight = minheight; 11686 } 11687 void setMaxSize (int maxwidth, int maxheight) { 11688 maxWidth = maxwidth; 11689 maxHeight = maxheight; 11690 } 11691 11692 // FIXME i'm not sure that Windows has this functionality 11693 // though it is nonessential anyway. 11694 void setResizeGranularity (int granx, int grany) {} 11695 11696 ScreenPainter getPainter(bool manualInvalidations) { 11697 return ScreenPainter(this, hwnd, manualInvalidations); 11698 } 11699 11700 HBITMAP buffer; 11701 11702 void setTitle(string title) { 11703 WCharzBuffer bfr = WCharzBuffer(title); 11704 SetWindowTextW(hwnd, bfr.ptr); 11705 } 11706 11707 string getTitle() { 11708 auto len = GetWindowTextLengthW(hwnd); 11709 if (!len) 11710 return null; 11711 wchar[256] tmpBuffer; 11712 wchar[] buffer = (len <= tmpBuffer.length) ? tmpBuffer[] : new wchar[len]; 11713 auto len2 = GetWindowTextW(hwnd, buffer.ptr, cast(int) buffer.length); 11714 auto str = buffer[0 .. len2]; 11715 return makeUtf8StringFromWindowsString(str); 11716 } 11717 11718 void move(int x, int y) { 11719 RECT rect; 11720 GetWindowRect(hwnd, &rect); 11721 // move it while maintaining the same size... 11722 MoveWindow(hwnd, x, y, rect.right - rect.left, rect.bottom - rect.top, true); 11723 } 11724 11725 void resize(int w, int h) { 11726 RECT rect; 11727 GetWindowRect(hwnd, &rect); 11728 11729 RECT client; 11730 GetClientRect(hwnd, &client); 11731 11732 rect.right = rect.right - client.right + w; 11733 rect.bottom = rect.bottom - client.bottom + h; 11734 11735 // same position, new size for the client rectangle 11736 MoveWindow(hwnd, rect.left, rect.top, rect.right, rect.bottom, true); 11737 11738 updateOpenglViewportIfNeeded(w, h); 11739 } 11740 11741 void moveResize (int x, int y, int w, int h) { 11742 // what's given is the client rectangle, we need to adjust 11743 11744 RECT rect; 11745 rect.left = x; 11746 rect.top = y; 11747 rect.right = w + x; 11748 rect.bottom = h + y; 11749 if(!AdjustWindowRect(&rect, GetWindowLong(hwnd, GWL_STYLE), GetMenu(hwnd) !is null)) 11750 throw new WindowsApiException("AdjustWindowRect", GetLastError()); 11751 11752 MoveWindow(hwnd, rect.left, rect.top, rect.right - rect.left, rect.bottom - rect.top, true); 11753 updateOpenglViewportIfNeeded(w, h); 11754 if (windowResized !is null) windowResized(w, h); 11755 } 11756 11757 version(without_opengl) {} else { 11758 HGLRC ghRC; 11759 HDC ghDC; 11760 } 11761 11762 void createWindow(int width, int height, string title, OpenGlOptions opengl, SimpleWindow parent) { 11763 string cnamec; 11764 if (sdpyWindowClassStr is null) loadBinNameToWindowClassName(); 11765 if (sdpyWindowClassStr is null || sdpyWindowClassStr[0] == 0) { 11766 cnamec = "DSimpleWindow"; 11767 } else { 11768 cnamec = sdpyWindowClass; 11769 } 11770 11771 WCharzBuffer cn = WCharzBuffer(cnamec); 11772 11773 HINSTANCE hInstance = cast(HINSTANCE) GetModuleHandle(null); 11774 11775 if(cnamec !in knownWinClasses) { 11776 WNDCLASSEX wc; 11777 11778 // FIXME: I might be able to use cbWndExtra to hold the pointer back 11779 // to the object. Maybe. 11780 wc.cbSize = wc.sizeof; 11781 wc.cbClsExtra = 0; 11782 wc.cbWndExtra = 0; 11783 wc.hbrBackground = cast(HBRUSH) (COLOR_WINDOW+1); // GetStockObject(WHITE_BRUSH); 11784 wc.hCursor = LoadCursorW(null, IDC_ARROW); 11785 wc.hIcon = LoadIcon(hInstance, null); 11786 wc.hInstance = hInstance; 11787 wc.lpfnWndProc = &WndProc; 11788 wc.lpszClassName = cn.ptr; 11789 wc.hIconSm = null; 11790 wc.style = CS_HREDRAW | CS_VREDRAW | CS_DBLCLKS; 11791 if(!RegisterClassExW(&wc)) 11792 throw new WindowsApiException("RegisterClassExW", GetLastError()); 11793 knownWinClasses[cnamec] = true; 11794 } 11795 11796 int style; 11797 uint flags = WS_EX_ACCEPTFILES; // accept drag-drop files 11798 11799 // FIXME: windowType and customizationFlags 11800 final switch(windowType) { 11801 case WindowTypes.normal: 11802 if(resizability == Resizability.fixedSize) { 11803 style = WS_SYSMENU | WS_OVERLAPPED | WS_CAPTION; 11804 } else { 11805 style = WS_OVERLAPPEDWINDOW; 11806 } 11807 break; 11808 case WindowTypes.undecorated: 11809 style = WS_POPUP | WS_SYSMENU; 11810 break; 11811 case WindowTypes.eventOnly: 11812 _hidden = true; 11813 break; 11814 case WindowTypes.dropdownMenu: 11815 case WindowTypes.popupMenu: 11816 case WindowTypes.notification: 11817 style = WS_POPUP; 11818 flags |= WS_EX_NOACTIVATE; 11819 break; 11820 case WindowTypes.nestedChild: 11821 style = WS_CHILD; 11822 break; 11823 case WindowTypes.minimallyWrapped: 11824 assert(0, "construct minimally wrapped through the other ctor overlad"); 11825 } 11826 11827 if ((customizationFlags & WindowFlags.extraComposite) != 0) 11828 flags |= WS_EX_LAYERED; // composite window for better performance and effects support 11829 11830 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 11831 CW_USEDEFAULT, CW_USEDEFAULT, width, height, 11832 parent is null ? null : parent.impl.hwnd, null, hInstance, null); 11833 11834 if ((customizationFlags & WindowFlags.extraComposite) != 0) 11835 setOpacity(255); 11836 11837 SimpleWindow.nativeMapping[hwnd] = this; 11838 CapableOfHandlingNativeEvent.nativeHandleMapping[hwnd] = this; 11839 11840 if(windowType == WindowTypes.eventOnly) 11841 return; 11842 11843 HDC hdc = GetDC(hwnd); 11844 11845 11846 version(without_opengl) {} 11847 else { 11848 if(opengl == OpenGlOptions.yes) { 11849 if(!openGlLibrariesSuccessfullyLoaded) throw new Exception("OpenGL libraries did not load"); 11850 static if (SdpyIsUsingIVGLBinds) {if (glbindGetProcAddress("glHint") is null) assert(0, "GL: error loading OpenGL"); } // loads all necessary functions 11851 static if (SdpyIsUsingIVGLBinds) import iv.glbinds; // override druntime windows imports 11852 ghDC = hdc; 11853 PIXELFORMATDESCRIPTOR pfd; 11854 11855 pfd.nSize = PIXELFORMATDESCRIPTOR.sizeof; 11856 pfd.nVersion = 1; 11857 pfd.dwFlags = PFD_DRAW_TO_WINDOW | PFD_SUPPORT_OPENGL | PFD_DOUBLEBUFFER; 11858 pfd.dwLayerMask = PFD_MAIN_PLANE; 11859 pfd.iPixelType = PFD_TYPE_RGBA; 11860 pfd.cColorBits = 24; 11861 pfd.cDepthBits = 24; 11862 pfd.cAccumBits = 0; 11863 pfd.cStencilBits = 8; // any reasonable OpenGL implementation should support this anyway 11864 11865 auto pixelformat = ChoosePixelFormat(hdc, &pfd); 11866 11867 if (pixelformat == 0) 11868 throw new WindowsApiException("ChoosePixelFormat", GetLastError()); 11869 11870 if (SetPixelFormat(hdc, pixelformat, &pfd) == 0) 11871 throw new WindowsApiException("SetPixelFormat", GetLastError()); 11872 11873 if (sdpyOpenGLContextVersion && wglCreateContextAttribsARB is null) { 11874 // windoze is idiotic: we have to have OpenGL context to get function addresses 11875 // so we will create fake context to get that stupid address 11876 auto tmpcc = wglCreateContext(ghDC); 11877 if (tmpcc !is null) { 11878 scope(exit) { wglMakeCurrent(ghDC, null); wglDeleteContext(tmpcc); } 11879 wglMakeCurrent(ghDC, tmpcc); 11880 wglInitOtherFunctions(); 11881 } 11882 } 11883 11884 if (wglCreateContextAttribsARB !is null && sdpyOpenGLContextVersion) { 11885 int[9] contextAttribs = [ 11886 WGL_CONTEXT_MAJOR_VERSION_ARB, (sdpyOpenGLContextVersion>>8), 11887 WGL_CONTEXT_MINOR_VERSION_ARB, (sdpyOpenGLContextVersion&0xff), 11888 WGL_CONTEXT_PROFILE_MASK_ARB, (sdpyOpenGLContextCompatible ? WGL_CONTEXT_COMPATIBILITY_PROFILE_BIT_ARB : WGL_CONTEXT_CORE_PROFILE_BIT_ARB), 11889 // for modern context, set "forward compatibility" flag too 11890 (sdpyOpenGLContextCompatible ? 0/*None*/ : WGL_CONTEXT_FLAGS_ARB), WGL_CONTEXT_FORWARD_COMPATIBLE_BIT_ARB, 11891 0/*None*/, 11892 ]; 11893 ghRC = wglCreateContextAttribsARB(ghDC, null, contextAttribs.ptr); 11894 if (ghRC is null && sdpyOpenGLContextAllowFallback) { 11895 // activate fallback mode 11896 // 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; 11897 ghRC = wglCreateContext(ghDC); 11898 } 11899 if (ghRC is null) 11900 throw new WindowsApiException("wglCreateContextAttribsARB", GetLastError()); 11901 } else { 11902 // try to do at least something 11903 if (sdpyOpenGLContextAllowFallback || sdpyOpenGLContextVersion == 0) { 11904 sdpyOpenGLContextVersion = 0; 11905 ghRC = wglCreateContext(ghDC); 11906 } 11907 if (ghRC is null) 11908 throw new WindowsApiException("wglCreateContext", GetLastError()); 11909 } 11910 } 11911 } 11912 11913 if(opengl == OpenGlOptions.no) { 11914 buffer = CreateCompatibleBitmap(hdc, width, height); 11915 11916 auto hdcBmp = CreateCompatibleDC(hdc); 11917 // make sure it's filled with a blank slate 11918 auto oldBmp = SelectObject(hdcBmp, buffer); 11919 auto oldBrush = SelectObject(hdcBmp, GetStockObject(WHITE_BRUSH)); 11920 auto oldPen = SelectObject(hdcBmp, GetStockObject(WHITE_PEN)); 11921 gdi.Rectangle(hdcBmp, 0, 0, width, height); 11922 SelectObject(hdcBmp, oldBmp); 11923 SelectObject(hdcBmp, oldBrush); 11924 SelectObject(hdcBmp, oldPen); 11925 DeleteDC(hdcBmp); 11926 11927 bmpWidth = width; 11928 bmpHeight = height; 11929 11930 ReleaseDC(hwnd, hdc); // we keep this in opengl mode since it is a class member now 11931 } 11932 11933 // We want the window's client area to match the image size 11934 RECT rcClient, rcWindow; 11935 POINT ptDiff; 11936 GetClientRect(hwnd, &rcClient); 11937 GetWindowRect(hwnd, &rcWindow); 11938 ptDiff.x = (rcWindow.right - rcWindow.left) - rcClient.right; 11939 ptDiff.y = (rcWindow.bottom - rcWindow.top) - rcClient.bottom; 11940 MoveWindow(hwnd,rcWindow.left, rcWindow.top, width + ptDiff.x, height + ptDiff.y, true); 11941 11942 if ((customizationFlags&WindowFlags.dontAutoShow) == 0) { 11943 ShowWindow(hwnd, SW_SHOWNORMAL); 11944 } else { 11945 _hidden = true; 11946 } 11947 this._visibleForTheFirstTimeCalled = false; // hack! 11948 } 11949 11950 11951 void dispose() { 11952 if(buffer) 11953 DeleteObject(buffer); 11954 } 11955 11956 void closeWindow() { 11957 if(ghRC) { 11958 wglDeleteContext(ghRC); 11959 ghRC = null; 11960 } 11961 DestroyWindow(hwnd); 11962 } 11963 11964 bool setOpacity(ubyte alpha) { 11965 return SetLayeredWindowAttributes(hwnd, 0, alpha, LWA_ALPHA) == TRUE; 11966 } 11967 11968 HANDLE currentCursor; 11969 11970 // returns zero if it recognized the event 11971 static int triggerEvents(HWND hwnd, uint msg, WPARAM wParam, LPARAM lParam, int offsetX, int offsetY, SimpleWindow wind) { 11972 MouseEvent mouse; 11973 11974 void mouseEvent(bool isScreen, ulong mods) { 11975 auto x = LOWORD(lParam); 11976 auto y = HIWORD(lParam); 11977 if(isScreen) { 11978 POINT p; 11979 p.x = x; 11980 p.y = y; 11981 ScreenToClient(hwnd, &p); 11982 x = cast(ushort) p.x; 11983 y = cast(ushort) p.y; 11984 } 11985 11986 if(wind.resizability == Resizability.automaticallyScaleIfPossible) { 11987 x = cast(ushort)( x * wind._virtualWidth / wind._width ); 11988 y = cast(ushort)( y * wind._virtualHeight / wind._height ); 11989 } 11990 11991 mouse.x = x + offsetX; 11992 mouse.y = y + offsetY; 11993 11994 wind.mdx(mouse); 11995 mouse.modifierState = cast(int) mods; 11996 mouse.window = wind; 11997 11998 if(wind.handleMouseEvent) 11999 wind.handleMouseEvent(mouse); 12000 } 12001 12002 switch(msg) { 12003 case WM_GETMINMAXINFO: 12004 MINMAXINFO* mmi = cast(MINMAXINFO*) lParam; 12005 12006 if(wind.minWidth > 0) { 12007 RECT rect; 12008 rect.left = 100; 12009 rect.top = 100; 12010 rect.right = wind.minWidth + 100; 12011 rect.bottom = wind.minHeight + 100; 12012 if(!AdjustWindowRect(&rect, GetWindowLong(wind.hwnd, GWL_STYLE), GetMenu(wind.hwnd) !is null)) 12013 throw new WindowsApiException("AdjustWindowRect", GetLastError()); 12014 12015 mmi.ptMinTrackSize.x = rect.right - rect.left; 12016 mmi.ptMinTrackSize.y = rect.bottom - rect.top; 12017 } 12018 12019 if(wind.maxWidth < int.max) { 12020 RECT rect; 12021 rect.left = 100; 12022 rect.top = 100; 12023 rect.right = wind.maxWidth + 100; 12024 rect.bottom = wind.maxHeight + 100; 12025 if(!AdjustWindowRect(&rect, GetWindowLong(wind.hwnd, GWL_STYLE), GetMenu(wind.hwnd) !is null)) 12026 throw new WindowsApiException("AdjustWindowRect", GetLastError()); 12027 12028 mmi.ptMaxTrackSize.x = rect.right - rect.left; 12029 mmi.ptMaxTrackSize.y = rect.bottom - rect.top; 12030 } 12031 break; 12032 case WM_CHAR: 12033 wchar c = cast(wchar) wParam; 12034 if(wind.handleCharEvent) 12035 wind.handleCharEvent(cast(dchar) c); 12036 break; 12037 case WM_SETFOCUS: 12038 case WM_KILLFOCUS: 12039 wind._focused = (msg == WM_SETFOCUS); 12040 if (msg == WM_SETFOCUS) altPressed = false; //k8: reset alt state on defocus (it is better than nothing...) 12041 if(wind.onFocusChange) 12042 wind.onFocusChange(msg == WM_SETFOCUS); 12043 break; 12044 12045 case WM_SYSKEYDOWN: 12046 goto case; 12047 case WM_SYSKEYUP: 12048 if(lParam & (1 << 29)) { 12049 goto case; 12050 } else { 12051 // no window has keyboard focus 12052 goto default; 12053 } 12054 case WM_KEYDOWN: 12055 case WM_KEYUP: 12056 KeyEvent ev; 12057 ev.key = cast(Key) wParam; 12058 ev.pressed = (msg == WM_KEYDOWN || msg == WM_SYSKEYDOWN); 12059 if (wParam == 0x12) ev.key = Key.Alt; // windows does it this way 12060 12061 ev.hardwareCode = (lParam & 0xff0000) >> 16; 12062 12063 if(GetKeyState(Key.Shift)&0x8000 || GetKeyState(Key.Shift_r)&0x8000) 12064 ev.modifierState |= ModifierState.shift; 12065 //k8: this doesn't work; thanks for nothing, windows 12066 /*if(GetKeyState(Key.Alt)&0x8000 || GetKeyState(Key.Alt_r)&0x8000) 12067 ev.modifierState |= ModifierState.alt;*/ 12068 // this never seems to actually be set 12069 // if (lParam & 0x2000 /* KF_ALTDOWN */) ev.modifierState |= ModifierState.alt; else ev.modifierState &= ~ModifierState.alt; 12070 12071 if (wParam == 0x12) { 12072 altPressed = (msg == WM_SYSKEYDOWN); 12073 } 12074 12075 if(msg == WM_KEYDOWN || msg == WM_KEYUP) { 12076 altPressed = false; 12077 } 12078 // sdpyPrintDebugString(altPressed ? "alt down" : " up "); 12079 12080 if (altPressed) ev.modifierState |= ModifierState.alt; else ev.modifierState &= ~ModifierState.alt; 12081 if(GetKeyState(Key.Ctrl)&0x8000 || GetKeyState(Key.Ctrl_r)&0x8000) 12082 ev.modifierState |= ModifierState.ctrl; 12083 if(GetKeyState(Key.Windows)&0x8000 || GetKeyState(Key.Windows_r)&0x8000) 12084 ev.modifierState |= ModifierState.windows; 12085 if(GetKeyState(Key.NumLock)) 12086 ev.modifierState |= ModifierState.numLock; 12087 if(GetKeyState(Key.CapsLock)) 12088 ev.modifierState |= ModifierState.capsLock; 12089 12090 /+ 12091 // we always want to send the character too, so let's convert it 12092 ubyte[256] state; 12093 wchar[16] buffer; 12094 GetKeyboardState(state.ptr); 12095 ToUnicodeEx(wParam, lParam, state.ptr, buffer.ptr, buffer.length, 0, null); 12096 12097 foreach(dchar d; buffer) { 12098 ev.character = d; 12099 break; 12100 } 12101 +/ 12102 12103 ev.window = wind; 12104 if(wind.handleKeyEvent) 12105 wind.handleKeyEvent(ev); 12106 break; 12107 case 0x020a /*WM_MOUSEWHEEL*/: 12108 // send click 12109 mouse.type = cast(MouseEventType) 1; 12110 mouse.button = ((GET_WHEEL_DELTA_WPARAM(wParam) > 0) ? MouseButton.wheelUp : MouseButton.wheelDown); 12111 mouseEvent(true, LOWORD(wParam)); 12112 12113 // also send release 12114 mouse.type = cast(MouseEventType) 2; 12115 mouse.button = ((GET_WHEEL_DELTA_WPARAM(wParam) > 0) ? MouseButton.wheelUp : MouseButton.wheelDown); 12116 mouseEvent(true, LOWORD(wParam)); 12117 break; 12118 case WM_MOUSEMOVE: 12119 mouse.type = cast(MouseEventType) 0; 12120 mouseEvent(false, wParam); 12121 break; 12122 case WM_LBUTTONDOWN: 12123 case WM_LBUTTONDBLCLK: 12124 mouse.type = cast(MouseEventType) 1; 12125 mouse.button = MouseButton.left; 12126 mouse.doubleClick = msg == WM_LBUTTONDBLCLK; 12127 mouseEvent(false, wParam); 12128 break; 12129 case WM_LBUTTONUP: 12130 mouse.type = cast(MouseEventType) 2; 12131 mouse.button = MouseButton.left; 12132 mouseEvent(false, wParam); 12133 break; 12134 case WM_RBUTTONDOWN: 12135 case WM_RBUTTONDBLCLK: 12136 mouse.type = cast(MouseEventType) 1; 12137 mouse.button = MouseButton.right; 12138 mouse.doubleClick = msg == WM_RBUTTONDBLCLK; 12139 mouseEvent(false, wParam); 12140 break; 12141 case WM_RBUTTONUP: 12142 mouse.type = cast(MouseEventType) 2; 12143 mouse.button = MouseButton.right; 12144 mouseEvent(false, wParam); 12145 break; 12146 case WM_MBUTTONDOWN: 12147 case WM_MBUTTONDBLCLK: 12148 mouse.type = cast(MouseEventType) 1; 12149 mouse.button = MouseButton.middle; 12150 mouse.doubleClick = msg == WM_MBUTTONDBLCLK; 12151 mouseEvent(false, wParam); 12152 break; 12153 case WM_MBUTTONUP: 12154 mouse.type = cast(MouseEventType) 2; 12155 mouse.button = MouseButton.middle; 12156 mouseEvent(false, wParam); 12157 break; 12158 case WM_XBUTTONDOWN: 12159 case WM_XBUTTONDBLCLK: 12160 mouse.type = cast(MouseEventType) 1; 12161 mouse.button = HIWORD(wParam) == 1 ? MouseButton.backButton : MouseButton.forwardButton; 12162 mouse.doubleClick = msg == WM_XBUTTONDBLCLK; 12163 mouseEvent(false, wParam); 12164 return 1; // MSDN says special treatment here, return TRUE to bypass simulation programs 12165 case WM_XBUTTONUP: 12166 mouse.type = cast(MouseEventType) 2; 12167 mouse.button = HIWORD(wParam) == 1 ? MouseButton.backButton : MouseButton.forwardButton; 12168 mouseEvent(false, wParam); 12169 return 1; // see: https://msdn.microsoft.com/en-us/library/windows/desktop/ms646246(v=vs.85).aspx 12170 12171 default: return 1; 12172 } 12173 return 0; 12174 } 12175 12176 HWND hwnd; 12177 private int oldWidth; 12178 private int oldHeight; 12179 private bool inSizeMove; 12180 12181 /++ 12182 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. 12183 12184 History: 12185 Added November 23, 2021 12186 12187 Not fully stable, may be moved out of the impl struct. 12188 12189 Default value changed to `true` on February 15, 2021 12190 +/ 12191 bool doLiveResizing = true; 12192 12193 package int bmpWidth; 12194 package int bmpHeight; 12195 12196 // the extern(Windows) wndproc should just forward to this 12197 LRESULT windowProcedure(HWND hwnd, uint msg, WPARAM wParam, LPARAM lParam) { 12198 try { 12199 assert(hwnd is this.hwnd); 12200 12201 if(triggerEvents(hwnd, msg, wParam, lParam, 0, 0, this)) 12202 switch(msg) { 12203 case WM_MENUCHAR: // menu active but key not associated with a thing. 12204 // you would ideally use this for like a search function but sdpy not that ideally designed. alas. 12205 // The main things we can do are select, execute, close, or ignore 12206 // the default is ignore, but it doesn't *just* ignore it - it also dings an audio alert to 12207 // the user. This can be a bit annoying for sdpy things so instead im overriding and setting it 12208 // to close, which can be really annoying when you hit the wrong button. but meh i think for sdpy 12209 // that's the lesser bad choice rn. Can always override by returning true in triggerEvents.... 12210 12211 // returns the value in the *high order word* of the return value 12212 // hence the << 16 12213 return 1 << 16; // MNC_CLOSE, close the menu without dinging at the user 12214 case WM_SETCURSOR: 12215 if(cast(HWND) wParam !is hwnd) 12216 return 0; // further processing elsewhere 12217 12218 if(LOWORD(lParam) == HTCLIENT && (this.curHidden > 0 || currentCursor !is null)) { 12219 SetCursor(this.curHidden > 0 ? null : currentCursor); 12220 return 1; 12221 } else { 12222 return DefWindowProc(hwnd, msg, wParam, lParam); 12223 } 12224 //break; 12225 12226 case WM_CLOSE: 12227 if (this.closeQuery !is null) this.closeQuery(); else this.close(); 12228 break; 12229 case WM_DESTROY: 12230 if (this.onDestroyed !is null) try { this.onDestroyed(); } catch (Exception e) {} // sorry 12231 SimpleWindow.nativeMapping.remove(hwnd); 12232 CapableOfHandlingNativeEvent.nativeHandleMapping.remove(hwnd); 12233 12234 bool anyImportant = false; 12235 foreach(SimpleWindow w; SimpleWindow.nativeMapping) 12236 if(w.beingOpenKeepsAppOpen) { 12237 anyImportant = true; 12238 break; 12239 } 12240 if(!anyImportant) { 12241 PostQuitMessage(0); 12242 } 12243 break; 12244 case 0x02E0 /*WM_DPICHANGED*/: 12245 this.actualDpi_ = LOWORD(wParam); // hiword is the y param but it is the same per docs 12246 12247 RECT* prcNewWindow = cast(RECT*)lParam; 12248 // docs say this is the recommended position and we should honor it 12249 SetWindowPos(hwnd, 12250 null, 12251 prcNewWindow.left, 12252 prcNewWindow.top, 12253 prcNewWindow.right - prcNewWindow.left, 12254 prcNewWindow.bottom - prcNewWindow.top, 12255 SWP_NOZORDER | SWP_NOACTIVATE); 12256 12257 // doing this because of https://github.com/microsoft/Windows-classic-samples/blob/main/Samples/DPIAwarenessPerWindow/client/DpiAwarenessContext.cpp 12258 // im not sure it is completely correct 12259 // but without it the tabs and such do look weird as things change. 12260 if(SystemParametersInfoForDpi) { 12261 LOGFONT lfText; 12262 SystemParametersInfoForDpi(SPI_GETICONTITLELOGFONT, lfText.sizeof, &lfText, FALSE, this.actualDpi_); 12263 HFONT hFontNew = CreateFontIndirect(&lfText); 12264 if (hFontNew) 12265 { 12266 //DeleteObject(hFontOld); 12267 static extern(Windows) BOOL helper(HWND hWnd, LPARAM lParam) { 12268 SendMessage(hWnd, WM_SETFONT, cast(WPARAM)lParam, MAKELPARAM(TRUE, 0)); 12269 return TRUE; 12270 } 12271 EnumChildWindows(hwnd, &helper, cast(LPARAM) hFontNew); 12272 } 12273 } 12274 12275 if(this.onDpiChanged) 12276 this.onDpiChanged(); 12277 break; 12278 case WM_ENTERIDLE: 12279 // when a menu is up, it stops normal event processing (modal message loop) 12280 // but this at least gives us a chance to SOMETIMES catch up 12281 // FIXME: I can use SetTimer while idle to keep working i think... but idk when i'd destroy it. 12282 SimpleWindow.processAllCustomEvents; 12283 SimpleWindow.processAllCustomEvents; 12284 SleepEx(0, true); 12285 break; 12286 case WM_SIZE: 12287 if(wParam == 1 /* SIZE_MINIMIZED */) 12288 break; 12289 _width = LOWORD(lParam); 12290 _height = HIWORD(lParam); 12291 12292 // I want to avoid tearing in the windows (my code is inefficient 12293 // so this is a hack around that) so while sizing, we don't trigger, 12294 // but we do want to trigger on events like mazimize. 12295 if(!inSizeMove || doLiveResizing) 12296 goto size_changed; 12297 break; 12298 /+ 12299 case WM_SIZING: 12300 writeln("size"); 12301 break; 12302 +/ 12303 // I don't like the tearing I get when redrawing on WM_SIZE 12304 // (I know there's other ways to fix that but I don't like that behavior anyway) 12305 // so instead it is going to redraw only at the end of a size. 12306 case 0x0231: /* WM_ENTERSIZEMOVE */ 12307 inSizeMove = true; 12308 break; 12309 case 0x0232: /* WM_EXITSIZEMOVE */ 12310 inSizeMove = false; 12311 12312 size_changed: 12313 12314 // nothing relevant changed, don't bother redrawing 12315 if(oldWidth == _width && oldHeight == _height) { 12316 if(msg == 0x0232) 12317 goto finalize_resize; 12318 break; 12319 } 12320 12321 // note: OpenGL windows don't use a backing bmp, so no need to change them 12322 // if resizability is anything other than allowResizing, it is meant to either stretch the one image or just do nothing 12323 if(openglMode == OpenGlOptions.no) { // && resizability == Resizability.allowResizing) { 12324 // gotta get the double buffer bmp to match the window 12325 // FIXME: could this be more efficient? it never relinquishes a large bitmap 12326 12327 // if it is auto-scaled, we keep the backing bitmap the same size all the time 12328 if(resizability != Resizability.automaticallyScaleIfPossible) 12329 if(_width > bmpWidth || _height > bmpHeight) { 12330 auto hdc = GetDC(hwnd); 12331 auto oldBuffer = buffer; 12332 buffer = CreateCompatibleBitmap(hdc, _width, _height); 12333 12334 auto hdcBmp = CreateCompatibleDC(hdc); 12335 auto oldBmp = SelectObject(hdcBmp, buffer); 12336 12337 auto hdcOldBmp = CreateCompatibleDC(hdc); 12338 auto oldOldBmp = SelectObject(hdcOldBmp, oldBuffer); 12339 12340 /+ 12341 RECT r; 12342 r.left = 0; 12343 r.top = 0; 12344 r.right = width; 12345 r.bottom = height; 12346 auto c = Color.green; 12347 auto brush = CreateSolidBrush(RGB(c.r, c.g, c.b)); 12348 FillRect(hdcBmp, &r, brush); 12349 DeleteObject(brush); 12350 +/ 12351 12352 BitBlt(hdcBmp, 0, 0, bmpWidth, bmpHeight, hdcOldBmp, 0, 0, SRCCOPY); 12353 12354 bmpWidth = _width; 12355 bmpHeight = _height; 12356 12357 SelectObject(hdcOldBmp, oldOldBmp); 12358 DeleteDC(hdcOldBmp); 12359 12360 SelectObject(hdcBmp, oldBmp); 12361 DeleteDC(hdcBmp); 12362 12363 ReleaseDC(hwnd, hdc); 12364 12365 DeleteObject(oldBuffer); 12366 } 12367 } 12368 12369 updateOpenglViewportIfNeeded(_width, _height); 12370 12371 if(resizability != Resizability.automaticallyScaleIfPossible) 12372 if(windowResized !is null) 12373 windowResized(_width, _height); 12374 12375 /+ 12376 if(inSizeMove) { 12377 // SimpleWindow.processAllCustomEvents(); 12378 // SimpleWindow.processAllCustomEvents(); 12379 12380 //RedrawWindow(hwnd, null, null, RDW_ERASE | RDW_INVALIDATE | RDW_ALLCHILDREN); 12381 //sdpyPrintDebugString("redraw b"); 12382 } else { 12383 +/ { 12384 finalize_resize: 12385 // when it is all done, make sure everything is freshly drawn or there might be 12386 // weird bugs left. 12387 SimpleWindow.processAllCustomEvents(); 12388 SimpleWindow.processAllCustomEvents(); 12389 12390 RedrawWindow(hwnd, null, null, RDW_ERASE | RDW_INVALIDATE | RDW_ALLCHILDREN); 12391 // sdpyPrintDebugString("redraw"); 12392 } 12393 12394 oldWidth = this._width; 12395 oldHeight = this._height; 12396 break; 12397 case WM_ERASEBKGND: 12398 // call `visibleForTheFirstTime` here, so we can do initialization as early as possible 12399 if (!this._visibleForTheFirstTimeCalled) { 12400 this._visibleForTheFirstTimeCalled = true; 12401 if (this.visibleForTheFirstTime !is null) { 12402 this.visibleForTheFirstTime(); 12403 } 12404 } 12405 // block it in OpenGL mode, 'cause no sane person will (or should) draw windows controls over OpenGL scene 12406 version(without_opengl) {} else { 12407 if (openglMode == OpenGlOptions.yes) return 1; 12408 } 12409 // call windows default handler, so it can paint standard controls 12410 goto default; 12411 case WM_CTLCOLORBTN: 12412 case WM_CTLCOLORSTATIC: 12413 SetBkMode(cast(HDC) wParam, TRANSPARENT); 12414 return cast(typeof(return)) //GetStockObject(NULL_BRUSH); 12415 GetSysColorBrush(COLOR_3DFACE); 12416 //break; 12417 case WM_SHOWWINDOW: 12418 this._visible = (wParam != 0); 12419 if (!this._visibleForTheFirstTimeCalled && this._visible) { 12420 this._visibleForTheFirstTimeCalled = true; 12421 if (this.visibleForTheFirstTime !is null) { 12422 this.visibleForTheFirstTime(); 12423 } 12424 } 12425 if (this.visibilityChanged !is null) this.visibilityChanged(this._visible); 12426 break; 12427 case WM_PAINT: { 12428 if (!this._visibleForTheFirstTimeCalled) { 12429 this._visibleForTheFirstTimeCalled = true; 12430 if (this.visibleForTheFirstTime !is null) { 12431 this.visibleForTheFirstTime(); 12432 } 12433 } 12434 12435 BITMAP bm; 12436 PAINTSTRUCT ps; 12437 12438 HDC hdc = BeginPaint(hwnd, &ps); 12439 12440 if(openglMode == OpenGlOptions.no) { 12441 12442 HDC hdcMem = CreateCompatibleDC(hdc); 12443 HBITMAP hbmOld = SelectObject(hdcMem, buffer); 12444 12445 GetObject(buffer, bm.sizeof, &bm); 12446 12447 // FIXME: only BitBlt the invalidated rectangle, not the whole thing 12448 if(resizability == Resizability.automaticallyScaleIfPossible) 12449 StretchBlt(hdc, 0, 0, this._width, this._height, hdcMem, 0, 0, bm.bmWidth, bm.bmHeight, SRCCOPY); 12450 else 12451 BitBlt(hdc, 0, 0, bm.bmWidth, bm.bmHeight, hdcMem, 0, 0, SRCCOPY); 12452 //BitBlt(hdc, ps.rcPaint.left, ps.rcPaint.top, ps.rcPaint.right - ps.rcPaint.left, ps.rcPaint.top - ps.rcPaint.bottom, hdcMem, 0, 0, SRCCOPY); 12453 12454 SelectObject(hdcMem, hbmOld); 12455 DeleteDC(hdcMem); 12456 EndPaint(hwnd, &ps); 12457 } else { 12458 EndPaint(hwnd, &ps); 12459 version(without_opengl) {} else 12460 redrawOpenGlSceneSoon(); 12461 } 12462 } break; 12463 default: 12464 return DefWindowProc(hwnd, msg, wParam, lParam); 12465 } 12466 return 0; 12467 12468 } 12469 catch(Throwable t) { 12470 sdpyPrintDebugString(t.toString); 12471 return 0; 12472 } 12473 } 12474 } 12475 12476 mixin template NativeImageImplementation() { 12477 HBITMAP handle; 12478 ubyte* rawData; 12479 12480 final: 12481 12482 Color getPixel(int x, int y) { 12483 auto itemsPerLine = ((cast(int) width * 3 + 3) / 4) * 4; 12484 // remember, bmps are upside down 12485 auto offset = itemsPerLine * (height - y - 1) + x * 3; 12486 12487 Color c; 12488 if(enableAlpha) 12489 c.a = rawData[offset + 3]; 12490 else 12491 c.a = 255; 12492 c.b = rawData[offset + 0]; 12493 c.g = rawData[offset + 1]; 12494 c.r = rawData[offset + 2]; 12495 c.unPremultiply(); 12496 return c; 12497 } 12498 12499 void setPixel(int x, int y, Color c) { 12500 auto itemsPerLine = ((cast(int) width * 3 + 3) / 4) * 4; 12501 // remember, bmps are upside down 12502 auto offset = itemsPerLine * (height - y - 1) + x * 3; 12503 12504 if(enableAlpha) 12505 c.premultiply(); 12506 12507 rawData[offset + 0] = c.b; 12508 rawData[offset + 1] = c.g; 12509 rawData[offset + 2] = c.r; 12510 if(enableAlpha) 12511 rawData[offset + 3] = c.a; 12512 } 12513 12514 void convertToRgbaBytes(ubyte[] where) { 12515 assert(where.length == this.width * this.height * 4); 12516 12517 auto itemsPerLine = ((cast(int) width * 3 + 3) / 4) * 4; 12518 int idx = 0; 12519 int offset = itemsPerLine * (height - 1); 12520 // remember, bmps are upside down 12521 for(int y = height - 1; y >= 0; y--) { 12522 auto offsetStart = offset; 12523 for(int x = 0; x < width; x++) { 12524 where[idx + 0] = rawData[offset + 2]; // r 12525 where[idx + 1] = rawData[offset + 1]; // g 12526 where[idx + 2] = rawData[offset + 0]; // b 12527 if(enableAlpha) { 12528 where[idx + 3] = rawData[offset + 3]; // a 12529 unPremultiplyRgba(where[idx .. idx + 4]); 12530 offset++; 12531 } else 12532 where[idx + 3] = 255; // a 12533 idx += 4; 12534 offset += 3; 12535 } 12536 12537 offset = offsetStart - itemsPerLine; 12538 } 12539 } 12540 12541 void setFromRgbaBytes(in ubyte[] what) { 12542 assert(what.length == this.width * this.height * 4); 12543 12544 auto itemsPerLine = enableAlpha ? (width * 4) : (((cast(int) width * 3 + 3) / 4) * 4); 12545 int idx = 0; 12546 int offset = itemsPerLine * (height - 1); 12547 // remember, bmps are upside down 12548 for(int y = height - 1; y >= 0; y--) { 12549 auto offsetStart = offset; 12550 for(int x = 0; x < width; x++) { 12551 if(enableAlpha) { 12552 auto a = what[idx + 3]; 12553 12554 rawData[offset + 2] = (a * what[idx + 0]) / 255; // r 12555 rawData[offset + 1] = (a * what[idx + 1]) / 255; // g 12556 rawData[offset + 0] = (a * what[idx + 2]) / 255; // b 12557 rawData[offset + 3] = a; // a 12558 //premultiplyBgra(rawData[offset .. offset + 4]); 12559 offset++; 12560 } else { 12561 rawData[offset + 2] = what[idx + 0]; // r 12562 rawData[offset + 1] = what[idx + 1]; // g 12563 rawData[offset + 0] = what[idx + 2]; // b 12564 } 12565 idx += 4; 12566 offset += 3; 12567 } 12568 12569 offset = offsetStart - itemsPerLine; 12570 } 12571 } 12572 12573 12574 void createImage(int width, int height, bool forcexshm=false, bool enableAlpha = false) { 12575 BITMAPINFO infoheader; 12576 infoheader.bmiHeader.biSize = infoheader.bmiHeader.sizeof; 12577 infoheader.bmiHeader.biWidth = width; 12578 infoheader.bmiHeader.biHeight = height; 12579 infoheader.bmiHeader.biPlanes = 1; 12580 infoheader.bmiHeader.biBitCount = enableAlpha ? 32: 24; 12581 infoheader.bmiHeader.biCompression = BI_RGB; 12582 12583 handle = CreateDIBSection( 12584 null, 12585 &infoheader, 12586 DIB_RGB_COLORS, 12587 cast(void**) &rawData, 12588 null, 12589 0); 12590 if(handle is null) 12591 throw new WindowsApiException("create image failed", GetLastError()); 12592 12593 } 12594 12595 void dispose() { 12596 DeleteObject(handle); 12597 } 12598 } 12599 12600 enum KEY_ESCAPE = 27; 12601 } 12602 version(X11) { 12603 /// This is the default font used. You might change this before doing anything else with 12604 /// the library if you want to try something else. Surround that in `static if(UsingSimpledisplayX11)` 12605 /// for cross-platform compatibility. 12606 //__gshared string xfontstr = "-*-dejavu sans-medium-r-*-*-12-*-*-*-*-*-*-*"; 12607 //__gshared string xfontstr = "-*-dejavu sans-medium-r-*-*-12-*-*-*-*-*-*-*"; 12608 __gshared string xfontstr = "-*-lucida-medium-r-normal-sans-12-*-*-*-*-*-*-*"; 12609 //__gshared string xfontstr = "-*-fixed-medium-r-*-*-14-*-*-*-*-*-*-*"; 12610 12611 alias int delegate(XEvent) NativeEventHandler; 12612 alias Window NativeWindowHandle; 12613 12614 enum KEY_ESCAPE = 9; 12615 12616 mixin template NativeScreenPainterImplementation() { 12617 Display* display; 12618 Drawable d; 12619 Drawable destiny; 12620 12621 // FIXME: should the gc be static too so it isn't recreated every time draw is called? 12622 GC gc; 12623 12624 __gshared bool fontAttempted; 12625 12626 __gshared XFontStruct* defaultfont; 12627 __gshared XFontSet defaultfontset; 12628 12629 XFontStruct* font; 12630 XFontSet fontset; 12631 12632 void create(PaintingHandle window) { 12633 this.display = XDisplayConnection.get(); 12634 12635 Drawable buffer = None; 12636 if(auto sw = cast(SimpleWindow) this.window) { 12637 buffer = sw.impl.buffer; 12638 this.destiny = cast(Drawable) window; 12639 } else { 12640 buffer = cast(Drawable) window; 12641 this.destiny = None; 12642 } 12643 12644 this.d = cast(Drawable) buffer; 12645 12646 auto dgc = DefaultGC(display, DefaultScreen(display)); 12647 12648 this.gc = XCreateGC(display, d, 0, null); 12649 12650 XCopyGC(display, dgc, 0xffffffff, this.gc); 12651 12652 ensureDefaultFontLoaded(); 12653 12654 font = defaultfont; 12655 fontset = defaultfontset; 12656 12657 if(font) { 12658 XSetFont(display, gc, font.fid); 12659 } 12660 } 12661 12662 static void ensureDefaultFontLoaded() { 12663 if(!fontAttempted) { 12664 auto display = XDisplayConnection.get; 12665 auto font = XLoadQueryFont(display, xfontstr.ptr); 12666 // if the user font choice fails, fixed is pretty reliable (required by X to start!) and not bad either 12667 if(font is null) { 12668 xfontstr = "-*-fixed-medium-r-*-*-13-*-*-*-*-*-*-*"; 12669 font = XLoadQueryFont(display, xfontstr.ptr); 12670 } 12671 12672 char** lol; 12673 int lol2; 12674 char* lol3; 12675 auto fontset = XCreateFontSet(display, xfontstr.ptr, &lol, &lol2, &lol3); 12676 12677 fontAttempted = true; 12678 12679 defaultfont = font; 12680 defaultfontset = fontset; 12681 } 12682 } 12683 12684 arsd.color.Rectangle _clipRectangle; 12685 void setClipRectangle(int x, int y, int width, int height) { 12686 auto old = _clipRectangle; 12687 _clipRectangle = arsd.color.Rectangle(Point(x, y), Size(width, height)); 12688 if(old == _clipRectangle) 12689 return; 12690 12691 if(width == 0 || height == 0) { 12692 XSetClipMask(display, gc, None); 12693 12694 if(xrenderPicturePainter) { 12695 12696 XRectangle[1] rects; 12697 rects[0] = XRectangle(short.min, short.min, short.max, short.max); 12698 XRenderSetPictureClipRectangles(display, xrenderPicturePainter, 0, 0, rects.ptr, cast(int) rects.length); 12699 } 12700 12701 version(with_xft) { 12702 if(xftFont is null || xftDraw is null) 12703 return; 12704 XftDrawSetClip(xftDraw, null); 12705 } 12706 } else { 12707 XRectangle[1] rects; 12708 rects[0] = XRectangle(cast(short)(x), cast(short)(y), cast(short) width, cast(short) height); 12709 XSetClipRectangles(XDisplayConnection.get, gc, 0, 0, rects.ptr, 1, 0); 12710 12711 if(xrenderPicturePainter) 12712 XRenderSetPictureClipRectangles(display, xrenderPicturePainter, 0, 0, rects.ptr, cast(int) rects.length); 12713 12714 version(with_xft) { 12715 if(xftFont is null || xftDraw is null) 12716 return; 12717 XftDrawSetClipRectangles(xftDraw, 0, 0, rects.ptr, 1); 12718 } 12719 } 12720 } 12721 12722 version(with_xft) { 12723 XftFont* xftFont; 12724 XftDraw* xftDraw; 12725 12726 XftColor xftColor; 12727 12728 void updateXftColor() { 12729 if(xftFont is null) 12730 return; 12731 12732 // not bothering with XftColorFree since p sure i don't need it on 24 bit displays.... 12733 XRenderColor colorIn = XRenderColor(_outlineColor.r * 255, _outlineColor.g * 255, _outlineColor.b * 255, _outlineColor.a * 255); 12734 12735 XftColorAllocValue( 12736 display, 12737 DefaultVisual(display, DefaultScreen(display)), 12738 DefaultColormap(display, 0), 12739 &colorIn, 12740 &xftColor 12741 ); 12742 } 12743 } 12744 12745 private OperatingSystemFont _activeFont; 12746 void setFont(OperatingSystemFont font) { 12747 _activeFont = font; 12748 version(with_xft) { 12749 if(font && font.isXft && font.xftFont) 12750 this.xftFont = font.xftFont; 12751 else 12752 this.xftFont = null; 12753 12754 if(this.xftFont) { 12755 if(xftDraw is null) { 12756 xftDraw = XftDrawCreate( 12757 display, 12758 d, 12759 DefaultVisual(display, DefaultScreen(display)), 12760 DefaultColormap(display, 0) 12761 ); 12762 12763 updateXftColor(); 12764 } 12765 12766 return; 12767 } 12768 } 12769 12770 if(font && font.font) { 12771 this.font = font.font; 12772 this.fontset = font.fontset; 12773 XSetFont(display, gc, font.font.fid); 12774 } else { 12775 this.font = defaultfont; 12776 this.fontset = defaultfontset; 12777 } 12778 12779 } 12780 12781 private Picture xrenderPicturePainter; 12782 12783 bool manualInvalidations; 12784 void invalidateRect(Rectangle invalidRect) { 12785 // FIXME if manualInvalidations 12786 } 12787 12788 void dispose() { 12789 this.rasterOp = RasterOp.normal; 12790 12791 if(xrenderPicturePainter) { 12792 XRenderFreePicture(display, xrenderPicturePainter); 12793 xrenderPicturePainter = None; 12794 } 12795 12796 // FIXME: this.window.width/height is probably wrong 12797 12798 // src x,y then dest x, y 12799 if(destiny != None) { 12800 // FIXME: if manual invalidations we can actually only copy some of the area. 12801 // if(manualInvalidations) 12802 XSetClipMask(display, gc, None); 12803 XCopyArea(display, d, destiny, gc, 0, 0, this.window.width, this.window.height, 0, 0); 12804 } 12805 12806 XFreeGC(display, gc); 12807 12808 version(with_xft) 12809 if(xftDraw) { 12810 XftDrawDestroy(xftDraw); 12811 xftDraw = null; 12812 } 12813 12814 /+ 12815 // this should prolly legit never be used since if it destroys the font handle from a OperatingSystemFont, it also ruins a reusable resource. 12816 if(font && font !is defaultfont) { 12817 XFreeFont(display, font); 12818 font = null; 12819 } 12820 if(fontset && fontset !is defaultfontset) { 12821 XFreeFontSet(display, fontset); 12822 fontset = null; 12823 } 12824 +/ 12825 XFlush(display); 12826 12827 if(window.paintingFinishedDg !is null) 12828 window.paintingFinishedDg()(); 12829 } 12830 12831 bool backgroundIsNotTransparent = true; 12832 bool foregroundIsNotTransparent = true; 12833 12834 bool _penInitialized = false; 12835 Pen _activePen; 12836 12837 Color _outlineColor; 12838 Color _fillColor; 12839 12840 @property void pen(Pen p) { 12841 if(_penInitialized && p == _activePen) { 12842 return; 12843 } 12844 _penInitialized = true; 12845 _activePen = p; 12846 _outlineColor = p.color; 12847 12848 int style; 12849 12850 byte dashLength; 12851 12852 final switch(p.style) { 12853 case Pen.Style.Solid: 12854 style = 0 /*LineSolid*/; 12855 break; 12856 case Pen.Style.Dashed: 12857 style = 1 /*LineOnOffDash*/; 12858 dashLength = 4; 12859 break; 12860 case Pen.Style.Dotted: 12861 style = 1 /*LineOnOffDash*/; 12862 dashLength = 1; 12863 break; 12864 } 12865 12866 XSetLineAttributes(display, gc, p.width, style, style == 0 ? 3 : 0, 0); 12867 if(dashLength) 12868 XSetDashes(display, gc, 0, &dashLength, 1); 12869 12870 if(p.color.a == 0) { 12871 foregroundIsNotTransparent = false; 12872 return; 12873 } 12874 12875 foregroundIsNotTransparent = true; 12876 12877 XSetForeground(display, gc, colorToX(p.color, display)); 12878 12879 version(with_xft) 12880 updateXftColor(); 12881 } 12882 12883 RasterOp _currentRasterOp; 12884 bool _currentRasterOpInitialized = false; 12885 @property void rasterOp(RasterOp op) { 12886 if(_currentRasterOpInitialized && _currentRasterOp == op) 12887 return; 12888 _currentRasterOp = op; 12889 _currentRasterOpInitialized = true; 12890 int mode; 12891 final switch(op) { 12892 case RasterOp.normal: 12893 mode = GXcopy; 12894 break; 12895 case RasterOp.xor: 12896 mode = GXxor; 12897 break; 12898 } 12899 XSetFunction(display, gc, mode); 12900 } 12901 12902 12903 bool _fillColorInitialized = false; 12904 12905 @property void fillColor(Color c) { 12906 if(_fillColorInitialized && _fillColor == c) 12907 return; // already good, no need to waste time calling it 12908 _fillColor = c; 12909 _fillColorInitialized = true; 12910 if(c.a == 0) { 12911 backgroundIsNotTransparent = false; 12912 return; 12913 } 12914 12915 backgroundIsNotTransparent = true; 12916 12917 XSetBackground(display, gc, colorToX(c, display)); 12918 12919 } 12920 12921 void swapColors() { 12922 auto tmp = _fillColor; 12923 fillColor = _outlineColor; 12924 auto newPen = _activePen; 12925 newPen.color = tmp; 12926 pen(newPen); 12927 } 12928 12929 uint colorToX(Color c, Display* display) { 12930 auto visual = DefaultVisual(display, DefaultScreen(display)); 12931 import core.bitop; 12932 uint color = 0; 12933 { 12934 auto startBit = bsf(visual.red_mask); 12935 auto lastBit = bsr(visual.red_mask); 12936 auto r = cast(uint) c.r; 12937 r >>= 7 - (lastBit - startBit); 12938 r <<= startBit; 12939 color |= r; 12940 } 12941 { 12942 auto startBit = bsf(visual.green_mask); 12943 auto lastBit = bsr(visual.green_mask); 12944 auto g = cast(uint) c.g; 12945 g >>= 7 - (lastBit - startBit); 12946 g <<= startBit; 12947 color |= g; 12948 } 12949 { 12950 auto startBit = bsf(visual.blue_mask); 12951 auto lastBit = bsr(visual.blue_mask); 12952 auto b = cast(uint) c.b; 12953 b >>= 7 - (lastBit - startBit); 12954 b <<= startBit; 12955 color |= b; 12956 } 12957 12958 12959 12960 return color; 12961 } 12962 12963 void drawImage(int x, int y, Image i, int ix, int iy, int w, int h) { 12964 // source x, source y 12965 if(ix >= i.width) return; 12966 if(iy >= i.height) return; 12967 if(ix + w > i.width) w = i.width - ix; 12968 if(iy + h > i.height) h = i.height - iy; 12969 if(i.usingXshm) 12970 XShmPutImage(display, d, gc, i.handle, ix, iy, x, y, w, h, false); 12971 else 12972 XPutImage(display, d, gc, i.handle, ix, iy, x, y, w, h); 12973 } 12974 12975 void drawPixmap(Sprite s, int x, int y, int ix, int iy, int w, int h) { 12976 if(s.enableAlpha) { 12977 // the Sprite must be created first, meaning if we're here, XRender is already loaded 12978 if(this.xrenderPicturePainter == None) { 12979 XRenderPictureAttributes attrs; 12980 // FIXME: I can prolly reuse this as long as the pixmap itself is valid. 12981 xrenderPicturePainter = XRenderCreatePicture(display, d, Sprite.RGB24, 0, &attrs); 12982 12983 // need to initialize the clip 12984 XRectangle[1] rects; 12985 rects[0] = XRectangle(cast(short)(_clipRectangle.left), cast(short)(_clipRectangle.top), cast(short) _clipRectangle.width, cast(short) _clipRectangle.height); 12986 12987 if(_clipRectangle != Rectangle.init) 12988 XRenderSetPictureClipRectangles(display, xrenderPicturePainter, 0, 0, rects.ptr, cast(int) rects.length); 12989 } 12990 12991 XRenderComposite( 12992 display, 12993 3, // PicOpOver 12994 s.xrenderPicture, 12995 None, 12996 this.xrenderPicturePainter, 12997 ix, 12998 iy, 12999 0, 13000 0, 13001 x, 13002 y, 13003 w ? w : s.width, 13004 h ? h : s.height 13005 ); 13006 } else { 13007 XCopyArea(display, s.handle, d, gc, ix, iy, w ? w : s.width, h ? h : s.height, x, y); 13008 } 13009 } 13010 13011 int fontHeight() { 13012 version(with_xft) 13013 if(xftFont !is null) 13014 return xftFont.height; 13015 if(font) 13016 return font.max_bounds.ascent + font.max_bounds.descent; 13017 return 12; // pretty common default... 13018 } 13019 13020 int textWidth(in char[] line) { 13021 version(with_xft) 13022 if(xftFont) { 13023 if(line.length == 0) 13024 return 0; 13025 XGlyphInfo extents; 13026 XftTextExtentsUtf8(display, xftFont, line.ptr, cast(int) line.length, &extents); 13027 return extents.width; 13028 } 13029 13030 if(fontset) { 13031 if(line.length == 0) 13032 return 0; 13033 XRectangle rect; 13034 Xutf8TextExtents(fontset, line.ptr, cast(int) line.length, null, &rect); 13035 13036 return rect.width; 13037 } 13038 13039 if(font) 13040 // FIXME: unicode 13041 return XTextWidth( font, line.ptr, cast(int) line.length); 13042 else 13043 return fontHeight / 2 * cast(int) line.length; // if no font is loaded, it is prolly Fixed, which is a 2:1 ratio 13044 } 13045 13046 Size textSize(in char[] text) { 13047 auto maxWidth = 0; 13048 auto lineHeight = fontHeight; 13049 int h = text.length ? 0 : lineHeight + 4; // if text is empty, it still gives the line height 13050 foreach(line; text.split('\n')) { 13051 int textWidth = this.textWidth(line); 13052 if(textWidth > maxWidth) 13053 maxWidth = textWidth; 13054 h += lineHeight + 4; 13055 } 13056 return Size(maxWidth, h); 13057 } 13058 13059 void drawText(in int x, in int y, in int x2, in int y2, in char[] originalText, in uint alignment) { 13060 const(char)[] text; 13061 version(with_xft) 13062 if(xftFont) { 13063 text = originalText; 13064 goto loaded; 13065 } 13066 13067 if(fontset) 13068 text = originalText; 13069 else { 13070 text.reserve(originalText.length); 13071 // the first 256 unicode codepoints are the same as ascii and latin-1, which is what X expects, so we can keep all those 13072 // then strip the rest so there isn't garbage 13073 foreach(dchar ch; originalText) 13074 if(ch < 256) 13075 text ~= cast(ubyte) ch; 13076 else 13077 text ~= 191; // FIXME: using a random character (upside down question mark) to fill the space 13078 } 13079 loaded: 13080 if(text.length == 0) 13081 return; 13082 13083 // FIXME: should we clip it to the bounding box? 13084 int textHeight = fontHeight; 13085 13086 auto lines = text.split('\n'); 13087 13088 const lineHeight = textHeight; 13089 textHeight *= lines.length; 13090 13091 int cy = y; 13092 13093 if(alignment & TextAlignment.VerticalBottom) { 13094 if(y2 <= 0) 13095 return; 13096 auto h = y2 - y; 13097 if(h > textHeight) { 13098 cy += h - textHeight; 13099 cy -= lineHeight / 2; 13100 } 13101 } else if(alignment & TextAlignment.VerticalCenter) { 13102 if(y2 <= 0) 13103 return; 13104 auto h = y2 - y; 13105 if(textHeight < h) { 13106 cy += (h - textHeight) / 2; 13107 //cy -= lineHeight / 4; 13108 } 13109 } 13110 13111 foreach(line; text.split('\n')) { 13112 int textWidth = this.textWidth(line); 13113 13114 int px = x, py = cy; 13115 13116 if(alignment & TextAlignment.Center) { 13117 if(x2 <= 0) 13118 return; 13119 auto w = x2 - x; 13120 if(w > textWidth) 13121 px += (w - textWidth) / 2; 13122 } else if(alignment & TextAlignment.Right) { 13123 if(x2 <= 0) 13124 return; 13125 auto pos = x2 - textWidth; 13126 if(pos > x) 13127 px = pos; 13128 } 13129 13130 version(with_xft) 13131 if(xftFont) { 13132 XftDrawStringUtf8(xftDraw, &xftColor, xftFont, px, py + xftFont.ascent, line.ptr, cast(int) line.length); 13133 13134 goto carry_on; 13135 } 13136 13137 if(fontset) 13138 Xutf8DrawString(display, d, fontset, gc, px, py + (font ? font.max_bounds.ascent : lineHeight), line.ptr, cast(int) line.length); 13139 else 13140 XDrawString(display, d, gc, px, py + (font ? font.max_bounds.ascent : lineHeight), line.ptr, cast(int) line.length); 13141 carry_on: 13142 cy += lineHeight + 4; 13143 } 13144 } 13145 13146 void drawPixel(int x, int y) { 13147 XDrawPoint(display, d, gc, x, y); 13148 } 13149 13150 // The basic shapes, outlined 13151 13152 void drawLine(int x1, int y1, int x2, int y2) { 13153 if(foregroundIsNotTransparent) 13154 XDrawLine(display, d, gc, x1, y1, x2, y2); 13155 } 13156 13157 void drawRectangle(int x, int y, int width, int height) { 13158 if(backgroundIsNotTransparent) { 13159 swapColors(); 13160 XFillRectangle(display, d, gc, x+1, y+1, width-2, height-2); // Need to ensure pixels are only drawn once... 13161 swapColors(); 13162 } 13163 // 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 13164 if(foregroundIsNotTransparent) 13165 XDrawRectangle(display, d, gc, x + _activePen.width / 2, y + _activePen.width / 2, width - 1 - _activePen.width / 2, height - 1 - _activePen.width / 2); 13166 } 13167 13168 /// Arguments are the points of the bounding rectangle 13169 void drawEllipse(int x1, int y1, int x2, int y2) { 13170 drawArc(x1, y1, x2 - x1, y2 - y1, 0, 360 * 64); 13171 } 13172 13173 // NOTE: start and finish are in units of degrees * 64 13174 void drawArc(int x1, int y1, int width, int height, int start, int finish) { 13175 if(backgroundIsNotTransparent) { 13176 swapColors(); 13177 XFillArc(display, d, gc, x1, y1, width, height, start, finish); 13178 swapColors(); 13179 } 13180 if(foregroundIsNotTransparent) { 13181 XDrawArc(display, d, gc, x1, y1, width, height, start, finish); 13182 13183 // Windows draws the straight lines on the edges too so FIXME sort of 13184 } 13185 } 13186 13187 void drawPolygon(Point[] vertexes) { 13188 XPoint[16] pointsBuffer; 13189 XPoint[] points; 13190 if(vertexes.length <= pointsBuffer.length) 13191 points = pointsBuffer[0 .. vertexes.length]; 13192 else 13193 points.length = vertexes.length; 13194 13195 foreach(i, p; vertexes) { 13196 points[i].x = cast(short) p.x; 13197 points[i].y = cast(short) p.y; 13198 } 13199 13200 if(backgroundIsNotTransparent) { 13201 swapColors(); 13202 XFillPolygon(display, d, gc, points.ptr, cast(int) points.length, PolygonShape.Complex, CoordMode.CoordModeOrigin); 13203 swapColors(); 13204 } 13205 if(foregroundIsNotTransparent) { 13206 XDrawLines(display, d, gc, points.ptr, cast(int) points.length, CoordMode.CoordModeOrigin); 13207 } 13208 } 13209 } 13210 13211 /* XRender { */ 13212 13213 struct XRenderColor { 13214 ushort red; 13215 ushort green; 13216 ushort blue; 13217 ushort alpha; 13218 } 13219 13220 alias Picture = XID; 13221 alias PictFormat = XID; 13222 13223 struct XGlyphInfo { 13224 ushort width; 13225 ushort height; 13226 short x; 13227 short y; 13228 short xOff; 13229 short yOff; 13230 } 13231 13232 struct XRenderDirectFormat { 13233 short red; 13234 short redMask; 13235 short green; 13236 short greenMask; 13237 short blue; 13238 short blueMask; 13239 short alpha; 13240 short alphaMask; 13241 } 13242 13243 struct XRenderPictFormat { 13244 PictFormat id; 13245 int type; 13246 int depth; 13247 XRenderDirectFormat direct; 13248 Colormap colormap; 13249 } 13250 13251 enum PictFormatID = (1 << 0); 13252 enum PictFormatType = (1 << 1); 13253 enum PictFormatDepth = (1 << 2); 13254 enum PictFormatRed = (1 << 3); 13255 enum PictFormatRedMask =(1 << 4); 13256 enum PictFormatGreen = (1 << 5); 13257 enum PictFormatGreenMask=(1 << 6); 13258 enum PictFormatBlue = (1 << 7); 13259 enum PictFormatBlueMask =(1 << 8); 13260 enum PictFormatAlpha = (1 << 9); 13261 enum PictFormatAlphaMask=(1 << 10); 13262 enum PictFormatColormap =(1 << 11); 13263 13264 struct XRenderPictureAttributes { 13265 int repeat; 13266 Picture alpha_map; 13267 int alpha_x_origin; 13268 int alpha_y_origin; 13269 int clip_x_origin; 13270 int clip_y_origin; 13271 Pixmap clip_mask; 13272 Bool graphics_exposures; 13273 int subwindow_mode; 13274 int poly_edge; 13275 int poly_mode; 13276 Atom dither; 13277 Bool component_alpha; 13278 } 13279 13280 alias int XFixed; 13281 13282 struct XPointFixed { 13283 XFixed x, y; 13284 } 13285 13286 struct XCircle { 13287 XFixed x; 13288 XFixed y; 13289 XFixed radius; 13290 } 13291 13292 struct XTransform { 13293 XFixed[3][3] matrix; 13294 } 13295 13296 struct XFilters { 13297 int nfilter; 13298 char **filter; 13299 int nalias; 13300 short *alias_; 13301 } 13302 13303 struct XIndexValue { 13304 c_ulong pixel; 13305 ushort red, green, blue, alpha; 13306 } 13307 13308 struct XAnimCursor { 13309 Cursor cursor; 13310 c_ulong delay; 13311 } 13312 13313 struct XLinearGradient { 13314 XPointFixed p1; 13315 XPointFixed p2; 13316 } 13317 13318 struct XRadialGradient { 13319 XCircle inner; 13320 XCircle outer; 13321 } 13322 13323 struct XConicalGradient { 13324 XPointFixed center; 13325 XFixed angle; /* in degrees */ 13326 } 13327 13328 enum PictStandardARGB32 = 0; 13329 enum PictStandardRGB24 = 1; 13330 enum PictStandardA8 = 2; 13331 enum PictStandardA4 = 3; 13332 enum PictStandardA1 = 4; 13333 enum PictStandardNUM = 5; 13334 13335 interface XRender { 13336 extern(C) @nogc: 13337 13338 Bool XRenderQueryExtension (Display *dpy, int *event_basep, int *error_basep); 13339 13340 Status XRenderQueryVersion (Display *dpy, 13341 int *major_versionp, 13342 int *minor_versionp); 13343 13344 Status XRenderQueryFormats (Display *dpy); 13345 13346 int XRenderQuerySubpixelOrder (Display *dpy, int screen); 13347 13348 Bool XRenderSetSubpixelOrder (Display *dpy, int screen, int subpixel); 13349 13350 XRenderPictFormat * 13351 XRenderFindVisualFormat (Display *dpy, const Visual *visual); 13352 13353 XRenderPictFormat * 13354 XRenderFindFormat (Display *dpy, 13355 c_ulong mask, 13356 const XRenderPictFormat *templ, 13357 int count); 13358 XRenderPictFormat * 13359 XRenderFindStandardFormat (Display *dpy, 13360 int format); 13361 13362 XIndexValue * 13363 XRenderQueryPictIndexValues(Display *dpy, 13364 const XRenderPictFormat *format, 13365 int *num); 13366 13367 Picture XRenderCreatePicture( 13368 Display *dpy, 13369 Drawable drawable, 13370 const XRenderPictFormat *format, 13371 c_ulong valuemask, 13372 const XRenderPictureAttributes *attributes); 13373 13374 void XRenderChangePicture (Display *dpy, 13375 Picture picture, 13376 c_ulong valuemask, 13377 const XRenderPictureAttributes *attributes); 13378 13379 void 13380 XRenderSetPictureClipRectangles (Display *dpy, 13381 Picture picture, 13382 int xOrigin, 13383 int yOrigin, 13384 const XRectangle *rects, 13385 int n); 13386 13387 void 13388 XRenderSetPictureClipRegion (Display *dpy, 13389 Picture picture, 13390 Region r); 13391 13392 void 13393 XRenderSetPictureTransform (Display *dpy, 13394 Picture picture, 13395 XTransform *transform); 13396 13397 void 13398 XRenderFreePicture (Display *dpy, 13399 Picture picture); 13400 13401 void 13402 XRenderComposite (Display *dpy, 13403 int op, 13404 Picture src, 13405 Picture mask, 13406 Picture dst, 13407 int src_x, 13408 int src_y, 13409 int mask_x, 13410 int mask_y, 13411 int dst_x, 13412 int dst_y, 13413 uint width, 13414 uint height); 13415 13416 13417 Picture XRenderCreateSolidFill (Display *dpy, 13418 const XRenderColor *color); 13419 13420 Picture XRenderCreateLinearGradient (Display *dpy, 13421 const XLinearGradient *gradient, 13422 const XFixed *stops, 13423 const XRenderColor *colors, 13424 int nstops); 13425 13426 Picture XRenderCreateRadialGradient (Display *dpy, 13427 const XRadialGradient *gradient, 13428 const XFixed *stops, 13429 const XRenderColor *colors, 13430 int nstops); 13431 13432 Picture XRenderCreateConicalGradient (Display *dpy, 13433 const XConicalGradient *gradient, 13434 const XFixed *stops, 13435 const XRenderColor *colors, 13436 int nstops); 13437 13438 13439 13440 Cursor 13441 XRenderCreateCursor (Display *dpy, 13442 Picture source, 13443 uint x, 13444 uint y); 13445 13446 XFilters * 13447 XRenderQueryFilters (Display *dpy, Drawable drawable); 13448 13449 void 13450 XRenderSetPictureFilter (Display *dpy, 13451 Picture picture, 13452 const char *filter, 13453 XFixed *params, 13454 int nparams); 13455 13456 Cursor 13457 XRenderCreateAnimCursor (Display *dpy, 13458 int ncursor, 13459 XAnimCursor *cursors); 13460 } 13461 13462 __gshared bool XRenderLibrarySuccessfullyLoaded = true; 13463 mixin DynamicLoad!(XRender, "Xrender", 1, XRenderLibrarySuccessfullyLoaded) XRenderLibrary; 13464 13465 /* XRender } */ 13466 13467 /* Xrandr { */ 13468 13469 struct XRRMonitorInfo { 13470 Atom name; 13471 Bool primary; 13472 Bool automatic; 13473 int noutput; 13474 int x; 13475 int y; 13476 int width; 13477 int height; 13478 int mwidth; 13479 int mheight; 13480 /*RROutput*/ void *outputs; 13481 } 13482 13483 struct XRRScreenChangeNotifyEvent { 13484 int type; /* event base */ 13485 c_ulong serial; /* # of last request processed by server */ 13486 Bool send_event; /* true if this came from a SendEvent request */ 13487 Display *display; /* Display the event was read from */ 13488 Window window; /* window which selected for this event */ 13489 Window root; /* Root window for changed screen */ 13490 Time timestamp; /* when the screen change occurred */ 13491 Time config_timestamp; /* when the last configuration change */ 13492 ushort/*SizeID*/ size_index; 13493 ushort/*SubpixelOrder*/ subpixel_order; 13494 ushort/*Rotation*/ rotation; 13495 int width; 13496 int height; 13497 int mwidth; 13498 int mheight; 13499 } 13500 13501 enum RRScreenChangeNotify = 0; 13502 13503 enum RRScreenChangeNotifyMask = 1; 13504 13505 __gshared int xrrEventBase = -1; 13506 13507 13508 interface XRandr { 13509 extern(C) @nogc: 13510 Bool XRRQueryExtension (Display *dpy, int *event_base_return, int *error_base_return); 13511 Status XRRQueryVersion (Display *dpy, int *major_version_return, int *minor_version_return); 13512 13513 XRRMonitorInfo * XRRGetMonitors(Display *dpy, Window window, Bool get_active, int *nmonitors); 13514 void XRRFreeMonitors(XRRMonitorInfo *monitors); 13515 13516 void XRRSelectInput(Display *dpy, Window window, int mask); 13517 } 13518 13519 __gshared bool XRandrLibrarySuccessfullyLoaded = true; 13520 mixin DynamicLoad!(XRandr, "Xrandr", 2, XRandrLibrarySuccessfullyLoaded) XRandrLibrary; 13521 /* Xrandr } */ 13522 13523 /* Xft { */ 13524 13525 // actually freetype 13526 alias void FT_Face; 13527 13528 // actually fontconfig 13529 private alias FcBool = int; 13530 alias void FcCharSet; 13531 alias void FcPattern; 13532 alias void FcResult; 13533 enum FcEndian { FcEndianBig, FcEndianLittle } 13534 struct FcFontSet { 13535 int nfont; 13536 int sfont; 13537 FcPattern** fonts; 13538 } 13539 13540 // actually XRegion 13541 struct BOX { 13542 short x1, x2, y1, y2; 13543 } 13544 struct _XRegion { 13545 c_long size; 13546 c_long numRects; 13547 BOX* rects; 13548 BOX extents; 13549 } 13550 13551 alias Region = _XRegion*; 13552 13553 // ok actually Xft 13554 13555 struct XftFontInfo; 13556 13557 struct XftFont { 13558 int ascent; 13559 int descent; 13560 int height; 13561 int max_advance_width; 13562 FcCharSet* charset; 13563 FcPattern* pattern; 13564 } 13565 13566 struct XftDraw; 13567 13568 struct XftColor { 13569 c_ulong pixel; 13570 XRenderColor color; 13571 } 13572 13573 struct XftCharSpec { 13574 dchar ucs4; 13575 short x; 13576 short y; 13577 } 13578 13579 struct XftCharFontSpec { 13580 XftFont *font; 13581 dchar ucs4; 13582 short x; 13583 short y; 13584 } 13585 13586 struct XftGlyphSpec { 13587 uint glyph; 13588 short x; 13589 short y; 13590 } 13591 13592 struct XftGlyphFontSpec { 13593 XftFont *font; 13594 uint glyph; 13595 short x; 13596 short y; 13597 } 13598 13599 interface Xft { 13600 extern(C) @nogc pure: 13601 13602 Bool XftColorAllocName (Display *dpy, 13603 const Visual *visual, 13604 Colormap cmap, 13605 const char *name, 13606 XftColor *result); 13607 13608 Bool XftColorAllocValue (Display *dpy, 13609 Visual *visual, 13610 Colormap cmap, 13611 const XRenderColor *color, 13612 XftColor *result); 13613 13614 void XftColorFree (Display *dpy, 13615 Visual *visual, 13616 Colormap cmap, 13617 XftColor *color); 13618 13619 Bool XftDefaultHasRender (Display *dpy); 13620 13621 Bool XftDefaultSet (Display *dpy, FcPattern *defaults); 13622 13623 void XftDefaultSubstitute (Display *dpy, int screen, FcPattern *pattern); 13624 13625 XftDraw * XftDrawCreate (Display *dpy, 13626 Drawable drawable, 13627 Visual *visual, 13628 Colormap colormap); 13629 13630 XftDraw * XftDrawCreateBitmap (Display *dpy, 13631 Pixmap bitmap); 13632 13633 XftDraw * XftDrawCreateAlpha (Display *dpy, 13634 Pixmap pixmap, 13635 int depth); 13636 13637 void XftDrawChange (XftDraw *draw, 13638 Drawable drawable); 13639 13640 Display * XftDrawDisplay (XftDraw *draw); 13641 13642 Drawable XftDrawDrawable (XftDraw *draw); 13643 13644 Colormap XftDrawColormap (XftDraw *draw); 13645 13646 Visual * XftDrawVisual (XftDraw *draw); 13647 13648 void XftDrawDestroy (XftDraw *draw); 13649 13650 Picture XftDrawPicture (XftDraw *draw); 13651 13652 Picture XftDrawSrcPicture (XftDraw *draw, const XftColor *color); 13653 13654 void XftDrawGlyphs (XftDraw *draw, 13655 const XftColor *color, 13656 XftFont *pub, 13657 int x, 13658 int y, 13659 const uint *glyphs, 13660 int nglyphs); 13661 13662 void XftDrawString8 (XftDraw *draw, 13663 const XftColor *color, 13664 XftFont *pub, 13665 int x, 13666 int y, 13667 const char *string, 13668 int len); 13669 13670 void XftDrawString16 (XftDraw *draw, 13671 const XftColor *color, 13672 XftFont *pub, 13673 int x, 13674 int y, 13675 const wchar *string, 13676 int len); 13677 13678 void XftDrawString32 (XftDraw *draw, 13679 const XftColor *color, 13680 XftFont *pub, 13681 int x, 13682 int y, 13683 const dchar *string, 13684 int len); 13685 13686 void XftDrawStringUtf8 (XftDraw *draw, 13687 const XftColor *color, 13688 XftFont *pub, 13689 int x, 13690 int y, 13691 const char *string, 13692 int len); 13693 void XftDrawStringUtf16 (XftDraw *draw, 13694 const XftColor *color, 13695 XftFont *pub, 13696 int x, 13697 int y, 13698 const char *string, 13699 FcEndian endian, 13700 int len); 13701 13702 void XftDrawCharSpec (XftDraw *draw, 13703 const XftColor *color, 13704 XftFont *pub, 13705 const XftCharSpec *chars, 13706 int len); 13707 13708 void XftDrawCharFontSpec (XftDraw *draw, 13709 const XftColor *color, 13710 const XftCharFontSpec *chars, 13711 int len); 13712 13713 void XftDrawGlyphSpec (XftDraw *draw, 13714 const XftColor *color, 13715 XftFont *pub, 13716 const XftGlyphSpec *glyphs, 13717 int len); 13718 13719 void XftDrawGlyphFontSpec (XftDraw *draw, 13720 const XftColor *color, 13721 const XftGlyphFontSpec *glyphs, 13722 int len); 13723 13724 void XftDrawRect (XftDraw *draw, 13725 const XftColor *color, 13726 int x, 13727 int y, 13728 uint width, 13729 uint height); 13730 13731 Bool XftDrawSetClip (XftDraw *draw, 13732 Region r); 13733 13734 13735 Bool XftDrawSetClipRectangles (XftDraw *draw, 13736 int xOrigin, 13737 int yOrigin, 13738 const XRectangle *rects, 13739 int n); 13740 13741 void XftDrawSetSubwindowMode (XftDraw *draw, 13742 int mode); 13743 13744 void XftGlyphExtents (Display *dpy, 13745 XftFont *pub, 13746 const uint *glyphs, 13747 int nglyphs, 13748 XGlyphInfo *extents); 13749 13750 void XftTextExtents8 (Display *dpy, 13751 XftFont *pub, 13752 const char *string, 13753 int len, 13754 XGlyphInfo *extents); 13755 13756 void XftTextExtents16 (Display *dpy, 13757 XftFont *pub, 13758 const wchar *string, 13759 int len, 13760 XGlyphInfo *extents); 13761 13762 void XftTextExtents32 (Display *dpy, 13763 XftFont *pub, 13764 const dchar *string, 13765 int len, 13766 XGlyphInfo *extents); 13767 13768 void XftTextExtentsUtf8 (Display *dpy, 13769 XftFont *pub, 13770 const char *string, 13771 int len, 13772 XGlyphInfo *extents); 13773 13774 void XftTextExtentsUtf16 (Display *dpy, 13775 XftFont *pub, 13776 const char *string, 13777 FcEndian endian, 13778 int len, 13779 XGlyphInfo *extents); 13780 13781 FcPattern * XftFontMatch (Display *dpy, 13782 int screen, 13783 const FcPattern *pattern, 13784 FcResult *result); 13785 13786 XftFont * XftFontOpen (Display *dpy, int screen, ...); 13787 13788 XftFont * XftFontOpenName (Display *dpy, int screen, const char *name); 13789 13790 XftFont * XftFontOpenXlfd (Display *dpy, int screen, const char *xlfd); 13791 13792 FT_Face XftLockFace (XftFont *pub); 13793 13794 void XftUnlockFace (XftFont *pub); 13795 13796 XftFontInfo * XftFontInfoCreate (Display *dpy, const FcPattern *pattern); 13797 13798 void XftFontInfoDestroy (Display *dpy, XftFontInfo *fi); 13799 13800 dchar XftFontInfoHash (const XftFontInfo *fi); 13801 13802 FcBool XftFontInfoEqual (const XftFontInfo *a, const XftFontInfo *b); 13803 13804 XftFont * XftFontOpenInfo (Display *dpy, 13805 FcPattern *pattern, 13806 XftFontInfo *fi); 13807 13808 XftFont * XftFontOpenPattern (Display *dpy, FcPattern *pattern); 13809 13810 XftFont * XftFontCopy (Display *dpy, XftFont *pub); 13811 13812 void XftFontClose (Display *dpy, XftFont *pub); 13813 13814 FcBool XftInitFtLibrary(); 13815 void XftFontLoadGlyphs (Display *dpy, 13816 XftFont *pub, 13817 FcBool need_bitmaps, 13818 const uint *glyphs, 13819 int nglyph); 13820 13821 void XftFontUnloadGlyphs (Display *dpy, 13822 XftFont *pub, 13823 const uint *glyphs, 13824 int nglyph); 13825 13826 FcBool XftFontCheckGlyph (Display *dpy, 13827 XftFont *pub, 13828 FcBool need_bitmaps, 13829 uint glyph, 13830 uint *missing, 13831 int *nmissing); 13832 13833 FcBool XftCharExists (Display *dpy, 13834 XftFont *pub, 13835 dchar ucs4); 13836 13837 uint XftCharIndex (Display *dpy, 13838 XftFont *pub, 13839 dchar ucs4); 13840 FcBool XftInit (const char *config); 13841 13842 int XftGetVersion (); 13843 13844 FcFontSet * XftListFonts (Display *dpy, 13845 int screen, 13846 ...); 13847 13848 FcPattern *XftNameParse (const char *name); 13849 13850 void XftGlyphRender (Display *dpy, 13851 int op, 13852 Picture src, 13853 XftFont *pub, 13854 Picture dst, 13855 int srcx, 13856 int srcy, 13857 int x, 13858 int y, 13859 const uint *glyphs, 13860 int nglyphs); 13861 13862 void XftGlyphSpecRender (Display *dpy, 13863 int op, 13864 Picture src, 13865 XftFont *pub, 13866 Picture dst, 13867 int srcx, 13868 int srcy, 13869 const XftGlyphSpec *glyphs, 13870 int nglyphs); 13871 13872 void XftCharSpecRender (Display *dpy, 13873 int op, 13874 Picture src, 13875 XftFont *pub, 13876 Picture dst, 13877 int srcx, 13878 int srcy, 13879 const XftCharSpec *chars, 13880 int len); 13881 void XftGlyphFontSpecRender (Display *dpy, 13882 int op, 13883 Picture src, 13884 Picture dst, 13885 int srcx, 13886 int srcy, 13887 const XftGlyphFontSpec *glyphs, 13888 int nglyphs); 13889 13890 void XftCharFontSpecRender (Display *dpy, 13891 int op, 13892 Picture src, 13893 Picture dst, 13894 int srcx, 13895 int srcy, 13896 const XftCharFontSpec *chars, 13897 int len); 13898 13899 void XftTextRender8 (Display *dpy, 13900 int op, 13901 Picture src, 13902 XftFont *pub, 13903 Picture dst, 13904 int srcx, 13905 int srcy, 13906 int x, 13907 int y, 13908 const char *string, 13909 int len); 13910 void XftTextRender16 (Display *dpy, 13911 int op, 13912 Picture src, 13913 XftFont *pub, 13914 Picture dst, 13915 int srcx, 13916 int srcy, 13917 int x, 13918 int y, 13919 const wchar *string, 13920 int len); 13921 13922 void XftTextRender16BE (Display *dpy, 13923 int op, 13924 Picture src, 13925 XftFont *pub, 13926 Picture dst, 13927 int srcx, 13928 int srcy, 13929 int x, 13930 int y, 13931 const char *string, 13932 int len); 13933 13934 void XftTextRender16LE (Display *dpy, 13935 int op, 13936 Picture src, 13937 XftFont *pub, 13938 Picture dst, 13939 int srcx, 13940 int srcy, 13941 int x, 13942 int y, 13943 const char *string, 13944 int len); 13945 13946 void XftTextRender32 (Display *dpy, 13947 int op, 13948 Picture src, 13949 XftFont *pub, 13950 Picture dst, 13951 int srcx, 13952 int srcy, 13953 int x, 13954 int y, 13955 const dchar *string, 13956 int len); 13957 13958 void XftTextRender32BE (Display *dpy, 13959 int op, 13960 Picture src, 13961 XftFont *pub, 13962 Picture dst, 13963 int srcx, 13964 int srcy, 13965 int x, 13966 int y, 13967 const char *string, 13968 int len); 13969 13970 void XftTextRender32LE (Display *dpy, 13971 int op, 13972 Picture src, 13973 XftFont *pub, 13974 Picture dst, 13975 int srcx, 13976 int srcy, 13977 int x, 13978 int y, 13979 const char *string, 13980 int len); 13981 13982 void XftTextRenderUtf8 (Display *dpy, 13983 int op, 13984 Picture src, 13985 XftFont *pub, 13986 Picture dst, 13987 int srcx, 13988 int srcy, 13989 int x, 13990 int y, 13991 const char *string, 13992 int len); 13993 13994 void XftTextRenderUtf16 (Display *dpy, 13995 int op, 13996 Picture src, 13997 XftFont *pub, 13998 Picture dst, 13999 int srcx, 14000 int srcy, 14001 int x, 14002 int y, 14003 const char *string, 14004 FcEndian endian, 14005 int len); 14006 FcPattern * XftXlfdParse (const char *xlfd_orig, Bool ignore_scalable, Bool complete); 14007 14008 } 14009 14010 interface FontConfig { 14011 extern(C) @nogc pure: 14012 int FcPatternGetString(const FcPattern *p, const char *object, int n, char ** s); 14013 void FcFontSetDestroy(FcFontSet*); 14014 char* FcNameUnparse(const FcPattern *); 14015 } 14016 14017 mixin DynamicLoad!(Xft, "Xft", 2, librariesSuccessfullyLoaded) XftLibrary; 14018 mixin DynamicLoad!(FontConfig, "fontconfig", 1, librariesSuccessfullyLoaded) FontConfigLibrary; 14019 14020 14021 /* Xft } */ 14022 14023 class XDisconnectException : Exception { 14024 bool userRequested; 14025 this(bool userRequested = true) { 14026 this.userRequested = userRequested; 14027 super("X disconnected"); 14028 } 14029 } 14030 14031 /++ 14032 Platform-specific for X11. Traps errors for the duration of `dg`. Avoid calling this from inside a call to this. 14033 14034 Please note that it returns 14035 +/ 14036 XErrorEvent[] trapXErrors(scope void delegate() dg) { 14037 14038 static XErrorEvent[] errorBuffer; 14039 14040 static extern(C) int handler (Display* dpy, XErrorEvent* evt) nothrow { 14041 errorBuffer ~= *evt; 14042 return 0; 14043 } 14044 14045 auto savedErrorHandler = XSetErrorHandler(&handler); 14046 14047 try { 14048 dg(); 14049 } finally { 14050 XSync(XDisplayConnection.get, 0/*False*/); 14051 XSetErrorHandler(savedErrorHandler); 14052 } 14053 14054 auto bfr = errorBuffer; 14055 errorBuffer = null; 14056 14057 return bfr; 14058 } 14059 14060 /// Platform-specific for X11. A singleton class (well, all its methods are actually static... so more like a namespace) wrapping a `Display*`. 14061 class XDisplayConnection { 14062 private __gshared Display* display; 14063 private __gshared XIM xim; 14064 private __gshared char* displayName; 14065 14066 private __gshared int connectionSequence_; 14067 private __gshared bool isLocal_; 14068 14069 /// use this for lazy caching when reconnection 14070 static int connectionSequenceNumber() { return connectionSequence_; } 14071 14072 /++ 14073 Guesses if the connection appears to be local. 14074 14075 History: 14076 Added June 3, 2021 14077 +/ 14078 static @property bool isLocal() nothrow @trusted @nogc { 14079 return isLocal_; 14080 } 14081 14082 /// Attempts recreation of state, may require application assistance 14083 /// You MUST call this OUTSIDE the event loop. Let the exception kill the loop, 14084 /// then call this, and if successful, reenter the loop. 14085 static void discardAndRecreate(string newDisplayString = null) { 14086 if(insideXEventLoop) 14087 throw new Error("You MUST call discardAndRecreate from OUTSIDE the event loop"); 14088 14089 // 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 14090 auto chnenhm = CapableOfHandlingNativeEvent.nativeHandleMapping.dup; 14091 14092 foreach(handle; chnenhm) { 14093 handle.discardConnectionState(); 14094 } 14095 14096 discardState(); 14097 14098 if(newDisplayString !is null) 14099 setDisplayName(newDisplayString); 14100 14101 auto display = get(); 14102 14103 foreach(handle; chnenhm) { 14104 handle.recreateAfterDisconnect(); 14105 } 14106 } 14107 14108 private __gshared EventMask rootEventMask; 14109 14110 /++ 14111 Requests the specified input from the root window on the connection, in addition to any other request. 14112 14113 14114 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. 14115 14116 $(WARNING it calls XSelectInput itself, which will override any other root window input you have!) 14117 +/ 14118 static void addRootInput(EventMask mask) { 14119 auto old = rootEventMask; 14120 rootEventMask |= mask; 14121 get(); // to ensure display connected 14122 if(display !is null && rootEventMask != old) 14123 XSelectInput(display, RootWindow(display, DefaultScreen(display)), rootEventMask); 14124 } 14125 14126 static void discardState() { 14127 freeImages(); 14128 14129 foreach(atomPtr; interredAtoms) 14130 *atomPtr = 0; 14131 interredAtoms = null; 14132 interredAtoms.assumeSafeAppend(); 14133 14134 ScreenPainterImplementation.fontAttempted = false; 14135 ScreenPainterImplementation.defaultfont = null; 14136 ScreenPainterImplementation.defaultfontset = null; 14137 14138 Image.impl.xshmQueryCompleted = false; 14139 Image.impl._xshmAvailable = false; 14140 14141 SimpleWindow.nativeMapping = null; 14142 CapableOfHandlingNativeEvent.nativeHandleMapping = null; 14143 // GlobalHotkeyManager 14144 14145 display = null; 14146 xim = null; 14147 } 14148 14149 // Do you want to know why do we need all this horrible-looking code? See comment at the bottom. 14150 private static void createXIM () { 14151 import core.stdc.locale : setlocale, LC_ALL; 14152 import core.stdc.stdio : stderr, fprintf; 14153 import core.stdc.stdlib : free; 14154 import core.stdc.string : strdup; 14155 14156 static immutable string[3] mtry = [ "", "@im=local", "@im=" ]; 14157 14158 auto olocale = strdup(setlocale(LC_ALL, null)); 14159 setlocale(LC_ALL, (sdx_isUTF8Locale ? "" : "en_US.UTF-8")); 14160 scope(exit) { setlocale(LC_ALL, olocale); free(olocale); } 14161 14162 //fprintf(stderr, "opening IM...\n"); 14163 foreach (string s; mtry) { 14164 XSetLocaleModifiers(s.ptr); // it's safe, as `s` is string literal 14165 if ((xim = XOpenIM(display, null, null, null)) !is null) return; 14166 } 14167 fprintf(stderr, "createXIM: XOpenIM failed!\n"); 14168 } 14169 14170 // for X11 we will keep all XShm-allocated images in this list, so we can free 'em on connection closing. 14171 // we'll use glibc malloc()/free(), 'cause `unregisterImage()` can be called from object dtor. 14172 static struct ImgList { 14173 size_t img; // class; hide it from GC 14174 ImgList* next; 14175 } 14176 14177 static __gshared ImgList* imglist = null; 14178 static __gshared bool imglistLocked = false; // true: don't register and unregister images 14179 14180 static void registerImage (Image img) { 14181 if (!imglistLocked && img !is null) { 14182 import core.stdc.stdlib : malloc; 14183 auto it = cast(ImgList*)malloc(ImgList.sizeof); 14184 assert(it !is null); // do proper checks 14185 it.img = cast(size_t)cast(void*)img; 14186 it.next = imglist; 14187 imglist = it; 14188 version(sdpy_debug_xshm) { import core.stdc.stdio : printf; printf("registering image %p\n", cast(void*)img); } 14189 } 14190 } 14191 14192 static void unregisterImage (Image img) { 14193 if (!imglistLocked && img !is null) { 14194 import core.stdc.stdlib : free; 14195 ImgList* prev = null; 14196 ImgList* cur = imglist; 14197 while (cur !is null) { 14198 if (cur.img == cast(size_t)cast(void*)img) break; // i found her! 14199 prev = cur; 14200 cur = cur.next; 14201 } 14202 if (cur !is null) { 14203 if (prev is null) imglist = cur.next; else prev.next = cur.next; 14204 free(cur); 14205 version(sdpy_debug_xshm) { import core.stdc.stdio : printf; printf("unregistering image %p\n", cast(void*)img); } 14206 } else { 14207 version(sdpy_debug_xshm) { import core.stdc.stdio : printf; printf("trying to unregister unknown image %p\n", cast(void*)img); } 14208 } 14209 } 14210 } 14211 14212 static void freeImages () { // needed for discardAndRecreate 14213 imglistLocked = true; 14214 scope(exit) imglistLocked = false; 14215 ImgList* cur = imglist; 14216 ImgList* next = null; 14217 while (cur !is null) { 14218 import core.stdc.stdlib : free; 14219 next = cur.next; 14220 version(sdpy_debug_xshm) { import core.stdc.stdio : printf; printf("disposing image %p\n", cast(void*)cur.img); } 14221 (cast(Image)cast(void*)cur.img).dispose(); 14222 free(cur); 14223 cur = next; 14224 } 14225 imglist = null; 14226 } 14227 14228 /// can be used to override normal handling of display name 14229 /// from environment and/or command line 14230 static setDisplayName(string newDisplayName) { 14231 displayName = cast(char*) (newDisplayName ~ '\0'); 14232 } 14233 14234 /// resets to the default display string 14235 static resetDisplayName() { 14236 displayName = null; 14237 } 14238 14239 /// 14240 static Display* get() { 14241 if(display is null) { 14242 if(!librariesSuccessfullyLoaded) 14243 throw new Exception("Unable to load X11 client libraries"); 14244 display = XOpenDisplay(displayName); 14245 14246 isLocal_ = false; 14247 14248 connectionSequence_++; 14249 if(display is null) 14250 throw new Exception("Unable to open X display"); 14251 14252 auto str = display.display_name; 14253 // this is a bit of a hack but like if it looks like a unix socket we assume it is local 14254 // and otherwise it probably isn't 14255 if(str is null || (str[0] != ':' && str[0] != '/')) 14256 isLocal_ = false; 14257 else 14258 isLocal_ = true; 14259 14260 debug(sdpy_x_errors) { 14261 XSetErrorHandler(&adrlogger); 14262 XSynchronize(display, true); 14263 14264 extern(C) int wtf() { 14265 if(errorHappened) { 14266 asm { int 3; } 14267 errorHappened = false; 14268 } 14269 return 0; 14270 } 14271 XSetAfterFunction(display, &wtf); 14272 } 14273 14274 14275 XSetIOErrorHandler(&x11ioerrCB); 14276 Bool sup; 14277 XkbSetDetectableAutoRepeat(display, 1, &sup); // so we will not receive KeyRelease until key is really released 14278 createXIM(); 14279 version(with_eventloop) { 14280 import arsd.eventloop; 14281 addFileEventListeners(display.fd, &eventListener, null, null); 14282 } 14283 } 14284 14285 return display; 14286 } 14287 14288 extern(C) 14289 static int x11ioerrCB(Display* dpy) { 14290 throw new XDisconnectException(false); 14291 } 14292 14293 version(with_eventloop) { 14294 import arsd.eventloop; 14295 static void eventListener(OsFileHandle fd) { 14296 //this.mtLock(); 14297 //scope(exit) this.mtUnlock(); 14298 while(XPending(display)) 14299 doXNextEvent(display); 14300 } 14301 } 14302 14303 // close connection on program exit -- we need this to properly free all images 14304 static ~this () { 14305 // the gui thread must clean up after itself or else Xlib might deadlock 14306 // using this flag on any thread destruction is the easiest way i know of 14307 // (shared static this is run by the LAST thread to exit, which may not be 14308 // the gui thread, and normal static this run by ALL threads, so we gotta check.) 14309 if(thisIsGuiThread) 14310 close(); 14311 } 14312 14313 /// 14314 static void close() { 14315 if(display is null) 14316 return; 14317 14318 version(with_eventloop) { 14319 import arsd.eventloop; 14320 removeFileEventListeners(display.fd); 14321 } 14322 14323 // now remove all registered images to prevent shared memory leaks 14324 freeImages(); 14325 14326 // tbh I don't know why it is doing this but like if this happens to run 14327 // from the other thread there's frequent hanging inside here. 14328 if(thisIsGuiThread) 14329 XCloseDisplay(display); 14330 display = null; 14331 } 14332 } 14333 14334 mixin template NativeImageImplementation() { 14335 XImage* handle; 14336 ubyte* rawData; 14337 14338 XShmSegmentInfo shminfo; 14339 bool premultiply = true; 14340 14341 __gshared bool xshmQueryCompleted; 14342 __gshared bool _xshmAvailable; 14343 public static @property bool xshmAvailable() { 14344 if(!xshmQueryCompleted) { 14345 int i1, i2, i3; 14346 xshmQueryCompleted = true; 14347 14348 if(!XDisplayConnection.isLocal) 14349 _xshmAvailable = false; 14350 else 14351 _xshmAvailable = XQueryExtension(XDisplayConnection.get(), "MIT-SHM", &i1, &i2, &i3) != 0; 14352 } 14353 return _xshmAvailable; 14354 } 14355 14356 bool usingXshm; 14357 final: 14358 14359 private __gshared bool xshmfailed; 14360 14361 void createImage(int width, int height, bool forcexshm=false, bool enableAlpha = false) { 14362 auto display = XDisplayConnection.get(); 14363 assert(display !is null); 14364 auto screen = DefaultScreen(display); 14365 14366 // it will only use shared memory for somewhat largish images, 14367 // since otherwise we risk wasting shared memory handles on a lot of little ones 14368 if (xshmAvailable && (forcexshm || (width > 100 && height > 100))) { 14369 14370 14371 // it is possible for the query extension to return true, the DISPLAY check to pass, yet 14372 // the actual use still fails. For example, if the program is in a container and permission denied 14373 // on shared memory, or if it is a local thing forwarded to a remote server, etc. 14374 // 14375 // If it does fail, we need to detect it now, abort the xshm and fall back to core protocol. 14376 14377 14378 // synchronize so preexisting buffers are clear 14379 XSync(display, false); 14380 xshmfailed = false; 14381 14382 auto oldErrorHandler = XSetErrorHandler(&XShmErrorHandler); 14383 14384 14385 usingXshm = true; 14386 handle = XShmCreateImage( 14387 display, 14388 DefaultVisual(display, screen), 14389 enableAlpha ? 32: 24, 14390 ImageFormat.ZPixmap, 14391 null, 14392 &shminfo, 14393 width, height); 14394 if(handle is null) 14395 goto abortXshm1; 14396 14397 if(handle.bytes_per_line != 4 * width) 14398 goto abortXshm2; 14399 14400 shminfo.shmid = shmget(IPC_PRIVATE, handle.bytes_per_line * height, IPC_CREAT | 511 /* 0777 */); 14401 if(shminfo.shmid < 0) 14402 goto abortXshm3; 14403 handle.data = shminfo.shmaddr = rawData = cast(ubyte*) shmat(shminfo.shmid, null, 0); 14404 if(rawData == cast(ubyte*) -1) 14405 goto abortXshm4; 14406 shminfo.readOnly = 0; 14407 XShmAttach(display, &shminfo); 14408 14409 // and now to the final error check to ensure it actually worked. 14410 XSync(display, false); 14411 if(xshmfailed) 14412 goto abortXshm5; 14413 14414 XSetErrorHandler(oldErrorHandler); 14415 14416 XDisplayConnection.registerImage(this); 14417 // if I don't flush here there's a chance the dtor will run before the 14418 // ctor and lead to a bad value X error. While this hurts the efficiency 14419 // it is local anyway so prolly better to keep it simple 14420 XFlush(display); 14421 14422 return; 14423 14424 abortXshm5: 14425 shmdt(shminfo.shmaddr); 14426 rawData = null; 14427 14428 abortXshm4: 14429 shmctl(shminfo.shmid, IPC_RMID, null); 14430 14431 abortXshm3: 14432 // nothing needed, the shmget failed so there's nothing to free 14433 14434 abortXshm2: 14435 XDestroyImage(handle); 14436 handle = null; 14437 14438 abortXshm1: 14439 XSetErrorHandler(oldErrorHandler); 14440 usingXshm = false; 14441 handle = null; 14442 14443 shminfo = typeof(shminfo).init; 14444 14445 _xshmAvailable = false; // don't try again in the future 14446 14447 // writeln("fallingback"); 14448 14449 goto fallback; 14450 14451 } else { 14452 fallback: 14453 14454 if (forcexshm) throw new Exception("can't create XShm Image"); 14455 // This actually needs to be malloc to avoid a double free error when XDestroyImage is called 14456 import core.stdc.stdlib : malloc; 14457 rawData = cast(ubyte*) malloc(width * height * 4); 14458 14459 handle = XCreateImage( 14460 display, 14461 DefaultVisual(display, screen), 14462 enableAlpha ? 32 : 24, // bpp 14463 ImageFormat.ZPixmap, 14464 0, // offset 14465 rawData, 14466 width, height, 14467 enableAlpha ? 32 : 8 /* FIXME */, 4 * width); // padding, bytes per line 14468 } 14469 } 14470 14471 void dispose() { 14472 // note: this calls free(rawData) for us 14473 if(handle) { 14474 if (usingXshm) { 14475 XDisplayConnection.unregisterImage(this); 14476 if (XDisplayConnection.get()) XShmDetach(XDisplayConnection.get(), &shminfo); 14477 } 14478 XDestroyImage(handle); 14479 if(usingXshm) { 14480 shmdt(shminfo.shmaddr); 14481 shmctl(shminfo.shmid, IPC_RMID, null); 14482 } 14483 handle = null; 14484 } 14485 } 14486 14487 Color getPixel(int x, int y) { 14488 auto offset = (y * width + x) * 4; 14489 Color c; 14490 c.a = enableAlpha ? rawData[offset + 3] : 255; 14491 c.b = rawData[offset + 0]; 14492 c.g = rawData[offset + 1]; 14493 c.r = rawData[offset + 2]; 14494 if(enableAlpha && premultiply) 14495 c.unPremultiply; 14496 return c; 14497 } 14498 14499 void setPixel(int x, int y, Color c) { 14500 if(enableAlpha && premultiply) 14501 c.premultiply(); 14502 auto offset = (y * width + x) * 4; 14503 rawData[offset + 0] = c.b; 14504 rawData[offset + 1] = c.g; 14505 rawData[offset + 2] = c.r; 14506 if(enableAlpha) 14507 rawData[offset + 3] = c.a; 14508 } 14509 14510 void convertToRgbaBytes(ubyte[] where) { 14511 assert(where.length == this.width * this.height * 4); 14512 14513 // if rawData had a length.... 14514 //assert(rawData.length == where.length); 14515 for(int idx = 0; idx < where.length; idx += 4) { 14516 where[idx + 0] = rawData[idx + 2]; // r 14517 where[idx + 1] = rawData[idx + 1]; // g 14518 where[idx + 2] = rawData[idx + 0]; // b 14519 where[idx + 3] = enableAlpha ? rawData[idx + 3] : 255; // a 14520 14521 if(enableAlpha && premultiply) 14522 unPremultiplyRgba(where[idx .. idx + 4]); 14523 } 14524 } 14525 14526 void setFromRgbaBytes(in ubyte[] where) { 14527 assert(where.length == this.width * this.height * 4); 14528 14529 // if rawData had a length.... 14530 //assert(rawData.length == where.length); 14531 for(int idx = 0; idx < where.length; idx += 4) { 14532 rawData[idx + 2] = where[idx + 0]; // r 14533 rawData[idx + 1] = where[idx + 1]; // g 14534 rawData[idx + 0] = where[idx + 2]; // b 14535 if(enableAlpha) { 14536 rawData[idx + 3] = where[idx + 3]; // a 14537 if(premultiply) 14538 premultiplyBgra(rawData[idx .. idx + 4]); 14539 } 14540 } 14541 } 14542 14543 } 14544 14545 mixin template NativeSimpleWindowImplementation() { 14546 GC gc; 14547 Window window; 14548 Display* display; 14549 14550 Pixmap buffer; 14551 int bufferw, bufferh; // size of the buffer; can be bigger than window 14552 XIC xic; // input context 14553 int curHidden = 0; // counter 14554 Cursor blankCurPtr = 0; 14555 int cursorSequenceNumber = 0; 14556 int warpEventCount = 0; // number of mouse movement events to eat 14557 14558 __gshared X11SetSelectionHandler[Atom] setSelectionHandlers; 14559 X11GetSelectionHandler[Atom] getSelectionHandlers; 14560 14561 version(without_opengl) {} else 14562 GLXContext glc; 14563 14564 private void fixFixedSize(bool forced=false) (int width, int height) { 14565 if (forced || this.resizability == Resizability.fixedSize) { 14566 //{ import core.stdc.stdio; printf("fixing size to: %dx%d\n", width, height); } 14567 XSizeHints sh; 14568 static if (!forced) { 14569 c_long spr; 14570 XGetWMNormalHints(display, window, &sh, &spr); 14571 sh.flags |= PMaxSize | PMinSize; 14572 } else { 14573 sh.flags = PMaxSize | PMinSize; 14574 } 14575 sh.min_width = width; 14576 sh.min_height = height; 14577 sh.max_width = width; 14578 sh.max_height = height; 14579 XSetWMNormalHints(display, window, &sh); 14580 //XFlush(display); 14581 } 14582 } 14583 14584 ScreenPainter getPainter(bool manualInvalidations) { 14585 return ScreenPainter(this, window, manualInvalidations); 14586 } 14587 14588 void move(int x, int y) { 14589 XMoveWindow(display, window, x, y); 14590 } 14591 14592 void resize(int w, int h) { 14593 if (w < 1) w = 1; 14594 if (h < 1) h = 1; 14595 XResizeWindow(display, window, w, h); 14596 14597 // calling this now to avoid waiting for the server to 14598 // acknowledge the resize; draws without returning to the 14599 // event loop will thus actually work. the server's event 14600 // btw might overrule this and resize it again 14601 recordX11Resize(display, this, w, h); 14602 14603 updateOpenglViewportIfNeeded(w, h); 14604 } 14605 14606 void moveResize (int x, int y, int w, int h) { 14607 if (w < 1) w = 1; 14608 if (h < 1) h = 1; 14609 XMoveResizeWindow(display, window, x, y, w, h); 14610 updateOpenglViewportIfNeeded(w, h); 14611 } 14612 14613 void hideCursor () { 14614 if (curHidden++ == 0) { 14615 if (!blankCurPtr || cursorSequenceNumber != XDisplayConnection.connectionSequenceNumber) { 14616 static const(char)[1] cmbmp = 0; 14617 XColor blackcolor = { 0, 0, 0, 0, 0, 0 }; 14618 Pixmap pm = XCreateBitmapFromData(display, window, cmbmp.ptr, 1, 1); 14619 blankCurPtr = XCreatePixmapCursor(display, pm, pm, &blackcolor, &blackcolor, 0, 0); 14620 cursorSequenceNumber = XDisplayConnection.connectionSequenceNumber; 14621 XFreePixmap(display, pm); 14622 } 14623 XDefineCursor(display, window, blankCurPtr); 14624 } 14625 } 14626 14627 void showCursor () { 14628 if (--curHidden == 0) XUndefineCursor(display, window); 14629 } 14630 14631 void warpMouse (int x, int y) { 14632 // here i will send dummy "ignore next mouse motion" event, 14633 // 'cause `XWarpPointer()` sends synthesised mouse motion, 14634 // and we don't need to report it to the user (as warping is 14635 // used when the user needs movement deltas). 14636 //XClientMessageEvent xclient; 14637 XEvent e; 14638 e.xclient.type = EventType.ClientMessage; 14639 e.xclient.window = window; 14640 e.xclient.message_type = GetAtom!("_X11SDPY_INSMME_FLAG_EVENT_", true)(display); // let's hope nobody else will use such stupid name ;-) 14641 e.xclient.format = 32; 14642 e.xclient.data.l[0] = 0; 14643 debug(x11sdpy_warp_debug) { import core.stdc.stdio : printf; printf("X11: sending \"INSMME\"...\n"); } 14644 //{ 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]); } 14645 XSendEvent(display, window, false, EventMask.NoEventMask, /*cast(XEvent*)&xclient*/&e); 14646 // now warp pointer... 14647 debug(x11sdpy_warp_debug) { import core.stdc.stdio : printf; printf("X11: sending \"warp\"...\n"); } 14648 XWarpPointer(display, None, window, 0, 0, 0, 0, x, y); 14649 // ...and flush 14650 debug(x11sdpy_warp_debug) { import core.stdc.stdio : printf; printf("X11: flushing...\n"); } 14651 XFlush(display); 14652 } 14653 14654 void sendDummyEvent () { 14655 // here i will send dummy event to ping event queue 14656 XEvent e; 14657 e.xclient.type = EventType.ClientMessage; 14658 e.xclient.window = window; 14659 e.xclient.message_type = GetAtom!("_X11SDPY_DUMMY_EVENT_", true)(display); // let's hope nobody else will use such stupid name ;-) 14660 e.xclient.format = 32; 14661 e.xclient.data.l[0] = 0; 14662 XSendEvent(display, window, false, EventMask.NoEventMask, /*cast(XEvent*)&xclient*/&e); 14663 XFlush(display); 14664 } 14665 14666 void setTitle(string title) { 14667 if (title.ptr is null) title = ""; 14668 auto XA_UTF8 = XInternAtom(display, "UTF8_STRING".ptr, false); 14669 auto XA_NETWM_NAME = XInternAtom(display, "_NET_WM_NAME".ptr, false); 14670 XTextProperty windowName; 14671 windowName.value = title.ptr; 14672 windowName.encoding = XA_UTF8; //XA_STRING; 14673 windowName.format = 8; 14674 windowName.nitems = cast(uint)title.length; 14675 XSetWMName(display, window, &windowName); 14676 char[1024] namebuf = 0; 14677 auto maxlen = namebuf.length-1; 14678 if (maxlen > title.length) maxlen = title.length; 14679 namebuf[0..maxlen] = title[0..maxlen]; 14680 XStoreName(display, window, namebuf.ptr); 14681 XChangeProperty(display, window, XA_NETWM_NAME, XA_UTF8, 8, PropModeReplace, title.ptr, cast(uint)title.length); 14682 flushGui(); // without this OpenGL windows has a LONG delay before changing title 14683 } 14684 14685 string[] getTitles() { 14686 auto XA_UTF8 = XInternAtom(display, "UTF8_STRING".ptr, false); 14687 auto XA_NETWM_NAME = XInternAtom(display, "_NET_WM_NAME".ptr, false); 14688 XTextProperty textProp; 14689 if (XGetTextProperty(display, window, &textProp, XA_NETWM_NAME) != 0 || XGetWMName(display, window, &textProp) != 0) { 14690 if ((textProp.encoding == XA_UTF8 || textProp.encoding == XA_STRING) && textProp.format == 8) { 14691 return textProp.value[0 .. textProp.nitems].idup.split('\0'); 14692 } else 14693 return []; 14694 } else 14695 return null; 14696 } 14697 14698 string getTitle() { 14699 auto titles = getTitles(); 14700 return titles.length ? titles[0] : null; 14701 } 14702 14703 void setMinSize (int minwidth, int minheight) { 14704 import core.stdc.config : c_long; 14705 if (minwidth < 1) minwidth = 1; 14706 if (minheight < 1) minheight = 1; 14707 XSizeHints sh; 14708 c_long spr; 14709 XGetWMNormalHints(display, window, &sh, &spr); 14710 sh.min_width = minwidth; 14711 sh.min_height = minheight; 14712 sh.flags |= PMinSize; 14713 XSetWMNormalHints(display, window, &sh); 14714 flushGui(); 14715 } 14716 14717 void setMaxSize (int maxwidth, int maxheight) { 14718 import core.stdc.config : c_long; 14719 if (maxwidth < 1) maxwidth = 1; 14720 if (maxheight < 1) maxheight = 1; 14721 XSizeHints sh; 14722 c_long spr; 14723 XGetWMNormalHints(display, window, &sh, &spr); 14724 sh.max_width = maxwidth; 14725 sh.max_height = maxheight; 14726 sh.flags |= PMaxSize; 14727 XSetWMNormalHints(display, window, &sh); 14728 flushGui(); 14729 } 14730 14731 void setResizeGranularity (int granx, int grany) { 14732 import core.stdc.config : c_long; 14733 if (granx < 1) granx = 1; 14734 if (grany < 1) grany = 1; 14735 XSizeHints sh; 14736 c_long spr; 14737 XGetWMNormalHints(display, window, &sh, &spr); 14738 sh.width_inc = granx; 14739 sh.height_inc = grany; 14740 sh.flags |= PResizeInc; 14741 XSetWMNormalHints(display, window, &sh); 14742 flushGui(); 14743 } 14744 14745 void setOpacity (uint opacity) { 14746 arch_ulong o = opacity; 14747 if (opacity == uint.max) 14748 XDeleteProperty(display, window, XInternAtom(display, "_NET_WM_WINDOW_OPACITY".ptr, false)); 14749 else 14750 XChangeProperty(display, window, XInternAtom(display, "_NET_WM_WINDOW_OPACITY".ptr, false), 14751 XA_CARDINAL, 32, PropModeReplace, &o, 1); 14752 } 14753 14754 void createWindow(int width, int height, string title, in OpenGlOptions opengl, SimpleWindow parent) { 14755 version(without_opengl) {} else if(opengl == OpenGlOptions.yes && !openGlLibrariesSuccessfullyLoaded) throw new Exception("OpenGL libraries did not load"); 14756 display = XDisplayConnection.get(); 14757 auto screen = DefaultScreen(display); 14758 14759 bool overrideRedirect = false; 14760 if(windowType == WindowTypes.dropdownMenu || windowType == WindowTypes.popupMenu || windowType == WindowTypes.notification)// || windowType == WindowTypes.nestedChild) 14761 overrideRedirect = true; 14762 14763 version(without_opengl) {} 14764 else { 14765 if(opengl == OpenGlOptions.yes) { 14766 GLXFBConfig fbconf = null; 14767 XVisualInfo* vi = null; 14768 bool useLegacy = false; 14769 static if (SdpyIsUsingIVGLBinds) {if (glbindGetProcAddress("glHint") is null) assert(0, "GL: error loading OpenGL"); } // loads all necessary functions 14770 if (sdpyOpenGLContextVersion != 0 && glXCreateContextAttribsARB_present()) { 14771 int[23] visualAttribs = [ 14772 GLX_X_RENDERABLE , 1/*True*/, 14773 GLX_DRAWABLE_TYPE, GLX_WINDOW_BIT, 14774 GLX_RENDER_TYPE , GLX_RGBA_BIT, 14775 GLX_X_VISUAL_TYPE, GLX_TRUE_COLOR, 14776 GLX_RED_SIZE , 8, 14777 GLX_GREEN_SIZE , 8, 14778 GLX_BLUE_SIZE , 8, 14779 GLX_ALPHA_SIZE , 8, 14780 GLX_DEPTH_SIZE , 24, 14781 GLX_STENCIL_SIZE , 8, 14782 GLX_DOUBLEBUFFER , 1/*True*/, 14783 0/*None*/, 14784 ]; 14785 int fbcount; 14786 GLXFBConfig* fbc = glXChooseFBConfig(display, screen, visualAttribs.ptr, &fbcount); 14787 if (fbcount == 0) { 14788 useLegacy = true; // try to do at least something 14789 } else { 14790 // pick the FB config/visual with the most samples per pixel 14791 int bestidx = -1, bestns = -1; 14792 foreach (int fbi; 0..fbcount) { 14793 int sb, samples; 14794 glXGetFBConfigAttrib(display, fbc[fbi], GLX_SAMPLE_BUFFERS, &sb); 14795 glXGetFBConfigAttrib(display, fbc[fbi], GLX_SAMPLES, &samples); 14796 if (bestidx < 0 || sb && samples > bestns) { bestidx = fbi; bestns = samples; } 14797 } 14798 //{ import core.stdc.stdio; printf("found gl visual with %d samples\n", bestns); } 14799 fbconf = fbc[bestidx]; 14800 // Be sure to free the FBConfig list allocated by glXChooseFBConfig() 14801 XFree(fbc); 14802 vi = cast(XVisualInfo*)glXGetVisualFromFBConfig(display, fbconf); 14803 } 14804 } 14805 if (vi is null || useLegacy) { 14806 static immutable GLint[5] attrs = [ GLX_RGBA, GLX_DEPTH_SIZE, 24, GLX_DOUBLEBUFFER, None ]; 14807 vi = cast(XVisualInfo*)glXChooseVisual(display, 0, attrs.ptr); 14808 useLegacy = true; 14809 } 14810 if (vi is null) throw new Exception("no open gl visual found"); 14811 14812 XSetWindowAttributes swa; 14813 auto root = RootWindow(display, screen); 14814 swa.colormap = XCreateColormap(display, root, vi.visual, AllocNone); 14815 14816 swa.override_redirect = overrideRedirect; 14817 14818 window = XCreateWindow(display, (windowType != WindowTypes.nestedChild || parent is null) ? root : parent.impl.window, 14819 0, 0, width, height, 14820 0, vi.depth, 1 /* InputOutput */, vi.visual, CWColormap | CWOverrideRedirect, &swa); 14821 14822 // now try to use `glXCreateContextAttribsARB()` if it's here 14823 if (!useLegacy) { 14824 // request fairly advanced context, even with stencil buffer! 14825 int[9] contextAttribs = [ 14826 GLX_CONTEXT_MAJOR_VERSION_ARB, (sdpyOpenGLContextVersion>>8), 14827 GLX_CONTEXT_MINOR_VERSION_ARB, (sdpyOpenGLContextVersion&0xff), 14828 /*GLX_CONTEXT_PROFILE_MASK_ARB*/0x9126, (sdpyOpenGLContextCompatible ? /*GLX_CONTEXT_COMPATIBILITY_PROFILE_BIT_ARB*/0x02 : /*GLX_CONTEXT_CORE_PROFILE_BIT_ARB*/ 0x01), 14829 // for modern context, set "forward compatibility" flag too 14830 (sdpyOpenGLContextCompatible ? None : /*GLX_CONTEXT_FLAGS_ARB*/ 0x2094), /*GLX_CONTEXT_FORWARD_COMPATIBLE_BIT_ARB*/ 0x02, 14831 0/*None*/, 14832 ]; 14833 glc = glXCreateContextAttribsARB(display, fbconf, null, 1/*True*/, contextAttribs.ptr); 14834 if (glc is null && sdpyOpenGLContextAllowFallback) { 14835 sdpyOpenGLContextVersion = 0; 14836 glc = glXCreateContext(display, vi, null, /*GL_TRUE*/1); 14837 } 14838 //{ import core.stdc.stdio; printf("using modern ogl v%d.%d\n", contextAttribs[1], contextAttribs[3]); } 14839 } else { 14840 // fallback to old GLX call 14841 if (sdpyOpenGLContextAllowFallback || sdpyOpenGLContextVersion == 0) { 14842 sdpyOpenGLContextVersion = 0; 14843 glc = glXCreateContext(display, vi, null, /*GL_TRUE*/1); 14844 } 14845 } 14846 // sync to ensure any errors generated are processed 14847 XSync(display, 0/*False*/); 14848 //{ import core.stdc.stdio; printf("ogl is here\n"); } 14849 if(glc is null) 14850 throw new Exception("glc"); 14851 } 14852 } 14853 14854 if(opengl == OpenGlOptions.no) { 14855 14856 XSetWindowAttributes swa; 14857 swa.background_pixel = WhitePixel(display, screen); 14858 swa.border_pixel = BlackPixel(display, screen); 14859 swa.override_redirect = overrideRedirect; 14860 auto root = RootWindow(display, screen); 14861 swa.colormap = XCreateColormap(display, root, DefaultVisual(display, screen), AllocNone); 14862 14863 window = XCreateWindow(display, (windowType != WindowTypes.nestedChild || parent is null) ? root : parent.impl.window, 14864 0, 0, width, height, 14865 // I'm commenting that CWBackPixel thing just because it actually causes flicker for no apparent benefit. 14866 0, CopyFromParent, 1 /* InputOutput */, cast(Visual*) CopyFromParent, CWColormap /*| CWBackPixel*/ | CWBorderPixel | CWOverrideRedirect, &swa); 14867 14868 14869 14870 /* 14871 window = XCreateSimpleWindow( 14872 display, 14873 parent is null ? RootWindow(display, screen) : parent.impl.window, 14874 0, 0, // x, y 14875 width, height, 14876 1, // border width 14877 BlackPixel(display, screen), // border 14878 WhitePixel(display, screen)); // background 14879 */ 14880 14881 buffer = XCreatePixmap(display, cast(Drawable) window, width, height, DefaultDepthOfDisplay(display)); 14882 bufferw = width; 14883 bufferh = height; 14884 14885 gc = DefaultGC(display, screen); 14886 14887 // clear out the buffer to get us started... 14888 XSetForeground(display, gc, WhitePixel(display, screen)); 14889 XFillRectangle(display, cast(Drawable) buffer, gc, 0, 0, width, height); 14890 XSetForeground(display, gc, BlackPixel(display, screen)); 14891 } 14892 14893 // input context 14894 //TODO: create this only for top-level windows, and reuse that? 14895 populateXic(); 14896 14897 if (sdpyWindowClassStr is null) loadBinNameToWindowClassName(); 14898 if (sdpyWindowClassStr is null) sdpyWindowClass = "DSimpleWindow"; 14899 // window class 14900 if (sdpyWindowClassStr !is null && sdpyWindowClassStr[0]) { 14901 //{ import core.stdc.stdio; printf("winclass: [%s]\n", sdpyWindowClassStr); } 14902 XClassHint klass; 14903 XWMHints wh; 14904 if(this.customizationFlags & WindowFlags.managesChildWindowFocus) { 14905 wh.input = true; 14906 wh.flags |= InputHint; 14907 } 14908 XSizeHints size; 14909 klass.res_name = sdpyWindowClassStr; 14910 klass.res_class = sdpyWindowClassStr; 14911 XSetWMProperties(display, window, null, null, null, 0, &size, &wh, &klass); 14912 } 14913 14914 setTitle(title); 14915 SimpleWindow.nativeMapping[window] = this; 14916 CapableOfHandlingNativeEvent.nativeHandleMapping[window] = this; 14917 14918 // This gives our window a close button 14919 if (windowType != WindowTypes.eventOnly) { 14920 Atom[2] atoms = [GetAtom!"WM_DELETE_WINDOW"(display), GetAtom!"WM_TAKE_FOCUS"(display)]; 14921 int useAtoms; 14922 if(this.customizationFlags & WindowFlags.managesChildWindowFocus) { 14923 useAtoms = 2; 14924 } else { 14925 useAtoms = 1; 14926 } 14927 assert(useAtoms <= atoms.length); 14928 XSetWMProtocols(display, window, atoms.ptr, useAtoms); 14929 } 14930 14931 // FIXME: windowType and customizationFlags 14932 Atom[8] wsatoms; // here, due to goto 14933 int wmsacount = 0; // here, due to goto 14934 14935 try 14936 final switch(windowType) { 14937 case WindowTypes.normal: 14938 setNetWMWindowType(GetAtom!"_NET_WM_WINDOW_TYPE_NORMAL"(display)); 14939 break; 14940 case WindowTypes.undecorated: 14941 motifHideDecorations(); 14942 setNetWMWindowType(GetAtom!"_NET_WM_WINDOW_TYPE_NORMAL"(display)); 14943 break; 14944 case WindowTypes.eventOnly: 14945 _hidden = true; 14946 XSelectInput(display, window, EventMask.StructureNotifyMask); // without this, we won't get destroy notification 14947 goto hiddenWindow; 14948 //break; 14949 case WindowTypes.nestedChild: 14950 // handled in XCreateWindow calls 14951 break; 14952 14953 case WindowTypes.dropdownMenu: 14954 motifHideDecorations(); 14955 setNetWMWindowType(GetAtom!"_NET_WM_WINDOW_TYPE_DROPDOWN_MENU"(display)); 14956 customizationFlags |= WindowFlags.skipTaskbar | WindowFlags.alwaysOnTop; 14957 break; 14958 case WindowTypes.popupMenu: 14959 motifHideDecorations(); 14960 setNetWMWindowType(GetAtom!"_NET_WM_WINDOW_TYPE_POPUP_MENU"(display)); 14961 customizationFlags |= WindowFlags.skipTaskbar | WindowFlags.alwaysOnTop; 14962 break; 14963 case WindowTypes.notification: 14964 motifHideDecorations(); 14965 setNetWMWindowType(GetAtom!"_NET_WM_WINDOW_TYPE_NOTIFICATION"(display)); 14966 customizationFlags |= WindowFlags.skipTaskbar | WindowFlags.alwaysOnTop; 14967 break; 14968 case WindowTypes.minimallyWrapped: 14969 assert(0, "don't create a minimallyWrapped thing explicitly!"); 14970 /+ 14971 case WindowTypes.menu: 14972 atoms[0] = GetAtom!"_NET_WM_WINDOW_TYPE_MENU"(display); 14973 motifHideDecorations(); 14974 break; 14975 case WindowTypes.desktop: 14976 atoms[0] = GetAtom!"_NET_WM_WINDOW_TYPE_DESKTOP"(display); 14977 break; 14978 case WindowTypes.dock: 14979 atoms[0] = GetAtom!"_NET_WM_WINDOW_TYPE_DOCK"(display); 14980 break; 14981 case WindowTypes.toolbar: 14982 atoms[0] = GetAtom!"_NET_WM_WINDOW_TYPE_TOOLBAR"(display); 14983 break; 14984 case WindowTypes.menu: 14985 atoms[0] = GetAtom!"_NET_WM_WINDOW_TYPE_MENU"(display); 14986 break; 14987 case WindowTypes.utility: 14988 atoms[0] = GetAtom!"_NET_WM_WINDOW_TYPE_UTILITY"(display); 14989 break; 14990 case WindowTypes.splash: 14991 atoms[0] = GetAtom!"_NET_WM_WINDOW_TYPE_SPLASH"(display); 14992 break; 14993 case WindowTypes.dialog: 14994 atoms[0] = GetAtom!"_NET_WM_WINDOW_TYPE_DIALOG"(display); 14995 break; 14996 case WindowTypes.tooltip: 14997 atoms[0] = GetAtom!"_NET_WM_WINDOW_TYPE_TOOLTIP"(display); 14998 break; 14999 case WindowTypes.notification: 15000 atoms[0] = GetAtom!"_NET_WM_WINDOW_TYPE_NOTIFICATION"(display); 15001 break; 15002 case WindowTypes.combo: 15003 atoms[0] = GetAtom!"_NET_WM_WINDOW_TYPE_COMBO"(display); 15004 break; 15005 case WindowTypes.dnd: 15006 atoms[0] = GetAtom!"_NET_WM_WINDOW_TYPE_DND"(display); 15007 break; 15008 +/ 15009 } 15010 catch(Exception e) { 15011 // XInternAtom failed, prolly a WM 15012 // that doesn't support these things 15013 } 15014 15015 if (customizationFlags&WindowFlags.skipTaskbar) wsatoms[wmsacount++] = GetAtom!("_NET_WM_STATE_SKIP_TASKBAR", true)(display); 15016 // the two following flags may be ignored by WM 15017 if (customizationFlags&WindowFlags.alwaysOnTop) wsatoms[wmsacount++] = GetAtom!("_NET_WM_STATE_ABOVE", true)(display); 15018 if (customizationFlags&WindowFlags.alwaysOnBottom) wsatoms[wmsacount++] = GetAtom!("_NET_WM_STATE_BELOW", true)(display); 15019 15020 if (wmsacount != 0) XChangeProperty(display, window, GetAtom!("_NET_WM_STATE", true)(display), XA_ATOM, 32 /* bits */,0 /*PropModeReplace*/, wsatoms.ptr, wmsacount); 15021 15022 if (this.resizability == Resizability.fixedSize || (opengl == OpenGlOptions.no && this.resizability != Resizability.allowResizing)) fixFixedSize!true(width, height); 15023 15024 // What would be ideal here is if they only were 15025 // selected if there was actually an event handler 15026 // for them... 15027 15028 selectDefaultInput((customizationFlags & WindowFlags.alwaysRequestMouseMotionEvents)?true:false); 15029 15030 hiddenWindow: 15031 15032 // set the pid property for lookup later by window managers 15033 // a standard convenience 15034 import core.sys.posix.unistd; 15035 arch_ulong pid = getpid(); 15036 15037 XChangeProperty( 15038 display, 15039 impl.window, 15040 GetAtom!("_NET_WM_PID", true)(display), 15041 XA_CARDINAL, 15042 32 /* bits */, 15043 0 /*PropModeReplace*/, 15044 &pid, 15045 1); 15046 15047 if(isTransient && parent) { // customizationFlags & WindowFlags.transient) { 15048 if(parent is null) assert(0); 15049 XChangeProperty( 15050 display, 15051 impl.window, 15052 GetAtom!("WM_TRANSIENT_FOR", true)(display), 15053 XA_WINDOW, 15054 32 /* bits */, 15055 0 /*PropModeReplace*/, 15056 &parent.impl.window, 15057 1); 15058 15059 } 15060 15061 if(windowType != WindowTypes.eventOnly && (customizationFlags&WindowFlags.dontAutoShow) == 0) { 15062 XMapWindow(display, window); 15063 } else { 15064 _hidden = true; 15065 } 15066 } 15067 15068 void populateXic() { 15069 if (XDisplayConnection.xim !is null) { 15070 xic = XCreateIC(XDisplayConnection.xim, 15071 /*XNInputStyle*/"inputStyle".ptr, XIMPreeditNothing|XIMStatusNothing, 15072 /*XNClientWindow*/"clientWindow".ptr, window, 15073 /*XNFocusWindow*/"focusWindow".ptr, window, 15074 null); 15075 if (xic is null) { 15076 import core.stdc.stdio : stderr, fprintf; 15077 fprintf(stderr, "XCreateIC failed for window %u\n", cast(uint)window); 15078 } 15079 } 15080 } 15081 15082 void selectDefaultInput(bool forceIncludeMouseMotion) { 15083 auto mask = EventMask.ExposureMask | 15084 EventMask.KeyPressMask | 15085 EventMask.KeyReleaseMask | 15086 EventMask.PropertyChangeMask | 15087 EventMask.FocusChangeMask | 15088 EventMask.StructureNotifyMask | 15089 EventMask.SubstructureNotifyMask | 15090 EventMask.VisibilityChangeMask 15091 | EventMask.ButtonPressMask 15092 | EventMask.ButtonReleaseMask 15093 ; 15094 15095 // xshm is our shortcut for local connections 15096 if(XDisplayConnection.isLocal || forceIncludeMouseMotion) 15097 mask |= EventMask.PointerMotionMask; 15098 else 15099 mask |= EventMask.ButtonMotionMask; 15100 15101 XSelectInput(display, window, mask); 15102 } 15103 15104 15105 void setNetWMWindowType(Atom type) { 15106 Atom[2] atoms; 15107 15108 atoms[0] = type; 15109 // generic fallback 15110 atoms[1] = GetAtom!"_NET_WM_WINDOW_TYPE_NORMAL"(display); 15111 15112 XChangeProperty( 15113 display, 15114 impl.window, 15115 GetAtom!"_NET_WM_WINDOW_TYPE"(display), 15116 XA_ATOM, 15117 32 /* bits */, 15118 0 /*PropModeReplace*/, 15119 atoms.ptr, 15120 cast(int) atoms.length); 15121 } 15122 15123 void motifHideDecorations(bool hide = true) { 15124 MwmHints hints; 15125 hints.flags = MWM_HINTS_DECORATIONS; 15126 hints.decorations = hide ? 0 : 1; 15127 15128 XChangeProperty( 15129 display, 15130 impl.window, 15131 GetAtom!"_MOTIF_WM_HINTS"(display), 15132 GetAtom!"_MOTIF_WM_HINTS"(display), 15133 32 /* bits */, 15134 0 /*PropModeReplace*/, 15135 &hints, 15136 hints.sizeof / 4); 15137 } 15138 15139 /*k8: unused 15140 void createOpenGlContext() { 15141 15142 } 15143 */ 15144 15145 void closeWindow() { 15146 // I can't close this or a child window closing will 15147 // break events for everyone. So I'm just leaking it right 15148 // now and that is probably perfectly fine... 15149 version(none) 15150 if (customEventFDRead != -1) { 15151 import core.sys.posix.unistd : close; 15152 auto same = customEventFDRead == customEventFDWrite; 15153 15154 close(customEventFDRead); 15155 if(!same) 15156 close(customEventFDWrite); 15157 customEventFDRead = -1; 15158 customEventFDWrite = -1; 15159 } 15160 15161 version(without_opengl) {} else 15162 if(glc !is null) { 15163 glXDestroyContext(display, glc); 15164 glc = null; 15165 } 15166 15167 if(buffer) 15168 XFreePixmap(display, buffer); 15169 bufferw = bufferh = 0; 15170 if (blankCurPtr && cursorSequenceNumber == XDisplayConnection.connectionSequenceNumber) XFreeCursor(display, blankCurPtr); 15171 XDestroyWindow(display, window); 15172 XFlush(display); 15173 } 15174 15175 void dispose() { 15176 } 15177 15178 bool destroyed = false; 15179 } 15180 15181 bool insideXEventLoop; 15182 } 15183 15184 version(X11) { 15185 15186 int mouseDoubleClickTimeout = 350; /// Double click timeout. X only, you probably shouldn't change this. 15187 15188 private class ResizeEvent { 15189 int width, height; 15190 } 15191 15192 void recordX11ResizeAsync(Display* display, SimpleWindow win, int width, int height) { 15193 if(win.windowType == WindowTypes.minimallyWrapped) 15194 return; 15195 15196 if(win.pendingResizeEvent is null) { 15197 win.pendingResizeEvent = new ResizeEvent(); 15198 win.addEventListener((ResizeEvent re) { 15199 recordX11Resize(XDisplayConnection.get, win, re.width, re.height); 15200 }); 15201 } 15202 win.pendingResizeEvent.width = width; 15203 win.pendingResizeEvent.height = height; 15204 if(!win.eventQueued!ResizeEvent) { 15205 win.postEvent(win.pendingResizeEvent); 15206 } 15207 } 15208 15209 void recordX11Resize(Display* display, SimpleWindow win, int width, int height) { 15210 if(win.windowType == WindowTypes.minimallyWrapped) 15211 return; 15212 if(win.closed) 15213 return; 15214 15215 if(width != win.width || height != win.height) { 15216 15217 // writeln("RESIZE: ", width, "x", height, " was ", win._width, "x", win._height, " window: ", win.windowType, "-", win.title, " ", win.window); 15218 win._width = width; 15219 win._height = height; 15220 15221 if(win.openglMode == OpenGlOptions.no) { 15222 // FIXME: could this be more efficient? 15223 15224 if (win.bufferw < width || win.bufferh < height) { 15225 //{ 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); } 15226 // grow the internal buffer to match the window... 15227 auto newPixmap = XCreatePixmap(display, cast(Drawable) win.window, width, height, DefaultDepthOfDisplay(display)); 15228 { 15229 GC xgc = XCreateGC(win.display, cast(Drawable)win.window, 0, null); 15230 XCopyGC(win.display, win.gc, 0xffffffff, xgc); 15231 scope(exit) XFreeGC(win.display, xgc); 15232 XSetClipMask(win.display, xgc, None); 15233 XSetForeground(win.display, xgc, 0); 15234 XFillRectangle(display, cast(Drawable)newPixmap, xgc, 0, 0, width, height); 15235 } 15236 XCopyArea(display, 15237 cast(Drawable) win.buffer, 15238 cast(Drawable) newPixmap, 15239 win.gc, 0, 0, 15240 win.bufferw < width ? win.bufferw : win.width, 15241 win.bufferh < height ? win.bufferh : win.height, 15242 0, 0); 15243 15244 XFreePixmap(display, win.buffer); 15245 win.buffer = newPixmap; 15246 win.bufferw = width; 15247 win.bufferh = height; 15248 } 15249 15250 // clear unused parts of the buffer 15251 if (win.bufferw > width || win.bufferh > height) { 15252 GC xgc = XCreateGC(win.display, cast(Drawable)win.window, 0, null); 15253 XCopyGC(win.display, win.gc, 0xffffffff, xgc); 15254 scope(exit) XFreeGC(win.display, xgc); 15255 XSetClipMask(win.display, xgc, None); 15256 XSetForeground(win.display, xgc, 0); 15257 immutable int maxw = (win.bufferw > width ? win.bufferw : width); 15258 immutable int maxh = (win.bufferh > height ? win.bufferh : height); 15259 XFillRectangle(win.display, cast(Drawable)win.buffer, xgc, width, 0, maxw, maxh); // let X11 do clipping 15260 XFillRectangle(win.display, cast(Drawable)win.buffer, xgc, 0, height, maxw, maxh); // let X11 do clipping 15261 } 15262 15263 } 15264 15265 win.updateOpenglViewportIfNeeded(width, height); 15266 15267 win.fixFixedSize(width, height); //k8: this does nothing on my FluxBox; wtf?! 15268 15269 if(win.resizability != Resizability.automaticallyScaleIfPossible) 15270 if(win.windowResized !is null) { 15271 XUnlockDisplay(display); 15272 scope(exit) XLockDisplay(display); 15273 win.windowResized(width, height); 15274 } 15275 } 15276 } 15277 15278 15279 /// Platform-specific, you might use it when doing a custom event loop. 15280 bool doXNextEvent(Display* display) { 15281 bool done; 15282 XEvent e; 15283 XNextEvent(display, &e); 15284 version(sddddd) { 15285 if(auto win = e.xany.window in CapableOfHandlingNativeEvent.nativeHandleMapping) { 15286 if(typeid(cast(Object) *win) == NotificationAreaIcon.classinfo) 15287 writeln("event for: ", e.xany.window, "; type is ", to!string(cast(EventType)e.type)); 15288 } 15289 } 15290 15291 // filter out compose events 15292 if (XFilterEvent(&e, None)) { 15293 //{ import core.stdc.stdio : printf; printf("XFilterEvent filtered!\n"); } 15294 //NOTE: we should ungrab keyboard here, but simpledisplay doesn't use keyboard grabbing (yet) 15295 return false; 15296 } 15297 // process keyboard mapping changes 15298 if (e.type == EventType.KeymapNotify) { 15299 //{ import core.stdc.stdio : printf; printf("KeymapNotify processed!\n"); } 15300 XRefreshKeyboardMapping(&e.xmapping); 15301 return false; 15302 } 15303 15304 version(with_eventloop) 15305 import arsd.eventloop; 15306 15307 if(SimpleWindow.handleNativeGlobalEvent !is null) { 15308 // see windows impl's comments 15309 XUnlockDisplay(display); 15310 scope(exit) XLockDisplay(display); 15311 auto ret = SimpleWindow.handleNativeGlobalEvent(e); 15312 if(ret == 0) 15313 return done; 15314 } 15315 15316 15317 if(auto win = e.xany.window in CapableOfHandlingNativeEvent.nativeHandleMapping) { 15318 if(win.getNativeEventHandler !is null) { 15319 XUnlockDisplay(display); 15320 scope(exit) XLockDisplay(display); 15321 auto ret = win.getNativeEventHandler()(e); 15322 if(ret == 0) 15323 return done; 15324 } 15325 } 15326 15327 if(xrrEventBase != -1 && e.type == xrrEventBase + RRScreenChangeNotify) { 15328 if(auto win = e.xany.window in SimpleWindow.nativeMapping) { 15329 // we get this because of the RRScreenChangeNotifyMask 15330 15331 // this isn't actually an ideal way to do it since it wastes time 15332 // but meh it is simple and it works. 15333 win.actualDpiLoadAttempted = false; 15334 SimpleWindow.xRandrInfoLoadAttemped = false; 15335 win.updateActualDpi(); // trigger a reload 15336 } 15337 } 15338 15339 switch(e.type) { 15340 case EventType.SelectionClear: 15341 if(auto win = e.xselectionclear.window in SimpleWindow.nativeMapping) { 15342 // FIXME so it is supposed to finish any in progress transfers... but idk... 15343 // writeln("SelectionClear"); 15344 SimpleWindow.impl.setSelectionHandlers.remove(e.xselectionclear.selection); 15345 } 15346 break; 15347 case EventType.SelectionRequest: 15348 if(auto win = e.xselectionrequest.owner in SimpleWindow.nativeMapping) 15349 if(auto ssh = e.xselectionrequest.selection in SimpleWindow.impl.setSelectionHandlers) { 15350 // printf("SelectionRequest %s\n", XGetAtomName(e.xselectionrequest.display, e.xselectionrequest.target)); 15351 XUnlockDisplay(display); 15352 scope(exit) XLockDisplay(display); 15353 (*ssh).handleRequest(e); 15354 } 15355 break; 15356 case EventType.PropertyNotify: 15357 // printf("PropertyNotify %s %d\n", XGetAtomName(e.xproperty.display, e.xproperty.atom), e.xproperty.state); 15358 15359 foreach(ssh; SimpleWindow.impl.setSelectionHandlers) { 15360 if(ssh.matchesIncr(e.xproperty.window, e.xproperty.atom) && e.xproperty.state == PropertyNotification.PropertyDelete) 15361 ssh.sendMoreIncr(&e.xproperty); 15362 } 15363 15364 15365 if(auto win = e.xproperty.window in SimpleWindow.nativeMapping) 15366 if(auto handler = e.xproperty.atom in win.getSelectionHandlers) { 15367 if(handler.matchesIncr(e.xproperty.window, e.xproperty.atom) && e.xproperty.state == PropertyNotification.PropertyNewValue) { 15368 Atom target; 15369 int format; 15370 arch_ulong bytesafter, length; 15371 void* value; 15372 15373 ubyte[] s; 15374 Atom targetToKeep; 15375 15376 XGetWindowProperty( 15377 e.xproperty.display, 15378 e.xproperty.window, 15379 e.xproperty.atom, 15380 0, 15381 100000 /* length */, 15382 true, /* erase it to signal we got it and want more */ 15383 0 /*AnyPropertyType*/, 15384 &target, &format, &length, &bytesafter, &value); 15385 15386 if(!targetToKeep) 15387 targetToKeep = target; 15388 15389 auto id = (cast(ubyte*) value)[0 .. length]; 15390 15391 handler.handleIncrData(targetToKeep, id); 15392 15393 XFree(value); 15394 } 15395 } 15396 break; 15397 case EventType.SelectionNotify: 15398 if(auto win = e.xselection.requestor in SimpleWindow.nativeMapping) 15399 if(auto handler = e.xproperty.atom in win.getSelectionHandlers) { 15400 if(e.xselection.property == None) { // || e.xselection.property == GetAtom!("NULL", true)(e.xselection.display)) { 15401 XUnlockDisplay(display); 15402 scope(exit) XLockDisplay(display); 15403 handler.handleData(None, null); 15404 } else { 15405 Atom target; 15406 int format; 15407 arch_ulong bytesafter, length; 15408 void* value; 15409 XGetWindowProperty( 15410 e.xselection.display, 15411 e.xselection.requestor, 15412 e.xselection.property, 15413 0, 15414 100000 /* length */, 15415 //false, /* don't erase it */ 15416 true, /* do erase it lol */ 15417 0 /*AnyPropertyType*/, 15418 &target, &format, &length, &bytesafter, &value); 15419 15420 // FIXME: I don't have to copy it now since it is in char[] instead of string 15421 15422 { 15423 XUnlockDisplay(display); 15424 scope(exit) XLockDisplay(display); 15425 15426 if(target == XA_ATOM) { 15427 // initial request, see what they are able to work with and request the best one 15428 // we can handle, if available 15429 15430 Atom[] answer = (cast(Atom*) value)[0 .. length]; 15431 Atom best = handler.findBestFormat(answer); 15432 15433 /+ 15434 writeln("got ", answer); 15435 foreach(a; answer) 15436 printf("%s\n", XGetAtomName(display, a)); 15437 writeln("best ", best); 15438 +/ 15439 15440 if(best != None) { 15441 // actually request the best format 15442 XConvertSelection(e.xselection.display, e.xselection.selection, best, GetAtom!("SDD_DATA", true)(display), e.xselection.requestor, 0 /*CurrentTime*/); 15443 } 15444 } else if(target == GetAtom!"INCR"(display)) { 15445 // incremental 15446 15447 handler.prepareIncremental(e.xselection.requestor, e.xselection.property); 15448 15449 // signal the sending program that we see 15450 // the incr and are ready to receive more. 15451 XDeleteProperty( 15452 e.xselection.display, 15453 e.xselection.requestor, 15454 e.xselection.property); 15455 } else { 15456 // unsupported type... maybe, forward 15457 handler.handleData(target, cast(ubyte[]) value[0 .. length]); 15458 } 15459 } 15460 XFree(value); 15461 /* 15462 XDeleteProperty( 15463 e.xselection.display, 15464 e.xselection.requestor, 15465 e.xselection.property); 15466 */ 15467 } 15468 } 15469 break; 15470 case EventType.ConfigureNotify: 15471 auto event = e.xconfigure; 15472 if(auto win = event.window in SimpleWindow.nativeMapping) { 15473 if(win.windowType == WindowTypes.minimallyWrapped) 15474 break; 15475 //version(sdddd) { writeln(" w=", event.width, "; h=", event.height); } 15476 15477 /+ 15478 The ICCCM says window managers must send a synthetic event when the window 15479 is moved but NOT when it is resized. In the resize case, an event is sent 15480 with position (0, 0) which can be wrong and break the dpi calculations. 15481 15482 So we only consider the synthetic events from the WM and otherwise 15483 need to wait for some other event to get the position which... sucks. 15484 15485 I'd rather not have windows changing their layout on mouse motion after 15486 switching monitors... might be forced to but for now just ignoring it. 15487 15488 Easiest way to switch monitors without sending a size position is by 15489 maximize or fullscreen in a setup like mine, but on most setups those 15490 work on the monitor it is already living on, so it should be ok most the 15491 time. 15492 +/ 15493 if(event.send_event) { 15494 win.screenPositionKnown = true; 15495 win.screenPositionX = event.x; 15496 win.screenPositionY = event.y; 15497 win.updateActualDpi(); 15498 } 15499 15500 win.updateIMEPopupLocation(); 15501 recordX11ResizeAsync(display, *win, event.width, event.height); 15502 } 15503 break; 15504 case EventType.Expose: 15505 if(auto win = e.xexpose.window in SimpleWindow.nativeMapping) { 15506 if(win.windowType == WindowTypes.minimallyWrapped) 15507 break; 15508 // if it is closing from a popup menu, it can get 15509 // an Expose event right by the end and trigger a 15510 // BadDrawable error ... we'll just check 15511 // closed to handle that. 15512 if((*win).closed) break; 15513 if((*win).openglMode == OpenGlOptions.no) { 15514 bool doCopy = true;// e.xexpose.count == 0; // the count is better if we copy all area but meh 15515 if (win.handleExpose !is null) doCopy = !win.handleExpose(e.xexpose.x, e.xexpose.y, e.xexpose.width, e.xexpose.height, e.xexpose.count); 15516 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); 15517 } else { 15518 // need to redraw the scene somehow 15519 if(e.xexpose.count == 0) { // only do the last one since redrawOpenGlSceneNow always does it all 15520 XUnlockDisplay(display); 15521 scope(exit) XLockDisplay(display); 15522 version(without_opengl) {} else 15523 win.redrawOpenGlSceneSoon(); 15524 } 15525 } 15526 } 15527 break; 15528 case EventType.FocusIn: 15529 case EventType.FocusOut: 15530 15531 if(auto win = e.xfocus.window in SimpleWindow.nativeMapping) { 15532 /+ 15533 15534 void info(string detail) { 15535 string s; 15536 // import std.conv; 15537 // import std.datetime; 15538 s ~= to!string(Clock.currTime); 15539 s ~= " "; 15540 s ~= e.type == EventType.FocusIn ? "in " : "out"; 15541 s ~= " "; 15542 s ~= win.windowType == WindowTypes.nestedChild ? "child " : "main "; 15543 s ~= e.xfocus.mode == NotifyModes.NotifyNormal ? " normal ": " grabbed "; 15544 s ~= detail; 15545 s ~= " "; 15546 15547 sdpyPrintDebugString(s); 15548 15549 } 15550 15551 switch(e.xfocus.detail) { 15552 case NotifyDetail.NotifyAncestor: info("Ancestor"); break; 15553 case NotifyDetail.NotifyVirtual: info("Virtual"); break; 15554 case NotifyDetail.NotifyInferior: info("Inferior"); break; 15555 case NotifyDetail.NotifyNonlinear: info("Nonlinear"); break; 15556 case NotifyDetail.NotifyNonlinearVirtual: info("nlinearvirtual"); break; 15557 case NotifyDetail.NotifyPointer: info("pointer"); break; 15558 case NotifyDetail.NotifyPointerRoot: info("pointerroot"); break; 15559 case NotifyDetail.NotifyDetailNone: info("none"); break; 15560 default: 15561 15562 } 15563 +/ 15564 15565 15566 if(e.xfocus.detail == NotifyDetail.NotifyPointer) 15567 break; // just ignore these they seem irrelevant 15568 15569 auto old = win._focused; 15570 win._focused = e.type == EventType.FocusIn; 15571 15572 // yes, we are losing the focus, but to our own child. that's actually kinda keeping it. 15573 if(e.type == EventType.FocusOut && e.xfocus.detail == NotifyDetail.NotifyInferior) 15574 win._focused = true; 15575 15576 if(win.demandingAttention) 15577 demandAttention(*win, false); 15578 15579 win.updateIMEFocused(); 15580 15581 if(old != win._focused && win.onFocusChange) { 15582 XUnlockDisplay(display); 15583 scope(exit) XLockDisplay(display); 15584 win.onFocusChange(win._focused); 15585 } 15586 } 15587 break; 15588 case EventType.VisibilityNotify: 15589 if(auto win = e.xfocus.window in SimpleWindow.nativeMapping) { 15590 if (e.xvisibility.state == VisibilityNotify.VisibilityFullyObscured) { 15591 if (win.visibilityChanged !is null) { 15592 XUnlockDisplay(display); 15593 scope(exit) XLockDisplay(display); 15594 win.visibilityChanged(false); 15595 } 15596 } else { 15597 if (win.visibilityChanged !is null) { 15598 XUnlockDisplay(display); 15599 scope(exit) XLockDisplay(display); 15600 win.visibilityChanged(true); 15601 } 15602 } 15603 } 15604 break; 15605 case EventType.ClientMessage: 15606 if (e.xclient.message_type == GetAtom!("_X11SDPY_INSMME_FLAG_EVENT_", true)(e.xany.display)) { 15607 // "ignore next mouse motion" event, increment ignore counter for teh window 15608 if (auto win = e.xclient.window in SimpleWindow.nativeMapping) { 15609 ++(*win).warpEventCount; 15610 debug(x11sdpy_warp_debug) { import core.stdc.stdio : printf; printf("X11: got \"INSMME\" message, new count=%d\n", (*win).warpEventCount); } 15611 } else { 15612 debug(x11sdpy_warp_debug) { import core.stdc.stdio : printf; printf("X11: got \"INSMME\" WTF?!!\n"); } 15613 } 15614 } else if(e.xclient.data.l[0] == GetAtom!"WM_DELETE_WINDOW"(e.xany.display)) { 15615 // user clicked the close button on the window manager 15616 if(auto win = e.xclient.window in SimpleWindow.nativeMapping) { 15617 XUnlockDisplay(display); 15618 scope(exit) XLockDisplay(display); 15619 if ((*win).closeQuery !is null) (*win).closeQuery(); else (*win).close(); 15620 } 15621 15622 } else if(e.xclient.data.l[0] == GetAtom!"WM_TAKE_FOCUS"(e.xany.display)) { 15623 // writeln("HAPPENED"); 15624 // user clicked the close button on the window manager 15625 if(auto win = e.xclient.window in SimpleWindow.nativeMapping) { 15626 XUnlockDisplay(display); 15627 scope(exit) XLockDisplay(display); 15628 15629 auto setTo = *win; 15630 15631 if(win.setRequestedInputFocus !is null) { 15632 auto s = win.setRequestedInputFocus(); 15633 if(s !is null) { 15634 setTo = s; 15635 } 15636 } 15637 15638 assert(setTo !is null); 15639 15640 // FIXME: so this is actually supposed to focus to a relevant child window if appropriate 15641 15642 XSetInputFocus(display, setTo.impl.window, RevertToParent, e.xclient.data.l[1]); 15643 } 15644 } else if(e.xclient.message_type == GetAtom!"MANAGER"(e.xany.display)) { 15645 foreach(nai; NotificationAreaIcon.activeIcons) 15646 nai.newManager(); 15647 } else if(auto win = e.xclient.window in SimpleWindow.nativeMapping) { 15648 15649 bool xDragWindow = true; 15650 if(xDragWindow && e.xclient.message_type == GetAtom!"XdndStatus"(e.xany.display)) { 15651 //XDefineCursor(display, xDragWindow.impl.window, 15652 //writeln("XdndStatus ", e.xclient.data.l); 15653 } 15654 if(auto dh = win.dropHandler) { 15655 15656 static Atom[3] xFormatsBuffer; 15657 static Atom[] xFormats; 15658 15659 void resetXFormats() { 15660 xFormatsBuffer[] = 0; 15661 xFormats = xFormatsBuffer[]; 15662 } 15663 15664 if(e.xclient.message_type == GetAtom!"XdndEnter"(e.xany.display)) { 15665 // on Windows it is supposed to return the effect you actually do FIXME 15666 15667 auto sourceWindow = e.xclient.data.l[0]; 15668 15669 xFormatsBuffer[0] = e.xclient.data.l[2]; 15670 xFormatsBuffer[1] = e.xclient.data.l[3]; 15671 xFormatsBuffer[2] = e.xclient.data.l[4]; 15672 15673 if(e.xclient.data.l[1] & 1) { 15674 // can just grab it all but like we don't necessarily need them... 15675 xFormats = cast(Atom[]) getX11PropertyData(sourceWindow, GetAtom!"XdndTypeList"(display), XA_ATOM); 15676 } else { 15677 int len; 15678 foreach(fmt; xFormatsBuffer) 15679 if(fmt) len++; 15680 xFormats = xFormatsBuffer[0 .. len]; 15681 } 15682 15683 auto pkg = DropPackage(*win, e.xclient.data.l[0], 0, xFormats); 15684 15685 dh.dragEnter(&pkg); 15686 } else if(e.xclient.message_type == GetAtom!"XdndPosition"(e.xany.display)) { 15687 15688 auto pack = e.xclient.data.l[2]; 15689 15690 auto result = dh.dragOver(Point((pack & 0xffff0000) >> 16, pack & 0xffff)); // FIXME: translate screen coordinates back to window coords 15691 15692 15693 XClientMessageEvent xclient; 15694 15695 xclient.type = EventType.ClientMessage; 15696 xclient.window = e.xclient.data.l[0]; 15697 xclient.message_type = GetAtom!"XdndStatus"(display); 15698 xclient.format = 32; 15699 xclient.data.l[0] = win.impl.window; 15700 xclient.data.l[1] = (result.action != DragAndDropAction.none) ? 1 : 0; // will accept 15701 auto r = result.consistentWithin; 15702 xclient.data.l[2] = ((cast(short) r.left) << 16) | (cast(short) r.top); 15703 xclient.data.l[3] = ((cast(short) r.width) << 16) | (cast(short) r.height); 15704 xclient.data.l[4] = dndActionAtom(e.xany.display, result.action); 15705 15706 XSendEvent( 15707 display, 15708 e.xclient.data.l[0], 15709 false, 15710 EventMask.NoEventMask, 15711 cast(XEvent*) &xclient 15712 ); 15713 15714 15715 } else if(e.xclient.message_type == GetAtom!"XdndLeave"(e.xany.display)) { 15716 //writeln("XdndLeave"); 15717 // drop cancelled. 15718 // data.l[0] is the source window 15719 dh.dragLeave(); 15720 15721 resetXFormats(); 15722 } else if(e.xclient.message_type == GetAtom!"XdndDrop"(e.xany.display)) { 15723 // drop happening, should fetch data, then send finished 15724 // writeln("XdndDrop"); 15725 15726 auto pkg = DropPackage(*win, e.xclient.data.l[0], e.xclient.data.l[2], xFormats); 15727 15728 dh.drop(&pkg); 15729 15730 resetXFormats(); 15731 } else if(e.xclient.message_type == GetAtom!"XdndFinished"(e.xany.display)) { 15732 // writeln("XdndFinished"); 15733 15734 dh.finish(); 15735 } 15736 15737 } 15738 } 15739 break; 15740 case EventType.MapNotify: 15741 if(auto win = e.xmap.window in SimpleWindow.nativeMapping) { 15742 (*win)._visible = true; 15743 if (!(*win)._visibleForTheFirstTimeCalled) { 15744 (*win)._visibleForTheFirstTimeCalled = true; 15745 if ((*win).visibleForTheFirstTime !is null) { 15746 XUnlockDisplay(display); 15747 scope(exit) XLockDisplay(display); 15748 (*win).visibleForTheFirstTime(); 15749 } 15750 } 15751 if ((*win).visibilityChanged !is null) { 15752 XUnlockDisplay(display); 15753 scope(exit) XLockDisplay(display); 15754 (*win).visibilityChanged(true); 15755 } 15756 } 15757 break; 15758 case EventType.UnmapNotify: 15759 if(auto win = e.xunmap.window in SimpleWindow.nativeMapping) { 15760 win._visible = false; 15761 if (win.visibilityChanged !is null) { 15762 XUnlockDisplay(display); 15763 scope(exit) XLockDisplay(display); 15764 win.visibilityChanged(false); 15765 } 15766 } 15767 break; 15768 case EventType.DestroyNotify: 15769 if(auto win = e.xdestroywindow.window in SimpleWindow.nativeMapping) { 15770 if(win.destroyed) 15771 break; // might get a notification both for itself and from its parent 15772 if (win.onDestroyed !is null) try { win.onDestroyed(); } catch (Exception e) {} // sorry 15773 win._closed = true; // just in case 15774 win.destroyed = true; 15775 if (win.xic !is null) { 15776 XDestroyIC(win.xic); 15777 win.xic = null; // just in case 15778 } 15779 SimpleWindow.nativeMapping.remove(e.xdestroywindow.window); 15780 bool anyImportant = false; 15781 foreach(SimpleWindow w; SimpleWindow.nativeMapping) 15782 if(w.beingOpenKeepsAppOpen) { 15783 anyImportant = true; 15784 break; 15785 } 15786 if(!anyImportant) { 15787 EventLoop.quitApplication(); 15788 done = true; 15789 } 15790 } 15791 auto window = e.xdestroywindow.window; 15792 if(window in CapableOfHandlingNativeEvent.nativeHandleMapping) 15793 CapableOfHandlingNativeEvent.nativeHandleMapping.remove(window); 15794 15795 version(with_eventloop) { 15796 if(done) exit(); 15797 } 15798 break; 15799 15800 case EventType.MotionNotify: 15801 MouseEvent mouse; 15802 auto event = e.xmotion; 15803 15804 mouse.type = MouseEventType.motion; 15805 mouse.x = event.x; 15806 mouse.y = event.y; 15807 mouse.modifierState = event.state; 15808 15809 mouse.timestamp = event.time; 15810 15811 if(auto win = e.xmotion.window in SimpleWindow.nativeMapping) { 15812 mouse.window = *win; 15813 if (win.warpEventCount > 0) { 15814 debug(x11sdpy_warp_debug) { import core.stdc.stdio : printf; printf("X11: got \"warp motion\" message, current count=%d\n", (*win).warpEventCount); } 15815 --(*win).warpEventCount; 15816 (*win).mdx(mouse); // so deltas will be correctly updated 15817 } else { 15818 win.warpEventCount = 0; // just in case 15819 (*win).mdx(mouse); 15820 if((*win).handleMouseEvent) { 15821 XUnlockDisplay(display); 15822 scope(exit) XLockDisplay(display); 15823 (*win).handleMouseEvent(mouse); 15824 } 15825 } 15826 } 15827 15828 version(with_eventloop) 15829 send(mouse); 15830 break; 15831 case EventType.ButtonPress: 15832 case EventType.ButtonRelease: 15833 MouseEvent mouse; 15834 auto event = e.xbutton; 15835 15836 mouse.timestamp = event.time; 15837 15838 mouse.type = cast(MouseEventType) (e.type == EventType.ButtonPress ? 1 : 2); 15839 mouse.x = event.x; 15840 mouse.y = event.y; 15841 15842 static Time lastMouseDownTime = 0; 15843 static int lastMouseDownButton = -1; 15844 15845 mouse.doubleClick = e.type == EventType.ButtonPress && event.button == lastMouseDownButton && (event.time - lastMouseDownTime) < mouseDoubleClickTimeout; 15846 if(e.type == EventType.ButtonPress) { 15847 lastMouseDownTime = event.time; 15848 lastMouseDownButton = event.button; 15849 } 15850 15851 switch(event.button) { 15852 case 1: mouse.button = MouseButton.left; break; // left 15853 case 2: mouse.button = MouseButton.middle; break; // middle 15854 case 3: mouse.button = MouseButton.right; break; // right 15855 case 4: mouse.button = MouseButton.wheelUp; break; // scroll up 15856 case 5: mouse.button = MouseButton.wheelDown; break; // scroll down 15857 case 6: break; // idk 15858 case 7: break; // idk 15859 case 8: mouse.button = MouseButton.backButton; break; 15860 case 9: mouse.button = MouseButton.forwardButton; break; 15861 default: 15862 } 15863 15864 // FIXME: double check this 15865 mouse.modifierState = event.state; 15866 15867 //mouse.modifierState = event.detail; 15868 15869 if(auto win = e.xbutton.window in SimpleWindow.nativeMapping) { 15870 mouse.window = *win; 15871 (*win).mdx(mouse); 15872 if((*win).handleMouseEvent) { 15873 XUnlockDisplay(display); 15874 scope(exit) XLockDisplay(display); 15875 (*win).handleMouseEvent(mouse); 15876 } 15877 } 15878 version(with_eventloop) 15879 send(mouse); 15880 break; 15881 15882 case EventType.KeyPress: 15883 case EventType.KeyRelease: 15884 //if (e.type == EventType.KeyPress) { import core.stdc.stdio : stderr, fprintf; fprintf(stderr, "X11 keyboard event!\n"); } 15885 KeyEvent ke; 15886 ke.pressed = e.type == EventType.KeyPress; 15887 ke.hardwareCode = cast(ubyte) e.xkey.keycode; 15888 15889 auto sym = XKeycodeToKeysym( 15890 XDisplayConnection.get(), 15891 e.xkey.keycode, 15892 0); 15893 15894 ke.key = cast(Key) sym;//e.xkey.keycode; 15895 15896 ke.modifierState = e.xkey.state; 15897 15898 // writefln("%x", sym); 15899 wchar_t[128] charbuf = void; // buffer for XwcLookupString; composed value can consist of many chars! 15900 int charbuflen = 0; // return value of XwcLookupString 15901 if (ke.pressed) { 15902 auto win = e.xkey.window in SimpleWindow.nativeMapping; 15903 if (win !is null && win.xic !is null) { 15904 //{ import core.stdc.stdio : printf; printf("using xic!\n"); } 15905 Status status; 15906 charbuflen = XwcLookupString(win.xic, &e.xkey, charbuf.ptr, cast(int)charbuf.length, &sym, &status); 15907 //{ import core.stdc.stdio : printf; printf("charbuflen=%d\n", charbuflen); } 15908 } else { 15909 //{ import core.stdc.stdio : printf; printf("NOT using xic!\n"); } 15910 // If XIM initialization failed, don't process intl chars. Sorry, boys and girls. 15911 char[16] buffer; 15912 auto res = XLookupString(&e.xkey, buffer.ptr, buffer.length, null, null); 15913 if (res && buffer[0] < 128) charbuf[charbuflen++] = cast(wchar_t)buffer[0]; 15914 } 15915 } 15916 15917 // if there's no char, subst one 15918 if (charbuflen == 0) { 15919 switch (sym) { 15920 case 0xff09: charbuf[charbuflen++] = '\t'; break; 15921 case 0xff8d: // keypad enter 15922 case 0xff0d: charbuf[charbuflen++] = '\n'; break; 15923 default : // ignore 15924 } 15925 } 15926 15927 if (auto win = e.xkey.window in SimpleWindow.nativeMapping) { 15928 ke.window = *win; 15929 15930 15931 if(win.inputProxy) 15932 win = &win.inputProxy; 15933 15934 // char events are separate since they are on Windows too 15935 // also, xcompose can generate long char sequences 15936 // don't send char events if Meta and/or Hyper is pressed 15937 // TODO: ctrl+char should only send control chars; not yet 15938 if ((e.xkey.state&ModifierState.ctrl) != 0) { 15939 if (charbuflen > 1 || charbuf[0] >= ' ') charbuflen = 0; 15940 } 15941 15942 dchar[32] charsComingBuffer; 15943 int charsComingPosition; 15944 dchar[] charsComing = charsComingBuffer[]; 15945 15946 if (ke.pressed && charbuflen > 0) { 15947 // FIXME: I think Windows sends these on releases... we should try to match that, but idk about repeats. 15948 foreach (immutable dchar ch; charbuf[0..charbuflen]) { 15949 if(charsComingPosition >= charsComing.length) 15950 charsComing.length = charsComingPosition + 8; 15951 15952 charsComing[charsComingPosition++] = ch; 15953 } 15954 15955 charsComing = charsComing[0 .. charsComingPosition]; 15956 } else { 15957 charsComing = null; 15958 } 15959 15960 ke.charsPossible = charsComing; 15961 15962 if (win.handleKeyEvent) { 15963 XUnlockDisplay(display); 15964 scope(exit) XLockDisplay(display); 15965 win.handleKeyEvent(ke); 15966 } 15967 15968 // Super and alt modifier keys never actually send the chars, they are assumed to be special. 15969 if ((e.xkey.state&(ModifierState.alt|ModifierState.windows)) == 0 && win.handleCharEvent) { 15970 XUnlockDisplay(display); 15971 scope(exit) XLockDisplay(display); 15972 foreach(ch; charsComing) 15973 win.handleCharEvent(ch); 15974 } 15975 } 15976 15977 version(with_eventloop) 15978 send(ke); 15979 break; 15980 default: 15981 } 15982 15983 return done; 15984 } 15985 } 15986 15987 /* *************************************** */ 15988 /* Done with simpledisplay stuff */ 15989 /* *************************************** */ 15990 15991 // Necessary C library bindings follow 15992 version(Windows) {} else 15993 version(X11) { 15994 15995 extern(C) int eventfd (uint initval, int flags) nothrow @trusted @nogc; 15996 15997 // X11 bindings needed here 15998 /* 15999 A little of this is from the bindings project on 16000 D Source and some of it is copy/paste from the C 16001 header. 16002 16003 The DSource listing consistently used D's long 16004 where C used long. That's wrong - C long is 32 bit, so 16005 it should be int in D. I changed that here. 16006 16007 Note: 16008 This isn't complete, just took what I needed for myself. 16009 */ 16010 16011 import core.stdc.stddef : wchar_t; 16012 16013 interface XLib { 16014 extern(C) nothrow @nogc { 16015 char* XResourceManagerString(Display*); 16016 void XrmInitialize(); 16017 XrmDatabase XrmGetStringDatabase(char* data); 16018 bool XrmGetResource(XrmDatabase, const char*, const char*, char**, XrmValue*); 16019 16020 Cursor XCreateFontCursor(Display*, uint shape); 16021 int XDefineCursor(Display* display, Window w, Cursor cursor); 16022 int XUndefineCursor(Display* display, Window w); 16023 16024 Pixmap XCreateBitmapFromData(Display* display, Drawable d, const(char)* data, uint width, uint height); 16025 Cursor XCreatePixmapCursor(Display* display, Pixmap source, Pixmap mask, XColor* foreground_color, XColor* background_color, uint x, uint y); 16026 int XFreeCursor(Display* display, Cursor cursor); 16027 16028 int XLookupString(XKeyEvent *event_struct, char *buffer_return, int bytes_buffer, KeySym *keysym_return, void *status_in_out); 16029 16030 int XwcLookupString(XIC ic, XKeyPressedEvent* event, wchar_t* buffer_return, int wchars_buffer, KeySym* keysym_return, Status* status_return); 16031 16032 XVaNestedList XVaCreateNestedList(int unused, ...); 16033 16034 char *XKeysymToString(KeySym keysym); 16035 KeySym XKeycodeToKeysym( 16036 Display* /* display */, 16037 KeyCode /* keycode */, 16038 int /* index */ 16039 ); 16040 16041 int XConvertSelection(Display *display, Atom selection, Atom target, Atom property, Window requestor, Time time); 16042 16043 int XFree(void*); 16044 int XDeleteProperty(Display *display, Window w, Atom property); 16045 16046 int XChangeProperty(Display *display, Window w, Atom property, Atom type, int format, int mode, scope const void *data, int nelements); 16047 16048 int XGetWindowProperty(Display *display, Window w, Atom property, arch_long 16049 long_offset, arch_long long_length, Bool del, Atom req_type, Atom 16050 *actual_type_return, int *actual_format_return, arch_ulong 16051 *nitems_return, arch_ulong *bytes_after_return, void** prop_return); 16052 Atom* XListProperties(Display *display, Window w, int *num_prop_return); 16053 Status XGetTextProperty(Display *display, Window w, XTextProperty *text_prop_return, Atom property); 16054 Status XQueryTree(Display *display, Window w, Window *root_return, Window *parent_return, Window **children_return, uint *nchildren_return); 16055 16056 int XSetSelectionOwner(Display *display, Atom selection, Window owner, Time time); 16057 16058 Window XGetSelectionOwner(Display *display, Atom selection); 16059 16060 XVisualInfo* XGetVisualInfo(Display*, c_long, XVisualInfo*, int*); 16061 16062 char** XListFonts(Display*, const char*, int, int*); 16063 void XFreeFontNames(char**); 16064 16065 Display* XOpenDisplay(const char*); 16066 int XCloseDisplay(Display*); 16067 16068 int function() XSynchronize(Display*, bool); 16069 int function() XSetAfterFunction(Display*, int function() proc); 16070 16071 Bool XQueryExtension(Display*, const char*, int*, int*, int*); 16072 16073 Bool XSupportsLocale(); 16074 char* XSetLocaleModifiers(const(char)* modifier_list); 16075 XOM XOpenOM(Display* display, _XrmHashBucketRec* rdb, const(char)* res_name, const(char)* res_class); 16076 Status XCloseOM(XOM om); 16077 16078 XIM XOpenIM(Display* dpy, _XrmHashBucketRec* rdb, const(char)* res_name, const(char)* res_class); 16079 Status XCloseIM(XIM im); 16080 16081 char* XGetIMValues(XIM im, ...) /*_X_SENTINEL(0)*/; 16082 char* XSetIMValues(XIM im, ...) /*_X_SENTINEL(0)*/; 16083 Display* XDisplayOfIM(XIM im); 16084 char* XLocaleOfIM(XIM im); 16085 XIC XCreateIC(XIM im, ...) /*_X_SENTINEL(0)*/; 16086 void XDestroyIC(XIC ic); 16087 void XSetICFocus(XIC ic); 16088 void XUnsetICFocus(XIC ic); 16089 //wchar_t* XwcResetIC(XIC ic); 16090 char* XmbResetIC(XIC ic); 16091 char* Xutf8ResetIC(XIC ic); 16092 char* XSetICValues(XIC ic, ...) /*_X_SENTINEL(0)*/; 16093 char* XGetICValues(XIC ic, ...) /*_X_SENTINEL(0)*/; 16094 XIM XIMOfIC(XIC ic); 16095 16096 uint XSendEvent(Display* display, Window w, Bool propagate, arch_long event_mask, XEvent* event_send); 16097 16098 16099 XFontStruct *XLoadQueryFont(Display *display, scope const char *name); 16100 int XFreeFont(Display *display, XFontStruct *font_struct); 16101 int XSetFont(Display* display, GC gc, Font font); 16102 int XTextWidth(XFontStruct*, scope const char*, int); 16103 16104 int XSetLineAttributes(Display *display, GC gc, uint line_width, int line_style, int cap_style, int join_style); 16105 int XSetDashes(Display *display, GC gc, int dash_offset, scope const byte* dash_list, int n); 16106 16107 Window XCreateSimpleWindow( 16108 Display* /* display */, 16109 Window /* parent */, 16110 int /* x */, 16111 int /* y */, 16112 uint /* width */, 16113 uint /* height */, 16114 uint /* border_width */, 16115 uint /* border */, 16116 uint /* background */ 16117 ); 16118 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); 16119 16120 int XReparentWindow(Display*, Window, Window, int, int); 16121 int XClearWindow(Display*, Window); 16122 int XMoveResizeWindow(Display*, Window, int, int, uint, uint); 16123 int XMoveWindow(Display*, Window, int, int); 16124 int XResizeWindow(Display *display, Window w, uint width, uint height); 16125 16126 Colormap XCreateColormap(Display *display, Window w, Visual *visual, int alloc); 16127 16128 Status XMatchVisualInfo(Display *display, int screen, int depth, int class_, XVisualInfo *vinfo_return); 16129 16130 Status XGetWindowAttributes(Display*, Window, XWindowAttributes*); 16131 16132 XImage *XCreateImage( 16133 Display* /* display */, 16134 Visual* /* visual */, 16135 uint /* depth */, 16136 int /* format */, 16137 int /* offset */, 16138 ubyte* /* data */, 16139 uint /* width */, 16140 uint /* height */, 16141 int /* bitmap_pad */, 16142 int /* bytes_per_line */ 16143 ); 16144 16145 Status XInitImage (XImage* image); 16146 16147 Atom XInternAtom( 16148 Display* /* display */, 16149 const char* /* atom_name */, 16150 Bool /* only_if_exists */ 16151 ); 16152 16153 Status XInternAtoms(Display*, const char**, int, Bool, Atom*); 16154 char* XGetAtomName(Display*, Atom); 16155 Status XGetAtomNames(Display*, Atom*, int count, char**); 16156 16157 int XPutImage( 16158 Display* /* display */, 16159 Drawable /* d */, 16160 GC /* gc */, 16161 XImage* /* image */, 16162 int /* src_x */, 16163 int /* src_y */, 16164 int /* dest_x */, 16165 int /* dest_y */, 16166 uint /* width */, 16167 uint /* height */ 16168 ); 16169 16170 XImage *XGetImage(Display *display, Drawable d, int x, int y, uint width, uint height, c_ulong plane_mask, int format); 16171 16172 16173 int XDestroyWindow( 16174 Display* /* display */, 16175 Window /* w */ 16176 ); 16177 16178 int XDestroyImage(XImage*); 16179 16180 int XSelectInput( 16181 Display* /* display */, 16182 Window /* w */, 16183 EventMask /* event_mask */ 16184 ); 16185 16186 int XMapWindow( 16187 Display* /* display */, 16188 Window /* w */ 16189 ); 16190 16191 Status XIconifyWindow(Display*, Window, int); 16192 int XMapRaised(Display*, Window); 16193 int XMapSubwindows(Display*, Window); 16194 16195 int XNextEvent( 16196 Display* /* display */, 16197 XEvent* /* event_return */ 16198 ); 16199 16200 int XMaskEvent(Display*, arch_long, XEvent*); 16201 16202 Bool XFilterEvent(XEvent *event, Window window); 16203 int XRefreshKeyboardMapping(XMappingEvent *event_map); 16204 16205 Status XSetWMProtocols( 16206 Display* /* display */, 16207 Window /* w */, 16208 Atom* /* protocols */, 16209 int /* count */ 16210 ); 16211 16212 void XSetWMNormalHints(Display *display, Window w, XSizeHints *hints); 16213 Status XGetWMNormalHints(Display *display, Window w, XSizeHints *hints, c_long* supplied_return); 16214 16215 16216 Status XInitThreads(); 16217 void XLockDisplay (Display* display); 16218 void XUnlockDisplay (Display* display); 16219 16220 void XSetWMProperties(Display*, Window, XTextProperty*, XTextProperty*, char**, int, XSizeHints*, XWMHints*, XClassHint*); 16221 16222 int XSetWindowBackground (Display* display, Window w, c_ulong background_pixel); 16223 int XSetWindowBackgroundPixmap (Display* display, Window w, Pixmap background_pixmap); 16224 //int XSetWindowBorder (Display* display, Window w, c_ulong border_pixel); 16225 //int XSetWindowBorderPixmap (Display* display, Window w, Pixmap border_pixmap); 16226 //int XSetWindowBorderWidth (Display* display, Window w, uint width); 16227 16228 16229 // check out Xft too: http://www.keithp.com/~keithp/render/Xft.tutorial 16230 int XDrawString(Display*, Drawable, GC, int, int, scope const char*, int); 16231 int XDrawLine(Display*, Drawable, GC, int, int, int, int); 16232 int XDrawRectangle(Display*, Drawable, GC, int, int, uint, uint); 16233 int XDrawArc(Display*, Drawable, GC, int, int, uint, uint, int, int); 16234 int XFillRectangle(Display*, Drawable, GC, int, int, uint, uint); 16235 int XFillArc(Display*, Drawable, GC, int, int, uint, uint, int, int); 16236 int XDrawPoint(Display*, Drawable, GC, int, int); 16237 int XSetForeground(Display*, GC, uint); 16238 int XSetBackground(Display*, GC, uint); 16239 16240 XFontSet XCreateFontSet(Display*, const char*, char***, int*, char**); 16241 void XFreeFontSet(Display*, XFontSet); 16242 void Xutf8DrawString(Display*, Drawable, XFontSet, GC, int, int, scope const char*, int); 16243 void Xutf8DrawText(Display*, Drawable, GC, int, int, XmbTextItem*, int); 16244 16245 int Xutf8TextExtents(XFontSet font_set, const char *, int num_bytes, XRectangle *overall_ink_return, XRectangle *overall_logical_return); 16246 16247 16248 //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); 16249 16250 void XDrawText(Display*, Drawable, GC, int, int, XTextItem*, int); 16251 int XSetFunction(Display*, GC, int); 16252 16253 GC XCreateGC(Display*, Drawable, uint, void*); 16254 int XCopyGC(Display*, GC, uint, GC); 16255 int XFreeGC(Display*, GC); 16256 16257 bool XCheckWindowEvent(Display*, Window, int, XEvent*); 16258 bool XCheckMaskEvent(Display*, int, XEvent*); 16259 16260 int XPending(Display*); 16261 int XEventsQueued(Display* display, int mode); 16262 16263 Pixmap XCreatePixmap(Display*, Drawable, uint, uint, uint); 16264 int XFreePixmap(Display*, Pixmap); 16265 int XCopyArea(Display*, Drawable, Drawable, GC, int, int, uint, uint, int, int); 16266 int XFlush(Display*); 16267 int XBell(Display*, int); 16268 int XSync(Display*, bool); 16269 16270 int XGrabKey (Display* display, int keycode, uint modifiers, Window grab_window, Bool owner_events, int pointer_mode, int keyboard_mode); 16271 int XUngrabKey (Display* display, int keycode, uint modifiers, Window grab_window); 16272 16273 int XGrabKeyboard(Display*, Window, Bool, int, int, Time); 16274 int XUngrabKeyboard(Display*, Time); 16275 16276 KeyCode XKeysymToKeycode (Display* display, KeySym keysym); 16277 16278 KeySym XStringToKeysym(const char *string); 16279 16280 Bool XCheckTypedEvent(Display *display, int event_type, XEvent *event_return); 16281 16282 Window XDefaultRootWindow(Display*); 16283 16284 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); 16285 16286 int XUngrabButton(Display *display, uint button, uint modifiers, Window grab_window); 16287 16288 int XDrawLines(Display*, Drawable, GC, XPoint*, int, CoordMode); 16289 int XFillPolygon(Display*, Drawable, GC, XPoint*, int, PolygonShape, CoordMode); 16290 16291 Status XAllocColor(Display*, Colormap, XColor*); 16292 16293 int XWithdrawWindow(Display*, Window, int); 16294 int XUnmapWindow(Display*, Window); 16295 int XLowerWindow(Display*, Window); 16296 int XRaiseWindow(Display*, Window); 16297 16298 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); 16299 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); 16300 16301 int XGetInputFocus(Display*, Window*, int*); 16302 int XSetInputFocus(Display*, Window, int, Time); 16303 16304 XErrorHandler XSetErrorHandler(XErrorHandler); 16305 16306 int XGetErrorText(Display*, int, char*, int); 16307 16308 Bool XkbSetDetectableAutoRepeat(Display* dpy, Bool detectable, Bool* supported); 16309 16310 16311 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); 16312 int XUngrabPointer(Display *display, Time time); 16313 int XChangeActivePointerGrab(Display *display, uint event_mask, Cursor cursor, Time time); 16314 16315 int XCopyPlane(Display*, Drawable, Drawable, GC, int, int, uint, uint, int, int, arch_ulong); 16316 16317 Status XGetGeometry(Display*, Drawable, Window*, int*, int*, uint*, uint*, uint*, uint*); 16318 int XSetClipMask(Display*, GC, Pixmap); 16319 int XSetClipOrigin(Display*, GC, int, int); 16320 16321 void XSetClipRectangles(Display*, GC, int, int, XRectangle*, int, int); 16322 16323 void XSetWMName(Display*, Window, XTextProperty*); 16324 Status XGetWMName(Display*, Window, XTextProperty*); 16325 int XStoreName(Display* display, Window w, const(char)* window_name); 16326 16327 XIOErrorHandler XSetIOErrorHandler (XIOErrorHandler handler); 16328 16329 } 16330 } 16331 16332 interface Xext { 16333 extern(C) nothrow @nogc { 16334 Status XShmAttach(Display*, XShmSegmentInfo*); 16335 Status XShmDetach(Display*, XShmSegmentInfo*); 16336 Status XShmPutImage( 16337 Display* /* dpy */, 16338 Drawable /* d */, 16339 GC /* gc */, 16340 XImage* /* image */, 16341 int /* src_x */, 16342 int /* src_y */, 16343 int /* dst_x */, 16344 int /* dst_y */, 16345 uint /* src_width */, 16346 uint /* src_height */, 16347 Bool /* send_event */ 16348 ); 16349 16350 Status XShmQueryExtension(Display*); 16351 16352 XImage *XShmCreateImage( 16353 Display* /* dpy */, 16354 Visual* /* visual */, 16355 uint /* depth */, 16356 int /* format */, 16357 char* /* data */, 16358 XShmSegmentInfo* /* shminfo */, 16359 uint /* width */, 16360 uint /* height */ 16361 ); 16362 16363 Pixmap XShmCreatePixmap( 16364 Display* /* dpy */, 16365 Drawable /* d */, 16366 char* /* data */, 16367 XShmSegmentInfo* /* shminfo */, 16368 uint /* width */, 16369 uint /* height */, 16370 uint /* depth */ 16371 ); 16372 16373 } 16374 } 16375 16376 // this requires -lXpm 16377 //int XpmCreatePixmapFromData(Display*, Drawable, scope const char**, Pixmap*, Pixmap*, void*); // FIXME: void* should be XpmAttributes 16378 16379 16380 mixin DynamicLoad!(XLib, "X11", 6, librariesSuccessfullyLoaded) xlib; 16381 mixin DynamicLoad!(Xext, "Xext", 6, librariesSuccessfullyLoaded) xext; 16382 shared static this() { 16383 xlib.loadDynamicLibrary(); 16384 xext.loadDynamicLibrary(); 16385 } 16386 16387 16388 extern(C) nothrow @nogc { 16389 16390 alias XrmDatabase = void*; 16391 struct XrmValue { 16392 uint size; 16393 void* addr; 16394 } 16395 16396 struct XVisualInfo { 16397 Visual* visual; 16398 VisualID visualid; 16399 int screen; 16400 uint depth; 16401 int c_class; 16402 c_ulong red_mask; 16403 c_ulong green_mask; 16404 c_ulong blue_mask; 16405 int colormap_size; 16406 int bits_per_rgb; 16407 } 16408 16409 enum VisualNoMask= 0x0; 16410 enum VisualIDMask= 0x1; 16411 enum VisualScreenMask=0x2; 16412 enum VisualDepthMask= 0x4; 16413 enum VisualClassMask= 0x8; 16414 enum VisualRedMaskMask=0x10; 16415 enum VisualGreenMaskMask=0x20; 16416 enum VisualBlueMaskMask=0x40; 16417 enum VisualColormapSizeMask=0x80; 16418 enum VisualBitsPerRGBMask=0x100; 16419 enum VisualAllMask= 0x1FF; 16420 16421 enum AnyKey = 0; 16422 enum AnyModifier = 1 << 15; 16423 16424 // XIM and other crap 16425 struct _XOM {} 16426 struct _XIM {} 16427 struct _XIC {} 16428 alias XOM = _XOM*; 16429 alias XIM = _XIM*; 16430 alias XIC = _XIC*; 16431 16432 alias XVaNestedList = void*; 16433 16434 alias XIMStyle = arch_ulong; 16435 enum : arch_ulong { 16436 XIMPreeditArea = 0x0001, 16437 XIMPreeditCallbacks = 0x0002, 16438 XIMPreeditPosition = 0x0004, 16439 XIMPreeditNothing = 0x0008, 16440 XIMPreeditNone = 0x0010, 16441 XIMStatusArea = 0x0100, 16442 XIMStatusCallbacks = 0x0200, 16443 XIMStatusNothing = 0x0400, 16444 XIMStatusNone = 0x0800, 16445 } 16446 16447 16448 /* X Shared Memory Extension functions */ 16449 //pragma(lib, "Xshm"); 16450 alias arch_ulong ShmSeg; 16451 struct XShmSegmentInfo { 16452 ShmSeg shmseg; 16453 int shmid; 16454 ubyte* shmaddr; 16455 Bool readOnly; 16456 } 16457 16458 // and the necessary OS functions 16459 int shmget(int, size_t, int); 16460 void* shmat(int, scope const void*, int); 16461 int shmdt(scope const void*); 16462 int shmctl (int shmid, int cmd, void* ptr /*struct shmid_ds *buf*/); 16463 16464 enum IPC_PRIVATE = 0; 16465 enum IPC_CREAT = 512; 16466 enum IPC_RMID = 0; 16467 16468 /* MIT-SHM end */ 16469 16470 16471 enum MappingType:int { 16472 MappingModifier =0, 16473 MappingKeyboard =1, 16474 MappingPointer =2 16475 } 16476 16477 /* ImageFormat -- PutImage, GetImage */ 16478 enum ImageFormat:int { 16479 XYBitmap =0, /* depth 1, XYFormat */ 16480 XYPixmap =1, /* depth == drawable depth */ 16481 ZPixmap =2 /* depth == drawable depth */ 16482 } 16483 16484 enum ModifierName:int { 16485 ShiftMapIndex =0, 16486 LockMapIndex =1, 16487 ControlMapIndex =2, 16488 Mod1MapIndex =3, 16489 Mod2MapIndex =4, 16490 Mod3MapIndex =5, 16491 Mod4MapIndex =6, 16492 Mod5MapIndex =7 16493 } 16494 16495 enum ButtonMask:int { 16496 Button1Mask =1<<8, 16497 Button2Mask =1<<9, 16498 Button3Mask =1<<10, 16499 Button4Mask =1<<11, 16500 Button5Mask =1<<12, 16501 AnyModifier =1<<15/* used in GrabButton, GrabKey */ 16502 } 16503 16504 enum KeyOrButtonMask:uint { 16505 ShiftMask =1<<0, 16506 LockMask =1<<1, 16507 ControlMask =1<<2, 16508 Mod1Mask =1<<3, 16509 Mod2Mask =1<<4, 16510 Mod3Mask =1<<5, 16511 Mod4Mask =1<<6, 16512 Mod5Mask =1<<7, 16513 Button1Mask =1<<8, 16514 Button2Mask =1<<9, 16515 Button3Mask =1<<10, 16516 Button4Mask =1<<11, 16517 Button5Mask =1<<12, 16518 AnyModifier =1<<15/* used in GrabButton, GrabKey */ 16519 } 16520 16521 enum ButtonName:int { 16522 Button1 =1, 16523 Button2 =2, 16524 Button3 =3, 16525 Button4 =4, 16526 Button5 =5 16527 } 16528 16529 /* Notify modes */ 16530 enum NotifyModes:int 16531 { 16532 NotifyNormal =0, 16533 NotifyGrab =1, 16534 NotifyUngrab =2, 16535 NotifyWhileGrabbed =3 16536 } 16537 enum NotifyHint = 1; /* for MotionNotify events */ 16538 16539 /* Notify detail */ 16540 enum NotifyDetail:int 16541 { 16542 NotifyAncestor =0, 16543 NotifyVirtual =1, 16544 NotifyInferior =2, 16545 NotifyNonlinear =3, 16546 NotifyNonlinearVirtual =4, 16547 NotifyPointer =5, 16548 NotifyPointerRoot =6, 16549 NotifyDetailNone =7 16550 } 16551 16552 /* Visibility notify */ 16553 16554 enum VisibilityNotify:int 16555 { 16556 VisibilityUnobscured =0, 16557 VisibilityPartiallyObscured =1, 16558 VisibilityFullyObscured =2 16559 } 16560 16561 16562 enum WindowStackingMethod:int 16563 { 16564 Above =0, 16565 Below =1, 16566 TopIf =2, 16567 BottomIf =3, 16568 Opposite =4 16569 } 16570 16571 /* Circulation request */ 16572 enum CirculationRequest:int 16573 { 16574 PlaceOnTop =0, 16575 PlaceOnBottom =1 16576 } 16577 16578 enum PropertyNotification:int 16579 { 16580 PropertyNewValue =0, 16581 PropertyDelete =1 16582 } 16583 16584 enum ColorMapNotification:int 16585 { 16586 ColormapUninstalled =0, 16587 ColormapInstalled =1 16588 } 16589 16590 16591 struct _XPrivate {} 16592 struct _XrmHashBucketRec {} 16593 16594 alias void* XPointer; 16595 alias void* XExtData; 16596 16597 version( X86_64 ) { 16598 alias ulong XID; 16599 alias ulong arch_ulong; 16600 alias long arch_long; 16601 } else version (AArch64) { 16602 alias ulong XID; 16603 alias ulong arch_ulong; 16604 alias long arch_long; 16605 } else { 16606 alias uint XID; 16607 alias uint arch_ulong; 16608 alias int arch_long; 16609 } 16610 16611 alias XID Window; 16612 alias XID Drawable; 16613 alias XID Pixmap; 16614 16615 alias arch_ulong Atom; 16616 alias int Bool; 16617 alias Display XDisplay; 16618 16619 alias int ByteOrder; 16620 alias arch_ulong Time; 16621 alias void ScreenFormat; 16622 16623 struct XImage { 16624 int width, height; /* size of image */ 16625 int xoffset; /* number of pixels offset in X direction */ 16626 ImageFormat format; /* XYBitmap, XYPixmap, ZPixmap */ 16627 void *data; /* pointer to image data */ 16628 ByteOrder byte_order; /* data byte order, LSBFirst, MSBFirst */ 16629 int bitmap_unit; /* quant. of scanline 8, 16, 32 */ 16630 int bitmap_bit_order; /* LSBFirst, MSBFirst */ 16631 int bitmap_pad; /* 8, 16, 32 either XY or ZPixmap */ 16632 int depth; /* depth of image */ 16633 int bytes_per_line; /* accelarator to next line */ 16634 int bits_per_pixel; /* bits per pixel (ZPixmap) */ 16635 arch_ulong red_mask; /* bits in z arrangment */ 16636 arch_ulong green_mask; 16637 arch_ulong blue_mask; 16638 XPointer obdata; /* hook for the object routines to hang on */ 16639 static struct F { /* image manipulation routines */ 16640 XImage* function( 16641 XDisplay* /* display */, 16642 Visual* /* visual */, 16643 uint /* depth */, 16644 int /* format */, 16645 int /* offset */, 16646 ubyte* /* data */, 16647 uint /* width */, 16648 uint /* height */, 16649 int /* bitmap_pad */, 16650 int /* bytes_per_line */) create_image; 16651 int function(XImage *) destroy_image; 16652 arch_ulong function(XImage *, int, int) get_pixel; 16653 int function(XImage *, int, int, arch_ulong) put_pixel; 16654 XImage* function(XImage *, int, int, uint, uint) sub_image; 16655 int function(XImage *, arch_long) add_pixel; 16656 } 16657 F f; 16658 } 16659 version(X86_64) static assert(XImage.sizeof == 136); 16660 else version(X86) static assert(XImage.sizeof == 88); 16661 16662 struct XCharStruct { 16663 short lbearing; /* origin to left edge of raster */ 16664 short rbearing; /* origin to right edge of raster */ 16665 short width; /* advance to next char's origin */ 16666 short ascent; /* baseline to top edge of raster */ 16667 short descent; /* baseline to bottom edge of raster */ 16668 ushort attributes; /* per char flags (not predefined) */ 16669 } 16670 16671 /* 16672 * To allow arbitrary information with fonts, there are additional properties 16673 * returned. 16674 */ 16675 struct XFontProp { 16676 Atom name; 16677 arch_ulong card32; 16678 } 16679 16680 alias Atom Font; 16681 16682 struct XFontStruct { 16683 XExtData *ext_data; /* Hook for extension to hang data */ 16684 Font fid; /* Font ID for this font */ 16685 uint direction; /* Direction the font is painted */ 16686 uint min_char_or_byte2; /* First character */ 16687 uint max_char_or_byte2; /* Last character */ 16688 uint min_byte1; /* First row that exists (for two-byte fonts) */ 16689 uint max_byte1; /* Last row that exists (for two-byte fonts) */ 16690 Bool all_chars_exist; /* Flag if all characters have nonzero size */ 16691 uint default_char; /* Char to print for undefined character */ 16692 int n_properties; /* How many properties there are */ 16693 XFontProp *properties; /* Pointer to array of additional properties*/ 16694 XCharStruct min_bounds; /* Minimum bounds over all existing char*/ 16695 XCharStruct max_bounds; /* Maximum bounds over all existing char*/ 16696 XCharStruct *per_char; /* first_char to last_char information */ 16697 int ascent; /* Max extent above baseline for spacing */ 16698 int descent; /* Max descent below baseline for spacing */ 16699 } 16700 16701 16702 /* 16703 * Definitions of specific events. 16704 */ 16705 struct XKeyEvent 16706 { 16707 int type; /* of event */ 16708 arch_ulong serial; /* # of last request processed by server */ 16709 Bool send_event; /* true if this came from a SendEvent request */ 16710 Display *display; /* Display the event was read from */ 16711 Window window; /* "event" window it is reported relative to */ 16712 Window root; /* root window that the event occurred on */ 16713 Window subwindow; /* child window */ 16714 Time time; /* milliseconds */ 16715 int x, y; /* pointer x, y coordinates in event window */ 16716 int x_root, y_root; /* coordinates relative to root */ 16717 KeyOrButtonMask state; /* key or button mask */ 16718 uint keycode; /* detail */ 16719 Bool same_screen; /* same screen flag */ 16720 } 16721 version(X86_64) static assert(XKeyEvent.sizeof == 96); 16722 alias XKeyEvent XKeyPressedEvent; 16723 alias XKeyEvent XKeyReleasedEvent; 16724 16725 struct XButtonEvent 16726 { 16727 int type; /* of event */ 16728 arch_ulong serial; /* # of last request processed by server */ 16729 Bool send_event; /* true if this came from a SendEvent request */ 16730 Display *display; /* Display the event was read from */ 16731 Window window; /* "event" window it is reported relative to */ 16732 Window root; /* root window that the event occurred on */ 16733 Window subwindow; /* child window */ 16734 Time time; /* milliseconds */ 16735 int x, y; /* pointer x, y coordinates in event window */ 16736 int x_root, y_root; /* coordinates relative to root */ 16737 KeyOrButtonMask state; /* key or button mask */ 16738 uint button; /* detail */ 16739 Bool same_screen; /* same screen flag */ 16740 } 16741 alias XButtonEvent XButtonPressedEvent; 16742 alias XButtonEvent XButtonReleasedEvent; 16743 16744 struct XMotionEvent{ 16745 int type; /* of event */ 16746 arch_ulong serial; /* # of last request processed by server */ 16747 Bool send_event; /* true if this came from a SendEvent request */ 16748 Display *display; /* Display the event was read from */ 16749 Window window; /* "event" window reported relative to */ 16750 Window root; /* root window that the event occurred on */ 16751 Window subwindow; /* child window */ 16752 Time time; /* milliseconds */ 16753 int x, y; /* pointer x, y coordinates in event window */ 16754 int x_root, y_root; /* coordinates relative to root */ 16755 KeyOrButtonMask state; /* key or button mask */ 16756 byte is_hint; /* detail */ 16757 Bool same_screen; /* same screen flag */ 16758 } 16759 alias XMotionEvent XPointerMovedEvent; 16760 16761 struct XCrossingEvent{ 16762 int type; /* of event */ 16763 arch_ulong serial; /* # of last request processed by server */ 16764 Bool send_event; /* true if this came from a SendEvent request */ 16765 Display *display; /* Display the event was read from */ 16766 Window window; /* "event" window reported relative to */ 16767 Window root; /* root window that the event occurred on */ 16768 Window subwindow; /* child window */ 16769 Time time; /* milliseconds */ 16770 int x, y; /* pointer x, y coordinates in event window */ 16771 int x_root, y_root; /* coordinates relative to root */ 16772 NotifyModes mode; /* NotifyNormal, NotifyGrab, NotifyUngrab */ 16773 NotifyDetail detail; 16774 /* 16775 * NotifyAncestor, NotifyVirtual, NotifyInferior, 16776 * NotifyNonlinear,NotifyNonlinearVirtual 16777 */ 16778 Bool same_screen; /* same screen flag */ 16779 Bool focus; /* Boolean focus */ 16780 KeyOrButtonMask state; /* key or button mask */ 16781 } 16782 alias XCrossingEvent XEnterWindowEvent; 16783 alias XCrossingEvent XLeaveWindowEvent; 16784 16785 struct XFocusChangeEvent{ 16786 int type; /* FocusIn or FocusOut */ 16787 arch_ulong serial; /* # of last request processed by server */ 16788 Bool send_event; /* true if this came from a SendEvent request */ 16789 Display *display; /* Display the event was read from */ 16790 Window window; /* window of event */ 16791 NotifyModes mode; /* NotifyNormal, NotifyWhileGrabbed, 16792 NotifyGrab, NotifyUngrab */ 16793 NotifyDetail detail; 16794 /* 16795 * NotifyAncestor, NotifyVirtual, NotifyInferior, 16796 * NotifyNonlinear,NotifyNonlinearVirtual, NotifyPointer, 16797 * NotifyPointerRoot, NotifyDetailNone 16798 */ 16799 } 16800 alias XFocusChangeEvent XFocusInEvent; 16801 alias XFocusChangeEvent XFocusOutEvent; 16802 16803 enum CWBackPixmap = (1L<<0); 16804 enum CWBackPixel = (1L<<1); 16805 enum CWBorderPixmap = (1L<<2); 16806 enum CWBorderPixel = (1L<<3); 16807 enum CWBitGravity = (1L<<4); 16808 enum CWWinGravity = (1L<<5); 16809 enum CWBackingStore = (1L<<6); 16810 enum CWBackingPlanes = (1L<<7); 16811 enum CWBackingPixel = (1L<<8); 16812 enum CWOverrideRedirect = (1L<<9); 16813 enum CWSaveUnder = (1L<<10); 16814 enum CWEventMask = (1L<<11); 16815 enum CWDontPropagate = (1L<<12); 16816 enum CWColormap = (1L<<13); 16817 enum CWCursor = (1L<<14); 16818 16819 struct XWindowAttributes { 16820 int x, y; /* location of window */ 16821 int width, height; /* width and height of window */ 16822 int border_width; /* border width of window */ 16823 int depth; /* depth of window */ 16824 Visual *visual; /* the associated visual structure */ 16825 Window root; /* root of screen containing window */ 16826 int class_; /* InputOutput, InputOnly*/ 16827 int bit_gravity; /* one of the bit gravity values */ 16828 int win_gravity; /* one of the window gravity values */ 16829 int backing_store; /* NotUseful, WhenMapped, Always */ 16830 arch_ulong backing_planes; /* planes to be preserved if possible */ 16831 arch_ulong backing_pixel; /* value to be used when restoring planes */ 16832 Bool save_under; /* boolean, should bits under be saved? */ 16833 Colormap colormap; /* color map to be associated with window */ 16834 Bool map_installed; /* boolean, is color map currently installed*/ 16835 int map_state; /* IsUnmapped, IsUnviewable, IsViewable */ 16836 arch_long all_event_masks; /* set of events all people have interest in*/ 16837 arch_long your_event_mask; /* my event mask */ 16838 arch_long do_not_propagate_mask; /* set of events that should not propagate */ 16839 Bool override_redirect; /* boolean value for override-redirect */ 16840 Screen *screen; /* back pointer to correct screen */ 16841 } 16842 16843 enum IsUnmapped = 0; 16844 enum IsUnviewable = 1; 16845 enum IsViewable = 2; 16846 16847 struct XSetWindowAttributes { 16848 Pixmap background_pixmap;/* background, None, or ParentRelative */ 16849 arch_ulong background_pixel;/* background pixel */ 16850 Pixmap border_pixmap; /* border of the window or CopyFromParent */ 16851 arch_ulong border_pixel;/* border pixel value */ 16852 int bit_gravity; /* one of bit gravity values */ 16853 int win_gravity; /* one of the window gravity values */ 16854 int backing_store; /* NotUseful, WhenMapped, Always */ 16855 arch_ulong backing_planes;/* planes to be preserved if possible */ 16856 arch_ulong backing_pixel;/* value to use in restoring planes */ 16857 Bool save_under; /* should bits under be saved? (popups) */ 16858 arch_long event_mask; /* set of events that should be saved */ 16859 arch_long do_not_propagate_mask;/* set of events that should not propagate */ 16860 Bool override_redirect; /* boolean value for override_redirect */ 16861 Colormap colormap; /* color map to be associated with window */ 16862 Cursor cursor; /* cursor to be displayed (or None) */ 16863 } 16864 16865 16866 alias int Status; 16867 16868 16869 enum EventMask:int 16870 { 16871 NoEventMask =0, 16872 KeyPressMask =1<<0, 16873 KeyReleaseMask =1<<1, 16874 ButtonPressMask =1<<2, 16875 ButtonReleaseMask =1<<3, 16876 EnterWindowMask =1<<4, 16877 LeaveWindowMask =1<<5, 16878 PointerMotionMask =1<<6, 16879 PointerMotionHintMask =1<<7, 16880 Button1MotionMask =1<<8, 16881 Button2MotionMask =1<<9, 16882 Button3MotionMask =1<<10, 16883 Button4MotionMask =1<<11, 16884 Button5MotionMask =1<<12, 16885 ButtonMotionMask =1<<13, 16886 KeymapStateMask =1<<14, 16887 ExposureMask =1<<15, 16888 VisibilityChangeMask =1<<16, 16889 StructureNotifyMask =1<<17, 16890 ResizeRedirectMask =1<<18, 16891 SubstructureNotifyMask =1<<19, 16892 SubstructureRedirectMask=1<<20, 16893 FocusChangeMask =1<<21, 16894 PropertyChangeMask =1<<22, 16895 ColormapChangeMask =1<<23, 16896 OwnerGrabButtonMask =1<<24 16897 } 16898 16899 struct MwmHints { 16900 c_ulong flags; 16901 c_ulong functions; 16902 c_ulong decorations; 16903 c_long input_mode; 16904 c_ulong status; 16905 } 16906 16907 enum { 16908 MWM_HINTS_FUNCTIONS = (1L << 0), 16909 MWM_HINTS_DECORATIONS = (1L << 1), 16910 16911 MWM_FUNC_ALL = (1L << 0), 16912 MWM_FUNC_RESIZE = (1L << 1), 16913 MWM_FUNC_MOVE = (1L << 2), 16914 MWM_FUNC_MINIMIZE = (1L << 3), 16915 MWM_FUNC_MAXIMIZE = (1L << 4), 16916 MWM_FUNC_CLOSE = (1L << 5), 16917 16918 MWM_DECOR_ALL = (1L << 0), 16919 MWM_DECOR_BORDER = (1L << 1), 16920 MWM_DECOR_RESIZEH = (1L << 2), 16921 MWM_DECOR_TITLE = (1L << 3), 16922 MWM_DECOR_MENU = (1L << 4), 16923 MWM_DECOR_MINIMIZE = (1L << 5), 16924 MWM_DECOR_MAXIMIZE = (1L << 6), 16925 } 16926 16927 import core.stdc.config : c_long, c_ulong; 16928 16929 /* Size hints mask bits */ 16930 16931 enum USPosition = (1L << 0) /* user specified x, y */; 16932 enum USSize = (1L << 1) /* user specified width, height */; 16933 enum PPosition = (1L << 2) /* program specified position */; 16934 enum PSize = (1L << 3) /* program specified size */; 16935 enum PMinSize = (1L << 4) /* program specified minimum size */; 16936 enum PMaxSize = (1L << 5) /* program specified maximum size */; 16937 enum PResizeInc = (1L << 6) /* program specified resize increments */; 16938 enum PAspect = (1L << 7) /* program specified min and max aspect ratios */; 16939 enum PBaseSize = (1L << 8); 16940 enum PWinGravity = (1L << 9); 16941 enum PAllHints = (PPosition|PSize| PMinSize|PMaxSize| PResizeInc|PAspect); 16942 struct XSizeHints { 16943 arch_long flags; /* marks which fields in this structure are defined */ 16944 int x, y; /* Obsolete */ 16945 int width, height; /* Obsolete */ 16946 int min_width, min_height; 16947 int max_width, max_height; 16948 int width_inc, height_inc; 16949 struct Aspect { 16950 int x; /* numerator */ 16951 int y; /* denominator */ 16952 } 16953 16954 Aspect min_aspect; 16955 Aspect max_aspect; 16956 int base_width, base_height; 16957 int win_gravity; 16958 /* this structure may be extended in the future */ 16959 } 16960 16961 16962 16963 enum EventType:int 16964 { 16965 KeyPress =2, 16966 KeyRelease =3, 16967 ButtonPress =4, 16968 ButtonRelease =5, 16969 MotionNotify =6, 16970 EnterNotify =7, 16971 LeaveNotify =8, 16972 FocusIn =9, 16973 FocusOut =10, 16974 KeymapNotify =11, 16975 Expose =12, 16976 GraphicsExpose =13, 16977 NoExpose =14, 16978 VisibilityNotify =15, 16979 CreateNotify =16, 16980 DestroyNotify =17, 16981 UnmapNotify =18, 16982 MapNotify =19, 16983 MapRequest =20, 16984 ReparentNotify =21, 16985 ConfigureNotify =22, 16986 ConfigureRequest =23, 16987 GravityNotify =24, 16988 ResizeRequest =25, 16989 CirculateNotify =26, 16990 CirculateRequest =27, 16991 PropertyNotify =28, 16992 SelectionClear =29, 16993 SelectionRequest =30, 16994 SelectionNotify =31, 16995 ColormapNotify =32, 16996 ClientMessage =33, 16997 MappingNotify =34, 16998 LASTEvent =35 /* must be bigger than any event # */ 16999 } 17000 /* generated on EnterWindow and FocusIn when KeyMapState selected */ 17001 struct XKeymapEvent 17002 { 17003 int type; 17004 arch_ulong serial; /* # of last request processed by server */ 17005 Bool send_event; /* true if this came from a SendEvent request */ 17006 Display *display; /* Display the event was read from */ 17007 Window window; 17008 byte[32] key_vector; 17009 } 17010 17011 struct XExposeEvent 17012 { 17013 int type; 17014 arch_ulong serial; /* # of last request processed by server */ 17015 Bool send_event; /* true if this came from a SendEvent request */ 17016 Display *display; /* Display the event was read from */ 17017 Window window; 17018 int x, y; 17019 int width, height; 17020 int count; /* if non-zero, at least this many more */ 17021 } 17022 17023 struct XGraphicsExposeEvent{ 17024 int type; 17025 arch_ulong serial; /* # of last request processed by server */ 17026 Bool send_event; /* true if this came from a SendEvent request */ 17027 Display *display; /* Display the event was read from */ 17028 Drawable drawable; 17029 int x, y; 17030 int width, height; 17031 int count; /* if non-zero, at least this many more */ 17032 int major_code; /* core is CopyArea or CopyPlane */ 17033 int minor_code; /* not defined in the core */ 17034 } 17035 17036 struct XNoExposeEvent{ 17037 int type; 17038 arch_ulong serial; /* # of last request processed by server */ 17039 Bool send_event; /* true if this came from a SendEvent request */ 17040 Display *display; /* Display the event was read from */ 17041 Drawable drawable; 17042 int major_code; /* core is CopyArea or CopyPlane */ 17043 int minor_code; /* not defined in the core */ 17044 } 17045 17046 struct XVisibilityEvent{ 17047 int type; 17048 arch_ulong serial; /* # of last request processed by server */ 17049 Bool send_event; /* true if this came from a SendEvent request */ 17050 Display *display; /* Display the event was read from */ 17051 Window window; 17052 VisibilityNotify state; /* Visibility state */ 17053 } 17054 17055 struct XCreateWindowEvent{ 17056 int type; 17057 arch_ulong serial; /* # of last request processed by server */ 17058 Bool send_event; /* true if this came from a SendEvent request */ 17059 Display *display; /* Display the event was read from */ 17060 Window parent; /* parent of the window */ 17061 Window window; /* window id of window created */ 17062 int x, y; /* window location */ 17063 int width, height; /* size of window */ 17064 int border_width; /* border width */ 17065 Bool override_redirect; /* creation should be overridden */ 17066 } 17067 17068 struct XDestroyWindowEvent 17069 { 17070 int type; 17071 arch_ulong serial; /* # of last request processed by server */ 17072 Bool send_event; /* true if this came from a SendEvent request */ 17073 Display *display; /* Display the event was read from */ 17074 Window event; 17075 Window window; 17076 } 17077 17078 struct XUnmapEvent 17079 { 17080 int type; 17081 arch_ulong serial; /* # of last request processed by server */ 17082 Bool send_event; /* true if this came from a SendEvent request */ 17083 Display *display; /* Display the event was read from */ 17084 Window event; 17085 Window window; 17086 Bool from_configure; 17087 } 17088 17089 struct XMapEvent 17090 { 17091 int type; 17092 arch_ulong serial; /* # of last request processed by server */ 17093 Bool send_event; /* true if this came from a SendEvent request */ 17094 Display *display; /* Display the event was read from */ 17095 Window event; 17096 Window window; 17097 Bool override_redirect; /* Boolean, is override set... */ 17098 } 17099 17100 struct XMapRequestEvent 17101 { 17102 int type; 17103 arch_ulong serial; /* # of last request processed by server */ 17104 Bool send_event; /* true if this came from a SendEvent request */ 17105 Display *display; /* Display the event was read from */ 17106 Window parent; 17107 Window window; 17108 } 17109 17110 struct XReparentEvent 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 Window parent; 17119 int x, y; 17120 Bool override_redirect; 17121 } 17122 17123 struct XConfigureEvent 17124 { 17125 int type; 17126 arch_ulong serial; /* # of last request processed by server */ 17127 Bool send_event; /* true if this came from a SendEvent request */ 17128 Display *display; /* Display the event was read from */ 17129 Window event; 17130 Window window; 17131 int x, y; 17132 int width, height; 17133 int border_width; 17134 Window above; 17135 Bool override_redirect; 17136 } 17137 17138 struct XGravityEvent 17139 { 17140 int type; 17141 arch_ulong serial; /* # of last request processed by server */ 17142 Bool send_event; /* true if this came from a SendEvent request */ 17143 Display *display; /* Display the event was read from */ 17144 Window event; 17145 Window window; 17146 int x, y; 17147 } 17148 17149 struct XResizeRequestEvent 17150 { 17151 int type; 17152 arch_ulong serial; /* # of last request processed by server */ 17153 Bool send_event; /* true if this came from a SendEvent request */ 17154 Display *display; /* Display the event was read from */ 17155 Window window; 17156 int width, height; 17157 } 17158 17159 struct XConfigureRequestEvent 17160 { 17161 int type; 17162 arch_ulong serial; /* # of last request processed by server */ 17163 Bool send_event; /* true if this came from a SendEvent request */ 17164 Display *display; /* Display the event was read from */ 17165 Window parent; 17166 Window window; 17167 int x, y; 17168 int width, height; 17169 int border_width; 17170 Window above; 17171 WindowStackingMethod detail; /* Above, Below, TopIf, BottomIf, Opposite */ 17172 arch_ulong value_mask; 17173 } 17174 17175 struct XCirculateEvent 17176 { 17177 int type; 17178 arch_ulong serial; /* # of last request processed by server */ 17179 Bool send_event; /* true if this came from a SendEvent request */ 17180 Display *display; /* Display the event was read from */ 17181 Window event; 17182 Window window; 17183 CirculationRequest place; /* PlaceOnTop, PlaceOnBottom */ 17184 } 17185 17186 struct XCirculateRequestEvent 17187 { 17188 int type; 17189 arch_ulong serial; /* # of last request processed by server */ 17190 Bool send_event; /* true if this came from a SendEvent request */ 17191 Display *display; /* Display the event was read from */ 17192 Window parent; 17193 Window window; 17194 CirculationRequest place; /* PlaceOnTop, PlaceOnBottom */ 17195 } 17196 17197 struct XPropertyEvent 17198 { 17199 int type; 17200 arch_ulong serial; /* # of last request processed by server */ 17201 Bool send_event; /* true if this came from a SendEvent request */ 17202 Display *display; /* Display the event was read from */ 17203 Window window; 17204 Atom atom; 17205 Time time; 17206 PropertyNotification state; /* NewValue, Deleted */ 17207 } 17208 17209 struct XSelectionClearEvent 17210 { 17211 int type; 17212 arch_ulong serial; /* # of last request processed by server */ 17213 Bool send_event; /* true if this came from a SendEvent request */ 17214 Display *display; /* Display the event was read from */ 17215 Window window; 17216 Atom selection; 17217 Time time; 17218 } 17219 17220 struct XSelectionRequestEvent 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 owner; 17227 Window requestor; 17228 Atom selection; 17229 Atom target; 17230 Atom property; 17231 Time time; 17232 } 17233 17234 struct XSelectionEvent 17235 { 17236 int type; 17237 arch_ulong serial; /* # of last request processed by server */ 17238 Bool send_event; /* true if this came from a SendEvent request */ 17239 Display *display; /* Display the event was read from */ 17240 Window requestor; 17241 Atom selection; 17242 Atom target; 17243 Atom property; /* ATOM or None */ 17244 Time time; 17245 } 17246 version(X86_64) static assert(XSelectionClearEvent.sizeof == 56); 17247 17248 struct XColormapEvent 17249 { 17250 int type; 17251 arch_ulong serial; /* # of last request processed by server */ 17252 Bool send_event; /* true if this came from a SendEvent request */ 17253 Display *display; /* Display the event was read from */ 17254 Window window; 17255 Colormap colormap; /* COLORMAP or None */ 17256 Bool new_; /* C++ */ 17257 ColorMapNotification state; /* ColormapInstalled, ColormapUninstalled */ 17258 } 17259 version(X86_64) static assert(XColormapEvent.sizeof == 56); 17260 17261 struct XClientMessageEvent 17262 { 17263 int type; 17264 arch_ulong serial; /* # of last request processed by server */ 17265 Bool send_event; /* true if this came from a SendEvent request */ 17266 Display *display; /* Display the event was read from */ 17267 Window window; 17268 Atom message_type; 17269 int format; 17270 union Data{ 17271 byte[20] b; 17272 short[10] s; 17273 arch_ulong[5] l; 17274 } 17275 Data data; 17276 17277 } 17278 version(X86_64) static assert(XClientMessageEvent.sizeof == 96); 17279 17280 struct XMappingEvent 17281 { 17282 int type; 17283 arch_ulong serial; /* # of last request processed by server */ 17284 Bool send_event; /* true if this came from a SendEvent request */ 17285 Display *display; /* Display the event was read from */ 17286 Window window; /* unused */ 17287 MappingType request; /* one of MappingModifier, MappingKeyboard, 17288 MappingPointer */ 17289 int first_keycode; /* first keycode */ 17290 int count; /* defines range of change w. first_keycode*/ 17291 } 17292 17293 struct XErrorEvent 17294 { 17295 int type; 17296 Display *display; /* Display the event was read from */ 17297 XID resourceid; /* resource id */ 17298 arch_ulong serial; /* serial number of failed request */ 17299 ubyte error_code; /* error code of failed request */ 17300 ubyte request_code; /* Major op-code of failed request */ 17301 ubyte minor_code; /* Minor op-code of failed request */ 17302 } 17303 17304 struct XAnyEvent 17305 { 17306 int type; 17307 arch_ulong serial; /* # of last request processed by server */ 17308 Bool send_event; /* true if this came from a SendEvent request */ 17309 Display *display;/* Display the event was read from */ 17310 Window window; /* window on which event was requested in event mask */ 17311 } 17312 17313 union XEvent{ 17314 int type; /* must not be changed; first element */ 17315 XAnyEvent xany; 17316 XKeyEvent xkey; 17317 XButtonEvent xbutton; 17318 XMotionEvent xmotion; 17319 XCrossingEvent xcrossing; 17320 XFocusChangeEvent xfocus; 17321 XExposeEvent xexpose; 17322 XGraphicsExposeEvent xgraphicsexpose; 17323 XNoExposeEvent xnoexpose; 17324 XVisibilityEvent xvisibility; 17325 XCreateWindowEvent xcreatewindow; 17326 XDestroyWindowEvent xdestroywindow; 17327 XUnmapEvent xunmap; 17328 XMapEvent xmap; 17329 XMapRequestEvent xmaprequest; 17330 XReparentEvent xreparent; 17331 XConfigureEvent xconfigure; 17332 XGravityEvent xgravity; 17333 XResizeRequestEvent xresizerequest; 17334 XConfigureRequestEvent xconfigurerequest; 17335 XCirculateEvent xcirculate; 17336 XCirculateRequestEvent xcirculaterequest; 17337 XPropertyEvent xproperty; 17338 XSelectionClearEvent xselectionclear; 17339 XSelectionRequestEvent xselectionrequest; 17340 XSelectionEvent xselection; 17341 XColormapEvent xcolormap; 17342 XClientMessageEvent xclient; 17343 XMappingEvent xmapping; 17344 XErrorEvent xerror; 17345 XKeymapEvent xkeymap; 17346 arch_ulong[24] pad; 17347 } 17348 17349 17350 struct Display { 17351 XExtData *ext_data; /* hook for extension to hang data */ 17352 _XPrivate *private1; 17353 int fd; /* Network socket. */ 17354 int private2; 17355 int proto_major_version;/* major version of server's X protocol */ 17356 int proto_minor_version;/* minor version of servers X protocol */ 17357 char *vendor; /* vendor of the server hardware */ 17358 XID private3; 17359 XID private4; 17360 XID private5; 17361 int private6; 17362 XID function(Display*)resource_alloc;/* allocator function */ 17363 ByteOrder byte_order; /* screen byte order, LSBFirst, MSBFirst */ 17364 int bitmap_unit; /* padding and data requirements */ 17365 int bitmap_pad; /* padding requirements on bitmaps */ 17366 ByteOrder bitmap_bit_order; /* LeastSignificant or MostSignificant */ 17367 int nformats; /* number of pixmap formats in list */ 17368 ScreenFormat *pixmap_format; /* pixmap format list */ 17369 int private8; 17370 int release; /* release of the server */ 17371 _XPrivate *private9; 17372 _XPrivate *private10; 17373 int qlen; /* Length of input event queue */ 17374 arch_ulong last_request_read; /* seq number of last event read */ 17375 arch_ulong request; /* sequence number of last request. */ 17376 XPointer private11; 17377 XPointer private12; 17378 XPointer private13; 17379 XPointer private14; 17380 uint max_request_size; /* maximum number 32 bit words in request*/ 17381 _XrmHashBucketRec *db; 17382 int function (Display*)private15; 17383 char *display_name; /* "host:display" string used on this connect*/ 17384 int default_screen; /* default screen for operations */ 17385 int nscreens; /* number of screens on this server*/ 17386 Screen *screens; /* pointer to list of screens */ 17387 arch_ulong motion_buffer; /* size of motion buffer */ 17388 arch_ulong private16; 17389 int min_keycode; /* minimum defined keycode */ 17390 int max_keycode; /* maximum defined keycode */ 17391 XPointer private17; 17392 XPointer private18; 17393 int private19; 17394 byte *xdefaults; /* contents of defaults from server */ 17395 /* there is more to this structure, but it is private to Xlib */ 17396 } 17397 17398 // I got these numbers from a C program as a sanity test 17399 version(X86_64) { 17400 static assert(Display.sizeof == 296); 17401 static assert(XPointer.sizeof == 8); 17402 static assert(XErrorEvent.sizeof == 40); 17403 static assert(XAnyEvent.sizeof == 40); 17404 static assert(XMappingEvent.sizeof == 56); 17405 static assert(XEvent.sizeof == 192); 17406 } else version (AArch64) { 17407 // omit check for aarch64 17408 } else { 17409 static assert(Display.sizeof == 176); 17410 static assert(XPointer.sizeof == 4); 17411 static assert(XEvent.sizeof == 96); 17412 } 17413 17414 struct Depth 17415 { 17416 int depth; /* this depth (Z) of the depth */ 17417 int nvisuals; /* number of Visual types at this depth */ 17418 Visual *visuals; /* list of visuals possible at this depth */ 17419 } 17420 17421 alias void* GC; 17422 alias c_ulong VisualID; 17423 alias XID Colormap; 17424 alias XID Cursor; 17425 alias XID KeySym; 17426 alias uint KeyCode; 17427 enum None = 0; 17428 } 17429 17430 version(without_opengl) {} 17431 else { 17432 extern(C) nothrow @nogc { 17433 17434 17435 static if(!SdpyIsUsingIVGLBinds) { 17436 enum GLX_USE_GL= 1; /* support GLX rendering */ 17437 enum GLX_BUFFER_SIZE= 2; /* depth of the color buffer */ 17438 enum GLX_LEVEL= 3; /* level in plane stacking */ 17439 enum GLX_RGBA= 4; /* true if RGBA mode */ 17440 enum GLX_DOUBLEBUFFER= 5; /* double buffering supported */ 17441 enum GLX_STEREO= 6; /* stereo buffering supported */ 17442 enum GLX_AUX_BUFFERS= 7; /* number of aux buffers */ 17443 enum GLX_RED_SIZE= 8; /* number of red component bits */ 17444 enum GLX_GREEN_SIZE= 9; /* number of green component bits */ 17445 enum GLX_BLUE_SIZE= 10; /* number of blue component bits */ 17446 enum GLX_ALPHA_SIZE= 11; /* number of alpha component bits */ 17447 enum GLX_DEPTH_SIZE= 12; /* number of depth bits */ 17448 enum GLX_STENCIL_SIZE= 13; /* number of stencil bits */ 17449 enum GLX_ACCUM_RED_SIZE= 14; /* number of red accum bits */ 17450 enum GLX_ACCUM_GREEN_SIZE= 15; /* number of green accum bits */ 17451 enum GLX_ACCUM_BLUE_SIZE= 16; /* number of blue accum bits */ 17452 enum GLX_ACCUM_ALPHA_SIZE= 17; /* number of alpha accum bits */ 17453 17454 17455 //XVisualInfo* glXChooseVisual(Display *dpy, int screen, in int *attrib_list); 17456 17457 17458 17459 enum GL_TRUE = 1; 17460 enum GL_FALSE = 0; 17461 } 17462 17463 alias XID GLXContextID; 17464 alias XID GLXPixmap; 17465 alias XID GLXDrawable; 17466 alias XID GLXPbuffer; 17467 alias XID GLXWindow; 17468 alias XID GLXFBConfigID; 17469 alias void* GLXContext; 17470 17471 } 17472 } 17473 17474 enum AllocNone = 0; 17475 17476 extern(C) { 17477 /* WARNING, this type not in Xlib spec */ 17478 extern(C) alias XIOErrorHandler = int function (Display* display); 17479 } 17480 17481 extern(C) nothrow 17482 alias XErrorHandler = int function(Display*, XErrorEvent*); 17483 17484 extern(C) nothrow @nogc { 17485 struct Screen{ 17486 XExtData *ext_data; /* hook for extension to hang data */ 17487 Display *display; /* back pointer to display structure */ 17488 Window root; /* Root window id. */ 17489 int width, height; /* width and height of screen */ 17490 int mwidth, mheight; /* width and height of in millimeters */ 17491 int ndepths; /* number of depths possible */ 17492 Depth *depths; /* list of allowable depths on the screen */ 17493 int root_depth; /* bits per pixel */ 17494 Visual *root_visual; /* root visual */ 17495 GC default_gc; /* GC for the root root visual */ 17496 Colormap cmap; /* default color map */ 17497 uint white_pixel; 17498 uint black_pixel; /* White and Black pixel values */ 17499 int max_maps, min_maps; /* max and min color maps */ 17500 int backing_store; /* Never, WhenMapped, Always */ 17501 bool save_unders; 17502 int root_input_mask; /* initial root input mask */ 17503 } 17504 17505 struct Visual 17506 { 17507 XExtData *ext_data; /* hook for extension to hang data */ 17508 VisualID visualid; /* visual id of this visual */ 17509 int class_; /* class of screen (monochrome, etc.) */ 17510 c_ulong red_mask, green_mask, blue_mask; /* mask values */ 17511 int bits_per_rgb; /* log base 2 of distinct color values */ 17512 int map_entries; /* color map entries */ 17513 } 17514 17515 alias Display* _XPrivDisplay; 17516 17517 extern(D) Screen* ScreenOfDisplay(Display* dpy, int scr) { 17518 assert(dpy !is null); 17519 return &dpy.screens[scr]; 17520 } 17521 17522 extern(D) Window RootWindow(Display *dpy,int scr) { 17523 return ScreenOfDisplay(dpy,scr).root; 17524 } 17525 17526 struct XWMHints { 17527 arch_long flags; 17528 Bool input; 17529 int initial_state; 17530 Pixmap icon_pixmap; 17531 Window icon_window; 17532 int icon_x, icon_y; 17533 Pixmap icon_mask; 17534 XID window_group; 17535 } 17536 17537 struct XClassHint { 17538 char* res_name; 17539 char* res_class; 17540 } 17541 17542 extern(D) int DefaultScreen(Display *dpy) { 17543 return dpy.default_screen; 17544 } 17545 17546 extern(D) int DefaultDepth(Display* dpy, int scr) { return ScreenOfDisplay(dpy, scr).root_depth; } 17547 extern(D) int DisplayWidth(Display* dpy, int scr) { return ScreenOfDisplay(dpy, scr).width; } 17548 extern(D) int DisplayHeight(Display* dpy, int scr) { return ScreenOfDisplay(dpy, scr).height; } 17549 extern(D) int DisplayWidthMM(Display* dpy, int scr) { return ScreenOfDisplay(dpy, scr).mwidth; } 17550 extern(D) int DisplayHeightMM(Display* dpy, int scr) { return ScreenOfDisplay(dpy, scr).mheight; } 17551 extern(D) auto DefaultColormap(Display* dpy, int scr) { return ScreenOfDisplay(dpy, scr).cmap; } 17552 17553 extern(D) int ConnectionNumber(Display* dpy) { return dpy.fd; } 17554 17555 enum int AnyPropertyType = 0; 17556 enum int Success = 0; 17557 17558 enum int RevertToNone = None; 17559 enum int PointerRoot = 1; 17560 enum Time CurrentTime = 0; 17561 enum int RevertToPointerRoot = PointerRoot; 17562 enum int RevertToParent = 2; 17563 17564 extern(D) int DefaultDepthOfDisplay(Display* dpy) { 17565 return ScreenOfDisplay(dpy, DefaultScreen(dpy)).root_depth; 17566 } 17567 17568 extern(D) Visual* DefaultVisual(Display *dpy,int scr) { 17569 return ScreenOfDisplay(dpy,scr).root_visual; 17570 } 17571 17572 extern(D) GC DefaultGC(Display *dpy,int scr) { 17573 return ScreenOfDisplay(dpy,scr).default_gc; 17574 } 17575 17576 extern(D) uint BlackPixel(Display *dpy,int scr) { 17577 return ScreenOfDisplay(dpy,scr).black_pixel; 17578 } 17579 17580 extern(D) uint WhitePixel(Display *dpy,int scr) { 17581 return ScreenOfDisplay(dpy,scr).white_pixel; 17582 } 17583 17584 alias void* XFontSet; // i think 17585 struct XmbTextItem { 17586 char* chars; 17587 int nchars; 17588 int delta; 17589 XFontSet font_set; 17590 } 17591 17592 struct XTextItem { 17593 char* chars; 17594 int nchars; 17595 int delta; 17596 Font font; 17597 } 17598 17599 enum { 17600 GXclear = 0x0, /* 0 */ 17601 GXand = 0x1, /* src AND dst */ 17602 GXandReverse = 0x2, /* src AND NOT dst */ 17603 GXcopy = 0x3, /* src */ 17604 GXandInverted = 0x4, /* NOT src AND dst */ 17605 GXnoop = 0x5, /* dst */ 17606 GXxor = 0x6, /* src XOR dst */ 17607 GXor = 0x7, /* src OR dst */ 17608 GXnor = 0x8, /* NOT src AND NOT dst */ 17609 GXequiv = 0x9, /* NOT src XOR dst */ 17610 GXinvert = 0xa, /* NOT dst */ 17611 GXorReverse = 0xb, /* src OR NOT dst */ 17612 GXcopyInverted = 0xc, /* NOT src */ 17613 GXorInverted = 0xd, /* NOT src OR dst */ 17614 GXnand = 0xe, /* NOT src OR NOT dst */ 17615 GXset = 0xf, /* 1 */ 17616 } 17617 enum QueueMode : int { 17618 QueuedAlready, 17619 QueuedAfterReading, 17620 QueuedAfterFlush 17621 } 17622 17623 enum GrabMode { GrabModeSync = 0, GrabModeAsync = 1 } 17624 17625 struct XPoint { 17626 short x; 17627 short y; 17628 } 17629 17630 enum CoordMode:int { 17631 CoordModeOrigin = 0, 17632 CoordModePrevious = 1 17633 } 17634 17635 enum PolygonShape:int { 17636 Complex = 0, 17637 Nonconvex = 1, 17638 Convex = 2 17639 } 17640 17641 struct XTextProperty { 17642 const(char)* value; /* same as Property routines */ 17643 Atom encoding; /* prop type */ 17644 int format; /* prop data format: 8, 16, or 32 */ 17645 arch_ulong nitems; /* number of data items in value */ 17646 } 17647 17648 version( X86_64 ) { 17649 static assert(XTextProperty.sizeof == 32); 17650 } 17651 17652 17653 struct XGCValues { 17654 int function_; /* logical operation */ 17655 arch_ulong plane_mask;/* plane mask */ 17656 arch_ulong foreground;/* foreground pixel */ 17657 arch_ulong background;/* background pixel */ 17658 int line_width; /* line width */ 17659 int line_style; /* LineSolid, LineOnOffDash, LineDoubleDash */ 17660 int cap_style; /* CapNotLast, CapButt, 17661 CapRound, CapProjecting */ 17662 int join_style; /* JoinMiter, JoinRound, JoinBevel */ 17663 int fill_style; /* FillSolid, FillTiled, 17664 FillStippled, FillOpaeueStippled */ 17665 int fill_rule; /* EvenOddRule, WindingRule */ 17666 int arc_mode; /* ArcChord, ArcPieSlice */ 17667 Pixmap tile; /* tile pixmap for tiling operations */ 17668 Pixmap stipple; /* stipple 1 plane pixmap for stipping */ 17669 int ts_x_origin; /* offset for tile or stipple operations */ 17670 int ts_y_origin; 17671 Font font; /* default text font for text operations */ 17672 int subwindow_mode; /* ClipByChildren, IncludeInferiors */ 17673 Bool graphics_exposures;/* boolean, should exposures be generated */ 17674 int clip_x_origin; /* origin for clipping */ 17675 int clip_y_origin; 17676 Pixmap clip_mask; /* bitmap clipping; other calls for rects */ 17677 int dash_offset; /* patterned/dashed line information */ 17678 char dashes; 17679 } 17680 17681 struct XColor { 17682 arch_ulong pixel; 17683 ushort red, green, blue; 17684 byte flags; 17685 byte pad; 17686 } 17687 17688 struct XRectangle { 17689 short x; 17690 short y; 17691 ushort width; 17692 ushort height; 17693 } 17694 17695 enum ClipByChildren = 0; 17696 enum IncludeInferiors = 1; 17697 17698 enum Atom XA_PRIMARY = 1; 17699 enum Atom XA_SECONDARY = 2; 17700 enum Atom XA_STRING = 31; 17701 enum Atom XA_CARDINAL = 6; 17702 enum Atom XA_WM_NAME = 39; 17703 enum Atom XA_ATOM = 4; 17704 enum Atom XA_WINDOW = 33; 17705 enum Atom XA_WM_HINTS = 35; 17706 enum int PropModeAppend = 2; 17707 enum int PropModeReplace = 0; 17708 enum int PropModePrepend = 1; 17709 17710 enum int CopyFromParent = 0; 17711 enum int InputOutput = 1; 17712 17713 // XWMHints 17714 enum InputHint = 1 << 0; 17715 enum StateHint = 1 << 1; 17716 enum IconPixmapHint = (1L << 2); 17717 enum IconWindowHint = (1L << 3); 17718 enum IconPositionHint = (1L << 4); 17719 enum IconMaskHint = (1L << 5); 17720 enum WindowGroupHint = (1L << 6); 17721 enum AllHints = (InputHint|StateHint|IconPixmapHint|IconWindowHint|IconPositionHint|IconMaskHint|WindowGroupHint); 17722 enum XUrgencyHint = (1L << 8); 17723 17724 // GC Components 17725 enum GCFunction = (1L<<0); 17726 enum GCPlaneMask = (1L<<1); 17727 enum GCForeground = (1L<<2); 17728 enum GCBackground = (1L<<3); 17729 enum GCLineWidth = (1L<<4); 17730 enum GCLineStyle = (1L<<5); 17731 enum GCCapStyle = (1L<<6); 17732 enum GCJoinStyle = (1L<<7); 17733 enum GCFillStyle = (1L<<8); 17734 enum GCFillRule = (1L<<9); 17735 enum GCTile = (1L<<10); 17736 enum GCStipple = (1L<<11); 17737 enum GCTileStipXOrigin = (1L<<12); 17738 enum GCTileStipYOrigin = (1L<<13); 17739 enum GCFont = (1L<<14); 17740 enum GCSubwindowMode = (1L<<15); 17741 enum GCGraphicsExposures= (1L<<16); 17742 enum GCClipXOrigin = (1L<<17); 17743 enum GCClipYOrigin = (1L<<18); 17744 enum GCClipMask = (1L<<19); 17745 enum GCDashOffset = (1L<<20); 17746 enum GCDashList = (1L<<21); 17747 enum GCArcMode = (1L<<22); 17748 enum GCLastBit = 22; 17749 17750 17751 enum int WithdrawnState = 0; 17752 enum int NormalState = 1; 17753 enum int IconicState = 3; 17754 17755 } 17756 } else version (OSXCocoa) { 17757 17758 /+ 17759 DON'T FORGET TO MARK THE CLASSES `extern`!! can cause "unrecognized selector sent to class" errors if you do. 17760 +/ 17761 17762 private __gshared AppDelegate globalAppDelegate; 17763 17764 extern(Objective-C) 17765 class AppDelegate : NSObject, NSApplicationDelegate { 17766 override static AppDelegate alloc() @selector("alloc"); 17767 17768 17769 void sdpyCustomEventWakeup(NSid arg) @selector("sdpyCustomEventWakeup:") { 17770 SimpleWindow.processAllCustomEvents(); 17771 } 17772 17773 override void applicationWillFinishLaunching(NSNotification notification) @selector("applicationWillFinishLaunching:") { 17774 immutable style = NSWindowStyleMask.resizable | 17775 NSWindowStyleMask.closable | 17776 NSWindowStyleMask.miniaturizable | 17777 NSWindowStyleMask.titled; 17778 17779 NSMenu mainMenu = NSMenu.alloc.init(MacString("Main").borrow); 17780 17781 { 17782 auto item = mainMenu.addItem(MacString("Test").borrow, null, MacString("").borrow); 17783 auto menu = NSMenu.alloc.init(MacString("Test2").borrow); 17784 mainMenu.setSubmenu(menu, item); 17785 17786 auto newItem = menu.addItem(MacString("Quit").borrow, sel_registerName("terminate:"), MacString("q").borrow); 17787 newItem.target = NSApp; 17788 auto newItem2 = menu.addItem(MacString("Disabled").borrow, sel_registerName("doesnotexist:"), MacString("x").borrow); 17789 newItem2.target = NSApp; 17790 } 17791 17792 { 17793 auto item = mainMenu.addItem(MacString("Test3").borrow, null, MacString("").borrow); 17794 auto menu = NSMenu.alloc.init(MacString("Test4").borrow); // this is the title actually used 17795 mainMenu.setSubmenu(menu, item); 17796 17797 auto newItem = menu.addItem(MacString("Quit2").borrow, sel_registerName("stop:"), MacString("s").borrow); 17798 menu.addItem(MacString("Pulse").borrow, sel_registerName("simpledisplay_pulse:"), MacString("p").borrow); 17799 } 17800 17801 17802 NSApp.menu = mainMenu; 17803 17804 17805 // auto controller = ViewController.alloc.init; 17806 17807 // auto timer = NSTimer.schedule(1.0, cast(NSid) view, sel_registerName("simpledisplay_pulse:"), null, true); 17808 17809 /+ 17810 this.window = window; 17811 this.controller = controller; 17812 +/ 17813 } 17814 17815 override void applicationDidFinishLaunching(NSNotification notification) @selector("applicationDidFinishLaunching:") { 17816 NSApplication.shared_.activateIgnoringOtherApps(true); 17817 } 17818 override bool applicationShouldTerminateAfterLastWindowClosed(NSNotification notification) @selector("applicationShouldTerminateAfterLastWindowClosed:") { 17819 return true; 17820 } 17821 } 17822 17823 extern(Objective-C) 17824 class SDWindowDelegate : NSObject, NSWindowDelegate { 17825 override static SDWindowDelegate alloc() @selector("alloc"); 17826 override SDWindowDelegate init() @selector("init"); 17827 17828 SimpleWindow simpleWindow; 17829 17830 override void windowWillClose(NSNotification notification) @selector("windowWillClose:") { 17831 auto window = cast(void*) notification.object; 17832 17833 // FIXME: do i need to release it? 17834 SimpleWindow.nativeMapping.remove(window); 17835 } 17836 17837 override NSSize windowWillResize(NSWindow sender, NSSize frameSize) @selector("windowWillResize:toSize:") { 17838 if(simpleWindow.windowResized) { 17839 // FIXME: automaticallyScaleIfPossible behaviors 17840 17841 simpleWindow._width = cast(int) frameSize.width; 17842 simpleWindow._height = cast(int) frameSize.height; 17843 17844 simpleWindow.view.setFrameSize(frameSize); 17845 17846 /+ 17847 auto size = simpleWindow.view.frame.size; 17848 writeln(cast(int) size.width, "x", cast(int) size.height); 17849 +/ 17850 17851 simpleWindow.createNewDrawingContext(simpleWindow._width, simpleWindow._height); 17852 17853 simpleWindow.windowResized(simpleWindow._width, simpleWindow._height); 17854 17855 // simpleWindow.view.setNeedsDisplay(true); 17856 } 17857 17858 return frameSize; 17859 } 17860 17861 /+ 17862 override void windowDidResize(NSNotification notification) @selector("windowDidResize:") { 17863 if(simpleWindow.windowResized) { 17864 auto window = simpleWindow.window; 17865 auto rect = window.contentRectForFrameRect(window.frame); 17866 import std.stdio; writeln(window.frame.size); 17867 simpleWindow.windowResized(cast(int) rect.size.width, cast(int) rect.size.height); 17868 } 17869 } 17870 +/ 17871 } 17872 17873 extern(Objective-C) 17874 class SDGraphicsView : NSView { 17875 SimpleWindow simpleWindow; 17876 17877 override static SDGraphicsView alloc() @selector("alloc"); 17878 override SDGraphicsView init() @selector("init") { 17879 super.init(); 17880 return this; 17881 } 17882 17883 override void drawRect(NSRect rect) @selector("drawRect:") { 17884 auto curCtx = NSGraphicsContext.currentContext.graphicsPort; 17885 auto cgImage = CGBitmapContextCreateImage(simpleWindow.drawingContext); 17886 auto size = CGSize(CGBitmapContextGetWidth(simpleWindow.drawingContext), CGBitmapContextGetHeight(simpleWindow.drawingContext)); 17887 CGContextDrawImage(curCtx, CGRect(CGPoint(0, 0), size), cgImage); 17888 CGImageRelease(cgImage); 17889 } 17890 17891 private void mouseHelper(NSEvent event, MouseEventType type, MouseButton button) { 17892 MouseEvent me; 17893 me.type = type; 17894 17895 auto pos = event.locationInWindow; 17896 17897 me.x = cast(int) pos.x; 17898 me.y = cast(int) (simpleWindow.height - pos.y); 17899 17900 me.dx = 0; // FIXME 17901 me.dy = 0; // FIXME 17902 17903 me.button = button; 17904 me.modifierState = cast(uint) event.modifierFlags; 17905 me.window = simpleWindow; 17906 17907 me.doubleClick = false; 17908 17909 if(simpleWindow && simpleWindow.handleMouseEvent) 17910 simpleWindow.handleMouseEvent(me); 17911 } 17912 17913 override void mouseDown(NSEvent event) @selector("mouseDown:") { 17914 // writeln(event.pressedMouseButtons); 17915 17916 mouseHelper(event, MouseEventType.buttonPressed, MouseButton.left); 17917 } 17918 override void mouseDragged(NSEvent event) @selector("mouseDragged:") { 17919 mouseHelper(event, MouseEventType.motion, MouseButton.left); 17920 } 17921 override void mouseUp(NSEvent event) @selector("mouseUp:") { 17922 mouseHelper(event, MouseEventType.buttonReleased, MouseButton.left); 17923 } 17924 override void mouseMoved(NSEvent event) @selector("mouseMoved:") { 17925 mouseHelper(event, MouseEventType.motion, MouseButton.left); // button wrong prolly 17926 } 17927 /+ 17928 // FIXME 17929 override void mouseEntered(NSEvent event) @selector("mouseEntered:") { 17930 mouseHelper(event, MouseEventType.buttonPressed, MouseButton.left); 17931 } 17932 override void mouseExited(NSEvent event) @selector("mouseExited:") { 17933 mouseHelper(event, MouseEventType.buttonPressed, MouseButton.left); 17934 } 17935 +/ 17936 17937 override void rightMouseDown(NSEvent event) @selector("rightMouseDown:") { 17938 mouseHelper(event, MouseEventType.buttonPressed, MouseButton.right); 17939 } 17940 override void rightMouseDragged(NSEvent event) @selector("rightMouseDragged:") { 17941 mouseHelper(event, MouseEventType.motion, MouseButton.right); 17942 } 17943 override void rightMouseUp(NSEvent event) @selector("rightMouseUp:") { 17944 mouseHelper(event, MouseEventType.buttonReleased, MouseButton.right); 17945 } 17946 17947 override void otherMouseDown(NSEvent event) @selector("otherMouseDown:") { 17948 mouseHelper(event, MouseEventType.buttonPressed, MouseButton.middle); 17949 } 17950 override void otherMouseDragged(NSEvent event) @selector("otherMouseDragged:") { 17951 mouseHelper(event, MouseEventType.motion, MouseButton.middle); 17952 } 17953 override void otherMouseUp(NSEvent event) @selector("otherMouseUp:") { 17954 mouseHelper(event, MouseEventType.buttonReleased, MouseButton.middle); 17955 } 17956 17957 override void scrollWheel(NSEvent event) @selector("scrollWheel:") { 17958 import std.stdio; 17959 writeln(event.deltaY); 17960 } 17961 17962 override void keyDown(NSEvent event) @selector("keyDown:") { 17963 // the event may have multiple characters, and we send them all at once. 17964 if (simpleWindow.handleCharEvent) { 17965 auto chars = DeifiedNSString(event.characters); 17966 foreach (dchar dc; chars.str) 17967 simpleWindow.handleCharEvent(dc); 17968 } 17969 17970 keyHelper(event, true); 17971 } 17972 17973 override void keyUp(NSEvent event) @selector("keyUp:") { 17974 keyHelper(event, false); 17975 } 17976 17977 private void keyHelper(NSEvent event, bool pressed) { 17978 if(simpleWindow.handleKeyEvent) { 17979 KeyEvent ev; 17980 ev.key = cast(Key) event.keyCode;// (event.specialKey ? event.specialKey : event.keyCode); 17981 ev.pressed = pressed; 17982 ev.hardwareCode = cast(ubyte) event.keyCode; 17983 ev.modifierState = cast(uint) event.modifierFlags; 17984 ev.window = simpleWindow; 17985 17986 simpleWindow.handleKeyEvent(ev); 17987 } 17988 } 17989 17990 override bool isFlipped() @selector("isFlipped") { 17991 return true; 17992 } 17993 override bool acceptsFirstResponder() @selector("acceptsFirstResponder") { 17994 return true; 17995 } 17996 17997 void simpledisplay_pulse(NSTimer timer) @selector("simpledisplay_pulse:") { 17998 if(simpleWindow && simpleWindow.handlePulse) 17999 simpleWindow.handlePulse(); 18000 /+ 18001 setNeedsDisplay = true; 18002 +/ 18003 } 18004 } 18005 18006 private: 18007 alias const(void)* CFStringRef; 18008 alias const(void)* CFAllocatorRef; 18009 alias const(void)* CFTypeRef; 18010 alias const(void)* CGColorSpaceRef; 18011 alias const(void)* CGImageRef; 18012 alias ulong CGBitmapInfo; 18013 alias NSGraphicsContext CGContextRef; 18014 18015 alias NSPoint CGPoint; 18016 alias NSSize CGSize; 18017 alias NSRect CGRect; 18018 18019 struct CGAffineTransform { 18020 double a, b, c, d, tx, ty; 18021 } 18022 18023 enum NSApplicationActivationPolicyRegular = 0; 18024 enum NSBackingStoreBuffered = 2; 18025 enum kCFStringEncodingUTF8 = 0x08000100; 18026 18027 enum : size_t { 18028 NSBorderlessWindowMask = 0, 18029 NSTitledWindowMask = 1 << 0, 18030 NSClosableWindowMask = 1 << 1, 18031 NSMiniaturizableWindowMask = 1 << 2, 18032 NSResizableWindowMask = 1 << 3, 18033 NSTexturedBackgroundWindowMask = 1 << 8 18034 } 18035 18036 enum : ulong { 18037 kCGImageAlphaNone, 18038 kCGImageAlphaPremultipliedLast, 18039 kCGImageAlphaPremultipliedFirst, 18040 kCGImageAlphaLast, 18041 kCGImageAlphaFirst, 18042 kCGImageAlphaNoneSkipLast, 18043 kCGImageAlphaNoneSkipFirst 18044 } 18045 enum : ulong { 18046 kCGBitmapAlphaInfoMask = 0x1F, 18047 kCGBitmapFloatComponents = (1 << 8), 18048 kCGBitmapByteOrderMask = 0x7000, 18049 kCGBitmapByteOrderDefault = (0 << 12), 18050 kCGBitmapByteOrder16Little = (1 << 12), 18051 kCGBitmapByteOrder32Little = (2 << 12), 18052 kCGBitmapByteOrder16Big = (3 << 12), 18053 kCGBitmapByteOrder32Big = (4 << 12) 18054 } 18055 enum CGPathDrawingMode { 18056 kCGPathFill, 18057 kCGPathEOFill, 18058 kCGPathStroke, 18059 kCGPathFillStroke, 18060 kCGPathEOFillStroke 18061 } 18062 enum objc_AssociationPolicy : size_t { 18063 OBJC_ASSOCIATION_ASSIGN = 0, 18064 OBJC_ASSOCIATION_RETAIN_NONATOMIC = 1, 18065 OBJC_ASSOCIATION_COPY_NONATOMIC = 3, 18066 OBJC_ASSOCIATION_RETAIN = 0x301, //01401, 18067 OBJC_ASSOCIATION_COPY = 0x303 //01403 18068 } 18069 18070 extern(C) { 18071 CGContextRef CGBitmapContextCreate(void* data, size_t width, size_t height, size_t bitsPerComponent, size_t bytesPerRow, CGColorSpaceRef colorspace, CGBitmapInfo bitmapInfo); 18072 void CGContextRelease(CGContextRef c); 18073 ubyte* CGBitmapContextGetData(CGContextRef c); 18074 CGImageRef CGBitmapContextCreateImage(CGContextRef c); 18075 size_t CGBitmapContextGetWidth(CGContextRef c); 18076 size_t CGBitmapContextGetHeight(CGContextRef c); 18077 18078 CGColorSpaceRef CGColorSpaceCreateDeviceRGB(); 18079 void CGColorSpaceRelease(CGColorSpaceRef cs); 18080 18081 void CGContextSetRGBStrokeColor(CGContextRef c, double red, double green, double blue, double alpha); 18082 void CGContextSetRGBFillColor(CGContextRef c, double red, double green, double blue, double alpha); 18083 void CGContextDrawImage(CGContextRef c, CGRect rect, CGImageRef image); 18084 void CGContextShowTextAtPoint(CGContextRef c, double x, double y, const(char)* str, size_t length); 18085 void CGContextStrokeLineSegments(CGContextRef c, const(CGPoint)* points, size_t count); 18086 void CGContextSetLineDash(CGContextRef c, CGFloat phase, const CGFloat *lengths, size_t count); 18087 18088 void CGContextBeginPath(CGContextRef c); 18089 void CGContextDrawPath(CGContextRef c, CGPathDrawingMode mode); 18090 void CGContextAddEllipseInRect(CGContextRef c, CGRect rect); 18091 void CGContextAddArc(CGContextRef c, double x, double y, double radius, double startAngle, double endAngle, long clockwise); 18092 void CGContextAddRect(CGContextRef c, CGRect rect); 18093 void CGContextAddLines(CGContextRef c, const(CGPoint)* points, size_t count); 18094 void CGContextSaveGState(CGContextRef c); 18095 void CGContextRestoreGState(CGContextRef c); 18096 void CGContextSelectFont(CGContextRef c, const(char)* name, double size, ulong textEncoding); 18097 CGAffineTransform CGContextGetTextMatrix(CGContextRef c); 18098 void CGContextSetTextMatrix(CGContextRef c, CGAffineTransform t); 18099 18100 void CGImageRelease(CGImageRef image); 18101 } 18102 } else static assert(0, "Unsupported operating system"); 18103 18104 18105 version(OSXCocoa) { 18106 // I don't know anything about the Mac, but a couple years ago, KennyTM on the newsgroup wrote this for me 18107 // 18108 // http://forum.dlang.org/thread/innr0v$1deh$1@digitalmars.com?page=4#post-int88l:24uaf:241:40digitalmars.com 18109 // https://github.com/kennytm/simpledisplay.d/blob/osx/simpledisplay.d 18110 // 18111 // and it is about time I merged it in here. It is available with -version=OSXCocoa until someone tests it for me! 18112 // Probably won't even fully compile right now 18113 18114 private enum double PI = 3.14159265358979323; 18115 18116 alias NSWindow NativeWindowHandle; 18117 alias void delegate(NSid) NativeEventHandler; 18118 18119 enum KEY_ESCAPE = 27; 18120 18121 mixin template NativeImageImplementation() { 18122 CGContextRef context; 18123 ubyte* rawData; 18124 18125 final: 18126 18127 void convertToRgbaBytes(ubyte[] where) { 18128 assert(where.length == this.width * this.height * 4); 18129 18130 // if rawData had a length.... 18131 //assert(rawData.length == where.length); 18132 for(long idx = 0; idx < where.length; idx += 4) { 18133 auto alpha = rawData[idx + 3]; 18134 if(alpha == 255) { 18135 where[idx + 0] = rawData[idx + 0]; // r 18136 where[idx + 1] = rawData[idx + 1]; // g 18137 where[idx + 2] = rawData[idx + 2]; // b 18138 where[idx + 3] = rawData[idx + 3]; // a 18139 } else { 18140 where[idx + 0] = cast(ubyte)(rawData[idx + 0] * 255 / alpha); // r 18141 where[idx + 1] = cast(ubyte)(rawData[idx + 1] * 255 / alpha); // g 18142 where[idx + 2] = cast(ubyte)(rawData[idx + 2] * 255 / alpha); // b 18143 where[idx + 3] = rawData[idx + 3]; // a 18144 18145 } 18146 } 18147 } 18148 18149 void setFromRgbaBytes(in ubyte[] where) { 18150 // FIXME: this is probably wrong 18151 assert(where.length == this.width * this.height * 4); 18152 18153 // if rawData had a length.... 18154 //assert(rawData.length == where.length); 18155 for(long idx = 0; idx < where.length; idx += 4) { 18156 auto alpha = where[idx + 3]; 18157 if(alpha == 255) { 18158 rawData[idx + 0] = where[idx + 0]; // r 18159 rawData[idx + 1] = where[idx + 1]; // g 18160 rawData[idx + 2] = where[idx + 2]; // b 18161 rawData[idx + 3] = where[idx + 3]; // a 18162 } else if(alpha == 0) { 18163 rawData[idx + 0] = 0; 18164 rawData[idx + 1] = 0; 18165 rawData[idx + 2] = 0; 18166 rawData[idx + 3] = 0; 18167 } else { 18168 rawData[idx + 0] = cast(ubyte)(where[idx + 0] * 255 / alpha); // r 18169 rawData[idx + 1] = cast(ubyte)(where[idx + 1] * 255 / alpha); // g 18170 rawData[idx + 2] = cast(ubyte)(where[idx + 2] * 255 / alpha); // b 18171 rawData[idx + 3] = where[idx + 3]; // a 18172 } 18173 } 18174 } 18175 18176 18177 void createImage(int width, int height, bool forcexshm=false, bool ignored = false) { 18178 auto colorSpace = CGColorSpaceCreateDeviceRGB(); 18179 context = CGBitmapContextCreate(null, width, height, 8, 4*width, colorSpace, kCGImageAlphaPremultipliedLast |kCGBitmapByteOrder32Big); 18180 CGColorSpaceRelease(colorSpace); 18181 rawData = CGBitmapContextGetData(context); 18182 } 18183 void dispose() { 18184 CGContextRelease(context); 18185 } 18186 18187 void setPixel(int x, int y, Color c) { 18188 auto offset = (y * width + x) * 4; 18189 if (c.a == 255) { 18190 rawData[offset + 0] = c.r; 18191 rawData[offset + 1] = c.g; 18192 rawData[offset + 2] = c.b; 18193 rawData[offset + 3] = c.a; 18194 } else { 18195 rawData[offset + 0] = cast(ubyte)(c.r*c.a/255); 18196 rawData[offset + 1] = cast(ubyte)(c.g*c.a/255); 18197 rawData[offset + 2] = cast(ubyte)(c.b*c.a/255); 18198 rawData[offset + 3] = c.a; 18199 } 18200 } 18201 } 18202 18203 mixin template NativeScreenPainterImplementation() { 18204 CGContextRef context; 18205 ubyte[4] _outlineComponents; 18206 NSView view; 18207 18208 Pen _activePen; 18209 Color _fillColor; 18210 Rectangle _clipRectangle; 18211 OperatingSystemFont _font; 18212 18213 OperatingSystemFont getFont() { 18214 if(_font is null) { 18215 static OperatingSystemFont _defaultFont; 18216 if(_defaultFont is null) { 18217 _defaultFont = new OperatingSystemFont(); 18218 _defaultFont.loadDefault(); 18219 } 18220 _font = _defaultFont; 18221 } 18222 18223 return _font; 18224 } 18225 18226 void create(PaintingHandle window) { 18227 // this.destiny = window; 18228 if(auto sw = cast(SimpleWindow) this.window) { 18229 context = sw.drawingContext; 18230 view = sw.view; 18231 } else { 18232 throw new NotYetImplementedException(); 18233 } 18234 } 18235 18236 void dispose() { 18237 view.setNeedsDisplay(true); 18238 } 18239 18240 bool manualInvalidations; 18241 void invalidateRect(Rectangle invalidRect) { } 18242 18243 // NotYetImplementedException 18244 void rasterOp(RasterOp op) { 18245 } 18246 void setClipRectangle(int, int, int, int) { 18247 } 18248 Size textSize(in char[] txt) { 18249 auto font = getFont(); 18250 return Size(font.stringWidth(txt), font.height()); 18251 } 18252 18253 void setFont(OperatingSystemFont font) { 18254 _font = font; 18255 //font.font.setInContext(context); 18256 } 18257 int fontHeight() { 18258 auto font = getFont(); 18259 return font.height; 18260 } 18261 18262 // end 18263 18264 void pen(Pen pen) { 18265 _activePen = pen; 18266 auto color = pen.color; // FIXME 18267 double alphaComponent = color.a/255.0f; 18268 CGContextSetRGBStrokeColor(context, color.r/255.0f, color.g/255.0f, color.b/255.0f, alphaComponent); 18269 18270 double[2] patternBuffer; 18271 double[] pattern; 18272 final switch(pen.style) { 18273 case Pen.Style.Solid: 18274 pattern = null; 18275 break; 18276 case Pen.Style.Dashed: 18277 patternBuffer[0] = 4; 18278 patternBuffer[1] = 1; 18279 pattern = patternBuffer[]; 18280 break; 18281 case Pen.Style.Dotted: 18282 patternBuffer[0] = 1; 18283 patternBuffer[1] = 1; 18284 pattern = patternBuffer[]; 18285 break; 18286 } 18287 18288 CGContextSetLineDash(context, 0, pattern.ptr, pattern.length); 18289 18290 if (color.a != 255) { 18291 _outlineComponents[0] = cast(ubyte)(color.r*color.a/255); 18292 _outlineComponents[1] = cast(ubyte)(color.g*color.a/255); 18293 _outlineComponents[2] = cast(ubyte)(color.b*color.a/255); 18294 _outlineComponents[3] = color.a; 18295 } else { 18296 _outlineComponents[0] = color.r; 18297 _outlineComponents[1] = color.g; 18298 _outlineComponents[2] = color.b; 18299 _outlineComponents[3] = color.a; 18300 } 18301 } 18302 18303 @property void fillColor(Color color) { 18304 CGContextSetRGBFillColor(context, color.r/255.0f, color.g/255.0f, color.b/255.0f, color.a/255.0f); 18305 } 18306 18307 void drawImage(int x, int y, Image image, int ulx, int upy, int width, int height) { 18308 // NotYetImplementedException for upper left/width/height 18309 auto cgImage = CGBitmapContextCreateImage(image.context); 18310 auto size = CGSize(CGBitmapContextGetWidth(image.context), CGBitmapContextGetHeight(image.context)); 18311 CGContextDrawImage(context, CGRect(CGPoint(x, y), size), cgImage); 18312 CGImageRelease(cgImage); 18313 } 18314 18315 void drawPixmap(Sprite s, int x, int y, int ix, int iy, int w, int h) { 18316 // FIXME: is this efficient? 18317 auto cgImage = CGBitmapContextCreateImage(s.handle); 18318 auto size = CGSize(CGBitmapContextGetWidth(s.handle), CGBitmapContextGetHeight(s.handle)); 18319 CGContextDrawImage(context, CGRect(CGPoint(x, y), size), cgImage); 18320 CGImageRelease(cgImage); 18321 } 18322 18323 18324 void drawText(int x, int y, int x2, int y2, in char[] text, uint alignment) { 18325 // FIXME: alignment 18326 if (_outlineComponents[3] != 0) { 18327 CGContextSaveGState(context); 18328 auto invAlpha = 1.0f/_outlineComponents[3]; 18329 CGContextSetRGBFillColor(context, _outlineComponents[0]*invAlpha, 18330 _outlineComponents[1]*invAlpha, 18331 _outlineComponents[2]*invAlpha, 18332 _outlineComponents[3]/255.0f); 18333 18334 18335 18336 // FIXME: should we clip it to the bounding box? 18337 int textHeight = fontHeight; 18338 18339 auto lines = text.split('\n'); 18340 18341 const lineHeight = textHeight; 18342 textHeight *= lines.length; 18343 18344 int cy = y; 18345 18346 if(alignment & TextAlignment.VerticalBottom) { 18347 if(y2 <= 0) 18348 return; 18349 auto h = y2 - y; 18350 if(h > textHeight) { 18351 cy += h - textHeight; 18352 cy -= lineHeight / 2; 18353 } 18354 } else if(alignment & TextAlignment.VerticalCenter) { 18355 if(y2 <= 0) 18356 return; 18357 auto h = y2 - y; 18358 if(textHeight < h) { 18359 cy += (h - textHeight) / 2; 18360 //cy -= lineHeight / 4; 18361 } 18362 } 18363 18364 foreach(line; text.split('\n')) { 18365 int textWidth = this.textSize(line).width; 18366 18367 int px = x, py = cy; 18368 18369 if(alignment & TextAlignment.Center) { 18370 if(x2 <= 0) 18371 return; 18372 auto w = x2 - x; 18373 if(w > textWidth) 18374 px += (w - textWidth) / 2; 18375 } else if(alignment & TextAlignment.Right) { 18376 if(x2 <= 0) 18377 return; 18378 auto pos = x2 - textWidth; 18379 if(pos > x) 18380 px = pos; 18381 } 18382 18383 CGContextShowTextAtPoint(context, px, py + getFont.ascent /* this is cuz this picks baseline but i want bounding box */, line.ptr, line.length); 18384 18385 carry_on: 18386 cy += lineHeight + 4; 18387 } 18388 18389 // auto cfstr = cast(NSid)createCFString(text); 18390 // objc_msgSend(cfstr, sel_registerName("drawAtPoint:withAttributes:"), 18391 // NSPoint(x, y), null); 18392 // CFRelease(cfstr); 18393 CGContextRestoreGState(context); 18394 } 18395 } 18396 18397 void drawPixel(int x, int y) { 18398 auto rawData = CGBitmapContextGetData(context); 18399 auto width = CGBitmapContextGetWidth(context); 18400 auto height = CGBitmapContextGetHeight(context); 18401 auto offset = ((height - y - 1) * width + x) * 4; 18402 rawData[offset .. offset+4] = _outlineComponents; 18403 } 18404 18405 void drawLine(int x1, int y1, int x2, int y2) { 18406 CGPoint[2] linePoints; 18407 linePoints[0] = CGPoint(x1, y1); 18408 linePoints[1] = CGPoint(x2, y2); 18409 CGContextStrokeLineSegments(context, linePoints.ptr, linePoints.length); 18410 } 18411 18412 void drawRectangle(int x, int y, int width, int height) { 18413 CGContextBeginPath(context); 18414 auto rect = CGRect(CGPoint(x, y), CGSize(width, height)); 18415 CGContextAddRect(context, rect); 18416 CGContextDrawPath(context, CGPathDrawingMode.kCGPathFillStroke); 18417 } 18418 18419 void drawEllipse(int x1, int y1, int x2, int y2) { 18420 CGContextBeginPath(context); 18421 auto rect = CGRect(CGPoint(x1, y1), CGSize(x2-x1, y2-y1)); 18422 CGContextAddEllipseInRect(context, rect); 18423 CGContextDrawPath(context, CGPathDrawingMode.kCGPathFillStroke); 18424 } 18425 18426 void drawArc(int x1, int y1, int width, int height, int start, int finish) { 18427 // @@@BUG@@@ Does not support elliptic arc (width != height). 18428 CGContextBeginPath(context); 18429 CGContextAddArc(context, x1+width*0.5f, y1+height*0.5f, width, 18430 start*PI/(180*64), finish*PI/(180*64), 0); 18431 CGContextDrawPath(context, CGPathDrawingMode.kCGPathFillStroke); 18432 } 18433 18434 void drawPolygon(Point[] intPoints) { 18435 CGContextBeginPath(context); 18436 CGPoint[16] pointsBuffer; 18437 CGPoint[] points; 18438 if(intPoints.length <= pointsBuffer.length) 18439 points = pointsBuffer[0 .. intPoints.length]; 18440 else 18441 points = new CGPoint[](intPoints.length); 18442 18443 foreach(idx, pt; intPoints) 18444 points[idx] = CGPoint(pt.x, pt.y); 18445 18446 CGContextAddLines(context, points.ptr, points.length); 18447 CGContextDrawPath(context, CGPathDrawingMode.kCGPathFillStroke); 18448 } 18449 } 18450 18451 private bool appInitialized = false; 18452 void initializeApp() { 18453 if(appInitialized) 18454 return; 18455 synchronized { 18456 if(appInitialized) 18457 return; 18458 18459 auto app = NSApp(); // ensure the is initialized 18460 18461 auto dg = AppDelegate.alloc; 18462 globalAppDelegate = dg; 18463 NSApp.delegate_ = dg; 18464 18465 NSApp.setActivationPolicy(NSApplicationActivationPolicy.regular); 18466 18467 appInitialized = true; 18468 } 18469 } 18470 18471 mixin template NativeSimpleWindowImplementation() { 18472 void createWindow(int width, int height, string title, OpenGlOptions opengl, SimpleWindow parent) { 18473 initializeApp(); 18474 18475 auto contentRect = NSRect(NSPoint(0, 0), NSSize(width, height)); 18476 18477 auto window = NSWindow.alloc.initWithContentRect( 18478 contentRect, 18479 NSWindowStyleMask.resizable | NSWindowStyleMask.closable | NSWindowStyleMask.miniaturizable | NSWindowStyleMask.titled, 18480 NSBackingStoreType.buffered, 18481 true 18482 ); 18483 18484 SimpleWindow.nativeMapping[cast(void*) window] = this; 18485 18486 window.title = MacString(title).borrow; 18487 18488 auto dg = SDWindowDelegate.alloc.init; 18489 dg.simpleWindow = this; 18490 window.delegate_ = dg; 18491 18492 auto view = SDGraphicsView.alloc.init; 18493 assert(view !is null); 18494 window.contentView = view; 18495 this.view = view; 18496 view.simpleWindow = this; 18497 18498 window.center(); 18499 18500 window.makeKeyAndOrderFront(null); 18501 18502 // no need to make a bitmap on mac since everything is double buffered already 18503 18504 // create area to draw on. 18505 createNewDrawingContext(width, height); 18506 18507 window.setBackgroundColor(NSColor.whiteColor); 18508 } 18509 18510 void createNewDrawingContext(int width, int height) { 18511 // FIXME need to preserve info from the old context too i think... maybe. or at least setNeedsDisplay 18512 if(this.drawingContext) 18513 CGContextRelease(this.drawingContext); 18514 auto colorSpace = CGColorSpaceCreateDeviceRGB(); 18515 this.drawingContext = CGBitmapContextCreate(null, width, height, 8, 4*width, colorSpace, kCGImageAlphaPremultipliedLast |kCGBitmapByteOrder32Big); 18516 CGColorSpaceRelease(colorSpace); 18517 CGContextSelectFont(drawingContext, "Lucida Grande", 12.0f, 1); 18518 auto matrix = CGContextGetTextMatrix(drawingContext); 18519 matrix.c = -matrix.c; 18520 matrix.d = -matrix.d; 18521 CGContextSetTextMatrix(drawingContext, matrix); 18522 18523 } 18524 18525 void dispose() { 18526 closeWindow(); 18527 // window.release(); // closing the window does this automatically i think 18528 } 18529 void closeWindow() { 18530 if(timer) 18531 timer.invalidate(); 18532 window.close(); 18533 } 18534 18535 ScreenPainter getPainter(bool manualInvalidations) { 18536 return ScreenPainter(this, this.window, manualInvalidations); 18537 } 18538 18539 NSWindow window; 18540 NSTimer timer; 18541 NSView view; 18542 CGContextRef drawingContext; 18543 } 18544 } 18545 18546 version(without_opengl) {} else 18547 extern(System) nothrow @nogc { 18548 //enum uint GL_VERSION = 0x1F02; 18549 //const(char)* glGetString (/*GLenum*/uint); 18550 version(X11) { 18551 static if (!SdpyIsUsingIVGLBinds) { 18552 18553 enum GLX_X_RENDERABLE = 0x8012; 18554 enum GLX_DRAWABLE_TYPE = 0x8010; 18555 enum GLX_RENDER_TYPE = 0x8011; 18556 enum GLX_X_VISUAL_TYPE = 0x22; 18557 enum GLX_TRUE_COLOR = 0x8002; 18558 enum GLX_WINDOW_BIT = 0x00000001; 18559 enum GLX_RGBA_BIT = 0x00000001; 18560 enum GLX_COLOR_INDEX_BIT = 0x00000002; 18561 enum GLX_SAMPLE_BUFFERS = 0x186a0; 18562 enum GLX_SAMPLES = 0x186a1; 18563 enum GLX_CONTEXT_MAJOR_VERSION_ARB = 0x2091; 18564 enum GLX_CONTEXT_MINOR_VERSION_ARB = 0x2092; 18565 } 18566 18567 // GLX_EXT_swap_control 18568 alias glXSwapIntervalEXT = void function (Display* dpy, /*GLXDrawable*/Drawable drawable, int interval); 18569 private __gshared glXSwapIntervalEXT _glx_swapInterval_fn = null; 18570 18571 //k8: ugly code to prevent warnings when sdpy is compiled into .a 18572 extern(System) { 18573 alias glXCreateContextAttribsARB_fna = GLXContext function (Display *dpy, GLXFBConfig config, GLXContext share_context, /*Bool*/int direct, const(int)* attrib_list); 18574 } 18575 private __gshared /*glXCreateContextAttribsARB_fna*/void* glXCreateContextAttribsARBFn = cast(void*)1; //HACK! 18576 18577 // this made public so we don't have to get it again and again 18578 public bool glXCreateContextAttribsARB_present () { 18579 if (glXCreateContextAttribsARBFn is cast(void*)1) { 18580 // get it 18581 glXCreateContextAttribsARBFn = cast(void*)glbindGetProcAddress("glXCreateContextAttribsARB"); 18582 //{ import core.stdc.stdio; printf("checking glXCreateContextAttribsARB: %shere\n", (glXCreateContextAttribsARBFn !is null ? "".ptr : "not ".ptr)); } 18583 } 18584 return (glXCreateContextAttribsARBFn !is null); 18585 } 18586 18587 // this made public so we don't have to get it again and again 18588 public GLXContext glXCreateContextAttribsARB (Display *dpy, GLXFBConfig config, GLXContext share_context, /*Bool*/int direct, const(int)* attrib_list) { 18589 if (!glXCreateContextAttribsARB_present()) assert(0, "glXCreateContextAttribsARB is not present"); 18590 return (cast(glXCreateContextAttribsARB_fna)glXCreateContextAttribsARBFn)(dpy, config, share_context, direct, attrib_list); 18591 } 18592 18593 // extern(C) private __gshared int function(int) glXSwapIntervalSGI; // seems totally redundant to the tohers 18594 extern(C) private __gshared int function(int) glXSwapIntervalMESA; 18595 18596 void glxSetVSync (Display* dpy, /*GLXDrawable*/Drawable drawable, bool wait) { 18597 if (cast(void*)_glx_swapInterval_fn is cast(void*)1) return; 18598 if (_glx_swapInterval_fn is null) { 18599 _glx_swapInterval_fn = cast(glXSwapIntervalEXT)glXGetProcAddress("glXSwapIntervalEXT"); 18600 if (_glx_swapInterval_fn is null) { 18601 _glx_swapInterval_fn = cast(glXSwapIntervalEXT)1; 18602 return; 18603 } 18604 version(sdddd) { debug writeln("glXSwapIntervalEXT found!"); } 18605 } 18606 18607 if(glXSwapIntervalMESA is null) { 18608 // it seems to require both to actually take effect on many computers 18609 // idk why 18610 glXSwapIntervalMESA = cast(typeof(glXSwapIntervalMESA)) glXGetProcAddress("glXSwapIntervalMESA"); 18611 if(glXSwapIntervalMESA is null) 18612 glXSwapIntervalMESA = cast(typeof(glXSwapIntervalMESA)) 1; 18613 } 18614 18615 if(cast(void*) glXSwapIntervalMESA > cast(void*) 1) 18616 glXSwapIntervalMESA(wait ? 1 : 0); 18617 18618 _glx_swapInterval_fn(dpy, drawable, (wait ? 1 : 0)); 18619 } 18620 } else version(Windows) { 18621 static if (!SdpyIsUsingIVGLBinds) { 18622 enum GL_TRUE = 1; 18623 enum GL_FALSE = 0; 18624 18625 public void* glbindGetProcAddress (const(char)* name) { 18626 void* res = wglGetProcAddress(name); 18627 if (res is null) { 18628 /+ 18629 //{ import core.stdc.stdio; printf("GL: '%s' not found (0)\n", name); } 18630 import core.sys.windows.windef, core.sys.windows.winbase; 18631 __gshared HINSTANCE dll = null; 18632 if (dll is null) { 18633 dll = LoadLibraryA("opengl32.dll"); 18634 if (dll is null) return null; // <32, but idc 18635 } 18636 res = GetProcAddress(dll, name); 18637 +/ 18638 res = GetProcAddress(gl.libHandle, name); 18639 } 18640 //{ import core.stdc.stdio; printf(" GL: '%s' is 0x%08x\n", name, cast(uint)res); } 18641 return res; 18642 } 18643 } 18644 18645 18646 private __gshared extern(System) BOOL function(int) wglSwapIntervalEXT; 18647 void wglSetVSync(bool wait) { 18648 if(wglSwapIntervalEXT is null) { 18649 wglSwapIntervalEXT = cast(typeof(wglSwapIntervalEXT)) wglGetProcAddress("wglSwapIntervalEXT"); 18650 if(wglSwapIntervalEXT is null) 18651 wglSwapIntervalEXT = cast(typeof(wglSwapIntervalEXT)) 1; 18652 } 18653 if(cast(void*) wglSwapIntervalEXT is cast(void*) 1) 18654 return; 18655 18656 wglSwapIntervalEXT(wait ? 1 : 0); 18657 } 18658 18659 enum WGL_CONTEXT_MAJOR_VERSION_ARB = 0x2091; 18660 enum WGL_CONTEXT_MINOR_VERSION_ARB = 0x2092; 18661 enum WGL_CONTEXT_LAYER_PLANE_ARB = 0x2093; 18662 enum WGL_CONTEXT_FLAGS_ARB = 0x2094; 18663 enum WGL_CONTEXT_PROFILE_MASK_ARB = 0x9126; 18664 18665 enum WGL_CONTEXT_DEBUG_BIT_ARB = 0x0001; 18666 enum WGL_CONTEXT_FORWARD_COMPATIBLE_BIT_ARB = 0x0002; 18667 18668 enum WGL_CONTEXT_CORE_PROFILE_BIT_ARB = 0x00000001; 18669 enum WGL_CONTEXT_COMPATIBILITY_PROFILE_BIT_ARB = 0x00000002; 18670 18671 alias wglCreateContextAttribsARB_fna = HGLRC function (HDC hDC, HGLRC hShareContext, const(int)* attribList); 18672 __gshared wglCreateContextAttribsARB_fna wglCreateContextAttribsARB = null; 18673 18674 void wglInitOtherFunctions () { 18675 if (wglCreateContextAttribsARB is null) { 18676 wglCreateContextAttribsARB = cast(wglCreateContextAttribsARB_fna)glbindGetProcAddress("wglCreateContextAttribsARB"); 18677 } 18678 } 18679 } 18680 18681 static if (!SdpyIsUsingIVGLBinds) { 18682 18683 interface GL { 18684 extern(System) @nogc nothrow: 18685 18686 void glGetIntegerv(int, void*); 18687 void glMatrixMode(int); 18688 void glPushMatrix(); 18689 void glLoadIdentity(); 18690 void glOrtho(double, double, double, double, double, double); 18691 void glFrustum(double, double, double, double, double, double); 18692 18693 void glPopMatrix(); 18694 void glEnable(int); 18695 void glDisable(int); 18696 void glClear(int); 18697 void glBegin(int); 18698 void glVertex2f(float, float); 18699 void glVertex3f(float, float, float); 18700 void glEnd(); 18701 void glColor3b(byte, byte, byte); 18702 void glColor3ub(ubyte, ubyte, ubyte); 18703 void glColor4b(byte, byte, byte, byte); 18704 void glColor4ub(ubyte, ubyte, ubyte, ubyte); 18705 void glColor3i(int, int, int); 18706 void glColor3ui(uint, uint, uint); 18707 void glColor4i(int, int, int, int); 18708 void glColor4ui(uint, uint, uint, uint); 18709 void glColor3f(float, float, float); 18710 void glColor4f(float, float, float, float); 18711 void glTranslatef(float, float, float); 18712 void glScalef(float, float, float); 18713 version(X11) { 18714 void glSecondaryColor3b(byte, byte, byte); 18715 void glSecondaryColor3ub(ubyte, ubyte, ubyte); 18716 void glSecondaryColor3i(int, int, int); 18717 void glSecondaryColor3ui(uint, uint, uint); 18718 void glSecondaryColor3f(float, float, float); 18719 } 18720 18721 void glDrawElements(int, int, int, void*); 18722 18723 void glRotatef(float, float, float, float); 18724 18725 uint glGetError(); 18726 18727 void glDeleteTextures(int, uint*); 18728 18729 18730 void glRasterPos2i(int, int); 18731 void glDrawPixels(int, int, uint, uint, void*); 18732 void glClearColor(float, float, float, float); 18733 18734 18735 void glPixelStorei(uint, int); 18736 18737 void glGenTextures(uint, uint*); 18738 void glBindTexture(int, int); 18739 void glTexParameteri(uint, uint, int); 18740 void glTexParameterf(uint/*GLenum*/ target, uint/*GLenum*/ pname, float param); 18741 void glTexImage2D(int, int, int, int, int, int, int, int, scope const void*); 18742 void glTexSubImage2D(uint/*GLenum*/ target, int level, int xoffset, int yoffset, 18743 /*GLsizei*/int width, /*GLsizei*/int height, 18744 uint/*GLenum*/ format, uint/*GLenum*/ type, scope const void* pixels); 18745 void glTexEnvf(uint/*GLenum*/ target, uint/*GLenum*/ pname, float param); 18746 18747 void glLineWidth(int); 18748 18749 18750 void glTexCoord2f(float, float); 18751 void glVertex2i(int, int); 18752 void glBlendFunc (int, int); 18753 void glDepthFunc (int); 18754 void glViewport(int, int, int, int); 18755 18756 void glClearDepth(double); 18757 18758 void glReadBuffer(uint); 18759 void glReadPixels(int, int, int, int, int, int, void*); 18760 18761 void glScissor(GLint x, GLint y, GLsizei width, GLsizei height); 18762 18763 void glFlush(); 18764 void glFinish(); 18765 18766 version(Windows) { 18767 BOOL wglCopyContext(HGLRC, HGLRC, UINT); 18768 HGLRC wglCreateContext(HDC); 18769 HGLRC wglCreateLayerContext(HDC, int); 18770 BOOL wglDeleteContext(HGLRC); 18771 BOOL wglDescribeLayerPlane(HDC, int, int, UINT, LPLAYERPLANEDESCRIPTOR); 18772 HGLRC wglGetCurrentContext(); 18773 HDC wglGetCurrentDC(); 18774 int wglGetLayerPaletteEntries(HDC, int, int, int, COLORREF*); 18775 PROC wglGetProcAddress(LPCSTR); 18776 BOOL wglMakeCurrent(HDC, HGLRC); 18777 BOOL wglRealizeLayerPalette(HDC, int, BOOL); 18778 int wglSetLayerPaletteEntries(HDC, int, int, int, const(COLORREF)*); 18779 BOOL wglShareLists(HGLRC, HGLRC); 18780 BOOL wglSwapLayerBuffers(HDC, UINT); 18781 BOOL wglUseFontBitmapsA(HDC, DWORD, DWORD, DWORD); 18782 BOOL wglUseFontBitmapsW(HDC, DWORD, DWORD, DWORD); 18783 BOOL wglUseFontOutlinesA(HDC, DWORD, DWORD, DWORD, FLOAT, FLOAT, int, LPGLYPHMETRICSFLOAT); 18784 BOOL wglUseFontOutlinesW(HDC, DWORD, DWORD, DWORD, FLOAT, FLOAT, int, LPGLYPHMETRICSFLOAT); 18785 } 18786 18787 } 18788 18789 interface GL3 { 18790 extern(System) @nogc nothrow: 18791 18792 void glGenVertexArrays(GLsizei, GLuint*); 18793 void glBindVertexArray(GLuint); 18794 void glDeleteVertexArrays(GLsizei, const(GLuint)*); 18795 void glGenerateMipmap(GLenum); 18796 void glBufferSubData(GLenum, GLintptr, GLsizeiptr, const(GLvoid)*); 18797 void glStencilMask(GLuint); 18798 void glStencilFunc(GLenum, GLint, GLuint); 18799 void glGetShaderInfoLog(GLuint, GLsizei, GLsizei*, GLchar*); 18800 void glGetProgramInfoLog(GLuint, GLsizei, GLsizei*, GLchar*); 18801 GLuint glCreateProgram(); 18802 GLuint glCreateShader(GLenum); 18803 void glShaderSource(GLuint, GLsizei, const(GLchar*)*, const(GLint)*); 18804 void glCompileShader(GLuint); 18805 void glGetShaderiv(GLuint, GLenum, GLint*); 18806 void glAttachShader(GLuint, GLuint); 18807 void glBindAttribLocation(GLuint, GLuint, const(GLchar)*); 18808 void glLinkProgram(GLuint); 18809 void glGetProgramiv(GLuint, GLenum, GLint*); 18810 void glDeleteProgram(GLuint); 18811 void glDeleteShader(GLuint); 18812 GLint glGetUniformLocation(GLuint, const(GLchar)*); 18813 void glGenBuffers(GLsizei, GLuint*); 18814 18815 void glUniform1f(GLint location, GLfloat v0); 18816 void glUniform2f(GLint location, GLfloat v0, GLfloat v1); 18817 void glUniform3f(GLint location, GLfloat v0, GLfloat v1, GLfloat v2); 18818 void glUniform4f(GLint location, GLfloat v0, GLfloat v1, GLfloat v2, GLfloat v3); 18819 void glUniform1i(GLint location, GLint v0); 18820 void glUniform2i(GLint location, GLint v0, GLint v1); 18821 void glUniform3i(GLint location, GLint v0, GLint v1, GLint v2); 18822 void glUniform4i(GLint location, GLint v0, GLint v1, GLint v2, GLint v3); 18823 void glUniform1ui(GLint location, GLuint v0); 18824 void glUniform2ui(GLint location, GLuint v0, GLuint v1); 18825 void glUniform3ui(GLint location, GLuint v0, GLuint v1, GLuint v2); 18826 void glUniform4ui(GLint location, GLuint v0, GLuint v1, GLuint v2, GLuint v3); 18827 void glUniform1fv(GLint location, GLsizei count, const GLfloat *value); 18828 void glUniform2fv(GLint location, GLsizei count, const GLfloat *value); 18829 void glUniform3fv(GLint location, GLsizei count, const GLfloat *value); 18830 void glUniform4fv(GLint location, GLsizei count, const GLfloat *value); 18831 void glUniform1iv(GLint location, GLsizei count, const GLint *value); 18832 void glUniform2iv(GLint location, GLsizei count, const GLint *value); 18833 void glUniform3iv(GLint location, GLsizei count, const GLint *value); 18834 void glUniform4iv(GLint location, GLsizei count, const GLint *value); 18835 void glUniform1uiv(GLint location, GLsizei count, const GLuint *value); 18836 void glUniform2uiv(GLint location, GLsizei count, const GLuint *value); 18837 void glUniform3uiv(GLint location, GLsizei count, const GLuint *value); 18838 void glUniform4uiv(GLint location, GLsizei count, const GLuint *value); 18839 void glUniformMatrix2fv(GLint location, GLsizei count, GLboolean transpose, const GLfloat *value); 18840 void glUniformMatrix3fv(GLint location, GLsizei count, GLboolean transpose, const GLfloat *value); 18841 void glUniformMatrix4fv(GLint location, GLsizei count, GLboolean transpose, const GLfloat *value); 18842 void glUniformMatrix2x3fv(GLint location, GLsizei count, GLboolean transpose, const GLfloat *value); 18843 void glUniformMatrix3x2fv(GLint location, GLsizei count, GLboolean transpose, const GLfloat *value); 18844 void glUniformMatrix2x4fv(GLint location, GLsizei count, GLboolean transpose, const GLfloat *value); 18845 void glUniformMatrix4x2fv(GLint location, GLsizei count, GLboolean transpose, const GLfloat *value); 18846 void glUniformMatrix3x4fv(GLint location, GLsizei count, GLboolean transpose, const GLfloat *value); 18847 void glUniformMatrix4x3fv(GLint location, GLsizei count, GLboolean transpose, const GLfloat *value); 18848 18849 void glColorMask(GLboolean, GLboolean, GLboolean, GLboolean); 18850 void glStencilOpSeparate(GLenum, GLenum, GLenum, GLenum); 18851 void glDrawArrays(GLenum, GLint, GLsizei); 18852 void glStencilOp(GLenum, GLenum, GLenum); 18853 void glUseProgram(GLuint); 18854 void glCullFace(GLenum); 18855 void glFrontFace(GLenum); 18856 void glActiveTexture(GLenum); 18857 void glBindBuffer(GLenum, GLuint); 18858 void glBufferData(GLenum, GLsizeiptr, const(void)*, GLenum); 18859 void glEnableVertexAttribArray(GLuint); 18860 void glVertexAttribPointer(GLuint, GLint, GLenum, GLboolean, GLsizei, const(void)*); 18861 void glUniform1i(GLint, GLint); 18862 void glUniform2fv(GLint, GLsizei, const(GLfloat)*); 18863 void glDisableVertexAttribArray(GLuint); 18864 void glDeleteBuffers(GLsizei, const(GLuint)*); 18865 void glBlendFuncSeparate(GLenum, GLenum, GLenum, GLenum); 18866 void glLogicOp (GLenum opcode); 18867 void glFramebufferTexture2D (GLenum target, GLenum attachment, GLenum textarget, GLuint texture, GLint level); 18868 void glDeleteFramebuffers (GLsizei n, const(GLuint)* framebuffers); 18869 void glGenFramebuffers (GLsizei n, GLuint* framebuffers); 18870 GLenum glCheckFramebufferStatus (GLenum target); 18871 void glBindFramebuffer (GLenum target, GLuint framebuffer); 18872 } 18873 18874 interface GL4 { 18875 extern(System) @nogc nothrow: 18876 18877 void glTextureSubImage2D(uint texture, int level, int xoffset, int yoffset, 18878 /*GLsizei*/int width, /*GLsizei*/int height, 18879 uint/*GLenum*/ format, uint/*GLenum*/ type, scope const void* pixels); 18880 } 18881 18882 interface GLU { 18883 extern(System) @nogc nothrow: 18884 18885 void gluLookAt(double, double, double, double, double, double, double, double, double); 18886 void gluPerspective(double, double, double, double); 18887 18888 char* gluErrorString(uint); 18889 } 18890 18891 18892 enum GL_RED = 0x1903; 18893 enum GL_ALPHA = 0x1906; 18894 18895 enum uint GL_FRONT = 0x0404; 18896 18897 enum uint GL_BLEND = 0x0be2; 18898 enum uint GL_LEQUAL = 0x0203; 18899 18900 18901 enum uint GL_RGB = 0x1907; 18902 enum uint GL_BGRA = 0x80e1; 18903 enum uint GL_RGBA = 0x1908; 18904 enum uint GL_RGBA8 = 0x8058; 18905 enum uint GL_TEXTURE_2D = 0x0DE1; 18906 enum uint GL_TEXTURE_MIN_FILTER = 0x2801; 18907 enum uint GL_NEAREST = 0x2600; 18908 enum uint GL_LINEAR = 0x2601; 18909 enum uint GL_TEXTURE_MAG_FILTER = 0x2800; 18910 enum uint GL_TEXTURE_WRAP_S = 0x2802; 18911 enum uint GL_TEXTURE_WRAP_T = 0x2803; 18912 enum uint GL_REPEAT = 0x2901; 18913 enum uint GL_CLAMP = 0x2900; 18914 enum uint GL_CLAMP_TO_EDGE = 0x812F; 18915 enum uint GL_CLAMP_TO_BORDER = 0x812D; 18916 enum uint GL_DECAL = 0x2101; 18917 enum uint GL_MODULATE = 0x2100; 18918 enum uint GL_TEXTURE_ENV = 0x2300; 18919 enum uint GL_TEXTURE_ENV_MODE = 0x2200; 18920 enum uint GL_REPLACE = 0x1E01; 18921 enum uint GL_LIGHTING = 0x0B50; 18922 enum uint GL_DITHER = 0x0BD0; 18923 18924 enum uint GL_NO_ERROR = 0; 18925 18926 18927 18928 enum int GL_VIEWPORT = 0x0BA2; 18929 enum int GL_MODELVIEW = 0x1700; 18930 enum int GL_TEXTURE = 0x1702; 18931 enum int GL_PROJECTION = 0x1701; 18932 enum int GL_DEPTH_TEST = 0x0B71; 18933 18934 enum int GL_COLOR_BUFFER_BIT = 0x00004000; 18935 enum int GL_ACCUM_BUFFER_BIT = 0x00000200; 18936 enum int GL_DEPTH_BUFFER_BIT = 0x00000100; 18937 enum uint GL_STENCIL_BUFFER_BIT = 0x00000400; 18938 18939 enum int GL_POINTS = 0x0000; 18940 enum int GL_LINES = 0x0001; 18941 enum int GL_LINE_LOOP = 0x0002; 18942 enum int GL_LINE_STRIP = 0x0003; 18943 enum int GL_TRIANGLES = 0x0004; 18944 enum int GL_TRIANGLE_STRIP = 5; 18945 enum int GL_TRIANGLE_FAN = 6; 18946 enum int GL_QUADS = 7; 18947 enum int GL_QUAD_STRIP = 8; 18948 enum int GL_POLYGON = 9; 18949 18950 alias GLvoid = void; 18951 alias GLboolean = ubyte; 18952 alias GLint = int; 18953 alias GLuint = uint; 18954 alias GLenum = uint; 18955 alias GLchar = char; 18956 alias GLsizei = int; 18957 alias GLfloat = float; 18958 alias GLintptr = size_t; 18959 alias GLsizeiptr = ptrdiff_t; 18960 18961 18962 enum uint GL_INVALID_ENUM = 0x0500; 18963 18964 enum uint GL_ZERO = 0; 18965 enum uint GL_ONE = 1; 18966 18967 enum uint GL_BYTE = 0x1400; 18968 enum uint GL_UNSIGNED_BYTE = 0x1401; 18969 enum uint GL_SHORT = 0x1402; 18970 enum uint GL_UNSIGNED_SHORT = 0x1403; 18971 enum uint GL_INT = 0x1404; 18972 enum uint GL_UNSIGNED_INT = 0x1405; 18973 enum uint GL_FLOAT = 0x1406; 18974 enum uint GL_2_BYTES = 0x1407; 18975 enum uint GL_3_BYTES = 0x1408; 18976 enum uint GL_4_BYTES = 0x1409; 18977 enum uint GL_DOUBLE = 0x140A; 18978 18979 enum uint GL_STREAM_DRAW = 0x88E0; 18980 18981 enum uint GL_CCW = 0x0901; 18982 18983 enum uint GL_STENCIL_TEST = 0x0B90; 18984 enum uint GL_SCISSOR_TEST = 0x0C11; 18985 18986 enum uint GL_EQUAL = 0x0202; 18987 enum uint GL_NOTEQUAL = 0x0205; 18988 18989 enum uint GL_ALWAYS = 0x0207; 18990 enum uint GL_KEEP = 0x1E00; 18991 18992 enum uint GL_INCR = 0x1E02; 18993 18994 enum uint GL_INCR_WRAP = 0x8507; 18995 enum uint GL_DECR_WRAP = 0x8508; 18996 18997 enum uint GL_CULL_FACE = 0x0B44; 18998 enum uint GL_BACK = 0x0405; 18999 19000 enum uint GL_FRAGMENT_SHADER = 0x8B30; 19001 enum uint GL_VERTEX_SHADER = 0x8B31; 19002 19003 enum uint GL_COMPILE_STATUS = 0x8B81; 19004 enum uint GL_LINK_STATUS = 0x8B82; 19005 19006 enum uint GL_ELEMENT_ARRAY_BUFFER = 0x8893; 19007 19008 enum uint GL_STATIC_DRAW = 0x88E4; 19009 19010 enum uint GL_UNPACK_ALIGNMENT = 0x0CF5; 19011 enum uint GL_UNPACK_ROW_LENGTH = 0x0CF2; 19012 enum uint GL_UNPACK_SKIP_PIXELS = 0x0CF4; 19013 enum uint GL_UNPACK_SKIP_ROWS = 0x0CF3; 19014 19015 enum uint GL_GENERATE_MIPMAP = 0x8191; 19016 enum uint GL_LINEAR_MIPMAP_LINEAR = 0x2703; 19017 19018 enum uint GL_TEXTURE0 = 0x84C0U; 19019 enum uint GL_TEXTURE1 = 0x84C1U; 19020 19021 enum uint GL_ARRAY_BUFFER = 0x8892; 19022 19023 enum uint GL_SRC_COLOR = 0x0300; 19024 enum uint GL_ONE_MINUS_SRC_COLOR = 0x0301; 19025 enum uint GL_SRC_ALPHA = 0x0302; 19026 enum uint GL_ONE_MINUS_SRC_ALPHA = 0x0303; 19027 enum uint GL_DST_ALPHA = 0x0304; 19028 enum uint GL_ONE_MINUS_DST_ALPHA = 0x0305; 19029 enum uint GL_DST_COLOR = 0x0306; 19030 enum uint GL_ONE_MINUS_DST_COLOR = 0x0307; 19031 enum uint GL_SRC_ALPHA_SATURATE = 0x0308; 19032 19033 enum uint GL_INVERT = 0x150AU; 19034 19035 enum uint GL_DEPTH_STENCIL = 0x84F9U; 19036 enum uint GL_UNSIGNED_INT_24_8 = 0x84FAU; 19037 19038 enum uint GL_FRAMEBUFFER = 0x8D40U; 19039 enum uint GL_COLOR_ATTACHMENT0 = 0x8CE0U; 19040 enum uint GL_DEPTH_STENCIL_ATTACHMENT = 0x821AU; 19041 19042 enum uint GL_FRAMEBUFFER_COMPLETE = 0x8CD5U; 19043 enum uint GL_FRAMEBUFFER_INCOMPLETE_ATTACHMENT = 0x8CD6U; 19044 enum uint GL_FRAMEBUFFER_INCOMPLETE_MISSING_ATTACHMENT = 0x8CD7U; 19045 enum uint GL_FRAMEBUFFER_INCOMPLETE_DIMENSIONS = 0x8CD9U; 19046 enum uint GL_FRAMEBUFFER_UNSUPPORTED = 0x8CDDU; 19047 19048 enum uint GL_COLOR_LOGIC_OP = 0x0BF2U; 19049 enum uint GL_CLEAR = 0x1500U; 19050 enum uint GL_COPY = 0x1503U; 19051 enum uint GL_XOR = 0x1506U; 19052 19053 enum uint GL_FRAMEBUFFER_BINDING = 0x8CA6U; 19054 19055 enum uint GL_TEXTURE_LOD_BIAS = 0x8501; 19056 19057 } 19058 } 19059 19060 /++ 19061 History: 19062 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. 19063 +/ 19064 __gshared bool gluSuccessfullyLoaded = true; 19065 19066 version(without_opengl) {} else { 19067 static if(!SdpyIsUsingIVGLBinds) { 19068 version(Windows) { 19069 mixin DynamicLoad!(GL, "opengl32", 1, openGlLibrariesSuccessfullyLoaded) gl; 19070 mixin DynamicLoad!(GLU, "glu32", 1, gluSuccessfullyLoaded) glu; 19071 } else { 19072 mixin DynamicLoad!(GL, "GL", 1, openGlLibrariesSuccessfullyLoaded) gl; 19073 mixin DynamicLoad!(GLU, "GLU", 3, gluSuccessfullyLoaded) glu; 19074 } 19075 mixin DynamicLoadSupplementalOpenGL!(GL3) gl3; 19076 19077 19078 shared static this() { 19079 gl.loadDynamicLibrary(); 19080 19081 // FIXME: this is NOT actually required and should NOT fail if it is not loaded 19082 // unless those functions are actually used 19083 // go to mark b openGlLibrariesSuccessfullyLoaded = false; 19084 glu.loadDynamicLibrary(); 19085 } 19086 } 19087 } 19088 19089 /++ 19090 Convenience method for converting D arrays to opengl buffer data 19091 19092 I would LOVE to overload it with the original glBufferData, but D won't 19093 let me since glBufferData is a function pointer :( 19094 19095 Added: August 25, 2020 (version 8.5) 19096 +/ 19097 version(without_opengl) {} else 19098 void glBufferDataSlice(GLenum target, const(void[]) data, GLenum usage) { 19099 glBufferData(target, data.length, data.ptr, usage); 19100 } 19101 19102 /+ 19103 /++ 19104 A matrix for simple uses that easily integrates with [OpenGlShader]. 19105 19106 Might not be useful to you since it only as some simple functions and 19107 probably isn't that fast. 19108 19109 Note it uses an inline static array for its storage, so copying it 19110 may be expensive. 19111 +/ 19112 struct BasicMatrix(int columns, int rows, T = float) { 19113 import core.stdc.math; 19114 19115 T[columns * rows] data = 0.0; 19116 19117 /++ 19118 Basic operations that operate *in place*. 19119 +/ 19120 void translate() { 19121 19122 } 19123 19124 /// ditto 19125 void scale() { 19126 19127 } 19128 19129 /// ditto 19130 void rotate() { 19131 19132 } 19133 19134 /++ 19135 19136 +/ 19137 static if(columns == rows) 19138 static BasicMatrix identity() { 19139 BasicMatrix m; 19140 foreach(i; 0 .. columns) 19141 data[0 + i + i * columns] = 1.0; 19142 return m; 19143 } 19144 19145 static BasicMatrix ortho() { 19146 return BasicMatrix.init; 19147 } 19148 } 19149 +/ 19150 19151 /++ 19152 Convenience class for using opengl shaders. 19153 19154 Ensure that you've loaded opengl 3+ and set your active 19155 context before trying to use this. 19156 19157 Added: August 25, 2020 (version 8.5) 19158 +/ 19159 version(without_opengl) {} else 19160 final class OpenGlShader { 19161 private int shaderProgram_; 19162 private @property void shaderProgram(int a) { 19163 shaderProgram_ = a; 19164 } 19165 /// Get the program ID for use in OpenGL functions. 19166 public @property int shaderProgram() { 19167 return shaderProgram_; 19168 } 19169 19170 /++ 19171 19172 +/ 19173 static struct Source { 19174 uint type; /// GL_FRAGMENT_SHADER, GL_VERTEX_SHADER, etc. 19175 string code; /// 19176 } 19177 19178 /++ 19179 Helper method to just compile some shader code and check for errors 19180 while you do glCreateShader, etc. on the outside yourself. 19181 19182 This just does `glShaderSource` and `glCompileShader` for the given code. 19183 19184 If you the OpenGlShader class constructor, you never need to call this yourself. 19185 +/ 19186 static void compile(int sid, Source code) { 19187 const(char)*[1] buffer; 19188 int[1] lengthBuffer; 19189 19190 buffer[0] = code.code.ptr; 19191 lengthBuffer[0] = cast(int) code.code.length; 19192 19193 glShaderSource(sid, cast(int) buffer.length, buffer.ptr, lengthBuffer.ptr); 19194 glCompileShader(sid); 19195 19196 int success; 19197 glGetShaderiv(sid, GL_COMPILE_STATUS, &success); 19198 if(!success) { 19199 char[512] info; 19200 int len; 19201 glGetShaderInfoLog(sid, info.length, &len, info.ptr); 19202 19203 throw new Exception("Shader compile failure: " ~ cast(immutable) info[0 .. len]); 19204 } 19205 } 19206 19207 /++ 19208 Calls `glLinkProgram` and throws if error a occurs. 19209 19210 If you the OpenGlShader class constructor, you never need to call this yourself. 19211 +/ 19212 static void link(int shaderProgram) { 19213 glLinkProgram(shaderProgram); 19214 int success; 19215 glGetProgramiv(shaderProgram, GL_LINK_STATUS, &success); 19216 if(!success) { 19217 char[512] info; 19218 int len; 19219 glGetProgramInfoLog(shaderProgram, info.length, &len, info.ptr); 19220 19221 throw new Exception("Shader link failure: " ~ cast(immutable) info[0 .. len]); 19222 } 19223 } 19224 19225 /++ 19226 Constructs the shader object by calling `glCreateProgram`, then 19227 compiling each given [Source], and finally, linking them together. 19228 19229 Throws: on compile or link failure. 19230 +/ 19231 this(Source[] codes...) { 19232 shaderProgram = glCreateProgram(); 19233 19234 int[16] shadersBufferStack; 19235 19236 int[] shadersBuffer = codes.length <= shadersBufferStack.length ? 19237 shadersBufferStack[0 .. codes.length] : 19238 new int[](codes.length); 19239 19240 foreach(idx, code; codes) { 19241 shadersBuffer[idx] = glCreateShader(code.type); 19242 19243 compile(shadersBuffer[idx], code); 19244 19245 glAttachShader(shaderProgram, shadersBuffer[idx]); 19246 } 19247 19248 link(shaderProgram); 19249 19250 foreach(s; shadersBuffer) 19251 glDeleteShader(s); 19252 } 19253 19254 /// Calls `glUseProgram(this.shaderProgram)` 19255 void use() { 19256 glUseProgram(this.shaderProgram); 19257 } 19258 19259 /// Deletes the program. 19260 void delete_() { 19261 glDeleteProgram(shaderProgram); 19262 shaderProgram = 0; 19263 } 19264 19265 /++ 19266 [OpenGlShader.uniforms].name gives you one of these. 19267 19268 You can get the id out of it or just assign 19269 +/ 19270 static struct Uniform { 19271 /// the id passed to glUniform* 19272 int id; 19273 19274 /// Assigns the 4 floats. You will probably have to call this via the .opAssign name 19275 void opAssign(float x, float y, float z, float w) { 19276 if(id != -1) 19277 glUniform4f(id, x, y, z, w); 19278 } 19279 19280 void opAssign(float x) { 19281 if(id != -1) 19282 glUniform1f(id, x); 19283 } 19284 19285 void opAssign(float x, float y) { 19286 if(id != -1) 19287 glUniform2f(id, x, y); 19288 } 19289 19290 void opAssign(T)(T t) { 19291 t.glUniform(id); 19292 } 19293 } 19294 19295 static struct UniformsHelper { 19296 OpenGlShader _shader; 19297 19298 @property Uniform opDispatch(string name)() { 19299 auto i = glGetUniformLocation(_shader.shaderProgram, name.ptr); 19300 // FIXME: decide what to do here; the exception is liable to be swallowed by the event syste 19301 //if(i == -1) 19302 //throw new Exception("Could not find uniform " ~ name); 19303 return Uniform(i); 19304 } 19305 19306 @property void opDispatch(string name, T)(T t) { 19307 Uniform f = this.opDispatch!name; 19308 t.glUniform(f); 19309 } 19310 } 19311 19312 /++ 19313 Gives access to the uniforms through dot access. 19314 `OpenGlShader.Uniform = shader.uniforms.foo; // calls glGetUniformLocation(this, "foo"); 19315 +/ 19316 @property UniformsHelper uniforms() { return UniformsHelper(this); } 19317 } 19318 19319 version(without_opengl) {} else { 19320 /++ 19321 A static container of experimental types and value constructors for opengl 3+ shaders. 19322 19323 19324 You can declare variables like: 19325 19326 ``` 19327 OGL.vec3f something; 19328 ``` 19329 19330 But generally it would be used with [OpenGlShader]'s uniform helpers like 19331 19332 ``` 19333 shader.uniforms.mouse = OGL.vec(mouseX, mouseY); // or OGL.vec2f if you want to be more specific 19334 ``` 19335 19336 This is still extremely experimental, not very useful at this point, and thus subject to change at random. 19337 19338 19339 History: 19340 Added December 7, 2021. Not yet stable. 19341 +/ 19342 final class OGL { 19343 static: 19344 19345 private template typeFromSpecifier(string specifier) { 19346 static if(specifier == "f") 19347 alias typeFromSpecifier = GLfloat; 19348 else static if(specifier == "i") 19349 alias typeFromSpecifier = GLint; 19350 else static if(specifier == "ui") 19351 alias typeFromSpecifier = GLuint; 19352 else static assert(0, "I don't know this ogl type suffix " ~ specifier); 19353 } 19354 19355 private template CommonType(T...) { 19356 static if(T.length == 1) 19357 alias CommonType = T[0]; 19358 else static if(is(typeof(true ? T[0].init : T[1].init) C)) 19359 alias CommonType = CommonType!(C, T[2 .. $]); 19360 } 19361 19362 private template typesToSpecifier(T...) { 19363 static if(is(CommonType!T == float)) 19364 enum typesToSpecifier = "f"; 19365 else static if(is(CommonType!T == int)) 19366 enum typesToSpecifier = "i"; 19367 else static if(is(CommonType!T == uint)) 19368 enum typesToSpecifier = "ui"; 19369 else static assert(0, "I can't find a gl type suffix for common type " ~ CommonType!T.stringof); 19370 } 19371 19372 private template genNames(size_t dim, size_t dim2 = 0) { 19373 string helper() { 19374 string s; 19375 if(dim2) { 19376 s ~= "type["~(dim + '0')~"]["~(dim2 + '0')~"] matrix;"; 19377 } else { 19378 if(dim > 0) s ~= "type x = 0;"; 19379 if(dim > 1) s ~= "type y = 0;"; 19380 if(dim > 2) s ~= "type z = 0;"; 19381 if(dim > 3) s ~= "type w = 0;"; 19382 } 19383 return s; 19384 } 19385 19386 enum genNames = helper(); 19387 } 19388 19389 // there's vec, arrays of vec, mat, and arrays of mat 19390 template opDispatch(string name) 19391 if(name.length > 4 && (name[0 .. 3] == "vec" || name[0 .. 3] == "mat")) 19392 { 19393 static if(name[4] == 'x') { 19394 enum dimX = cast(int) (name[3] - '0'); 19395 static assert(dimX > 0 && dimX <= 4, "Bad dimension for OGL X type " ~ name[3]); 19396 19397 enum dimY = cast(int) (name[5] - '0'); 19398 static assert(dimY > 0 && dimY <= 4, "Bad dimension for OGL Y type " ~ name[5]); 19399 19400 enum isArray = name[$ - 1] == 'v'; 19401 enum typeSpecifier = isArray ? name[6 .. $ - 1] : name[6 .. $]; 19402 alias type = typeFromSpecifier!typeSpecifier; 19403 } else { 19404 enum dim = cast(int) (name[3] - '0'); 19405 static assert(dim > 0 && dim <= 4, "Bad dimension for OGL type " ~ name[3]); 19406 enum isArray = name[$ - 1] == 'v'; 19407 enum typeSpecifier = isArray ? name[4 .. $ - 1] : name[4 .. $]; 19408 alias type = typeFromSpecifier!typeSpecifier; 19409 } 19410 19411 align(1) 19412 struct opDispatch { 19413 align(1): 19414 static if(name[4] == 'x') 19415 mixin(genNames!(dimX, dimY)); 19416 else 19417 mixin(genNames!dim); 19418 19419 private void glUniform(OpenGlShader.Uniform assignTo) { 19420 glUniform(assignTo.id); 19421 } 19422 private void glUniform(int assignTo) { 19423 static if(name[4] == 'x') { 19424 // FIXME 19425 pragma(msg, "This matrix uniform helper has never been tested!!!!"); 19426 mixin("glUniformMatrix" ~ name[3 .. $] ~ "v")(assignTo, dimX * dimY, false, this.matrix.ptr); 19427 } else 19428 mixin("glUniform" ~ name[3 .. $])(assignTo, this.tupleof); 19429 } 19430 } 19431 } 19432 19433 auto vec(T...)(T members) { 19434 return typeof(this).opDispatch!("vec" ~ toInternal!string(cast(int) T.length)~ typesToSpecifier!T)(members); 19435 } 19436 } 19437 } 19438 19439 version(linux) { 19440 version(with_eventloop) {} else { 19441 private int epollFd = -1; 19442 void prepareEventLoop() { 19443 if(epollFd != -1) 19444 return; // already initialized, no need to do it again 19445 import ep = core.sys.linux.epoll; 19446 19447 epollFd = ep.epoll_create1(ep.EPOLL_CLOEXEC); 19448 if(epollFd == -1) 19449 throw new Exception("epoll create failure"); 19450 } 19451 } 19452 } else version(Posix) { 19453 void prepareEventLoop() {} 19454 } 19455 19456 version(X11) { 19457 import core.stdc.locale : LC_ALL; // rdmd fix 19458 __gshared bool sdx_isUTF8Locale; 19459 19460 // This whole crap is used to initialize X11 locale, so that you can use XIM methods later. 19461 // Yes, there are people with non-utf locale (it's me, Ketmar!), but XIM (composing) will 19462 // not work right if app/X11 locale is not utf. This sux. That's why all that "utf detection" 19463 // anal magic is here. I (Ketmar) hope you like it. 19464 // We will use `sdx_isUTF8Locale` on XIM creation to enforce UTF-8 locale, so XCompose will 19465 // always return correct unicode symbols. The detection is here 'cause user can change locale 19466 // later. 19467 19468 // NOTE: IT IS VERY IMPORTANT THAT THIS BE THE LAST STATIC CTOR OF THE FILE since it tests librariesSuccessfullyLoaded 19469 shared static this () { 19470 if(!librariesSuccessfullyLoaded) 19471 return; 19472 19473 import core.stdc.locale : setlocale, LC_ALL, LC_CTYPE; 19474 19475 // this doesn't hurt; it may add some locking, but the speed is still 19476 // allows doing 60 FPS videogames; also, ignore the result, as most 19477 // users will probably won't do mulththreaded X11 anyway (and I (ketmar) 19478 // never seen this failing). 19479 if (XInitThreads() == 0) { import core.stdc.stdio; fprintf(stderr, "XInitThreads() failed!\n"); } 19480 19481 setlocale(LC_ALL, ""); 19482 // check if out locale is UTF-8 19483 auto lct = setlocale(LC_CTYPE, null); 19484 if (lct is null) { 19485 sdx_isUTF8Locale = false; 19486 } else { 19487 for (size_t idx = 0; lct[idx] && lct[idx+1] && lct[idx+2]; ++idx) { 19488 if ((lct[idx+0] == 'u' || lct[idx+0] == 'U') && 19489 (lct[idx+1] == 't' || lct[idx+1] == 'T') && 19490 (lct[idx+2] == 'f' || lct[idx+2] == 'F')) 19491 { 19492 sdx_isUTF8Locale = true; 19493 break; 19494 } 19495 } 19496 } 19497 //{ import core.stdc.stdio : stderr, fprintf; fprintf(stderr, "UTF8: %s\n", sdx_isUTF8Locale ? "tan".ptr : "ona".ptr); } 19498 } 19499 } 19500 19501 class ExperimentalTextComponent2 { 19502 /+ 19503 Stage 1: get it working monospace 19504 Stage 2: use proportional font 19505 Stage 3: allow changes in inline style 19506 Stage 4: allow new fonts and sizes in the middle 19507 Stage 5: optimize gap buffer 19508 Stage 6: optimize layout 19509 Stage 7: word wrap 19510 Stage 8: justification 19511 Stage 9: editing, selection, etc. 19512 19513 Operations: 19514 insert text 19515 overstrike text 19516 select 19517 cut 19518 modify 19519 +/ 19520 19521 /++ 19522 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. 19523 +/ 19524 this(SimpleWindow window) { 19525 this.window = window; 19526 } 19527 19528 private SimpleWindow window; 19529 19530 19531 /++ 19532 When you render a [ComponentInFlow], it returns an arbitrary number of these interfaces 19533 representing the internal parts. The first pass is focused on the x parameter, then the 19534 renderer is responsible for going back to the parts in the current line and calling 19535 adjustDownForAscent to change the y params. 19536 +/ 19537 static interface ComponentRenderHelper { 19538 19539 /+ 19540 When you do an edit, possibly stuff on the same line previously need to move (to adjust 19541 the baseline), stuff subsequent needs to move (adjust x) and possibly stuff below needs 19542 to move (adjust y to make room for new line) until you get back to the same position, 19543 then you can stop - if one thing is unchanged, nothing after it is changed too. 19544 19545 Word wrap might change this as if can rewrap tons of stuff, but the same idea applies, 19546 once you reach something that is unchanged, you can stop. 19547 +/ 19548 19549 void adjustDownForAscent(int amount); // at the end of the line it needs to do these 19550 19551 int ascent() const; 19552 int descent() const; 19553 19554 int advance() const; 19555 19556 bool endsWithExplititLineBreak() const; 19557 } 19558 19559 static interface RenderResult { 19560 /++ 19561 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. 19562 +/ 19563 void popFront(); 19564 @property bool empty() const; 19565 @property ComponentRenderHelper front() const; 19566 19567 void repositionForNextLine(Point baseline, int availableWidth); 19568 } 19569 19570 static interface ComponentInFlow { 19571 void draw(ScreenPainter painter); 19572 //RenderResult render(Point baseline, int availableWidth); // FIXME: it needs to be able to say "my cache is good, nothing different" 19573 19574 bool startsWithExplicitLineBreak() const; 19575 } 19576 19577 static class TextFlowComponent : ComponentInFlow { 19578 bool startsWithExplicitLineBreak() const { return false; } // FIXME: if it is block this can return true 19579 19580 Color foreground; 19581 Color background; 19582 19583 OperatingSystemFont font; // should NEVER be null 19584 19585 ubyte attributes; // underline, strike through, display on new block 19586 19587 version(Windows) 19588 const(wchar)[] content; 19589 else 19590 const(char)[] content; // this should NEVER have a newline, except at the end 19591 19592 RenderedComponent[] rendered; // entirely controlled by [rerender] 19593 19594 // could prolly put some spacing around it too like margin / padding 19595 19596 this(Color f, Color b, OperatingSystemFont font, ubyte attr, const(char)[] c) 19597 in { assert(font !is null); 19598 assert(!font.isNull); } 19599 do 19600 { 19601 this.foreground = f; 19602 this.background = b; 19603 this.font = font; 19604 19605 this.attributes = attr; 19606 version(Windows) { 19607 auto conversionFlags = 0;//WindowsStringConversionFlags.convertNewLines; 19608 auto sz = sizeOfConvertedWstring(c, conversionFlags); 19609 auto buffer = new wchar[](sz); 19610 this.content = makeWindowsString(c, buffer, conversionFlags); 19611 } else { 19612 this.content = c.dup; 19613 } 19614 } 19615 19616 void draw(ScreenPainter painter) { 19617 painter.setFont(this.font); 19618 painter.outlineColor = this.foreground; 19619 painter.fillColor = Color.transparent; 19620 foreach(rendered; this.rendered) { 19621 // the component works in term of baseline, 19622 // but the painter works in term of upper left bounding box 19623 // so need to translate that 19624 19625 if(this.background.a) { 19626 painter.fillColor = this.background; 19627 painter.outlineColor = this.background; 19628 19629 painter.drawRectangle(Point(rendered.startX, rendered.startY - this.font.ascent), Size(rendered.width, this.font.height)); 19630 19631 painter.outlineColor = this.foreground; 19632 painter.fillColor = Color.transparent; 19633 } 19634 19635 painter.drawText(Point(rendered.startX, rendered.startY - this.font.ascent), rendered.slice); 19636 19637 // FIXME: strike through, underline, highlight selection, etc. 19638 } 19639 } 19640 } 19641 19642 // I could split the parts into words on render 19643 // for easier word-wrap, each one being an unbreakable "inline-block" 19644 private TextFlowComponent[] parts; 19645 private int needsRerenderFrom; 19646 19647 void addPart(Color f, Color b, OperatingSystemFont font, ubyte attr, const(char)[] c) { 19648 // FIXME: needsRerenderFrom. Basically if the bounding box and baseline is the same as the previous thing, it can prolly just stop. 19649 parts ~= new TextFlowComponent(f, b, font, attr, c); 19650 } 19651 19652 static struct RenderedComponent { 19653 int startX; 19654 int startY; 19655 short width; 19656 // 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! 19657 // for individual chars in here you've gotta process on demand 19658 version(Windows) 19659 const(wchar)[] slice; 19660 else 19661 const(char)[] slice; 19662 } 19663 19664 19665 void rerender(Rectangle boundingBox) { 19666 Point baseline = boundingBox.upperLeft; 19667 19668 this.boundingBox.left = boundingBox.left; 19669 this.boundingBox.top = boundingBox.top; 19670 19671 auto remainingParts = parts; 19672 19673 int largestX; 19674 19675 19676 foreach(part; parts) 19677 part.font.prepareContext(window); 19678 scope(exit) 19679 foreach(part; parts) 19680 part.font.releaseContext(); 19681 19682 calculateNextLine: 19683 19684 int nextLineHeight = 0; 19685 int nextBiggestDescent = 0; 19686 19687 foreach(part; remainingParts) { 19688 auto height = part.font.ascent; 19689 if(height > nextLineHeight) 19690 nextLineHeight = height; 19691 if(part.font.descent > nextBiggestDescent) 19692 nextBiggestDescent = part.font.descent; 19693 if(part.content.length && part.content[$-1] == '\n') 19694 break; 19695 } 19696 19697 baseline.y += nextLineHeight; 19698 auto lineStart = baseline; 19699 19700 while(remainingParts.length) { 19701 remainingParts[0].rendered = null; 19702 19703 bool eol; 19704 if(remainingParts[0].content.length && remainingParts[0].content[$-1] == '\n') 19705 eol = true; 19706 19707 // FIXME: word wrap 19708 auto font = remainingParts[0].font; 19709 auto slice = remainingParts[0].content[0 .. $ - (eol ? 1 : 0)]; 19710 auto width = font.stringWidth(slice, window); 19711 remainingParts[0].rendered ~= RenderedComponent(baseline.x, baseline.y, cast(short) width, slice); 19712 19713 remainingParts = remainingParts[1 .. $]; 19714 baseline.x += width; 19715 19716 if(eol) { 19717 baseline.y += nextBiggestDescent; 19718 if(baseline.x > largestX) 19719 largestX = baseline.x; 19720 baseline.x = lineStart.x; 19721 goto calculateNextLine; 19722 } 19723 } 19724 19725 if(baseline.x > largestX) 19726 largestX = baseline.x; 19727 19728 this.boundingBox.right = largestX; 19729 this.boundingBox.bottom = baseline.y; 19730 } 19731 19732 // you must call rerender first! 19733 void draw(ScreenPainter painter) { 19734 foreach(part; parts) { 19735 part.draw(painter); 19736 } 19737 } 19738 19739 struct IdentifyResult { 19740 TextFlowComponent part; 19741 int charIndexInPart; 19742 int totalCharIndex = -1; // if this is -1, it just means the end 19743 19744 Rectangle boundingBox; 19745 } 19746 19747 IdentifyResult identify(Point pt, bool exact = false) { 19748 if(parts.length == 0) 19749 return IdentifyResult(null, 0); 19750 19751 if(pt.y < boundingBox.top) { 19752 if(exact) 19753 return IdentifyResult(null, 1); 19754 return IdentifyResult(parts[0], 0); 19755 } 19756 if(pt.y > boundingBox.bottom) { 19757 if(exact) 19758 return IdentifyResult(null, 2); 19759 return IdentifyResult(parts[$-1], cast(int) parts[$-1].content.length); 19760 } 19761 19762 int tci = 0; 19763 19764 // I should probably like binary search this or something... 19765 foreach(ref part; parts) { 19766 foreach(rendered; part.rendered) { 19767 auto rect = Rectangle(rendered.startX, rendered.startY - part.font.ascent, rendered.startX + rendered.width, rendered.startY + part.font.descent); 19768 if(rect.contains(pt)) { 19769 auto x = pt.x - rendered.startX; 19770 auto estimatedIdx = x / part.font.averageWidth; 19771 19772 if(estimatedIdx < 0) 19773 estimatedIdx = 0; 19774 19775 if(estimatedIdx > rendered.slice.length) 19776 estimatedIdx = cast(int) rendered.slice.length; 19777 19778 int idx; 19779 int x1, x2; 19780 if(part.font.isMonospace) { 19781 auto w = part.font.averageWidth; 19782 if(!exact && x > (estimatedIdx + 1) * w) 19783 return IdentifyResult(null, 4); 19784 idx = estimatedIdx; 19785 x1 = idx * w; 19786 x2 = (idx + 1) * w; 19787 } else { 19788 idx = estimatedIdx; 19789 19790 part.font.prepareContext(window); 19791 scope(exit) part.font.releaseContext(); 19792 19793 // int iterations; 19794 19795 while(true) { 19796 // iterations++; 19797 x1 = idx ? part.font.stringWidth(rendered.slice[0 .. idx - 1]) : 0; 19798 x2 = part.font.stringWidth(rendered.slice[0 .. idx]); // should be the maximum since `averageWidth` kinda lies. 19799 19800 x1 += rendered.startX; 19801 x2 += rendered.startX; 19802 19803 if(pt.x < x1) { 19804 if(idx == 0) { 19805 if(exact) 19806 return IdentifyResult(null, 6); 19807 else 19808 break; 19809 } 19810 idx--; 19811 } else if(pt.x > x2) { 19812 idx++; 19813 if(idx > rendered.slice.length) { 19814 if(exact) 19815 return IdentifyResult(null, 5); 19816 else 19817 break; 19818 } 19819 } else if(pt.x >= x1 && pt.x <= x2) { 19820 if(idx) 19821 idx--; // point it at the original index 19822 break; // we fit 19823 } 19824 } 19825 19826 // writeln(iterations) 19827 } 19828 19829 19830 return IdentifyResult(part, idx, tci + idx, Rectangle(x1, rect.top, x2, rect.bottom)); // FIXME: utf-8? 19831 } 19832 } 19833 tci += cast(int) part.content.length; // FIXME: utf-8? 19834 } 19835 return IdentifyResult(null, 3); 19836 } 19837 19838 Rectangle boundingBox; // only set after [rerender] 19839 19840 // text will be positioned around the exclusion zone 19841 static struct ExclusionZone { 19842 19843 } 19844 19845 ExclusionZone[] exclusionZones; 19846 } 19847 19848 19849 // Don't use this yet. When I'm happy with it, I will move it to the 19850 // regular module namespace. 19851 mixin template ExperimentalTextComponent() { 19852 19853 static: 19854 19855 alias Rectangle = arsd.color.Rectangle; 19856 19857 struct ForegroundColor { 19858 Color color; 19859 alias color this; 19860 19861 this(Color c) { 19862 color = c; 19863 } 19864 19865 this(int r, int g, int b, int a = 255) { 19866 color = Color(r, g, b, a); 19867 } 19868 19869 static ForegroundColor opDispatch(string s)() if(__traits(compiles, ForegroundColor(mixin("Color." ~ s)))) { 19870 return ForegroundColor(mixin("Color." ~ s)); 19871 } 19872 } 19873 19874 struct BackgroundColor { 19875 Color color; 19876 alias color this; 19877 19878 this(Color c) { 19879 color = c; 19880 } 19881 19882 this(int r, int g, int b, int a = 255) { 19883 color = Color(r, g, b, a); 19884 } 19885 19886 static BackgroundColor opDispatch(string s)() if(__traits(compiles, BackgroundColor(mixin("Color." ~ s)))) { 19887 return BackgroundColor(mixin("Color." ~ s)); 19888 } 19889 } 19890 19891 static class InlineElement { 19892 string text; 19893 19894 BlockElement containingBlock; 19895 19896 Color color = Color.black; 19897 Color backgroundColor = Color.transparent; 19898 ushort styles; 19899 19900 string font; 19901 int fontSize; 19902 19903 int lineHeight; 19904 19905 void* identifier; 19906 19907 Rectangle boundingBox; 19908 int[] letterXs; // FIXME: maybe i should do bounding boxes for every character 19909 19910 bool isMergeCompatible(InlineElement other) { 19911 return 19912 containingBlock is other.containingBlock && 19913 color == other.color && 19914 backgroundColor == other.backgroundColor && 19915 styles == other.styles && 19916 font == other.font && 19917 fontSize == other.fontSize && 19918 lineHeight == other.lineHeight && 19919 true; 19920 } 19921 19922 int xOfIndex(size_t index) { 19923 if(index < letterXs.length) 19924 return letterXs[index]; 19925 else 19926 return boundingBox.right; 19927 } 19928 19929 InlineElement clone() { 19930 auto ie = new InlineElement(); 19931 ie.tupleof = this.tupleof; 19932 return ie; 19933 } 19934 19935 InlineElement getPreviousInlineElement() { 19936 InlineElement prev = null; 19937 foreach(ie; this.containingBlock.parts) { 19938 if(ie is this) 19939 break; 19940 prev = ie; 19941 } 19942 if(prev is null) { 19943 BlockElement pb; 19944 BlockElement cb = this.containingBlock; 19945 moar: 19946 foreach(ie; this.containingBlock.containingLayout.blocks) { 19947 if(ie is cb) 19948 break; 19949 pb = ie; 19950 } 19951 if(pb is null) 19952 return null; 19953 if(pb.parts.length == 0) { 19954 cb = pb; 19955 goto moar; 19956 } 19957 19958 prev = pb.parts[$-1]; 19959 19960 } 19961 return prev; 19962 } 19963 19964 InlineElement getNextInlineElement() { 19965 InlineElement next = null; 19966 foreach(idx, ie; this.containingBlock.parts) { 19967 if(ie is this) { 19968 if(idx + 1 < this.containingBlock.parts.length) 19969 next = this.containingBlock.parts[idx + 1]; 19970 break; 19971 } 19972 } 19973 if(next is null) { 19974 BlockElement n; 19975 foreach(idx, ie; this.containingBlock.containingLayout.blocks) { 19976 if(ie is this.containingBlock) { 19977 if(idx + 1 < this.containingBlock.containingLayout.blocks.length) 19978 n = this.containingBlock.containingLayout.blocks[idx + 1]; 19979 break; 19980 } 19981 } 19982 if(n is null) 19983 return null; 19984 19985 if(n.parts.length) 19986 next = n.parts[0]; 19987 else {} // FIXME 19988 19989 } 19990 return next; 19991 } 19992 19993 } 19994 19995 // Block elements are used entirely for positioning inline elements, 19996 // which are the things that are actually drawn. 19997 class BlockElement { 19998 InlineElement[] parts; 19999 uint alignment; 20000 20001 int whiteSpace; // pre, pre-wrap, wrap 20002 20003 TextLayout containingLayout; 20004 20005 // inputs 20006 Point where; 20007 Size minimumSize; 20008 Size maximumSize; 20009 Rectangle[] excludedBoxes; // like if you want it to write around a floated image or something. Coordinates are relative to the bounding box. 20010 void* identifier; 20011 20012 Rectangle margin; 20013 Rectangle padding; 20014 20015 // outputs 20016 Rectangle[] boundingBoxes; 20017 } 20018 20019 struct TextIdentifyResult { 20020 InlineElement element; 20021 int offset; 20022 20023 private TextIdentifyResult fixupNewline() { 20024 if(element !is null && offset < element.text.length && element.text[offset] == '\n') { 20025 offset--; 20026 } else if(element !is null && offset == element.text.length && element.text.length > 1 && element.text[$-1] == '\n') { 20027 offset--; 20028 } 20029 return this; 20030 } 20031 } 20032 20033 class TextLayout { 20034 BlockElement[] blocks; 20035 Rectangle boundingBox_; 20036 Rectangle boundingBox() { return boundingBox_; } 20037 void boundingBox(Rectangle r) { 20038 if(r != boundingBox_) { 20039 boundingBox_ = r; 20040 layoutInvalidated = true; 20041 } 20042 } 20043 20044 Rectangle contentBoundingBox() { 20045 Rectangle r; 20046 foreach(block; blocks) 20047 foreach(ie; block.parts) { 20048 if(ie.boundingBox.right > r.right) 20049 r.right = ie.boundingBox.right; 20050 if(ie.boundingBox.bottom > r.bottom) 20051 r.bottom = ie.boundingBox.bottom; 20052 } 20053 return r; 20054 } 20055 20056 BlockElement[] getBlocks() { 20057 return blocks; 20058 } 20059 20060 InlineElement[] getTexts() { 20061 InlineElement[] elements; 20062 foreach(block; blocks) 20063 elements ~= block.parts; 20064 return elements; 20065 } 20066 20067 string getPlainText() { 20068 string text; 20069 foreach(block; blocks) 20070 foreach(part; block.parts) 20071 text ~= part.text; 20072 return text; 20073 } 20074 20075 string getHtml() { 20076 return null; // FIXME 20077 } 20078 20079 this(Rectangle boundingBox) { 20080 this.boundingBox = boundingBox; 20081 } 20082 20083 BlockElement addBlock(InlineElement after = null, Rectangle margin = Rectangle(0, 0, 0, 0), Rectangle padding = Rectangle(0, 0, 0, 0)) { 20084 auto be = new BlockElement(); 20085 be.containingLayout = this; 20086 if(after is null) 20087 blocks ~= be; 20088 else { 20089 foreach(idx, b; blocks) { 20090 if(b is after.containingBlock) { 20091 blocks = blocks[0 .. idx + 1] ~ be ~ blocks[idx + 1 .. $]; 20092 break; 20093 } 20094 } 20095 } 20096 return be; 20097 } 20098 20099 void clear() { 20100 blocks = null; 20101 selectionStart = selectionEnd = caret = Caret.init; 20102 } 20103 20104 void addText(Args...)(Args args) { 20105 if(blocks.length == 0) 20106 addBlock(); 20107 20108 InlineElement ie = new InlineElement(); 20109 foreach(idx, arg; args) { 20110 static if(is(typeof(arg) == ForegroundColor)) 20111 ie.color = arg; 20112 else static if(is(typeof(arg) == TextFormat)) { 20113 if(arg & 0x8000) // ~TextFormat.something turns it off 20114 ie.styles &= arg; 20115 else 20116 ie.styles |= arg; 20117 } else static if(is(typeof(arg) == string)) { 20118 static if(idx == 0 && args.length > 1) 20119 static assert(0, "Put styles before the string."); 20120 size_t lastLineIndex; 20121 foreach(cidx, char a; arg) { 20122 if(a == '\n') { 20123 ie.text = arg[lastLineIndex .. cidx + 1]; 20124 lastLineIndex = cidx + 1; 20125 ie.containingBlock = blocks[$-1]; 20126 blocks[$-1].parts ~= ie.clone; 20127 ie.text = null; 20128 } else { 20129 20130 } 20131 } 20132 20133 ie.text = arg[lastLineIndex .. $]; 20134 ie.containingBlock = blocks[$-1]; 20135 blocks[$-1].parts ~= ie.clone; 20136 caret = Caret(this, blocks[$-1].parts[$-1], cast(int) blocks[$-1].parts[$-1].text.length); 20137 } 20138 } 20139 20140 invalidateLayout(); 20141 } 20142 20143 void tryMerge(InlineElement into, InlineElement what) { 20144 if(!into.isMergeCompatible(what)) { 20145 return; // cannot merge, different configs 20146 } 20147 20148 // cool, can merge, bring text together... 20149 into.text ~= what.text; 20150 20151 // and remove what 20152 for(size_t a = 0; a < what.containingBlock.parts.length; a++) { 20153 if(what.containingBlock.parts[a] is what) { 20154 for(size_t i = a; i < what.containingBlock.parts.length - 1; i++) 20155 what.containingBlock.parts[i] = what.containingBlock.parts[i + 1]; 20156 what.containingBlock.parts = what.containingBlock.parts[0 .. $-1]; 20157 20158 } 20159 } 20160 20161 // FIXME: ensure no other carets have a reference to it 20162 } 20163 20164 /// exact = true means return null if no match. otherwise, get the closest one that makes sense for a mouse click. 20165 TextIdentifyResult identify(int x, int y, bool exact = false) { 20166 TextIdentifyResult inexactMatch; 20167 foreach(block; blocks) { 20168 foreach(part; block.parts) { 20169 if(x >= part.boundingBox.left && x < part.boundingBox.right && y >= part.boundingBox.top && y < part.boundingBox.bottom) { 20170 20171 // FIXME binary search 20172 int tidx; 20173 int lastX; 20174 foreach_reverse(idxo, lx; part.letterXs) { 20175 int idx = cast(int) idxo; 20176 if(lx <= x) { 20177 if(lastX && lastX - x < x - lx) 20178 tidx = idx + 1; 20179 else 20180 tidx = idx; 20181 break; 20182 } 20183 lastX = lx; 20184 } 20185 20186 return TextIdentifyResult(part, tidx).fixupNewline; 20187 } else if(!exact) { 20188 // we're not in the box, but are we on the same line? 20189 if(y >= part.boundingBox.top && y < part.boundingBox.bottom) 20190 inexactMatch = TextIdentifyResult(part, x == 0 ? 0 : cast(int) part.text.length); 20191 } 20192 } 20193 } 20194 20195 if(!exact && inexactMatch is TextIdentifyResult.init && blocks.length && blocks[$-1].parts.length) 20196 return TextIdentifyResult(blocks[$-1].parts[$-1], cast(int) blocks[$-1].parts[$-1].text.length).fixupNewline; 20197 20198 return exact ? TextIdentifyResult.init : inexactMatch.fixupNewline; 20199 } 20200 20201 void moveCaretToPixelCoordinates(int x, int y) { 20202 auto result = identify(x, y); 20203 caret.inlineElement = result.element; 20204 caret.offset = result.offset; 20205 } 20206 20207 void selectToPixelCoordinates(int x, int y) { 20208 auto result = identify(x, y); 20209 20210 if(y < caretLastDrawnY1) { 20211 // on a previous line, carat is selectionEnd 20212 selectionEnd = caret; 20213 20214 selectionStart = Caret(this, result.element, result.offset); 20215 } else if(y > caretLastDrawnY2) { 20216 // on a later line 20217 selectionStart = caret; 20218 20219 selectionEnd = Caret(this, result.element, result.offset); 20220 } else { 20221 // on the same line... 20222 if(x <= caretLastDrawnX) { 20223 selectionEnd = caret; 20224 selectionStart = Caret(this, result.element, result.offset); 20225 } else { 20226 selectionStart = caret; 20227 selectionEnd = Caret(this, result.element, result.offset); 20228 } 20229 20230 } 20231 } 20232 20233 20234 /// Call this if the inputs change. It will reflow everything 20235 void redoLayout(ScreenPainter painter) { 20236 //painter.setClipRectangle(boundingBox); 20237 auto pos = Point(boundingBox.left, boundingBox.top); 20238 20239 int lastHeight; 20240 void nl() { 20241 pos.x = boundingBox.left; 20242 pos.y += lastHeight; 20243 } 20244 foreach(block; blocks) { 20245 nl(); 20246 foreach(part; block.parts) { 20247 part.letterXs = null; 20248 20249 auto size = painter.textSize(part.text); 20250 version(Windows) 20251 if(part.text.length && part.text[$-1] == '\n') 20252 size.height /= 2; // windows counts the new line at the end, but we don't want that 20253 20254 part.boundingBox = Rectangle(pos.x, pos.y, pos.x + size.width, pos.y + size.height); 20255 20256 foreach(idx, char c; part.text) { 20257 // FIXME: unicode 20258 part.letterXs ~= painter.textSize(part.text[0 .. idx]).width + pos.x; 20259 } 20260 20261 pos.x += size.width; 20262 if(pos.x >= boundingBox.right) { 20263 pos.y += size.height; 20264 pos.x = boundingBox.left; 20265 lastHeight = 0; 20266 } else { 20267 lastHeight = size.height; 20268 } 20269 20270 if(part.text.length && part.text[$-1] == '\n') 20271 nl(); 20272 } 20273 } 20274 20275 layoutInvalidated = false; 20276 } 20277 20278 bool layoutInvalidated = true; 20279 void invalidateLayout() { 20280 layoutInvalidated = true; 20281 } 20282 20283 // FIXME: caret can remain sometimes when inserting 20284 // FIXME: inserting at the beginning once you already have something can eff it up. 20285 void drawInto(ScreenPainter painter, bool focused = false) { 20286 if(layoutInvalidated) 20287 redoLayout(painter); 20288 foreach(block; blocks) { 20289 foreach(part; block.parts) { 20290 painter.outlineColor = part.color; 20291 painter.fillColor = part.backgroundColor; 20292 20293 auto pos = part.boundingBox.upperLeft; 20294 auto size = part.boundingBox.size; 20295 20296 painter.drawText(pos, part.text); 20297 if(part.styles & TextFormat.underline) 20298 painter.drawLine(Point(pos.x, pos.y + size.height - 4), Point(pos.x + size.width, pos.y + size.height - 4)); 20299 if(part.styles & TextFormat.strikethrough) 20300 painter.drawLine(Point(pos.x, pos.y + size.height/2), Point(pos.x + size.width, pos.y + size.height/2)); 20301 } 20302 } 20303 20304 // on every redraw, I will force the caret to be 20305 // redrawn too, in order to eliminate perceived lag 20306 // when moving around with the mouse. 20307 eraseCaret(painter); 20308 20309 if(focused) { 20310 highlightSelection(painter); 20311 drawCaret(painter); 20312 } 20313 } 20314 20315 Color selectionXorColor = Color(255, 255, 127); 20316 20317 void highlightSelection(ScreenPainter painter) { 20318 if(selectionStart is selectionEnd) 20319 return; // no selection 20320 20321 if(selectionStart.inlineElement is null) return; 20322 if(selectionEnd.inlineElement is null) return; 20323 20324 assert(selectionStart.inlineElement !is null); 20325 assert(selectionEnd.inlineElement !is null); 20326 20327 painter.rasterOp = RasterOp.xor; 20328 painter.outlineColor = Color.transparent; 20329 painter.fillColor = selectionXorColor; 20330 20331 auto at = selectionStart.inlineElement; 20332 auto atOffset = selectionStart.offset; 20333 bool done; 20334 while(at) { 20335 auto box = at.boundingBox; 20336 if(atOffset < at.letterXs.length) 20337 box.left = at.letterXs[atOffset]; 20338 20339 if(at is selectionEnd.inlineElement) { 20340 if(selectionEnd.offset < at.letterXs.length) 20341 box.right = at.letterXs[selectionEnd.offset]; 20342 done = true; 20343 } 20344 20345 painter.drawRectangle(box.upperLeft, box.width, box.height); 20346 20347 if(done) 20348 break; 20349 20350 at = at.getNextInlineElement(); 20351 atOffset = 0; 20352 } 20353 } 20354 20355 int caretLastDrawnX, caretLastDrawnY1, caretLastDrawnY2; 20356 bool caretShowingOnScreen = false; 20357 void drawCaret(ScreenPainter painter) { 20358 //painter.setClipRectangle(boundingBox); 20359 int x, y1, y2; 20360 if(caret.inlineElement is null) { 20361 x = boundingBox.left; 20362 y1 = boundingBox.top + 2; 20363 y2 = boundingBox.top + painter.fontHeight; 20364 } else { 20365 x = caret.inlineElement.xOfIndex(caret.offset); 20366 y1 = caret.inlineElement.boundingBox.top + 2; 20367 y2 = caret.inlineElement.boundingBox.bottom - 2; 20368 } 20369 20370 if(caretShowingOnScreen && (x != caretLastDrawnX || y1 != caretLastDrawnY1 || y2 != caretLastDrawnY2)) 20371 eraseCaret(painter); 20372 20373 painter.pen = Pen(Color.white, 1); 20374 painter.rasterOp = RasterOp.xor; 20375 painter.drawLine( 20376 Point(x, y1), 20377 Point(x, y2) 20378 ); 20379 painter.rasterOp = RasterOp.normal; 20380 caretShowingOnScreen = !caretShowingOnScreen; 20381 20382 if(caretShowingOnScreen) { 20383 caretLastDrawnX = x; 20384 caretLastDrawnY1 = y1; 20385 caretLastDrawnY2 = y2; 20386 } 20387 } 20388 20389 Rectangle caretBoundingBox() { 20390 int x, y1, y2; 20391 if(caret.inlineElement is null) { 20392 x = boundingBox.left; 20393 y1 = boundingBox.top + 2; 20394 y2 = boundingBox.top + 16; 20395 } else { 20396 x = caret.inlineElement.xOfIndex(caret.offset); 20397 y1 = caret.inlineElement.boundingBox.top + 2; 20398 y2 = caret.inlineElement.boundingBox.bottom - 2; 20399 } 20400 20401 return Rectangle(x, y1, x + 1, y2); 20402 } 20403 20404 void eraseCaret(ScreenPainter painter) { 20405 //painter.setClipRectangle(boundingBox); 20406 if(!caretShowingOnScreen) return; 20407 painter.pen = Pen(Color.white, 1); 20408 painter.rasterOp = RasterOp.xor; 20409 painter.drawLine( 20410 Point(caretLastDrawnX, caretLastDrawnY1), 20411 Point(caretLastDrawnX, caretLastDrawnY2) 20412 ); 20413 20414 caretShowingOnScreen = false; 20415 painter.rasterOp = RasterOp.normal; 20416 } 20417 20418 /// Caret movement api 20419 /// These should give the user a logical result based on what they see on screen... 20420 /// thus they locate predominately by *pixels* not char index. (These will generally coincide with monospace fonts tho!) 20421 void moveUp() { 20422 if(caret.inlineElement is null) return; 20423 auto x = caret.inlineElement.xOfIndex(caret.offset); 20424 auto y = caret.inlineElement.boundingBox.top + 2; 20425 20426 y -= caret.inlineElement.boundingBox.bottom - caret.inlineElement.boundingBox.top; 20427 if(y < 0) 20428 return; 20429 20430 auto i = identify(x, y); 20431 20432 if(i.element) { 20433 caret.inlineElement = i.element; 20434 caret.offset = i.offset; 20435 } 20436 } 20437 void moveDown() { 20438 if(caret.inlineElement is null) return; 20439 auto x = caret.inlineElement.xOfIndex(caret.offset); 20440 auto y = caret.inlineElement.boundingBox.bottom - 2; 20441 20442 y += caret.inlineElement.boundingBox.bottom - caret.inlineElement.boundingBox.top; 20443 20444 auto i = identify(x, y); 20445 if(i.element) { 20446 caret.inlineElement = i.element; 20447 caret.offset = i.offset; 20448 } 20449 } 20450 void moveLeft() { 20451 if(caret.inlineElement is null) return; 20452 if(caret.offset) 20453 caret.offset--; 20454 else { 20455 auto p = caret.inlineElement.getPreviousInlineElement(); 20456 if(p) { 20457 caret.inlineElement = p; 20458 if(p.text.length && p.text[$-1] == '\n') 20459 caret.offset = cast(int) p.text.length - 1; 20460 else 20461 caret.offset = cast(int) p.text.length; 20462 } 20463 } 20464 } 20465 void moveRight() { 20466 if(caret.inlineElement is null) return; 20467 if(caret.offset < caret.inlineElement.text.length && caret.inlineElement.text[caret.offset] != '\n') { 20468 caret.offset++; 20469 } else { 20470 auto p = caret.inlineElement.getNextInlineElement(); 20471 if(p) { 20472 caret.inlineElement = p; 20473 caret.offset = 0; 20474 } 20475 } 20476 } 20477 void moveHome() { 20478 if(caret.inlineElement is null) return; 20479 auto x = 0; 20480 auto y = caret.inlineElement.boundingBox.top + 2; 20481 20482 auto i = identify(x, y); 20483 20484 if(i.element) { 20485 caret.inlineElement = i.element; 20486 caret.offset = i.offset; 20487 } 20488 } 20489 void moveEnd() { 20490 if(caret.inlineElement is null) return; 20491 auto x = int.max; 20492 auto y = caret.inlineElement.boundingBox.top + 2; 20493 20494 auto i = identify(x, y); 20495 20496 if(i.element) { 20497 caret.inlineElement = i.element; 20498 caret.offset = i.offset; 20499 } 20500 20501 } 20502 void movePageUp(ref Caret caret) {} 20503 void movePageDown(ref Caret caret) {} 20504 20505 void moveDocumentStart(ref Caret caret) { 20506 if(blocks.length && blocks[0].parts.length) 20507 caret = Caret(this, blocks[0].parts[0], 0); 20508 else 20509 caret = Caret.init; 20510 } 20511 20512 void moveDocumentEnd(ref Caret caret) { 20513 if(blocks.length) { 20514 auto parts = blocks[$-1].parts; 20515 if(parts.length) { 20516 caret = Caret(this, parts[$-1], cast(int) parts[$-1].text.length); 20517 } else { 20518 caret = Caret.init; 20519 } 20520 } else 20521 caret = Caret.init; 20522 } 20523 20524 void deleteSelection() { 20525 if(selectionStart is selectionEnd) 20526 return; 20527 20528 if(selectionStart.inlineElement is null) return; 20529 if(selectionEnd.inlineElement is null) return; 20530 20531 assert(selectionStart.inlineElement !is null); 20532 assert(selectionEnd.inlineElement !is null); 20533 20534 auto at = selectionStart.inlineElement; 20535 20536 if(selectionEnd.inlineElement is at) { 20537 // same element, need to chop out 20538 at.text = at.text[0 .. selectionStart.offset] ~ at.text[selectionEnd.offset .. $]; 20539 at.letterXs = at.letterXs[0 .. selectionStart.offset] ~ at.letterXs[selectionEnd.offset .. $]; 20540 selectionEnd.offset -= selectionEnd.offset - selectionStart.offset; 20541 } else { 20542 // different elements, we can do it with slicing 20543 at.text = at.text[0 .. selectionStart.offset]; 20544 if(selectionStart.offset < at.letterXs.length) 20545 at.letterXs = at.letterXs[0 .. selectionStart.offset]; 20546 20547 at = at.getNextInlineElement(); 20548 20549 while(at) { 20550 if(at is selectionEnd.inlineElement) { 20551 at.text = at.text[selectionEnd.offset .. $]; 20552 if(selectionEnd.offset < at.letterXs.length) 20553 at.letterXs = at.letterXs[selectionEnd.offset .. $]; 20554 selectionEnd.offset = 0; 20555 break; 20556 } else { 20557 auto cfd = at; 20558 cfd.text = null; // delete the whole thing 20559 20560 at = at.getNextInlineElement(); 20561 20562 if(cfd.text.length == 0) { 20563 // and remove cfd 20564 for(size_t a = 0; a < cfd.containingBlock.parts.length; a++) { 20565 if(cfd.containingBlock.parts[a] is cfd) { 20566 for(size_t i = a; i < cfd.containingBlock.parts.length - 1; i++) 20567 cfd.containingBlock.parts[i] = cfd.containingBlock.parts[i + 1]; 20568 cfd.containingBlock.parts = cfd.containingBlock.parts[0 .. $-1]; 20569 20570 } 20571 } 20572 } 20573 } 20574 } 20575 } 20576 20577 caret = selectionEnd; 20578 selectNone(); 20579 20580 invalidateLayout(); 20581 20582 } 20583 20584 /// Plain text editing api. These work at the current caret inside the selected inline element. 20585 void insert(in char[] text) { 20586 foreach(dchar ch; text) 20587 insert(ch); 20588 } 20589 /// ditto 20590 void insert(dchar ch) { 20591 20592 bool selectionDeleted = false; 20593 if(selectionStart !is selectionEnd) { 20594 deleteSelection(); 20595 selectionDeleted = true; 20596 } 20597 20598 if(ch == 127) { 20599 delete_(); 20600 return; 20601 } 20602 if(ch == 8) { 20603 if(!selectionDeleted) 20604 backspace(); 20605 return; 20606 } 20607 20608 invalidateLayout(); 20609 20610 if(ch == 13) ch = 10; 20611 auto e = caret.inlineElement; 20612 if(e is null) { 20613 addText("" ~ cast(char) ch) ; // FIXME 20614 return; 20615 } 20616 20617 if(caret.offset == e.text.length) { 20618 e.text ~= cast(char) ch; // FIXME 20619 caret.offset++; 20620 if(ch == 10) { 20621 auto c = caret.inlineElement.clone; 20622 c.text = null; 20623 c.letterXs = null; 20624 insertPartAfter(c,e); 20625 caret = Caret(this, c, 0); 20626 } 20627 } else { 20628 // FIXME cast char sucks 20629 if(ch == 10) { 20630 auto c = caret.inlineElement.clone; 20631 c.text = e.text[caret.offset .. $]; 20632 if(caret.offset < c.letterXs.length) 20633 c.letterXs = e.letterXs[caret.offset .. $]; // FIXME boundingBox 20634 e.text = e.text[0 .. caret.offset] ~ cast(char) ch; 20635 if(caret.offset <= e.letterXs.length) { 20636 e.letterXs = e.letterXs[0 .. caret.offset] ~ 0; // FIXME bounding box 20637 } 20638 insertPartAfter(c,e); 20639 caret = Caret(this, c, 0); 20640 } else { 20641 e.text = e.text[0 .. caret.offset] ~ cast(char) ch ~ e.text[caret.offset .. $]; 20642 caret.offset++; 20643 } 20644 } 20645 } 20646 20647 void insertPartAfter(InlineElement what, InlineElement where) { 20648 foreach(idx, p; where.containingBlock.parts) { 20649 if(p is where) { 20650 if(idx + 1 == where.containingBlock.parts.length) 20651 where.containingBlock.parts ~= what; 20652 else 20653 where.containingBlock.parts = where.containingBlock.parts[0 .. idx + 1] ~ what ~ where.containingBlock.parts[idx + 1 .. $]; 20654 return; 20655 } 20656 } 20657 } 20658 20659 void cleanupStructures() { 20660 for(size_t i = 0; i < blocks.length; i++) { 20661 auto block = blocks[i]; 20662 for(size_t a = 0; a < block.parts.length; a++) { 20663 auto part = block.parts[a]; 20664 if(part.text.length == 0) { 20665 for(size_t b = a; b < block.parts.length - 1; b++) 20666 block.parts[b] = block.parts[b+1]; 20667 block.parts = block.parts[0 .. $-1]; 20668 } 20669 } 20670 if(block.parts.length == 0) { 20671 for(size_t a = i; a < blocks.length - 1; a++) 20672 blocks[a] = blocks[a+1]; 20673 blocks = blocks[0 .. $-1]; 20674 } 20675 } 20676 } 20677 20678 void backspace() { 20679 try_again: 20680 auto e = caret.inlineElement; 20681 if(e is null) 20682 return; 20683 if(caret.offset == 0) { 20684 auto prev = e.getPreviousInlineElement(); 20685 if(prev is null) 20686 return; 20687 auto newOffset = cast(int) prev.text.length; 20688 tryMerge(prev, e); 20689 caret.inlineElement = prev; 20690 caret.offset = prev is null ? 0 : newOffset; 20691 20692 goto try_again; 20693 } else if(caret.offset == e.text.length) { 20694 e.text = e.text[0 .. $-1]; 20695 caret.offset--; 20696 } else { 20697 e.text = e.text[0 .. caret.offset - 1] ~ e.text[caret.offset .. $]; 20698 caret.offset--; 20699 } 20700 //cleanupStructures(); 20701 20702 invalidateLayout(); 20703 } 20704 void delete_() { 20705 if(selectionStart !is selectionEnd) 20706 deleteSelection(); 20707 else { 20708 auto before = caret; 20709 moveRight(); 20710 if(caret != before) { 20711 backspace(); 20712 } 20713 } 20714 20715 invalidateLayout(); 20716 } 20717 void overstrike() {} 20718 20719 /// Selection API. See also: caret movement. 20720 void selectAll() { 20721 moveDocumentStart(selectionStart); 20722 moveDocumentEnd(selectionEnd); 20723 } 20724 bool selectNone() { 20725 if(selectionStart != selectionEnd) { 20726 selectionStart = selectionEnd = Caret.init; 20727 return true; 20728 } 20729 return false; 20730 } 20731 20732 /// Rich text editing api. These allow you to manipulate the meta data of the current element and add new elements. 20733 /// They will modify the current selection if there is one and will splice one in if needed. 20734 void changeAttributes() {} 20735 20736 20737 /// Text search api. They manipulate the selection and/or caret. 20738 void findText(string text) {} 20739 void findIndex(size_t textIndex) {} 20740 20741 // sample event handlers 20742 20743 void handleEvent(KeyEvent event) { 20744 //if(event.type == KeyEvent.Type.KeyPressed) { 20745 20746 //} 20747 } 20748 20749 void handleEvent(dchar ch) { 20750 20751 } 20752 20753 void handleEvent(MouseEvent event) { 20754 20755 } 20756 20757 bool contentEditable; // can it be edited? 20758 bool contentCaretable; // is there a caret/cursor that moves around in there? 20759 bool contentSelectable; // selectable? 20760 20761 Caret caret; 20762 Caret selectionStart; 20763 Caret selectionEnd; 20764 20765 bool insertMode; 20766 } 20767 20768 struct Caret { 20769 TextLayout layout; 20770 InlineElement inlineElement; 20771 int offset; 20772 } 20773 20774 enum TextFormat : ushort { 20775 // decorations 20776 underline = 1, 20777 strikethrough = 2, 20778 20779 // font selectors 20780 20781 bold = 0x4000 | 1, // weight 700 20782 light = 0x4000 | 2, // weight 300 20783 veryBoldOrLight = 0x4000 | 4, // weight 100 with light, weight 900 with bold 20784 // bold | light is really invalid but should give weight 500 20785 // veryBoldOrLight without one of the others should just give the default for the font; it should be ignored. 20786 20787 italic = 0x4000 | 8, 20788 smallcaps = 0x4000 | 16, 20789 } 20790 20791 void* findFont(string family, int weight, TextFormat formats) { 20792 return null; 20793 } 20794 20795 } 20796 20797 /++ 20798 $(PITFALL This is not yet stable and may break in future versions without notice.) 20799 20800 History: 20801 Added February 19, 2021 20802 +/ 20803 /// Group: drag_and_drop 20804 interface DropHandler { 20805 /++ 20806 Called when the drag enters the handler's area. 20807 +/ 20808 DragAndDropAction dragEnter(DropPackage*); 20809 /++ 20810 Called when the drag leaves the handler's area or is 20811 cancelled. You should free your resources when this is called. 20812 +/ 20813 void dragLeave(); 20814 /++ 20815 Called continually as the drag moves over the handler's area. 20816 20817 Returns: feedback to the dragger 20818 +/ 20819 DropParameters dragOver(Point pt); 20820 /++ 20821 The user dropped the data and you should process it now. You can 20822 access the data through the given [DropPackage]. 20823 +/ 20824 void drop(scope DropPackage*); 20825 /++ 20826 Called when the drop is complete. You should free whatever temporary 20827 resources you were using. It is often reasonable to simply forward 20828 this call to [dragLeave]. 20829 +/ 20830 void finish(); 20831 20832 /++ 20833 Parameters returned by [DropHandler.drop]. 20834 +/ 20835 static struct DropParameters { 20836 /++ 20837 Acceptable action over this area. 20838 +/ 20839 DragAndDropAction action; 20840 /++ 20841 Rectangle, in client coordinates, where the dragger can expect the same result during this drag session and thus need not ask again. 20842 20843 If you leave this as Rectangle.init, the dragger will continue to ask and this can waste resources. 20844 +/ 20845 Rectangle consistentWithin; 20846 } 20847 } 20848 20849 /++ 20850 History: 20851 Added February 19, 2021 20852 +/ 20853 /// Group: drag_and_drop 20854 enum DragAndDropAction { 20855 none = 0, 20856 copy, 20857 move, 20858 link, 20859 ask, 20860 custom 20861 } 20862 20863 /++ 20864 An opaque structure representing dropped data. It contains 20865 private, platform-specific data that your `drop` function 20866 should simply forward. 20867 20868 $(PITFALL This is not yet stable and may break in future versions without notice.) 20869 20870 History: 20871 Added February 19, 2021 20872 +/ 20873 /// Group: drag_and_drop 20874 struct DropPackage { 20875 /++ 20876 Lists the available formats as magic numbers. You should compare these 20877 against looked-up formats (see [DraggableData.getFormatId]) you know you support and can 20878 understand the passed data. 20879 +/ 20880 DraggableData.FormatId[] availableFormats() { 20881 version(X11) { 20882 return xFormats; 20883 } else version(Windows) { 20884 if(pDataObj is null) 20885 return null; 20886 20887 typeof(return) ret; 20888 20889 IEnumFORMATETC ef; 20890 if(pDataObj.EnumFormatEtc(DATADIR.DATADIR_GET, &ef) == S_OK) { 20891 FORMATETC fmt; 20892 ULONG fetched; 20893 while(ef.Next(1, &fmt, &fetched) == S_OK) { 20894 if(fetched == 0) 20895 break; 20896 20897 if(fmt.lindex != -1) 20898 continue; 20899 if(fmt.dwAspect != DVASPECT.DVASPECT_CONTENT) 20900 continue; 20901 if(!(fmt.tymed & TYMED.TYMED_HGLOBAL)) 20902 continue; 20903 20904 ret ~= fmt.cfFormat; 20905 } 20906 } 20907 20908 return ret; 20909 } else throw new NotYetImplementedException(); 20910 } 20911 20912 /++ 20913 Gets data from the drop and optionally accepts it. 20914 20915 Returns: 20916 void because the data is fed asynchronously through the `dg` parameter. 20917 20918 Params: 20919 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. 20920 20921 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. 20922 20923 Calling `getData` again after accepting a drop is not permitted. 20924 20925 format = the format you want, from [availableFormats]. Use [DraggableData.getFormatId] to convert from a MIME string or well-known standard format. 20926 20927 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. 20928 20929 Throws: 20930 if `format` was not compatible with the [availableFormats] or if the drop has already been accepted. 20931 20932 History: 20933 Included in first release of [DropPackage]. 20934 +/ 20935 void getData(DragAndDropAction acceptedAction, DraggableData.FormatId format, void delegate(scope ubyte[] data) dg) { 20936 version(X11) { 20937 20938 auto display = XDisplayConnection.get(); 20939 auto selectionAtom = GetAtom!"XdndSelection"(display); 20940 auto best = format; 20941 20942 static class X11GetSelectionHandler_Drop : X11GetSelectionHandler { 20943 20944 XDisplay* display; 20945 Atom selectionAtom; 20946 DraggableData.FormatId best; 20947 DraggableData.FormatId format; 20948 void delegate(scope ubyte[] data) dg; 20949 DragAndDropAction acceptedAction; 20950 Window sourceWindow; 20951 SimpleWindow win; 20952 this(XDisplay* display, SimpleWindow win, Window sourceWindow, DraggableData.FormatId format, Atom selectionAtom, DraggableData.FormatId best, void delegate(scope ubyte[] data) dg, DragAndDropAction acceptedAction) { 20953 this.display = display; 20954 this.win = win; 20955 this.sourceWindow = sourceWindow; 20956 this.format = format; 20957 this.selectionAtom = selectionAtom; 20958 this.best = best; 20959 this.dg = dg; 20960 this.acceptedAction = acceptedAction; 20961 } 20962 20963 20964 mixin X11GetSelectionHandler_Basics; 20965 20966 void handleData(Atom target, in ubyte[] data) { 20967 //if(target == GetAtom!"UTF8_STRING"(XDisplayConnection.get) || target == XA_STRING || target == GetAtom!"text/plain"(XDisplayConnection.get)) 20968 20969 dg(cast(ubyte[]) data); 20970 20971 if(acceptedAction != DragAndDropAction.none) { 20972 auto display = XDisplayConnection.get; 20973 20974 XClientMessageEvent xclient; 20975 20976 xclient.type = EventType.ClientMessage; 20977 xclient.window = sourceWindow; 20978 xclient.message_type = GetAtom!"XdndFinished"(display); 20979 xclient.format = 32; 20980 xclient.data.l[0] = win.impl.window; 20981 xclient.data.l[1] = 1; // drop successful 20982 xclient.data.l[2] = dndActionAtom(display, acceptedAction); 20983 20984 XSendEvent( 20985 display, 20986 sourceWindow, 20987 false, 20988 EventMask.NoEventMask, 20989 cast(XEvent*) &xclient 20990 ); 20991 20992 XFlush(display); 20993 } 20994 } 20995 20996 Atom findBestFormat(Atom[] answer) { 20997 Atom best = None; 20998 foreach(option; answer) { 20999 if(option == format) { 21000 best = option; 21001 break; 21002 } 21003 /* 21004 if(option == GetAtom!"UTF8_STRING"(XDisplayConnection.get)) { 21005 best = option; 21006 break; 21007 } else if(option == XA_STRING) { 21008 best = option; 21009 } else if(option == GetAtom!"text/plain"(XDisplayConnection.get)) { 21010 best = option; 21011 } 21012 */ 21013 } 21014 return best; 21015 } 21016 } 21017 21018 win.impl.getSelectionHandlers[selectionAtom] = new X11GetSelectionHandler_Drop(display, win, sourceWindow, format, selectionAtom, best, dg, acceptedAction); 21019 21020 XConvertSelection(display, selectionAtom, best, GetAtom!("SDD_DATA", true)(display), win.impl.window, dataTimestamp); 21021 21022 } else version(Windows) { 21023 21024 // clean up like DragLeave 21025 // pass effect back up 21026 21027 FORMATETC t; 21028 assert(format >= 0 && format <= ushort.max); 21029 t.cfFormat = cast(ushort) format; 21030 t.lindex = -1; 21031 t.dwAspect = DVASPECT.DVASPECT_CONTENT; 21032 t.tymed = TYMED.TYMED_HGLOBAL; 21033 21034 STGMEDIUM m; 21035 21036 if(pDataObj.GetData(&t, &m) != S_OK) { 21037 // fail 21038 } else { 21039 // succeed, take the data and clean up 21040 21041 // FIXME: ensure it is legit HGLOBAL 21042 auto handle = m.hGlobal; 21043 21044 if(handle) { 21045 auto sz = GlobalSize(handle); 21046 if(auto ptr = cast(ubyte*) GlobalLock(handle)) { 21047 scope(exit) GlobalUnlock(handle); 21048 scope(exit) GlobalFree(handle); 21049 21050 auto data = ptr[0 .. sz]; 21051 21052 dg(data); 21053 } 21054 } 21055 } 21056 } 21057 } 21058 21059 private: 21060 21061 version(X11) { 21062 SimpleWindow win; 21063 Window sourceWindow; 21064 Time dataTimestamp; 21065 21066 Atom[] xFormats; 21067 } 21068 version(Windows) { 21069 IDataObject pDataObj; 21070 } 21071 } 21072 21073 /++ 21074 A generic helper base class for making a drop handler with a preference list of custom types. 21075 This is the base for [TextDropHandler] and [FilesDropHandler] and you can use it for your own 21076 droppers too. 21077 21078 It assumes the whole window it used, but you can subclass to change that. 21079 21080 $(PITFALL This is not yet stable and may break in future versions without notice.) 21081 21082 History: 21083 Added February 19, 2021 21084 +/ 21085 /// Group: drag_and_drop 21086 class GenericDropHandlerBase : DropHandler { 21087 // no fancy state here so no need to do anything here 21088 void finish() { } 21089 void dragLeave() { } 21090 21091 private DragAndDropAction acceptedAction; 21092 private DraggableData.FormatId acceptedFormat; 21093 private void delegate(scope ubyte[]) acceptedHandler; 21094 21095 struct FormatHandler { 21096 DraggableData.FormatId format; 21097 void delegate(scope ubyte[]) handler; 21098 } 21099 21100 protected abstract FormatHandler[] formatHandlers(); 21101 21102 DragAndDropAction dragEnter(DropPackage* pkg) { 21103 debug(sdpy_dnd) { foreach(fmt; pkg.availableFormats()) writeln(fmt, " ", DraggableData.getFormatName(fmt)); } 21104 foreach(fmt; formatHandlers()) 21105 foreach(f; pkg.availableFormats()) 21106 if(f == fmt.format) { 21107 acceptedFormat = f; 21108 acceptedHandler = fmt.handler; 21109 return acceptedAction = DragAndDropAction.copy; 21110 } 21111 return acceptedAction = DragAndDropAction.none; 21112 } 21113 DropParameters dragOver(Point pt) { 21114 return DropParameters(acceptedAction); 21115 } 21116 21117 void drop(scope DropPackage* dropPackage) { 21118 if(!acceptedFormat || acceptedHandler is null) { 21119 debug(sdpy_dnd) { writeln("drop called w/ handler ", acceptedHandler, " and format ", acceptedFormat); } 21120 return; // prolly shouldn't happen anyway... 21121 } 21122 21123 dropPackage.getData(acceptedAction, acceptedFormat, acceptedHandler); 21124 } 21125 } 21126 21127 /++ 21128 A simple handler for making your window accept drops of plain text. 21129 21130 $(PITFALL This is not yet stable and may break in future versions without notice.) 21131 21132 History: 21133 Added February 22, 2021 21134 +/ 21135 /// Group: drag_and_drop 21136 class TextDropHandler : GenericDropHandlerBase { 21137 private void delegate(in char[] text) dg; 21138 21139 /++ 21140 21141 +/ 21142 this(void delegate(in char[] text) dg) { 21143 this.dg = dg; 21144 } 21145 21146 protected override FormatHandler[] formatHandlers() { 21147 version(X11) 21148 return [ 21149 FormatHandler(GetAtom!"UTF8_STRING"(XDisplayConnection.get), &translator), 21150 FormatHandler(GetAtom!"text/plain;charset=utf-8"(XDisplayConnection.get), &translator), 21151 ]; 21152 else version(Windows) 21153 return [ 21154 FormatHandler(CF_UNICODETEXT, &translator), 21155 ]; 21156 else throw new NotYetImplementedException(); 21157 } 21158 21159 private void translator(scope ubyte[] data) { 21160 version(X11) 21161 dg(cast(char[]) data); 21162 else version(Windows) 21163 dg(makeUtf8StringFromWindowsString(cast(wchar[]) data)); 21164 } 21165 } 21166 21167 /++ 21168 A simple handler for making your window accept drops of files, issued to you as file names. 21169 21170 $(PITFALL This is not yet stable and may break in future versions without notice.) 21171 21172 History: 21173 Added February 22, 2021 21174 +/ 21175 /// Group: drag_and_drop 21176 21177 class FilesDropHandler : GenericDropHandlerBase { 21178 private void delegate(in char[][]) dg; 21179 21180 /++ 21181 21182 +/ 21183 this(void delegate(in char[][] fileNames) dg) { 21184 this.dg = dg; 21185 } 21186 21187 protected override FormatHandler[] formatHandlers() { 21188 version(X11) 21189 return [ 21190 FormatHandler(GetAtom!"text/uri-list"(XDisplayConnection.get), &translator), 21191 ]; 21192 else version(Windows) 21193 return [ 21194 FormatHandler(CF_HDROP, &translator), 21195 ]; 21196 else throw new NotYetImplementedException(); 21197 } 21198 21199 private void translator(scope ubyte[] data) { 21200 version(X11) { 21201 char[] listString = cast(char[]) data; 21202 char[][16] buffer; 21203 int count; 21204 char[][] result = buffer[]; 21205 21206 void commit(char[] s) { 21207 if(count == result.length) 21208 result.length += 16; 21209 if(s.length > 7 && s[0 ..7] == "file://") 21210 s = s[7 .. $]; // FIXME: also may need to trim out the host and do some entity decoding 21211 result[count++] = s; 21212 } 21213 21214 size_t last; 21215 foreach(idx, char c; listString) { 21216 if(c == '\n') { 21217 commit(listString[last .. idx - 1]); // a \r 21218 last = idx + 1; // a \n 21219 } 21220 } 21221 21222 if(last < listString.length) { 21223 commit(listString[last .. $]); 21224 } 21225 21226 // FIXME: they are uris now, should I translate it to local file names? 21227 // of course the host name is supposed to be there cuz of X rokking... 21228 21229 dg(result[0 .. count]); 21230 } else version(Windows) { 21231 21232 static struct DROPFILES { 21233 DWORD pFiles; 21234 POINT pt; 21235 BOOL fNC; 21236 BOOL fWide; 21237 } 21238 21239 21240 const(char)[][16] buffer; 21241 int count; 21242 const(char)[][] result = buffer[]; 21243 size_t last; 21244 21245 void commitA(in char[] stuff) { 21246 if(count == result.length) 21247 result.length += 16; 21248 result[count++] = stuff; 21249 } 21250 21251 void commitW(in wchar[] stuff) { 21252 commitA(makeUtf8StringFromWindowsString(stuff)); 21253 } 21254 21255 void magic(T)(T chars) { 21256 size_t idx; 21257 while(chars[idx]) { 21258 last = idx; 21259 while(chars[idx]) { 21260 idx++; 21261 } 21262 static if(is(T == char*)) 21263 commitA(chars[last .. idx]); 21264 else 21265 commitW(chars[last .. idx]); 21266 idx++; 21267 } 21268 } 21269 21270 auto df = cast(DROPFILES*) data.ptr; 21271 if(df.fWide) { 21272 wchar* chars = cast(wchar*) (data.ptr + df.pFiles); 21273 magic(chars); 21274 } else { 21275 char* chars = cast(char*) (data.ptr + df.pFiles); 21276 magic(chars); 21277 } 21278 dg(result[0 .. count]); 21279 } 21280 else throw new NotYetImplementedException(); 21281 } 21282 } 21283 21284 /++ 21285 Interface to describe data being dragged. See also [draggable] helper function. 21286 21287 $(PITFALL This is not yet stable and may break in future versions without notice.) 21288 21289 History: 21290 Added February 19, 2021 21291 +/ 21292 interface DraggableData { 21293 version(X11) 21294 alias FormatId = Atom; 21295 else 21296 alias FormatId = uint; 21297 /++ 21298 Gets the platform-specific FormatId associated with the given named format. 21299 21300 This may be a MIME type, but may also be other various strings defined by the 21301 programs you want to interoperate with. 21302 21303 FIXME: sdpy needs to offer data adapter things that look for compatible formats 21304 and convert it to some particular type for you. 21305 +/ 21306 static FormatId getFormatId(string name)() { 21307 version(X11) 21308 return GetAtom!name(XDisplayConnection.get); 21309 else version(Windows) { 21310 static UINT cache; 21311 if(!cache) 21312 cache = RegisterClipboardFormatA(name); 21313 return cache; 21314 } else 21315 throw new NotYetImplementedException(); 21316 } 21317 21318 /++ 21319 Looks up a string to represent the name for the given format, if there is one. 21320 21321 You should avoid using this function because it is slow. It is provided more for 21322 debugging than for primary use. 21323 +/ 21324 static string getFormatName(FormatId format) { 21325 version(X11) { 21326 if(format == 0) 21327 return "None"; 21328 else 21329 return getAtomName(format, XDisplayConnection.get); 21330 } else version(Windows) { 21331 switch(format) { 21332 case CF_UNICODETEXT: return "CF_UNICODETEXT"; 21333 case CF_DIBV5: return "CF_DIBV5"; 21334 case CF_RIFF: return "CF_RIFF"; 21335 case CF_WAVE: return "CF_WAVE"; 21336 case CF_HDROP: return "CF_HDROP"; 21337 default: 21338 char[1024] name; 21339 auto count = GetClipboardFormatNameA(format, name.ptr, name.length); 21340 return name[0 .. count].idup; 21341 } 21342 } else throw new NotYetImplementedException(); 21343 } 21344 21345 FormatId[] availableFormats(); 21346 // Return the slice of data you filled, empty slice if done. 21347 // this is to support the incremental thing 21348 ubyte[] getData(FormatId format, return scope ubyte[] data); 21349 21350 size_t dataLength(FormatId format); 21351 } 21352 21353 /++ 21354 $(PITFALL This is not yet stable and may break in future versions without notice.) 21355 21356 History: 21357 Added February 19, 2021 21358 +/ 21359 DraggableData draggable(string s) { 21360 version(X11) 21361 return new class X11SetSelectionHandler_Text, DraggableData { 21362 this() { 21363 super(s); 21364 } 21365 21366 override FormatId[] availableFormats() { 21367 return X11SetSelectionHandler_Text.availableFormats(); 21368 } 21369 21370 override ubyte[] getData(FormatId format, return scope ubyte[] data) { 21371 return X11SetSelectionHandler_Text.getData(format, data); 21372 } 21373 21374 size_t dataLength(FormatId format) { 21375 return s.length; 21376 } 21377 }; 21378 else version(Windows) 21379 return new class DraggableData { 21380 FormatId[] availableFormats() { 21381 return [CF_UNICODETEXT]; 21382 } 21383 21384 ubyte[] getData(FormatId format, return scope ubyte[] data) { 21385 return cast(ubyte[]) makeWindowsString(s, cast(wchar[]) data, WindowsStringConversionFlags.convertNewLines | WindowsStringConversionFlags.zeroTerminate); 21386 } 21387 21388 size_t dataLength(FormatId format) { 21389 return sizeOfConvertedWstring(s, WindowsStringConversionFlags.convertNewLines | WindowsStringConversionFlags.zeroTerminate) * wchar.sizeof; 21390 } 21391 }; 21392 else 21393 throw new NotYetImplementedException(); 21394 } 21395 21396 /++ 21397 $(PITFALL This is not yet stable and may break in future versions without notice.) 21398 21399 History: 21400 Added February 19, 2021 21401 +/ 21402 /// Group: drag_and_drop 21403 int doDragDrop(SimpleWindow window, DraggableData handler, DragAndDropAction action = DragAndDropAction.copy) 21404 in { 21405 assert(window !is null); 21406 assert(handler !is null); 21407 } 21408 do 21409 { 21410 version(X11) { 21411 auto sh = cast(X11SetSelectionHandler) handler; 21412 if(sh is null) { 21413 // gotta make my own adapter. 21414 sh = new class X11SetSelectionHandler { 21415 mixin X11SetSelectionHandler_Basics; 21416 21417 Atom[] availableFormats() { return handler.availableFormats(); } 21418 ubyte[] getData(Atom format, return scope ubyte[] data) { 21419 return handler.getData(format, data); 21420 } 21421 21422 // since the drop selection is only ever used once it isn't important 21423 // to reset it. 21424 void done() {} 21425 }; 21426 } 21427 return doDragDropX11(window, sh, action); 21428 } else version(Windows) { 21429 return doDragDropWindows(window, handler, action); 21430 } else throw new NotYetImplementedException(); 21431 } 21432 21433 version(Windows) 21434 private int doDragDropWindows(SimpleWindow window, DraggableData handler, DragAndDropAction action = DragAndDropAction.copy) { 21435 IDataObject obj = new class IDataObject { 21436 ULONG refCount; 21437 ULONG AddRef() { 21438 return ++refCount; 21439 } 21440 ULONG Release() { 21441 return --refCount; 21442 } 21443 HRESULT QueryInterface(const (IID)*riid, LPVOID *ppv) { 21444 if (IID_IUnknown == *riid) { 21445 *ppv = cast(void*) cast(IUnknown) this; 21446 } 21447 else if (IID_IDataObject == *riid) { 21448 *ppv = cast(void*) cast(IDataObject) this; 21449 } 21450 else { 21451 *ppv = null; 21452 return E_NOINTERFACE; 21453 } 21454 21455 AddRef(); 21456 return NOERROR; 21457 } 21458 21459 HRESULT DAdvise(FORMATETC* pformatetc, DWORD advf, IAdviseSink pAdvSink, DWORD* pdwConnection) { 21460 // writeln("Advise"); 21461 return E_NOTIMPL; 21462 } 21463 HRESULT DUnadvise(DWORD dwConnection) { 21464 return E_NOTIMPL; 21465 } 21466 HRESULT EnumDAdvise(IEnumSTATDATA* ppenumAdvise) { 21467 // writeln("EnumDAdvise"); 21468 return OLE_E_ADVISENOTSUPPORTED; 21469 } 21470 // tell what formats it supports 21471 21472 FORMATETC[] types; 21473 this() { 21474 FORMATETC t; 21475 foreach(ty; handler.availableFormats()) { 21476 assert(ty <= ushort.max && ty >= 0); 21477 t.cfFormat = cast(ushort) ty; 21478 t.lindex = -1; 21479 t.dwAspect = DVASPECT.DVASPECT_CONTENT; 21480 t.tymed = TYMED.TYMED_HGLOBAL; 21481 } 21482 types ~= t; 21483 } 21484 HRESULT EnumFormatEtc(DWORD dwDirection, IEnumFORMATETC* ppenumFormatEtc) { 21485 if(dwDirection == DATADIR.DATADIR_GET) { 21486 *ppenumFormatEtc = new class IEnumFORMATETC { 21487 ULONG refCount; 21488 ULONG AddRef() { 21489 return ++refCount; 21490 } 21491 ULONG Release() { 21492 return --refCount; 21493 } 21494 HRESULT QueryInterface(const (IID)*riid, LPVOID *ppv) { 21495 if (IID_IUnknown == *riid) { 21496 *ppv = cast(void*) cast(IUnknown) this; 21497 } 21498 else if (IID_IEnumFORMATETC == *riid) { 21499 *ppv = cast(void*) cast(IEnumFORMATETC) this; 21500 } 21501 else { 21502 *ppv = null; 21503 return E_NOINTERFACE; 21504 } 21505 21506 AddRef(); 21507 return NOERROR; 21508 } 21509 21510 21511 int pos; 21512 this() { 21513 pos = 0; 21514 } 21515 21516 HRESULT Clone(IEnumFORMATETC* ppenum) { 21517 // writeln("clone"); 21518 return E_NOTIMPL; // FIXME 21519 } 21520 21521 // Caller is responsible for freeing memory 21522 HRESULT Next(ULONG celt, FORMATETC* rgelt, ULONG* pceltFetched) { 21523 // fetched may be null if celt is one 21524 if(celt != 1) 21525 return E_NOTIMPL; // FIXME 21526 21527 if(celt + pos > types.length) 21528 return S_FALSE; 21529 21530 *rgelt = types[pos++]; 21531 21532 if(pceltFetched !is null) 21533 *pceltFetched = 1; 21534 21535 // writeln("ok celt ", celt); 21536 return S_OK; 21537 } 21538 21539 HRESULT Reset() { 21540 pos = 0; 21541 return S_OK; 21542 } 21543 21544 HRESULT Skip(ULONG celt) { 21545 if(celt + pos <= types.length) { 21546 pos += celt; 21547 return S_OK; 21548 } 21549 return S_FALSE; 21550 } 21551 }; 21552 21553 return S_OK; 21554 } else 21555 return E_NOTIMPL; 21556 } 21557 // given a format, return the format you'd prefer to use cuz it is identical 21558 HRESULT GetCanonicalFormatEtc(FORMATETC* pformatectIn, FORMATETC* pformatetcOut) { 21559 // FIXME: prolly could be better but meh 21560 // writeln("gcf: ", *pformatectIn); 21561 *pformatetcOut = *pformatectIn; 21562 return S_OK; 21563 } 21564 HRESULT GetData(FORMATETC* pformatetcIn, STGMEDIUM* pmedium) { 21565 foreach(ty; types) { 21566 if(ty == *pformatetcIn) { 21567 auto format = ty.cfFormat; 21568 // writeln("A: ", *pformatetcIn, "\nB: ", ty); 21569 STGMEDIUM medium; 21570 medium.tymed = TYMED.TYMED_HGLOBAL; 21571 21572 auto sz = handler.dataLength(format); 21573 auto handle = GlobalAlloc(GMEM_MOVEABLE, sz); 21574 if(handle is null) throw new WindowsApiException("GlobalAlloc", GetLastError()); 21575 if(auto data = cast(wchar*) GlobalLock(handle)) { 21576 auto slice = data[0 .. sz]; 21577 scope(exit) 21578 GlobalUnlock(handle); 21579 21580 handler.getData(format, cast(ubyte[]) slice[]); 21581 } 21582 21583 21584 medium.hGlobal = handle; // FIXME 21585 *pmedium = medium; 21586 return S_OK; 21587 } 21588 } 21589 return DV_E_FORMATETC; 21590 } 21591 HRESULT GetDataHere(FORMATETC* pformatetcIn, STGMEDIUM* pmedium) { 21592 // writeln("GDH: ", *pformatetcIn); 21593 return E_NOTIMPL; // FIXME 21594 } 21595 HRESULT QueryGetData(FORMATETC* pformatetc) { 21596 auto search = *pformatetc; 21597 search.tymed &= TYMED.TYMED_HGLOBAL; 21598 foreach(ty; types) 21599 if(ty == search) { 21600 // writeln("QueryGetData ", search, " ", types[0]); 21601 return S_OK; 21602 } 21603 if(pformatetc.cfFormat==CF_UNICODETEXT) { 21604 //writeln("QueryGetData FALSE ", search, " ", types[0]); 21605 } 21606 return S_FALSE; 21607 } 21608 HRESULT SetData(FORMATETC* pformatetc, STGMEDIUM* pmedium, BOOL fRelease) { 21609 // writeln("SetData: "); 21610 return E_NOTIMPL; 21611 } 21612 }; 21613 21614 21615 IDropSource src = new class IDropSource { 21616 ULONG refCount; 21617 ULONG AddRef() { 21618 return ++refCount; 21619 } 21620 ULONG Release() { 21621 return --refCount; 21622 } 21623 HRESULT QueryInterface(const (IID)*riid, LPVOID *ppv) { 21624 if (IID_IUnknown == *riid) { 21625 *ppv = cast(void*) cast(IUnknown) this; 21626 } 21627 else if (IID_IDropSource == *riid) { 21628 *ppv = cast(void*) cast(IDropSource) this; 21629 } 21630 else { 21631 *ppv = null; 21632 return E_NOINTERFACE; 21633 } 21634 21635 AddRef(); 21636 return NOERROR; 21637 } 21638 21639 int QueryContinueDrag(int fEscapePressed, uint grfKeyState) { 21640 if(fEscapePressed) 21641 return DRAGDROP_S_CANCEL; 21642 if(!(grfKeyState & MK_LBUTTON)) 21643 return DRAGDROP_S_DROP; 21644 return S_OK; 21645 } 21646 21647 int GiveFeedback(uint dwEffect) { 21648 return DRAGDROP_S_USEDEFAULTCURSORS; 21649 } 21650 }; 21651 21652 DWORD effect; 21653 21654 if(action == DragAndDropAction.none) assert(0, "Don't drag something with a none effect."); 21655 21656 DROPEFFECT de = win32DragAndDropAction(action); 21657 21658 // I'm not as concerned about the GC here since DoDragDrop blocks so the stack frame still sane the whole time 21659 // but still prolly a FIXME 21660 21661 auto ret = DoDragDrop(obj, src, de, &effect); 21662 /+ 21663 if(ret == DRAGDROP_S_DROP) 21664 writeln("drop ", effect); 21665 else if(ret == DRAGDROP_S_CANCEL) 21666 writeln("cancel"); 21667 else if(ret == S_OK) 21668 writeln("ok"); 21669 else writeln(ret); 21670 +/ 21671 21672 return ret; 21673 } 21674 21675 version(Windows) 21676 DROPEFFECT win32DragAndDropAction(DragAndDropAction action) { 21677 DROPEFFECT de; 21678 21679 with(DragAndDropAction) 21680 with(DROPEFFECT) 21681 final switch(action) { 21682 case none: de = DROPEFFECT_NONE; break; 21683 case copy: de = DROPEFFECT_COPY; break; 21684 case move: de = DROPEFFECT_MOVE; break; 21685 case link: de = DROPEFFECT_LINK; break; 21686 case ask: throw new Exception("ask not implemented yet"); 21687 case custom: throw new Exception("custom not implemented yet"); 21688 } 21689 21690 return de; 21691 } 21692 21693 21694 /++ 21695 History: 21696 Added February 19, 2021 21697 +/ 21698 /// Group: drag_and_drop 21699 void enableDragAndDrop(SimpleWindow window, DropHandler handler) { 21700 version(X11) { 21701 auto display = XDisplayConnection.get; 21702 21703 Atom atom = 5; // right??? 21704 21705 XChangeProperty( 21706 display, 21707 window.impl.window, 21708 GetAtom!"XdndAware"(display), 21709 XA_ATOM, 21710 32 /* bits */, 21711 PropModeReplace, 21712 &atom, 21713 1); 21714 21715 window.dropHandler = handler; 21716 } else version(Windows) { 21717 21718 initDnd(); 21719 21720 auto dropTarget = new class (handler) IDropTarget { 21721 DropHandler handler; 21722 this(DropHandler handler) { 21723 this.handler = handler; 21724 } 21725 ULONG refCount; 21726 ULONG AddRef() { 21727 return ++refCount; 21728 } 21729 ULONG Release() { 21730 return --refCount; 21731 } 21732 HRESULT QueryInterface(const (IID)*riid, LPVOID *ppv) { 21733 if (IID_IUnknown == *riid) { 21734 *ppv = cast(void*) cast(IUnknown) this; 21735 } 21736 else if (IID_IDropTarget == *riid) { 21737 *ppv = cast(void*) cast(IDropTarget) this; 21738 } 21739 else { 21740 *ppv = null; 21741 return E_NOINTERFACE; 21742 } 21743 21744 AddRef(); 21745 return NOERROR; 21746 } 21747 21748 21749 // /////////////////// 21750 21751 HRESULT DragEnter(IDataObject pDataObj, DWORD grfKeyState, POINTL pt, DWORD* pdwEffect) { 21752 DropPackage dropPackage = DropPackage(pDataObj); 21753 *pdwEffect = win32DragAndDropAction(handler.dragEnter(&dropPackage)); 21754 return S_OK; // https://docs.microsoft.com/en-us/windows/win32/api/oleidl/nf-oleidl-idroptarget-dragenter 21755 } 21756 21757 HRESULT DragLeave() { 21758 handler.dragLeave(); 21759 // release the IDataObject if needed 21760 return S_OK; 21761 } 21762 21763 HRESULT DragOver(DWORD grfKeyState, POINTL pt, DWORD* pdwEffect) { 21764 auto res = handler.dragOver(Point(pt.x, pt.y)); // FIXME: translate screen coordinates back to window coordinates 21765 21766 *pdwEffect = win32DragAndDropAction(res.action); 21767 // same as DragEnter basically 21768 return S_OK; 21769 } 21770 21771 HRESULT Drop(IDataObject pDataObj, DWORD grfKeyState, POINTL pt, DWORD* pdwEffect) { 21772 DropPackage pkg = DropPackage(pDataObj); 21773 handler.drop(&pkg); 21774 21775 return S_OK; 21776 } 21777 }; 21778 // Windows can hold on to the handler and try to call it 21779 // during which time the GC can't see it. so important to 21780 // manually manage this. At some point i'll FIXME and make 21781 // all my com instances manually managed since they supposed 21782 // to respect the refcount. 21783 import core.memory; 21784 GC.addRoot(cast(void*) dropTarget); 21785 21786 if(RegisterDragDrop(window.impl.hwnd, dropTarget) != S_OK) 21787 throw new WindowsApiException("RegisterDragDrop", GetLastError()); 21788 21789 window.dropHandler = handler; 21790 } else throw new NotYetImplementedException(); 21791 } 21792 21793 21794 21795 static if(UsingSimpledisplayX11) { 21796 21797 enum _NET_WM_STATE_ADD = 1; 21798 enum _NET_WM_STATE_REMOVE = 0; 21799 enum _NET_WM_STATE_TOGGLE = 2; 21800 21801 /// X-specific. Use [SimpleWindow.requestAttention] instead for most cases. 21802 void demandAttention(SimpleWindow window, bool needs = true) { 21803 demandAttention(window.impl.window, needs); 21804 } 21805 21806 /// ditto 21807 void demandAttention(Window window, bool needs = true) { 21808 setNetWmStateAtom(window, GetAtom!("_NET_WM_STATE_DEMANDS_ATTENTION", false)(XDisplayConnection.get), needs); 21809 } 21810 21811 void setNetWmStateAtom(Window window, Atom atom, bool set = true, Atom atom2 = None) { 21812 auto display = XDisplayConnection.get(); 21813 if(atom == None) 21814 return; // non-failure error 21815 //auto atom2 = GetAtom!"_NET_WM_STATE_SHADED"(display); 21816 21817 XClientMessageEvent xclient; 21818 21819 xclient.type = EventType.ClientMessage; 21820 xclient.window = window; 21821 xclient.message_type = GetAtom!"_NET_WM_STATE"(display); 21822 xclient.format = 32; 21823 xclient.data.l[0] = set ? _NET_WM_STATE_ADD : _NET_WM_STATE_REMOVE; 21824 xclient.data.l[1] = atom; 21825 xclient.data.l[2] = atom2; 21826 xclient.data.l[3] = 1; 21827 // [3] == source. 0 == unknown, 1 == app, 2 == else 21828 21829 XSendEvent( 21830 display, 21831 RootWindow(display, DefaultScreen(display)), 21832 false, 21833 EventMask.SubstructureRedirectMask | EventMask.SubstructureNotifyMask, 21834 cast(XEvent*) &xclient 21835 ); 21836 21837 /+ 21838 XChangeProperty( 21839 display, 21840 window.impl.window, 21841 GetAtom!"_NET_WM_STATE"(display), 21842 XA_ATOM, 21843 32 /* bits */, 21844 PropModeAppend, 21845 &atom, 21846 1); 21847 +/ 21848 } 21849 21850 private Atom dndActionAtom(Display* display, DragAndDropAction action) { 21851 Atom actionAtom; 21852 with(DragAndDropAction) 21853 final switch(action) { 21854 case none: actionAtom = None; break; 21855 case copy: actionAtom = GetAtom!"XdndActionCopy"(display); break; 21856 case move: actionAtom = GetAtom!"XdndActionMove"(display); break; 21857 case link: actionAtom = GetAtom!"XdndActionLink"(display); break; 21858 case ask: actionAtom = GetAtom!"XdndActionAsk"(display); break; 21859 case custom: actionAtom = GetAtom!"XdndActionCustom"(display); break; 21860 } 21861 21862 return actionAtom; 21863 } 21864 21865 private int doDragDropX11(SimpleWindow window, X11SetSelectionHandler handler, DragAndDropAction action) { 21866 // FIXME: I need to show user feedback somehow. 21867 auto display = XDisplayConnection.get; 21868 21869 auto actionAtom = dndActionAtom(display, action); 21870 assert(actionAtom, "Don't use action none to accept a drop"); 21871 21872 setX11Selection!"XdndSelection"(window, handler, null); 21873 21874 auto oldKeyHandler = window.handleKeyEvent; 21875 scope(exit) window.handleKeyEvent = oldKeyHandler; 21876 21877 auto oldCharHandler = window.handleCharEvent; 21878 scope(exit) window.handleCharEvent = oldCharHandler; 21879 21880 auto oldMouseHandler = window.handleMouseEvent; 21881 scope(exit) window.handleMouseEvent = oldMouseHandler; 21882 21883 Window[Window] eligibility; // 0 == not eligible, otherwise it is the window id of an eligible child 21884 21885 import core.sys.posix.sys.time; 21886 timeval tv; 21887 gettimeofday(&tv, null); 21888 21889 Time dataTimestamp = tv.tv_sec * 1000 + tv.tv_usec / 1000; 21890 21891 Time lastMouseTimestamp; 21892 21893 bool dnding = true; 21894 Window lastIn = None; 21895 21896 void leave() { 21897 if(lastIn == None) 21898 return; 21899 21900 XEvent ev; 21901 ev.xclient.type = EventType.ClientMessage; 21902 ev.xclient.window = lastIn; 21903 ev.xclient.message_type = GetAtom!("XdndLeave", true)(display); 21904 ev.xclient.format = 32; 21905 ev.xclient.data.l[0] = window.impl.window; 21906 21907 XSendEvent(XDisplayConnection.get, lastIn, false, EventMask.NoEventMask, &ev); 21908 XFlush(display); 21909 21910 lastIn = None; 21911 } 21912 21913 void enter(Window w) { 21914 assert(lastIn == None); 21915 21916 lastIn = w; 21917 21918 XEvent ev; 21919 ev.xclient.type = EventType.ClientMessage; 21920 ev.xclient.window = lastIn; 21921 ev.xclient.message_type = GetAtom!("XdndEnter", true)(display); 21922 ev.xclient.format = 32; 21923 ev.xclient.data.l[0] = window.impl.window; 21924 ev.xclient.data.l[1] = (5 << 24) | 0; // version 5, no more sources. FIXME source types 21925 21926 auto types = handler.availableFormats(); 21927 assert(types.length > 0); 21928 21929 ev.xclient.data.l[2] = types[0]; 21930 if(types.length > 1) 21931 ev.xclient.data.l[3] = types[1]; 21932 if(types.length > 2) 21933 ev.xclient.data.l[4] = types[2]; 21934 21935 // FIXME: other types?!?!? and make sure we skip TARGETS 21936 21937 XSendEvent(XDisplayConnection.get, lastIn, false, EventMask.NoEventMask, &ev); 21938 XFlush(display); 21939 } 21940 21941 void position(int rootX, int rootY) { 21942 assert(lastIn != None); 21943 21944 XEvent ev; 21945 ev.xclient.type = EventType.ClientMessage; 21946 ev.xclient.window = lastIn; 21947 ev.xclient.message_type = GetAtom!("XdndPosition", true)(display); 21948 ev.xclient.format = 32; 21949 ev.xclient.data.l[0] = window.impl.window; 21950 ev.xclient.data.l[1] = 0; // reserved 21951 ev.xclient.data.l[2] = (rootX << 16) | rootY; 21952 ev.xclient.data.l[3] = dataTimestamp; 21953 ev.xclient.data.l[4] = actionAtom; 21954 21955 XSendEvent(XDisplayConnection.get, lastIn, false, EventMask.NoEventMask, &ev); 21956 XFlush(display); 21957 21958 } 21959 21960 void drop() { 21961 XEvent ev; 21962 ev.xclient.type = EventType.ClientMessage; 21963 ev.xclient.window = lastIn; 21964 ev.xclient.message_type = GetAtom!("XdndDrop", true)(display); 21965 ev.xclient.format = 32; 21966 ev.xclient.data.l[0] = window.impl.window; 21967 ev.xclient.data.l[1] = 0; // reserved 21968 ev.xclient.data.l[2] = dataTimestamp; 21969 21970 XSendEvent(XDisplayConnection.get, lastIn, false, EventMask.NoEventMask, &ev); 21971 XFlush(display); 21972 21973 lastIn = None; 21974 dnding = false; 21975 } 21976 21977 // fyi nativeEventHandler can return 0 if it handles it, or otherwise it goes back to the normal handler 21978 // but idk if i should... 21979 21980 window.setEventHandlers( 21981 delegate(KeyEvent ev) { 21982 if(ev.pressed == true && ev.key == Key.Escape) { 21983 // cancel 21984 dnding = false; 21985 } 21986 }, 21987 delegate(MouseEvent ev) { 21988 if(ev.timestamp < lastMouseTimestamp) 21989 return; 21990 21991 lastMouseTimestamp = ev.timestamp; 21992 21993 if(ev.type == MouseEventType.motion) { 21994 auto display = XDisplayConnection.get; 21995 auto root = RootWindow(display, DefaultScreen(display)); 21996 21997 Window topWindow; 21998 int rootX, rootY; 21999 22000 XTranslateCoordinates(display, window.impl.window, root, ev.x, ev.y, &rootX, &rootY, &topWindow); 22001 22002 if(topWindow == None) 22003 return; 22004 22005 top: 22006 if(auto result = topWindow in eligibility) { 22007 auto dropWindow = *result; 22008 if(dropWindow == None) { 22009 leave(); 22010 return; 22011 } 22012 22013 if(dropWindow != lastIn) { 22014 leave(); 22015 enter(dropWindow); 22016 position(rootX, rootY); 22017 } else { 22018 position(rootX, rootY); 22019 } 22020 } else { 22021 // determine eligibility 22022 auto data = cast(Atom[]) getX11PropertyData(topWindow, GetAtom!"XdndAware"(display), XA_ATOM); 22023 if(data.length == 1) { 22024 // in case there is no WM or it isn't reparenting 22025 eligibility[topWindow] = (data[0] == 5) ? topWindow : None; // FIXME I'm supposed to handle older versions too but meh 22026 } else { 22027 22028 Window tryScanChildren(Window search, int maxRecurse) { 22029 // could be reparenting window manager, so gotta check the next few children too 22030 Window child; 22031 int x; 22032 int y; 22033 XTranslateCoordinates(display, window.impl.window, search, ev.x, ev.y, &x, &y, &child); 22034 22035 if(child == None) 22036 return None; 22037 auto data = cast(Atom[]) getX11PropertyData(child, GetAtom!"XdndAware"(display), XA_ATOM); 22038 if(data.length == 1) { 22039 return (data[0] == 5) ? child : None; // FIXME I'm supposed to handle older versions too but meh 22040 } else { 22041 if(maxRecurse) 22042 return tryScanChildren(child, maxRecurse - 1); 22043 else 22044 return None; 22045 } 22046 22047 } 22048 22049 // if a WM puts more than 3 layers on it, like wtf is it doing, screw that. 22050 auto topResult = tryScanChildren(topWindow, 3); 22051 // it is easy to have a false negative due to the mouse going over a WM 22052 // child window like the close button if separate from the frame... so I 22053 // can't really cache negatives, :( 22054 if(topResult != None) { 22055 eligibility[topWindow] = topResult; 22056 goto top; // reload to do the positioning iff eligibility changed lest we endless loop 22057 } 22058 } 22059 22060 } 22061 22062 } else if(ev.type == MouseEventType.buttonReleased) { 22063 drop(); 22064 dnding = false; 22065 } 22066 } 22067 ); 22068 22069 window.grabInput(); 22070 scope(exit) 22071 window.releaseInputGrab(); 22072 22073 22074 EventLoop.get.run(() => dnding); 22075 22076 return 0; 22077 } 22078 22079 /// X-specific 22080 TrueColorImage getWindowNetWmIcon(Window window) { 22081 try { 22082 auto display = XDisplayConnection.get; 22083 22084 auto data = getX11PropertyData (window, GetAtom!"_NET_WM_ICON"(display), XA_CARDINAL); 22085 22086 if (data.length > arch_ulong.sizeof * 2) { 22087 auto meta = cast(arch_ulong[]) (data[0 .. arch_ulong.sizeof * 2]); 22088 // these are an array of rgba images that we have to convert into pixmaps ourself 22089 22090 int width = cast(int) meta[0]; 22091 int height = cast(int) meta[1]; 22092 22093 auto bytes = cast(ubyte[]) (data[arch_ulong.sizeof * 2 .. $]); 22094 22095 static if(arch_ulong.sizeof == 4) { 22096 bytes = bytes[0 .. width * height * 4]; 22097 alias imageData = bytes; 22098 } else static if(arch_ulong.sizeof == 8) { 22099 bytes = bytes[0 .. width * height * 8]; 22100 auto imageData = new ubyte[](4 * width * height); 22101 } else static assert(0); 22102 22103 22104 22105 // this returns ARGB. Remember it is little-endian so 22106 // we have BGRA 22107 // our thing uses RGBA, which in little endian, is ABGR 22108 for(int idx = 0, idx2 = 0; idx < bytes.length; idx += arch_ulong.sizeof, idx2 += 4) { 22109 auto r = bytes[idx + 2]; 22110 auto g = bytes[idx + 1]; 22111 auto b = bytes[idx + 0]; 22112 auto a = bytes[idx + 3]; 22113 22114 imageData[idx2 + 0] = r; 22115 imageData[idx2 + 1] = g; 22116 imageData[idx2 + 2] = b; 22117 imageData[idx2 + 3] = a; 22118 } 22119 22120 return new TrueColorImage(width, height, imageData); 22121 } 22122 22123 return null; 22124 } catch(Exception e) { 22125 return null; 22126 } 22127 } 22128 22129 } /* UsingSimpledisplayX11 */ 22130 22131 22132 void loadBinNameToWindowClassName () { 22133 import core.stdc.stdlib : realloc; 22134 version(linux) { 22135 // args[0] MAY be empty, so we'll just use this 22136 import core.sys.posix.unistd : readlink; 22137 char[1024] ebuf = void; // 1KB should be enough for everyone! 22138 auto len = readlink("/proc/self/exe", ebuf.ptr, ebuf.length); 22139 if (len < 1) return; 22140 } else /*version(Windows)*/ { 22141 import core.runtime : Runtime; 22142 if (Runtime.args.length == 0 || Runtime.args[0].length == 0) return; 22143 auto ebuf = Runtime.args[0]; 22144 auto len = ebuf.length; 22145 } 22146 auto pos = len; 22147 while (pos > 0 && ebuf[pos-1] != '/') --pos; 22148 sdpyWindowClassStr = cast(char*)realloc(sdpyWindowClassStr, len-pos+1); 22149 if (sdpyWindowClassStr is null) return; // oops 22150 sdpyWindowClassStr[0..len-pos+1] = 0; // just in case 22151 sdpyWindowClassStr[0..len-pos] = ebuf[pos..len]; 22152 } 22153 22154 /++ 22155 An interface representing a font that is drawn with custom facilities. 22156 22157 You might want [OperatingSystemFont] instead, which represents 22158 a font loaded and drawn by functions native to the operating system. 22159 22160 WARNING: I might still change this. 22161 +/ 22162 interface DrawableFont : MeasurableFont { 22163 /++ 22164 Please note the point is upperLeft, NOT baseline! This is the point of a bounding box of the string. 22165 22166 Implementations must use the painter's fillColor to draw a rectangle behind the string, 22167 then use the outlineColor to draw the string. It might alpha composite if there's a transparent 22168 fill color, but that's up to the implementation. 22169 +/ 22170 void drawString(ScreenPainter painter, Point upperLeft, in char[] text); 22171 22172 /++ 22173 Requests that the given string is added to the image cache. You should only do this rarely, but 22174 if you have a string that you know will be used over and over again, adding it to a cache can 22175 improve things (assuming the implementation actually has a cache; it is also valid for an implementation 22176 to implement this as a do-nothing method). 22177 +/ 22178 void cacheString(SimpleWindow window, Color foreground, Color background, string text); 22179 } 22180 22181 /++ 22182 Loads a true type font using [arsd.ttf] that can be drawn as images on windows 22183 through a [ScreenPainter]. That module must be compiled in if you choose to use this function. 22184 22185 You should also consider [OperatingSystemFont], which loads and draws a font with 22186 facilities native to the user's operating system. You might also consider 22187 [arsd.ttf.OpenGlLimitedFont] or using [arsd.nanovega] if you are making some kind 22188 of game, as they have their own ways to draw text too. 22189 22190 Be warned: this can be slow, especially on remote connections to the X server, since 22191 it needs to create and transfer bitmaps instead of just text. The [DrawableFont] interface 22192 offers [DrawableFont.cacheString] which can help with this, sometimes. You might want to 22193 experiment in your specific case. 22194 22195 Please note that the return type of [DrawableFont] also includes an implementation of 22196 [MeasurableFont]. 22197 +/ 22198 DrawableFont arsdTtfFont()(in ubyte[] data, int size) { 22199 import arsd.ttf; 22200 static class ArsdTtfFont : DrawableFont { 22201 TtfFont font; 22202 int size; 22203 this(in ubyte[] data, int size) { 22204 font = TtfFont(data); 22205 this.size = size; 22206 22207 22208 auto scale = stbtt_ScaleForPixelHeight(&font.font, size); 22209 int ascent_, descent_, line_gap; 22210 stbtt_GetFontVMetrics(&font.font, &ascent_, &descent_, &line_gap); 22211 22212 int advance, lsb; 22213 stbtt_GetCodepointHMetrics(&font.font, 'x', &advance, &lsb); 22214 xWidth = cast(int) (advance * scale); 22215 stbtt_GetCodepointHMetrics(&font.font, 'M', &advance, &lsb); 22216 MWidth = cast(int) (advance * scale); 22217 } 22218 22219 private int ascent_; 22220 private int descent_; 22221 private int xWidth; 22222 private int MWidth; 22223 22224 bool isMonospace() { 22225 return xWidth == MWidth; 22226 } 22227 int averageWidth() { 22228 return xWidth; 22229 } 22230 int height() { 22231 return size; 22232 } 22233 int ascent() { 22234 return ascent_; 22235 } 22236 int descent() { 22237 return descent_; 22238 } 22239 22240 int stringWidth(scope const(char)[] s, SimpleWindow window = null) { 22241 int width, height; 22242 font.getStringSize(s, size, width, height); 22243 return width; 22244 } 22245 22246 22247 22248 Sprite[string] cache; 22249 22250 void cacheString(SimpleWindow window, Color foreground, Color background, string text) { 22251 auto sprite = new Sprite(window, stringToImage(foreground, background, text)); 22252 cache[text] = sprite; 22253 } 22254 22255 Image stringToImage(Color fg, Color bg, in char[] text) { 22256 int width, height; 22257 auto data = font.renderString(text, size, width, height); 22258 auto image = new TrueColorImage(width, height); 22259 int pos = 0; 22260 foreach(y; 0 .. height) 22261 foreach(x; 0 .. width) { 22262 fg.a = data[0]; 22263 bg.a = 255; 22264 auto color = alphaBlend(fg, bg); 22265 image.imageData.bytes[pos++] = color.r; 22266 image.imageData.bytes[pos++] = color.g; 22267 image.imageData.bytes[pos++] = color.b; 22268 image.imageData.bytes[pos++] = data[0]; 22269 data = data[1 .. $]; 22270 } 22271 assert(data.length == 0); 22272 22273 return Image.fromMemoryImage(image); 22274 } 22275 22276 void drawString(ScreenPainter painter, Point upperLeft, in char[] text) { 22277 Sprite sprite = (text in cache) ? *(text in cache) : null; 22278 22279 auto fg = painter.impl._outlineColor; 22280 auto bg = painter.impl._fillColor; 22281 22282 if(sprite !is null) { 22283 auto w = cast(SimpleWindow) painter.window; 22284 assert(w !is null); 22285 22286 sprite.drawAt(painter, upperLeft); 22287 } else { 22288 painter.drawImage(upperLeft, stringToImage(fg, bg, text)); 22289 } 22290 } 22291 } 22292 22293 return new ArsdTtfFont(data, size); 22294 } 22295 22296 class NotYetImplementedException : Exception { 22297 this(string file = __FILE__, size_t line = __LINE__) { 22298 super("Not yet implemented", file, line); 22299 } 22300 } 22301 22302 /// 22303 __gshared bool librariesSuccessfullyLoaded = true; 22304 /// 22305 __gshared bool openGlLibrariesSuccessfullyLoaded = true; 22306 22307 private mixin template DynamicLoadSupplementalOpenGL(Iface) { 22308 mixin(staticForeachReplacement!Iface); 22309 22310 void loadDynamicLibrary() @nogc { 22311 (cast(void function() @nogc) &loadDynamicLibraryForReal)(); 22312 } 22313 22314 void loadDynamicLibraryForReal() { 22315 foreach(name; __traits(derivedMembers, Iface)) { 22316 mixin("alias tmp = " ~ name ~ ";"); 22317 tmp = cast(typeof(tmp)) glbindGetProcAddress(name); 22318 if(tmp is null) throw new Exception("load failure of function " ~ name ~ " from supplemental OpenGL"); 22319 } 22320 } 22321 } 22322 22323 private const(char)[] staticForeachReplacement(Iface)() pure { 22324 /* 22325 // just this for gdc 9.... 22326 // when i drop support for it and switch to gdc10, we can put this original back for a slight compile time ram decrease 22327 22328 static foreach(name; __traits(derivedMembers, Iface)) 22329 mixin("__gshared typeof(&__traits(getMember, Iface, name)) " ~ name ~ ";"); 22330 */ 22331 22332 char[] code = new char[](__traits(derivedMembers, Iface).length * 64); 22333 size_t pos; 22334 22335 void append(in char[] what) { 22336 if(pos + what.length > code.length) 22337 code.length = (code.length * 3) / 2; 22338 code[pos .. pos + what.length] = what[]; 22339 pos += what.length; 22340 } 22341 22342 foreach(name; __traits(derivedMembers, Iface)) { 22343 append(`__gshared typeof(&__traits(getMember, Iface, "`); 22344 append(name); 22345 append(`")) `); 22346 append(name); 22347 append(";"); 22348 } 22349 22350 return code[0 .. pos]; 22351 } 22352 22353 private mixin template DynamicLoad(Iface, string library, int majorVersion, alias success) { 22354 mixin(staticForeachReplacement!Iface); 22355 22356 private __gshared void* libHandle; 22357 private __gshared bool attempted; 22358 22359 void loadDynamicLibrary() @nogc { 22360 (cast(void function() @nogc) &loadDynamicLibraryForReal)(); 22361 } 22362 22363 bool loadAttempted() { 22364 return attempted; 22365 } 22366 bool loadSuccessful() { 22367 return libHandle !is null; 22368 } 22369 22370 void loadDynamicLibraryForReal() { 22371 attempted = true; 22372 version(Posix) { 22373 import core.sys.posix.dlfcn; 22374 version(OSX) { 22375 version(X11) 22376 libHandle = dlopen("/usr/X11/lib/lib" ~ library ~ ".dylib", RTLD_NOW); 22377 else 22378 libHandle = dlopen(library ~ ".dylib", RTLD_NOW); 22379 } else { 22380 libHandle = dlopen("lib" ~ library ~ ".so", RTLD_NOW); 22381 if(libHandle is null) 22382 libHandle = dlopen(("lib" ~ library ~ ".so." ~ toInternal!string(majorVersion) ~ "\0").ptr, RTLD_NOW); 22383 } 22384 22385 static void* loadsym(void* l, const char* name) { 22386 import core.stdc.stdlib; 22387 if(l is null) 22388 return &abort; 22389 return dlsym(l, name); 22390 } 22391 } else version(Windows) { 22392 import core.sys.windows.winbase; 22393 libHandle = LoadLibrary(library ~ ".dll"); 22394 static void* loadsym(void* l, const char* name) { 22395 import core.stdc.stdlib; 22396 if(l is null) 22397 return &abort; 22398 return GetProcAddress(l, name); 22399 } 22400 } 22401 if(libHandle is null) { 22402 success = false; 22403 //throw new Exception("load failure of library " ~ library); 22404 } 22405 foreach(name; __traits(derivedMembers, Iface)) { 22406 mixin("alias tmp = " ~ name ~ ";"); 22407 tmp = cast(typeof(tmp)) loadsym(libHandle, name); 22408 if(tmp is null) throw new Exception("load failure of function " ~ name ~ " from " ~ library); 22409 } 22410 } 22411 22412 void unloadDynamicLibrary() { 22413 version(Posix) { 22414 import core.sys.posix.dlfcn; 22415 dlclose(libHandle); 22416 } else version(Windows) { 22417 import core.sys.windows.winbase; 22418 FreeLibrary(libHandle); 22419 } 22420 foreach(name; __traits(derivedMembers, Iface)) 22421 mixin(name ~ " = null;"); 22422 } 22423 } 22424 22425 /+ 22426 The GC can be called from any thread, and a lot of cleanup must be done 22427 on the gui thread. Since the GC can interrupt any locks - including being 22428 triggered inside a critical section - it is vital to avoid deadlocks to get 22429 these functions called from the right place. 22430 22431 If the buffer overflows, things are going to get leaked. I'm kinda ok with that 22432 right now. 22433 22434 The cleanup function is run when the event loop gets around to it, which is just 22435 whenever there's something there after it has been woken up for other work. It does 22436 NOT wake up the loop itself - can't risk doing that from inside the GC in another thread. 22437 (Well actually it might be ok but i don't wanna mess with it right now.) 22438 +/ 22439 private struct CleanupQueue { 22440 import core.stdc.stdlib; 22441 22442 void queue(alias func, T...)(T args) { 22443 static struct Args { 22444 T args; 22445 } 22446 static struct RealJob { 22447 Job j; 22448 Args a; 22449 } 22450 static void call(Job* data) { 22451 auto rj = cast(RealJob*) data; 22452 func(rj.a.args); 22453 } 22454 22455 RealJob* thing = cast(RealJob*) malloc(RealJob.sizeof); 22456 thing.j.call = &call; 22457 thing.a.args = args; 22458 22459 buffer[tail++] = cast(Job*) thing; 22460 22461 // FIXME: set overflowed 22462 } 22463 22464 void process() { 22465 const tail = this.tail; 22466 22467 while(tail != head) { 22468 Job* job = cast(Job*) buffer[head++]; 22469 job.call(job); 22470 free(job); 22471 } 22472 22473 if(overflowed) 22474 throw new Exception("cleanup overflowed"); 22475 } 22476 22477 private: 22478 22479 ubyte tail; // must ONLY be written by queue 22480 ubyte head; // must ONLY be written by process 22481 bool overflowed; 22482 22483 static struct Job { 22484 void function(Job*) call; 22485 } 22486 22487 void*[256] buffer; 22488 } 22489 private __gshared CleanupQueue cleanupQueue; 22490 22491 // version(X11) 22492 /++ 22493 Returns the custom scaling factor read out of environment["ARSD_SCALING_FACTOR"]. 22494 22495 $(WARNING 22496 This function is exempted from stability guarantees. 22497 ) 22498 +/ 22499 float customScalingFactorForMonitor(int monitorNumber) { 22500 import core.stdc.stdlib; 22501 auto val = getenv("ARSD_SCALING_FACTOR"); 22502 22503 // FIXME: maybe we should assume a default nbased on the dpi thing if this isn't given 22504 if(val is null) 22505 return 1.0; 22506 22507 char[16] buffer = 0; 22508 int pos; 22509 22510 const(char)* at = val; 22511 22512 foreach(item; 0 .. monitorNumber + 1) { 22513 if(*at == 0) 22514 break; // reuse the last number when we at the end of the string 22515 pos = 0; 22516 while(pos + 1 < buffer.length && *at && *at != ';') { 22517 buffer[pos++] = *at; 22518 at++; 22519 } 22520 if(*at) 22521 at++; // skip the semicolon 22522 buffer[pos] = 0; 22523 } 22524 22525 //sdpyPrintDebugString(buffer[0 .. pos]); 22526 22527 import core.stdc.math; 22528 auto f = atof(buffer.ptr); 22529 22530 if(f <= 0.0 || isnan(f) || isinf(f)) 22531 return 1.0; 22532 22533 return f; 22534 } 22535 22536 void guiAbortProcess(string msg) { 22537 import core.stdc.stdlib; 22538 version(Windows) { 22539 WCharzBuffer t = WCharzBuffer(msg); 22540 MessageBoxW(null, t.ptr, "Program Termination"w.ptr, 0); 22541 } else { 22542 import core.stdc.stdio; 22543 fwrite(msg.ptr, 1, msg.length, stderr); 22544 msg = "\n"; 22545 fwrite(msg.ptr, 1, msg.length, stderr); 22546 fflush(stderr); 22547 } 22548 22549 abort(); 22550 } 22551 22552 private int minInternal(int a, int b) { 22553 return (a < b) ? a : b; 22554 } 22555 22556 private alias scriptable = arsd_jsvar_compatible;