1 // https://dpaste.dzfl.pl/7a77355acaec 2 3 /+ 4 To share some stuff between two opengl threads: 5 windows 6 https://www.khronos.org/opengl/wiki/OpenGL_and_multithreading 7 linux 8 https://stackoverflow.com/questions/18879520/sharing-opengl-objects-between-contexts-on-linux 9 +/ 10 11 12 // Search for: FIXME: leaks if multithreaded gc 13 14 // https://freedesktop.org/wiki/Specifications/XDND/ 15 16 // https://docs.microsoft.com/en-us/windows/win32/dataxchg/html-clipboard-format 17 18 // https://www.x.org/releases/X11R7.7/doc/libXext/dbelib.html 19 // https://www.x.org/releases/X11R7.6/doc/libXext/synclib.html 20 21 22 // on Mac with X11: -L-L/usr/X11/lib 23 24 /+ 25 26 * I might need to set modal hints too _NET_WM_STATE_MODAL and make sure that TRANSIENT_FOR legit works 27 28 Progress bar in taskbar 29 - i can probably just set a property on the window... 30 it sets that prop to an integer 0 .. 100. Taskbar 31 deletes it or window deletes it when it is handled. 32 - prolly display it as a nice little line at the bottom. 33 34 35 from gtk: 36 37 #define PROGRESS_HINT "_NET_WM_XAPP_PROGRESS" 38 #define PROGRESS_PULSE_HINT "_NET_WM_XAPP_PROGRESS_PULSE" 39 40 >+ if (cardinal > 0) 41 >+ { 42 >+ XChangeProperty (GDK_DISPLAY_XDISPLAY (display), 43 >+ xid, 44 >+ gdk_x11_get_xatom_by_name_for_display (display, atom_name), 45 >+ XA_CARDINAL, 32, 46 >+ PropModeReplace, 47 >+ (guchar *) &cardinal, 1); 48 >+ } 49 >+ else 50 >+ { 51 >+ XDeleteProperty (GDK_DISPLAY_XDISPLAY (display), 52 >+ xid, 53 >+ gdk_x11_get_xatom_by_name_for_display (display, atom_name)); 54 >+ } 55 56 from Windows: 57 58 see: https://docs.microsoft.com/en-us/windows/win32/api/shobjidl_core/nn-shobjidl_core-itaskbarlist3 59 60 interface 61 CoCreateInstance( CLSID_TaskbarList, nullptr, CLSCTX_ALL, __uuidof(ITaskbarList3), (LPVOID*)&m_pTL3 ); 62 auto msg = RegisterWindowMessage(TEXT(“TaskbarButtonCreated”)); 63 listen for msg, return TRUE 64 interface->SetProgressState(hwnd, TBPF_NORMAL); 65 interface->SetProgressValue(hwnd, 40, 100); 66 67 68 My new notification system. 69 - use a unix socket? or a x property? or a udp port? 70 - could of course also get on the dbus train but ugh. 71 - it could also reply with the info as a string for easy remote examination. 72 73 +/ 74 75 /* 76 Event Loop would be nices: 77 78 * add on idle - runs when nothing else happens 79 * which can specify how long to yield for 80 * send messages without a recipient window 81 * setTimeout 82 * setInterval 83 */ 84 85 /* 86 Classic games I want to add: 87 * my tetris clone 88 * pac man 89 */ 90 91 /* 92 Text layout needs a lot of work. Plain drawText is useful but too 93 limited. It will need some kind of text context thing which it will 94 update and you can pass it on and get more details out of it. 95 96 It will need a bounding box, a current cursor location that is updated 97 as drawing continues, and various changable facts (which can also be 98 changed on the painter i guess) like font, color, size, background, 99 etc. 100 101 We can also fetch the caret location from it somehow. 102 103 Should prolly be an overload of drawText 104 105 blink taskbar / demand attention cross platform. FlashWindow and demandAttention 106 107 WS_EX_NOACTIVATE 108 WS_CHILD - owner and owned vs parent and child. Does X have something similar? 109 full screen windows. Can just set the atom on X. Windows will be harder. 110 111 moving windows. resizing windows. 112 113 hide cursor, capture cursor, change cursor. 114 115 REMEMBER: simpledisplay does NOT have to do everything! It just needs to make 116 sure the pieces are there to do its job easily and make other jobs possible. 117 */ 118 119 /++ 120 simpledisplay.d (often abbreviated to "sdpy") provides basic cross-platform GUI-related functionality, 121 including creating windows, drawing on them, working with the clipboard, 122 timers, OpenGL, and more. However, it does NOT provide high level GUI 123 widgets. See my minigui.d, an extension to this module, for that 124 functionality. 125 126 simpledisplay provides cross-platform wrapping for Windows and Linux 127 (and perhaps other OSes that use X11), but also does not prevent you 128 from using the underlying facilities if you need them. It has a goal 129 of working efficiently over a remote X link (at least as far as Xlib 130 reasonably allows.) 131 132 simpledisplay depends on [arsd.color|color.d], which should be available from the 133 same place where you got this file. Other than that, however, it has 134 very few dependencies and ones that don't come with the OS and/or the 135 compiler are all opt-in. 136 137 simpledisplay.d's home base is on my arsd repo on Github. The file is: 138 https://github.com/adamdruppe/arsd/blob/master/simpledisplay.d 139 140 simpledisplay is basically stable. I plan to refactor the internals, 141 and may add new features and fix bugs, but It do not expect to 142 significantly change the API. It has been stable a few years already now. 143 144 Installation_instructions: 145 146 `simpledisplay.d` does not have any dependencies outside the 147 operating system and `color.d`, so it should just work most the 148 time, but there are a few caveats on some systems: 149 150 On Win32, you can pass `-L/subsystem:windows` if you don't want a 151 console to be automatically allocated. 152 153 Please note when compiling on Win64, you need to explicitly list 154 `-Lgdi32.lib -Luser32.lib` on the build command. If you want the Windows 155 subsystem too, use `-L/subsystem:windows -L/entry:mainCRTStartup`. 156 157 If using ldc instead of dmd, use `-L/entry:wmainCRTStartup` instead of `mainCRTStartup`; 158 note the "w". 159 160 I provided a `mixin EnableWindowsSubsystem;` helper to do those linker flags for you, 161 but you still need to use dmd -m32mscoff or -m64 (which dub does by default too fyi). 162 See [EnableWindowsSubsystem] for more information. 163 164 $(PITFALL 165 With the Windows subsystem, there is no console, so standard writeln will throw! 166 You can use [sdpyPrintDebugString] instead of stdio writeln instead which will 167 create a console as needed. 168 ) 169 170 On Mac, when compiling with X11, you need XQuartz and -L-L/usr/X11R6/lib passed to dmd. If using the Cocoa implementation on Mac, you need to pass `-L-framework -LCocoa` to dmd. For OpenGL, add `-L-framework -LOpenGL` to the build command. 171 172 On Ubuntu, you might need to install X11 development libraries to 173 successfully link. 174 175 $(CONSOLE 176 $ sudo apt-get install libglc-dev 177 $ sudo apt-get install libx11-dev 178 ) 179 180 181 Jump_list: 182 183 Don't worry, you don't have to read this whole documentation file! 184 185 Check out the [#event-example] and [#Pong-example] to get started quickly. 186 187 The main classes you may want to create are [SimpleWindow], [Timer], 188 [Image], and [Sprite]. 189 190 The main functions you'll want are [setClipboardText] and [getClipboardText]. 191 192 There are also platform-specific functions available such as [XDisplayConnection] 193 and [GetAtom] for X11, among others. 194 195 See the examples and topics list below to learn more. 196 197 $(WARNING 198 There should only be one GUI thread per application, 199 and all windows should be created in it and your 200 event loop should run there. 201 202 To do otherwise is undefined behavior and has no 203 cross platform guarantees. 204 ) 205 206 $(H2 About this documentation) 207 208 The goal here is to give some complete programs as overview examples first, then a look at each major feature with working examples first, then, finally, the inline class and method list will follow. 209 210 Scan for headers for a topic - $(B they will visually stand out) - you're interested in to get started quickly and feel free to copy and paste any example as a starting point for your program. I encourage you to learn the library by experimenting with the examples! 211 212 All examples are provided with no copyright restrictions whatsoever. You do not need to credit me or carry any kind of notice with the source if you copy and paste from them. 213 214 To get started, download `simpledisplay.d` and `color.d` to a working directory. Copy an example info a file called `example.d` and compile using the command given at the top of each example. 215 216 If you need help, email me: destructionator@gmail.com or IRC us, #d on Freenode (I am destructionator or adam_d_ruppe there). If you learn something that isn't documented, I appreciate pull requests on github to this file. 217 218 At points, I will talk about implementation details in the documentation. These are sometimes 219 subject to change, but nevertheless useful to understand what is really going on. You can learn 220 more about some of the referenced things by searching the web for info about using them from C. 221 You can always look at the source of simpledisplay.d too for the most authoritative source on 222 its specific implementation. If you disagree with how I did something, please contact me so we 223 can discuss it! 224 225 $(H2 Using with fibers) 226 227 simpledisplay can be used with [core.thread.Fiber], but be warned many of the functions can use a significant amount of stack space. I recommend at least 64 KB stack for each fiber (just set through the second argument to Fiber's constructor). 228 229 $(H2 Topics) 230 231 $(H3 $(ID topic-windows) Windows) 232 The [SimpleWindow] class is simpledisplay's flagship feature. It represents a single 233 window on the user's screen. 234 235 You may create multiple windows, if the underlying platform supports it. You may check 236 `static if(multipleWindowsSupported)` at compile time, or catch exceptions thrown by 237 SimpleWindow's constructor at runtime to handle those cases. 238 239 A single running event loop will handle as many windows as needed. 240 241 $(H3 $(ID topic-event-loops) Event loops) 242 The simpledisplay event loop is designed to handle common cases easily while being extensible for more advanced cases, or replaceable by other libraries. 243 244 The most common scenario is creating a window, then calling [SimpleWindow.eventLoop|window.eventLoop] when setup is complete. You can pass several handlers to the `eventLoop` method right there: 245 246 --- 247 // dmd example.d simpledisplay.d color.d 248 import arsd.simpledisplay; 249 void main() { 250 auto window = new SimpleWindow(200, 200); 251 window.eventLoop(0, 252 delegate (dchar) { /* got a character key press */ } 253 ); 254 } 255 --- 256 257 $(TIP If you get a compile error saying "I can't use this event handler", the most common thing in my experience is passing a function instead of a delegate. The simple solution is to use the `delegate` keyword, like I did in the example above.) 258 259 On Linux, the event loop is implemented with the `epoll` system call for efficiency an extensibility to other files. On Windows, it runs a traditional `GetMessage` + `DispatchMessage` loop, with a call to `SleepEx` in each iteration to allow the thread to enter an alertable wait state regularly, primarily so Overlapped I/O callbacks will get a chance to run. 260 261 On Linux, simpledisplay also supports my (deprecated) [arsd.eventloop] module. Compile your program, including the eventloop.d file, with the `-version=with_eventloop` switch. 262 263 It should be possible to integrate simpledisplay with vibe.d as well, though I haven't tried. 264 265 You can also run the event loop independently of a window, with [EventLoop.run|EventLoop.get.run], though since it will automatically terminate when there are no open windows, you will want to have one anyway. 266 267 $(H3 $(ID topic-notification-areas) Notification area (aka systray) icons) 268 Notification area icons are currently implemented on X11 and Windows. On X11, it defaults to using `libnotify` to show bubbles, if available, and will do a custom bubble window if not. You can `version=without_libnotify` to avoid this run-time dependency, if you like. 269 270 See the [NotificationAreaIcon] class. 271 272 $(H3 $(ID topic-input-handling) Input handling) 273 There are event handlers for low-level keyboard and mouse events, and higher level handlers for character events. 274 275 See [SimpleWindow.handleCharEvent], [SimpleWindow.handleKeyEvent], [SimpleWindow.handleMouseEvent]. 276 277 $(H3 $(ID topic-2d-drawing) 2d Drawing) 278 To draw on your window, use the [SimpleWindow.draw] method. It returns a [ScreenPainter] structure with drawing methods. 279 280 Important: `ScreenPainter` double-buffers and will not actually update the window until its destructor is run. Always ensure the painter instance goes out-of-scope before proceeding. You can do this by calling it inside an event handler, a timer callback, or an small scope inside main. For example: 281 282 --- 283 // dmd example.d simpledisplay.d color.d 284 import arsd.simpledisplay; 285 void main() { 286 auto window = new SimpleWindow(200, 200); 287 { // introduce sub-scope 288 auto painter = window.draw(); // begin drawing 289 /* draw here */ 290 painter.outlineColor = Color.red; 291 painter.fillColor = Color.black; 292 painter.drawRectangle(Point(0, 0), 200, 200); 293 } // end scope, calling `painter`'s destructor, drawing to the screen. 294 window.eventLoop(0); // handle events 295 } 296 --- 297 298 Painting is done based on two color properties, a pen and a brush. 299 300 At this time, the 2d drawing does not support alpha blending, except for the [Sprite] class. If you need that, use a 2d OpenGL context instead. 301 302 FIXME Add example of 2d opengl drawing here. 303 $(H3 $(ID topic-3d-drawing) 3d Drawing (or 2d with OpenGL)) 304 simpledisplay can create OpenGL contexts on your window. It works quite differently than 2d drawing. 305 306 Note that it is still possible to draw 2d on top of an OpenGL window, using the `draw` method, though I don't recommend it. 307 308 To start, you create a [SimpleWindow] with OpenGL enabled by passing the argument [OpenGlOptions.yes] to the constructor. 309 310 Next, you set [SimpleWindow.redrawOpenGlScene|window.redrawOpenGlScene] to a delegate which draws your frame. 311 312 To force a redraw of the scene, call [SimpleWindow.redrawOpenGlSceneNow|window.redrawOpenGlSceneNow()] or to queue a redraw after processing the next batch of pending events, use [SimpleWindow.redrawOpenGlSceneSoon|window.redrawOpenGlSceneSoon]. 313 314 simpledisplay supports both old-style `glBegin` and newer-style shader-based code all through its built-in bindings. See the next section of the docs to see a shader-based program. 315 316 This example program will draw a rectangle on your window using old-style OpenGL with a pulsating color: 317 318 --- 319 import arsd.simpledisplay; 320 321 void main() { 322 auto window = new SimpleWindow(800, 600, "opengl 1", OpenGlOptions.yes, Resizability.allowResizing); 323 324 float otherColor = 0.0; 325 float colorDelta = 0.05; 326 327 window.redrawOpenGlScene = delegate() { 328 glLoadIdentity(); 329 glBegin(GL_QUADS); 330 331 glColor3f(1.0, otherColor, 0); 332 glVertex3f(-0.8, -0.8, 0); 333 334 glColor3f(1.0, otherColor, 1.0); 335 glVertex3f(0.8, -0.8, 0); 336 337 glColor3f(0, 1.0, otherColor); 338 glVertex3f(0.8, 0.8, 0); 339 340 glColor3f(otherColor, 0, 1.0); 341 glVertex3f(-0.8, 0.8, 0); 342 343 glEnd(); 344 }; 345 346 window.eventLoop(50, () { 347 otherColor += colorDelta; 348 if(otherColor > 1.0) { 349 otherColor = 1.0; 350 colorDelta = -0.05; 351 } 352 if(otherColor < 0) { 353 otherColor = 0; 354 colorDelta = 0.05; 355 } 356 // at the end of the timer, we have to request a redraw 357 // or we won't see the changes. 358 window.redrawOpenGlSceneSoon(); 359 }); 360 } 361 --- 362 363 My [arsd.game] module has some helpers for using old-style opengl to make 2D windows too. See: [arsd.game.create2dWindow]. 364 $(H3 $(ID topic-modern-opengl) Modern OpenGL) 365 simpledisplay's opengl support, by default, is for "legacy" opengl. To use "modern" functions, you must opt-into them with a little more setup. But the library provides helpers for this too. 366 367 This example program shows how you can set up a shader to draw a rectangle: 368 369 --- 370 module opengl3test; 371 import arsd.simpledisplay; 372 373 // based on https://learnopengl.com/Getting-started/Hello-Triangle 374 375 void main() { 376 // First thing we do, before creating the window, is declare what version we want. 377 setOpenGLContextVersion(3, 3); 378 // turning off legacy compat is required to use version 3.3 and newer 379 openGLContextCompatible = false; 380 381 uint VAO; 382 OpenGlShader shader; 383 384 // then we can create the window. 385 auto window = new SimpleWindow(800, 600, "opengl 3", OpenGlOptions.yes, Resizability.allowResizing); 386 387 // additional setup needs to be done when it is visible, simpledisplay offers a property 388 // for exactly that: 389 window.visibleForTheFirstTime = delegate() { 390 // now with the window loaded, we can start loading the modern opengl functions. 391 392 // you MUST set the context first. 393 window.setAsCurrentOpenGlContext; 394 // then load the remainder of the library 395 gl3.loadDynamicLibrary(); 396 397 // now you can create the shaders, etc. 398 shader = new OpenGlShader( 399 OpenGlShader.Source(GL_VERTEX_SHADER, ` 400 #version 330 core 401 layout (location = 0) in vec3 aPos; 402 void main() { 403 gl_Position = vec4(aPos.x, aPos.y, aPos.z, 1.0); 404 } 405 `), 406 OpenGlShader.Source(GL_FRAGMENT_SHADER, ` 407 #version 330 core 408 out vec4 FragColor; 409 uniform vec4 mycolor; 410 void main() { 411 FragColor = mycolor; 412 } 413 `), 414 ); 415 416 // and do whatever other setup you want. 417 418 float[] vertices = [ 419 0.5f, 0.5f, 0.0f, // top right 420 0.5f, -0.5f, 0.0f, // bottom right 421 -0.5f, -0.5f, 0.0f, // bottom left 422 -0.5f, 0.5f, 0.0f // top left 423 ]; 424 uint[] indices = [ // note that we start from 0! 425 0, 1, 3, // first Triangle 426 1, 2, 3 // second Triangle 427 ]; 428 uint VBO, EBO; 429 glGenVertexArrays(1, &VAO); 430 // bind the Vertex Array Object first, then bind and set vertex buffer(s), and then configure vertex attributes(s). 431 glBindVertexArray(VAO); 432 433 glGenBuffers(1, &VBO); 434 glGenBuffers(1, &EBO); 435 436 glBindBuffer(GL_ARRAY_BUFFER, VBO); 437 glBufferDataSlice(GL_ARRAY_BUFFER, vertices, GL_STATIC_DRAW); 438 439 glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, EBO); 440 glBufferDataSlice(GL_ELEMENT_ARRAY_BUFFER, indices, GL_STATIC_DRAW); 441 442 glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, 3 * float.sizeof, null); 443 glEnableVertexAttribArray(0); 444 445 // the library will set the initial viewport and trigger our first draw, 446 // so these next two lines are NOT needed. they are just here as comments 447 // to show what would happen next. 448 449 // glViewport(0, 0, window.width, window.height); 450 // window.redrawOpenGlSceneNow(); 451 }; 452 453 // this delegate is called any time the window needs to be redrawn or if you call `window.redrawOpenGlSceneNow;` 454 // it is our render method. 455 window.redrawOpenGlScene = delegate() { 456 glClearColor(0.2f, 0.3f, 0.3f, 1.0f); 457 glClear(GL_COLOR_BUFFER_BIT); 458 459 glUseProgram(shader.shaderProgram); 460 461 // the shader helper class has methods to set uniforms too 462 shader.uniforms.mycolor.opAssign(1.0, 1.0, 0, 1.0); 463 464 glBindVertexArray(VAO); 465 glDrawElements(GL_TRIANGLES, 6, GL_UNSIGNED_INT, null); 466 }; 467 468 window.eventLoop(0); 469 } 470 --- 471 472 This program only draws the image once because that's all that is necessary, since it is static. If you want to do animation, you might set a pulse timer (which would be a fixed max fps, not necessarily consistent) or use a render loop in a separate thread. 473 474 $(H3 $(ID vulkan) Vulkan) 475 476 See a couple examples ported from GLFW to simpledisplay using the erupted vulkan bindings: 477 478 https://github.com/adamdruppe/VulkanizeDSdpy 479 480 https://github.com/adamdruppe/VulkanSdpyDemo/tree/demo 481 482 $(H3 $(ID topic-images) Displaying images) 483 You can also load PNG images using [arsd.png]. 484 485 --- 486 // dmd example.d simpledisplay.d color.d png.d 487 import arsd.simpledisplay; 488 import arsd.png; 489 490 void main() { 491 auto image = Image.fromMemoryImage(readPng("image.png")); 492 displayImage(image); 493 } 494 --- 495 496 Compile with `dmd example.d simpledisplay.d png.d`. 497 498 If you find an image file which is a valid png that [arsd.png] fails to load, please let me know. In the mean time of fixing the bug, you can probably convert the file into an easier-to-load format. Be sure to turn OFF png interlacing, as that isn't supported. Other things to try would be making the image smaller, or trying 24 bit truecolor mode with an alpha channel. 499 500 $(H3 $(ID topic-sprites) Sprites) 501 The [Sprite] class is used to make images on the display server for fast blitting to screen. This is especially important to use to support fast drawing of repeated images on a remote X11 link. 502 503 [Sprite] is also the only facility that currently supports alpha blending without using OpenGL . 504 505 $(H3 $(ID topic-clipboard) Clipboard) 506 The free functions [getClipboardText] and [setClipboardText] consist of simpledisplay's cross-platform clipboard support at this time. 507 508 It also has helpers for handling X-specific events. 509 510 $(H3 $(ID topic-dnd) Drag and Drop) 511 See [enableDragAndDrop] and [draggable]. 512 513 $(H3 $(ID topic-timers) Timers) 514 There are two timers in simpledisplay: one is the pulse timeout you can set on the call to `window.eventLoop`, and the other is a customizable class, [Timer]. 515 516 The pulse timeout is used by setting a non-zero interval as the first argument to `eventLoop` function and adding a zero-argument delegate to handle the pulse. 517 518 --- 519 import arsd.simpledisplay; 520 521 void main() { 522 auto window = new SimpleWindow(400, 400); 523 // every 100 ms, it will draw a random line 524 // on the window. 525 window.eventLoop(100, { 526 auto painter = window.draw(); 527 528 import std.random; 529 // random color 530 painter.outlineColor = Color(uniform(0, 256), uniform(0, 256), uniform(0, 256)); 531 // random line 532 painter.drawLine( 533 Point(uniform(0, window.width), uniform(0, window.height)), 534 Point(uniform(0, window.width), uniform(0, window.height))); 535 536 }); 537 } 538 --- 539 540 The `Timer` class works similarly, but is created separately from the event loop. (It still fires through the event loop, though.) You may make as many instances of `Timer` as you wish. 541 542 The pulse timer and instances of the [Timer] class may be combined at will. 543 544 --- 545 import arsd.simpledisplay; 546 547 void main() { 548 auto window = new SimpleWindow(400, 400); 549 auto timer = new Timer(1000, delegate { 550 auto painter = window.draw(); 551 painter.clear(); 552 }); 553 554 window.eventLoop(0); 555 } 556 --- 557 558 Timers are currently only implemented on Windows, using `SetTimer` and Linux, using `timerfd_create`. These deliver timeout messages through your application event loop. 559 560 $(H3 $(ID topic-os-helpers) OS-specific helpers) 561 simpledisplay carries a lot of code to help implement itself without extra dependencies, and much of this code is available for you too, so you may extend the functionality yourself. 562 563 See also: `xwindows.d` from my github. 564 565 $(H3 $(ID topic-os-extension) Extending with OS-specific functionality) 566 `handleNativeEvent` and `handleNativeGlobalEvent`. 567 568 $(H3 $(ID topic-integration) Integration with other libraries) 569 Integration with a third-party event loop is possible. 570 571 On Linux, you might want to support both terminal input and GUI input. You can do this by using simpledisplay together with eventloop.d and terminal.d. 572 573 $(H3 $(ID topic-guis) GUI widgets) 574 simpledisplay does not provide GUI widgets such as text areas, buttons, checkboxes, etc. It only gives basic windows, the ability to draw on it, receive input from it, and access native information for extension. You may write your own gui widgets with these, but you don't have to because I already did for you! 575 576 Download `minigui.d` from my github repository and add it to your project. minigui builds these things on top of simpledisplay and offers its own Window class (and subclasses) to use that wrap SimpleWindow, adding a new event and drawing model that is hookable by subwidgets, represented by their own classes. 577 578 Migrating to minigui from simpledisplay is often easy though, because they both use the same ScreenPainter API, and the same simpledisplay events are available, if you want them. (Though you may like using the minigui model, especially if you are familiar with writing web apps in the browser with Javascript.) 579 580 minigui still needs a lot of work to be finished at this time, but it already offers a number of useful classes. 581 582 $(H2 Platform-specific tips and tricks) 583 584 X_tips: 585 586 On X11, if you set an environment variable, `ARSD_SCALING_FACTOR`, you can control the per-monitor DPI scaling returned to the application. The format is `ARSD_SCALING_FACTOR=2;1`, for example, to set 2x scaling on your first monitor and 1x scaling on your second monitor. Support for this was added on March 22, 2022, the dub 10.7 release. 587 588 Windows_tips: 589 590 You can add icons or manifest files to your exe using a resource file. 591 592 To create a Windows .ico file, use the gimp or something. I'll write a helper 593 program later. 594 595 Create `yourapp.rc`: 596 597 ```rc 598 1 ICON filename.ico 599 CREATEPROCESS_MANIFEST_RESOURCE_ID RT_MANIFEST "YourApp.exe.manifest" 600 ``` 601 602 And `yourapp.exe.manifest`: 603 604 ```xml 605 <?xml version="1.0" encoding="UTF-8" standalone="yes"?> 606 <assembly xmlns="urn:schemas-microsoft-com:asm.v1" manifestVersion="1.0"> 607 <assemblyIdentity 608 version="1.0.0.0" 609 processorArchitecture="*" 610 name="CompanyName.ProductName.YourApplication" 611 type="win32" 612 /> 613 <description>Your application description here.</description> 614 <dependency> 615 <dependentAssembly> 616 <assemblyIdentity 617 type="win32" 618 name="Microsoft.Windows.Common-Controls" 619 version="6.0.0.0" 620 processorArchitecture="*" 621 publicKeyToken="6595b64144ccf1df" 622 language="*" 623 /> 624 </dependentAssembly> 625 </dependency> 626 <application xmlns="urn:schemas-microsoft-com:asm.v3"> 627 <windowsSettings> 628 <dpiAware xmlns="http://schemas.microsoft.com/SMI/2005/WindowsSettings">true/pm</dpiAware> <!-- old style --> 629 <dpiAwareness xmlns="http://schemas.microsoft.com/SMI/2016/WindowsSettings">PerMonitorV2</dpiAwareness> <!-- new style --> 630 <!-- Un-comment the line below to enable GDI-scaling in this project. This will enable text --> 631 <!-- to render crisply in DPI-unaware contexts --> 632 <!--<gdiScaling xmlns="http://schemas.microsoft.com/SMI/2017/WindowsSettings">true</gdiScaling>--> 633 </windowsSettings> 634 </application> 635 </assembly> 636 ``` 637 638 You can also just distribute yourapp.exe.manifest as a separate file alongside yourapp.exe, or link it in to the exe with linker command lines `/manifest:embed` and `/manifestinput:yourfile.exe.manifest`. 639 640 Doing this lets you opt into various new things since Windows XP. 641 642 See: https://docs.microsoft.com/en-us/windows/win32/SbsCs/application-manifests 643 644 $(H2 Tips) 645 646 $(H3 Name conflicts) 647 648 simpledisplay has a lot of symbols and more are liable to be added without notice, since it contains its own bindings as needed to accomplish its goals. Some of these may conflict with other bindings you use. If so, you can use a static import in D, possibly combined with a selective import: 649 650 --- 651 static import sdpy = arsd.simpledisplay; 652 import arsd.simpledisplay : SimpleWindow; 653 654 void main() { 655 auto window = new SimpleWindow(); 656 sdpy.EventLoop.get.run(); 657 } 658 --- 659 660 $(H2 $(ID developer-notes) Developer notes) 661 662 I don't have a Mac, so that code isn't maintained. I would like to have a Cocoa 663 implementation though. 664 665 The NativeSimpleWindowImplementation and NativeScreenPainterImplementation both 666 suck. If I was rewriting it, I wouldn't do it that way again. 667 668 This file must not have any more required dependencies. If you need bindings, add 669 them right to this file. Once it gets into druntime and is there for a while, remove 670 bindings from here to avoid conflicts (or put them in an appropriate version block 671 so it continues to just work on old dmd), but wait a couple releases before making the 672 transition so this module remains usable with older versions of dmd. 673 674 You may have optional dependencies if needed by putting them in version blocks or 675 template functions. You may also extend the module with other modules with UFCS without 676 actually editing this - that is nice to do if you can. 677 678 Try to make functions work the same way across operating systems. I typically make 679 it thinly wrap Windows, then emulate that on Linux. 680 681 A goal of this is to keep a gui hello world to less than 250 KB. This means avoiding 682 Phobos! So try to avoid it. 683 684 See more comments throughout the source. 685 686 I realize this file is fairly large, but over half that is just bindings at the bottom 687 or documentation at the top. Some of the classes are a bit big too, but hopefully easy 688 to understand. I suggest you jump around the source by looking for a particular 689 declaration you're interested in, like `class SimpleWindow` using your editor's search 690 function, then look at one piece at a time. 691 692 Authors: Adam D. Ruppe with the help of others. If you need help, please email me with 693 destructionator@gmail.com or find me on IRC. Our channel is #d on Freenode and you can 694 ping me, adam_d_ruppe, and I'll usually see it if I'm around. 695 696 I live in the eastern United States, so I will most likely not be around at night in 697 that US east timezone. 698 699 License: Copyright Adam D. Ruppe, 2011-2021. Released under the Boost Software License. 700 701 Building documentation: use my adrdox generator, `dub run adrdox`. 702 703 Examples: 704 705 $(DIV $(ID Event-example)) 706 $(H3 $(ID event-example) Event example) 707 This program creates a window and draws events inside them as they 708 happen, scrolling the text in the window as needed. Run this program 709 and experiment to get a feel for where basic input events take place 710 in the library. 711 712 --- 713 // dmd example.d simpledisplay.d color.d 714 import arsd.simpledisplay; 715 import std.conv; 716 717 void main() { 718 auto window = new SimpleWindow(Size(500, 500), "Event example - simpledisplay.d"); 719 720 int y = 0; 721 722 void addLine(string text) { 723 auto painter = window.draw(); 724 725 if(y + painter.fontHeight >= window.height) { 726 painter.scrollArea(Point(0, 0), window.width, window.height, 0, painter.fontHeight); 727 y -= painter.fontHeight; 728 } 729 730 painter.outlineColor = Color.red; 731 painter.fillColor = Color.black; 732 painter.drawRectangle(Point(0, y), window.width, painter.fontHeight); 733 734 painter.outlineColor = Color.white; 735 736 painter.drawText(Point(10, y), text); 737 738 y += painter.fontHeight; 739 } 740 741 window.eventLoop(1000, 742 () { 743 addLine("Timer went off!"); 744 }, 745 (KeyEvent event) { 746 addLine(to!string(event)); 747 }, 748 (MouseEvent event) { 749 addLine(to!string(event)); 750 }, 751 (dchar ch) { 752 addLine(to!string(ch)); 753 } 754 ); 755 } 756 --- 757 758 If you are interested in more game writing with D, check out my gamehelpers.d which builds upon simpledisplay, and its other stand-alone support modules, simpleaudio.d and joystick.d, too. 759 760 $(COMMENT 761 This program displays a pie chart. Clicking on a color will increase its share of the pie. 762 763 --- 764 765 --- 766 ) 767 768 History: 769 Initial release in April 2011. 770 771 simpledisplay was stand alone until about 2015. It then added a dependency on [arsd.color] and changed its name to `arsd.simpledisplay`. 772 773 On March 4, 2023 (dub v11.0), it started importing [arsd.core] as well, making that a build-time requirement. 774 +/ 775 module arsd.simpledisplay; 776 777 import arsd.core; 778 779 // FIXME: tetris demo 780 // FIXME: space invaders demo 781 // FIXME: asteroids demo 782 783 /++ $(ID Pong-example) 784 $(H3 Pong) 785 786 This program creates a little Pong-like game. Player one is controlled 787 with the keyboard. Player two is controlled with the mouse. It demos 788 the pulse timer, event handling, and some basic drawing. 789 +/ 790 version(demos) 791 unittest { 792 // dmd example.d simpledisplay.d color.d 793 import arsd.simpledisplay; 794 795 enum paddleMovementSpeed = 8; 796 enum paddleHeight = 48; 797 798 void main() { 799 auto window = new SimpleWindow(600, 400, "Pong game!"); 800 801 int playerOnePosition, playerTwoPosition; 802 int playerOneMovement, playerTwoMovement; 803 int playerOneScore, playerTwoScore; 804 805 int ballX, ballY; 806 int ballDx, ballDy; 807 808 void serve() { 809 import std.random; 810 811 ballX = window.width / 2; 812 ballY = window.height / 2; 813 ballDx = uniform(-4, 4) * 3; 814 ballDy = uniform(-4, 4) * 3; 815 if(ballDx == 0) 816 ballDx = uniform(0, 2) == 0 ? 3 : -3; 817 } 818 819 serve(); 820 821 window.eventLoop(50, // set a 50 ms timer pulls 822 // This runs once per timer pulse 823 delegate () { 824 auto painter = window.draw(); 825 826 painter.clear(); 827 828 // Update everyone's motion 829 playerOnePosition += playerOneMovement; 830 playerTwoPosition += playerTwoMovement; 831 832 ballX += ballDx; 833 ballY += ballDy; 834 835 // Bounce off the top and bottom edges of the window 836 if(ballY + 7 >= window.height) 837 ballDy = -ballDy; 838 if(ballY - 8 <= 0) 839 ballDy = -ballDy; 840 841 // Bounce off the paddle, if it is in position 842 if(ballX - 8 <= 16) { 843 if(ballY + 7 > playerOnePosition && ballY - 8 < playerOnePosition + paddleHeight) { 844 ballDx = -ballDx + 1; // add some speed to keep it interesting 845 ballDy += playerOneMovement; // and y movement based on your controls too 846 ballX = 24; // move it past the paddle so it doesn't wiggle inside 847 } else { 848 // Missed it 849 playerTwoScore ++; 850 serve(); 851 } 852 } 853 854 if(ballX + 7 >= window.width - 16) { // do the same thing but for player 1 855 if(ballY + 7 > playerTwoPosition && ballY - 8 < playerTwoPosition + paddleHeight) { 856 ballDx = -ballDx - 1; 857 ballDy += playerTwoMovement; 858 ballX = window.width - 24; 859 } else { 860 // Missed it 861 playerOneScore ++; 862 serve(); 863 } 864 } 865 866 // Draw the paddles 867 painter.outlineColor = Color.black; 868 painter.drawLine(Point(16, playerOnePosition), Point(16, playerOnePosition + paddleHeight)); 869 painter.drawLine(Point(window.width - 16, playerTwoPosition), Point(window.width - 16, playerTwoPosition + paddleHeight)); 870 871 // Draw the ball 872 painter.fillColor = Color.red; 873 painter.outlineColor = Color.yellow; 874 painter.drawEllipse(Point(ballX - 8, ballY - 8), Point(ballX + 7, ballY + 7)); 875 876 // Draw the score 877 painter.outlineColor = Color.blue; 878 import std.conv; 879 painter.drawText(Point(64, 4), to!string(playerOneScore)); 880 painter.drawText(Point(window.width - 64, 4), to!string(playerTwoScore)); 881 882 }, 883 delegate (KeyEvent event) { 884 // Player 1's controls are the arrow keys on the keyboard 885 if(event.key == Key.Down) 886 playerOneMovement = event.pressed ? paddleMovementSpeed : 0; 887 if(event.key == Key.Up) 888 playerOneMovement = event.pressed ? -paddleMovementSpeed : 0; 889 890 }, 891 delegate (MouseEvent event) { 892 // Player 2's controls are mouse movement while the left button is held down 893 if(event.type == MouseEventType.motion && (event.modifierState & ModifierState.leftButtonDown)) { 894 if(event.dy > 0) 895 playerTwoMovement = paddleMovementSpeed; 896 else if(event.dy < 0) 897 playerTwoMovement = -paddleMovementSpeed; 898 } else { 899 playerTwoMovement = 0; 900 } 901 } 902 ); 903 } 904 } 905 906 /++ $(H3 $(ID example-minesweeper) Minesweeper) 907 908 This minesweeper demo shows how we can implement another classic 909 game with simpledisplay and shows some mouse input and basic output 910 code. 911 +/ 912 version(demos) 913 unittest { 914 import arsd.simpledisplay; 915 916 enum GameSquare { 917 mine = 0, 918 clear, 919 m1, m2, m3, m4, m5, m6, m7, m8 920 } 921 922 enum UserSquare { 923 unknown, 924 revealed, 925 flagged, 926 questioned 927 } 928 929 enum GameState { 930 inProgress, 931 lose, 932 win 933 } 934 935 GameSquare[] board; 936 UserSquare[] userState; 937 GameState gameState; 938 int boardWidth; 939 int boardHeight; 940 941 bool isMine(int x, int y) { 942 if(x < 0 || y < 0 || x >= boardWidth || y >= boardHeight) 943 return false; 944 return board[y * boardWidth + x] == GameSquare.mine; 945 } 946 947 GameState reveal(int x, int y) { 948 if(board[y * boardWidth + x] == GameSquare.clear) { 949 floodFill(userState, boardWidth, boardHeight, 950 UserSquare.unknown, UserSquare.revealed, 951 x, y, 952 (x, y) { 953 if(board[y * boardWidth + x] == GameSquare.clear) 954 return true; 955 else { 956 userState[y * boardWidth + x] = UserSquare.revealed; 957 return false; 958 } 959 }); 960 } else { 961 userState[y * boardWidth + x] = UserSquare.revealed; 962 if(isMine(x, y)) 963 return GameState.lose; 964 } 965 966 foreach(state; userState) { 967 if(state == UserSquare.unknown || state == UserSquare.questioned) 968 return GameState.inProgress; 969 } 970 971 return GameState.win; 972 } 973 974 void initializeBoard(int width, int height, int numberOfMines) { 975 boardWidth = width; 976 boardHeight = height; 977 board.length = width * height; 978 979 userState.length = width * height; 980 userState[] = UserSquare.unknown; 981 982 import std.algorithm, std.random, std.range; 983 984 board[] = GameSquare.clear; 985 986 foreach(minePosition; randomSample(iota(0, board.length), numberOfMines)) 987 board[minePosition] = GameSquare.mine; 988 989 int x; 990 int y; 991 foreach(idx, ref square; board) { 992 if(square == GameSquare.clear) { 993 int danger = 0; 994 danger += isMine(x-1, y-1)?1:0; 995 danger += isMine(x-1, y)?1:0; 996 danger += isMine(x-1, y+1)?1:0; 997 danger += isMine(x, y-1)?1:0; 998 danger += isMine(x, y+1)?1:0; 999 danger += isMine(x+1, y-1)?1:0; 1000 danger += isMine(x+1, y)?1:0; 1001 danger += isMine(x+1, y+1)?1:0; 1002 1003 square = cast(GameSquare) (danger + 1); 1004 } 1005 1006 x++; 1007 if(x == width) { 1008 x = 0; 1009 y++; 1010 } 1011 } 1012 } 1013 1014 void redraw(SimpleWindow window) { 1015 import std.conv; 1016 1017 auto painter = window.draw(); 1018 1019 painter.clear(); 1020 1021 final switch(gameState) with(GameState) { 1022 case inProgress: 1023 break; 1024 case win: 1025 painter.fillColor = Color.green; 1026 painter.drawRectangle(Point(0, 0), window.width, window.height); 1027 return; 1028 case lose: 1029 painter.fillColor = Color.red; 1030 painter.drawRectangle(Point(0, 0), window.width, window.height); 1031 return; 1032 } 1033 1034 int x = 0; 1035 int y = 0; 1036 1037 foreach(idx, square; board) { 1038 auto state = userState[idx]; 1039 1040 final switch(state) with(UserSquare) { 1041 case unknown: 1042 painter.outlineColor = Color.black; 1043 painter.fillColor = Color(128,128,128); 1044 1045 painter.drawRectangle( 1046 Point(x * 20, y * 20), 1047 20, 20 1048 ); 1049 break; 1050 case revealed: 1051 if(square == GameSquare.clear) { 1052 painter.outlineColor = Color.white; 1053 painter.fillColor = Color.white; 1054 1055 painter.drawRectangle( 1056 Point(x * 20, y * 20), 1057 20, 20 1058 ); 1059 } else { 1060 painter.outlineColor = Color.black; 1061 painter.fillColor = Color.white; 1062 1063 painter.drawText( 1064 Point(x * 20, y * 20), 1065 to!string(square)[1..2], 1066 Point(x * 20 + 20, y * 20 + 20), 1067 TextAlignment.Center | TextAlignment.VerticalCenter); 1068 } 1069 break; 1070 case flagged: 1071 painter.outlineColor = Color.black; 1072 painter.fillColor = Color.red; 1073 painter.drawRectangle( 1074 Point(x * 20, y * 20), 1075 20, 20 1076 ); 1077 break; 1078 case questioned: 1079 painter.outlineColor = Color.black; 1080 painter.fillColor = Color.yellow; 1081 painter.drawRectangle( 1082 Point(x * 20, y * 20), 1083 20, 20 1084 ); 1085 break; 1086 } 1087 1088 x++; 1089 if(x == boardWidth) { 1090 x = 0; 1091 y++; 1092 } 1093 } 1094 1095 } 1096 1097 void main() { 1098 auto window = new SimpleWindow(200, 200); 1099 1100 initializeBoard(10, 10, 10); 1101 1102 redraw(window); 1103 window.eventLoop(0, 1104 delegate (MouseEvent me) { 1105 if(me.type != MouseEventType.buttonPressed) 1106 return; 1107 auto x = me.x / 20; 1108 auto y = me.y / 20; 1109 if(x >= 0 && x < boardWidth && y >= 0 && y < boardHeight) { 1110 if(me.button == MouseButton.left) { 1111 gameState = reveal(x, y); 1112 } else { 1113 userState[y*boardWidth+x] = UserSquare.flagged; 1114 } 1115 redraw(window); 1116 } 1117 } 1118 ); 1119 } 1120 } 1121 1122 import arsd.core; 1123 1124 // FIXME: tetris demo 1125 // FIXME: space invaders demo 1126 // FIXME: asteroids demo 1127 1128 version(OSX) version(DigitalMars) version=OSXCocoa; 1129 1130 1131 version(OSXCocoa) { 1132 version=without_opengl; 1133 version=allow_unimplemented_features; 1134 // version=OSXCocoa; 1135 // pragma(linkerDirective, "-framework Cocoa"); 1136 } 1137 1138 version(without_opengl) { 1139 enum SdpyIsUsingIVGLBinds = false; 1140 } else /*version(Posix)*/ { 1141 static if (__traits(compiles, (){import iv.glbinds;})) { 1142 enum SdpyIsUsingIVGLBinds = true; 1143 public import iv.glbinds; 1144 //pragma(msg, "SDPY: using iv.glbinds"); 1145 } else { 1146 enum SdpyIsUsingIVGLBinds = false; 1147 } 1148 //} else { 1149 // enum SdpyIsUsingIVGLBinds = false; 1150 } 1151 1152 1153 version(Windows) { 1154 //import core.sys.windows.windows; 1155 import core.sys.windows.winnls; 1156 import core.sys.windows.windef; 1157 import core.sys.windows.basetyps; 1158 import core.sys.windows.winbase; 1159 import core.sys.windows.winuser; 1160 import core.sys.windows.shellapi; 1161 import core.sys.windows.wingdi; 1162 static import gdi = core.sys.windows.wingdi; // so i 1163 1164 pragma(lib, "gdi32"); 1165 pragma(lib, "user32"); 1166 1167 // for AlphaBlend... a breaking change.... 1168 version(CRuntime_DigitalMars) { } else 1169 pragma(lib, "msimg32"); 1170 } else version (linux) { 1171 //k8: this is hack for rdmd. sorry. 1172 static import core.sys.linux.epoll; 1173 static import core.sys.linux.timerfd; 1174 } 1175 1176 1177 // FIXME: icons on Windows don't look quite right, I think the transparency mask is off. 1178 1179 // http://wiki.dlang.org/Simpledisplay.d 1180 1181 // see : http://www.sbin.org/doc/Xlib/chapt_09.html section on Keyboard Preferences re: scroll lock led 1182 1183 // Cool stuff: I want right alt and scroll lock to do different stuff for personal use. maybe even right ctrl 1184 // but can i control the scroll lock led 1185 1186 1187 // Note: if you are using Image on X, you might want to do: 1188 /* 1189 static if(UsingSimpledisplayX11) { 1190 if(!Image.impl.xshmAvailable) { 1191 // the images will use the slower XPutImage, you might 1192 // want to consider an alternative method to get better speed 1193 } 1194 } 1195 1196 If the shared memory extension is available though, simpledisplay uses it 1197 for a significant speed boost whenever you draw large Images. 1198 */ 1199 1200 // CHANGE FROM LAST VERSION: the window background is no longer fixed, so you might want to fill the screen with a particular color before drawing. 1201 1202 // WARNING: if you are using with_eventloop, don't forget to call XFlush(XDisplayConnection.get()); before calling loop()! 1203 1204 /* 1205 Biggest FIXME: 1206 make sure the key event numbers match between X and Windows OR provide symbolic constants on each system 1207 1208 clean up opengl contexts when their windows close 1209 1210 fix resizing the bitmaps/pixmaps 1211 */ 1212 1213 // BTW on Windows: 1214 // -L/SUBSYSTEM:WINDOWS:5.0 1215 // to dmd will make a nice windows binary w/o a console if you want that. 1216 1217 /* 1218 Stuff to add: 1219 1220 use multibyte functions everywhere we can 1221 1222 OpenGL windows 1223 more event stuff 1224 extremely basic windows w/ no decoration for tooltips, splash screens, etc. 1225 1226 1227 resizeEvent 1228 and make the windows non-resizable by default, 1229 or perhaps stretched (if I can find something in X like StretchBlt) 1230 1231 take a screenshot function! 1232 1233 Pens and brushes? 1234 Maybe a global event loop? 1235 1236 Mouse deltas 1237 Key items 1238 */ 1239 1240 /* 1241 From MSDN: 1242 1243 You can also use the GET_X_LPARAM or GET_Y_LPARAM macro to extract the x- or y-coordinate. 1244 1245 Important Do not use the LOWORD or HIWORD macros to extract the x- and y- coordinates of the cursor position because these macros return incorrect results on systems with multiple monitors. Systems with multiple monitors can have negative x- and y- coordinates, and LOWORD and HIWORD treat the coordinates as unsigned quantities. 1246 1247 */ 1248 1249 version(linux) { 1250 version = X11; 1251 version(without_libnotify) { 1252 // we cool 1253 } 1254 else 1255 version = libnotify; 1256 } 1257 1258 version(libnotify) { 1259 pragma(lib, "dl"); 1260 import core.sys.posix.dlfcn; 1261 1262 void delegate()[int] libnotify_action_delegates; 1263 int libnotify_action_delegates_count; 1264 extern(C) static void libnotify_action_callback_sdpy(void* notification, char* action, void* user_data) { 1265 auto idx = cast(int) user_data; 1266 if(auto dgptr = idx in libnotify_action_delegates) { 1267 (*dgptr)(); 1268 libnotify_action_delegates.remove(idx); 1269 } 1270 } 1271 1272 struct C_DynamicLibrary { 1273 void* handle; 1274 this(string name) { 1275 handle = dlopen((name ~ "\0").ptr, RTLD_NOW); 1276 if(handle is null) 1277 throw new Exception("dlopen"); 1278 } 1279 1280 void close() { 1281 dlclose(handle); 1282 } 1283 1284 ~this() { 1285 // close 1286 } 1287 1288 // FIXME: this looks up by name every time.... 1289 template call(string func, Ret, Args...) { 1290 extern(C) Ret function(Args) fptr; 1291 typeof(fptr) call() { 1292 fptr = cast(typeof(fptr)) dlsym(handle, func); 1293 return fptr; 1294 } 1295 } 1296 } 1297 1298 C_DynamicLibrary* libnotify; 1299 } 1300 1301 version(OSX) { 1302 version(OSXCocoa) {} 1303 else { version = X11; } 1304 } 1305 //version = OSXCocoa; // this was written by KennyTM 1306 version(FreeBSD) 1307 version = X11; 1308 version(Solaris) 1309 version = X11; 1310 1311 version(X11) { 1312 version(without_xft) {} 1313 else version=with_xft; 1314 } 1315 1316 void featureNotImplemented()() { 1317 version(allow_unimplemented_features) 1318 throw new NotYetImplementedException(); 1319 else 1320 static assert(0); 1321 } 1322 1323 // these are so the static asserts don't trigger unless you want to 1324 // add support to it for an OS 1325 version(Windows) 1326 version = with_timer; 1327 version(linux) 1328 version = with_timer; 1329 version(OSXCocoa) 1330 version = with_timer; 1331 1332 version(with_timer) 1333 enum bool SimpledisplayTimerAvailable = true; 1334 else 1335 enum bool SimpledisplayTimerAvailable = false; 1336 1337 /// If you have to get down and dirty with implementation details, this helps figure out if Windows is available you can `static if(UsingSimpledisplayWindows) ...` more reliably than `version()` because `version` is module-local. 1338 version(Windows) 1339 enum bool UsingSimpledisplayWindows = true; 1340 else 1341 enum bool UsingSimpledisplayWindows = false; 1342 1343 /// If you have to get down and dirty with implementation details, this helps figure out if X is available you can `static if(UsingSimpledisplayX11) ...` more reliably than `version()` because `version` is module-local. 1344 version(X11) 1345 enum bool UsingSimpledisplayX11 = true; 1346 else 1347 enum bool UsingSimpledisplayX11 = false; 1348 1349 /// If you have to get down and dirty with implementation details, this helps figure out if Cocoa is available you can `static if(UsingSimpledisplayCocoa) ...` more reliably than `version()` because `version` is module-local. 1350 version(OSXCocoa) 1351 enum bool UsingSimpledisplayCocoa = true; 1352 else 1353 enum bool UsingSimpledisplayCocoa = false; 1354 1355 /// Does this platform support multiple windows? If not, trying to create another will cause it to throw an exception. 1356 version(Windows) 1357 enum multipleWindowsSupported = true; 1358 else version(X11) 1359 enum multipleWindowsSupported = true; 1360 else version(OSXCocoa) 1361 enum multipleWindowsSupported = true; 1362 else 1363 static assert(0); 1364 1365 version(without_opengl) 1366 enum bool OpenGlEnabled = false; 1367 else 1368 enum bool OpenGlEnabled = true; 1369 1370 /++ 1371 Adds the necessary pragmas to your application to use the Windows gui subsystem. 1372 If you mix this in above your `main` function, you no longer need to use the linker 1373 flags explicitly. It does the necessary version blocks for various compilers and runtimes. 1374 1375 It does nothing if not compiling for Windows, so you need not version it out yourself. 1376 1377 Please note that Windows gui subsystem applications must NOT use std.stdio's stdout and 1378 stderr writeln. It will fail and throw an exception. 1379 1380 This will NOT work with plain `dmd` on Windows; you must use `dmd -m32mscoff` or `dmd -m64`. 1381 1382 History: 1383 Added November 24, 2021 (dub v10.4) 1384 +/ 1385 mixin template EnableWindowsSubsystem() { 1386 version(Windows) 1387 version(CRuntime_Microsoft) { 1388 pragma(linkerDirective, "/subsystem:windows"); 1389 version(LDC) 1390 pragma(linkerDirective, "/entry:wmainCRTStartup"); 1391 else 1392 pragma(linkerDirective, "/entry:mainCRTStartup"); 1393 } 1394 } 1395 1396 1397 /++ 1398 After selecting a type from [WindowTypes], you may further customize 1399 its behavior by setting one or more of these flags. 1400 1401 1402 The different window types have different meanings of `normal`. If the 1403 window type already is a good match for what you want to do, you should 1404 just use [WindowFlags.normal], the default, which will do the right thing 1405 for your users. 1406 1407 The window flags will not always be honored by the operating system 1408 and window managers; they are hints, not commands. 1409 +/ 1410 enum WindowFlags : int { 1411 normal = 0, /// 1412 skipTaskbar = 1, /// 1413 alwaysOnTop = 2, /// 1414 alwaysOnBottom = 4, /// 1415 cannotBeActivated = 8, /// 1416 alwaysRequestMouseMotionEvents = 16, /// By default, simpledisplay will attempt to optimize mouse motion event reporting when it detects a remote connection, causing them to only be issued if input is grabbed (see: [SimpleWindow.grabInput]). This means doing hover effects and mouse game control on a remote X connection may not work right. Include this flag to override this optimization and always request the motion events. However btw, if you are doing mouse game control, you probably want to grab input anyway, and hover events are usually expendable! So think before you use this flag. 1417 extraComposite = 32, /// On windows this will make this a layered windows (not supported for child windows before windows 8) to support transparency and improve animation performance. 1418 /++ 1419 Sets the window as a short-lived child of its parent, but unlike an ordinary child, 1420 it is still a top-level window. This should NOT be set separately for most window types. 1421 1422 A transient window will not keep the application open if its main window closes. 1423 1424 $(PITFALL This may not be correctly implemented and its behavior is subject to change.) 1425 1426 1427 From the ICCM: 1428 1429 $(BLOCKQUOTE 1430 It is important not to confuse WM_TRANSIENT_FOR with override-redirect. WM_TRANSIENT_FOR should be used in those cases where the pointer is not grabbed while the window is mapped (in other words, if other windows are allowed to be active while the transient is up). If other windows must be prevented from processing input (for example, when implementing pop-up menus), use override-redirect and grab the pointer while the window is mapped. 1431 1432 $(CITE https://tronche.com/gui/x/icccm/sec-4.html) 1433 ) 1434 1435 So if you are using a window type that already describes this like [WindowTypes.dropdownMenu] etc., you should not use this flag. 1436 1437 History: 1438 Added February 23, 2021 but not yet stabilized. 1439 +/ 1440 transient = 64, 1441 /++ 1442 This indicates that the window manages its own platform-specific child window input focus. You must use a delegate, [SimpleWindow.setRequestedInputFocus], to set the input when requested. This delegate returns the handle to the window that should receive the focus. 1443 1444 This is currently only used for the WM_TAKE_FOCUS protocol on X11 at this time. 1445 1446 History: 1447 Added April 1, 2022 1448 +/ 1449 managesChildWindowFocus = 128, 1450 1451 dontAutoShow = 0x1000_0000, /// Don't automatically show window after creation; you will have to call `show()` manually. 1452 } 1453 1454 /++ 1455 When creating a window, you can pass a type to SimpleWindow's constructor, 1456 then further customize the window by changing `WindowFlags`. 1457 1458 1459 You should mostly only need [normal], [undecorated], and [eventOnly] for normal 1460 use. The others are there to build a foundation for a higher level GUI toolkit, 1461 but are themselves not as high level as you might think from their names. 1462 1463 This list is based on the EMWH spec for X11. 1464 http://standards.freedesktop.org/wm-spec/1.4/ar01s05.html#idm139704063786896 1465 +/ 1466 enum WindowTypes : int { 1467 /// An ordinary application window. 1468 normal, 1469 /// A generic window without a title bar or border. You can draw on the entire area of the screen it takes up and use it as you wish. Remember that users don't really expect these though, so don't use it where a window of any other type is appropriate. 1470 undecorated, 1471 /// A window that doesn't actually display on screen. You can use it for cases where you need a dummy window handle to communicate with or something. 1472 eventOnly, 1473 /// A drop down menu, such as from a menu bar 1474 dropdownMenu, 1475 /// A popup menu, such as from a right click 1476 popupMenu, 1477 /// A popup bubble notification 1478 notification, 1479 /* 1480 menu, /// a tearable menu bar 1481 splashScreen, /// a loading splash screen for your application 1482 tooltip, /// A tiny window showing temporary help text or something. 1483 comboBoxDropdown, 1484 dialog, 1485 toolbar 1486 */ 1487 /// a child nested inside the parent. You must pass a parent window to the ctor 1488 nestedChild, 1489 1490 /++ 1491 The type you get when you pass in an existing browser handle, which means most 1492 of simpledisplay's fancy things will not be done since they were never set up. 1493 1494 Using this to the main SimpleWindow constructor explicitly will trigger an assertion 1495 failure; you should use the existing handle constructor. 1496 1497 History: 1498 Added November 17, 2022 (previously it would have type `normal`) 1499 +/ 1500 minimallyWrapped 1501 } 1502 1503 1504 private __gshared ushort sdpyOpenGLContextVersion = 0; // default: use legacy call 1505 private __gshared bool sdpyOpenGLContextCompatible = true; // default: allow "deprecated" features 1506 private __gshared char* sdpyWindowClassStr = null; 1507 private __gshared bool sdpyOpenGLContextAllowFallback = false; 1508 1509 /** 1510 Set OpenGL context version to use. This has no effect on non-OpenGL windows. 1511 You may want to change context version if you want to use advanced shaders or 1512 other modern OpenGL techinques. This setting doesn't affect already created 1513 windows. You may use version 2.1 as your default, which should be supported 1514 by any box since 2006, so seems to be a reasonable choice. 1515 1516 Note that by default version is set to `0`, which forces SimpleDisplay to use 1517 old context creation code without any version specified. This is the safest 1518 way to init OpenGL, but it may not give you access to advanced features. 1519 1520 See available OpenGL versions here: https://en.wikipedia.org/wiki/OpenGL 1521 */ 1522 void setOpenGLContextVersion() (ubyte hi, ubyte lo) { sdpyOpenGLContextVersion = cast(ushort)(hi<<8|lo); } 1523 1524 /** 1525 Set OpenGL context mode. Modern (3.0+) OpenGL versions deprecated old fixed 1526 pipeline functions, and without "compatible" mode you won't be able to use 1527 your old non-shader-based code with such contexts. By default SimpleDisplay 1528 creates compatible context, so you can gradually upgrade your OpenGL code if 1529 you want to (or leave it as is, as it should "just work"). 1530 */ 1531 @property void openGLContextCompatible() (bool v) { sdpyOpenGLContextCompatible = v; } 1532 1533 /** 1534 Set to `true` to allow creating OpenGL context with lower version than requested 1535 instead of throwing. If fallback was activated (or legacy OpenGL was requested), 1536 `openGLContextFallbackActivated()` will return `true`. 1537 */ 1538 @property void openGLContextAllowFallback() (bool v) { sdpyOpenGLContextAllowFallback = v; } 1539 1540 /** 1541 After creating OpenGL window, you can check this to see if you got only "legacy" OpenGL context. 1542 */ 1543 @property bool openGLContextFallbackActivated() () { return (sdpyOpenGLContextVersion == 0); } 1544 1545 /++ 1546 History: 1547 Added April 24, 2023 (dub v11.0) 1548 +/ 1549 version(without_opengl) {} else 1550 auto openGLCurrentContext() { 1551 version(Windows) 1552 return wglGetCurrentContext(); 1553 else 1554 return glXGetCurrentContext(); 1555 } 1556 1557 1558 /** 1559 Set window class name for all following `new SimpleWindow()` calls. 1560 1561 WARNING! For Windows, you should set your class name before creating any 1562 window, and NEVER change it after that! 1563 */ 1564 void sdpyWindowClass (const(char)[] v) { 1565 import core.stdc.stdlib : realloc; 1566 if (v.length == 0) v = "SimpleDisplayWindow"; 1567 sdpyWindowClassStr = cast(char*)realloc(sdpyWindowClassStr, v.length+1); 1568 if (sdpyWindowClassStr is null) return; // oops 1569 sdpyWindowClassStr[0..v.length+1] = 0; 1570 sdpyWindowClassStr[0..v.length] = v[]; 1571 } 1572 1573 /** 1574 Get current window class name. 1575 */ 1576 string sdpyWindowClass () { 1577 if (sdpyWindowClassStr is null) return null; 1578 foreach (immutable idx; 0..size_t.max-1) { 1579 if (sdpyWindowClassStr[idx] == 0) return sdpyWindowClassStr[0..idx].idup; 1580 } 1581 return null; 1582 } 1583 1584 /++ 1585 Returns the logical DPI of the default monitor. [0] is width, [1] is height (they are usually the same though). You may wish to round the numbers off. This isn't necessarily related to the physical side of the screen; it is associated with a user-defined scaling factor. 1586 1587 If you want per-monitor dpi values, check [SimpleWindow.actualDpi], but you can fall back to this if it returns 0. 1588 +/ 1589 float[2] getDpi() { 1590 float[2] dpi; 1591 version(Windows) { 1592 HDC screen = GetDC(null); 1593 dpi[0] = GetDeviceCaps(screen, LOGPIXELSX); 1594 dpi[1] = GetDeviceCaps(screen, LOGPIXELSY); 1595 } else version(X11) { 1596 auto display = XDisplayConnection.get; 1597 auto screen = DefaultScreen(display); 1598 1599 void fallback() { 1600 /+ 1601 // 25.4 millimeters in an inch... 1602 dpi[0] = cast(float) DisplayWidth(display, screen) / DisplayWidthMM(display, screen) * 25.4; 1603 dpi[1] = cast(float) DisplayHeight(display, screen) / DisplayHeightMM(display, screen) * 25.4; 1604 +/ 1605 1606 // the physical size isn't actually as important as the logical size since this is 1607 // all about scaling really 1608 dpi[0] = 96; 1609 dpi[1] = 96; 1610 } 1611 1612 auto xft = getXftDpi(); 1613 if(xft is float.init) 1614 fallback(); 1615 else { 1616 dpi[0] = xft; 1617 dpi[1] = xft; 1618 } 1619 } 1620 1621 return dpi; 1622 } 1623 1624 version(X11) 1625 float getXftDpi() { 1626 auto display = XDisplayConnection.get; 1627 1628 char* resourceString = XResourceManagerString(display); 1629 XrmInitialize(); 1630 1631 if (resourceString) { 1632 auto db = XrmGetStringDatabase(resourceString); 1633 XrmValue value; 1634 char* type; 1635 if (XrmGetResource(db, "Xft.dpi", "String", &type, &value) == true) { 1636 if (value.addr) { 1637 import core.stdc.stdlib; 1638 return atof(cast(char*) value.addr); 1639 } 1640 } 1641 } 1642 1643 return float.init; 1644 } 1645 1646 /++ 1647 Implementation used by [SimpleWindow.takeScreenshot]. 1648 1649 Params: 1650 handle = the native window handle. If `NativeWindowHandle.init`, it will attempt to get the whole screen. 1651 width = the width of the image you wish to capture. If 0, it will attempt to capture the full width of the target. 1652 height = the height of the image you wish to capture. If 0, it will attempt to capture the full height of the target. 1653 x = the x-offset of the image to capture, from the left. 1654 y = the y-offset of the image to capture, from the top. 1655 1656 History: 1657 Added on March 14, 2021 1658 1659 Documented public on September 23, 2021 with full support for null params (dub 10.3) 1660 1661 +/ 1662 TrueColorImage trueColorImageFromNativeHandle(PaintingHandle handle, int width = 0, int height = 0, int x = 0, int y = 0) { 1663 TrueColorImage got; 1664 version(X11) { 1665 auto display = XDisplayConnection.get; 1666 if(handle == 0) 1667 handle = RootWindow(display, DefaultScreen(display)); 1668 1669 if(width == 0 || height == 0) { 1670 Window root; 1671 int xpos, ypos; 1672 uint widthret, heightret, borderret, depthret; 1673 XGetGeometry(display, handle, &root, &xpos, &ypos, &widthret, &heightret, &borderret, &depthret); 1674 1675 if(width == 0) 1676 width = widthret; 1677 if(height == 0) 1678 height = heightret; 1679 } 1680 1681 auto image = XGetImage(display, handle, x, y, width, height, (cast(c_ulong) ~0) /*AllPlanes*/, ImageFormat.ZPixmap); 1682 1683 // https://github.com/adamdruppe/arsd/issues/98 1684 1685 auto i = new Image(image); 1686 got = i.toTrueColorImage(); 1687 1688 XDestroyImage(image); 1689 } else version(Windows) { 1690 auto hdc = GetDC(handle); 1691 scope(exit) ReleaseDC(handle, hdc); 1692 1693 if(width == 0 || height == 0) { 1694 BITMAP bmHeader; 1695 auto bm = GetCurrentObject(hdc, OBJ_BITMAP); 1696 GetObject(bm, BITMAP.sizeof, &bmHeader); 1697 if(width == 0) 1698 width = bmHeader.bmWidth; 1699 if(height == 0) 1700 height = bmHeader.bmHeight; 1701 } 1702 1703 auto i = new Image(width, height); 1704 HDC hdcMem = CreateCompatibleDC(hdc); 1705 HBITMAP hbmOld = SelectObject(hdcMem, i.handle); 1706 BitBlt(hdcMem, x, y, width, height, hdc, 0, 0, SRCCOPY); 1707 SelectObject(hdcMem, hbmOld); 1708 DeleteDC(hdcMem); 1709 1710 got = i.toTrueColorImage(); 1711 } else featureNotImplemented(); 1712 1713 return got; 1714 } 1715 1716 version(Windows) extern(Windows) private alias SetProcessDpiAwarenessContext_t = BOOL function(HANDLE); 1717 version(Windows) extern(Windows) private __gshared UINT function(HWND) GetDpiForWindow; 1718 version(Windows) extern(Windows) private __gshared BOOL function(UINT, UINT, PVOID, UINT, UINT) SystemParametersInfoForDpi; 1719 1720 version(Windows) 1721 shared static this() { 1722 auto lib = LoadLibrary("User32.dll"); 1723 if(lib is null) 1724 return; 1725 //scope(exit) 1726 //FreeLibrary(lib); 1727 1728 SetProcessDpiAwarenessContext_t SetProcessDpiAwarenessContext = cast(SetProcessDpiAwarenessContext_t) GetProcAddress(lib, "SetProcessDpiAwarenessContext"); 1729 1730 if(SetProcessDpiAwarenessContext is null) 1731 return; 1732 1733 enum DPI_AWARENESS_CONTEXT_PER_MONITOR_AWARE_V2 = cast(HANDLE) -4; 1734 if(!SetProcessDpiAwarenessContext(DPI_AWARENESS_CONTEXT_PER_MONITOR_AWARE_V2)) { 1735 //writeln(GetLastError()); 1736 } 1737 1738 GetDpiForWindow = cast(typeof(GetDpiForWindow)) GetProcAddress(lib, "GetDpiForWindow"); 1739 SystemParametersInfoForDpi = cast(typeof(SystemParametersInfoForDpi)) GetProcAddress(lib, "SystemParametersInfoForDpi"); 1740 } 1741 1742 /++ 1743 Blocking mode for event loop calls associated with a window instance. 1744 1745 History: 1746 Added December 8, 2021 (dub v10.5). Prior to that, all calls to 1747 `window.eventLoop` were the same as calls to `EventLoop.get.run`; that 1748 is, all would block until the application quit. 1749 1750 That behavior can still be achieved here with `untilApplicationQuits`, 1751 or explicitly calling the top-level `EventLoop.get.run` function. 1752 +/ 1753 enum BlockingMode { 1754 /++ 1755 The event loop call will block until the whole application is ready 1756 to quit if it is the only one running, but if it is nested inside 1757 another one, it will only block until the window you're calling it on 1758 closes. 1759 +/ 1760 automatic = 0x00, 1761 /++ 1762 The event loop call will only return when the whole application 1763 is ready to quit. This usually means all windows have been closed. 1764 1765 This is appropriate for your main application event loop. 1766 +/ 1767 untilApplicationQuits = 0x01, 1768 /++ 1769 The event loop will return when the window you're calling it on 1770 closes. If there are other windows still open, they may be destroyed 1771 unless you have another event loop running later. 1772 1773 This might be appropriate for a modal dialog box loop. Remember that 1774 other windows are still processing input though, so you can end up 1775 with a lengthy call stack if this happens in a loop, similar to a 1776 recursive function (well, it literally is a recursive function, just 1777 not an obvious looking one). 1778 +/ 1779 untilWindowCloses = 0x02, 1780 /++ 1781 If an event loop is already running, this call will immediately 1782 return, allowing the existing loop to handle it. If not, this call 1783 will block until the condition you bitwise-or into the flag. 1784 1785 The default is to block until the application quits, same as with 1786 the `automatic` setting (since if it were nested, which triggers until 1787 window closes in automatic, this flag would instead not block at all), 1788 but if you used `BlockingMode.onlyIfNotNested | BlockingMode.untilWindowCloses`, 1789 it will only nest until the window closes. You might want that if you are 1790 going to open two windows simultaneously and want closing just one of them 1791 to trigger the event loop return. 1792 +/ 1793 onlyIfNotNested = 0x10, 1794 } 1795 1796 /++ 1797 The flagship window class. 1798 1799 1800 SimpleWindow tries to make ordinary windows very easy to create and use without locking you 1801 out of more advanced or complex features of the underlying windowing system. 1802 1803 For many applications, you can simply call `new SimpleWindow(some_width, some_height, "some title")` 1804 and get a suitable window to work with. 1805 1806 From there, you can opt into additional features, like custom resizability and OpenGL support 1807 with the next two constructor arguments. Or, if you need even more, you can set a window type 1808 and customization flags with the final two constructor arguments. 1809 1810 If none of that works for you, you can also create a window using native function calls, then 1811 wrap the window in a SimpleWindow instance by calling `new SimpleWindow(native_handle)`. Remember, 1812 though, if you do this, managing the window is still your own responsibility! Notably, you 1813 will need to destroy it yourself. 1814 +/ 1815 class SimpleWindow : CapableOfHandlingNativeEvent, CapableOfBeingDrawnUpon { 1816 1817 /++ 1818 Copies the window's current state into a [TrueColorImage]. 1819 1820 Be warned: this can be a very slow operation 1821 1822 History: 1823 Actually implemented on March 14, 2021 1824 +/ 1825 TrueColorImage takeScreenshot() { 1826 version(Windows) 1827 return trueColorImageFromNativeHandle(impl.hwnd, _width, _height); 1828 else version(OSXCocoa) 1829 throw new NotYetImplementedException(); 1830 else 1831 return trueColorImageFromNativeHandle(impl.window, _width, _height); 1832 } 1833 1834 /++ 1835 Returns the actual logical DPI for the window on its current display monitor. If the window 1836 straddles monitors, it will return the value of one or the other in a platform-defined manner. 1837 1838 Please note this function may return zero if it doesn't know the answer! 1839 1840 1841 On Windows, it returns the dpi per monitor if the operating system supports it (Windows 10), 1842 or a system dpi value if not, which will live-update if the OS supports it (Windows 8 and up). 1843 1844 On X, it reads the xrandr extension to determine monitor positions and sizes. On some systems, 1845 this is not provided, meaning it will return 0. Otherwise, it will determine which monitor the 1846 window primarily resides on by checking the center point of the window against the monitor map. 1847 1848 Returns: 1849 0 if unknown. Otherwise, a rounded value of dots per inch reported by the monitor. It 1850 assumes the X and Y dpi are the same. 1851 1852 History: 1853 Added November 26, 2021 (dub v10.4) 1854 1855 It said "physical dpi" in the description prior to July 29, 2022, but the behavior was 1856 always a logical value on Windows and usually one on Linux too, so now the docs reflect 1857 that. 1858 1859 Bugs: 1860 Probably plenty. I haven't done a lot of tests on this. I know it doesn't automatically 1861 just work on linux; you need to set ARSD_SCALING_FACTOR as an environment variable to 1862 set it. Set ARSD_SCALING_FACTOR=1;1.5 for example to set it to 1x on the primary monitor 1863 and 1.5 on the secondary monitor. 1864 1865 The local dpi is not necessarily related to the physical dpi of the monitor. The name 1866 is a historical misnomer - the real thing of interest is the scale factor and due to 1867 compatibility concerns the scale would modify dpi values to trick applications. But since 1868 that's the terminology common out there, I used it too. 1869 1870 See_Also: 1871 [getDpi] gives the value provided for the default monitor. Not necessarily the same 1872 as this since the window many be on a different monitor, but it is a reasonable fallback 1873 to use if `actualDpi` returns 0. 1874 1875 [onDpiChanged] is changed when `actualDpi` has changed. 1876 +/ 1877 int actualDpi() { 1878 version(X11) bool useFallbackDpi = false; 1879 if(!actualDpiLoadAttempted) { 1880 // FIXME: do the actual monitor we are on 1881 // and on X this is a good chance to load the monitor map. 1882 version(Windows) { 1883 if(GetDpiForWindow) 1884 actualDpi_ = GetDpiForWindow(impl.hwnd); 1885 } else version(X11) { 1886 if(!xRandrInfoLoadAttemped) { 1887 xRandrInfoLoadAttemped = true; 1888 if(!XRandrLibrary.attempted) { 1889 XRandrLibrary.loadDynamicLibrary(); 1890 } 1891 1892 if(XRandrLibrary.loadSuccessful) { 1893 auto display = XDisplayConnection.get; 1894 int scratch; 1895 int major, minor; 1896 if(!XRRQueryExtension(display, &xrrEventBase, &scratch)) 1897 goto fallback; 1898 1899 XRRQueryVersion(display, &major, &minor); 1900 if(major <= 1 && minor < 5) 1901 goto fallback; 1902 1903 int count; 1904 XRRMonitorInfo *monitors = XRRGetMonitors(display, RootWindow(display, DefaultScreen(display)), true, &count); 1905 if(monitors is null) 1906 goto fallback; 1907 scope(exit) XRRFreeMonitors(monitors); 1908 1909 MonitorInfo.info = MonitorInfo.info[0 .. 0]; 1910 MonitorInfo.info.assumeSafeAppend(); 1911 foreach(idx, monitor; monitors[0 .. count]) { 1912 MonitorInfo.info ~= MonitorInfo( 1913 Rectangle(Point(monitor.x, monitor.y), Size(monitor.width, monitor.height)), 1914 Size(monitor.mwidth, monitor.mheight), 1915 cast(int) (customScalingFactorForMonitor(cast(int) idx) * getDpi()[0]) 1916 ); 1917 1918 /+ 1919 if(monitor.mwidth == 0 || monitor.mheight == 0) 1920 // unknown physical size, just guess 96 to avoid divide by zero 1921 MonitorInfo.info ~= MonitorInfo( 1922 Rectangle(Point(monitor.x, monitor.y), Size(monitor.width, monitor.height)), 1923 Size(monitor.mwidth, monitor.mheight), 1924 96 1925 ); 1926 else 1927 // and actual thing 1928 MonitorInfo.info ~= MonitorInfo( 1929 Rectangle(Point(monitor.x, monitor.y), Size(monitor.width, monitor.height)), 1930 Size(monitor.mwidth, monitor.mheight), 1931 minInternal( 1932 // millimeter to int then rounding up. 1933 cast(int)(monitor.width * 25.4 / monitor.mwidth + 0.5), 1934 cast(int)(monitor.height * 25.4 / monitor.mheight + 0.5) 1935 ) 1936 ); 1937 +/ 1938 } 1939 // writeln("Here", MonitorInfo.info); 1940 } 1941 } 1942 1943 if(XRandrLibrary.loadSuccessful) { 1944 updateActualDpi(true); 1945 // writeln("updated"); 1946 1947 if(!requestedInput) { 1948 // this is what requests live updates should the configuration change 1949 // each time you select input, it sends an initial event, so very important 1950 // to not get into a loop of selecting input, getting event, updating data, 1951 // and reselecting input... 1952 requestedInput = true; 1953 XRRSelectInput(display, impl.window, RRScreenChangeNotifyMask); 1954 // writeln("requested input"); 1955 } 1956 } else { 1957 fallback: 1958 // make sure we disable events that aren't coming 1959 xrrEventBase = -1; 1960 // best guess... respect the custom scaling user command to some extent at least though 1961 useFallbackDpi = true; 1962 } 1963 } 1964 actualDpiLoadAttempted = true; 1965 } else version(X11) if(MonitorInfo.info.length == 0) { 1966 useFallbackDpi = true; 1967 } 1968 1969 version(X11) 1970 if(useFallbackDpi) 1971 actualDpi_ = cast(int) (getDpi()[0] * customScalingFactorForMonitor(0)); 1972 1973 return actualDpi_; 1974 } 1975 1976 private int actualDpi_; 1977 private bool actualDpiLoadAttempted; 1978 1979 version(X11) private { 1980 bool requestedInput; 1981 static bool xRandrInfoLoadAttemped; 1982 struct MonitorInfo { 1983 Rectangle position; 1984 Size size; 1985 int dpi; 1986 1987 static MonitorInfo[] info; 1988 } 1989 bool screenPositionKnown; 1990 int screenPositionX; 1991 int screenPositionY; 1992 void updateActualDpi(bool loadingNow = false) { 1993 if(!loadingNow && !actualDpiLoadAttempted) 1994 actualDpi(); // just to make it do the load 1995 foreach(idx, m; MonitorInfo.info) { 1996 if(m.position.contains(Point(screenPositionX + this.width / 2, screenPositionY + this.height / 2))) { 1997 bool changed = actualDpi_ && actualDpi_ != m.dpi; 1998 actualDpi_ = m.dpi; 1999 // writeln("monitor ", idx); 2000 if(changed && onDpiChanged) 2001 onDpiChanged(); 2002 break; 2003 } 2004 } 2005 } 2006 } 2007 2008 /++ 2009 Sent when the window is moved to a new DPI context, for example, when it is dragged between monitors 2010 or if the window is moved to a new remote connection or a monitor is hot-swapped. 2011 2012 History: 2013 Added November 26, 2021 (dub v10.4) 2014 2015 See_Also: 2016 [actualDpi] 2017 +/ 2018 void delegate() onDpiChanged; 2019 2020 version(X11) { 2021 void recreateAfterDisconnect() { 2022 if(!stateDiscarded) return; 2023 2024 if(_parent !is null && _parent.stateDiscarded) 2025 _parent.recreateAfterDisconnect(); 2026 2027 bool wasHidden = hidden; 2028 2029 activeScreenPainter = null; // should already be done but just to confirm 2030 2031 actualDpi_ = 0; 2032 actualDpiLoadAttempted = false; 2033 xRandrInfoLoadAttemped = false; 2034 2035 impl.createWindow(_width, _height, _title, openglMode, _parent); 2036 2037 if(auto dh = dropHandler) { 2038 dropHandler = null; 2039 enableDragAndDrop(this, dh); 2040 } 2041 2042 if(recreateAdditionalConnectionState) 2043 recreateAdditionalConnectionState(); 2044 2045 hidden = wasHidden; 2046 stateDiscarded = false; 2047 } 2048 2049 bool stateDiscarded; 2050 void discardConnectionState() { 2051 if(XDisplayConnection.display) 2052 impl.dispose(); // if display is already null, it is hopeless to try to destroy stuff on it anyway 2053 if(discardAdditionalConnectionState) 2054 discardAdditionalConnectionState(); 2055 stateDiscarded = true; 2056 } 2057 2058 void delegate() discardAdditionalConnectionState; 2059 void delegate() recreateAdditionalConnectionState; 2060 2061 } 2062 2063 private DropHandler dropHandler; 2064 2065 SimpleWindow _parent; 2066 bool beingOpenKeepsAppOpen = true; 2067 /++ 2068 This creates a window with the given options. The window will be visible and able to receive input as soon as you start your event loop. You may draw on it immediately after creating the window, without needing to wait for the event loop to start if you want. 2069 2070 The constructor tries to have sane default arguments, so for many cases, you only need to provide a few of them. 2071 2072 Params: 2073 2074 width = the width of the window's client area, in pixels 2075 height = the height of the window's client area, in pixels 2076 title = the title of the window (seen in the title bar, taskbar, etc.). You can change it after construction with the [SimpleWindow.title] property. 2077 opengl = [OpenGlOptions] are yes and no. If yes, it creates an OpenGL context on the window. 2078 resizable = [Resizability] has three options: 2079 $(P `allowResizing`, which allows the window to be resized by the user. The `windowResized` delegate will be called when the size is changed.) 2080 $(P `fixedSize` will not allow the user to resize the window.) 2081 $(P `automaticallyScaleIfPossible` will allow the user to resize, but will still present the original size to the API user. The contents you draw will be scaled to the size the user chose. If this scaling is not efficient, the window will be fixed size. The `windowResized` event handler will never be called. This is the default.) 2082 windowType = The type of window you want to make. 2083 customizationFlags = A way to make a window without a border, always on top, skip taskbar, and more. Do not use this if one of the pre-defined [WindowTypes], given in the `windowType` argument, is a good match for what you need. 2084 parent = the parent window, if applicable. This makes the child window nested inside the parent unless you set [WindowFlags.transient], which makes it a top-level window merely owned by the "parent". 2085 +/ 2086 this(int width = 640, int height = 480, string title = null, OpenGlOptions opengl = OpenGlOptions.no, Resizability resizable = Resizability.automaticallyScaleIfPossible, WindowTypes windowType = WindowTypes.normal, int customizationFlags = WindowFlags.normal, SimpleWindow parent = null) { 2087 claimGuiThread(); 2088 version(sdpy_thread_checks) assert(thisIsGuiThread); 2089 this._width = this._virtualWidth = width; 2090 this._height = this._virtualHeight = height; 2091 this.openglMode = opengl; 2092 version(X11) { 2093 // auto scale not implemented except with opengl and even there it is kinda weird 2094 if(resizable == Resizability.automaticallyScaleIfPossible && opengl == OpenGlOptions.no) 2095 resizable = Resizability.fixedSize; 2096 } 2097 this.resizability = resizable; 2098 this.windowType = windowType; 2099 this.customizationFlags = customizationFlags; 2100 this._title = (title is null ? "D Application" : title); 2101 this._parent = parent; 2102 impl.createWindow(width, height, this._title, opengl, parent); 2103 2104 if(windowType == WindowTypes.dropdownMenu || windowType == WindowTypes.popupMenu || windowType == WindowTypes.nestedChild || (customizationFlags & WindowFlags.transient)) 2105 beingOpenKeepsAppOpen = false; 2106 } 2107 2108 /// ditto 2109 this(int width, int height, string title, Resizability resizable, OpenGlOptions opengl = OpenGlOptions.no, WindowTypes windowType = WindowTypes.normal, int customizationFlags = WindowFlags.normal, SimpleWindow parent = null) { 2110 this(width, height, title, opengl, resizable, windowType, customizationFlags, parent); 2111 } 2112 2113 /// Same as above, except using the `Size` struct instead of separate width and height. 2114 this(Size size, string title = null, OpenGlOptions opengl = OpenGlOptions.no, Resizability resizable = Resizability.automaticallyScaleIfPossible) { 2115 this(size.width, size.height, title, opengl, resizable); 2116 } 2117 2118 /// ditto 2119 this(Size size, string title, Resizability resizable, OpenGlOptions opengl = OpenGlOptions.no) { 2120 this(size, title, opengl, resizable); 2121 } 2122 2123 2124 /++ 2125 Creates a window based on the given [Image]. It's client area 2126 width and height is equal to the image. (A window's client area 2127 is the drawable space inside; it excludes the title bar, etc.) 2128 2129 Windows based on images will not be resizable and do not use OpenGL. 2130 2131 It will draw the image in upon creation, but this will be overwritten 2132 upon any draws, including the initial window visible event. 2133 2134 You probably do not want to use this and it may be removed from 2135 the library eventually, or I might change it to be a "permanent" 2136 background image; one that is automatically drawn on it before any 2137 other drawing event. idk. 2138 +/ 2139 this(Image image, string title = null) { 2140 this(image.width, image.height, title); 2141 this.image = image; 2142 } 2143 2144 /++ 2145 Wraps a native window handle with very little additional processing - notably no destruction 2146 this is incomplete so don't use it for much right now. The purpose of this is to make native 2147 windows created through the low level API (so you can use platform-specific options and 2148 other details SimpleWindow does not expose) available to the event loop wrappers. 2149 +/ 2150 this(NativeWindowHandle nativeWindow) { 2151 windowType = WindowTypes.minimallyWrapped; 2152 version(Windows) 2153 impl.hwnd = nativeWindow; 2154 else version(X11) { 2155 impl.window = nativeWindow; 2156 if(nativeWindow) 2157 display = XDisplayConnection.get(); // get initial display to not segfault 2158 } else version(OSXCocoa) { 2159 if(nativeWindow !is NullWindow) throw new NotYetImplementedException(); 2160 } else featureNotImplemented(); 2161 // FIXME: set the size correctly 2162 _width = 1; 2163 _height = 1; 2164 if(nativeWindow) 2165 nativeMapping[cast(void*) nativeWindow] = this; 2166 2167 beingOpenKeepsAppOpen = false; 2168 2169 if(nativeWindow) 2170 CapableOfHandlingNativeEvent.nativeHandleMapping[nativeWindow] = this; 2171 _suppressDestruction = true; // so it doesn't try to close 2172 } 2173 2174 /++ 2175 Used iff [WindowFlags.managesChildWindowFocus] is set when the window is created. 2176 The delegate will be called when the window manager asks you to take focus. 2177 2178 This is currently only used for the WM_TAKE_FOCUS protocol on X11 at this time. 2179 2180 History: 2181 Added April 1, 2022 (dub v10.8) 2182 +/ 2183 SimpleWindow delegate() setRequestedInputFocus; 2184 2185 /// Experimental, do not use yet 2186 /++ 2187 Grabs exclusive input from the user until you release it with 2188 [releaseInputGrab]. 2189 2190 2191 Note: it is extremely rude to do this without good reason. 2192 Reasons may include doing some kind of mouse drag operation 2193 or popping up a temporary menu that should get events and will 2194 be dismissed at ease by the user clicking away. 2195 2196 Params: 2197 keyboard = do you want to grab keyboard input? 2198 mouse = grab mouse input? 2199 confine = confine the mouse cursor to inside this window? 2200 2201 History: 2202 Prior to March 11, 2021, grabbing the keyboard would always also 2203 set the X input focus. Now, it only focuses if it is a non-transient 2204 window and otherwise manages the input direction internally. 2205 2206 This means spurious focus/blur events will no longer be sent and the 2207 application will not steal focus from other applications (which the 2208 window manager may have rejected anyway). 2209 +/ 2210 void grabInput(bool keyboard = true, bool mouse = true, bool confine = false) { 2211 static if(UsingSimpledisplayX11) { 2212 XSync(XDisplayConnection.get, 0); 2213 if(keyboard) { 2214 if(isTransient && _parent) { 2215 /* 2216 FIXME: 2217 setting the keyboard focus is not actually that helpful, what I more likely want 2218 is the events from the parent window to be sent over here if we're transient. 2219 */ 2220 2221 _parent.inputProxy = this; 2222 } else { 2223 2224 SimpleWindow setTo; 2225 if(setRequestedInputFocus !is null) 2226 setTo = setRequestedInputFocus(); 2227 if(setTo is null) 2228 setTo = this; 2229 XSetInputFocus(XDisplayConnection.get, setTo.impl.window, RevertToParent, CurrentTime); 2230 } 2231 } 2232 if(mouse) { 2233 if(auto res = XGrabPointer(XDisplayConnection.get, this.impl.window, false /* owner_events */, 2234 EventMask.PointerMotionMask // FIXME: not efficient 2235 | EventMask.ButtonPressMask 2236 | EventMask.ButtonReleaseMask 2237 /* event mask */, GrabMode.GrabModeAsync, GrabMode.GrabModeAsync, confine ? this.impl.window : None, None, CurrentTime) 2238 ) 2239 { 2240 XSync(XDisplayConnection.get, 0); 2241 import core.stdc.stdio; 2242 printf("Grab input failed %d\n", res); 2243 //throw new Exception("Grab input failed"); 2244 } else { 2245 // cool 2246 } 2247 } 2248 2249 } else version(Windows) { 2250 // FIXME: keyboard? 2251 SetCapture(impl.hwnd); 2252 if(confine) { 2253 RECT rcClip; 2254 //RECT rcOldClip; 2255 //GetClipCursor(&rcOldClip); 2256 GetWindowRect(hwnd, &rcClip); 2257 ClipCursor(&rcClip); 2258 } 2259 } else version(OSXCocoa) { 2260 // throw new NotYetImplementedException(); 2261 } else static assert(0); 2262 } 2263 2264 private Point imePopupLocation = Point(0, 0); 2265 2266 /++ 2267 Sets the location for the IME (input method editor) to pop up when the user activates it. 2268 2269 Bugs: 2270 Not implemented outside X11. 2271 +/ 2272 void setIMEPopupLocation(Point location) { 2273 static if(UsingSimpledisplayX11) { 2274 imePopupLocation = location; 2275 updateIMEPopupLocation(); 2276 } else { 2277 // this is non-fatal at this point... but still wanna find it when i search for NotYetImplementedException at least 2278 // throw new NotYetImplementedException(); 2279 } 2280 } 2281 2282 /// ditto 2283 void setIMEPopupLocation(int x, int y) { 2284 return setIMEPopupLocation(Point(x, y)); 2285 } 2286 2287 // we need to remind XIM of where we wanted to place the IME whenever the window moves 2288 // so this function gets called in setIMEPopupLocation as well as whenever the window 2289 // receives a ConfigureNotify event 2290 private void updateIMEPopupLocation() { 2291 static if(UsingSimpledisplayX11) { 2292 if (xic is null) { 2293 return; 2294 } 2295 2296 XPoint nspot; 2297 nspot.x = cast(short) imePopupLocation.x; 2298 nspot.y = cast(short) imePopupLocation.y; 2299 XVaNestedList preeditAttr = XVaCreateNestedList(0, /*XNSpotLocation*/"spotLocation".ptr, &nspot, null); 2300 XSetICValues(xic, /*XNPreeditAttributes*/"preeditAttributes".ptr, preeditAttr, null); 2301 XFree(preeditAttr); 2302 } 2303 } 2304 2305 private bool imeFocused = true; 2306 2307 /++ 2308 Tells the IME whether or not an input field is currently focused in the window. 2309 2310 Bugs: 2311 Not implemented outside X11. 2312 +/ 2313 void setIMEFocused(bool value) { 2314 imeFocused = value; 2315 updateIMEFocused(); 2316 } 2317 2318 // used to focus/unfocus the IC if necessary when the window gains/loses focus 2319 private void updateIMEFocused() { 2320 static if(UsingSimpledisplayX11) { 2321 if (xic is null) { 2322 return; 2323 } 2324 2325 if (focused && imeFocused) { 2326 XSetICFocus(xic); 2327 } else { 2328 XUnsetICFocus(xic); 2329 } 2330 } 2331 } 2332 2333 /++ 2334 Returns the native window. 2335 2336 History: 2337 Added November 5, 2021 (dub v10.4). Prior to that, you'd have 2338 to access it through the `impl` member (which is semi-supported 2339 but platform specific and here it is simple enough to offer an accessor). 2340 2341 Bugs: 2342 Not implemented outside Windows or X11. 2343 +/ 2344 NativeWindowHandle nativeWindowHandle() { 2345 version(X11) 2346 return impl.window; 2347 else version(Windows) 2348 return impl.hwnd; 2349 else 2350 throw new NotYetImplementedException(); 2351 } 2352 2353 private bool isTransient() { 2354 with(WindowTypes) 2355 final switch(windowType) { 2356 case normal, undecorated, eventOnly: 2357 case nestedChild, minimallyWrapped: 2358 return (customizationFlags & WindowFlags.transient) ? true : false; 2359 case dropdownMenu, popupMenu, notification: 2360 return true; 2361 } 2362 } 2363 2364 private SimpleWindow inputProxy; 2365 2366 /++ 2367 Releases the grab acquired by [grabInput]. 2368 +/ 2369 void releaseInputGrab() { 2370 static if(UsingSimpledisplayX11) { 2371 XUngrabPointer(XDisplayConnection.get, CurrentTime); 2372 if(_parent) 2373 _parent.inputProxy = null; 2374 } else version(Windows) { 2375 ReleaseCapture(); 2376 ClipCursor(null); 2377 } else version(OSXCocoa) { 2378 // throw new NotYetImplementedException(); 2379 } else static assert(0); 2380 } 2381 2382 /++ 2383 Sets the input focus to this window. 2384 2385 You shouldn't call this very often - please let the user control the input focus. 2386 +/ 2387 void focus() { 2388 static if(UsingSimpledisplayX11) { 2389 SimpleWindow setTo; 2390 if(setRequestedInputFocus !is null) 2391 setTo = setRequestedInputFocus(); 2392 if(setTo is null) 2393 setTo = this; 2394 XSetInputFocus(XDisplayConnection.get, setTo.impl.window, RevertToParent, CurrentTime); 2395 } else version(Windows) { 2396 SetFocus(this.impl.hwnd); 2397 } else version(OSXCocoa) { 2398 throw new NotYetImplementedException(); 2399 } else static assert(0); 2400 } 2401 2402 /++ 2403 Requests attention from the user for this window. 2404 2405 2406 The typical result of this function is to change the color 2407 of the taskbar icon, though it may be tweaked on specific 2408 platforms. 2409 2410 It is meant to unobtrusively tell the user that something 2411 relevant to them happened in the background and they should 2412 check the window when they get a chance. Upon receiving the 2413 keyboard focus, the window will automatically return to its 2414 natural state. 2415 2416 If the window already has the keyboard focus, this function 2417 may do nothing, because the user is presumed to already be 2418 giving the window attention. 2419 2420 Implementation_note: 2421 2422 `requestAttention` uses the _NET_WM_STATE_DEMANDS_ATTENTION 2423 atom on X11 and the FlashWindow function on Windows. 2424 +/ 2425 void requestAttention() { 2426 if(_focused) 2427 return; 2428 2429 version(Windows) { 2430 FLASHWINFO info; 2431 info.cbSize = info.sizeof; 2432 info.hwnd = impl.hwnd; 2433 info.dwFlags = FLASHW_TRAY; 2434 info.uCount = 1; 2435 2436 FlashWindowEx(&info); 2437 2438 } else version(X11) { 2439 demandingAttention = true; 2440 demandAttention(this, true); 2441 } else version(OSXCocoa) { 2442 throw new NotYetImplementedException(); 2443 } else static assert(0); 2444 } 2445 2446 private bool _focused; 2447 2448 version(X11) private bool demandingAttention; 2449 2450 /// This will be called when WM wants to close your window (i.e. user clicked "close" icon, for example). 2451 /// You'll have to call `close()` manually if you set this delegate. 2452 void delegate () closeQuery; 2453 2454 /// This will be called when window visibility was changed. 2455 void delegate (bool becomesVisible) visibilityChanged; 2456 2457 /// This will be called when window becomes visible for the first time. 2458 /// You can do OpenGL initialization here. Note that in X11 you can't call 2459 /// [setAsCurrentOpenGlContext] right after window creation, or X11 may 2460 /// fail to send reparent and map events (hit that with proprietary NVidia drivers). 2461 /// So you need to wait until this is called and call setAsCurrentOpenGlContext in there, then do the OpenGL initialization. 2462 private bool _visibleForTheFirstTimeCalled; 2463 void delegate () visibleForTheFirstTime; 2464 2465 /// Returns true if the window has been closed. 2466 final @property bool closed() { return _closed; } 2467 2468 private final @property bool notClosed() { return !_closed; } 2469 2470 /// Returns true if the window is focused. 2471 final @property bool focused() { return _focused; } 2472 2473 private bool _visible; 2474 /// Returns true if the window is visible (mapped). 2475 final @property bool visible() { return _visible; } 2476 2477 /// Closes the window. If there are no more open windows, the event loop will terminate. 2478 void close() { 2479 if (!_closed) { 2480 runInGuiThread( { 2481 if(_closed) return; // another thread got to it first. this isn't a big deal, it just means our message was queued 2482 if (onClosing !is null) onClosing(); 2483 impl.closeWindow(); 2484 _closed = true; 2485 } ); 2486 } 2487 } 2488 2489 /++ 2490 `close` is one of the few methods that can be called from other threads. This `shared` overload reflects that. 2491 2492 History: 2493 Overload added on March 7, 2021. 2494 +/ 2495 void close() shared { 2496 (cast() this).close(); 2497 } 2498 2499 /++ 2500 2501 +/ 2502 void maximize() { 2503 version(Windows) 2504 ShowWindow(impl.hwnd, SW_MAXIMIZE); 2505 else version(X11) { 2506 setNetWmStateAtom(this.impl.window, GetAtom!("_NET_WM_STATE_MAXIMIZED_VERT", false)(XDisplayConnection.get), true, GetAtom!("_NET_WM_STATE_MAXIMIZED_HORZ", false)(XDisplayConnection.get)); 2507 2508 // also note _NET_WM_STATE_FULLSCREEN 2509 } 2510 2511 } 2512 2513 private bool _fullscreen; 2514 version(Windows) 2515 private WINDOWPLACEMENT g_wpPrev; 2516 2517 /// not fully implemented but planned for a future release 2518 void fullscreen(bool yes) { 2519 version(Windows) { 2520 g_wpPrev.length = WINDOWPLACEMENT.sizeof; 2521 DWORD dwStyle = GetWindowLong(hwnd, GWL_STYLE); 2522 if (dwStyle & WS_OVERLAPPEDWINDOW) { 2523 MONITORINFO mi; 2524 mi.cbSize = MONITORINFO.sizeof; 2525 if (GetWindowPlacement(hwnd, &g_wpPrev) && 2526 GetMonitorInfo(MonitorFromWindow(hwnd, 2527 MONITOR_DEFAULTTOPRIMARY), &mi)) { 2528 SetWindowLong(hwnd, GWL_STYLE, 2529 dwStyle & ~WS_OVERLAPPEDWINDOW); 2530 SetWindowPos(hwnd, HWND_TOP, 2531 mi.rcMonitor.left, mi.rcMonitor.top, 2532 mi.rcMonitor.right - mi.rcMonitor.left, 2533 mi.rcMonitor.bottom - mi.rcMonitor.top, 2534 SWP_NOOWNERZORDER | SWP_FRAMECHANGED); 2535 } 2536 } else { 2537 SetWindowLong(hwnd, GWL_STYLE, 2538 dwStyle | WS_OVERLAPPEDWINDOW); 2539 SetWindowPlacement(hwnd, &g_wpPrev); 2540 SetWindowPos(hwnd, null, 0, 0, 0, 0, 2541 SWP_NOMOVE | SWP_NOSIZE | SWP_NOZORDER | 2542 SWP_NOOWNERZORDER | SWP_FRAMECHANGED); 2543 } 2544 2545 } else version(X11) { 2546 setNetWmStateAtom(this.impl.window, GetAtom!("_NET_WM_STATE_FULLSCREEN", false)(XDisplayConnection.get), yes); 2547 } 2548 2549 _fullscreen = yes; 2550 2551 } 2552 2553 bool fullscreen() { 2554 return _fullscreen; 2555 } 2556 2557 /++ 2558 Note: only implemented on Windows. No-op on other platforms. You may want to use [hide] instead. 2559 2560 +/ 2561 void minimize() { 2562 version(Windows) 2563 ShowWindow(impl.hwnd, SW_MINIMIZE); 2564 //else version(X11) 2565 //setNetWmStateAtom(this, GetAtom!("_NET_WM_STATE_MINIMIZED", false)(XDisplayConnection.get), true); 2566 } 2567 2568 /// Alias for `hidden = false` 2569 void show() { 2570 hidden = false; 2571 } 2572 2573 /// Alias for `hidden = true` 2574 void hide() { 2575 hidden = true; 2576 } 2577 2578 /// Hide cursor when it enters the window. 2579 void hideCursor() { 2580 version(OSXCocoa) throw new NotYetImplementedException(); else 2581 if (!_closed) impl.hideCursor(); 2582 } 2583 2584 /// Don't hide cursor when it enters the window. 2585 void showCursor() { 2586 version(OSXCocoa) throw new NotYetImplementedException(); else 2587 if (!_closed) impl.showCursor(); 2588 } 2589 2590 /** "Warp" mouse pointer to coordinates relative to window top-left corner. Return "success" flag. 2591 * 2592 * Please remember that the cursor is a shared resource that should usually be left to the user's 2593 * control. Try to think for other approaches before using this function. 2594 * 2595 * Note: "warping" pointer will not send any synthesised mouse events, so you probably doesn't want 2596 * to use it to move mouse pointer to some active GUI area, for example, as your window won't 2597 * receive "mouse moved here" event. 2598 */ 2599 bool warpMouse (int x, int y) { 2600 version(X11) { 2601 if (!_closed) { impl.warpMouse(x, y); return true; } 2602 } else version(Windows) { 2603 if (!_closed) { 2604 POINT point; 2605 point.x = x; 2606 point.y = y; 2607 if(ClientToScreen(impl.hwnd, &point)) { 2608 SetCursorPos(point.x, point.y); 2609 return true; 2610 } 2611 } 2612 } 2613 return false; 2614 } 2615 2616 /// Send dummy window event to ping event loop. Required to process NotificationIcon on X11, for example. 2617 void sendDummyEvent () { 2618 version(X11) { 2619 if (!_closed) { impl.sendDummyEvent(); } 2620 } 2621 } 2622 2623 /// Set window minimal size. 2624 void setMinSize (int minwidth, int minheight) { 2625 version(OSXCocoa) throw new NotYetImplementedException(); else 2626 if (!_closed) impl.setMinSize(minwidth, minheight); 2627 } 2628 2629 /// Set window maximal size. 2630 void setMaxSize (int maxwidth, int maxheight) { 2631 version(OSXCocoa) throw new NotYetImplementedException(); else 2632 if (!_closed) impl.setMaxSize(maxwidth, maxheight); 2633 } 2634 2635 /// Set window resize step (window size will be changed with the given granularity on supported platforms). 2636 /// Currently only supported on X11. 2637 void setResizeGranularity (int granx, int grany) { 2638 version(OSXCocoa) throw new NotYetImplementedException(); else 2639 if (!_closed) impl.setResizeGranularity(granx, grany); 2640 } 2641 2642 /// Move window. 2643 void move(int x, int y) { 2644 version(OSXCocoa) throw new NotYetImplementedException(); else 2645 if (!_closed) impl.move(x, y); 2646 } 2647 2648 /// ditto 2649 void move(Point p) { 2650 version(OSXCocoa) throw new NotYetImplementedException(); else 2651 if (!_closed) impl.move(p.x, p.y); 2652 } 2653 2654 /++ 2655 Resize window. 2656 2657 Note that the width and height of the window are NOT instantly 2658 updated - it waits for the window manager to approve the resize 2659 request, which means you must return to the event loop before the 2660 width and height are actually changed. 2661 +/ 2662 void resize(int w, int h) { 2663 if(!_closed && _fullscreen) fullscreen = false; 2664 version(OSXCocoa) throw new NotYetImplementedException(); else 2665 if (!_closed) impl.resize(w, h); 2666 } 2667 2668 /// Move and resize window (this can be faster and more visually pleasant than doing it separately). 2669 void moveResize (int x, int y, int w, int h) { 2670 if(!_closed && _fullscreen) fullscreen = false; 2671 version(OSXCocoa) throw new NotYetImplementedException(); else 2672 if (!_closed) impl.moveResize(x, y, w, h); 2673 } 2674 2675 private bool _hidden; 2676 2677 /// Returns true if the window is hidden. 2678 final @property bool hidden() { 2679 return _hidden; 2680 } 2681 2682 /// Shows or hides the window based on the bool argument. 2683 final @property void hidden(bool b) { 2684 _hidden = b; 2685 version(Windows) { 2686 ShowWindow(impl.hwnd, b ? SW_HIDE : SW_SHOW); 2687 } else version(X11) { 2688 if(b) 2689 //XUnmapWindow(impl.display, impl.window); 2690 XWithdrawWindow(impl.display, impl.window, DefaultScreen(impl.display)); 2691 else 2692 XMapWindow(impl.display, impl.window); 2693 } else version(OSXCocoa) { 2694 // throw new NotYetImplementedException(); 2695 } else static assert(0); 2696 } 2697 2698 /// Sets the window opacity. On X11 this requires a compositor to be running. On windows the WindowFlags.extraComposite must be set at window creation. 2699 void opacity(double opacity) @property 2700 in { 2701 assert(opacity >= 0 && opacity <= 1); 2702 } do { 2703 version (Windows) { 2704 impl.setOpacity(cast(ubyte)(255 * opacity)); 2705 } else version (X11) { 2706 impl.setOpacity(cast(uint)(uint.max * opacity)); 2707 } else throw new NotYetImplementedException(); 2708 } 2709 2710 /++ 2711 Sets your event handlers, without entering the event loop. Useful if you 2712 have multiple windows - set the handlers on each window, then only do 2713 [eventLoop] on your main window or call `EventLoop.get.run();`. 2714 2715 This assigns the given handlers to [handleKeyEvent], [handleCharEvent], 2716 [handlePulse], and [handleMouseEvent] automatically based on the provide 2717 delegate signatures. 2718 +/ 2719 void setEventHandlers(T...)(T eventHandlers) { 2720 // FIXME: add more events 2721 foreach(handler; eventHandlers) { 2722 static if(__traits(compiles, handleKeyEvent = handler)) { 2723 handleKeyEvent = handler; 2724 } else static if(__traits(compiles, handleCharEvent = handler)) { 2725 handleCharEvent = handler; 2726 } else static if(__traits(compiles, handlePulse = handler)) { 2727 handlePulse = handler; 2728 } else static if(__traits(compiles, handleMouseEvent = handler)) { 2729 handleMouseEvent = handler; 2730 } else static assert(0, "I can't use this event handler " ~ typeof(handler).stringof ~ "\nHave you tried using the delegate keyword?"); 2731 } 2732 } 2733 2734 /++ 2735 The event loop automatically returns when the window is closed 2736 pulseTimeout is given in milliseconds. If pulseTimeout == 0, no 2737 pulse timer is created. The event loop will block until an event 2738 arrives or the pulse timer goes off. 2739 2740 The given `eventHandlers` are passed to [setEventHandlers], which in turn 2741 assigns them to [handleKeyEvent], [handleCharEvent], [handlePulse], and 2742 [handleMouseEvent], based on the signature of delegates you provide. 2743 2744 Give one with no parameters to set a timer pulse handler. Give one that 2745 takes [KeyEvent] for a key handler, [MouseEvent], for a mouse handler, 2746 and one that takes `dchar` for a char event handler. You can use as many 2747 or as few handlers as you need for your application. 2748 2749 Bugs: 2750 2751 $(PITFALL 2752 You should always have one event loop live for your application. 2753 If you make two windows in sequence, the second call to eventLoop 2754 might fail: 2755 2756 --- 2757 // don't do this! 2758 auto window = new SimpleWindow(); 2759 window.eventLoop(0); 2760 2761 auto window2 = new SimpleWindow(); 2762 window2.eventLoop(0); // problematic! might crash 2763 --- 2764 2765 simpledisplay's current implementation assumes that final cleanup is 2766 done when the event loop refcount reaches zero. So after the first 2767 eventLoop returns, when there isn't already another one active, it assumes 2768 the program will exit soon and cleans up. 2769 2770 This is arguably a bug that it doesn't reinitialize, and I'll probably change 2771 it eventually, but in the mean time, there's an easy solution: 2772 2773 --- 2774 // do this 2775 EventLoop mainEventLoop = EventLoop.get; // just add this line 2776 2777 auto window = new SimpleWindow(); 2778 window.eventLoop(0); 2779 2780 auto window2 = new SimpleWindow(); 2781 window2.eventLoop(0); // perfectly fine since mainEventLoop still alive 2782 --- 2783 2784 By adding a top-level reference to the event loop, it ensures the final cleanup 2785 is not performed until it goes out of scope too, letting the individual window loops 2786 work without trouble despite the bug. 2787 ) 2788 2789 History: 2790 The overload without `pulseTimeout` was added on December 8, 2021. 2791 2792 On December 9, 2021, the default blocking mode (which is now configurable 2793 because [eventLoopWithBlockingMode] was added) switched from 2794 [BlockingMode.untilApplicationQuits] over to [BlockingMode.automatic]. This 2795 should almost never be noticeable to you since the typical simpledisplay 2796 paradigm has been (and I still recommend) to have one `eventLoop` call. 2797 2798 See_Also: 2799 [eventLoopWithBlockingMode] 2800 +/ 2801 final int eventLoop(T...)( 2802 long pulseTimeout, /// set to zero if you don't want a pulse. 2803 T eventHandlers) /// delegate list like std.concurrency.receive 2804 { 2805 return eventLoopWithBlockingMode(BlockingMode.automatic, pulseTimeout, eventHandlers); 2806 } 2807 2808 /// ditto 2809 final int eventLoop(T...)(T eventHandlers) if(T.length == 0 || is(T[0] == delegate)) 2810 { 2811 return eventLoopWithBlockingMode(BlockingMode.automatic, 0, eventHandlers); 2812 } 2813 2814 /++ 2815 This is the function [eventLoop] forwards to. It, in turn, forwards to `EventLoop.get.run`. 2816 2817 History: 2818 Added December 8, 2021 (dub v10.5) 2819 2820 Previously, this implementation was right inside [eventLoop], but when I wanted 2821 to add the new [BlockingMode] parameter, the compiler got in a trouble loop so I 2822 just renamed it instead of adding as an overload. Besides, the new name makes it 2823 easier to remember the order and avoids ambiguity between two int-like params anyway. 2824 2825 See_Also: 2826 [SimpleWindow.eventLoop], [EventLoop] 2827 2828 Bugs: 2829 The blocking mode is not implemented on OSX Cocoa nor on the (deprecated) arsd.eventloop. 2830 +/ 2831 final int eventLoopWithBlockingMode(T...)( 2832 BlockingMode blockingMode, /// when you want this function to block until 2833 long pulseTimeout, /// set to zero if you don't want a pulse. 2834 T eventHandlers) /// delegate list like std.concurrency.receive 2835 { 2836 setEventHandlers(eventHandlers); 2837 2838 version(with_eventloop) { 2839 // delegates event loop to my other module 2840 version(X11) 2841 XFlush(display); 2842 2843 import arsd.eventloop; 2844 auto handle = setInterval(handlePulse, cast(int) pulseTimeout); 2845 scope(exit) clearInterval(handle); 2846 2847 loop(); 2848 return 0; 2849 } else version(OSXCocoa) { 2850 // FIXME 2851 if (handlePulse !is null && pulseTimeout != 0) { 2852 timer = NSTimer.schedule(pulseTimeout*1e-3, 2853 cast(NSid) view, sel_registerName("simpledisplay_pulse:"), 2854 null, true); 2855 } 2856 2857 view.setNeedsDisplay(true); 2858 2859 NSApp.run(); 2860 return 0; 2861 } else { 2862 EventLoop el = EventLoop(pulseTimeout, handlePulse); 2863 2864 if((blockingMode & BlockingMode.onlyIfNotNested) && el.impl.refcount > 1) 2865 return 0; 2866 2867 return el.run( 2868 ((blockingMode & 0x0f) == BlockingMode.untilApplicationQuits) ? 2869 null : 2870 &this.notClosed 2871 ); 2872 } 2873 } 2874 2875 /++ 2876 This lets you draw on the window (or its backing buffer) using basic 2877 2D primitives. 2878 2879 Be sure to call this in a limited scope because your changes will not 2880 actually appear on the window until ScreenPainter's destructor runs. 2881 2882 Returns: an instance of [ScreenPainter], which has the drawing methods 2883 on it to draw on this window. 2884 2885 Params: 2886 manualInvalidations = if you set this to true, you will need to 2887 set the invalid rectangle on the painter yourself. If false, it 2888 assumes the whole window has been redrawn each time you draw. 2889 2890 Only invalidated rectangles are blitted back to the window when 2891 the destructor runs. Doing this yourself can reduce flickering 2892 of child windows. 2893 2894 History: 2895 The `manualInvalidations` parameter overload was added on 2896 December 30, 2021 (dub v10.5) 2897 +/ 2898 ScreenPainter draw() { 2899 return draw(false); 2900 } 2901 /// ditto 2902 ScreenPainter draw(bool manualInvalidations) { 2903 return impl.getPainter(manualInvalidations); 2904 } 2905 2906 // This is here to implement the interface we use for various native handlers. 2907 NativeEventHandler getNativeEventHandler() { return handleNativeEvent; } 2908 2909 // maps native window handles to SimpleWindow instances, if there are any 2910 // you shouldn't need this, but it is public in case you do in a native event handler or something 2911 // mac uses void* cuz NSObject opHash won't pick up in typeinfo 2912 version(OSXCocoa) 2913 public __gshared SimpleWindow[void*] nativeMapping; 2914 else 2915 public __gshared SimpleWindow[NativeWindowHandle] nativeMapping; 2916 2917 // the size the user requested in the constructor, in automatic scale modes it always pretends to be this size 2918 private int _virtualWidth; 2919 private int _virtualHeight; 2920 2921 /// Width of the window's drawable client area, in pixels. 2922 @scriptable 2923 final @property int width() const pure nothrow @safe @nogc { 2924 if(resizability == Resizability.automaticallyScaleIfPossible) 2925 return _virtualWidth; 2926 else 2927 return _width; 2928 } 2929 2930 /// Height of the window's drawable client area, in pixels. 2931 @scriptable 2932 final @property int height() const pure nothrow @safe @nogc { 2933 if(resizability == Resizability.automaticallyScaleIfPossible) 2934 return _virtualHeight; 2935 else 2936 return _height; 2937 } 2938 2939 /++ 2940 Returns the actual size of the window, bypassing the logical 2941 illusions of [Resizability.automaticallyScaleIfPossible]. 2942 2943 History: 2944 Added November 11, 2022 (dub v10.10) 2945 +/ 2946 final @property Size actualWindowSize() const pure nothrow @safe @nogc { 2947 return Size(_width, _height); 2948 } 2949 2950 2951 private int _width; 2952 private int _height; 2953 2954 // HACK: making the best of some copy constructor woes with refcounting 2955 private ScreenPainterImplementation* activeScreenPainter_; 2956 2957 protected ScreenPainterImplementation* activeScreenPainter() { return activeScreenPainter_; } 2958 protected void activeScreenPainter(ScreenPainterImplementation* i) { activeScreenPainter_ = i; } 2959 2960 private OpenGlOptions openglMode; 2961 private Resizability resizability; 2962 private WindowTypes windowType; 2963 private int customizationFlags; 2964 2965 /// `true` if OpenGL was initialized for this window. 2966 @property bool isOpenGL () const pure nothrow @safe @nogc { 2967 version(without_opengl) 2968 return false; 2969 else 2970 return (openglMode == OpenGlOptions.yes); 2971 } 2972 @property Resizability resizingMode () const pure nothrow @safe @nogc { return resizability; } /// Original resizability. 2973 @property WindowTypes type () const pure nothrow @safe @nogc { return windowType; } /// Original window type. 2974 @property int customFlags () const pure nothrow @safe @nogc { return customizationFlags; } /// Original customization flags. 2975 2976 /// "Lock" this window handle, to do multithreaded synchronization. You probably won't need 2977 /// to call this, as it's not recommended to share window between threads. 2978 void mtLock () { 2979 version(X11) { 2980 XLockDisplay(this.display); 2981 } 2982 } 2983 2984 /// "Unlock" this window handle, to do multithreaded synchronization. You probably won't need 2985 /// to call this, as it's not recommended to share window between threads. 2986 void mtUnlock () { 2987 version(X11) { 2988 XUnlockDisplay(this.display); 2989 } 2990 } 2991 2992 /// Emit a beep to get user's attention. 2993 void beep () { 2994 version(X11) { 2995 XBell(this.display, 100); 2996 } else version(Windows) { 2997 MessageBeep(0xFFFFFFFF); 2998 } 2999 } 3000 3001 3002 3003 version(without_opengl) {} else { 3004 3005 /// Put your code in here that you want to be drawn automatically when your window is uncovered. Set a handler here *before* entering your event loop any time you pass `OpenGlOptions.yes` to the constructor. Ideally, you will set this delegate immediately after constructing the `SimpleWindow`. 3006 void delegate() redrawOpenGlScene; 3007 3008 /// This will allow you to change OpenGL vsync state. 3009 final @property void vsync (bool wait) { 3010 if (this._closed) return; // window may be closed, but timer is still firing; avoid GLXBadDrawable error 3011 version(X11) { 3012 setAsCurrentOpenGlContext(); 3013 glxSetVSync(display, impl.window, wait); 3014 } else version(Windows) { 3015 setAsCurrentOpenGlContext(); 3016 wglSetVSync(wait); 3017 } 3018 } 3019 3020 /// Set this to `false` if you don't need to do `glFinish()` after `swapOpenGlBuffers()`. 3021 /// Note that at least NVidia proprietary driver may segfault if you will modify texture fast 3022 /// enough without waiting 'em to finish their frame business. 3023 bool useGLFinish = true; 3024 3025 // FIXME: it should schedule it for the end of the current iteration of the event loop... 3026 /// call this to invoke your delegate. It automatically sets up the context and flips the buffer. If you need to redraw the scene in response to an event, call this. 3027 void redrawOpenGlSceneNow() { 3028 version(X11) if (!this._visible) return; // no need to do this if window is invisible 3029 if (this._closed) return; // window may be closed, but timer is still firing; avoid GLXBadDrawable error 3030 if(redrawOpenGlScene is null) 3031 return; 3032 3033 this.mtLock(); 3034 scope(exit) this.mtUnlock(); 3035 3036 this.setAsCurrentOpenGlContext(); 3037 3038 redrawOpenGlScene(); 3039 3040 this.swapOpenGlBuffers(); 3041 // at least nvidia proprietary crap segfaults on exit if you won't do this and will call glTexSubImage2D() too fast; no, `glFlush()` won't work. 3042 if (useGLFinish) glFinish(); 3043 } 3044 3045 private bool redrawOpenGlSceneSoonSet = false; 3046 private static class RedrawOpenGlSceneEvent { 3047 SimpleWindow w; 3048 this(SimpleWindow w) { this.w = w; } 3049 } 3050 private RedrawOpenGlSceneEvent redrawOpenGlSceneEvent; 3051 /++ 3052 Queues an opengl redraw as soon as the other pending events are cleared. 3053 +/ 3054 void redrawOpenGlSceneSoon() { 3055 if(redrawOpenGlScene is null) 3056 return; 3057 3058 if(!redrawOpenGlSceneSoonSet) { 3059 redrawOpenGlSceneEvent = new RedrawOpenGlSceneEvent(this); 3060 this.addEventListener((RedrawOpenGlSceneEvent e) { e.w.redrawOpenGlSceneNow(); }); 3061 redrawOpenGlSceneSoonSet = true; 3062 } 3063 this.postEvent(redrawOpenGlSceneEvent, true); 3064 } 3065 3066 3067 /// Makes all gl* functions target this window until changed. This is only valid if you passed `OpenGlOptions.yes` to the constructor. 3068 void setAsCurrentOpenGlContext() { 3069 assert(openglMode == OpenGlOptions.yes); 3070 version(X11) { 3071 if(glXMakeCurrent(display, impl.window, impl.glc) == 0) 3072 throw new Exception("glXMakeCurrent"); 3073 } else version(Windows) { 3074 static if (SdpyIsUsingIVGLBinds) import iv.glbinds; // override druntime windows imports 3075 if (!wglMakeCurrent(ghDC, ghRC)) 3076 throw new Exception("wglMakeCurrent " ~ toInternal!int(GetLastError())); // let windows users suffer too 3077 } 3078 } 3079 3080 /// Makes all gl* functions target this window until changed. This is only valid if you passed `OpenGlOptions.yes` to the constructor. 3081 /// This doesn't throw, returning success flag instead. 3082 bool setAsCurrentOpenGlContextNT() nothrow { 3083 assert(openglMode == OpenGlOptions.yes); 3084 version(X11) { 3085 return (glXMakeCurrent(display, impl.window, impl.glc) != 0); 3086 } else version(Windows) { 3087 static if (SdpyIsUsingIVGLBinds) import iv.glbinds; // override druntime windows imports 3088 return wglMakeCurrent(ghDC, ghRC) ? true : false; 3089 } 3090 } 3091 3092 /// Releases OpenGL context, so it can be reused in, for example, different thread. This is only valid if you passed `OpenGlOptions.yes` to the constructor. 3093 /// This doesn't throw, returning success flag instead. 3094 bool releaseCurrentOpenGlContext() nothrow { 3095 assert(openglMode == OpenGlOptions.yes); 3096 version(X11) { 3097 return (glXMakeCurrent(display, 0, null) != 0); 3098 } else version(Windows) { 3099 static if (SdpyIsUsingIVGLBinds) import iv.glbinds; // override druntime windows imports 3100 return wglMakeCurrent(ghDC, null) ? true : false; 3101 } 3102 } 3103 3104 /++ 3105 simpledisplay always uses double buffering, usually automatically. This 3106 manually swaps the OpenGL buffers. 3107 3108 3109 You should not need to call this yourself because simpledisplay will do it 3110 for you after calling your `redrawOpenGlScene`. 3111 3112 Remember that this may throw an exception, which you can catch in a multithreaded 3113 application to keep your thread from dying from an unhandled exception. 3114 +/ 3115 void swapOpenGlBuffers() { 3116 assert(openglMode == OpenGlOptions.yes); 3117 version(X11) { 3118 if (!this._visible) return; // no need to do this if window is invisible 3119 if (this._closed) return; // window may be closed, but timer is still firing; avoid GLXBadDrawable error 3120 glXSwapBuffers(display, impl.window); 3121 } else version(Windows) { 3122 SwapBuffers(ghDC); 3123 } 3124 } 3125 } 3126 3127 /++ 3128 Set the window title, which is visible on the window manager title bar, operating system taskbar, etc. 3129 3130 3131 --- 3132 auto window = new SimpleWindow(100, 100, "First title"); 3133 window.title = "A new title"; 3134 --- 3135 3136 You may call this function at any time. 3137 +/ 3138 @property void title(string title) { 3139 _title = title; 3140 version(OSXCocoa) throw new NotYetImplementedException(); else 3141 impl.setTitle(title); 3142 } 3143 3144 private string _title; 3145 3146 /// Gets the title 3147 @property string title() { 3148 if(_title is null) 3149 _title = getRealTitle(); 3150 return _title; 3151 } 3152 3153 /++ 3154 Get the title as set by the window manager. 3155 May not match what you attempted to set. 3156 +/ 3157 string getRealTitle() { 3158 static if(is(typeof(impl.getTitle()))) 3159 return impl.getTitle(); 3160 else 3161 return null; 3162 } 3163 3164 // don't use this generally it is not yet really released 3165 version(X11) 3166 @property Image secret_icon() { 3167 return secret_icon_inner; 3168 } 3169 private Image secret_icon_inner; 3170 3171 3172 /// Set the icon that is seen in the title bar or taskbar, etc., for the user. If passed `null`, does nothing. 3173 @property void icon(MemoryImage icon) { 3174 if(icon is null) 3175 return; 3176 auto tci = icon.getAsTrueColorImage(); 3177 version(Windows) { 3178 winIcon = new WindowsIcon(icon); 3179 SendMessageA(impl.hwnd, 0x0080 /*WM_SETICON*/, 0 /*ICON_SMALL*/, cast(LPARAM) winIcon.hIcon); // there is also 1 == ICON_BIG 3180 } else version(X11) { 3181 secret_icon_inner = Image.fromMemoryImage(icon); 3182 // FIXME: ensure this is correct 3183 auto display = XDisplayConnection.get; 3184 arch_ulong[] buffer; 3185 buffer ~= icon.width; 3186 buffer ~= icon.height; 3187 foreach(c; tci.imageData.colors) { 3188 arch_ulong b; 3189 b |= c.a << 24; 3190 b |= c.r << 16; 3191 b |= c.g << 8; 3192 b |= c.b; 3193 buffer ~= b; 3194 } 3195 3196 XChangeProperty( 3197 display, 3198 impl.window, 3199 GetAtom!("_NET_WM_ICON", true)(display), 3200 GetAtom!"CARDINAL"(display), 3201 32 /* bits */, 3202 0 /*PropModeReplace*/, 3203 buffer.ptr, 3204 cast(int) buffer.length); 3205 } else version(OSXCocoa) { 3206 throw new NotYetImplementedException(); 3207 } else static assert(0); 3208 } 3209 3210 version(Windows) 3211 private WindowsIcon winIcon; 3212 3213 bool _suppressDestruction; 3214 3215 ~this() { 3216 if(!thisIsGuiThread) return; // FIXME: leaks if multithreaded gc 3217 if(_suppressDestruction) 3218 return; 3219 impl.dispose(); 3220 } 3221 3222 private bool _closed; 3223 3224 // the idea here is to draw something temporary on top of the main picture e.g. a blinking cursor 3225 /* 3226 ScreenPainter drawTransiently() { 3227 return impl.getPainter(); 3228 } 3229 */ 3230 3231 /// Draws an image on the window. This is meant to provide quick look 3232 /// of a static image generated elsewhere. 3233 @property void image(Image i) { 3234 /+ 3235 version(Windows) { 3236 BITMAP bm; 3237 HDC hdc = GetDC(hwnd); 3238 HDC hdcMem = CreateCompatibleDC(hdc); 3239 HBITMAP hbmOld = SelectObject(hdcMem, i.handle); 3240 3241 GetObject(i.handle, bm.sizeof, &bm); 3242 3243 BitBlt(hdc, 0, 0, bm.bmWidth, bm.bmHeight, hdcMem, 0, 0, SRCCOPY); 3244 3245 SelectObject(hdcMem, hbmOld); 3246 DeleteDC(hdcMem); 3247 ReleaseDC(hwnd, hdc); 3248 3249 /* 3250 RECT r; 3251 r.right = i.width; 3252 r.bottom = i.height; 3253 InvalidateRect(hwnd, &r, false); 3254 */ 3255 } else 3256 version(X11) { 3257 if(!destroyed) { 3258 if(i.usingXshm) 3259 XShmPutImage(display, cast(Drawable) window, gc, i.handle, 0, 0, 0, 0, i.width, i.height, false); 3260 else 3261 XPutImage(display, cast(Drawable) window, gc, i.handle, 0, 0, 0, 0, i.width, i.height); 3262 } 3263 } else 3264 version(OSXCocoa) { 3265 draw().drawImage(Point(0, 0), i); 3266 setNeedsDisplay(view, true); 3267 } else static assert(0); 3268 +/ 3269 auto painter = this.draw; 3270 painter.drawImage(Point(0, 0), i); 3271 } 3272 3273 /++ 3274 Changes the cursor for the window. If the cursor is hidden via [hideCursor], this has no effect. 3275 3276 --- 3277 window.cursor = GenericCursor.Help; 3278 // now the window mouse cursor is set to a generic help 3279 --- 3280 3281 +/ 3282 @property void cursor(MouseCursor cursor) { 3283 version(OSXCocoa) 3284 {} // featureNotImplemented(); 3285 else 3286 if(this.impl.curHidden <= 0) { 3287 static if(UsingSimpledisplayX11) { 3288 auto ch = cursor.cursorHandle; 3289 XDefineCursor(XDisplayConnection.get(), this.impl.window, ch); 3290 } else version(Windows) { 3291 auto ch = cursor.cursorHandle; 3292 impl.currentCursor = ch; 3293 SetCursor(ch); // redraw without waiting for mouse movement to update 3294 } else featureNotImplemented(); 3295 } 3296 3297 } 3298 3299 /// What follows are the event handlers. These are set automatically 3300 /// by the eventLoop function, but are still public so you can change 3301 /// them later. wasPressed == true means key down. false == key up. 3302 3303 /// Handles a low-level keyboard event. Settable through setEventHandlers. 3304 void delegate(KeyEvent ke) handleKeyEvent; 3305 3306 /// Handles a higher level keyboard event - c is the character just pressed. Settable through setEventHandlers. 3307 void delegate(dchar c) handleCharEvent; 3308 3309 /// Handles a timer pulse. Settable through setEventHandlers. 3310 void delegate() handlePulse; 3311 3312 /// Called when the focus changes, param is if we have it (true) or are losing it (false). 3313 void delegate(bool) onFocusChange; 3314 3315 /** Called inside `close()` method. Our window is still alive, and we can free various resources. 3316 * Sometimes it is easier to setup the delegate instead of subclassing. */ 3317 void delegate() onClosing; 3318 3319 /** Called when we received destroy notification. At this stage we cannot do much with our window 3320 * (as it is already dead, and it's native handle cannot be used), but we still can do some 3321 * last minute cleanup. */ 3322 void delegate() onDestroyed; 3323 3324 static if (UsingSimpledisplayX11) 3325 /** Called when Expose event comes. See Xlib manual to understand the arguments. 3326 * Return `false` if you want Simpledisplay to copy backbuffer, or `true` if you did it yourself. 3327 * You will probably never need to setup this handler, it is for very low-level stuff. 3328 * 3329 * WARNING! Xlib is multithread-locked when this handles is called! */ 3330 bool delegate(int x, int y, int width, int height, int eventsLeft) handleExpose; 3331 3332 //version(Windows) 3333 //bool delegate(WPARAM wParam, LPARAM lParam) handleWM_PAINT; 3334 3335 private { 3336 int lastMouseX = int.min; 3337 int lastMouseY = int.min; 3338 void mdx(ref MouseEvent ev) { 3339 if(lastMouseX == int.min || lastMouseY == int.min) { 3340 ev.dx = 0; 3341 ev.dy = 0; 3342 } else { 3343 ev.dx = ev.x - lastMouseX; 3344 ev.dy = ev.y - lastMouseY; 3345 } 3346 3347 lastMouseX = ev.x; 3348 lastMouseY = ev.y; 3349 } 3350 } 3351 3352 /// Mouse event handler. Settable through setEventHandlers. 3353 void delegate(MouseEvent) handleMouseEvent; 3354 3355 /// use to redraw child widgets if you use system apis to add stuff 3356 void delegate() paintingFinished; 3357 3358 void delegate() paintingFinishedDg() { 3359 return paintingFinished; 3360 } 3361 3362 /// handle a resize, after it happens. You must construct the window with Resizability.allowResizing 3363 /// for this to ever happen. 3364 void delegate(int width, int height) windowResized; 3365 3366 /++ 3367 Platform specific - handle any native message this window gets. 3368 3369 Note: this is called *in addition to* other event handlers, unless you either: 3370 3371 1) On X11, return 0 indicating that you handled it. Any other return value is simply discarded. 3372 3373 2) On Windows, set the `mustReturn` parameter to 1 indicating you've done it and your return value should be forwarded to the operating system. If you do not set `mustReturn`, your return value will be discarded. 3374 3375 On Windows, your delegate takes the form of `int delegate(HWND hwnd, UINT message, WPARAM wParam, LPARAM lParam, out int mustReturn)`. 3376 3377 On X, it takes the form of `int delegate(XEvent)`. 3378 3379 History: 3380 In ancient versions, this was `static`. If you want a global hook, use [handleNativeGlobalEvent] instead. 3381 3382 Prior to November 27, 2021, the `mustReturn` parameter was not present, and the Windows implementation would discard return values. There's still a deprecated shim with that signature, but since the return value is often important, you shouldn't use it. 3383 +/ 3384 NativeEventHandler handleNativeEvent_; 3385 3386 @property NativeEventHandler handleNativeEvent() nothrow pure @nogc const @safe { 3387 return handleNativeEvent_; 3388 } 3389 @property void handleNativeEvent(NativeEventHandler neh) nothrow pure @nogc @safe { 3390 handleNativeEvent_ = neh; 3391 } 3392 3393 version(Windows) 3394 // compatibility shim with the old deprecated way 3395 // in this one, if you return 0, it means you must return. otherwise the ret value is ignored. 3396 deprecated("This old api ignored your non-zero return values and that hurt it a lot. Add an `out int pleaseReturn` param to your delegate and set it to one if you must return the result to Windows. Otherwise, leave it zero and processing will continue through to the default window message processor.") @property void handleNativeEvent(int delegate(HWND, UINT, WPARAM, LPARAM) dg) { 3397 handleNativeEvent_ = delegate int(HWND h, UINT m, WPARAM w, LPARAM l, out int r) { 3398 auto ret = dg(h, m, w, l); 3399 if(ret == 0) 3400 r = 1; 3401 return ret; 3402 }; 3403 } 3404 3405 /// This is the same as handleNativeEvent, but static so it can hook ALL events in the loop. 3406 /// If you used to use handleNativeEvent depending on it being static, just change it to use 3407 /// this instead and it will work the same way. 3408 __gshared NativeEventHandler handleNativeGlobalEvent; 3409 3410 // private: 3411 /// The native implementation is available, but you shouldn't use it unless you are 3412 /// familiar with the underlying operating system, don't mind depending on it, and 3413 /// know simpledisplay.d's internals too. It is virtually private; you can hopefully 3414 /// do what you need to do with handleNativeEvent instead. 3415 /// 3416 /// This is likely to eventually change to be just a struct holding platform-specific 3417 /// handles instead of a template mixin at some point because I'm not happy with the 3418 /// code duplication here (ironically). 3419 mixin NativeSimpleWindowImplementation!() impl; 3420 3421 /** 3422 This is in-process one-way (from anything to window) event sending mechanics. 3423 It is thread-safe, so it can be used in multi-threaded applications to send, 3424 for example, "wake up and repaint" events when thread completed some operation. 3425 This will allow to avoid using timer pulse to check events with synchronization, 3426 'cause event handler will be called in UI thread. You can stop guessing which 3427 pulse frequency will be enough for your app. 3428 Note that events handlers may be called in arbitrary order, i.e. last registered 3429 handler can be called first, and vice versa. 3430 */ 3431 public: 3432 /** Is our custom event queue empty? Can be used in simple cases to prevent 3433 * "spamming" window with events it can't cope with. 3434 * It is safe to call this from non-UI threads. 3435 */ 3436 @property bool eventQueueEmpty() () { 3437 synchronized(this) { 3438 foreach (const ref o; eventQueue[0..eventQueueUsed]) if (!o.doProcess) return false; 3439 } 3440 return true; 3441 } 3442 3443 /** Does our custom event queue contains at least one with the given type? 3444 * Can be used in simple cases to prevent "spamming" window with events 3445 * it can't cope with. 3446 * It is safe to call this from non-UI threads. 3447 */ 3448 @property bool eventQueued(ET:Object) () { 3449 synchronized(this) { 3450 foreach (const ref o; eventQueue[0..eventQueueUsed]) { 3451 if (!o.doProcess) { 3452 if (cast(ET)(o.evt)) return true; 3453 } 3454 } 3455 } 3456 return false; 3457 } 3458 3459 /++ 3460 Event listeners added with [addEventListener] have their exceptions swallowed by the event loop. This delegate can handle them again before it proceeds. 3461 3462 History: 3463 Added May 12, 2021 3464 +/ 3465 void delegate(Exception e) nothrow eventUncaughtException; 3466 3467 /** Add listener for custom event. Can be used like this: 3468 * 3469 * --------------------- 3470 * auto eid = win.addEventListener((MyStruct evt) { ... }); 3471 * ... 3472 * win.removeEventListener(eid); 3473 * --------------------- 3474 * 3475 * Returns: 0 on failure (should never happen, so ignore it) 3476 * 3477 * $(WARNING Don't use this method in object destructors!) 3478 * 3479 * $(WARNING It is better to register all event handlers and don't remove 'em, 3480 * 'cause if event handler id counter will overflow, you won't be able 3481 * to register any more events.) 3482 */ 3483 uint addEventListener(ET:Object) (void delegate (ET) dg) { 3484 if (dg is null) return 0; // ignore empty handlers 3485 synchronized(this) { 3486 //FIXME: abort on overflow? 3487 if (++lastUsedHandlerId == 0) { --lastUsedHandlerId; return 0; } // alas, can't register more events. at all. 3488 EventHandlerEntry e; 3489 e.dg = delegate (Object o) { 3490 if (auto co = cast(ET)o) { 3491 try { 3492 dg(co); 3493 } catch (Exception e) { 3494 // sorry! 3495 if(eventUncaughtException) 3496 eventUncaughtException(e); 3497 } 3498 return true; 3499 } 3500 return false; 3501 }; 3502 e.id = lastUsedHandlerId; 3503 auto optr = eventHandlers.ptr; 3504 eventHandlers ~= e; 3505 if (eventHandlers.ptr !is optr) { 3506 import core.memory : GC; 3507 if (eventHandlers.ptr is GC.addrOf(eventHandlers.ptr)) GC.setAttr(eventHandlers.ptr, GC.BlkAttr.NO_INTERIOR); 3508 } 3509 return lastUsedHandlerId; 3510 } 3511 } 3512 3513 /// Remove event listener. It is safe to pass invalid event id here. 3514 /// $(WARNING Don't use this method in object destructors!) 3515 void removeEventListener() (uint id) { 3516 if (id == 0 || id > lastUsedHandlerId) return; 3517 synchronized(this) { 3518 foreach (immutable idx; 0..eventHandlers.length) { 3519 if (eventHandlers[idx].id == id) { 3520 foreach (immutable c; idx+1..eventHandlers.length) eventHandlers[c-1] = eventHandlers[c]; 3521 eventHandlers[$-1].dg = null; 3522 eventHandlers.length -= 1; 3523 eventHandlers.assumeSafeAppend; 3524 return; 3525 } 3526 } 3527 } 3528 } 3529 3530 /// Post event to queue. It is safe to call this from non-UI threads. 3531 /// If `timeoutmsecs` is greater than zero, the event will be delayed for at least `timeoutmsecs` milliseconds. 3532 /// if `replace` is `true`, replace all existing events typed `ET` with the new one (if `evt` is empty, remove 'em all) 3533 /// Returns `true` if event was queued. Always returns `false` if `evt` is null. 3534 bool postTimeout(ET:Object) (ET evt, uint timeoutmsecs, bool replace=false) { 3535 if (this.closed) return false; // closed windows can't handle events 3536 3537 // remove all events of type `ET` 3538 void removeAllET () { 3539 uint eidx = 0, ec = eventQueueUsed; 3540 auto eptr = eventQueue.ptr; 3541 while (eidx < ec) { 3542 if (eptr.doProcess) { ++eidx; ++eptr; continue; } 3543 if (cast(ET)eptr.evt !is null) { 3544 // i found her! 3545 if (inCustomEventProcessor) { 3546 // if we're in custom event processing loop, processor will clear it for us 3547 eptr.evt = null; 3548 ++eidx; 3549 ++eptr; 3550 } else { 3551 foreach (immutable c; eidx+1..ec) eventQueue.ptr[c-1] = eventQueue.ptr[c]; 3552 ec = --eventQueueUsed; 3553 // clear last event (it is already copied) 3554 eventQueue.ptr[ec].evt = null; 3555 } 3556 } else { 3557 ++eidx; 3558 ++eptr; 3559 } 3560 } 3561 } 3562 3563 if (evt is null) { 3564 if (replace) { synchronized(this) removeAllET(); } 3565 // ignore empty events, they can't be handled anyway 3566 return false; 3567 } 3568 3569 // add events even if no event FD/event object created yet 3570 synchronized(this) { 3571 if (replace) removeAllET(); 3572 if (eventQueueUsed == uint.max) return false; // just in case 3573 if (eventQueueUsed < eventQueue.length) { 3574 eventQueue[eventQueueUsed++] = QueuedEvent(evt, timeoutmsecs); 3575 } else { 3576 if (eventQueue.capacity == eventQueue.length) { 3577 // need to reallocate; do a trick to ensure that old array is cleared 3578 auto oarr = eventQueue; 3579 eventQueue ~= QueuedEvent(evt, timeoutmsecs); 3580 // just in case, do yet another check 3581 if (oarr.length != 0 && oarr.ptr !is eventQueue.ptr) foreach (ref e; oarr[0..eventQueueUsed]) e.evt = null; 3582 import core.memory : GC; 3583 if (eventQueue.ptr is GC.addrOf(eventQueue.ptr)) GC.setAttr(eventQueue.ptr, GC.BlkAttr.NO_INTERIOR); 3584 } else { 3585 auto optr = eventQueue.ptr; 3586 eventQueue ~= QueuedEvent(evt, timeoutmsecs); 3587 assert(eventQueue.ptr is optr); 3588 } 3589 ++eventQueueUsed; 3590 assert(eventQueueUsed == eventQueue.length); 3591 } 3592 if (!eventWakeUp()) { 3593 // can't wake up event processor, so there is no reason to keep the event 3594 assert(eventQueueUsed > 0); 3595 eventQueue[--eventQueueUsed].evt = null; 3596 return false; 3597 } 3598 return true; 3599 } 3600 } 3601 3602 /// Post event to queue. It is safe to call this from non-UI threads. 3603 /// if `replace` is `true`, replace all existing events typed `ET` with the new one (if `evt` is empty, remove 'em all) 3604 /// Returns `true` if event was queued. Always returns `false` if `evt` is null. 3605 bool postEvent(ET:Object) (ET evt, bool replace=false) { 3606 return postTimeout!ET(evt, 0, replace); 3607 } 3608 3609 private: 3610 private import core.time : MonoTime; 3611 3612 version(Posix) { 3613 __gshared int customEventFDRead = -1; 3614 __gshared int customEventFDWrite = -1; 3615 __gshared int customSignalFD = -1; 3616 } else version(Windows) { 3617 __gshared HANDLE customEventH = null; 3618 } 3619 3620 // wake up event processor 3621 static bool eventWakeUp () { 3622 version(X11) { 3623 import core.sys.posix.unistd : write; 3624 ulong n = 1; 3625 if (customEventFDWrite >= 0) write(customEventFDWrite, &n, n.sizeof); 3626 return true; 3627 } else version(Windows) { 3628 if (customEventH !is null) SetEvent(customEventH); 3629 return true; 3630 } else version(OSXCocoa) { 3631 if(globalAppDelegate) 3632 globalAppDelegate.performSelectorOnMainThread(sel_registerName("sdpyCustomEventWakeup:"), null, false); 3633 return true; 3634 } else { 3635 // not implemented for other OSes 3636 return false; 3637 } 3638 } 3639 3640 static struct QueuedEvent { 3641 Object evt; 3642 bool timed = false; 3643 MonoTime hittime = MonoTime.zero; 3644 bool doProcess = false; // process event at the current iteration (internal flag) 3645 3646 this (Object aevt, uint toutmsecs) { 3647 evt = aevt; 3648 if (toutmsecs > 0) { 3649 import core.time : msecs; 3650 timed = true; 3651 hittime = MonoTime.currTime+toutmsecs.msecs; 3652 } 3653 } 3654 } 3655 3656 alias CustomEventHandler = bool delegate (Object o) nothrow; 3657 static struct EventHandlerEntry { 3658 CustomEventHandler dg; 3659 uint id; 3660 } 3661 3662 uint lastUsedHandlerId; 3663 EventHandlerEntry[] eventHandlers; 3664 QueuedEvent[] eventQueue = null; 3665 uint eventQueueUsed = 0; // to avoid `.assumeSafeAppend` and length changes 3666 bool inCustomEventProcessor = false; // required to properly remove events 3667 3668 // process queued events and call custom event handlers 3669 // this will not process events posted from called handlers (such events are postponed for the next iteration) 3670 void processCustomEvents () { 3671 bool hasSomethingToDo = false; 3672 uint ecount; 3673 bool ocep; 3674 synchronized(this) { 3675 ocep = inCustomEventProcessor; 3676 inCustomEventProcessor = true; 3677 ecount = eventQueueUsed; // user may want to post new events from an event handler; process 'em on next iteration 3678 auto ctt = MonoTime.currTime; 3679 bool hasEmpty = false; 3680 // mark events to process (this is required for `eventQueued()`) 3681 foreach (ref qe; eventQueue[0..ecount]) { 3682 if (qe.evt is null) { hasEmpty = true; continue; } 3683 if (qe.timed) { 3684 qe.doProcess = (qe.hittime <= ctt); 3685 } else { 3686 qe.doProcess = true; 3687 } 3688 hasSomethingToDo = (hasSomethingToDo || qe.doProcess); 3689 } 3690 if (!hasSomethingToDo) { 3691 // remove empty events 3692 if (hasEmpty) { 3693 uint eidx = 0, ec = eventQueueUsed; 3694 auto eptr = eventQueue.ptr; 3695 while (eidx < ec) { 3696 if (eptr.evt is null) { 3697 foreach (immutable c; eidx+1..ec) eventQueue.ptr[c-1] = eventQueue.ptr[c]; 3698 ec = --eventQueueUsed; 3699 eventQueue.ptr[ec].evt = null; // make GC life easier 3700 } else { 3701 ++eidx; 3702 ++eptr; 3703 } 3704 } 3705 } 3706 inCustomEventProcessor = ocep; 3707 return; 3708 } 3709 } 3710 // process marked events 3711 uint efree = 0; // non-processed events will be put at this index 3712 EventHandlerEntry[] eh; 3713 Object evt; 3714 foreach (immutable eidx; 0..ecount) { 3715 synchronized(this) { 3716 if (!eventQueue[eidx].doProcess) { 3717 // skip this event 3718 assert(efree <= eidx); 3719 if (efree != eidx) { 3720 // copy this event to queue start 3721 eventQueue[efree] = eventQueue[eidx]; 3722 eventQueue[eidx].evt = null; // just in case 3723 } 3724 ++efree; 3725 continue; 3726 } 3727 evt = eventQueue[eidx].evt; 3728 eventQueue[eidx].evt = null; // in case event handler will hit GC 3729 if (evt is null) continue; // just in case 3730 // try all handlers; this can be slow, but meh... 3731 eh = eventHandlers; 3732 } 3733 foreach (ref evhan; eh) if (evhan.dg !is null) evhan.dg(evt); 3734 evt = null; 3735 eh = null; 3736 } 3737 synchronized(this) { 3738 // move all unprocessed events to queue top; efree holds first "free index" 3739 foreach (immutable eidx; ecount..eventQueueUsed) { 3740 assert(efree <= eidx); 3741 if (efree != eidx) eventQueue[efree] = eventQueue[eidx]; 3742 ++efree; 3743 } 3744 eventQueueUsed = efree; 3745 // wake up event processor on next event loop iteration if we have more queued events 3746 // also, remove empty events 3747 bool awaken = false; 3748 uint eidx = 0, ec = eventQueueUsed; 3749 auto eptr = eventQueue.ptr; 3750 while (eidx < ec) { 3751 if (eptr.evt is null) { 3752 foreach (immutable c; eidx+1..ec) eventQueue.ptr[c-1] = eventQueue.ptr[c]; 3753 ec = --eventQueueUsed; 3754 eventQueue.ptr[ec].evt = null; // make GC life easier 3755 } else { 3756 if (!awaken && !eptr.timed) { eventWakeUp(); awaken = true; } 3757 ++eidx; 3758 ++eptr; 3759 } 3760 } 3761 inCustomEventProcessor = ocep; 3762 } 3763 } 3764 3765 // for all windows in nativeMapping 3766 package static void processAllCustomEvents () { 3767 3768 cleanupQueue.process(); 3769 3770 justCommunication.processCustomEvents(); 3771 3772 foreach (SimpleWindow sw; SimpleWindow.nativeMapping.byValue) { 3773 if (sw is null || sw.closed) continue; 3774 sw.processCustomEvents(); 3775 } 3776 3777 runPendingRunInGuiThreadDelegates(); 3778 } 3779 3780 // 0: infinite (i.e. no scheduled events in queue) 3781 uint eventQueueTimeoutMSecs () { 3782 synchronized(this) { 3783 if (eventQueueUsed == 0) return 0; 3784 if (inCustomEventProcessor) assert(0, "WUTAFUUUUUUU..."); // the thing that should not be. ABSOLUTELY! (c) 3785 uint res = int.max; 3786 auto ctt = MonoTime.currTime; 3787 foreach (const ref qe; eventQueue[0..eventQueueUsed]) { 3788 if (qe.evt is null) assert(0, "WUTAFUUUUUUU..."); // the thing that should not be. ABSOLUTELY! (c) 3789 if (qe.doProcess) continue; // just in case 3790 if (!qe.timed) return 1; // minimal 3791 if (qe.hittime <= ctt) return 1; // minimal 3792 auto tms = (qe.hittime-ctt).total!"msecs"; 3793 if (tms < 1) tms = 1; // safety net 3794 if (tms >= int.max) tms = int.max-1; // and another safety net 3795 if (res > tms) res = cast(uint)tms; 3796 } 3797 return (res >= int.max ? 0 : res); 3798 } 3799 } 3800 3801 // for all windows in nativeMapping 3802 static uint eventAllQueueTimeoutMSecs () { 3803 uint res = uint.max; 3804 foreach (SimpleWindow sw; SimpleWindow.nativeMapping.byValue) { 3805 if (sw is null || sw.closed) continue; 3806 uint to = sw.eventQueueTimeoutMSecs(); 3807 if (to && to < res) { 3808 res = to; 3809 if (to == 1) break; // can't have less than this 3810 } 3811 } 3812 return (res >= int.max ? 0 : res); 3813 } 3814 3815 version(X11) { 3816 ResizeEvent pendingResizeEvent; 3817 } 3818 3819 /++ 3820 When in opengl mode and automatically resizing, it will set the opengl viewport to stretch. 3821 3822 If you work with multiple opengl contexts and/or threads, this might be more trouble than it is 3823 worth so you can disable it by setting this to `true`. 3824 3825 History: 3826 Added November 13, 2022. 3827 +/ 3828 public bool suppressAutoOpenglViewport = false; 3829 private void updateOpenglViewportIfNeeded(int width, int height) { 3830 if(suppressAutoOpenglViewport) return; 3831 3832 version(without_opengl) {} else 3833 if(openglMode == OpenGlOptions.yes && resizability == Resizability.automaticallyScaleIfPossible) { 3834 // writeln(width, " ", height); 3835 setAsCurrentOpenGlContextNT(); 3836 glViewport(0, 0, width, height); 3837 } 3838 } 3839 } 3840 3841 version(OSXCocoa) 3842 enum NSWindow NullWindow = null; 3843 else 3844 enum NullWindow = NativeWindowHandle.init; 3845 3846 /++ 3847 Magic pseudo-window for just posting events to a global queue. 3848 3849 Not entirely supported, I might delete it at any time. 3850 3851 Added Nov 5, 2021. 3852 +/ 3853 __gshared SimpleWindow justCommunication = new SimpleWindow(NullWindow); 3854 3855 /* Drag and drop support { */ 3856 version(X11) { 3857 3858 } else version(Windows) { 3859 import core.sys.windows.uuid; 3860 import core.sys.windows.ole2; 3861 import core.sys.windows.oleidl; 3862 import core.sys.windows.objidl; 3863 import core.sys.windows.wtypes; 3864 3865 pragma(lib, "ole32"); 3866 void initDnd() { 3867 auto err = OleInitialize(null); 3868 if(err != S_OK && err != S_FALSE) 3869 throw new Exception("init");//err); 3870 } 3871 } 3872 /* } End drag and drop support */ 3873 3874 3875 /// Represents a mouse cursor (aka the mouse pointer, the image seen on screen that indicates where the mouse is pointing). 3876 /// See [GenericCursor]. 3877 class MouseCursor { 3878 int osId; 3879 bool isStockCursor; 3880 private this(int osId) { 3881 this.osId = osId; 3882 this.isStockCursor = true; 3883 } 3884 3885 // https://msdn.microsoft.com/en-us/library/windows/desktop/ms648385(v=vs.85).aspx 3886 this(int xHotSpot, int yHotSpot, ubyte[] andMask, ubyte[] xorMask) {} 3887 3888 version(Windows) { 3889 HCURSOR cursor_; 3890 HCURSOR cursorHandle() { 3891 if(cursor_ is null) 3892 cursor_ = LoadCursor(null, MAKEINTRESOURCE(osId)); 3893 return cursor_; 3894 } 3895 3896 } else static if(UsingSimpledisplayX11) { 3897 Cursor cursor_ = None; 3898 int xDisplaySequence; 3899 3900 Cursor cursorHandle() { 3901 if(this.osId == None) 3902 return None; 3903 3904 // we need to reload if we on a new X connection 3905 if(cursor_ == None || XDisplayConnection.connectionSequenceNumber != xDisplaySequence) { 3906 cursor_ = XCreateFontCursor(XDisplayConnection.get(), this.osId); 3907 xDisplaySequence = XDisplayConnection.connectionSequenceNumber; 3908 } 3909 return cursor_; 3910 } 3911 } 3912 } 3913 3914 // https://developer.mozilla.org/en-US/docs/Web/CSS/cursor 3915 // https://tronche.com/gui/x/xlib/appendix/b/ 3916 // https://msdn.microsoft.com/en-us/library/windows/desktop/ms648391(v=vs.85).aspx 3917 /// Note that there is no exact appearance guaranteed for any of these items; it may change appearance on different operating systems or future simpledisplay versions. 3918 enum GenericCursorType { 3919 Default, /// The default arrow pointer. 3920 Wait, /// A cursor indicating something is loading and the user must wait. 3921 Hand, /// A pointing finger, like the one used hovering over hyperlinks in a web browser. 3922 Help, /// A cursor indicating the user can get help about the pointer location. 3923 Cross, /// A crosshair. 3924 Text, /// An i-beam shape, typically used to indicate text selection is possible. 3925 Move, /// Pointer indicating movement is possible. May also be used as SizeAll. 3926 UpArrow, /// An arrow pointing straight up. 3927 Progress, /// The hourglass and arrow, indicating the computer is working but the user can still work. Not great results on X11. 3928 NotAllowed, /// Indicates the current operation is not allowed. Not great results on X11. 3929 SizeNesw, /// Arrow pointing northeast and southwest (lower-left corner resize indicator). 3930 SizeNs, /// Arrow pointing north and south (upper/lower edge resize indicator). 3931 SizeNwse, /// Arrow pointing northwest and southeast (upper-left corner resize indicator). 3932 SizeWe, /// Arrow pointing west and east (left/right edge resize indicator). 3933 3934 } 3935 3936 /* 3937 X_plus == css cell == Windows ? 3938 */ 3939 3940 /// You get one by `GenericCursor.SomeTime`. See [GenericCursorType] for a list of types. 3941 static struct GenericCursor { 3942 static: 3943 /// 3944 MouseCursor opDispatch(string str)() if(__traits(hasMember, GenericCursorType, str)) { 3945 static MouseCursor mc; 3946 3947 auto type = __traits(getMember, GenericCursorType, str); 3948 3949 if(mc is null) { 3950 3951 version(Windows) { 3952 int osId; 3953 final switch(type) { 3954 case GenericCursorType.Default: osId = IDC_ARROW; break; 3955 case GenericCursorType.Wait: osId = IDC_WAIT; break; 3956 case GenericCursorType.Hand: osId = IDC_HAND; break; 3957 case GenericCursorType.Help: osId = IDC_HELP; break; 3958 case GenericCursorType.Cross: osId = IDC_CROSS; break; 3959 case GenericCursorType.Text: osId = IDC_IBEAM; break; 3960 case GenericCursorType.Move: osId = IDC_SIZEALL; break; 3961 case GenericCursorType.UpArrow: osId = IDC_UPARROW; break; 3962 case GenericCursorType.Progress: osId = IDC_APPSTARTING; break; 3963 case GenericCursorType.NotAllowed: osId = IDC_NO; break; 3964 case GenericCursorType.SizeNesw: osId = IDC_SIZENESW; break; 3965 case GenericCursorType.SizeNs: osId = IDC_SIZENS; break; 3966 case GenericCursorType.SizeNwse: osId = IDC_SIZENWSE; break; 3967 case GenericCursorType.SizeWe: osId = IDC_SIZEWE; break; 3968 } 3969 } else static if(UsingSimpledisplayX11) { 3970 int osId; 3971 final switch(type) { 3972 case GenericCursorType.Default: osId = None; break; 3973 case GenericCursorType.Wait: osId = 150 /* XC_watch */; break; 3974 case GenericCursorType.Hand: osId = 60 /* XC_hand2 */; break; 3975 case GenericCursorType.Help: osId = 92 /* XC_question_arrow */; break; 3976 case GenericCursorType.Cross: osId = 34 /* XC_crosshair */; break; 3977 case GenericCursorType.Text: osId = 152 /* XC_xterm */; break; 3978 case GenericCursorType.Move: osId = 52 /* XC_fleur */; break; 3979 case GenericCursorType.UpArrow: osId = 22 /* XC_center_ptr */; break; 3980 case GenericCursorType.Progress: osId = 150 /* XC_watch, best i can do i think */; break; 3981 3982 case GenericCursorType.NotAllowed: osId = 24 /* XC_circle. not great */; break; 3983 case GenericCursorType.SizeNesw: osId = 12 /* XC_bottom_left_corner */ ; break; 3984 case GenericCursorType.SizeNs: osId = 116 /* XC_sb_v_double_arrow */; break; 3985 case GenericCursorType.SizeNwse: osId = 14 /* XC_bottom_right_corner */; break; 3986 case GenericCursorType.SizeWe: osId = 108 /* XC_sb_h_double_arrow */; break; 3987 } 3988 3989 } else { 3990 int osId; 3991 // featureNotImplemented(); 3992 } 3993 3994 mc = new MouseCursor(osId); 3995 } 3996 return mc; 3997 } 3998 } 3999 4000 4001 /++ 4002 If you want to get more control over the event loop, you can use this. 4003 4004 Typically though, you can just call [SimpleWindow.eventLoop] which forwards 4005 to `EventLoop.get.run`. 4006 +/ 4007 struct EventLoop { 4008 @disable this(); 4009 4010 /// Gets a reference to an existing event loop 4011 static EventLoop get() { 4012 return EventLoop(0, null); 4013 } 4014 4015 static void quitApplication() { 4016 EventLoop.get().exit(); 4017 } 4018 4019 private __gshared static Object monitor = new Object(); // deliberate CTFE usage here fyi 4020 4021 /// Construct an application-global event loop for yourself 4022 /// See_Also: [SimpleWindow.setEventHandlers] 4023 this(long pulseTimeout, void delegate() handlePulse) { 4024 synchronized(monitor) { 4025 if(impl is null) { 4026 claimGuiThread(); 4027 version(sdpy_thread_checks) assert(thisIsGuiThread); 4028 impl = new EventLoopImpl(pulseTimeout, handlePulse); 4029 } else { 4030 if(pulseTimeout) { 4031 impl.pulseTimeout = pulseTimeout; 4032 impl.handlePulse = handlePulse; 4033 } 4034 } 4035 impl.refcount++; 4036 } 4037 } 4038 4039 ~this() { 4040 if(impl is null) 4041 return; 4042 impl.refcount--; 4043 if(impl.refcount == 0) { 4044 impl.dispose(); 4045 if(thisIsGuiThread) 4046 guiThreadFinalize(); 4047 } 4048 4049 } 4050 4051 this(this) { 4052 if(impl is null) 4053 return; 4054 impl.refcount++; 4055 } 4056 4057 /// Runs the event loop until the whileCondition, if present, returns false 4058 int run(bool delegate() whileCondition = null) { 4059 assert(impl !is null); 4060 impl.notExited = true; 4061 return impl.run(whileCondition); 4062 } 4063 4064 /// Exits the event loop 4065 void exit() { 4066 assert(impl !is null); 4067 impl.notExited = false; 4068 } 4069 4070 version(linux) 4071 ref void delegate(int) signalHandler() { 4072 assert(impl !is null); 4073 return impl.signalHandler; 4074 } 4075 4076 __gshared static EventLoopImpl* impl; 4077 } 4078 4079 version(linux) 4080 void delegate(int, int) globalHupHandler; 4081 4082 version(Posix) 4083 void makeNonBlocking(int fd) { 4084 import fcntl = core.sys.posix.fcntl; 4085 auto flags = fcntl.fcntl(fd, fcntl.F_GETFL, 0); 4086 if(flags == -1) 4087 throw new Exception("fcntl get"); 4088 flags |= fcntl.O_NONBLOCK; 4089 auto s = fcntl.fcntl(fd, fcntl.F_SETFL, flags); 4090 if(s == -1) 4091 throw new Exception("fcntl set"); 4092 } 4093 4094 struct EventLoopImpl { 4095 int refcount; 4096 4097 bool notExited = true; 4098 4099 version(linux) { 4100 static import ep = core.sys.linux.epoll; 4101 static import unix = core.sys.posix.unistd; 4102 static import err = core.stdc.errno; 4103 import core.sys.linux.timerfd; 4104 4105 void delegate(int) signalHandler; 4106 } 4107 4108 version(X11) { 4109 int pulseFd = -1; 4110 version(linux) ep.epoll_event[16] events = void; 4111 } else version(Windows) { 4112 Timer pulser; 4113 HANDLE[] handles; 4114 } 4115 4116 4117 /// "Lock" this window handle, to do multithreaded synchronization. You probably won't need 4118 /// to call this, as it's not recommended to share window between threads. 4119 void mtLock () { 4120 version(X11) { 4121 XLockDisplay(this.display); 4122 } 4123 } 4124 4125 version(X11) 4126 auto display() { return XDisplayConnection.get; } 4127 4128 /// "Unlock" this window handle, to do multithreaded synchronization. You probably won't need 4129 /// to call this, as it's not recommended to share window between threads. 4130 void mtUnlock () { 4131 version(X11) { 4132 XUnlockDisplay(this.display); 4133 } 4134 } 4135 4136 version(with_eventloop) 4137 void initialize(long pulseTimeout) {} 4138 else 4139 void initialize(long pulseTimeout) { 4140 version(Windows) { 4141 if(pulseTimeout && handlePulse !is null) 4142 pulser = new Timer(cast(int) pulseTimeout, handlePulse); 4143 4144 if (customEventH is null) { 4145 customEventH = CreateEvent(null, FALSE/*autoreset*/, FALSE/*initial state*/, null); 4146 if (customEventH !is null) { 4147 handles ~= customEventH; 4148 } else { 4149 // this is something that should not be; better be safe than sorry 4150 throw new Exception("can't create eventfd for custom event processing"); 4151 } 4152 } 4153 4154 SimpleWindow.processAllCustomEvents(); // process events added before event object creation 4155 } 4156 4157 version(linux) { 4158 prepareEventLoop(); 4159 { 4160 auto display = XDisplayConnection.get; 4161 // adding Xlib file 4162 ep.epoll_event ev = void; 4163 { import core.stdc.string : memset; memset(&ev, 0, ev.sizeof); } // this makes valgrind happy 4164 ev.events = ep.EPOLLIN; 4165 ev.data.fd = display.fd; 4166 if(ep.epoll_ctl(epollFd, ep.EPOLL_CTL_ADD, display.fd, &ev) == -1) 4167 throw new Exception("add x fd");// ~ to!string(epollFd)); 4168 displayFd = display.fd; 4169 } 4170 4171 if(pulseTimeout && handlePulse !is null) { 4172 pulseFd = timerfd_create(CLOCK_MONOTONIC, 0); 4173 if(pulseFd == -1) 4174 throw new Exception("pulse timer create failed"); 4175 4176 itimerspec value; 4177 value.it_value.tv_sec = cast(int) (pulseTimeout / 1000); 4178 value.it_value.tv_nsec = (pulseTimeout % 1000) * 1000_000; 4179 4180 value.it_interval.tv_sec = cast(int) (pulseTimeout / 1000); 4181 value.it_interval.tv_nsec = (pulseTimeout % 1000) * 1000_000; 4182 4183 if(timerfd_settime(pulseFd, 0, &value, null) == -1) 4184 throw new Exception("couldn't make pulse timer"); 4185 4186 ep.epoll_event ev = void; 4187 { import core.stdc.string : memset; memset(&ev, 0, ev.sizeof); } // this makes valgrind happy 4188 ev.events = ep.EPOLLIN; 4189 ev.data.fd = pulseFd; 4190 ep.epoll_ctl(epollFd, ep.EPOLL_CTL_ADD, pulseFd, &ev); 4191 } 4192 4193 // eventfd for custom events 4194 if (customEventFDWrite == -1) { 4195 customEventFDWrite = eventfd(0, 0); 4196 customEventFDRead = customEventFDWrite; 4197 if (customEventFDRead >= 0) { 4198 ep.epoll_event ev = void; 4199 { import core.stdc.string : memset; memset(&ev, 0, ev.sizeof); } // this makes valgrind happy 4200 ev.events = ep.EPOLLIN; 4201 ev.data.fd = customEventFDRead; 4202 ep.epoll_ctl(epollFd, ep.EPOLL_CTL_ADD, customEventFDRead, &ev); 4203 } else { 4204 // this is something that should not be; better be safe than sorry 4205 throw new Exception("can't create eventfd for custom event processing"); 4206 } 4207 } 4208 4209 if (customSignalFD == -1) { 4210 import core.sys.linux.sys.signalfd; 4211 4212 sigset_t sigset; 4213 auto err = sigemptyset(&sigset); 4214 assert(!err); 4215 err = sigaddset(&sigset, SIGINT); 4216 assert(!err); 4217 err = sigaddset(&sigset, SIGHUP); 4218 assert(!err); 4219 err = sigprocmask(SIG_BLOCK, &sigset, null); 4220 assert(!err); 4221 4222 customSignalFD = signalfd(-1, &sigset, SFD_NONBLOCK); 4223 assert(customSignalFD != -1); 4224 4225 ep.epoll_event ev = void; 4226 { import core.stdc.string : memset; memset(&ev, 0, ev.sizeof); } // this makes valgrind happy 4227 ev.events = ep.EPOLLIN; 4228 ev.data.fd = customSignalFD; 4229 ep.epoll_ctl(epollFd, ep.EPOLL_CTL_ADD, customSignalFD, &ev); 4230 } 4231 } else version(Posix) { 4232 prepareEventLoop(); 4233 if (customEventFDRead == -1) { 4234 int[2] bfr; 4235 import core.sys.posix.unistd; 4236 auto ret = pipe(bfr); 4237 if(ret == -1) throw new Exception("pipe"); 4238 customEventFDRead = bfr[0]; 4239 customEventFDWrite = bfr[1]; 4240 } 4241 4242 } 4243 4244 SimpleWindow.processAllCustomEvents(); // process events added before event FD creation 4245 4246 version(linux) { 4247 this.mtLock(); 4248 scope(exit) this.mtUnlock(); 4249 XPending(display); // no, really 4250 } 4251 4252 disposed = false; 4253 } 4254 4255 bool disposed = true; 4256 version(X11) 4257 int displayFd = -1; 4258 4259 version(with_eventloop) 4260 void dispose() {} 4261 else 4262 void dispose() { 4263 disposed = true; 4264 version(X11) { 4265 if(pulseFd != -1) { 4266 import unix = core.sys.posix.unistd; 4267 unix.close(pulseFd); 4268 pulseFd = -1; 4269 } 4270 4271 version(linux) 4272 if(displayFd != -1) { 4273 // clean up xlib fd when we exit, in case we come back later e.g. X disconnect and reconnect with new FD, don't want to still keep the old one around 4274 ep.epoll_event ev = void; 4275 { import core.stdc.string : memset; memset(&ev, 0, ev.sizeof); } // this makes valgrind happy 4276 ev.events = ep.EPOLLIN; 4277 ev.data.fd = displayFd; 4278 ep.epoll_ctl(epollFd, ep.EPOLL_CTL_DEL, displayFd, &ev); 4279 displayFd = -1; 4280 } 4281 4282 } else version(Windows) { 4283 if(pulser !is null) { 4284 pulser.destroy(); 4285 pulser = null; 4286 } 4287 if (customEventH !is null) { 4288 CloseHandle(customEventH); 4289 customEventH = null; 4290 } 4291 } 4292 } 4293 4294 this(long pulseTimeout, void delegate() handlePulse) { 4295 this.pulseTimeout = pulseTimeout; 4296 this.handlePulse = handlePulse; 4297 initialize(pulseTimeout); 4298 } 4299 4300 private long pulseTimeout; 4301 void delegate() handlePulse; 4302 4303 ~this() { 4304 dispose(); 4305 } 4306 4307 version(Posix) 4308 ref int customEventFDRead() { return SimpleWindow.customEventFDRead; } 4309 version(Posix) 4310 ref int customEventFDWrite() { return SimpleWindow.customEventFDWrite; } 4311 version(linux) 4312 ref int customSignalFD() { return SimpleWindow.customSignalFD; } 4313 version(Windows) 4314 ref auto customEventH() { return SimpleWindow.customEventH; } 4315 4316 version(with_eventloop) { 4317 int loopHelper(bool delegate() whileCondition) { 4318 // FIXME: whileCondition 4319 import arsd.eventloop; 4320 loop(); 4321 return 0; 4322 } 4323 } else 4324 int loopHelper(bool delegate() whileCondition) { 4325 version(X11) { 4326 bool done = false; 4327 4328 XFlush(display); 4329 insideXEventLoop = true; 4330 scope(exit) insideXEventLoop = false; 4331 4332 version(linux) { 4333 while(!done && (whileCondition is null || whileCondition() == true) && notExited) { 4334 bool forceXPending = false; 4335 auto wto = SimpleWindow.eventAllQueueTimeoutMSecs(); 4336 // eh... some events may be queued for "squashing" (or "late delivery"), so we have to do the following magic 4337 { 4338 this.mtLock(); 4339 scope(exit) this.mtUnlock(); 4340 if (XEventsQueued(this.display, QueueMode.QueuedAlready)) { forceXPending = true; if (wto > 10 || wto <= 0) wto = 10; } // so libX event loop will be able to do it's work 4341 } 4342 //{ import core.stdc.stdio; printf("*** wto=%d; force=%d\n", wto, (forceXPending ? 1 : 0)); } 4343 auto nfds = ep.epoll_wait(epollFd, events.ptr, events.length, (wto == 0 || wto >= int.max ? -1 : cast(int)wto)); 4344 if(nfds == -1) { 4345 if(err.errno == err.EINTR) { 4346 //if(forceXPending) goto xpending; 4347 continue; // interrupted by signal, just try again 4348 } 4349 throw new Exception("epoll wait failure"); 4350 } 4351 // writeln(nfds, " ", events[0].data.fd); 4352 4353 SimpleWindow.processAllCustomEvents(); // anyway 4354 //version(sdddd) { writeln("nfds=", nfds, "; [0]=", events[0].data.fd); } 4355 foreach(idx; 0 .. nfds) { 4356 if(done) break; 4357 auto fd = events[idx].data.fd; 4358 assert(fd != -1); // should never happen cuz the api doesn't do that but better to assert than assume. 4359 auto flags = events[idx].events; 4360 if(flags & ep.EPOLLIN) { 4361 if (fd == customSignalFD) { 4362 version(linux) { 4363 import core.sys.linux.sys.signalfd; 4364 import core.sys.posix.unistd : read; 4365 signalfd_siginfo info; 4366 read(customSignalFD, &info, info.sizeof); 4367 4368 auto sig = info.ssi_signo; 4369 4370 if(EventLoop.get.signalHandler !is null) { 4371 EventLoop.get.signalHandler()(sig); 4372 } else { 4373 EventLoop.get.exit(); 4374 } 4375 } 4376 } else if(fd == display.fd) { 4377 version(sdddd) { writeln("X EVENT PENDING!"); } 4378 this.mtLock(); 4379 scope(exit) this.mtUnlock(); 4380 while(!done && XPending(display)) { 4381 done = doXNextEvent(this.display); 4382 } 4383 forceXPending = false; 4384 } else if(fd == pulseFd) { 4385 long expirationCount; 4386 // if we go over the count, I ignore it because i don't want the pulse to go off more often and eat tons of cpu time... 4387 4388 handlePulse(); 4389 4390 // read just to clear the buffer so poll doesn't trigger again 4391 // BTW I read AFTER the pulse because if the pulse handler takes 4392 // a lot of time to execute, we don't want the app to get stuck 4393 // in a loop of timer hits without a chance to do anything else 4394 // 4395 // IOW handlePulse happens at most once per pulse interval. 4396 unix.read(pulseFd, &expirationCount, expirationCount.sizeof); 4397 forceXPending = true; // some events might have been added while the pulse was going off and xlib already read it from the fd (like as a result of a draw done in the timer handler). if so we need to flush that separately to ensure it is not delayed 4398 } else if (fd == customEventFDRead) { 4399 // we have some custom events; process 'em 4400 import core.sys.posix.unistd : read; 4401 ulong n; 4402 read(customEventFDRead, &n, n.sizeof); // reset counter value to zero again 4403 //{ import core.stdc.stdio; printf("custom event! count=%u\n", eventQueueUsed); } 4404 //SimpleWindow.processAllCustomEvents(); 4405 4406 forceXPending = true; 4407 } else { 4408 // some other timer 4409 version(sdddd) { writeln("unknown fd: ", fd); } 4410 4411 if(Timer* t = fd in Timer.mapping) 4412 (*t).trigger(); 4413 4414 if(PosixFdReader* pfr = fd in PosixFdReader.mapping) 4415 (*pfr).ready(flags); 4416 4417 // we don't know what the user did in this timer, so we need to assume that 4418 // there's X data to be flushed and potentially processed 4419 forceXPending = true; 4420 4421 // or i might add support for other FDs too 4422 // but for now it is just timer 4423 // (if you want other fds, use arsd.eventloop and compile with -version=with_eventloop), it offers a fuller api for arbitrary stuff. 4424 } 4425 } 4426 if(flags & ep.EPOLLHUP) { 4427 if(PosixFdReader* pfr = fd in PosixFdReader.mapping) 4428 (*pfr).hup(flags); 4429 if(globalHupHandler) 4430 globalHupHandler(fd, flags); 4431 } 4432 /+ 4433 } else { 4434 // not interested in OUT, we are just reading here. 4435 // 4436 // error or hup might also be reported 4437 // but it shouldn't here since we are only 4438 // using a few types of FD and Xlib will report 4439 // if it dies. 4440 // so instead of thoughtfully handling it, I'll 4441 // just throw. for now at least 4442 4443 throw new Exception("epoll did something else"); 4444 } 4445 +/ 4446 } 4447 // if we won't call `XPending()` here, libX may delay some internal event delivery. 4448 // i.e. we HAVE to repeatedly call `XPending()` even if libX fd wasn't signalled! 4449 xpending: 4450 if (!done && forceXPending) { 4451 this.mtLock(); 4452 scope(exit) this.mtUnlock(); 4453 //{ import core.stdc.stdio; printf("*** queued: %d\n", XEventsQueued(this.display, QueueMode.QueuedAlready)); } 4454 while(!done && XPending(display)) { 4455 done = doXNextEvent(this.display); 4456 } 4457 } 4458 } 4459 } else { 4460 // Generic fallback: yes to simple pulse support, 4461 // but NO timer support! 4462 4463 // FIXME: we could probably support the POSIX timer_create 4464 // signal-based option, but I'm in no rush to write it since 4465 // I prefer the fd-based functions. 4466 while (!done && (whileCondition is null || whileCondition() == true) && notExited) { 4467 4468 import core.sys.posix.poll; 4469 4470 pollfd[] pfds; 4471 pollfd[32] pfdsBuffer; 4472 auto len = PosixFdReader.mapping.length + 2; 4473 // FIXME: i should just reuse the buffer 4474 if(len < pfdsBuffer.length) 4475 pfds = pfdsBuffer[0 .. len]; 4476 else 4477 pfds = new pollfd[](len); 4478 4479 pfds[0].fd = display.fd; 4480 pfds[0].events = POLLIN; 4481 pfds[0].revents = 0; 4482 4483 int slot = 1; 4484 4485 if(customEventFDRead != -1) { 4486 pfds[slot].fd = customEventFDRead; 4487 pfds[slot].events = POLLIN; 4488 pfds[slot].revents = 0; 4489 4490 slot++; 4491 } 4492 4493 foreach(fd, obj; PosixFdReader.mapping) { 4494 if(!obj.enabled) continue; 4495 pfds[slot].fd = fd; 4496 pfds[slot].events = POLLIN; 4497 pfds[slot].revents = 0; 4498 4499 slot++; 4500 } 4501 4502 auto ret = poll(pfds.ptr, slot, pulseTimeout > 0 ? cast(int) pulseTimeout : -1); 4503 if(ret == -1) throw new Exception("poll"); 4504 4505 if(ret == 0) { 4506 // FIXME it may not necessarily time out if events keep coming 4507 if(handlePulse !is null) 4508 handlePulse(); 4509 } else { 4510 foreach(s; 0 .. slot) { 4511 if(pfds[s].revents == 0) continue; 4512 4513 if(pfds[s].fd == display.fd) { 4514 while(!done && XPending(display)) { 4515 this.mtLock(); 4516 scope(exit) this.mtUnlock(); 4517 done = doXNextEvent(this.display); 4518 } 4519 } else if(customEventFDRead != -1 && pfds[s].fd == customEventFDRead) { 4520 4521 import core.sys.posix.unistd : read; 4522 ulong n; 4523 read(customEventFDRead, &n, n.sizeof); 4524 SimpleWindow.processAllCustomEvents(); 4525 } else { 4526 auto obj = PosixFdReader.mapping[pfds[s].fd]; 4527 if(pfds[s].revents & POLLNVAL) { 4528 obj.dispose(); 4529 } else { 4530 obj.ready(pfds[s].revents); 4531 } 4532 } 4533 4534 ret--; 4535 if(ret == 0) break; 4536 } 4537 } 4538 } 4539 } 4540 } 4541 4542 version(Windows) { 4543 int ret = -1; 4544 MSG message; 4545 while(ret != 0 && (whileCondition is null || whileCondition() == true) && notExited) { 4546 eventLoopRound++; 4547 auto wto = SimpleWindow.eventAllQueueTimeoutMSecs(); 4548 auto waitResult = MsgWaitForMultipleObjectsEx( 4549 cast(int) handles.length, handles.ptr, 4550 (wto == 0 ? INFINITE : wto), /* timeout */ 4551 0x04FF, /* QS_ALLINPUT */ 4552 0x0002 /* MWMO_ALERTABLE */ | 0x0004 /* MWMO_INPUTAVAILABLE */); 4553 4554 SimpleWindow.processAllCustomEvents(); // anyway 4555 enum WAIT_OBJECT_0 = 0; 4556 if(waitResult >= WAIT_OBJECT_0 && waitResult < handles.length + WAIT_OBJECT_0) { 4557 auto h = handles[waitResult - WAIT_OBJECT_0]; 4558 if(auto e = h in WindowsHandleReader.mapping) { 4559 (*e).ready(); 4560 } 4561 } else if(waitResult == handles.length + WAIT_OBJECT_0) { 4562 // message ready 4563 int count; 4564 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 4565 ret = GetMessage(&message, null, 0, 0); 4566 if(ret == -1) 4567 throw new WindowsApiException("GetMessage", GetLastError()); 4568 TranslateMessage(&message); 4569 DispatchMessage(&message); 4570 4571 count++; 4572 if(count > 10) 4573 break; // take the opportunity to catch up on other events 4574 4575 if(ret == 0) { // WM_QUIT 4576 EventLoop.quitApplication(); 4577 break; 4578 } 4579 } 4580 } else if(waitResult == 0x000000C0L /* WAIT_IO_COMPLETION */) { 4581 SleepEx(0, true); // I call this to give it a chance to do stuff like async io 4582 } else if(waitResult == 258L /* WAIT_TIMEOUT */) { 4583 // timeout, should never happen since we aren't using it 4584 } else if(waitResult == 0xFFFFFFFF) { 4585 // failed 4586 throw new WindowsApiException("MsgWaitForMultipleObjectsEx", GetLastError()); 4587 } else { 4588 // idk.... 4589 } 4590 } 4591 4592 // return message.wParam; 4593 return 0; 4594 } else { 4595 return 0; 4596 } 4597 } 4598 4599 int run(bool delegate() whileCondition = null) { 4600 if(disposed) 4601 initialize(this.pulseTimeout); 4602 4603 version(X11) { 4604 try { 4605 return loopHelper(whileCondition); 4606 } catch(XDisconnectException e) { 4607 if(e.userRequested) { 4608 foreach(item; CapableOfHandlingNativeEvent.nativeHandleMapping) 4609 item.discardConnectionState(); 4610 XCloseDisplay(XDisplayConnection.display); 4611 } 4612 4613 XDisplayConnection.display = null; 4614 4615 this.dispose(); 4616 4617 throw e; 4618 } 4619 } else { 4620 return loopHelper(whileCondition); 4621 } 4622 } 4623 } 4624 4625 4626 /++ 4627 Provides an icon on the system notification area (also known as the system tray). 4628 4629 4630 If a notification area is not available with the NotificationIcon object is created, 4631 it will silently succeed and simply attempt to create one when an area becomes available. 4632 4633 4634 NotificationAreaIcon on Windows assumes you are on Windows Vista or later. Support for 4635 Windows XP was dropped on October 31, 2023. On the other hand, support for 32 bit transparency 4636 with true color was added at that time. I was just too lazy to write the fallback. 4637 4638 If this is an issue, let me know, it'd take about an hour to get it back in there, but I suggest 4639 you use arsd 10.x when targeting Windows XP. 4640 +/ 4641 version(OSXCocoa) {} else // NotYetImplementedException 4642 class NotificationAreaIcon : CapableOfHandlingNativeEvent { 4643 4644 version(X11) { 4645 void recreateAfterDisconnect() { 4646 stateDiscarded = false; 4647 clippixmap = None; 4648 throw new Exception("NOT IMPLEMENTED"); 4649 } 4650 4651 bool stateDiscarded; 4652 void discardConnectionState() { 4653 stateDiscarded = true; 4654 } 4655 } 4656 4657 4658 version(X11) { 4659 Image img; 4660 4661 NativeEventHandler getNativeEventHandler() { 4662 return delegate int(XEvent e) { 4663 switch(e.type) { 4664 case EventType.Expose: 4665 //case EventType.VisibilityNotify: 4666 redraw(); 4667 break; 4668 case EventType.ClientMessage: 4669 version(sddddd) { 4670 writeln("\t", e.xclient.message_type == GetAtom!("_XEMBED")(XDisplayConnection.get)); 4671 writeln("\t", e.xclient.format); 4672 writeln("\t", e.xclient.data.l); 4673 } 4674 break; 4675 case EventType.ButtonPress: 4676 auto event = e.xbutton; 4677 if (onClick !is null || onClickEx !is null) { 4678 MouseButton mb = cast(MouseButton)0; 4679 switch (event.button) { 4680 case 1: mb = MouseButton.left; break; // left 4681 case 2: mb = MouseButton.middle; break; // middle 4682 case 3: mb = MouseButton.right; break; // right 4683 case 4: mb = MouseButton.wheelUp; break; // scroll up 4684 case 5: mb = MouseButton.wheelDown; break; // scroll down 4685 case 6: break; // scroll left... 4686 case 7: break; // scroll right... 4687 case 8: mb = MouseButton.backButton; break; 4688 case 9: mb = MouseButton.forwardButton; break; 4689 default: 4690 } 4691 if (mb) { 4692 try { onClick()(mb); } catch (Exception) {} 4693 if (onClickEx !is null) try { onClickEx(event.x_root, event.y_root, mb, cast(ModifierState)event.state); } catch (Exception) {} 4694 } 4695 } 4696 break; 4697 case EventType.EnterNotify: 4698 if (onEnter !is null) { 4699 onEnter(e.xcrossing.x_root, e.xcrossing.y_root, cast(ModifierState)e.xcrossing.state); 4700 } 4701 break; 4702 case EventType.LeaveNotify: 4703 if (onLeave !is null) try { onLeave(); } catch (Exception) {} 4704 break; 4705 case EventType.DestroyNotify: 4706 active = false; 4707 CapableOfHandlingNativeEvent.nativeHandleMapping.remove(nativeHandle); 4708 break; 4709 case EventType.ConfigureNotify: 4710 auto event = e.xconfigure; 4711 this.width = event.width; 4712 this.height = event.height; 4713 // writeln(width, " x " , height, " @ ", event.x, " ", event.y); 4714 redraw(); 4715 break; 4716 default: return 1; 4717 } 4718 return 1; 4719 }; 4720 } 4721 4722 /* private */ void hideBalloon() { 4723 balloon.close(); 4724 version(with_timer) 4725 timer.destroy(); 4726 balloon = null; 4727 version(with_timer) 4728 timer = null; 4729 } 4730 4731 void redraw() { 4732 if (!active) return; 4733 4734 auto display = XDisplayConnection.get; 4735 GC gc; 4736 4737 // from https://stackoverflow.com/questions/10492275/how-to-upload-32-bit-image-to-server-side-pixmap 4738 4739 int gc_depth(int depth, Display *dpy, Window root, GC *gc) { 4740 Visual *visual; 4741 XVisualInfo vis_info; 4742 XSetWindowAttributes win_attr; 4743 c_ulong win_mask; 4744 4745 if(!XMatchVisualInfo(dpy, 0, depth, 4 /*TrueColor*/, &vis_info)) { 4746 assert(0); 4747 // return 1; 4748 } 4749 4750 visual = vis_info.visual; 4751 4752 win_attr.colormap = XCreateColormap(dpy, root, visual, AllocNone); 4753 win_attr.background_pixel = 0; 4754 win_attr.border_pixel = 0; 4755 4756 win_mask = CWBackPixel | CWColormap | CWBorderPixel; 4757 4758 *gc = XCreateGC(dpy, nativeHandle, 0, null); 4759 4760 return 0; 4761 } 4762 4763 if(useAlpha) 4764 gc_depth(32, display, RootWindow(display, DefaultScreen(display)), &gc); 4765 else 4766 gc = DefaultGC(display, DefaultScreen(display)); 4767 4768 XClearWindow(display, nativeHandle); 4769 4770 if(!useAlpha && img !is null) 4771 XSetClipMask(display, gc, clippixmap); 4772 4773 /+ 4774 XSetForeground(display, gc, 4775 cast(uint) 0 << 16 | 4776 cast(uint) 0 << 8 | 4777 cast(uint) 0); 4778 XFillRectangle(display, nativeHandle, gc, 0, 0, width, height); 4779 +/ 4780 4781 if (img is null) { 4782 XSetForeground(display, gc, 4783 cast(uint) 0 << 16 | 4784 cast(uint) 127 << 8 | 4785 cast(uint) 0); 4786 XFillArc(display, nativeHandle, 4787 gc, width / 4, height / 4, width * 2 / 4, height * 2 / 4, 0 * 64, 360 * 64); 4788 } else { 4789 int dx = 0; 4790 int dy = 0; 4791 if(width > img.width) 4792 dx = (width - img.width) / 2; 4793 if(height > img.height) 4794 dy = (height - img.height) / 2; 4795 // writeln(img.width, " ", img.height, " vs ", width, " ", height); 4796 XSetClipOrigin(display, gc, dx, dy); 4797 4798 int max(int a, int b) { 4799 if(a > b) return a; else return b; 4800 } 4801 4802 if (img.usingXshm) 4803 XShmPutImage(display, cast(Drawable)nativeHandle, gc, img.handle, 0, 0, dx, dy, max(img.width, width), max(img.height, height), false); 4804 else 4805 XPutImage(display, cast(Drawable)nativeHandle, gc, img.handle, 0, 0, dx, dy, max(img.width, width), max(img.height, height)); 4806 } 4807 XSetClipMask(display, gc, None); 4808 flushGui(); 4809 } 4810 4811 static Window getTrayOwner() { 4812 auto display = XDisplayConnection.get; 4813 auto i = cast(int) DefaultScreen(display); 4814 if(i < 10 && i >= 0) { 4815 static Atom atom; 4816 if(atom == None) 4817 atom = XInternAtom(display, cast(char*) ("_NET_SYSTEM_TRAY_S"~(cast(char) (i + '0')) ~ '\0').ptr, false); 4818 return XGetSelectionOwner(display, atom); 4819 } 4820 return None; 4821 } 4822 4823 static void sendTrayMessage(arch_long message, arch_long d1, arch_long d2, arch_long d3) { 4824 auto to = getTrayOwner(); 4825 auto display = XDisplayConnection.get; 4826 XEvent ev; 4827 ev.xclient.type = EventType.ClientMessage; 4828 ev.xclient.window = to; 4829 ev.xclient.message_type = GetAtom!("_NET_SYSTEM_TRAY_OPCODE", true)(display); 4830 ev.xclient.format = 32; 4831 ev.xclient.data.l[0] = CurrentTime; 4832 ev.xclient.data.l[1] = message; 4833 ev.xclient.data.l[2] = d1; 4834 ev.xclient.data.l[3] = d2; 4835 ev.xclient.data.l[4] = d3; 4836 4837 XSendEvent(XDisplayConnection.get, to, false, EventMask.NoEventMask, &ev); 4838 } 4839 4840 private static NotificationAreaIcon[] activeIcons; 4841 4842 // FIXME: possible leak with this stuff, should be able to clear it and stuff. 4843 private void newManager() { 4844 close(); 4845 createXWin(); 4846 4847 if(this.clippixmap) 4848 XFreePixmap(XDisplayConnection.get, clippixmap); 4849 if(this.originalMemoryImage) 4850 this.icon = this.originalMemoryImage; 4851 else if(this.img) 4852 this.icon = this.img; 4853 } 4854 4855 private bool useAlpha = false; 4856 4857 private void createXWin () { 4858 // create window 4859 auto display = XDisplayConnection.get; 4860 4861 // to check for MANAGER on root window to catch new/changed tray owners 4862 XDisplayConnection.addRootInput(EventMask.StructureNotifyMask); 4863 // so if a thing does appear, we can handle it 4864 foreach(ai; activeIcons) 4865 if(ai is this) 4866 goto alreadythere; 4867 activeIcons ~= this; 4868 alreadythere: 4869 4870 // and check for an existing tray 4871 auto trayOwner = getTrayOwner(); 4872 if(trayOwner == None) 4873 return; 4874 //throw new Exception("No notification area found"); 4875 4876 Visual* v = cast(Visual*) CopyFromParent; 4877 4878 // GNOME's default is 22x22 and KDE assumes all icons are going to match that then bitmap scales 4879 // from there. It is ugly and stupid but this gives the fewest artifacts. Good environments will send 4880 // a resize event later. 4881 width = 22; 4882 height = 22; 4883 4884 // if they system gave us a 32 bit visual we need to switch to it too 4885 int depth = 24; 4886 4887 auto visualProp = getX11PropertyData(trayOwner, GetAtom!("_NET_SYSTEM_TRAY_VISUAL", true)(display)); 4888 if(visualProp !is null) { 4889 c_ulong[] info = cast(c_ulong[]) visualProp; 4890 if(info.length == 1) { 4891 auto vid = info[0]; 4892 int returned; 4893 XVisualInfo t; 4894 t.visualid = vid; 4895 auto got = XGetVisualInfo(display, VisualIDMask, &t, &returned); 4896 if(got !is null) { 4897 if(returned == 1) { 4898 v = got.visual; 4899 depth = got.depth; 4900 // writeln("using special visual ", got.depth); 4901 // writeln(depth); 4902 } 4903 XFree(got); 4904 } 4905 } 4906 } 4907 4908 int CWFlags = CWBackPixel | CWBorderPixel | CWOverrideRedirect; 4909 XSetWindowAttributes attr; 4910 attr.background_pixel = 0; 4911 attr.border_pixel = 0; 4912 attr.override_redirect = 0; 4913 if(v !is cast(Visual*) CopyFromParent) { 4914 attr.colormap = XCreateColormap(display, RootWindow(display, DefaultScreen(display)), v, AllocNone); 4915 CWFlags |= CWColormap; 4916 if(depth == 32) 4917 useAlpha = true; 4918 else 4919 goto plain; 4920 } else { 4921 plain: 4922 attr.background_pixmap = 1 /* ParentRelative */; 4923 CWFlags |= CWBackPixmap; 4924 } 4925 4926 auto nativeWindow = XCreateWindow(display, RootWindow(display, DefaultScreen(display)), 0, 0, width, height, 0, depth, InputOutput, v, CWFlags, &attr); 4927 4928 assert(nativeWindow); 4929 4930 if(!useAlpha) 4931 XSetWindowBackgroundPixmap(display, nativeWindow, 1 /* ParentRelative */); 4932 4933 nativeHandle = nativeWindow; 4934 4935 ///+ 4936 arch_ulong[2] info; 4937 info[0] = 0; 4938 info[1] = 1; 4939 4940 string title = this.name is null ? "simpledisplay.d program" : this.name; 4941 auto XA_UTF8 = XInternAtom(display, "UTF8_STRING".ptr, false); 4942 auto XA_NETWM_NAME = XInternAtom(display, "_NET_WM_NAME".ptr, false); 4943 XChangeProperty(display, nativeWindow, XA_NETWM_NAME, XA_UTF8, 8, PropModeReplace, title.ptr, cast(uint)title.length); 4944 4945 XChangeProperty( 4946 display, 4947 nativeWindow, 4948 GetAtom!("_XEMBED_INFO", true)(display), 4949 GetAtom!("_XEMBED_INFO", true)(display), 4950 32 /* bits */, 4951 0 /*PropModeReplace*/, 4952 info.ptr, 4953 2); 4954 4955 import core.sys.posix.unistd; 4956 arch_ulong pid = getpid(); 4957 4958 XChangeProperty( 4959 display, 4960 nativeWindow, 4961 GetAtom!("_NET_WM_PID", true)(display), 4962 XA_CARDINAL, 4963 32 /* bits */, 4964 0 /*PropModeReplace*/, 4965 &pid, 4966 1); 4967 4968 updateNetWmIcon(); 4969 4970 if (sdpyWindowClassStr !is null && sdpyWindowClassStr[0]) { 4971 //{ import core.stdc.stdio; printf("winclass: [%s]\n", sdpyWindowClassStr); } 4972 XClassHint klass; 4973 XWMHints wh; 4974 XSizeHints size; 4975 klass.res_name = sdpyWindowClassStr; 4976 klass.res_class = sdpyWindowClassStr; 4977 XSetWMProperties(display, nativeWindow, null, null, null, 0, &size, &wh, &klass); 4978 } 4979 4980 // believe it or not, THIS is what xfce needed for the 9999 issue 4981 XSizeHints sh; 4982 c_long spr; 4983 XGetWMNormalHints(display, nativeWindow, &sh, &spr); 4984 sh.flags |= PMaxSize | PMinSize; 4985 // FIXME maybe nicer resizing 4986 sh.min_width = 16; 4987 sh.min_height = 16; 4988 sh.max_width = 22; 4989 sh.max_height = 22; 4990 XSetWMNormalHints(display, nativeWindow, &sh); 4991 4992 4993 //+/ 4994 4995 4996 XSelectInput(display, nativeWindow, 4997 EventMask.ButtonPressMask | EventMask.ExposureMask | EventMask.StructureNotifyMask | EventMask.VisibilityChangeMask | 4998 EventMask.EnterWindowMask | EventMask.LeaveWindowMask); 4999 5000 sendTrayMessage(SYSTEM_TRAY_REQUEST_DOCK, nativeWindow, 0, 0); 5001 // XMapWindow(display, nativeWindow); // to demo it w/o a tray 5002 5003 CapableOfHandlingNativeEvent.nativeHandleMapping[nativeWindow] = this; 5004 active = true; 5005 } 5006 5007 void updateNetWmIcon() { 5008 if(img is null) return; 5009 auto display = XDisplayConnection.get; 5010 // FIXME: ensure this is correct 5011 arch_ulong[] buffer; 5012 auto imgMi = img.toTrueColorImage; 5013 buffer ~= imgMi.width; 5014 buffer ~= imgMi.height; 5015 foreach(c; imgMi.imageData.colors) { 5016 arch_ulong b; 5017 b |= c.a << 24; 5018 b |= c.r << 16; 5019 b |= c.g << 8; 5020 b |= c.b; 5021 buffer ~= b; 5022 } 5023 5024 XChangeProperty( 5025 display, 5026 nativeHandle, 5027 GetAtom!"_NET_WM_ICON"(display), 5028 GetAtom!"CARDINAL"(display), 5029 32 /* bits */, 5030 0 /*PropModeReplace*/, 5031 buffer.ptr, 5032 cast(int) buffer.length); 5033 } 5034 5035 5036 5037 private SimpleWindow balloon; 5038 version(with_timer) 5039 private Timer timer; 5040 5041 private Window nativeHandle; 5042 private Pixmap clippixmap = None; 5043 private int width = 16; 5044 private int height = 16; 5045 private bool active = false; 5046 5047 void delegate (int x, int y, MouseButton button, ModifierState mods) onClickEx; /// x and y are globals (relative to root window). X11 only. 5048 void delegate (int x, int y, ModifierState mods) onEnter; /// x and y are global window coordinates. X11 only. 5049 void delegate () onLeave; /// X11 only. 5050 5051 @property bool closed () const pure nothrow @safe @nogc { return !active; } /// 5052 5053 /// X11 only. Get global window coordinates and size. This can be used to show various notifications. 5054 void getWindowRect (out int x, out int y, out int width, out int height) { 5055 if (!active) { width = 1; height = 1; return; } // 1: just in case 5056 Window dummyw; 5057 auto dpy = XDisplayConnection.get; 5058 //XWindowAttributes xwa; 5059 //XGetWindowAttributes(dpy, nativeHandle, &xwa); 5060 //XTranslateCoordinates(dpy, nativeHandle, RootWindow(dpy, DefaultScreen(dpy)), xwa.x, xwa.y, &x, &y, &dummyw); 5061 XTranslateCoordinates(dpy, nativeHandle, RootWindow(dpy, DefaultScreen(dpy)), x, y, &x, &y, &dummyw); 5062 width = this.width; 5063 height = this.height; 5064 } 5065 } 5066 5067 /+ 5068 What I actually want from this: 5069 5070 * set / change: icon, tooltip 5071 * handle: mouse click, right click 5072 * show: notification bubble. 5073 +/ 5074 5075 version(Windows) { 5076 WindowsIcon win32Icon; 5077 HWND hwnd; 5078 5079 NOTIFYICONDATAW data; 5080 5081 NativeEventHandler getNativeEventHandler() { 5082 return delegate int(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam, out int mustReturn) { 5083 if(msg == WM_USER) { 5084 auto event = LOWORD(lParam); 5085 auto iconId = HIWORD(lParam); 5086 //auto x = GET_X_LPARAM(wParam); 5087 //auto y = GET_Y_LPARAM(wParam); 5088 switch(event) { 5089 case WM_LBUTTONDOWN: 5090 onClick()(MouseButton.left); 5091 break; 5092 case WM_RBUTTONDOWN: 5093 onClick()(MouseButton.right); 5094 break; 5095 case WM_MBUTTONDOWN: 5096 onClick()(MouseButton.middle); 5097 break; 5098 case WM_MOUSEMOVE: 5099 // sent, we could use it. 5100 break; 5101 case WM_MOUSEWHEEL: 5102 // NOT SENT 5103 break; 5104 //case NIN_KEYSELECT: 5105 //case NIN_SELECT: 5106 //break; 5107 default: {} 5108 } 5109 } 5110 return 0; 5111 }; 5112 } 5113 5114 enum NIF_SHOWTIP = 0x00000080; 5115 5116 private static struct NOTIFYICONDATAW { 5117 DWORD cbSize; 5118 HWND hWnd; 5119 UINT uID; 5120 UINT uFlags; 5121 UINT uCallbackMessage; 5122 HICON hIcon; 5123 WCHAR[128] szTip; 5124 DWORD dwState; 5125 DWORD dwStateMask; 5126 WCHAR[256] szInfo; 5127 union { 5128 UINT uTimeout; 5129 UINT uVersion; 5130 } 5131 WCHAR[64] szInfoTitle; 5132 DWORD dwInfoFlags; 5133 GUID guidItem; 5134 HICON hBalloonIcon; 5135 } 5136 5137 } 5138 5139 /++ 5140 Note that on Windows, only left, right, and middle buttons are sent. 5141 Mouse wheel buttons are NOT set, so don't rely on those events if your 5142 program is meant to be used on Windows too. 5143 +/ 5144 this(string name, MemoryImage icon, void delegate(MouseButton button) onClick) { 5145 // The canonical constructor for Windows needs the MemoryImage, so it is here, 5146 // but on X, we need an Image, so its canonical ctor is there. They should 5147 // forward to each other though. 5148 version(X11) { 5149 this.name = name; 5150 this.onClick = onClick; 5151 createXWin(); 5152 this.icon = icon; 5153 } else version(Windows) { 5154 this.onClick = onClick; 5155 this.win32Icon = new WindowsIcon(icon); 5156 5157 HINSTANCE hInstance = cast(HINSTANCE) GetModuleHandle(null); 5158 5159 static bool registered = false; 5160 if(!registered) { 5161 WNDCLASSEX wc; 5162 wc.cbSize = wc.sizeof; 5163 wc.hInstance = hInstance; 5164 wc.lpfnWndProc = &WndProc; 5165 wc.lpszClassName = "arsd_simpledisplay_notification_icon"w.ptr; 5166 if(!RegisterClassExW(&wc)) 5167 throw new WindowsApiException("RegisterClass", GetLastError()); 5168 registered = true; 5169 } 5170 5171 this.hwnd = CreateWindowW("arsd_simpledisplay_notification_icon"w.ptr, "test"w.ptr /* name */, 0 /* dwStyle */, 0, 0, 0, 0, HWND_MESSAGE, null, hInstance, null); 5172 if(hwnd is null) 5173 throw new WindowsApiException("CreateWindow", GetLastError()); 5174 5175 data.cbSize = data.sizeof; 5176 data.hWnd = hwnd; 5177 data.uID = cast(uint) cast(void*) this; 5178 data.uFlags = NIF_MESSAGE | NIF_ICON | NIF_TIP | NIF_STATE | NIF_SHOWTIP /* use default tooltip, for now. */; 5179 // NIF_INFO means show balloon 5180 data.uCallbackMessage = WM_USER; 5181 data.hIcon = this.win32Icon.hIcon; 5182 data.szTip = ""; // FIXME 5183 data.dwState = 0; // NIS_HIDDEN; // windows vista 5184 data.dwStateMask = NIS_HIDDEN; // windows vista 5185 5186 data.uVersion = 4; // NOTIFYICON_VERSION_4; // Windows Vista and up 5187 5188 5189 Shell_NotifyIcon(NIM_ADD, cast(NOTIFYICONDATA*) &data); 5190 5191 CapableOfHandlingNativeEvent.nativeHandleMapping[this.hwnd] = this; 5192 } else version(OSXCocoa) { 5193 throw new NotYetImplementedException(); 5194 } else static assert(0); 5195 } 5196 5197 /// ditto 5198 this(string name, Image icon, void delegate(MouseButton button) onClick) { 5199 version(X11) { 5200 this.onClick = onClick; 5201 this.name = name; 5202 createXWin(); 5203 this.icon = icon; 5204 } else version(Windows) { 5205 this(name, icon is null ? null : icon.toTrueColorImage(), onClick); 5206 } else version(OSXCocoa) { 5207 throw new NotYetImplementedException(); 5208 } else static assert(0); 5209 } 5210 5211 version(X11) { 5212 /++ 5213 X-specific extension (for now at least) 5214 +/ 5215 this(string name, MemoryImage icon, void delegate(int x, int y, MouseButton button, ModifierState mods) onClickEx) { 5216 this.onClickEx = onClickEx; 5217 createXWin(); 5218 if (icon !is null) this.icon = icon; 5219 } 5220 5221 /// ditto 5222 this(string name, Image icon, void delegate(int x, int y, MouseButton button, ModifierState mods) onClickEx) { 5223 this.onClickEx = onClickEx; 5224 createXWin(); 5225 this.icon = icon; 5226 } 5227 } 5228 5229 private void delegate (MouseButton button) onClick_; 5230 5231 /// 5232 @property final void delegate(MouseButton) onClick() { 5233 if(onClick_ is null) 5234 onClick_ = delegate void(MouseButton) {}; 5235 return onClick_; 5236 } 5237 5238 /// ditto 5239 @property final void onClick(void delegate(MouseButton) handler) { 5240 // I made this a property setter so we can wrap smaller arg 5241 // delegates and just forward all to onClickEx or something. 5242 onClick_ = handler; 5243 } 5244 5245 5246 string name_; 5247 @property void name(string n) { 5248 name_ = n; 5249 } 5250 5251 @property string name() { 5252 return name_; 5253 } 5254 5255 private MemoryImage originalMemoryImage; 5256 5257 /// 5258 @property void icon(MemoryImage i) { 5259 version(X11) { 5260 this.originalMemoryImage = i; 5261 if (!active) return; 5262 if (i !is null) { 5263 this.img = Image.fromMemoryImage(i, useAlpha, false); 5264 if(!useAlpha) 5265 this.clippixmap = transparencyMaskFromMemoryImage(i, nativeHandle); 5266 // writeln("using pixmap ", clippixmap); 5267 updateNetWmIcon(); 5268 redraw(); 5269 } else { 5270 if (this.img !is null) { 5271 this.img = null; 5272 redraw(); 5273 } 5274 } 5275 } else version(Windows) { 5276 this.win32Icon = new WindowsIcon(i); 5277 5278 data.uFlags = NIF_ICON; 5279 data.hIcon = this.win32Icon.hIcon; 5280 5281 Shell_NotifyIcon(NIM_MODIFY, cast(NOTIFYICONDATA*) &data); 5282 } else version(OSXCocoa) { 5283 throw new NotYetImplementedException(); 5284 } else static assert(0); 5285 } 5286 5287 /// ditto 5288 @property void icon (Image i) { 5289 version(X11) { 5290 if (!active) return; 5291 if (i !is img) { 5292 originalMemoryImage = null; 5293 img = i; 5294 redraw(); 5295 } 5296 } else version(Windows) { 5297 this.icon(i is null ? null : i.toTrueColorImage()); 5298 } else version(OSXCocoa) { 5299 throw new NotYetImplementedException(); 5300 } else static assert(0); 5301 } 5302 5303 /++ 5304 Shows a balloon notification. You can only show one balloon at a time, if you call 5305 it twice while one is already up, the first balloon will be replaced. 5306 5307 5308 The user is free to block notifications and they will automatically disappear after 5309 a timeout period. 5310 5311 Params: 5312 title = Title of the notification. Must be 40 chars or less or the OS may truncate it. 5313 message = The message to pop up. Must be 220 chars or less or the OS may truncate it. 5314 icon = the icon to display with the notification. If null, it uses your existing icon. 5315 onclick = delegate called if the user clicks the balloon. (not yet implemented) 5316 timeout = your suggested timeout period. The operating system is free to ignore your suggestion. 5317 +/ 5318 void showBalloon(string title, string message, MemoryImage icon = null, void delegate() onclick = null, int timeout = 2_500) { 5319 bool useCustom = true; 5320 version(libnotify) { 5321 if(onclick is null) // libnotify impl doesn't support callbacks yet because it doesn't do a dbus message loop 5322 try { 5323 if(!active) return; 5324 5325 if(libnotify is null) { 5326 libnotify = new C_DynamicLibrary("libnotify.so"); 5327 libnotify.call!("notify_init", int, const char*)()((ApplicationName ~ "\0").ptr); 5328 } 5329 5330 auto n = libnotify.call!("notify_notification_new", void*, const char*, const char*, const char*)()((title~"\0").ptr, (message~"\0").ptr, null /* icon */); 5331 5332 libnotify.call!("notify_notification_set_timeout", void, void*, int)()(n, timeout); 5333 5334 if(onclick) { 5335 libnotify_action_delegates[libnotify_action_delegates_count] = onclick; 5336 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); 5337 libnotify_action_delegates_count++; 5338 } 5339 5340 // FIXME icon 5341 5342 // set hint image-data 5343 // set default action for onclick 5344 5345 void* error; 5346 libnotify.call!("notify_notification_show", bool, void*, void**)()(n, &error); 5347 5348 useCustom = false; 5349 } catch(Exception e) { 5350 5351 } 5352 } 5353 5354 version(X11) { 5355 if(useCustom) { 5356 if(!active) return; 5357 if(balloon) { 5358 hideBalloon(); 5359 } 5360 // I know there are two specs for this, but one is never 5361 // implemented by any window manager I have ever seen, and 5362 // the other is a bloated mess and too complicated for simpledisplay... 5363 // so doing my own little window instead. 5364 balloon = new SimpleWindow(380, 120, null, OpenGlOptions.no, Resizability.fixedSize, WindowTypes.notification, WindowFlags.dontAutoShow/*, window*/); 5365 5366 int x, y, width, height; 5367 getWindowRect(x, y, width, height); 5368 5369 int bx = x - balloon.width; 5370 int by = y - balloon.height; 5371 if(bx < 0) 5372 bx = x + width + balloon.width; 5373 if(by < 0) 5374 by = y + height; 5375 5376 // just in case, make sure it is actually on scren 5377 if(bx < 0) 5378 bx = 0; 5379 if(by < 0) 5380 by = 0; 5381 5382 balloon.move(bx, by); 5383 auto painter = balloon.draw(); 5384 painter.fillColor = Color(220, 220, 220); 5385 painter.outlineColor = Color.black; 5386 painter.drawRectangle(Point(0, 0), balloon.width, balloon.height); 5387 auto iconWidth = icon is null ? 0 : icon.width; 5388 if(icon) 5389 painter.drawImage(Point(4, 4), Image.fromMemoryImage(icon)); 5390 iconWidth += 6; // margin around the icon 5391 5392 // draw a close button 5393 painter.outlineColor = Color(44, 44, 44); 5394 painter.fillColor = Color(255, 255, 255); 5395 painter.drawRectangle(Point(balloon.width - 15, 3), 13, 13); 5396 painter.pen = Pen(Color.black, 3); 5397 painter.drawLine(Point(balloon.width - 14, 4), Point(balloon.width - 4, 14)); 5398 painter.drawLine(Point(balloon.width - 4, 4), Point(balloon.width - 14, 13)); 5399 painter.pen = Pen(Color.black, 1); 5400 painter.fillColor = Color(220, 220, 220); 5401 5402 // Draw the title and message 5403 painter.drawText(Point(4 + iconWidth, 4), title); 5404 painter.drawLine( 5405 Point(4 + iconWidth, 4 + painter.fontHeight + 1), 5406 Point(balloon.width - 4, 4 + painter.fontHeight + 1), 5407 ); 5408 painter.drawText(Point(4 + iconWidth, 4 + painter.fontHeight + 4), message); 5409 5410 balloon.setEventHandlers( 5411 (MouseEvent ev) { 5412 if(ev.type == MouseEventType.buttonPressed) { 5413 if(ev.x > balloon.width - 16 && ev.y < 16) 5414 hideBalloon(); 5415 else if(onclick) 5416 onclick(); 5417 } 5418 } 5419 ); 5420 balloon.show(); 5421 5422 version(with_timer) 5423 timer = new Timer(timeout, &hideBalloon); 5424 else {} // FIXME 5425 } 5426 } else version(Windows) { 5427 enum NIF_INFO = 0x00000010; 5428 5429 data.uFlags = NIF_INFO; 5430 5431 // FIXME: go back to the last valid unicode code point 5432 if(title.length > 40) 5433 title = title[0 .. 40]; 5434 if(message.length > 220) 5435 message = message[0 .. 220]; 5436 5437 enum NIIF_RESPECT_QUIET_TIME = 0x00000080; 5438 enum NIIF_LARGE_ICON = 0x00000020; 5439 enum NIIF_NOSOUND = 0x00000010; 5440 enum NIIF_USER = 0x00000004; 5441 enum NIIF_ERROR = 0x00000003; 5442 enum NIIF_WARNING = 0x00000002; 5443 enum NIIF_INFO = 0x00000001; 5444 enum NIIF_NONE = 0; 5445 5446 WCharzBuffer t = WCharzBuffer(title); 5447 WCharzBuffer m = WCharzBuffer(message); 5448 5449 t.copyInto(data.szInfoTitle); 5450 m.copyInto(data.szInfo); 5451 data.dwInfoFlags = NIIF_RESPECT_QUIET_TIME; 5452 5453 if(icon !is null) { 5454 auto i = new WindowsIcon(icon); 5455 data.hBalloonIcon = i.hIcon; 5456 data.dwInfoFlags |= NIIF_USER; 5457 } 5458 5459 Shell_NotifyIcon(NIM_MODIFY, cast(NOTIFYICONDATA*) &data); 5460 } else version(OSXCocoa) { 5461 throw new NotYetImplementedException(); 5462 } else static assert(0); 5463 } 5464 5465 /// 5466 //version(Windows) 5467 void show() { 5468 version(X11) { 5469 if(!hidden) 5470 return; 5471 sendTrayMessage(SYSTEM_TRAY_REQUEST_DOCK, nativeHandle, 0, 0); 5472 hidden = false; 5473 } else version(Windows) { 5474 data.uFlags = NIF_STATE; 5475 data.dwState = 0; // NIS_HIDDEN; // windows vista 5476 data.dwStateMask = NIS_HIDDEN; // windows vista 5477 Shell_NotifyIcon(NIM_MODIFY, cast(NOTIFYICONDATA*) &data); 5478 } else version(OSXCocoa) { 5479 throw new NotYetImplementedException(); 5480 } else static assert(0); 5481 } 5482 5483 version(X11) 5484 bool hidden = false; 5485 5486 /// 5487 //version(Windows) 5488 void hide() { 5489 version(X11) { 5490 if(hidden) 5491 return; 5492 hidden = true; 5493 XUnmapWindow(XDisplayConnection.get, nativeHandle); 5494 } else version(Windows) { 5495 data.uFlags = NIF_STATE; 5496 data.dwState = NIS_HIDDEN; // windows vista 5497 data.dwStateMask = NIS_HIDDEN; // windows vista 5498 Shell_NotifyIcon(NIM_MODIFY, cast(NOTIFYICONDATA*) &data); 5499 } else version(OSXCocoa) { 5500 throw new NotYetImplementedException(); 5501 } else static assert(0); 5502 } 5503 5504 /// 5505 void close () { 5506 version(X11) { 5507 if (active) { 5508 active = false; // event handler will set this too, but meh 5509 XUnmapWindow(XDisplayConnection.get, nativeHandle); // 'cause why not; let's be polite 5510 XDestroyWindow(XDisplayConnection.get, nativeHandle); 5511 flushGui(); 5512 } 5513 } else version(Windows) { 5514 Shell_NotifyIcon(NIM_DELETE, cast(NOTIFYICONDATA*) &data); 5515 } else version(OSXCocoa) { 5516 throw new NotYetImplementedException(); 5517 } else static assert(0); 5518 } 5519 5520 ~this() { 5521 if(!thisIsGuiThread) return; // FIXME: leaks if multithreaded gc 5522 version(X11) 5523 if(clippixmap != None) 5524 XFreePixmap(XDisplayConnection.get, clippixmap); 5525 close(); 5526 } 5527 } 5528 5529 version(X11) 5530 /// Call `XFreePixmap` on the return value. 5531 Pixmap transparencyMaskFromMemoryImage(MemoryImage i, Window window) { 5532 char[] data = new char[](i.width * i.height / 8 + 2); 5533 data[] = 0; 5534 5535 int bitOffset = 0; 5536 foreach(c; i.getAsTrueColorImage().imageData.colors) { // FIXME inefficient unnecessary conversion in palette cases 5537 ubyte v = c.a > 128 ? 1 : 0; 5538 data[bitOffset / 8] |= v << (bitOffset%8); 5539 bitOffset++; 5540 } 5541 auto handle = XCreateBitmapFromData(XDisplayConnection.get, cast(Drawable) window, data.ptr, i.width, i.height); 5542 return handle; 5543 } 5544 5545 5546 // basic functions to make timers 5547 /** 5548 A timer that will trigger your function on a given interval. 5549 5550 5551 You create a timer with an interval and a callback. It will continue 5552 to fire on the interval until it is destroyed. 5553 5554 There are currently no one-off timers (instead, just create one and 5555 destroy it when it is triggered) nor are there pause/resume functions - 5556 the timer must again be destroyed and recreated if you want to pause it. 5557 5558 --- 5559 auto timer = new Timer(50, { it happened!; }); 5560 timer.destroy(); 5561 --- 5562 5563 Timers can only be expected to fire when the event loop is running and only 5564 once per iteration through the event loop. 5565 5566 History: 5567 Prior to December 9, 2020, a timer pulse set too high with a handler too 5568 slow could lock up the event loop. It now guarantees other things will 5569 get a chance to run between timer calls, even if that means not keeping up 5570 with the requested interval. 5571 */ 5572 version(with_timer) { 5573 class Timer { 5574 // FIXME: needs pause and unpause 5575 // FIXME: I might add overloads for ones that take a count of 5576 // how many elapsed since last time (on Windows, it will divide 5577 // the ticks thing given, on Linux it is just available) and 5578 // maybe one that takes an instance of the Timer itself too 5579 /// Create a timer with a callback when it triggers. 5580 this(int intervalInMilliseconds, void delegate() onPulse) { 5581 assert(onPulse !is null); 5582 5583 this.intervalInMilliseconds = intervalInMilliseconds; 5584 this.onPulse = onPulse; 5585 5586 version(Windows) { 5587 /* 5588 handle = SetTimer(null, handle, intervalInMilliseconds, &timerCallback); 5589 if(handle == 0) 5590 throw new WindowsApiException("SetTimer", GetLastError()); 5591 */ 5592 5593 // thanks to Archival 998 for the WaitableTimer blocks 5594 handle = CreateWaitableTimer(null, false, null); 5595 long initialTime = -intervalInMilliseconds; 5596 if(handle is null || !SetWaitableTimer(handle, cast(LARGE_INTEGER*)&initialTime, intervalInMilliseconds, &timerCallback, handle, false)) 5597 throw new WindowsApiException("SetWaitableTimer", GetLastError()); 5598 5599 mapping[handle] = this; 5600 5601 } else version(linux) { 5602 static import ep = core.sys.linux.epoll; 5603 5604 import core.sys.linux.timerfd; 5605 5606 fd = timerfd_create(CLOCK_MONOTONIC, 0); 5607 if(fd == -1) 5608 throw new Exception("timer create failed"); 5609 5610 mapping[fd] = this; 5611 5612 itimerspec value = makeItimerspec(intervalInMilliseconds); 5613 5614 if(timerfd_settime(fd, 0, &value, null) == -1) 5615 throw new Exception("couldn't make pulse timer"); 5616 5617 version(with_eventloop) { 5618 import arsd.eventloop; 5619 addFileEventListeners(fd, &trigger, null, null); 5620 } else { 5621 prepareEventLoop(); 5622 5623 ep.epoll_event ev = void; 5624 ev.events = ep.EPOLLIN; 5625 ev.data.fd = fd; 5626 ep.epoll_ctl(epollFd, ep.EPOLL_CTL_ADD, fd, &ev); 5627 } 5628 } else featureNotImplemented(); 5629 } 5630 5631 private int intervalInMilliseconds; 5632 5633 // just cuz I sometimes call it this. 5634 alias dispose = destroy; 5635 5636 /// Stop and destroy the timer object. 5637 void destroy() { 5638 version(Windows) { 5639 staticDestroy(handle); 5640 handle = null; 5641 } else version(linux) { 5642 staticDestroy(fd); 5643 fd = -1; 5644 } else featureNotImplemented(); 5645 } 5646 5647 version(Windows) 5648 static void staticDestroy(HANDLE handle) { 5649 if(handle) { 5650 // KillTimer(null, handle); 5651 CancelWaitableTimer(cast(void*)handle); 5652 mapping.remove(handle); 5653 CloseHandle(handle); 5654 } 5655 } 5656 else version(linux) 5657 static void staticDestroy(int fd) { 5658 if(fd != -1) { 5659 import unix = core.sys.posix.unistd; 5660 static import ep = core.sys.linux.epoll; 5661 5662 version(with_eventloop) { 5663 import arsd.eventloop; 5664 removeFileEventListeners(fd); 5665 } else { 5666 ep.epoll_event ev = void; 5667 ev.events = ep.EPOLLIN; 5668 ev.data.fd = fd; 5669 5670 ep.epoll_ctl(epollFd, ep.EPOLL_CTL_DEL, fd, &ev); 5671 } 5672 unix.close(fd); 5673 mapping.remove(fd); 5674 } 5675 } 5676 5677 ~this() { 5678 version(Windows) { if(handle) 5679 cleanupQueue.queue!staticDestroy(handle); 5680 } else version(linux) { if(fd != -1) 5681 cleanupQueue.queue!staticDestroy(fd); 5682 } 5683 } 5684 5685 void changeTime(int intervalInMilliseconds) 5686 { 5687 this.intervalInMilliseconds = intervalInMilliseconds; 5688 version(Windows) 5689 { 5690 if(handle) 5691 { 5692 //handle = SetTimer(null, handle, intervalInMilliseconds, &timerCallback); 5693 long initialTime = -intervalInMilliseconds; 5694 if(handle is null || !SetWaitableTimer(handle, cast(LARGE_INTEGER*)&initialTime, intervalInMilliseconds, &timerCallback, handle, false)) 5695 throw new WindowsApiException("couldn't change pulse timer", GetLastError()); 5696 } 5697 } else version(linux) { 5698 import core.sys.linux.timerfd; 5699 5700 itimerspec value = makeItimerspec(intervalInMilliseconds); 5701 if(timerfd_settime(fd, 0, &value, null) == -1) { 5702 throw new Exception("couldn't change pulse timer"); 5703 } 5704 } else { 5705 assert(false, "Timer.changeTime(int) is not implemented for this platform"); 5706 } 5707 } 5708 5709 5710 private: 5711 5712 void delegate() onPulse; 5713 5714 int lastEventLoopRoundTriggered; 5715 5716 version(linux) { 5717 static auto makeItimerspec(int intervalInMilliseconds) { 5718 import core.sys.linux.timerfd; 5719 5720 itimerspec value; 5721 value.it_value.tv_sec = cast(int) (intervalInMilliseconds / 1000); 5722 value.it_value.tv_nsec = (intervalInMilliseconds % 1000) * 1000_000; 5723 5724 value.it_interval.tv_sec = cast(int) (intervalInMilliseconds / 1000); 5725 value.it_interval.tv_nsec = (intervalInMilliseconds % 1000) * 1000_000; 5726 5727 return value; 5728 } 5729 } 5730 5731 void trigger() { 5732 version(linux) { 5733 import unix = core.sys.posix.unistd; 5734 long val; 5735 unix.read(fd, &val, val.sizeof); // gotta clear the pipe 5736 } else version(Windows) { 5737 if(this.lastEventLoopRoundTriggered == eventLoopRound) 5738 return; // never try to actually run faster than the event loop 5739 lastEventLoopRoundTriggered = eventLoopRound; 5740 } else featureNotImplemented(); 5741 5742 onPulse(); 5743 } 5744 5745 version(Windows) 5746 void rearm() { 5747 5748 } 5749 5750 version(Windows) 5751 extern(Windows) 5752 //static void timerCallback(HWND, UINT, UINT_PTR timer, DWORD dwTime) nothrow { 5753 static void timerCallback(HANDLE timer, DWORD lowTime, DWORD hiTime) nothrow { 5754 if(Timer* t = timer in mapping) { 5755 try 5756 (*t).trigger(); 5757 catch(Exception e) { sdpy_abort(e); assert(0); } 5758 } 5759 } 5760 5761 version(Windows) { 5762 //UINT_PTR handle; 5763 //static Timer[UINT_PTR] mapping; 5764 HANDLE handle; 5765 __gshared Timer[HANDLE] mapping; 5766 } else version(linux) { 5767 int fd = -1; 5768 __gshared Timer[int] mapping; 5769 } else version(OSXCocoa) { 5770 } else static assert(0, "timer not supported"); 5771 } 5772 } 5773 5774 version(Windows) 5775 private int eventLoopRound; 5776 5777 version(Windows) 5778 /// 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 5779 class WindowsHandleReader { 5780 /// 5781 this(void delegate() onReady, HANDLE handle) { 5782 this.onReady = onReady; 5783 this.handle = handle; 5784 5785 mapping[handle] = this; 5786 5787 enable(); 5788 } 5789 5790 /// 5791 void enable() { 5792 auto el = EventLoop.get().impl; 5793 el.handles ~= handle; 5794 } 5795 5796 /// 5797 void disable() { 5798 auto el = EventLoop.get().impl; 5799 for(int i = 0; i < el.handles.length; i++) { 5800 if(el.handles[i] is handle) { 5801 el.handles[i] = el.handles[$-1]; 5802 el.handles = el.handles[0 .. $-1]; 5803 return; 5804 } 5805 } 5806 } 5807 5808 void dispose() { 5809 disable(); 5810 if(handle) 5811 mapping.remove(handle); 5812 handle = null; 5813 } 5814 5815 void ready() { 5816 if(onReady) 5817 onReady(); 5818 } 5819 5820 HANDLE handle; 5821 void delegate() onReady; 5822 5823 __gshared WindowsHandleReader[HANDLE] mapping; 5824 } 5825 5826 version(Posix) 5827 /// Lets you add files to the event loop for reading. Use at your own risk. 5828 class PosixFdReader { 5829 /// 5830 this(void delegate() onReady, int fd, bool captureReads = true, bool captureWrites = false) { 5831 this((int, bool, bool) { onReady(); }, fd, captureReads, captureWrites); 5832 } 5833 5834 /// 5835 this(void delegate(int) onReady, int fd, bool captureReads = true, bool captureWrites = false) { 5836 this((int fd, bool, bool) { onReady(fd); }, fd, captureReads, captureWrites); 5837 } 5838 5839 /// 5840 this(void delegate(int fd, bool read, bool write) onReady, int fd, bool captureReads = true, bool captureWrites = false) { 5841 this.onReady = onReady; 5842 this.fd = fd; 5843 this.captureWrites = captureWrites; 5844 this.captureReads = captureReads; 5845 5846 mapping[fd] = this; 5847 5848 version(with_eventloop) { 5849 import arsd.eventloop; 5850 addFileEventListeners(fd, &readyel); 5851 } else { 5852 enable(); 5853 } 5854 } 5855 5856 bool captureReads; 5857 bool captureWrites; 5858 5859 version(with_eventloop) {} else 5860 /// 5861 void enable() { 5862 prepareEventLoop(); 5863 5864 enabled = true; 5865 5866 version(linux) { 5867 static import ep = core.sys.linux.epoll; 5868 ep.epoll_event ev = void; 5869 ev.events = (captureReads ? ep.EPOLLIN : 0) | (captureWrites ? ep.EPOLLOUT : 0); 5870 // writeln("enable ", fd, " ", captureReads, " ", captureWrites); 5871 ev.data.fd = fd; 5872 ep.epoll_ctl(epollFd, ep.EPOLL_CTL_ADD, fd, &ev); 5873 } else { 5874 5875 } 5876 } 5877 5878 version(with_eventloop) {} else 5879 /// 5880 void disable() { 5881 prepareEventLoop(); 5882 5883 enabled = false; 5884 5885 version(linux) { 5886 static import ep = core.sys.linux.epoll; 5887 ep.epoll_event ev = void; 5888 ev.events = (captureReads ? ep.EPOLLIN : 0) | (captureWrites ? ep.EPOLLOUT : 0); 5889 // writeln("disable ", fd, " ", captureReads, " ", captureWrites); 5890 ev.data.fd = fd; 5891 ep.epoll_ctl(epollFd, ep.EPOLL_CTL_DEL, fd, &ev); 5892 } 5893 } 5894 5895 version(with_eventloop) {} else 5896 /// 5897 void dispose() { 5898 if(enabled) 5899 disable(); 5900 if(fd != -1) 5901 mapping.remove(fd); 5902 fd = -1; 5903 } 5904 5905 void delegate(int, bool, bool) onReady; 5906 5907 version(with_eventloop) 5908 void readyel() { 5909 onReady(fd, true, true); 5910 } 5911 5912 void ready(uint flags) { 5913 version(linux) { 5914 static import ep = core.sys.linux.epoll; 5915 onReady(fd, (flags & ep.EPOLLIN) ? true : false, (flags & ep.EPOLLOUT) ? true : false); 5916 } else { 5917 import core.sys.posix.poll; 5918 onReady(fd, (flags & POLLIN) ? true : false, (flags & POLLOUT) ? true : false); 5919 } 5920 } 5921 5922 void hup(uint flags) { 5923 if(onHup) 5924 onHup(); 5925 } 5926 5927 void delegate() onHup; 5928 5929 int fd = -1; 5930 private bool enabled; 5931 __gshared PosixFdReader[int] mapping; 5932 } 5933 5934 // basic functions to access the clipboard 5935 /+ 5936 5937 5938 http://msdn.microsoft.com/en-us/library/windows/desktop/ff729168%28v=vs.85%29.aspx 5939 http://msdn.microsoft.com/en-us/library/windows/desktop/ms649039%28v=vs.85%29.aspx 5940 http://msdn.microsoft.com/en-us/library/windows/desktop/ms649035%28v=vs.85%29.aspx 5941 http://msdn.microsoft.com/en-us/library/windows/desktop/ms649051%28v=vs.85%29.aspx 5942 http://msdn.microsoft.com/en-us/library/windows/desktop/ms649037%28v=vs.85%29.aspx 5943 http://msdn.microsoft.com/en-us/library/windows/desktop/ms649035%28v=vs.85%29.aspx 5944 http://msdn.microsoft.com/en-us/library/windows/desktop/ms649016%28v=vs.85%29.aspx 5945 5946 +/ 5947 5948 /++ 5949 this does a delegate because it is actually an async call on X... 5950 the receiver may never be called if the clipboard is empty or unavailable 5951 gets plain text from the clipboard. 5952 +/ 5953 void getClipboardText(SimpleWindow clipboardOwner, void delegate(in char[]) receiver) { 5954 version(Windows) { 5955 HWND hwndOwner = clipboardOwner ? clipboardOwner.impl.hwnd : null; 5956 if(OpenClipboard(hwndOwner) == 0) 5957 throw new WindowsApiException("OpenClipboard", GetLastError()); 5958 scope(exit) 5959 CloseClipboard(); 5960 // see: https://docs.microsoft.com/en-us/windows/win32/api/winuser/nf-winuser-getpriorityclipboardformat 5961 if(auto dataHandle = GetClipboardData(CF_UNICODETEXT)) { 5962 5963 if(auto data = cast(wchar*) GlobalLock(dataHandle)) { 5964 scope(exit) 5965 GlobalUnlock(dataHandle); 5966 5967 // FIXME: CR/LF conversions 5968 // FIXME: I might not have to copy it now that the receiver is in char[] instead of string 5969 int len = 0; 5970 auto d = data; 5971 while(*d) { 5972 d++; 5973 len++; 5974 } 5975 string s; 5976 s.reserve(len); 5977 foreach(dchar ch; data[0 .. len]) { 5978 s ~= ch; 5979 } 5980 receiver(s); 5981 } 5982 } 5983 } else version(X11) { 5984 getX11Selection!"CLIPBOARD"(clipboardOwner, receiver); 5985 } else version(OSXCocoa) { 5986 throw new NotYetImplementedException(); 5987 } else static assert(0); 5988 } 5989 5990 // FIXME: a clipboard listener might be cool btw 5991 5992 /++ 5993 this does a delegate because it is actually an async call on X... 5994 the receiver may never be called if the clipboard is empty or unavailable 5995 gets image from the clipboard. 5996 5997 templated because it introduces an optional dependency on arsd.bmp 5998 +/ 5999 void getClipboardImage()(SimpleWindow clipboardOwner, void delegate(MemoryImage) receiver) { 6000 version(Windows) { 6001 HWND hwndOwner = clipboardOwner ? clipboardOwner.impl.hwnd : null; 6002 if(OpenClipboard(hwndOwner) == 0) 6003 throw new WindowsApiException("OpenClipboard", GetLastError()); 6004 scope(exit) 6005 CloseClipboard(); 6006 if(auto dataHandle = GetClipboardData(CF_DIBV5)) { 6007 if(auto data = cast(ubyte*) GlobalLock(dataHandle)) { 6008 scope(exit) 6009 GlobalUnlock(dataHandle); 6010 6011 auto len = GlobalSize(dataHandle); 6012 6013 import arsd.bmp; 6014 auto img = readBmp(data[0 .. len], false); 6015 receiver(img); 6016 } 6017 } 6018 } else version(X11) { 6019 getX11Selection!"CLIPBOARD"(clipboardOwner, receiver); 6020 } else version(OSXCocoa) { 6021 throw new NotYetImplementedException(); 6022 } else static assert(0); 6023 } 6024 6025 /// Copies some text to the clipboard. 6026 void setClipboardText(SimpleWindow clipboardOwner, string text) { 6027 assert(clipboardOwner !is null); 6028 version(Windows) { 6029 if(OpenClipboard(clipboardOwner.impl.hwnd) == 0) 6030 throw new WindowsApiException("OpenClipboard", GetLastError()); 6031 scope(exit) 6032 CloseClipboard(); 6033 EmptyClipboard(); 6034 auto sz = sizeOfConvertedWstring(text, WindowsStringConversionFlags.convertNewLines | WindowsStringConversionFlags.zeroTerminate); 6035 auto handle = GlobalAlloc(GMEM_MOVEABLE, sz * 2); // zero terminated wchars 6036 if(handle is null) throw new WindowsApiException("GlobalAlloc", GetLastError()); 6037 if(auto data = cast(wchar*) GlobalLock(handle)) { 6038 auto slice = data[0 .. sz]; 6039 scope(failure) 6040 GlobalUnlock(handle); 6041 6042 auto str = makeWindowsString(text, slice, WindowsStringConversionFlags.convertNewLines | WindowsStringConversionFlags.zeroTerminate); 6043 6044 GlobalUnlock(handle); 6045 SetClipboardData(CF_UNICODETEXT, handle); 6046 } 6047 } else version(X11) { 6048 setX11Selection!"CLIPBOARD"(clipboardOwner, text); 6049 } else version(OSXCocoa) { 6050 throw new NotYetImplementedException(); 6051 } else static assert(0); 6052 } 6053 6054 void setClipboardImage()(SimpleWindow clipboardOwner, MemoryImage img) { 6055 assert(clipboardOwner !is null); 6056 version(Windows) { 6057 if(OpenClipboard(clipboardOwner.impl.hwnd) == 0) 6058 throw new WindowsApiException("OpenClipboard", GetLastError()); 6059 scope(exit) 6060 CloseClipboard(); 6061 EmptyClipboard(); 6062 6063 6064 import arsd.bmp; 6065 ubyte[] mdata; 6066 mdata.reserve(img.width * img.height); 6067 void sink(ubyte b) { 6068 mdata ~= b; 6069 } 6070 writeBmpIndirect(img, &sink, false); 6071 6072 auto handle = GlobalAlloc(GMEM_MOVEABLE, mdata.length); 6073 if(handle is null) throw new WindowsApiException("GlobalAlloc", GetLastError()); 6074 if(auto data = cast(ubyte*) GlobalLock(handle)) { 6075 auto slice = data[0 .. mdata.length]; 6076 scope(failure) 6077 GlobalUnlock(handle); 6078 6079 slice[] = mdata[]; 6080 6081 GlobalUnlock(handle); 6082 SetClipboardData(CF_DIB, handle); 6083 } 6084 } else version(X11) { 6085 static class X11SetSelectionHandler_Image : X11SetSelectionHandler { 6086 mixin X11SetSelectionHandler_Basics; 6087 private const(ubyte)[] mdata; 6088 private const(ubyte)[] mdata_original; 6089 this(MemoryImage img) { 6090 import arsd.bmp; 6091 6092 mdata.reserve(img.width * img.height); 6093 void sink(ubyte b) { 6094 mdata ~= b; 6095 } 6096 writeBmpIndirect(img, &sink, true); 6097 6098 mdata_original = mdata; 6099 } 6100 6101 Atom[] availableFormats() { 6102 auto display = XDisplayConnection.get; 6103 return [ 6104 GetAtom!"image/bmp"(display), 6105 GetAtom!"TARGETS"(display) 6106 ]; 6107 } 6108 6109 ubyte[] getData(Atom format, return scope ubyte[] data) { 6110 if(mdata.length < data.length) { 6111 data[0 .. mdata.length] = mdata[]; 6112 auto ret = data[0 .. mdata.length]; 6113 mdata = mdata[$..$]; 6114 return ret; 6115 } else { 6116 data[] = mdata[0 .. data.length]; 6117 mdata = mdata[data.length .. $]; 6118 return data[]; 6119 } 6120 } 6121 6122 void done() { 6123 mdata = mdata_original; 6124 } 6125 } 6126 6127 setX11Selection!"CLIPBOARD"(clipboardOwner, new X11SetSelectionHandler_Image(img)); 6128 } else version(OSXCocoa) { 6129 throw new NotYetImplementedException(); 6130 } else static assert(0); 6131 } 6132 6133 6134 version(X11) { 6135 // and the PRIMARY on X, be sure to put these in static if(UsingSimpledisplayX11) 6136 6137 private __gshared Atom*[] interredAtoms; // for discardAndRecreate 6138 6139 // FIXME: do a GetAtomUpfront too that just queues all at CT and combines it all. 6140 /// Platform-specific for X11. 6141 /// History: On February 21, 2021, I changed the default value of `create` to be true. 6142 @property Atom GetAtom(string name, bool create = true)(Display* display) { 6143 __gshared static Atom a; 6144 if(!a) { 6145 a = XInternAtom(display, name, !create); 6146 // FIXME: might need to synchronize this and attach it to the actual object 6147 interredAtoms ~= &a; 6148 } 6149 if(a == None) 6150 throw new Exception("XInternAtom " ~ name ~ " " ~ (create ? "true":"false")); 6151 return a; 6152 } 6153 6154 /// Platform-specific for X11 - gets atom names as a string. 6155 string getAtomName(Atom atom, Display* display) { 6156 auto got = XGetAtomName(display, atom); 6157 scope(exit) XFree(got); 6158 import core.stdc.string; 6159 string s = got[0 .. strlen(got)].idup; 6160 return s; 6161 } 6162 6163 /// Asserts ownership of PRIMARY and copies the text into a buffer that clients can request later. 6164 void setPrimarySelection(SimpleWindow window, string text) { 6165 setX11Selection!"PRIMARY"(window, text); 6166 } 6167 6168 /// Asserts ownership of SECONDARY and copies the text into a buffer that clients can request later. 6169 void setSecondarySelection(SimpleWindow window, string text) { 6170 setX11Selection!"SECONDARY"(window, text); 6171 } 6172 6173 interface X11SetSelectionHandler { 6174 // should include TARGETS right now 6175 Atom[] availableFormats(); 6176 // Return the slice of data you filled, empty slice if done. 6177 // this is to support the incremental thing 6178 ubyte[] getData(Atom format, return scope ubyte[] data); 6179 6180 void done(); 6181 6182 void handleRequest(XEvent); 6183 6184 bool matchesIncr(Window, Atom); 6185 void sendMoreIncr(XPropertyEvent*); 6186 } 6187 6188 mixin template X11SetSelectionHandler_Basics() { 6189 Window incrWindow; 6190 Atom incrAtom; 6191 Atom selectionAtom; 6192 Atom formatAtom; 6193 ubyte[] toSend; 6194 bool matchesIncr(Window w, Atom a) { 6195 return incrAtom && incrAtom == a && w == incrWindow; 6196 } 6197 void sendMoreIncr(XPropertyEvent* event) { 6198 auto display = XDisplayConnection.get; 6199 6200 XChangeProperty (display, 6201 incrWindow, 6202 incrAtom, 6203 formatAtom, 6204 8 /* bits */, PropModeReplace, 6205 toSend.ptr, cast(int) toSend.length); 6206 6207 if(toSend.length != 0) { 6208 toSend = this.getData(formatAtom, toSend[]); 6209 } else { 6210 this.done(); 6211 incrWindow = None; 6212 incrAtom = None; 6213 selectionAtom = None; 6214 formatAtom = None; 6215 toSend = null; 6216 } 6217 } 6218 void handleRequest(XEvent ev) { 6219 6220 auto display = XDisplayConnection.get; 6221 6222 XSelectionRequestEvent* event = &ev.xselectionrequest; 6223 XSelectionEvent selectionEvent; 6224 selectionEvent.type = EventType.SelectionNotify; 6225 selectionEvent.display = event.display; 6226 selectionEvent.requestor = event.requestor; 6227 selectionEvent.selection = event.selection; 6228 selectionEvent.time = event.time; 6229 selectionEvent.target = event.target; 6230 6231 bool supportedType() { 6232 foreach(t; this.availableFormats()) 6233 if(t == event.target) 6234 return true; 6235 return false; 6236 } 6237 6238 if(event.property == None) { 6239 selectionEvent.property = event.target; 6240 6241 XSendEvent(display, selectionEvent.requestor, false, 0, cast(XEvent*) &selectionEvent); 6242 XFlush(display); 6243 } if(event.target == GetAtom!"TARGETS"(display)) { 6244 /* respond with the supported types */ 6245 auto tlist = this.availableFormats(); 6246 XChangeProperty(display, event.requestor, event.property, XA_ATOM, 32, PropModeReplace, cast(void*)tlist.ptr, cast(int) tlist.length); 6247 selectionEvent.property = event.property; 6248 6249 XSendEvent(display, selectionEvent.requestor, false, 0, cast(XEvent*) &selectionEvent); 6250 XFlush(display); 6251 } else if(supportedType()) { 6252 auto buffer = new ubyte[](1024 * 64); 6253 auto toSend = this.getData(event.target, buffer[]); 6254 6255 if(toSend.length < 32 * 1024) { 6256 // small enough to send directly... 6257 selectionEvent.property = event.property; 6258 XChangeProperty (display, 6259 selectionEvent.requestor, 6260 selectionEvent.property, 6261 event.target, 6262 8 /* bits */, 0 /* PropModeReplace */, 6263 toSend.ptr, cast(int) toSend.length); 6264 6265 XSendEvent(display, selectionEvent.requestor, false, 0, cast(XEvent*) &selectionEvent); 6266 XFlush(display); 6267 } else { 6268 // large, let's send incrementally 6269 arch_ulong l = toSend.length; 6270 6271 // if I wanted other events from this window don't want to clear that out.... 6272 XWindowAttributes xwa; 6273 XGetWindowAttributes(display, selectionEvent.requestor, &xwa); 6274 6275 XSelectInput(display, selectionEvent.requestor, cast(EventMask) (xwa.your_event_mask | EventMask.PropertyChangeMask)); 6276 6277 incrWindow = event.requestor; 6278 incrAtom = event.property; 6279 formatAtom = event.target; 6280 selectionAtom = event.selection; 6281 this.toSend = toSend; 6282 6283 selectionEvent.property = event.property; 6284 XChangeProperty (display, 6285 selectionEvent.requestor, 6286 selectionEvent.property, 6287 GetAtom!"INCR"(display), 6288 32 /* bits */, PropModeReplace, 6289 &l, 1); 6290 6291 XSendEvent(display, selectionEvent.requestor, false, 0, cast(XEvent*) &selectionEvent); 6292 XFlush(display); 6293 } 6294 //if(after) 6295 //after(); 6296 } else { 6297 debug(sdpy_clip) { 6298 writeln("Unsupported data ", getAtomName(event.target, display)); 6299 } 6300 selectionEvent.property = None; // I don't know how to handle this type... 6301 XSendEvent(display, selectionEvent.requestor, false, EventMask.NoEventMask, cast(XEvent*) &selectionEvent); 6302 XFlush(display); 6303 } 6304 } 6305 } 6306 6307 class X11SetSelectionHandler_Text : X11SetSelectionHandler { 6308 mixin X11SetSelectionHandler_Basics; 6309 private const(ubyte)[] text; 6310 private const(ubyte)[] text_original; 6311 this(string text) { 6312 this.text = cast(const ubyte[]) text; 6313 this.text_original = this.text; 6314 } 6315 Atom[] availableFormats() { 6316 auto display = XDisplayConnection.get; 6317 return [ 6318 GetAtom!"UTF8_STRING"(display), 6319 GetAtom!"text/plain"(display), 6320 XA_STRING, 6321 GetAtom!"TARGETS"(display) 6322 ]; 6323 } 6324 6325 ubyte[] getData(Atom format, return scope ubyte[] data) { 6326 if(text.length < data.length) { 6327 data[0 .. text.length] = text[]; 6328 return data[0 .. text.length]; 6329 } else { 6330 data[] = text[0 .. data.length]; 6331 text = text[data.length .. $]; 6332 return data[]; 6333 } 6334 } 6335 6336 void done() { 6337 text = text_original; 6338 } 6339 } 6340 6341 /// 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?!) 6342 void setX11Selection(string atomName)(SimpleWindow window, string text, void delegate() after = null) { 6343 setX11Selection!atomName(window, new X11SetSelectionHandler_Text(text), after); 6344 } 6345 6346 void setX11Selection(string atomName)(SimpleWindow window, X11SetSelectionHandler data, void delegate() after = null) { 6347 assert(window !is null); 6348 6349 auto display = XDisplayConnection.get(); 6350 static if (atomName == "PRIMARY") Atom a = XA_PRIMARY; 6351 else static if (atomName == "SECONDARY") Atom a = XA_SECONDARY; 6352 else Atom a = GetAtom!atomName(display); 6353 6354 XSetSelectionOwner(display, a, window.impl.window, 0 /* CurrentTime */); 6355 6356 window.impl.setSelectionHandlers[a] = data; 6357 } 6358 6359 /// 6360 void getPrimarySelection(SimpleWindow window, void delegate(in char[]) handler) { 6361 getX11Selection!"PRIMARY"(window, handler); 6362 } 6363 6364 // added July 28, 2020 6365 // undocumented as experimental tho 6366 interface X11GetSelectionHandler { 6367 void handleData(Atom target, in ubyte[] data); 6368 Atom findBestFormat(Atom[] answer); 6369 6370 void prepareIncremental(Window, Atom); 6371 bool matchesIncr(Window, Atom); 6372 void handleIncrData(Atom, in ubyte[] data); 6373 } 6374 6375 mixin template X11GetSelectionHandler_Basics() { 6376 Window incrWindow; 6377 Atom incrAtom; 6378 6379 void prepareIncremental(Window w, Atom a) { 6380 incrWindow = w; 6381 incrAtom = a; 6382 } 6383 bool matchesIncr(Window w, Atom a) { 6384 return incrWindow == w && incrAtom == a; 6385 } 6386 6387 Atom incrFormatAtom; 6388 ubyte[] incrData; 6389 void handleIncrData(Atom format, in ubyte[] data) { 6390 incrFormatAtom = format; 6391 6392 if(data.length) 6393 incrData ~= data; 6394 else 6395 handleData(incrFormatAtom, incrData); 6396 6397 } 6398 } 6399 6400 /// 6401 void getX11Selection(string atomName)(SimpleWindow window, void delegate(in char[]) handler, Time timestamp = 0 /* CurrentTime */) { 6402 assert(window !is null); 6403 6404 auto display = XDisplayConnection.get(); 6405 auto atom = GetAtom!atomName(display); 6406 6407 static class X11GetSelectionHandler_Text : X11GetSelectionHandler { 6408 this(void delegate(in char[]) handler) { 6409 this.handler = handler; 6410 } 6411 6412 mixin X11GetSelectionHandler_Basics; 6413 6414 void delegate(in char[]) handler; 6415 6416 void handleData(Atom target, in ubyte[] data) { 6417 if(target == GetAtom!"UTF8_STRING"(XDisplayConnection.get) || target == XA_STRING || target == GetAtom!"text/plain"(XDisplayConnection.get)) 6418 handler(cast(const char[]) data); 6419 } 6420 6421 Atom findBestFormat(Atom[] answer) { 6422 Atom best = None; 6423 foreach(option; answer) { 6424 if(option == GetAtom!"UTF8_STRING"(XDisplayConnection.get)) { 6425 best = option; 6426 break; 6427 } else if(option == XA_STRING) { 6428 best = option; 6429 } else if(option == GetAtom!"text/plain"(XDisplayConnection.get)) { 6430 best = option; 6431 } 6432 } 6433 return best; 6434 } 6435 } 6436 6437 window.impl.getSelectionHandlers[atom] = new X11GetSelectionHandler_Text(handler); 6438 6439 auto target = GetAtom!"TARGETS"(display); 6440 6441 // SDD_DATA is "simpledisplay.d data" 6442 XConvertSelection(display, atom, target, GetAtom!("SDD_DATA", true)(display), window.impl.window, timestamp); 6443 } 6444 6445 /// Gets the image on the clipboard, if there is one. Added July 2020. 6446 void getX11Selection(string atomName)(SimpleWindow window, void delegate(MemoryImage) handler) { 6447 assert(window !is null); 6448 6449 auto display = XDisplayConnection.get(); 6450 auto atom = GetAtom!atomName(display); 6451 6452 static class X11GetSelectionHandler_Image : X11GetSelectionHandler { 6453 this(void delegate(MemoryImage) handler) { 6454 this.handler = handler; 6455 } 6456 6457 mixin X11GetSelectionHandler_Basics; 6458 6459 void delegate(MemoryImage) handler; 6460 6461 void handleData(Atom target, in ubyte[] data) { 6462 if(target == GetAtom!"image/bmp"(XDisplayConnection.get)) { 6463 import arsd.bmp; 6464 handler(readBmp(data)); 6465 } 6466 } 6467 6468 Atom findBestFormat(Atom[] answer) { 6469 Atom best = None; 6470 foreach(option; answer) { 6471 if(option == GetAtom!"image/bmp"(XDisplayConnection.get)) { 6472 best = option; 6473 } 6474 } 6475 return best; 6476 } 6477 6478 } 6479 6480 6481 window.impl.getSelectionHandlers[atom] = new X11GetSelectionHandler_Image(handler); 6482 6483 auto target = GetAtom!"TARGETS"(display); 6484 6485 // SDD_DATA is "simpledisplay.d data" 6486 XConvertSelection(display, atom, target, GetAtom!("SDD_DATA", true)(display), window.impl.window, 0 /*CurrentTime*/); 6487 } 6488 6489 6490 /// 6491 void[] getX11PropertyData(Window window, Atom property, Atom type = AnyPropertyType) { 6492 Atom actualType; 6493 int actualFormat; 6494 arch_ulong actualItems; 6495 arch_ulong bytesRemaining; 6496 void* data; 6497 6498 auto display = XDisplayConnection.get(); 6499 if(XGetWindowProperty(display, window, property, 0, 0x7fffffff, false, type, &actualType, &actualFormat, &actualItems, &bytesRemaining, &data) == Success) { 6500 if(actualFormat == 0) 6501 return null; 6502 else { 6503 int byteLength; 6504 if(actualFormat == 32) { 6505 // 32 means it is a C long... which is variable length 6506 actualFormat = cast(int) arch_long.sizeof * 8; 6507 } 6508 6509 // then it is just a bit count 6510 byteLength = cast(int) (actualItems * actualFormat / 8); 6511 6512 auto d = new ubyte[](byteLength); 6513 d[] = cast(ubyte[]) data[0 .. byteLength]; 6514 XFree(data); 6515 return d; 6516 } 6517 } 6518 return null; 6519 } 6520 6521 /* defined in the systray spec */ 6522 enum SYSTEM_TRAY_REQUEST_DOCK = 0; 6523 enum SYSTEM_TRAY_BEGIN_MESSAGE = 1; 6524 enum SYSTEM_TRAY_CANCEL_MESSAGE = 2; 6525 6526 6527 /** Global hotkey handler. Simpledisplay will usually create one for you, but if you want to use subclassing 6528 * instead of delegates, you can subclass this, and override `doHandle()` method. */ 6529 public class GlobalHotkey { 6530 KeyEvent key; 6531 void delegate () handler; 6532 6533 void doHandle () { if (handler !is null) handler(); } /// this will be called by hotkey manager 6534 6535 /// Create from initialzed KeyEvent object 6536 this (KeyEvent akey, void delegate () ahandler=null) { 6537 if (akey.key == 0 || !GlobalHotkeyManager.isGoodModifierMask(akey.modifierState)) throw new Exception("invalid global hotkey"); 6538 key = akey; 6539 handler = ahandler; 6540 } 6541 6542 /// Create from emacs-like key name ("C-M-Y", etc.) 6543 this (const(char)[] akey, void delegate () ahandler=null) { 6544 key = KeyEvent.parse(akey); 6545 if (key.key == 0 || !GlobalHotkeyManager.isGoodModifierMask(key.modifierState)) throw new Exception("invalid global hotkey"); 6546 handler = ahandler; 6547 } 6548 6549 } 6550 6551 private extern(C) int XGrabErrorHandler (Display* dpy, XErrorEvent* evt) nothrow @nogc { 6552 //conwriteln("failed to grab key"); 6553 GlobalHotkeyManager.ghfailed = true; 6554 return 0; 6555 } 6556 6557 private extern(C) int XShmErrorHandler (Display* dpy, XErrorEvent* evt) nothrow @nogc { 6558 Image.impl.xshmfailed = true; 6559 return 0; 6560 } 6561 6562 private __gshared int errorHappened; 6563 private extern(C) int adrlogger (Display* dpy, XErrorEvent* evt) nothrow @nogc { 6564 import core.stdc.stdio; 6565 char[265] buffer; 6566 XGetErrorText(dpy, evt.error_code, buffer.ptr, cast(int) buffer.length); 6567 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); 6568 errorHappened = true; 6569 return 0; 6570 } 6571 6572 /++ 6573 Global hotkey manager. It contains static methods to manage global hotkeys. 6574 6575 --- 6576 try { 6577 GlobalHotkeyManager.register("M-H-A", delegate () { hideShowWindows(); }); 6578 } catch (Exception e) { 6579 conwriteln("ERROR registering hotkey!"); 6580 } 6581 EventLoop.get.run(); 6582 --- 6583 6584 The key strings are based on Emacs. In practical terms, 6585 `M` means `alt` and `H` means the Windows logo key. `C` 6586 is `ctrl`. 6587 6588 $(WARNING 6589 This is X-specific right now. If you are on 6590 Windows, try [registerHotKey] instead. 6591 6592 We will probably merge these into a single 6593 interface later. 6594 ) 6595 +/ 6596 public class GlobalHotkeyManager : CapableOfHandlingNativeEvent { 6597 version(X11) { 6598 void recreateAfterDisconnect() { 6599 throw new Exception("NOT IMPLEMENTED"); 6600 } 6601 void discardConnectionState() { 6602 throw new Exception("NOT IMPLEMENTED"); 6603 } 6604 } 6605 6606 private static immutable uint[8] masklist = [ 0, 6607 KeyOrButtonMask.LockMask, 6608 KeyOrButtonMask.Mod2Mask, 6609 KeyOrButtonMask.Mod3Mask, 6610 KeyOrButtonMask.LockMask|KeyOrButtonMask.Mod2Mask, 6611 KeyOrButtonMask.LockMask|KeyOrButtonMask.Mod3Mask, 6612 KeyOrButtonMask.LockMask|KeyOrButtonMask.Mod2Mask|KeyOrButtonMask.Mod3Mask, 6613 KeyOrButtonMask.Mod2Mask|KeyOrButtonMask.Mod3Mask, 6614 ]; 6615 private __gshared GlobalHotkeyManager ghmanager; 6616 private __gshared bool ghfailed = false; 6617 6618 private static bool isGoodModifierMask (uint modmask) pure nothrow @safe @nogc { 6619 if (modmask == 0) return false; 6620 if (modmask&(KeyOrButtonMask.LockMask|KeyOrButtonMask.Mod2Mask|KeyOrButtonMask.Mod3Mask)) return false; 6621 if (modmask&~(KeyOrButtonMask.Mod5Mask-1)) return false; 6622 return true; 6623 } 6624 6625 private static uint cleanupModifiers (uint modmask) pure nothrow @safe @nogc { 6626 modmask &= ~(KeyOrButtonMask.LockMask|KeyOrButtonMask.Mod2Mask|KeyOrButtonMask.Mod3Mask); // remove caps, num, scroll 6627 modmask &= (KeyOrButtonMask.Mod5Mask-1); // and other modifiers 6628 return modmask; 6629 } 6630 6631 private static uint keyEvent2KeyCode() (scope auto ref const KeyEvent ke) { 6632 uint keycode = cast(uint)ke.key; 6633 auto dpy = XDisplayConnection.get; 6634 return XKeysymToKeycode(dpy, keycode); 6635 } 6636 6637 private static ulong keyCode2Hash() (uint keycode, uint modstate) pure nothrow @safe @nogc { return ((cast(ulong)modstate)<<32)|keycode; } 6638 6639 private __gshared GlobalHotkey[ulong] globalHotkeyList; 6640 6641 NativeEventHandler getNativeEventHandler () { 6642 return delegate int (XEvent e) { 6643 if (e.type != EventType.KeyPress) return 1; 6644 auto kev = cast(const(XKeyEvent)*)&e; 6645 auto hash = keyCode2Hash(e.xkey.keycode, cleanupModifiers(e.xkey.state)); 6646 if (auto ghkp = hash in globalHotkeyList) { 6647 try { 6648 ghkp.doHandle(); 6649 } catch (Exception e) { 6650 import core.stdc.stdio : stderr, fprintf; 6651 stderr.fprintf("HOTKEY HANDLER EXCEPTION: %.*s", cast(uint)e.msg.length, e.msg.ptr); 6652 } 6653 } 6654 return 1; 6655 }; 6656 } 6657 6658 private this () { 6659 auto dpy = XDisplayConnection.get; 6660 auto root = RootWindow(dpy, DefaultScreen(dpy)); 6661 CapableOfHandlingNativeEvent.nativeHandleMapping[root] = this; 6662 XDisplayConnection.addRootInput(EventMask.KeyPressMask); 6663 } 6664 6665 /// Register new global hotkey with initialized `GlobalHotkey` object. 6666 /// This function will throw if it failed to register hotkey (i.e. hotkey is invalid or already taken). 6667 static void register (GlobalHotkey gh) { 6668 if (gh is null) return; 6669 if (gh.key.key == 0 || !isGoodModifierMask(gh.key.modifierState)) throw new Exception("invalid global hotkey"); 6670 6671 auto dpy = XDisplayConnection.get; 6672 immutable keycode = keyEvent2KeyCode(gh.key); 6673 6674 auto hash = keyCode2Hash(keycode, gh.key.modifierState); 6675 if (hash in globalHotkeyList) throw new Exception("duplicate global hotkey"); 6676 if (ghmanager is null) ghmanager = new GlobalHotkeyManager(); 6677 XSync(dpy, 0/*False*/); 6678 6679 Window root = RootWindow(dpy, DefaultScreen(dpy)); 6680 XErrorHandler savedErrorHandler = XSetErrorHandler(&XGrabErrorHandler); 6681 ghfailed = false; 6682 foreach (immutable uint ormask; masklist[]) { 6683 XGrabKey(dpy, keycode, gh.key.modifierState|ormask, /*grab_window*/root, /*owner_events*/0/*False*/, GrabMode.GrabModeAsync, GrabMode.GrabModeAsync); 6684 } 6685 XSync(dpy, 0/*False*/); 6686 XSetErrorHandler(savedErrorHandler); 6687 6688 if (ghfailed) { 6689 savedErrorHandler = XSetErrorHandler(&XGrabErrorHandler); 6690 foreach (immutable uint ormask; masklist[]) XUngrabKey(dpy, keycode, gh.key.modifierState|ormask, /*grab_window*/root); 6691 XSync(dpy, 0/*False*/); 6692 XSetErrorHandler(savedErrorHandler); 6693 throw new Exception("cannot register global hotkey"); 6694 } 6695 6696 globalHotkeyList[hash] = gh; 6697 } 6698 6699 /// Ditto 6700 static void register (const(char)[] akey, void delegate () ahandler) { 6701 register(new GlobalHotkey(akey, ahandler)); 6702 } 6703 6704 private static void removeByHash (ulong hash) { 6705 if (auto ghp = hash in globalHotkeyList) { 6706 auto dpy = XDisplayConnection.get; 6707 immutable keycode = keyEvent2KeyCode(ghp.key); 6708 Window root = RootWindow(dpy, DefaultScreen(dpy)); 6709 XSync(dpy, 0/*False*/); 6710 XErrorHandler savedErrorHandler = XSetErrorHandler(&XGrabErrorHandler); 6711 foreach (immutable uint ormask; masklist[]) XUngrabKey(dpy, keycode, ghp.key.modifierState|ormask, /*grab_window*/root); 6712 XSync(dpy, 0/*False*/); 6713 XSetErrorHandler(savedErrorHandler); 6714 globalHotkeyList.remove(hash); 6715 } 6716 } 6717 6718 /// Register new global hotkey with previously used `GlobalHotkey` object. 6719 /// It is safe to unregister unknown or invalid hotkey. 6720 static void unregister (GlobalHotkey gh) { 6721 //TODO: add second AA for faster search? prolly doesn't worth it. 6722 if (gh is null) return; 6723 foreach (const ref kv; globalHotkeyList.byKeyValue) { 6724 if (kv.value is gh) { 6725 removeByHash(kv.key); 6726 return; 6727 } 6728 } 6729 } 6730 6731 /// Ditto. 6732 static void unregister (const(char)[] key) { 6733 auto kev = KeyEvent.parse(key); 6734 immutable keycode = keyEvent2KeyCode(kev); 6735 removeByHash(keyCode2Hash(keycode, kev.modifierState)); 6736 } 6737 } 6738 } 6739 6740 version(Windows) { 6741 /++ 6742 See [SyntheticInput.sendSyntheticInput] instead for cross-platform applications. 6743 6744 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). 6745 +/ 6746 void sendSyntheticInput(wstring s) { 6747 INPUT[] inputs; 6748 inputs.reserve(s.length * 2); 6749 6750 foreach(wchar c; s) { 6751 INPUT input; 6752 input.type = INPUT_KEYBOARD; 6753 input.ki.wScan = c; 6754 input.ki.dwFlags = KEYEVENTF_UNICODE; 6755 inputs ~= input; 6756 6757 input.ki.dwFlags |= KEYEVENTF_KEYUP; 6758 inputs ~= input; 6759 } 6760 6761 if(SendInput(cast(int) inputs.length, inputs.ptr, INPUT.sizeof) != inputs.length) { 6762 throw new WindowsApiException("SendInput", GetLastError()); 6763 } 6764 6765 } 6766 6767 6768 // global hotkey helper function 6769 6770 /// Platform-specific for Windows. Registers a global hotkey. Returns a registration ID. See [GlobalHotkeyManager] for Linux. Maybe some day I will merge these. 6771 int registerHotKey(SimpleWindow window, UINT modifiers, UINT vk, void delegate() handler) { 6772 __gshared int hotkeyId = 0; 6773 int id = ++hotkeyId; 6774 if(!RegisterHotKey(window.impl.hwnd, id, modifiers, vk)) 6775 throw new Exception("RegisterHotKey"); 6776 6777 __gshared void delegate()[WPARAM][HWND] handlers; 6778 6779 handlers[window.impl.hwnd][id] = handler; 6780 6781 int delegate(HWND, UINT, WPARAM, LPARAM, out int) oldHandler; 6782 6783 auto nativeEventHandler = delegate int(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam, out int mustReturn) { 6784 switch(msg) { 6785 // http://msdn.microsoft.com/en-us/library/windows/desktop/ms646279%28v=vs.85%29.aspx 6786 case WM_HOTKEY: 6787 if(auto list = hwnd in handlers) { 6788 if(auto h = wParam in *list) { 6789 (*h)(); 6790 return 0; 6791 } 6792 } 6793 goto default; 6794 default: 6795 } 6796 if(oldHandler) 6797 return oldHandler(hwnd, msg, wParam, lParam, mustReturn); 6798 return 1; // pass it on 6799 }; 6800 6801 if(window.handleNativeEvent.funcptr !is nativeEventHandler.funcptr) { 6802 oldHandler = window.handleNativeEvent; 6803 window.handleNativeEvent = nativeEventHandler; 6804 } 6805 6806 return id; 6807 } 6808 6809 /// Platform-specific for Windows. Unregisters a key. The id is the value returned by [registerHotKey]. 6810 void unregisterHotKey(SimpleWindow window, int id) { 6811 if(!UnregisterHotKey(window.impl.hwnd, id)) 6812 throw new WindowsApiException("UnregisterHotKey", GetLastError()); 6813 } 6814 } 6815 6816 version (X11) { 6817 pragma(lib, "dl"); 6818 import core.sys.posix.dlfcn; 6819 } 6820 6821 /++ 6822 Allows for sending synthetic input to the X server via the Xtst 6823 extension or on Windows using SendInput. 6824 6825 Please remember user input is meant to be user - don't use this 6826 if you have some other alternative! 6827 6828 History: 6829 Added May 17, 2020 with the X implementation. 6830 6831 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.) 6832 Bugs: 6833 All methods on OSX Cocoa will throw not yet implemented exceptions. 6834 +/ 6835 struct SyntheticInput { 6836 @disable this(); 6837 6838 private int* refcount; 6839 6840 version(X11) { 6841 private void* lib; 6842 6843 private extern(C) { 6844 void function(Display*, uint keycode, bool press, arch_ulong delay) XTestFakeKeyEvent; 6845 void function(Display*, uint button, bool press, arch_ulong delay) XTestFakeButtonEvent; 6846 } 6847 } 6848 6849 /// The dummy param must be 0. 6850 this(int dummy) { 6851 version(X11) { 6852 lib = dlopen("libXtst.so", RTLD_NOW); 6853 if(lib is null) 6854 throw new Exception("cannot load xtest lib extension"); 6855 scope(failure) 6856 dlclose(lib); 6857 6858 XTestFakeButtonEvent = cast(typeof(XTestFakeButtonEvent)) dlsym(lib, "XTestFakeButtonEvent"); 6859 XTestFakeKeyEvent = cast(typeof(XTestFakeKeyEvent)) dlsym(lib, "XTestFakeKeyEvent"); 6860 6861 if(XTestFakeKeyEvent is null) 6862 throw new Exception("No XTestFakeKeyEvent"); 6863 if(XTestFakeButtonEvent is null) 6864 throw new Exception("No XTestFakeButtonEvent"); 6865 } 6866 6867 refcount = new int; 6868 *refcount = 1; 6869 } 6870 6871 this(this) { 6872 if(refcount) 6873 *refcount += 1; 6874 } 6875 6876 ~this() { 6877 if(refcount) { 6878 *refcount -= 1; 6879 if(*refcount == 0) 6880 // I commented this because if I close the lib before 6881 // XCloseDisplay, it is liable to segfault... so just 6882 // gonna keep it loaded if it is loaded, no big deal 6883 // anyway. 6884 {} // dlclose(lib); 6885 } 6886 } 6887 6888 /++ 6889 Simulates typing a string into the keyboard. 6890 6891 Bugs: 6892 On X11, this ONLY works with basic ascii! On Windows, it can handle more. 6893 6894 Not implemented except on Windows and X11. 6895 +/ 6896 void sendSyntheticInput(string s) { 6897 version(Windows) { 6898 INPUT[] inputs; 6899 inputs.reserve(s.length * 2); 6900 6901 auto ei = GetMessageExtraInfo(); 6902 6903 foreach(wchar c; s) { 6904 INPUT input; 6905 input.type = INPUT_KEYBOARD; 6906 input.ki.wScan = c; 6907 input.ki.dwFlags = KEYEVENTF_UNICODE; 6908 input.ki.dwExtraInfo = ei; 6909 inputs ~= input; 6910 6911 input.ki.dwFlags |= KEYEVENTF_KEYUP; 6912 inputs ~= input; 6913 } 6914 6915 if(SendInput(cast(int) inputs.length, inputs.ptr, INPUT.sizeof) != inputs.length) { 6916 throw new WindowsApiException("SendInput", GetLastError()); 6917 } 6918 } else version(X11) { 6919 int delay = 0; 6920 foreach(ch; s) { 6921 pressKey(cast(Key) ch, true, delay); 6922 pressKey(cast(Key) ch, false, delay); 6923 delay += 5; 6924 } 6925 } else throw new NotYetImplementedException(); 6926 } 6927 6928 /++ 6929 Sends a fake press or release key event. 6930 6931 Please note you need to call [flushGui] or return to the event loop for this to actually be sent on X11. 6932 6933 Bugs: 6934 The `delay` parameter is not implemented yet on Windows. 6935 6936 Not implemented except on Windows and X11. 6937 +/ 6938 void pressKey(Key key, bool pressed, int delay = 0) { 6939 version(Windows) { 6940 INPUT input; 6941 input.type = INPUT_KEYBOARD; 6942 input.ki.wVk = cast(ushort) key; 6943 6944 input.ki.dwFlags = pressed ? 0 : KEYEVENTF_KEYUP; 6945 input.ki.dwExtraInfo = GetMessageExtraInfo(); 6946 6947 if(SendInput(1, &input, INPUT.sizeof) != 1) { 6948 throw new WindowsApiException("SendInput", GetLastError()); 6949 } 6950 } else version(X11) { 6951 XTestFakeKeyEvent(XDisplayConnection.get, XKeysymToKeycode(XDisplayConnection.get, key), pressed, delay + pressed ? 0 : 5); 6952 } else throw new NotYetImplementedException(); 6953 } 6954 6955 /++ 6956 Sends a fake mouse button press or release event. 6957 6958 Please note you need to call [flushGui] or return to the event loop for this to actually be sent on X11. 6959 6960 `pressed` param must be `true` if button is `wheelUp` or `wheelDown`. 6961 6962 Bugs: 6963 The `delay` parameter is not implemented yet on Windows. 6964 6965 The backButton and forwardButton will throw NotYetImplementedException on Windows. 6966 6967 All arguments will throw NotYetImplementedException on OSX Cocoa. 6968 +/ 6969 void pressMouseButton(MouseButton button, bool pressed, int delay = 0) { 6970 version(Windows) { 6971 INPUT input; 6972 input.type = INPUT_MOUSE; 6973 input.mi.dwExtraInfo = GetMessageExtraInfo(); 6974 6975 // input.mi.mouseData for a wheel event 6976 6977 switch(button) { 6978 case MouseButton.left: input.mi.dwFlags = pressed ? MOUSEEVENTF_LEFTDOWN : MOUSEEVENTF_LEFTUP; break; 6979 case MouseButton.middle: input.mi.dwFlags = pressed ? MOUSEEVENTF_MIDDLEDOWN : MOUSEEVENTF_MIDDLEUP; break; 6980 case MouseButton.right: input.mi.dwFlags = pressed ? MOUSEEVENTF_RIGHTDOWN : MOUSEEVENTF_RIGHTUP; break; 6981 case MouseButton.wheelUp: 6982 case MouseButton.wheelDown: 6983 input.mi.dwFlags = MOUSEEVENTF_WHEEL; 6984 input.mi.mouseData = button == MouseButton.wheelUp ? 120 : -120; 6985 break; 6986 case MouseButton.backButton: throw new NotYetImplementedException(); 6987 case MouseButton.forwardButton: throw new NotYetImplementedException(); 6988 default: 6989 } 6990 6991 if(SendInput(1, &input, INPUT.sizeof) != 1) { 6992 throw new WindowsApiException("SendInput", GetLastError()); 6993 } 6994 } else version(X11) { 6995 int btn; 6996 6997 switch(button) { 6998 case MouseButton.left: btn = 1; break; 6999 case MouseButton.middle: btn = 2; break; 7000 case MouseButton.right: btn = 3; break; 7001 case MouseButton.wheelUp: btn = 4; break; 7002 case MouseButton.wheelDown: btn = 5; break; 7003 case MouseButton.backButton: btn = 8; break; 7004 case MouseButton.forwardButton: btn = 9; break; 7005 default: 7006 } 7007 7008 assert(btn); 7009 7010 XTestFakeButtonEvent(XDisplayConnection.get, btn, pressed, delay); 7011 } else throw new NotYetImplementedException(); 7012 } 7013 7014 /// 7015 static void moveMouseArrowBy(int dx, int dy) { 7016 version(Windows) { 7017 INPUT input; 7018 input.type = INPUT_MOUSE; 7019 input.mi.dwExtraInfo = GetMessageExtraInfo(); 7020 input.mi.dx = dx; 7021 input.mi.dy = dy; 7022 input.mi.dwFlags = MOUSEEVENTF_MOVE; 7023 7024 if(SendInput(1, &input, INPUT.sizeof) != 1) { 7025 throw new WindowsApiException("SendInput", GetLastError()); 7026 } 7027 } else version(X11) { 7028 auto disp = XDisplayConnection.get(); 7029 XWarpPointer(disp, None, None, 0, 0, 0, 0, dx, dy); 7030 XFlush(disp); 7031 } else throw new NotYetImplementedException(); 7032 } 7033 7034 /// 7035 static void moveMouseArrowTo(int x, int y) { 7036 version(Windows) { 7037 INPUT input; 7038 input.type = INPUT_MOUSE; 7039 input.mi.dwExtraInfo = GetMessageExtraInfo(); 7040 input.mi.dx = x; 7041 input.mi.dy = y; 7042 input.mi.dwFlags = MOUSEEVENTF_MOVE | MOUSEEVENTF_ABSOLUTE; 7043 7044 if(SendInput(1, &input, INPUT.sizeof) != 1) { 7045 throw new WindowsApiException("SendInput", GetLastError()); 7046 } 7047 } else version(X11) { 7048 auto disp = XDisplayConnection.get(); 7049 auto root = RootWindow(disp, DefaultScreen(disp)); 7050 XWarpPointer(disp, None, root, 0, 0, 0, 0, x, y); 7051 XFlush(disp); 7052 } else throw new NotYetImplementedException(); 7053 } 7054 } 7055 7056 7057 7058 /++ 7059 [ScreenPainter] operations can use different operations to combine the color with the color on screen. 7060 7061 See_Also: 7062 $(LIST 7063 *[ScreenPainter] 7064 *[ScreenPainter.rasterOp] 7065 ) 7066 +/ 7067 enum RasterOp { 7068 normal, /// Replaces the pixel. 7069 xor, /// Uses bitwise xor to draw. 7070 } 7071 7072 // being phobos-free keeps the size WAY down 7073 private const(char)* toStringz(string s) { return (s ~ '\0').ptr; } 7074 package(arsd) const(wchar)* toWStringz(wstring s) { return (s ~ '\0').ptr; } 7075 package(arsd) const(wchar)* toWStringz(string s) { 7076 wstring r; 7077 foreach(dchar c; s) 7078 r ~= c; 7079 r ~= '\0'; 7080 return r.ptr; 7081 } 7082 private string[] split(in void[] a, char c) { 7083 string[] ret; 7084 size_t previous = 0; 7085 foreach(i, char ch; cast(ubyte[]) a) { 7086 if(ch == c) { 7087 ret ~= cast(string) a[previous .. i]; 7088 previous = i + 1; 7089 } 7090 } 7091 if(previous != a.length) 7092 ret ~= cast(string) a[previous .. $]; 7093 return ret; 7094 } 7095 7096 version(without_opengl) { 7097 enum OpenGlOptions { 7098 no, 7099 } 7100 } else { 7101 /++ 7102 Determines if you want an OpenGL context created on the new window. 7103 7104 7105 See more: [#topics-3d|in the 3d topic]. 7106 7107 --- 7108 import arsd.simpledisplay; 7109 void main() { 7110 auto window = new SimpleWindow(500, 500, "OpenGL Test", OpenGlOptions.yes); 7111 7112 // Set up the matrix 7113 window.setAsCurrentOpenGlContext(); // make this window active 7114 7115 // This is called on each frame, we will draw our scene 7116 window.redrawOpenGlScene = delegate() { 7117 7118 }; 7119 7120 window.eventLoop(0); 7121 } 7122 --- 7123 +/ 7124 enum OpenGlOptions { 7125 no, /// No OpenGL context is created 7126 yes, /// Yes, create an OpenGL context 7127 } 7128 7129 version(X11) { 7130 static if (!SdpyIsUsingIVGLBinds) { 7131 7132 7133 struct __GLXFBConfigRec {} 7134 alias GLXFBConfig = __GLXFBConfigRec*; 7135 7136 //pragma(lib, "GL"); 7137 //pragma(lib, "GLU"); 7138 interface GLX { 7139 extern(C) nothrow @nogc { 7140 XVisualInfo* glXChooseVisual(Display *dpy, int screen, 7141 const int *attrib_list); 7142 7143 void glXCopyContext(Display *dpy, GLXContext src, 7144 GLXContext dst, arch_ulong mask); 7145 7146 GLXContext glXCreateContext(Display *dpy, XVisualInfo *vis, 7147 GLXContext share_list, Bool direct); 7148 7149 GLXPixmap glXCreateGLXPixmap(Display *dpy, XVisualInfo *vis, 7150 Pixmap pixmap); 7151 7152 void glXDestroyContext(Display *dpy, GLXContext ctx); 7153 7154 void glXDestroyGLXPixmap(Display *dpy, GLXPixmap pix); 7155 7156 int glXGetConfig(Display *dpy, XVisualInfo *vis, 7157 int attrib, int *value); 7158 7159 GLXContext glXGetCurrentContext(); 7160 7161 GLXDrawable glXGetCurrentDrawable(); 7162 7163 Bool glXIsDirect(Display *dpy, GLXContext ctx); 7164 7165 Bool glXMakeCurrent(Display *dpy, GLXDrawable drawable, 7166 GLXContext ctx); 7167 7168 Bool glXQueryExtension(Display *dpy, int *error_base, int *event_base); 7169 7170 Bool glXQueryVersion(Display *dpy, int *major, int *minor); 7171 7172 void glXSwapBuffers(Display *dpy, GLXDrawable drawable); 7173 7174 void glXUseXFont(Font font, int first, int count, int list_base); 7175 7176 void glXWaitGL(); 7177 7178 void glXWaitX(); 7179 7180 7181 GLXFBConfig* glXChooseFBConfig (Display*, int, int*, int*); 7182 int glXGetFBConfigAttrib (Display*, GLXFBConfig, int, int*); 7183 XVisualInfo* glXGetVisualFromFBConfig (Display*, GLXFBConfig); 7184 7185 char* glXQueryExtensionsString (Display*, int); 7186 void* glXGetProcAddress (const(char)*); 7187 7188 } 7189 } 7190 7191 version(OSX) 7192 mixin DynamicLoad!(GLX, "GL", 0, openGlLibrariesSuccessfullyLoaded) glx; 7193 else 7194 mixin DynamicLoad!(GLX, "GLX", 0, openGlLibrariesSuccessfullyLoaded) glx; 7195 shared static this() { 7196 glx.loadDynamicLibrary(); 7197 } 7198 7199 alias glbindGetProcAddress = glXGetProcAddress; 7200 } 7201 } else version(Windows) { 7202 /* it is done below by interface GL */ 7203 } else 7204 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."); 7205 } 7206 7207 deprecated("Sorry, I misspelled it in the first version! Use `Resizability` instead.") 7208 alias Resizablity = Resizability; 7209 7210 /// When you create a SimpleWindow, you can see its resizability to be one of these via the constructor... 7211 enum Resizability { 7212 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. 7213 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. 7214 /++ 7215 $(PITFALL 7216 Planned for the future but not implemented. 7217 ) 7218 7219 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. 7220 7221 History: 7222 Added November 11, 2022, but not yet implemented and may not be for some time. 7223 +/ 7224 /*@__future*/ allowResizingMaintainingAspectRatio, 7225 /++ 7226 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. 7227 7228 History: 7229 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. 7230 7231 Your programs should not be affected, as they will continue to function as if the user simply never resized the window at all. 7232 +/ 7233 automaticallyScaleIfPossible, 7234 } 7235 /// ditto 7236 alias Resizeability = Resizability; 7237 7238 7239 /++ 7240 Alignment for [ScreenPainter.drawText]. Left, Center, or Right may be combined with VerticalTop, VerticalCenter, or VerticalBottom via bitwise or. 7241 +/ 7242 enum TextAlignment : uint { 7243 Left = 0, /// 7244 Center = 1, /// 7245 Right = 2, /// 7246 7247 VerticalTop = 0, /// 7248 VerticalCenter = 4, /// 7249 VerticalBottom = 8, /// 7250 } 7251 7252 public import arsd.color; // no longer stand alone... :-( but i need a common type for this to work with images easily. 7253 alias Rectangle = arsd.color.Rectangle; 7254 7255 7256 /++ 7257 Keyboard press and release events. 7258 +/ 7259 struct KeyEvent { 7260 /// see table below. Always use the symbolic names, even for ASCII characters, since the actual numbers vary across platforms. See [Key] 7261 Key key; 7262 ubyte hardwareCode; /// A platform and hardware specific code for the key 7263 bool pressed; /// true if the key was just pressed, false if it was just released. note: released events aren't always sent... 7264 7265 deprecated("This never actually worked anyway, you should do a character event handler instead.") dchar character; 7266 7267 uint modifierState; /// see enum [ModifierState]. They are bitwise combined together. 7268 7269 SimpleWindow window; /// associated Window 7270 7271 /++ 7272 A view into the upcoming buffer holding coming character events that are sent if and only if neither 7273 the alt or super modifier keys are pressed (check this with `!(modifierState & (ModifierState.window | ModifierState.alt))` 7274 to predict if char events are actually coming.. 7275 7276 Only available on X systems since this information is not given ahead of time elsewhere. 7277 (Well, you COULD probably dig it up, but as far as I know right now, it isn't terribly pretty.) 7278 7279 I'm adding this because it is useful to the terminal emulator, but given its platform specificness 7280 and potential quirks I'd recommend avoiding it. 7281 7282 History: 7283 Added April 26, 2021 (dub v9.5) 7284 +/ 7285 version(X11) 7286 dchar[] charsPossible; 7287 7288 // convert key event to simplified string representation a-la emacs 7289 const(char)[] toStrBuf(bool growdest=false) (char[] dest) const nothrow @trusted { 7290 uint dpos = 0; 7291 void put (const(char)[] s...) nothrow @trusted { 7292 static if (growdest) { 7293 foreach (char ch; s) if (dpos < dest.length) dest.ptr[dpos++] = ch; else { dest ~= ch; ++dpos; } 7294 } else { 7295 foreach (char ch; s) if (dpos < dest.length) dest.ptr[dpos++] = ch; 7296 } 7297 } 7298 7299 void putMod (ModifierState mod, Key key, string text) nothrow @trusted { 7300 if ((this.modifierState&mod) != 0 && (this.pressed || this.key != key)) put(text); 7301 } 7302 7303 if (!this.key && !(this.modifierState&(ModifierState.ctrl|ModifierState.alt|ModifierState.shift|ModifierState.windows))) return null; 7304 7305 // put modifiers 7306 // releasing modifier keys can produce bizarre things like "Ctrl+Ctrl", so hack around it 7307 putMod(ModifierState.ctrl, Key.Ctrl, "Ctrl+"); 7308 putMod(ModifierState.alt, Key.Alt, "Alt+"); 7309 putMod(ModifierState.windows, Key.Shift, "Windows+"); 7310 putMod(ModifierState.shift, Key.Shift, "Shift+"); 7311 7312 if (this.key) { 7313 foreach (string kn; __traits(allMembers, Key)) { 7314 if (this.key == __traits(getMember, Key, kn)) { 7315 // HACK! 7316 static if (kn == "N0") put("0"); 7317 else static if (kn == "N1") put("1"); 7318 else static if (kn == "N2") put("2"); 7319 else static if (kn == "N3") put("3"); 7320 else static if (kn == "N4") put("4"); 7321 else static if (kn == "N5") put("5"); 7322 else static if (kn == "N6") put("6"); 7323 else static if (kn == "N7") put("7"); 7324 else static if (kn == "N8") put("8"); 7325 else static if (kn == "N9") put("9"); 7326 else put(kn); 7327 return dest[0..dpos]; 7328 } 7329 } 7330 put("Unknown"); 7331 } else { 7332 if (dpos && dest[dpos-1] == '+') --dpos; 7333 } 7334 return dest[0..dpos]; 7335 } 7336 7337 string toStr() () { return cast(string)toStrBuf!true(null); } // it is safe to cast here 7338 7339 /** Parse string into key name with modifiers. It accepts things like: 7340 * 7341 * C-H-1 -- emacs style (ctrl, and windows, and 1) 7342 * 7343 * Ctrl+Win+1 -- windows style 7344 * 7345 * Ctrl-Win-1 -- '-' is a valid delimiter too 7346 * 7347 * Ctrl Win 1 -- and space 7348 * 7349 * and even "Win + 1 + Ctrl". 7350 */ 7351 static KeyEvent parse (const(char)[] name, bool* ignoreModsOut=null, int* updown=null) nothrow @trusted @nogc { 7352 auto nanchor = name; // keep it anchored, 'cause `name` may have NO_INTERIOR set 7353 7354 // remove trailing spaces 7355 while (name.length && name[$-1] <= ' ') name = name[0..$-1]; 7356 7357 // tokens delimited by blank, '+', or '-' 7358 // null on eol 7359 const(char)[] getToken () nothrow @trusted @nogc { 7360 // remove leading spaces and delimiters 7361 while (name.length && (name[0] <= ' ' || name[0] == '+' || name[0] == '-')) name = name[1..$]; 7362 if (name.length == 0) return null; // oops, no more tokens 7363 // get token 7364 size_t epos = 0; 7365 while (epos < name.length && name[epos] > ' ' && name[epos] != '+' && name[epos] != '-') ++epos; 7366 assert(epos > 0 && epos <= name.length); 7367 auto res = name[0..epos]; 7368 name = name[epos..$]; 7369 return res; 7370 } 7371 7372 static bool strEquCI (const(char)[] s0, const(char)[] s1) pure nothrow @trusted @nogc { 7373 if (s0.length != s1.length) return false; 7374 foreach (immutable ci, char c0; s0) { 7375 if (c0 >= 'A' && c0 <= 'Z') c0 += 32; // poor man's tolower 7376 char c1 = s1[ci]; 7377 if (c1 >= 'A' && c1 <= 'Z') c1 += 32; // poor man's tolower 7378 if (c0 != c1) return false; 7379 } 7380 return true; 7381 } 7382 7383 if (ignoreModsOut !is null) *ignoreModsOut = false; 7384 if (updown !is null) *updown = -1; 7385 KeyEvent res; 7386 res.key = cast(Key)0; // just in case 7387 const(char)[] tk, tkn; // last token 7388 bool allowEmascStyle = true; 7389 bool ignoreModifiers = false; 7390 tokenloop: for (;;) { 7391 tk = tkn; 7392 tkn = getToken(); 7393 //k8: yay, i took "Bloody Mess" trait from Fallout! 7394 if (tkn.length != 0 && tk.length == 0) { tk = tkn; continue tokenloop; } 7395 if (tkn.length == 0 && tk.length == 0) break; // no more tokens 7396 if (allowEmascStyle && tkn.length != 0) { 7397 if (tk.length == 1) { 7398 char mdc = tk[0]; 7399 if (mdc >= 'a' && mdc <= 'z') mdc -= 32; // poor man's toupper() 7400 if (mdc == 'C' && (res.modifierState&ModifierState.ctrl) == 0) {res.modifierState |= ModifierState.ctrl; continue tokenloop; } 7401 if (mdc == 'M' && (res.modifierState&ModifierState.alt) == 0) { res.modifierState |= ModifierState.alt; continue tokenloop; } 7402 if (mdc == 'H' && (res.modifierState&ModifierState.windows) == 0) { res.modifierState |= ModifierState.windows; continue tokenloop; } 7403 if (mdc == 'S' && (res.modifierState&ModifierState.shift) == 0) { res.modifierState |= ModifierState.shift; continue tokenloop; } 7404 if (mdc == '*') { ignoreModifiers = true; continue tokenloop; } 7405 if (mdc == 'U' || mdc == 'R') { if (updown !is null) *updown = 0; continue tokenloop; } 7406 if (mdc == 'D' || mdc == 'P') { if (updown !is null) *updown = 1; continue tokenloop; } 7407 } 7408 } 7409 allowEmascStyle = false; 7410 if (strEquCI(tk, "Ctrl")) { res.modifierState |= ModifierState.ctrl; continue tokenloop; } 7411 if (strEquCI(tk, "Alt")) { res.modifierState |= ModifierState.alt; continue tokenloop; } 7412 if (strEquCI(tk, "Win") || strEquCI(tk, "Windows")) { res.modifierState |= ModifierState.windows; continue tokenloop; } 7413 if (strEquCI(tk, "Shift")) { res.modifierState |= ModifierState.shift; continue tokenloop; } 7414 if (strEquCI(tk, "Release")) { if (updown !is null) *updown = 0; continue tokenloop; } 7415 if (strEquCI(tk, "Press")) { if (updown !is null) *updown = 1; continue tokenloop; } 7416 if (tk == "*") { ignoreModifiers = true; continue tokenloop; } 7417 if (tk.length == 0) continue; 7418 // try key name 7419 if (res.key == 0) { 7420 // little hack 7421 if (tk.length == 1 && tk[0] >= '0' && tk[0] <= '9') { 7422 final switch (tk[0]) { 7423 case '0': tk = "N0"; break; 7424 case '1': tk = "N1"; break; 7425 case '2': tk = "N2"; break; 7426 case '3': tk = "N3"; break; 7427 case '4': tk = "N4"; break; 7428 case '5': tk = "N5"; break; 7429 case '6': tk = "N6"; break; 7430 case '7': tk = "N7"; break; 7431 case '8': tk = "N8"; break; 7432 case '9': tk = "N9"; break; 7433 } 7434 } 7435 foreach (string kn; __traits(allMembers, Key)) { 7436 if (strEquCI(tk, kn)) { res.key = __traits(getMember, Key, kn); continue tokenloop; } 7437 } 7438 } 7439 // unknown or duplicate key name, get out of here 7440 break; 7441 } 7442 if (ignoreModsOut !is null) *ignoreModsOut = ignoreModifiers; 7443 return res; // something 7444 } 7445 7446 bool opEquals() (const(char)[] name) const nothrow @trusted @nogc { 7447 enum modmask = (ModifierState.ctrl|ModifierState.alt|ModifierState.shift|ModifierState.windows); 7448 void doModKey (ref uint mask, ref Key kk, Key k, ModifierState mst) { 7449 if (kk == k) { mask |= mst; kk = cast(Key)0; } 7450 } 7451 bool ignoreMods; 7452 int updown; 7453 auto ke = KeyEvent.parse(name, &ignoreMods, &updown); 7454 if ((updown == 0 && this.pressed) || (updown == 1 && !this.pressed)) return false; 7455 if (this.key != ke.key) { 7456 // things like "ctrl+alt" are complicated 7457 uint tkm = this.modifierState&modmask; 7458 uint kkm = ke.modifierState&modmask; 7459 Key tk = this.key; 7460 // ke 7461 doModKey(kkm, ke.key, Key.Ctrl, ModifierState.ctrl); 7462 doModKey(kkm, ke.key, Key.Alt, ModifierState.alt); 7463 doModKey(kkm, ke.key, Key.Windows, ModifierState.windows); 7464 doModKey(kkm, ke.key, Key.Shift, ModifierState.shift); 7465 // this 7466 doModKey(tkm, tk, Key.Ctrl, ModifierState.ctrl); 7467 doModKey(tkm, tk, Key.Alt, ModifierState.alt); 7468 doModKey(tkm, tk, Key.Windows, ModifierState.windows); 7469 doModKey(tkm, tk, Key.Shift, ModifierState.shift); 7470 return (tk == ke.key && tkm == kkm); 7471 } 7472 return (ignoreMods || ((this.modifierState&modmask) == (ke.modifierState&modmask))); 7473 } 7474 } 7475 7476 /// Sets the application name. 7477 @property string ApplicationName(string name) { 7478 return _applicationName = name; 7479 } 7480 7481 string _applicationName; 7482 7483 /// ditto 7484 @property string ApplicationName() { 7485 if(_applicationName is null) { 7486 import core.runtime; 7487 return Runtime.args[0]; 7488 } 7489 return _applicationName; 7490 } 7491 7492 7493 /// Type of a [MouseEvent]. 7494 enum MouseEventType : int { 7495 motion = 0, /// The mouse moved inside the window 7496 buttonPressed = 1, /// A mouse button was pressed or the wheel was spun 7497 buttonReleased = 2, /// A mouse button was released 7498 } 7499 7500 // FIXME: mouse move should be distinct from presses+releases, so we can avoid subscribing to those events in X unnecessarily 7501 /++ 7502 Listen for this on your event listeners if you are interested in mouse action. 7503 7504 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. 7505 7506 Examples: 7507 7508 This will draw boxes on the window with the mouse as you hold the left button. 7509 --- 7510 import arsd.simpledisplay; 7511 7512 void main() { 7513 auto window = new SimpleWindow(); 7514 7515 window.eventLoop(0, 7516 (MouseEvent ev) { 7517 if(ev.modifierState & ModifierState.leftButtonDown) { 7518 auto painter = window.draw(); 7519 painter.fillColor = Color.red; 7520 painter.outlineColor = Color.black; 7521 painter.drawRectangle(Point(ev.x / 16 * 16, ev.y / 16 * 16), 16, 16); 7522 } 7523 } 7524 ); 7525 } 7526 --- 7527 +/ 7528 struct MouseEvent { 7529 MouseEventType type; /// movement, press, release, double click. See [MouseEventType] 7530 7531 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. 7532 int y; /// Current Y position of the cursor when the event fired. 7533 7534 int dx; /// Change in X position since last report 7535 int dy; /// Change in Y position since last report 7536 7537 MouseButton button; /// See [MouseButton] 7538 int modifierState; /// See [ModifierState] 7539 7540 version(X11) 7541 private Time timestamp; 7542 7543 /// Returns a linear representation of mouse button, 7544 /// for use with static arrays. Guaranteed to be >= 0 && <= 15 7545 /// 7546 /// Its implementation is based on range-limiting `core.bitop.bsf(button) + 1`. 7547 @property ubyte buttonLinear() const { 7548 import core.bitop; 7549 if(button == 0) 7550 return 0; 7551 return (bsf(button) + 1) & 0b1111; 7552 } 7553 7554 bool doubleClick; /// was it a double click? Only set on type == [MouseEventType.buttonPressed] 7555 7556 SimpleWindow window; /// The window in which the event happened. 7557 7558 Point globalCoordinates() { 7559 Point p; 7560 if(window is null) 7561 throw new Exception("wtf"); 7562 static if(UsingSimpledisplayX11) { 7563 Window child; 7564 XTranslateCoordinates( 7565 XDisplayConnection.get, 7566 window.impl.window, 7567 RootWindow(XDisplayConnection.get, DefaultScreen(XDisplayConnection.get)), 7568 x, y, &p.x, &p.y, &child); 7569 return p; 7570 } else version(Windows) { 7571 POINT[1] points; 7572 points[0].x = x; 7573 points[0].y = y; 7574 MapWindowPoints( 7575 window.impl.hwnd, 7576 null, 7577 points.ptr, 7578 points.length 7579 ); 7580 p.x = points[0].x; 7581 p.y = points[0].y; 7582 7583 return p; 7584 } else version(OSXCocoa) { 7585 throw new NotYetImplementedException(); 7586 } else static assert(0); 7587 } 7588 7589 bool opEquals() (const(char)[] str) pure nothrow @trusted @nogc { return equStr(this, str); } 7590 7591 /** 7592 can contain emacs-like modifier prefix 7593 case-insensitive names: 7594 lmbX/leftX 7595 rmbX/rightX 7596 mmbX/middleX 7597 wheelX 7598 motion (no prefix allowed) 7599 'X' is either "up" or "down" (or "-up"/"-down"); if omited, means "down" 7600 */ 7601 static bool equStr() (scope auto ref const MouseEvent event, const(char)[] str) pure nothrow @trusted @nogc { 7602 if (str.length == 0) return false; // just in case 7603 debug(arsd_mevent_strcmp) { import iv.cmdcon; conwriteln("str=<", str, ">"); } 7604 enum Flag : uint { Up = 0x8000_0000U, Down = 0x4000_0000U, Any = 0x1000_0000U } 7605 auto anchor = str; 7606 uint mods = 0; // uint.max == any 7607 // interesting bits in kmod 7608 uint kmodmask = 7609 ModifierState.shift| 7610 ModifierState.ctrl| 7611 ModifierState.alt| 7612 ModifierState.windows| 7613 ModifierState.leftButtonDown| 7614 ModifierState.middleButtonDown| 7615 ModifierState.rightButtonDown| 7616 0; 7617 uint lastButt = uint.max; // otherwise, bit 31 means "down" 7618 bool wasButtons = false; 7619 while (str.length) { 7620 if (str.ptr[0] <= ' ') { 7621 while (str.length && str.ptr[0] <= ' ') str = str[1..$]; 7622 continue; 7623 } 7624 // one-letter modifier? 7625 if (str.length >= 2 && str.ptr[1] == '-') { 7626 switch (str.ptr[0]) { 7627 case '*': // "any" modifier (cannot be undone) 7628 mods = mods.max; 7629 break; 7630 case 'C': case 'c': // emacs "ctrl" 7631 if (mods != mods.max) mods |= ModifierState.ctrl; 7632 break; 7633 case 'M': case 'm': // emacs "meta" 7634 if (mods != mods.max) mods |= ModifierState.alt; 7635 break; 7636 case 'S': case 's': // emacs "shift" 7637 if (mods != mods.max) mods |= ModifierState.shift; 7638 break; 7639 case 'H': case 'h': // emacs "hyper" (aka winkey) 7640 if (mods != mods.max) mods |= ModifierState.windows; 7641 break; 7642 default: 7643 return false; // unknown modifier 7644 } 7645 str = str[2..$]; 7646 continue; 7647 } 7648 // word 7649 char[16] buf = void; // locased 7650 auto wep = 0; 7651 while (str.length) { 7652 immutable char ch = str.ptr[0]; 7653 if (ch <= ' ' || ch == '-') break; 7654 str = str[1..$]; 7655 if (wep > buf.length) return false; // too long 7656 if (ch >= 'A' && ch <= 'Z') buf.ptr[wep++] = cast(char)(ch+32); // poor man tolower 7657 else if (ch >= 'a' && ch <= 'z') buf.ptr[wep++] = ch; 7658 else return false; // invalid char 7659 } 7660 if (wep == 0) return false; // just in case 7661 uint bnum; 7662 enum UpDown { None = -1, Up, Down, Any } 7663 auto updown = UpDown.None; // 0: up; 1: down 7664 switch (buf[0..wep]) { 7665 // left button 7666 case "lmbup": case "leftup": updown = UpDown.Up; goto case "lmb"; 7667 case "lmbdown": case "leftdown": updown = UpDown.Down; goto case "lmb"; 7668 case "lmbany": case "leftany": updown = UpDown.Any; goto case "lmb"; 7669 case "lmb": case "left": bnum = 0; break; 7670 // middle button 7671 case "mmbup": case "middleup": updown = UpDown.Up; goto case "mmb"; 7672 case "mmbdown": case "middledown": updown = UpDown.Down; goto case "mmb"; 7673 case "mmbany": case "middleany": updown = UpDown.Any; goto case "mmb"; 7674 case "mmb": case "middle": bnum = 1; break; 7675 // right button 7676 case "rmbup": case "rightup": updown = UpDown.Up; goto case "rmb"; 7677 case "rmbdown": case "rightdown": updown = UpDown.Down; goto case "rmb"; 7678 case "rmbany": case "rightany": updown = UpDown.Any; goto case "rmb"; 7679 case "rmb": case "right": bnum = 2; break; 7680 // wheel 7681 case "wheelup": updown = UpDown.Up; goto case "wheel"; 7682 case "wheeldown": updown = UpDown.Down; goto case "wheel"; 7683 case "wheelany": updown = UpDown.Any; goto case "wheel"; 7684 case "wheel": bnum = 3; break; 7685 // motion 7686 case "motion": bnum = 7; break; 7687 // unknown 7688 default: return false; 7689 } 7690 debug(arsd_mevent_strcmp) { import iv.cmdcon; conprintfln(" 0: mods=0x%08x; bnum=%u; updown=%s [%s]", mods, bnum, updown, str); } 7691 // parse possible "-up" or "-down" 7692 if (updown == UpDown.None && bnum < 7 && str.length > 0 && str.ptr[0] == '-') { 7693 wep = 0; 7694 foreach (immutable idx, immutable char ch; str[1..$]) { 7695 if (ch <= ' ' || ch == '-') break; 7696 assert(idx == wep); // for now; trick 7697 if (wep > buf.length) { wep = 0; break; } // too long 7698 if (ch >= 'A' && ch <= 'Z') buf.ptr[wep++] = cast(char)(ch+32); // poor man tolower 7699 else if (ch >= 'a' && ch <= 'z') buf.ptr[wep++] = ch; 7700 else { wep = 0; break; } // invalid char 7701 } 7702 if (wep == 2 && buf[0..wep] == "up") updown = UpDown.Up; 7703 else if (wep == 4 && buf[0..wep] == "down") updown = UpDown.Down; 7704 else if (wep == 3 && buf[0..wep] == "any") updown = UpDown.Any; 7705 // remove parsed part 7706 if (updown != UpDown.None) str = str[wep+1..$]; 7707 } 7708 if (updown == UpDown.None) { 7709 updown = UpDown.Down; 7710 } 7711 wasButtons = wasButtons || (bnum <= 2); 7712 //assert(updown != UpDown.None); 7713 debug(arsd_mevent_strcmp) { import iv.cmdcon; conprintfln(" 1: mods=0x%08x; bnum=%u; updown=%s [%s]", mods, bnum, updown, str); } 7714 // if we have a previous button, it goes to modifiers (unless it is a wheel or motion) 7715 if (lastButt != lastButt.max) { 7716 if ((lastButt&0xff) >= 3) return false; // wheel or motion 7717 if (mods != mods.max) { 7718 uint butbit = 0; 7719 final switch (lastButt&0x03) { 7720 case 0: butbit = ModifierState.leftButtonDown; break; 7721 case 1: butbit = ModifierState.middleButtonDown; break; 7722 case 2: butbit = ModifierState.rightButtonDown; break; 7723 } 7724 if (lastButt&Flag.Down) mods |= butbit; 7725 else if (lastButt&Flag.Up) mods &= ~butbit; 7726 else if (lastButt&Flag.Any) kmodmask &= ~butbit; 7727 } 7728 } 7729 // remember last button 7730 lastButt = bnum|(updown == UpDown.Up ? Flag.Up : updown == UpDown.Any ? Flag.Any : Flag.Down); 7731 } 7732 // no button -- nothing to do 7733 if (lastButt == lastButt.max) return false; 7734 // done parsing, check if something's left 7735 foreach (immutable char ch; str) if (ch > ' ') return false; // oops 7736 // remove action button from mask 7737 if ((lastButt&0xff) < 3) { 7738 final switch (lastButt&0x03) { 7739 case 0: kmodmask &= ~cast(uint)ModifierState.leftButtonDown; break; 7740 case 1: kmodmask &= ~cast(uint)ModifierState.middleButtonDown; break; 7741 case 2: kmodmask &= ~cast(uint)ModifierState.rightButtonDown; break; 7742 } 7743 } 7744 // special case: "Motion" means "ignore buttons" 7745 if ((lastButt&0xff) == 7 && !wasButtons) { 7746 debug(arsd_mevent_strcmp) { import iv.cmdcon; conwriteln(" *: special motion"); } 7747 kmodmask &= ~cast(uint)(ModifierState.leftButtonDown|ModifierState.middleButtonDown|ModifierState.rightButtonDown); 7748 } 7749 uint kmod = event.modifierState&kmodmask; 7750 debug(arsd_mevent_strcmp) { import iv.cmdcon; conprintfln(" *: mods=0x%08x; lastButt=0x%08x; kmod=0x%08x; type=%s", mods, lastButt, kmod, event.type); } 7751 // check modifier state 7752 if (mods != mods.max) { 7753 if (kmod != mods) return false; 7754 } 7755 // now check type 7756 if ((lastButt&0xff) == 7) { 7757 // motion 7758 if (event.type != MouseEventType.motion) return false; 7759 } else if ((lastButt&0xff) == 3) { 7760 // wheel 7761 if (lastButt&Flag.Up) return (event.type == MouseEventType.buttonPressed && event.button == MouseButton.wheelUp); 7762 if (lastButt&Flag.Down) return (event.type == MouseEventType.buttonPressed && event.button == MouseButton.wheelDown); 7763 if (lastButt&Flag.Any) return (event.type == MouseEventType.buttonPressed && (event.button == MouseButton.wheelUp || event.button == MouseButton.wheelUp)); 7764 return false; 7765 } else { 7766 // buttons 7767 if (((lastButt&Flag.Down) != 0 && event.type != MouseEventType.buttonPressed) || 7768 ((lastButt&Flag.Up) != 0 && event.type != MouseEventType.buttonReleased)) 7769 { 7770 return false; 7771 } 7772 // button number 7773 switch (lastButt&0x03) { 7774 case 0: if (event.button != MouseButton.left) return false; break; 7775 case 1: if (event.button != MouseButton.middle) return false; break; 7776 case 2: if (event.button != MouseButton.right) return false; break; 7777 default: return false; 7778 } 7779 } 7780 return true; 7781 } 7782 } 7783 7784 version(arsd_mevent_strcmp_test) unittest { 7785 MouseEvent event; 7786 event.type = MouseEventType.buttonPressed; 7787 event.button = MouseButton.left; 7788 event.modifierState = ModifierState.ctrl; 7789 assert(event == "C-LMB"); 7790 assert(event != "C-LMBUP"); 7791 assert(event != "C-LMB-UP"); 7792 assert(event != "C-S-LMB"); 7793 assert(event == "*-LMB"); 7794 assert(event != "*-LMB-UP"); 7795 7796 event.type = MouseEventType.buttonReleased; 7797 assert(event != "C-LMB"); 7798 assert(event == "C-LMBUP"); 7799 assert(event == "C-LMB-UP"); 7800 assert(event != "C-S-LMB"); 7801 assert(event != "*-LMB"); 7802 assert(event == "*-LMB-UP"); 7803 7804 event.button = MouseButton.right; 7805 event.modifierState |= ModifierState.shift; 7806 event.type = MouseEventType.buttonPressed; 7807 assert(event != "C-LMB"); 7808 assert(event != "C-LMBUP"); 7809 assert(event != "C-LMB-UP"); 7810 assert(event != "C-S-LMB"); 7811 assert(event != "*-LMB"); 7812 assert(event != "*-LMB-UP"); 7813 7814 assert(event != "C-RMB"); 7815 assert(event != "C-RMBUP"); 7816 assert(event != "C-RMB-UP"); 7817 assert(event == "C-S-RMB"); 7818 assert(event == "*-RMB"); 7819 assert(event != "*-RMB-UP"); 7820 } 7821 7822 /// This gives a few more options to drawing lines and such 7823 struct Pen { 7824 Color color; /// the foreground color 7825 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. 7826 Style style; /// See [Style] 7827 /+ 7828 // From X.h 7829 7830 #define LineSolid 0 7831 #define LineOnOffDash 1 7832 #define LineDoubleDash 2 7833 LineDou- The full path of the line is drawn, but the 7834 bleDash even dashes are filled differently from the 7835 odd dashes (see fill-style) with CapButt 7836 style used where even and odd dashes meet. 7837 7838 7839 7840 /* capStyle */ 7841 7842 #define CapNotLast 0 7843 #define CapButt 1 7844 #define CapRound 2 7845 #define CapProjecting 3 7846 7847 /* joinStyle */ 7848 7849 #define JoinMiter 0 7850 #define JoinRound 1 7851 #define JoinBevel 2 7852 7853 /* fillStyle */ 7854 7855 #define FillSolid 0 7856 #define FillTiled 1 7857 #define FillStippled 2 7858 #define FillOpaqueStippled 3 7859 7860 7861 +/ 7862 /// Style of lines drawn 7863 enum Style { 7864 Solid, /// a solid line 7865 Dashed, /// a dashed line 7866 Dotted, /// a dotted line 7867 } 7868 } 7869 7870 7871 /++ 7872 Represents an in-memory image in the format that the GUI expects, but with its raw data available to your program. 7873 7874 7875 On Windows, this means a device-independent bitmap. On X11, it is an XImage. 7876 7877 $(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.) 7878 7879 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. 7880 7881 If you intend to draw an image to screen several times, you will want to convert it into a [Sprite]. 7882 7883 $(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. 7884 7885 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! 7886 7887 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!) 7888 7889 Please call `destroy(image);` when you are done with it. The easiest way to do this is with scope: 7890 7891 --- 7892 auto image = new Image(256, 256); 7893 scope(exit) destroy(image); 7894 --- 7895 7896 As long as you don't hold on to it outside the scope. 7897 7898 I might change it to be an owned pointer at some point in the future. 7899 7900 ) 7901 7902 Drawing pixels on the image may be simple, using the `opIndexAssign` function, but 7903 you can also often get a fair amount of speedup by getting the raw data format and 7904 writing some custom code. 7905 7906 FIXME INSERT EXAMPLES HERE 7907 7908 7909 +/ 7910 final class Image { 7911 /// 7912 this(int width, int height, bool forcexshm=false, bool enableAlpha = false) { 7913 this.width = width; 7914 this.height = height; 7915 this.enableAlpha = enableAlpha; 7916 7917 impl.createImage(width, height, forcexshm, enableAlpha); 7918 } 7919 7920 /// 7921 this(Size size, bool forcexshm=false, bool enableAlpha = false) { 7922 this(size.width, size.height, forcexshm, enableAlpha); 7923 } 7924 7925 private bool suppressDestruction; 7926 7927 version(X11) 7928 this(XImage* handle) { 7929 this.handle = handle; 7930 this.rawData = cast(ubyte*) handle.data; 7931 this.width = handle.width; 7932 this.height = handle.height; 7933 this.enableAlpha = handle.depth == 32; 7934 suppressDestruction = true; 7935 } 7936 7937 ~this() { 7938 if(suppressDestruction) return; 7939 impl.dispose(); 7940 } 7941 7942 // these numbers are used for working with rawData itself, skipping putPixel and getPixel 7943 /// if you do the math yourself you might be able to optimize it. Call these functions only once and cache the value. 7944 pure const @system nothrow { 7945 /* 7946 To use these to draw a blue rectangle with size WxH at position X,Y... 7947 7948 // make certain that it will fit before we proceed 7949 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! 7950 7951 // gather all the values you'll need up front. These can be kept until the image changes size if you want 7952 // (though calculating them isn't really that expensive). 7953 auto nextLineAdjustment = img.adjustmentForNextLine(); 7954 auto offR = img.redByteOffset(); 7955 auto offB = img.blueByteOffset(); 7956 auto offG = img.greenByteOffset(); 7957 auto bpp = img.bytesPerPixel(); 7958 7959 auto data = img.getDataPointer(); 7960 7961 // figure out the starting byte offset 7962 auto offset = img.offsetForTopLeftPixel() + nextLineAdjustment*Y + bpp * X; 7963 7964 auto startOfLine = data + offset; // get our pointer lined up on the first pixel 7965 7966 // and now our drawing loop for the rectangle 7967 foreach(y; 0 .. H) { 7968 auto data = startOfLine; // we keep the start of line separately so moving to the next line is simple and portable 7969 foreach(x; 0 .. W) { 7970 // write our color 7971 data[offR] = 0; 7972 data[offG] = 0; 7973 data[offB] = 255; 7974 7975 data += bpp; // moving to the next pixel is just an addition... 7976 } 7977 startOfLine += nextLineAdjustment; 7978 } 7979 7980 7981 As you can see, the loop itself was very simple thanks to the calculations being moved outside. 7982 7983 FIXME: I wonder if I can make the pixel formats consistently 32 bit across platforms, so the color offsets 7984 can be made into a bitmask or something so we can write them as *uint... 7985 */ 7986 7987 /// 7988 int offsetForTopLeftPixel() { 7989 version(X11) { 7990 return 0; 7991 } else version(Windows) { 7992 if(enableAlpha) { 7993 return (width * 4) * (height - 1); 7994 } else { 7995 return (((cast(int) width * 3 + 3) / 4) * 4) * (height - 1); 7996 } 7997 } else version(OSXCocoa) { 7998 return 0 ; //throw new NotYetImplementedException(); 7999 } else static assert(0, "fill in this info for other OSes"); 8000 } 8001 8002 /// 8003 int offsetForPixel(int x, int y) { 8004 version(X11) { 8005 auto offset = (y * width + x) * 4; 8006 return offset; 8007 } else version(Windows) { 8008 if(enableAlpha) { 8009 auto itemsPerLine = width * 4; 8010 // remember, bmps are upside down 8011 auto offset = itemsPerLine * (height - y - 1) + x * 4; 8012 return offset; 8013 } else { 8014 auto itemsPerLine = ((cast(int) width * 3 + 3) / 4) * 4; 8015 // remember, bmps are upside down 8016 auto offset = itemsPerLine * (height - y - 1) + x * 3; 8017 return offset; 8018 } 8019 } else version(OSXCocoa) { 8020 return 0 ; //throw new NotYetImplementedException(); 8021 } else static assert(0, "fill in this info for other OSes"); 8022 } 8023 8024 /// 8025 int adjustmentForNextLine() { 8026 version(X11) { 8027 return width * 4; 8028 } else version(Windows) { 8029 // windows bmps are upside down, so the adjustment is actually negative 8030 if(enableAlpha) 8031 return - (cast(int) width * 4); 8032 else 8033 return -((cast(int) width * 3 + 3) / 4) * 4; 8034 } else version(OSXCocoa) { 8035 return 0 ; //throw new NotYetImplementedException(); 8036 } else static assert(0, "fill in this info for other OSes"); 8037 } 8038 8039 /// once you have the position of a pixel, use these to get to the proper color 8040 int redByteOffset() { 8041 version(X11) { 8042 return 2; 8043 } else version(Windows) { 8044 return 2; 8045 } else version(OSXCocoa) { 8046 return 0 ; //throw new NotYetImplementedException(); 8047 } else static assert(0, "fill in this info for other OSes"); 8048 } 8049 8050 /// 8051 int greenByteOffset() { 8052 version(X11) { 8053 return 1; 8054 } else version(Windows) { 8055 return 1; 8056 } else version(OSXCocoa) { 8057 return 0 ; //throw new NotYetImplementedException(); 8058 } else static assert(0, "fill in this info for other OSes"); 8059 } 8060 8061 /// 8062 int blueByteOffset() { 8063 version(X11) { 8064 return 0; 8065 } else version(Windows) { 8066 return 0; 8067 } else version(OSXCocoa) { 8068 return 0 ; //throw new NotYetImplementedException(); 8069 } else static assert(0, "fill in this info for other OSes"); 8070 } 8071 8072 /// Only valid if [enableAlpha] is true 8073 int alphaByteOffset() { 8074 version(X11) { 8075 return 3; 8076 } else version(Windows) { 8077 return 3; 8078 } else version(OSXCocoa) { 8079 return 3; //throw new NotYetImplementedException(); 8080 } else static assert(0, "fill in this info for other OSes"); 8081 } 8082 } 8083 8084 /// 8085 final void putPixel(int x, int y, Color c) { 8086 if(x < 0 || x >= width) 8087 return; 8088 if(y < 0 || y >= height) 8089 return; 8090 8091 impl.setPixel(x, y, c); 8092 } 8093 8094 /// 8095 final Color getPixel(int x, int y) { 8096 if(x < 0 || x >= width) 8097 return Color.transparent; 8098 if(y < 0 || y >= height) 8099 return Color.transparent; 8100 8101 version(OSXCocoa) throw new NotYetImplementedException(); else 8102 return impl.getPixel(x, y); 8103 } 8104 8105 /// 8106 final void opIndexAssign(Color c, int x, int y) { 8107 putPixel(x, y, c); 8108 } 8109 8110 /// 8111 TrueColorImage toTrueColorImage() { 8112 auto tci = new TrueColorImage(width, height); 8113 convertToRgbaBytes(tci.imageData.bytes); 8114 return tci; 8115 } 8116 8117 /// 8118 static Image fromMemoryImage(MemoryImage i, bool enableAlpha = false, bool premultiply = true) { 8119 auto tci = i.getAsTrueColorImage(); 8120 auto img = new Image(tci.width, tci.height, false, enableAlpha); 8121 static if(UsingSimpledisplayX11) 8122 img.premultiply = premultiply; 8123 img.setRgbaBytes(tci.imageData.bytes); 8124 return img; 8125 } 8126 8127 /// this is here for interop with arsd.image. where can be a TrueColorImage's data member 8128 /// if you pass in a buffer, it will put it right there. length must be width*height*4 already 8129 /// if you pass null, it will allocate a new one. 8130 ubyte[] getRgbaBytes(ubyte[] where = null) { 8131 if(where is null) 8132 where = new ubyte[this.width*this.height*4]; 8133 convertToRgbaBytes(where); 8134 return where; 8135 } 8136 8137 /// this is here for interop with arsd.image. from can be a TrueColorImage's data member 8138 void setRgbaBytes(in ubyte[] from ) { 8139 assert(from.length == this.width * this.height * 4); 8140 setFromRgbaBytes(from); 8141 } 8142 8143 // FIXME: make properly cross platform by getting rgba right 8144 8145 /// warning: this is not portable across platforms because the data format can change 8146 ubyte* getDataPointer() { 8147 return impl.rawData; 8148 } 8149 8150 /// for use with getDataPointer 8151 final int bytesPerLine() const pure @safe nothrow { 8152 version(Windows) 8153 return enableAlpha ? (width * 4) : (((cast(int) width * 3 + 3) / 4) * 4); 8154 else version(X11) 8155 return 4 * width; 8156 else version(OSXCocoa) 8157 return 4 * width; 8158 else static assert(0); 8159 } 8160 8161 /// for use with getDataPointer 8162 final int bytesPerPixel() const pure @safe nothrow { 8163 version(Windows) 8164 return enableAlpha ? 4 : 3; 8165 else version(X11) 8166 return 4; 8167 else version(OSXCocoa) 8168 return 4; 8169 else static assert(0); 8170 } 8171 8172 /// 8173 immutable int width; 8174 8175 /// 8176 immutable int height; 8177 8178 /// 8179 immutable bool enableAlpha; 8180 //private: 8181 mixin NativeImageImplementation!() impl; 8182 } 8183 8184 /++ 8185 A convenience function to pop up a window displaying the image. 8186 If you pass a win, it will draw the image in it. Otherwise, it will 8187 create a window with the size of the image and run its event loop, closing 8188 when a key is pressed. 8189 8190 History: 8191 `BlockingMode` parameter added on December 8, 2021. Previously, it would 8192 always block until the application quit which could cause bizarre behavior 8193 inside a more complex application. Now, the default is to block until 8194 this window closes if it is the only event loop running, and otherwise, 8195 not to block at all and just pop up the display window asynchronously. 8196 +/ 8197 void displayImage(Image image, SimpleWindow win = null, BlockingMode bm = BlockingMode.untilWindowCloses | BlockingMode.onlyIfNotNested) { 8198 if(win is null) { 8199 win = new SimpleWindow(image); 8200 { 8201 auto p = win.draw; 8202 p.drawImage(Point(0, 0), image); 8203 } 8204 win.eventLoopWithBlockingMode( 8205 bm, 0, 8206 (KeyEvent ev) { 8207 if (ev.pressed && (ev.key == Key.Escape || ev.key == Key.Space)) win.close(); 8208 } ); 8209 } else { 8210 win.image = image; 8211 } 8212 } 8213 8214 enum FontWeight : int { 8215 dontcare = 0, 8216 thin = 100, 8217 extralight = 200, 8218 light = 300, 8219 regular = 400, 8220 medium = 500, 8221 semibold = 600, 8222 bold = 700, 8223 extrabold = 800, 8224 heavy = 900 8225 } 8226 8227 /++ 8228 Interface with the common functionality for font measurements between [OperatingSystemFont] and [DrawableFont]. 8229 8230 History: 8231 Added October 24, 2022. The methods were already on [OperatingSystemFont] before that. 8232 +/ 8233 interface MeasurableFont { 8234 /++ 8235 Returns true if it is a monospace font, meaning each of the 8236 glyphs (at least the ascii characters) have matching width 8237 and no kerning, so you can determine the display width of some 8238 strings by simply multiplying the string width by [averageWidth]. 8239 8240 (Please note that multiply doesn't $(I actually) work in general, 8241 consider characters like tab and newline, but it does sometimes.) 8242 +/ 8243 bool isMonospace(); 8244 8245 /++ 8246 The average width of glyphs in the font, traditionally equal to the 8247 width of the lowercase x. Can be used to estimate bounding boxes, 8248 especially if the font [isMonospace]. 8249 8250 Given in pixels. 8251 +/ 8252 int averageWidth(); 8253 /++ 8254 The height of the bounding box of a line. 8255 +/ 8256 int height(); 8257 /++ 8258 The maximum ascent of a glyph above the baseline. 8259 8260 Given in pixels. 8261 +/ 8262 int ascent(); 8263 /++ 8264 The maximum descent of a glyph below the baseline. For example, how low the g might go. 8265 8266 Given in pixels. 8267 +/ 8268 int descent(); 8269 /++ 8270 The display width of the given string, and if you provide a window, it will use it to 8271 make the pixel count on screen more accurate too, but this shouldn't generally be necessary. 8272 8273 Given in pixels. 8274 +/ 8275 int stringWidth(scope const(char)[] s, SimpleWindow window = null); 8276 8277 } 8278 8279 // FIXME: i need a font cache and it needs to handle disconnects. 8280 8281 /++ 8282 Represents a font loaded off the operating system or the X server. 8283 8284 8285 While the api here is unified cross platform, the fonts are not necessarily 8286 available, even across machines of the same platform, so be sure to always check 8287 for null (using [isNull]) and have a fallback plan. 8288 8289 When you have a font you like, use [ScreenPainter.setFont] to load it for drawing. 8290 8291 Worst case, a null font will automatically fall back to the default font loaded 8292 for your system. 8293 +/ 8294 class OperatingSystemFont : MeasurableFont { 8295 // FIXME: when the X Connection is lost, these need to be invalidated! 8296 // that means I need to store the original stuff again to reconstruct it too. 8297 8298 version(X11) { 8299 XFontStruct* font; 8300 XFontSet fontset; 8301 8302 version(with_xft) { 8303 XftFont* xftFont; 8304 bool isXft; 8305 } 8306 } else version(Windows) { 8307 HFONT font; 8308 int width_; 8309 int height_; 8310 } else version(OSXCocoa) { 8311 NSFont font; 8312 } else static assert(0); 8313 8314 /++ 8315 Constructs the class and immediately calls [load]. 8316 +/ 8317 this(string name, int size = 0, FontWeight weight = FontWeight.dontcare, bool italic = false) { 8318 load(name, size, weight, italic); 8319 } 8320 8321 /++ 8322 Constructs the object, but does nothing. Call one of [load] or [loadDefault] to populate the object. 8323 8324 You can also call the platform-specific [loadXft], [loadCoreX], or [loadWin32] functions if appropriate for you. 8325 8326 History: 8327 Added January 24, 2021. 8328 +/ 8329 this() { 8330 // this space intentionally left blank 8331 } 8332 8333 /++ 8334 Constructs a copy of the given font object. 8335 8336 History: 8337 Added January 7, 2023. 8338 +/ 8339 this(OperatingSystemFont font) { 8340 if(font is null || font.loadedInfo is LoadedInfo.init) 8341 loadDefault(); 8342 else 8343 load(font.loadedInfo.tupleof); 8344 } 8345 8346 /++ 8347 Loads specifically with the Xft library - a freetype font from a fontconfig string. 8348 8349 History: 8350 Added November 13, 2020. 8351 +/ 8352 version(with_xft) 8353 bool loadXft(string name, int size = 0, FontWeight weight = FontWeight.dontcare, bool italic = false) { 8354 unload(); 8355 8356 if(!XftLibrary.attempted) { 8357 XftLibrary.loadDynamicLibrary(); 8358 } 8359 8360 if(!XftLibrary.loadSuccessful) 8361 return false; 8362 8363 auto display = XDisplayConnection.get; 8364 8365 char[256] nameBuffer = void; 8366 int nbp = 0; 8367 8368 void add(in char[] a) { 8369 nameBuffer[nbp .. nbp + a.length] = a[]; 8370 nbp += a.length; 8371 } 8372 add(name); 8373 8374 if(size) { 8375 add(":size="); 8376 add(toInternal!string(size)); 8377 } 8378 if(weight != FontWeight.dontcare) { 8379 add(":weight="); 8380 add(weightToString(weight)); 8381 } 8382 if(italic) 8383 add(":slant=100"); 8384 8385 nameBuffer[nbp] = 0; 8386 8387 this.xftFont = XftFontOpenName( 8388 display, 8389 DefaultScreen(display), 8390 nameBuffer.ptr 8391 ); 8392 8393 this.isXft = true; 8394 8395 if(xftFont !is null) { 8396 isMonospace_ = stringWidth("x") == stringWidth("M"); 8397 ascent_ = xftFont.ascent; 8398 descent_ = xftFont.descent; 8399 } 8400 8401 return !isNull(); 8402 } 8403 8404 /++ 8405 Lists available fonts from the system that match the given pattern, finding names that are suitable for passing to [OperatingSystemFont]'s constructor. 8406 8407 8408 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. 8409 8410 If `pattern` is null, it returns all available font families. 8411 8412 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. 8413 8414 The format of the pattern is platform-specific. 8415 8416 History: 8417 Added May 1, 2021 (dub v9.5) 8418 +/ 8419 static void listFonts(string pattern, bool delegate(in char[] name) handler) { 8420 version(Windows) { 8421 auto hdc = GetDC(null); 8422 scope(exit) ReleaseDC(null, hdc); 8423 LOGFONT logfont; 8424 static extern(Windows) int proc(const LOGFONT* lf, const TEXTMETRIC* tm, DWORD type, LPARAM p) { 8425 auto localHandler = *(cast(typeof(handler)*) p); 8426 return localHandler(lf.lfFaceName[].sliceCString) ? 1 : 0; 8427 } 8428 EnumFontFamiliesEx(hdc, &logfont, &proc, cast(LPARAM) &handler, 0); 8429 } else version(X11) { 8430 //import core.stdc.stdio; 8431 bool done = false; 8432 version(with_xft) { 8433 if(!XftLibrary.attempted) { 8434 XftLibrary.loadDynamicLibrary(); 8435 } 8436 8437 if(!XftLibrary.loadSuccessful) 8438 goto skipXft; 8439 8440 if(!FontConfigLibrary.attempted) 8441 FontConfigLibrary.loadDynamicLibrary(); 8442 if(!FontConfigLibrary.loadSuccessful) 8443 goto skipXft; 8444 8445 { 8446 auto got = XftListFonts(XDisplayConnection.get, 0, null, "family".ptr, "style".ptr, null); 8447 if(got is null) 8448 goto skipXft; 8449 scope(exit) FcFontSetDestroy(got); 8450 8451 auto fontPatterns = got.fonts[0 .. got.nfont]; 8452 foreach(candidate; fontPatterns) { 8453 char* where, whereStyle; 8454 8455 char* pmg = FcNameUnparse(candidate); 8456 8457 //FcPatternGetString(candidate, "family", 0, &where); 8458 //FcPatternGetString(candidate, "style", 0, &whereStyle); 8459 //if(where && whereStyle) { 8460 if(pmg) { 8461 if(!handler(pmg.sliceCString)) 8462 return; 8463 //printf("%s || %s %s\n", pmg, where, whereStyle); 8464 } 8465 } 8466 } 8467 } 8468 8469 skipXft: 8470 8471 if(pattern is null) 8472 pattern = "*"; 8473 8474 int count; 8475 auto coreFontsRaw = XListFonts(XDisplayConnection.get, pattern.toStringz, 10000 /* max return */, &count); 8476 scope(exit) XFreeFontNames(coreFontsRaw); 8477 8478 auto coreFonts = coreFontsRaw[0 .. count]; 8479 8480 foreach(font; coreFonts) { 8481 char[128] tmp; 8482 tmp[0 ..5] = "core:"; 8483 auto cf = font.sliceCString; 8484 if(5 + cf.length > tmp.length) 8485 assert(0, "a font name was too long, sorry i didn't bother implementing a fallback"); 8486 tmp[5 .. 5 + cf.length] = cf; 8487 if(!handler(tmp[0 .. 5 + cf.length])) 8488 return; 8489 } 8490 } 8491 } 8492 8493 /++ 8494 Returns the raw content of the ttf file, if possible. This allows you to use OperatingSystemFont 8495 to look up fonts that you then pass to things like [arsd.ttf.OpenGlLimitedFont] or [arsd.nanovega]. 8496 8497 Returns null if impossible. It is impossible if the loaded font is not a local TTF file or if the 8498 underlying system doesn't support returning the raw bytes. 8499 8500 History: 8501 Added September 10, 2021 (dub v10.3) 8502 +/ 8503 ubyte[] getTtfBytes() { 8504 if(isNull) 8505 return null; 8506 8507 version(Windows) { 8508 auto dc = GetDC(null); 8509 auto orig = SelectObject(dc, font); 8510 8511 scope(exit) { 8512 SelectObject(dc, orig); 8513 ReleaseDC(null, dc); 8514 } 8515 8516 auto res = GetFontData(dc, 0 /* whole file */, 0 /* offset */, null, 0); 8517 if(res == GDI_ERROR) 8518 return null; 8519 8520 ubyte[] buffer = new ubyte[](res); 8521 res = GetFontData(dc, 0 /* whole file */, 0 /* offset */, buffer.ptr, cast(DWORD) buffer.length); 8522 if(res == GDI_ERROR) 8523 return null; // wtf really tbh 8524 8525 return buffer; 8526 } else version(with_xft) { 8527 if(isXft && xftFont) { 8528 if(!FontConfigLibrary.attempted) 8529 FontConfigLibrary.loadDynamicLibrary(); 8530 if(!FontConfigLibrary.loadSuccessful) 8531 return null; 8532 8533 char* file; 8534 if (FcPatternGetString(xftFont.pattern, "file", 0, &file) == 0 /*FcResultMatch*/) { 8535 if (file !is null && file[0]) { 8536 import core.stdc.stdio; 8537 auto fp = fopen(file, "rb"); 8538 if(fp is null) 8539 return null; 8540 scope(exit) 8541 fclose(fp); 8542 fseek(fp, 0, SEEK_END); 8543 ubyte[] buffer = new ubyte[](ftell(fp)); 8544 fseek(fp, 0, SEEK_SET); 8545 8546 auto got = fread(buffer.ptr, 1, buffer.length, fp); 8547 if(got != buffer.length) 8548 return null; 8549 8550 return buffer; 8551 } 8552 } 8553 } 8554 return null; 8555 } else throw new NotYetImplementedException(); 8556 } 8557 8558 // see also: XftLockFace(font) which gives a FT_Face. from /usr/include/X11/Xft/Xft.h line 352 8559 8560 private string weightToString(FontWeight weight) { 8561 with(FontWeight) 8562 final switch(weight) { 8563 case dontcare: return "*"; 8564 case thin: return "extralight"; 8565 case extralight: return "extralight"; 8566 case light: return "light"; 8567 case regular: return "regular"; 8568 case medium: return "medium"; 8569 case semibold: return "demibold"; 8570 case bold: return "bold"; 8571 case extrabold: return "demibold"; 8572 case heavy: return "black"; 8573 } 8574 } 8575 8576 /++ 8577 Loads specifically a Core X font - rendered on the X server without antialiasing. Best performance. 8578 8579 History: 8580 Added November 13, 2020. Before then, this code was integrated in the [load] function. 8581 +/ 8582 version(X11) 8583 bool loadCoreX(string name, int size = 0, FontWeight weight = FontWeight.dontcare, bool italic = false) { 8584 unload(); 8585 8586 string xfontstr; 8587 8588 if(name.length > 3 && name[0 .. 3] == "-*-") { 8589 // this is kinda a disgusting hack but if the user sends an exact 8590 // string I'd like to honor it... 8591 xfontstr = name; 8592 } else { 8593 string weightstr = weightToString(weight); 8594 string sizestr; 8595 if(size == 0) 8596 sizestr = "*"; 8597 else 8598 sizestr = toInternal!string(size); 8599 xfontstr = "-*-"~name~"-"~weightstr~"-"~(italic ? "i" : "r")~"-*-*-"~sizestr~"-*-*-*-*-*-*-*\0"; 8600 } 8601 8602 // writeln(xfontstr); 8603 8604 auto display = XDisplayConnection.get; 8605 8606 font = XLoadQueryFont(display, xfontstr.ptr); 8607 if(font is null) 8608 return false; 8609 8610 char** lol; 8611 int lol2; 8612 char* lol3; 8613 fontset = XCreateFontSet(display, xfontstr.ptr, &lol, &lol2, &lol3); 8614 8615 prepareFontInfo(); 8616 8617 return !isNull(); 8618 } 8619 8620 version(X11) 8621 private void prepareFontInfo() { 8622 if(font !is null) { 8623 isMonospace_ = stringWidth("l") == stringWidth("M"); 8624 ascent_ = font.max_bounds.ascent; 8625 descent_ = font.max_bounds.descent; 8626 } 8627 } 8628 8629 version(OSXCocoa) 8630 private void prepareFontInfo() { 8631 if(font !is null) { 8632 isMonospace_ = font.isFixedPitch; 8633 ascent_ = cast(int) font.ascender; 8634 descent_ = cast(int) - font.descender; 8635 } 8636 } 8637 8638 8639 /++ 8640 Loads a Windows font. You probably want to use [load] instead to be more generic. 8641 8642 History: 8643 Added November 13, 2020. Before then, this code was integrated in the [load] function. 8644 +/ 8645 version(Windows) 8646 bool loadWin32(string name, int size = 0, FontWeight weight = FontWeight.dontcare, bool italic = false, HDC hdc = null) { 8647 unload(); 8648 8649 WCharzBuffer buffer = WCharzBuffer(name); 8650 font = CreateFont(size, 0, 0, 0, cast(int) weight, italic, 0, 0, 0, 0, 0, 0, 0, buffer.ptr); 8651 8652 prepareFontInfo(hdc); 8653 8654 return !isNull(); 8655 } 8656 8657 version(Windows) 8658 void prepareFontInfo(HDC hdc = null) { 8659 if(font is null) 8660 return; 8661 8662 TEXTMETRIC tm; 8663 auto dc = hdc ? hdc : GetDC(null); 8664 auto orig = SelectObject(dc, font); 8665 GetTextMetrics(dc, &tm); 8666 SelectObject(dc, orig); 8667 if(hdc is null) 8668 ReleaseDC(null, dc); 8669 8670 width_ = tm.tmAveCharWidth; 8671 height_ = tm.tmHeight; 8672 ascent_ = tm.tmAscent; 8673 descent_ = tm.tmDescent; 8674 // 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. 8675 isMonospace_ = (tm.tmPitchAndFamily & TMPF_FIXED_PITCH) == 0; 8676 } 8677 8678 8679 /++ 8680 `name` is a font name, but it can also be a more complicated string parsed in an OS-specific way. 8681 8682 On X, you may prefix a name with `core:` to bypass the freetype engine causing this function to forward to [loadCoreX]. Otherwise, 8683 it calls [loadXft] if the library is available. If the library or font is not available on Xft, it falls back on [loadCoreX]. 8684 8685 On Windows, it forwards directly to [loadWin32]. 8686 8687 Params: 8688 name = font name. This is looked up by the operating system and may be interpreted differently across platforms or user machines and their preferences. 8689 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. 8690 weight = approximate boldness, results may vary. 8691 italic = try to get a slanted version of the given font. 8692 8693 History: 8694 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. 8695 +/ 8696 bool load(string name, int size = 0, FontWeight weight = FontWeight.dontcare, bool italic = false) { 8697 this.loadedInfo = LoadedInfo(name, size, weight, italic); 8698 version(X11) { 8699 version(with_xft) { 8700 if(name.length > 5 && name[0 .. 5] == "core:") { 8701 goto core; 8702 } 8703 8704 if(loadXft(name, size, weight, italic)) 8705 return true; 8706 // if xft fails, fallback to core to avoid breaking 8707 // code that already depended on this. 8708 } 8709 8710 core: 8711 8712 if(name.length > 5 && name[0 .. 5] == "core:") { 8713 name = name[5 .. $]; 8714 } 8715 8716 return loadCoreX(name, size, weight, italic); 8717 } else version(Windows) { 8718 return loadWin32(name, size, weight, italic); 8719 } else version(OSXCocoa) { 8720 return loadCocoa(name, size, weight, italic); 8721 } else static assert(0); 8722 } 8723 8724 version(OSXCocoa) 8725 bool loadCocoa(string name, int size, FontWeight weight, bool italic) { 8726 unload(); 8727 8728 font = NSFont.fontWithName(MacString(name).borrow, size); // FIXME: weight and italic? 8729 prepareFontInfo(); 8730 8731 return !isNull(); 8732 } 8733 8734 private struct LoadedInfo { 8735 string name; 8736 int size; 8737 FontWeight weight; 8738 bool italic; 8739 } 8740 private LoadedInfo loadedInfo; 8741 8742 /// 8743 void unload() { 8744 if(isNull()) 8745 return; 8746 8747 version(X11) { 8748 auto display = XDisplayConnection.display; 8749 8750 if(display is null) 8751 return; 8752 8753 version(with_xft) { 8754 if(isXft) { 8755 if(xftFont) 8756 XftFontClose(display, xftFont); 8757 isXft = false; 8758 xftFont = null; 8759 return; 8760 } 8761 } 8762 8763 if(font && font !is ScreenPainterImplementation.defaultfont) 8764 XFreeFont(display, font); 8765 if(fontset && fontset !is ScreenPainterImplementation.defaultfontset) 8766 XFreeFontSet(display, fontset); 8767 8768 font = null; 8769 fontset = null; 8770 } else version(Windows) { 8771 DeleteObject(font); 8772 font = null; 8773 } else version(OSXCocoa) { 8774 font.release(); 8775 font = null; 8776 } else static assert(0); 8777 } 8778 8779 private bool isMonospace_; 8780 8781 /++ 8782 History: 8783 Added January 16, 2021 8784 +/ 8785 bool isMonospace() { 8786 return isMonospace_; 8787 } 8788 8789 /++ 8790 Returns the average width of the font, conventionally defined as the width of the lowercase 'x' character. 8791 8792 History: 8793 Added March 26, 2020 8794 Documented January 16, 2021 8795 +/ 8796 int averageWidth() { 8797 version(X11) { 8798 return stringWidth("x"); 8799 } version(OSXCocoa) { 8800 return stringWidth("x"); 8801 } else version(Windows) 8802 return width_; 8803 else assert(0); 8804 } 8805 8806 /++ 8807 Returns the width of the string as drawn on the specified window, or the default screen if the window is null. 8808 8809 History: 8810 Added January 16, 2021 8811 +/ 8812 int stringWidth(scope const(char)[] s, SimpleWindow window = null) { 8813 // FIXME: what about tab? 8814 if(isNull) 8815 return 0; 8816 8817 version(X11) { 8818 version(with_xft) 8819 if(isXft && xftFont !is null) { 8820 //return xftFont.max_advance_width; 8821 XGlyphInfo extents; 8822 XftTextExtentsUtf8(XDisplayConnection.get, xftFont, s.ptr, cast(int) s.length, &extents); 8823 // writeln(extents); 8824 return extents.xOff; 8825 } 8826 if(font is null) 8827 return 0; 8828 else if(fontset) { 8829 XRectangle rect; 8830 Xutf8TextExtents(fontset, s.ptr, cast(int) s.length, null, &rect); 8831 8832 return rect.width; 8833 } else { 8834 return XTextWidth(font, s.ptr, cast(int) s.length); 8835 } 8836 } else version(Windows) { 8837 WCharzBuffer buffer = WCharzBuffer(s); 8838 8839 return stringWidth(buffer.slice, window); 8840 } else version(OSXCocoa) { 8841 /+ 8842 int charCount = [string length]; 8843 CGGlyph glyphs[charCount]; 8844 CGRect rects[charCount]; 8845 8846 CTFontGetGlyphsForCharacters(theCTFont, (const unichar*)[string cStringUsingEncoding:NSUnicodeStringEncoding], glyphs, charCount); 8847 CTFontGetBoundingRectsForGlyphs(theCTFont, kCTFontDefaultOrientation, glyphs, rects, charCount); 8848 8849 int totalwidth = 0, maxheight = 0; 8850 for (int i=0; i < charCount; i++) 8851 { 8852 totalwidth += rects[i].size.width; 8853 maxheight = maxheight < rects[i].size.height ? rects[i].size.height : maxheight; 8854 } 8855 8856 dim = CGSizeMake(totalwidth, maxheight); 8857 +/ 8858 8859 return 16; // FIXME 8860 } 8861 else assert(0); 8862 } 8863 8864 version(Windows) 8865 /// ditto 8866 int stringWidth(scope const(wchar)[] s, SimpleWindow window = null) { 8867 if(isNull) 8868 return 0; 8869 version(Windows) { 8870 SIZE size; 8871 8872 prepareContext(window); 8873 scope(exit) releaseContext(); 8874 8875 GetTextExtentPoint32W(dc, s.ptr, cast(int) s.length, &size); 8876 8877 return size.cx; 8878 } else { 8879 // std.conv can do this easily but it is slow to import and i don't think it is worth it 8880 static assert(0, "not implemented yet"); 8881 //return stringWidth(s, window); 8882 } 8883 } 8884 8885 private { 8886 int prepRefcount; 8887 8888 version(Windows) { 8889 HDC dc; 8890 HANDLE orig; 8891 HWND hwnd; 8892 } 8893 } 8894 /++ 8895 [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. 8896 8897 History: 8898 Added January 23, 2021 8899 +/ 8900 void prepareContext(SimpleWindow window = null) { 8901 prepRefcount++; 8902 if(prepRefcount == 1) { 8903 version(Windows) { 8904 hwnd = window is null ? null : window.impl.hwnd; 8905 dc = GetDC(hwnd); 8906 orig = SelectObject(dc, font); 8907 } 8908 } 8909 } 8910 /// ditto 8911 void releaseContext() { 8912 prepRefcount--; 8913 if(prepRefcount == 0) { 8914 version(Windows) { 8915 SelectObject(dc, orig); 8916 ReleaseDC(hwnd, dc); 8917 hwnd = null; 8918 dc = null; 8919 orig = null; 8920 } 8921 } 8922 } 8923 8924 /+ 8925 FIXME: I think I need advance and kerning pair 8926 8927 int advance(dchar from, dchar to) { } // use dchar.init for first item in string 8928 +/ 8929 8930 /++ 8931 Returns the height of the font. 8932 8933 History: 8934 Added March 26, 2020 8935 Documented January 16, 2021 8936 +/ 8937 int height() { 8938 version(X11) { 8939 version(with_xft) 8940 if(isXft && xftFont !is null) { 8941 return xftFont.ascent + xftFont.descent; // i don't use height here because it doesn't include the baseline pixel 8942 } 8943 if(font is null) 8944 return 0; 8945 return font.max_bounds.ascent + font.max_bounds.descent; 8946 } else version(Windows) { 8947 return height_; 8948 } else version(OSXCocoa) { 8949 if(font is null) 8950 return 0; 8951 return cast(int) font.capHeight; 8952 } 8953 else assert(0); 8954 } 8955 8956 private int ascent_; 8957 private int descent_; 8958 8959 /++ 8960 Max ascent above the baseline. 8961 8962 History: 8963 Added January 22, 2021 8964 +/ 8965 int ascent() { 8966 return ascent_; 8967 } 8968 8969 /++ 8970 Max descent below the baseline. 8971 8972 History: 8973 Added January 22, 2021 8974 +/ 8975 int descent() { 8976 return descent_; 8977 } 8978 8979 /++ 8980 Loads the default font used by [ScreenPainter] if none others are loaded. 8981 8982 Returns: 8983 This method mutates the `this` object, but then returns `this` for 8984 easy chaining like: 8985 8986 --- 8987 auto font = foo.isNull ? foo : foo.loadDefault 8988 --- 8989 8990 History: 8991 Added previously, but left unimplemented until January 24, 2021. 8992 +/ 8993 OperatingSystemFont loadDefault() { 8994 unload(); 8995 8996 loadedInfo = LoadedInfo.init; 8997 8998 version(X11) { 8999 // another option would be https://tronche.com/gui/x/xlib/graphics/font-metrics/XQueryFont.html 9000 // but meh since sdpy does its own thing, this should be ok too 9001 9002 ScreenPainterImplementation.ensureDefaultFontLoaded(); 9003 this.font = ScreenPainterImplementation.defaultfont; 9004 this.fontset = ScreenPainterImplementation.defaultfontset; 9005 9006 prepareFontInfo(); 9007 return this; 9008 } else version(Windows) { 9009 ScreenPainterImplementation.ensureDefaultFontLoaded(); 9010 this.font = ScreenPainterImplementation.defaultGuiFont; 9011 9012 prepareFontInfo(); 9013 return this; 9014 } else version(OSXCocoa) { 9015 this.font = NSFont.systemFontOfSize(12); 9016 9017 prepareFontInfo(); 9018 return this; 9019 } else throw new NotYetImplementedException(); 9020 } 9021 9022 /// 9023 bool isNull() { 9024 version(with_xft) 9025 if(isXft) 9026 return xftFont is null; 9027 return font is null; 9028 } 9029 9030 /* Metrics */ 9031 /+ 9032 GetABCWidth 9033 GetKerningPairs 9034 9035 if I do it right, I can size it all here, and match 9036 what happens when I draw the full string with the OS functions. 9037 9038 subclasses might do the same thing while getting the glyphs on images 9039 struct GlyphInfo { 9040 int glyph; 9041 9042 size_t stringIdxStart; 9043 size_t stringIdxEnd; 9044 9045 Rectangle boundingBox; 9046 } 9047 GlyphInfo[] getCharBoxes() { 9048 // XftTextExtentsUtf8 9049 return null; 9050 9051 } 9052 +/ 9053 9054 ~this() { 9055 if(!thisIsGuiThread) return; // FIXME: leaks if multithreaded gc 9056 unload(); 9057 } 9058 } 9059 9060 version(Windows) 9061 private string sliceCString(const(wchar)[] w) { 9062 return makeUtf8StringFromWindowsString(cast(wchar*) w.ptr); 9063 } 9064 9065 private inout(char)[] sliceCString(inout(char)* s) { 9066 import core.stdc.string; 9067 auto len = strlen(s); 9068 return s[0 .. len]; 9069 } 9070 9071 version(OSXCocoa) 9072 alias PaintingHandle = NSObject; 9073 else 9074 alias PaintingHandle = NativeWindowHandle; 9075 9076 /** 9077 The 2D drawing proxy. You acquire one of these with [SimpleWindow.draw] rather 9078 than constructing it directly. Then, it is reference counted so you can pass it 9079 at around and when the last ref goes out of scope, the buffered drawing activities 9080 are all carried out. 9081 9082 9083 Most functions use the outlineColor instead of taking a color themselves. 9084 ScreenPainter is reference counted and draws its buffer to the screen when its 9085 final reference goes out of scope. 9086 */ 9087 struct ScreenPainter { 9088 CapableOfBeingDrawnUpon window; 9089 this(CapableOfBeingDrawnUpon window, PaintingHandle handle, bool manualInvalidations) { 9090 this.window = window; 9091 if(window.closed) 9092 return; // null painter is now allowed so no need to throw anymore, this likely happens at the end of a program anyway 9093 //currentClipRectangle = arsd.color.Rectangle(0, 0, window.width, window.height); 9094 currentClipRectangle = arsd.color.Rectangle(short.min, short.min, short.max, short.max); 9095 if(window.activeScreenPainter !is null) { 9096 impl = window.activeScreenPainter; 9097 if(impl.referenceCount == 0) { 9098 impl.window = window; 9099 impl.create(handle); 9100 } 9101 impl.manualInvalidations = manualInvalidations; 9102 impl.referenceCount++; 9103 // writeln("refcount ++ ", impl.referenceCount); 9104 } else { 9105 impl = new ScreenPainterImplementation; 9106 impl.window = window; 9107 impl.create(handle); 9108 impl.referenceCount = 1; 9109 impl.manualInvalidations = manualInvalidations; 9110 window.activeScreenPainter = impl; 9111 // writeln("constructed"); 9112 } 9113 9114 copyActiveOriginals(); 9115 } 9116 9117 /++ 9118 EXPERIMENTAL. subject to change. 9119 9120 When you draw a cursor, you can draw this to notify your window of where it is, 9121 for IME systems to use. 9122 +/ 9123 void notifyCursorPosition(int x, int y, int width, int height) { 9124 if(auto w = cast(SimpleWindow) window) { 9125 w.setIMEPopupLocation(x + _originX + width, y + _originY + height); 9126 } 9127 } 9128 9129 /++ 9130 If you are using manual invalidations, this informs the 9131 window system that a section needs to be redrawn. 9132 9133 If you didn't opt into manual invalidation, you don't 9134 have to call this. 9135 9136 History: 9137 Added December 30, 2021 (dub v10.5) 9138 +/ 9139 void invalidateRect(Rectangle rect) { 9140 if(impl is null) return; 9141 9142 // transform(rect) 9143 rect.left += _originX; 9144 rect.right += _originX; 9145 rect.top += _originY; 9146 rect.bottom += _originY; 9147 9148 impl.invalidateRect(rect); 9149 } 9150 9151 private Pen originalPen; 9152 private Color originalFillColor; 9153 private arsd.color.Rectangle originalClipRectangle; 9154 private OperatingSystemFont originalFont; 9155 void copyActiveOriginals() { 9156 if(impl is null) return; 9157 originalPen = impl._activePen; 9158 originalFillColor = impl._fillColor; 9159 originalClipRectangle = impl._clipRectangle; 9160 version(OSXCocoa) {} else 9161 originalFont = impl._activeFont; 9162 } 9163 9164 ~this() { 9165 if(impl is null) return; 9166 impl.referenceCount--; 9167 //writeln("refcount -- ", impl.referenceCount); 9168 if(impl.referenceCount == 0) { 9169 // writeln("destructed"); 9170 impl.dispose(); 9171 *window.activeScreenPainter = ScreenPainterImplementation.init; 9172 // writeln("paint finished"); 9173 } else { 9174 // there is still an active reference, reset stuff so the 9175 // next user doesn't get weirdness via the reference 9176 this.rasterOp = RasterOp.normal; 9177 pen = originalPen; 9178 fillColor = originalFillColor; 9179 if(originalFont) 9180 setFont(originalFont); 9181 impl.setClipRectangle(originalClipRectangle.left, originalClipRectangle.top, originalClipRectangle.width, originalClipRectangle.height); 9182 } 9183 } 9184 9185 this(this) { 9186 if(impl is null) return; 9187 impl.referenceCount++; 9188 //writeln("refcount ++ ", impl.referenceCount); 9189 9190 copyActiveOriginals(); 9191 } 9192 9193 private int _originX; 9194 private int _originY; 9195 @property int originX() { return _originX; } 9196 @property int originY() { return _originY; } 9197 @property int originX(int a) { 9198 _originX = a; 9199 return _originX; 9200 } 9201 @property int originY(int a) { 9202 _originY = a; 9203 return _originY; 9204 } 9205 arsd.color.Rectangle currentClipRectangle; // set BEFORE doing any transformations 9206 private void transform(ref Point p) { 9207 if(impl is null) return; 9208 p.x += _originX; 9209 p.y += _originY; 9210 } 9211 9212 // this needs to be checked BEFORE the originX/Y transformation 9213 private bool isClipped(Point p) { 9214 return !currentClipRectangle.contains(p); 9215 } 9216 private bool isClipped(Point p, int width, int height) { 9217 return !currentClipRectangle.overlaps(arsd.color.Rectangle(p, Size(width + 1, height + 1))); 9218 } 9219 private bool isClipped(Point p, Size s) { 9220 return !currentClipRectangle.overlaps(arsd.color.Rectangle(p, Size(s.width + 1, s.height + 1))); 9221 } 9222 private bool isClipped(Point p, Point p2) { 9223 // need to ensure the end points are actually included inside, so the +1 does that 9224 return !currentClipRectangle.overlaps(arsd.color.Rectangle(p, p2 + Point(1, 1))); 9225 } 9226 9227 9228 /++ 9229 Sets the clipping region for drawing. If width == 0 && height == 0, disabled clipping. 9230 9231 Returns: 9232 The old clip rectangle. 9233 9234 History: 9235 Return value was `void` prior to May 10, 2021. 9236 9237 +/ 9238 arsd.color.Rectangle setClipRectangle(Point pt, int width, int height) { 9239 if(impl is null) return currentClipRectangle; 9240 if(pt == currentClipRectangle.upperLeft && width == currentClipRectangle.width && height == currentClipRectangle.height) 9241 return currentClipRectangle; // no need to do anything 9242 auto old = currentClipRectangle; 9243 currentClipRectangle = arsd.color.Rectangle(pt, Size(width, height)); 9244 transform(pt); 9245 9246 impl.setClipRectangle(pt.x, pt.y, width, height); 9247 9248 return old; 9249 } 9250 9251 /// ditto 9252 arsd.color.Rectangle setClipRectangle(arsd.color.Rectangle rect) { 9253 if(impl is null) return currentClipRectangle; 9254 return setClipRectangle(rect.upperLeft, rect.width, rect.height); 9255 } 9256 9257 /// 9258 void setFont(OperatingSystemFont font) { 9259 if(impl is null) return; 9260 impl.setFont(font); 9261 } 9262 9263 /// 9264 int fontHeight() { 9265 if(impl is null) return 0; 9266 return impl.fontHeight(); 9267 } 9268 9269 private Pen activePen; 9270 9271 /// 9272 @property void pen(Pen p) { 9273 if(impl is null) return; 9274 activePen = p; 9275 impl.pen(p); 9276 } 9277 9278 /// 9279 @scriptable 9280 @property void outlineColor(Color c) { 9281 if(impl is null) return; 9282 if(activePen.color == c) 9283 return; 9284 activePen.color = c; 9285 impl.pen(activePen); 9286 } 9287 9288 /// 9289 @scriptable 9290 @property void fillColor(Color c) { 9291 if(impl is null) return; 9292 impl.fillColor(c); 9293 } 9294 9295 /// 9296 @property void rasterOp(RasterOp op) { 9297 if(impl is null) return; 9298 impl.rasterOp(op); 9299 } 9300 9301 9302 void updateDisplay() { 9303 // FIXME this should do what the dtor does 9304 } 9305 9306 /// 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) 9307 void scrollArea(Point upperLeft, int width, int height, int dx, int dy) { 9308 if(impl is null) return; 9309 if(isClipped(upperLeft, width, height)) return; 9310 transform(upperLeft); 9311 version(Windows) { 9312 // http://msdn.microsoft.com/en-us/library/windows/desktop/bb787589%28v=vs.85%29.aspx 9313 RECT scroll = RECT(upperLeft.x, upperLeft.y, upperLeft.x + width, upperLeft.y + height); 9314 RECT clip = scroll; 9315 RECT uncovered; 9316 HRGN hrgn; 9317 if(!ScrollDC(impl.hdc, -dx, -dy, &scroll, &clip, hrgn, &uncovered)) 9318 throw new WindowsApiException("ScrollDC", GetLastError()); 9319 9320 } else version(X11) { 9321 // FIXME: clip stuff outside this rectangle 9322 XCopyArea(impl.display, impl.d, impl.d, impl.gc, upperLeft.x, upperLeft.y, width, height, upperLeft.x - dx, upperLeft.y - dy); 9323 } else version(OSXCocoa) { 9324 throw new NotYetImplementedException(); 9325 } else static assert(0); 9326 } 9327 9328 /// 9329 void clear(Color color = Color.white()) { 9330 if(impl is null) return; 9331 fillColor = color; 9332 outlineColor = color; 9333 drawRectangle(Point(0, 0), window.width, window.height); 9334 } 9335 9336 /++ 9337 Draws a pixmap (represented by the [Sprite] class) on the drawable. 9338 9339 Params: 9340 upperLeft = point on the window where the upper left corner of the image will be drawn 9341 imageUpperLeft = point on the image to start the slice to draw 9342 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. 9343 History: 9344 The `imageUpperLeft` and `sliceSize` parameters were added on March 11, 2021 (dub v9.3.0) 9345 +/ 9346 void drawPixmap(Sprite s, Point upperLeft, Point imageUpperLeft = Point(0, 0), Size sliceSize = Size(0, 0)) { 9347 if(impl is null) return; 9348 if(isClipped(upperLeft, s.width, s.height)) return; 9349 transform(upperLeft); 9350 impl.drawPixmap(s, upperLeft.x, upperLeft.y, imageUpperLeft.x, imageUpperLeft.y, sliceSize.width, sliceSize.height); 9351 } 9352 9353 /// 9354 void drawImage(Point upperLeft, Image i, Point upperLeftOfImage = Point(0, 0), int w = 0, int h = 0) { 9355 if(impl is null) return; 9356 //if(isClipped(upperLeft, w, h)) return; // FIXME 9357 transform(upperLeft); 9358 if(w == 0 || w > i.width) 9359 w = i.width; 9360 if(h == 0 || h > i.height) 9361 h = i.height; 9362 if(upperLeftOfImage.x < 0) 9363 upperLeftOfImage.x = 0; 9364 if(upperLeftOfImage.y < 0) 9365 upperLeftOfImage.y = 0; 9366 9367 impl.drawImage(upperLeft.x, upperLeft.y, i, upperLeftOfImage.x, upperLeftOfImage.y, w, h); 9368 } 9369 9370 /// 9371 Size textSize(in char[] text) { 9372 if(impl is null) return Size(0, 0); 9373 return impl.textSize(text); 9374 } 9375 9376 /++ 9377 Draws a string in the window with the set font (see [setFont] to change it). 9378 9379 Params: 9380 upperLeft = the upper left point of the bounding box of the text 9381 text = the string to draw 9382 lowerRight = the lower right point of the bounding box of the text. If 0, 0, there is no lower right bound. 9383 alignment = A [arsd.docs.general_concepts#bitflags|combination] of [TextAlignment] flags 9384 +/ 9385 @scriptable 9386 void drawText(Point upperLeft, in char[] text, Point lowerRight = Point(0, 0), uint alignment = 0) { 9387 if(impl is null) return; 9388 if(lowerRight.x != 0 || lowerRight.y != 0) { 9389 if(isClipped(upperLeft, lowerRight)) return; 9390 transform(lowerRight); 9391 } else { 9392 if(isClipped(upperLeft, textSize(text))) return; 9393 } 9394 transform(upperLeft); 9395 impl.drawText(upperLeft.x, upperLeft.y, lowerRight.x, lowerRight.y, text, alignment); 9396 } 9397 9398 /++ 9399 Draws text using a custom font. 9400 9401 This is still MAJOR work in progress. 9402 9403 Creating a [DrawableFont] can be tricky and require additional dependencies. 9404 +/ 9405 void drawText(DrawableFont font, Point upperLeft, in char[] text) { 9406 if(impl is null) return; 9407 if(isClipped(upperLeft, Point(int.max, int.max))) return; 9408 transform(upperLeft); 9409 font.drawString(this, upperLeft, text); 9410 } 9411 9412 version(Windows) 9413 void drawText(Point upperLeft, scope const(wchar)[] text) { 9414 if(impl is null) return; 9415 if(isClipped(upperLeft, Point(int.max, int.max))) return; 9416 transform(upperLeft); 9417 9418 if(text.length && text[$-1] == '\n') 9419 text = text[0 .. $-1]; // tailing newlines are weird on windows... 9420 9421 TextOutW(impl.hdc, upperLeft.x, upperLeft.y, text.ptr, cast(int) text.length); 9422 } 9423 9424 static struct TextDrawingContext { 9425 Point boundingBoxUpperLeft; 9426 Point boundingBoxLowerRight; 9427 9428 Point currentLocation; 9429 9430 Point lastDrewUpperLeft; 9431 Point lastDrewLowerRight; 9432 9433 // how do i do right aligned rich text? 9434 // i kinda want to do a pre-made drawing then right align 9435 // draw the whole block. 9436 // 9437 // That's exactly the diff: inline vs block stuff. 9438 9439 // I need to get coordinates of an inline section out too, 9440 // not just a bounding box, but a series of bounding boxes 9441 // should be ok. Consider what's needed to detect a click 9442 // on a link in the middle of a paragraph breaking a line. 9443 // 9444 // Generally, we should be able to get the rectangles of 9445 // any portion we draw. 9446 // 9447 // It also needs to tell what text is left if it overflows 9448 // out of the box, so we can do stuff like float images around 9449 // it. It should not attempt to draw a letter that would be 9450 // clipped. 9451 // 9452 // I might also turn off word wrap stuff. 9453 } 9454 9455 void drawText(TextDrawingContext context, in char[] text, uint alignment = 0) { 9456 if(impl is null) return; 9457 // FIXME 9458 } 9459 9460 /// Drawing an individual pixel is slow. Avoid it if possible. 9461 void drawPixel(Point where) { 9462 if(impl is null) return; 9463 if(isClipped(where)) return; 9464 transform(where); 9465 impl.drawPixel(where.x, where.y); 9466 } 9467 9468 9469 /// Draws a pen using the current pen / outlineColor 9470 @scriptable 9471 void drawLine(Point starting, Point ending) { 9472 if(impl is null) return; 9473 if(isClipped(starting, ending)) return; 9474 transform(starting); 9475 transform(ending); 9476 impl.drawLine(starting.x, starting.y, ending.x, ending.y); 9477 } 9478 9479 /// Draws a rectangle using the current pen/outline color for the border and brush/fill color for the insides 9480 /// The outer lines, inclusive of x = 0, y = 0, x = width - 1, and y = height - 1 are drawn with the outlineColor 9481 /// The rest of the pixels are drawn with the fillColor. If fillColor is transparent, those pixels are not drawn. 9482 @scriptable 9483 void drawRectangle(Point upperLeft, int width, int height) { 9484 if(impl is null) return; 9485 if(isClipped(upperLeft, width, height)) return; 9486 transform(upperLeft); 9487 impl.drawRectangle(upperLeft.x, upperLeft.y, width, height); 9488 } 9489 9490 /// ditto 9491 void drawRectangle(Point upperLeft, Size size) { 9492 if(impl is null) return; 9493 if(isClipped(upperLeft, size.width, size.height)) return; 9494 transform(upperLeft); 9495 impl.drawRectangle(upperLeft.x, upperLeft.y, size.width, size.height); 9496 } 9497 9498 /// ditto 9499 void drawRectangle(Point upperLeft, Point lowerRightInclusive) { 9500 if(impl is null) return; 9501 if(isClipped(upperLeft, lowerRightInclusive + Point(1, 1))) return; 9502 transform(upperLeft); 9503 transform(lowerRightInclusive); 9504 impl.drawRectangle(upperLeft.x, upperLeft.y, 9505 lowerRightInclusive.x - upperLeft.x + 1, lowerRightInclusive.y - upperLeft.y + 1); 9506 } 9507 9508 // overload added on May 12, 2021 9509 /// ditto 9510 void drawRectangle(Rectangle rect) { 9511 drawRectangle(rect.upperLeft, rect.size); 9512 } 9513 9514 /// Arguments are the points of the bounding rectangle 9515 void drawEllipse(Point upperLeft, Point lowerRight) { 9516 if(impl is null) return; 9517 if(isClipped(upperLeft, lowerRight)) return; 9518 transform(upperLeft); 9519 transform(lowerRight); 9520 impl.drawEllipse(upperLeft.x, upperLeft.y, lowerRight.x, lowerRight.y); 9521 } 9522 9523 /++ 9524 start and finish are units of degrees * 64 9525 9526 History: 9527 The Windows implementation didn't match the Linux implementation until September 24, 2021. 9528 9529 They still don't exactly match in outlining the arc with straight lines (Windows does, Linux doesn't for now). 9530 +/ 9531 void drawArc(Point upperLeft, int width, int height, int start, int finish) { 9532 if(impl is null) return; 9533 // FIXME: not actually implemented 9534 if(isClipped(upperLeft, width, height)) return; 9535 transform(upperLeft); 9536 impl.drawArc(upperLeft.x, upperLeft.y, width, height, start, finish); 9537 } 9538 9539 /// this function draws a circle with the drawEllipse() function above, it requires the upper left point and the radius 9540 void drawCircle(Point upperLeft, int diameter) { 9541 drawEllipse(upperLeft, Point(upperLeft.x + diameter, upperLeft.y + diameter)); 9542 } 9543 9544 /// . 9545 void drawPolygon(Point[] vertexes) { 9546 if(impl is null) return; 9547 assert(vertexes.length); 9548 int minX = int.max, minY = int.max, maxX = int.min, maxY = int.min; 9549 foreach(ref vertex; vertexes) { 9550 if(vertex.x < minX) 9551 minX = vertex.x; 9552 if(vertex.y < minY) 9553 minY = vertex.y; 9554 if(vertex.x > maxX) 9555 maxX = vertex.x; 9556 if(vertex.y > maxY) 9557 maxY = vertex.y; 9558 transform(vertex); 9559 } 9560 if(isClipped(Point(minX, maxY), Point(maxX + 1, maxY + 1))) return; 9561 impl.drawPolygon(vertexes); 9562 } 9563 9564 /// ditto 9565 void drawPolygon(Point[] vertexes...) { 9566 if(impl is null) return; 9567 drawPolygon(vertexes); 9568 } 9569 9570 9571 // and do a draw/fill in a single call maybe. Windows can do it... but X can't, though it could do two calls. 9572 9573 //mixin NativeScreenPainterImplementation!() impl; 9574 9575 9576 // HACK: if I mixin the impl directly, it won't let me override the copy 9577 // constructor! The linker complains about there being multiple definitions. 9578 // I'll make the best of it and reference count it though. 9579 ScreenPainterImplementation* impl; 9580 } 9581 9582 // HACK: I need a pointer to the implementation so it's separate 9583 struct ScreenPainterImplementation { 9584 CapableOfBeingDrawnUpon window; 9585 int referenceCount; 9586 mixin NativeScreenPainterImplementation!(); 9587 } 9588 9589 // FIXME: i haven't actually tested the sprite class on MS Windows 9590 9591 /** 9592 Sprites are optimized for fast drawing on the screen, but slow for direct pixel 9593 access. They are best for drawing a relatively unchanging image repeatedly on the screen. 9594 9595 9596 On X11, this corresponds to an `XPixmap`. On Windows, it still uses a bitmap, 9597 though I'm not sure that's ideal and the implementation might change. 9598 9599 You create one by giving a window and an image. It optimizes for that window, 9600 and copies the image into it to use as the initial picture. Creating a sprite 9601 can be quite slow (especially over a network connection) so you should do it 9602 as little as possible and just hold on to your sprite handles after making them. 9603 simpledisplay does try to do its best though, using the XSHM extension if available, 9604 but you should still write your code as if it will always be slow. 9605 9606 Then you can use `sprite.drawAt(painter, point);` to draw it, which should be 9607 a fast operation - much faster than drawing the Image itself every time. 9608 9609 `Sprite` represents a scarce resource which should be freed when you 9610 are done with it. Use the `dispose` method to do this. Do not use a `Sprite` 9611 after it has been disposed. If you are unsure about this, don't take chances, 9612 just let the garbage collector do it for you. But ideally, you can manage its 9613 lifetime more efficiently. 9614 9615 $(NOTE `Sprite`, like the rest of simpledisplay's `ScreenPainter`, does not 9616 support alpha blending in its drawing at this time. That might change in the 9617 future, but if you need alpha blending right now, use OpenGL instead. See 9618 `gamehelpers.d` for a similar class to `Sprite` that uses OpenGL: `OpenGlTexture`.) 9619 9620 Update: on April 23, 2021, I finally added alpha blending support. You must opt 9621 in by setting the enableAlpha = true in the constructor. 9622 */ 9623 class Sprite : CapableOfBeingDrawnUpon { 9624 9625 /// 9626 ScreenPainter draw() { 9627 return ScreenPainter(this, handle, false); 9628 } 9629 9630 /++ 9631 Copies the sprite's current state into a [TrueColorImage]. 9632 9633 Be warned: this can be a very slow operation 9634 9635 History: 9636 Actually implemented on March 14, 2021 9637 +/ 9638 TrueColorImage takeScreenshot() { 9639 return trueColorImageFromNativeHandle(handle, width, height); 9640 } 9641 9642 void delegate() paintingFinishedDg() { return null; } 9643 bool closed() { return false; } 9644 ScreenPainterImplementation* activeScreenPainter_; 9645 protected ScreenPainterImplementation* activeScreenPainter() { return activeScreenPainter_; } 9646 protected void activeScreenPainter(ScreenPainterImplementation* i) { activeScreenPainter_ = i; } 9647 9648 version(Windows) 9649 private ubyte* rawData; 9650 // FIXME: sprites are lost when disconnecting from X! We need some way to invalidate them... 9651 // ditto on the XPicture stuff 9652 9653 version(X11) { 9654 private static XRenderPictFormat* RGB24; 9655 private static XRenderPictFormat* ARGB32; 9656 9657 private Picture xrenderPicture; 9658 } 9659 9660 version(X11) 9661 private static void requireXRender() { 9662 if(!XRenderLibrary.loadAttempted) { 9663 XRenderLibrary.loadDynamicLibrary(); 9664 } 9665 9666 if(!XRenderLibrary.loadSuccessful) 9667 throw new Exception("XRender library load failure"); 9668 9669 auto display = XDisplayConnection.get; 9670 9671 // FIXME: if we migrate X displays, these need to be changed 9672 if(RGB24 is null) 9673 RGB24 = XRenderFindStandardFormat(display, PictStandardRGB24); 9674 if(ARGB32 is null) 9675 ARGB32 = XRenderFindStandardFormat(display, PictStandardARGB32); 9676 } 9677 9678 protected this() {} 9679 9680 this(SimpleWindow win, int width, int height, bool enableAlpha = false) { 9681 this._width = width; 9682 this._height = height; 9683 this.enableAlpha = enableAlpha; 9684 9685 version(X11) { 9686 auto display = XDisplayConnection.get(); 9687 9688 if(enableAlpha) { 9689 requireXRender(); 9690 } 9691 9692 handle = XCreatePixmap(display, cast(Drawable) win.window, width, height, enableAlpha ? 32 : DefaultDepthOfDisplay(display)); 9693 9694 if(enableAlpha) { 9695 XRenderPictureAttributes attrs; 9696 xrenderPicture = XRenderCreatePicture(display, handle, ARGB32, 0, &attrs); 9697 } 9698 } else version(Windows) { 9699 version(CRuntime_DigitalMars) { 9700 //if(enableAlpha) 9701 //throw new Exception("Alpha support not available, try recompiling with -m32mscoff"); 9702 } 9703 9704 BITMAPINFO infoheader; 9705 infoheader.bmiHeader.biSize = infoheader.bmiHeader.sizeof; 9706 infoheader.bmiHeader.biWidth = width; 9707 infoheader.bmiHeader.biHeight = height; 9708 infoheader.bmiHeader.biPlanes = 1; 9709 infoheader.bmiHeader.biBitCount = enableAlpha ? 32 : 24; 9710 infoheader.bmiHeader.biCompression = BI_RGB; 9711 9712 // FIXME: this should prolly be a device dependent bitmap... 9713 handle = CreateDIBSection( 9714 null, 9715 &infoheader, 9716 DIB_RGB_COLORS, 9717 cast(void**) &rawData, 9718 null, 9719 0); 9720 9721 if(handle is null) 9722 throw new WindowsApiException("couldn't create pixmap", GetLastError()); 9723 } 9724 } 9725 9726 /// Makes a sprite based on the image with the initial contents from the Image 9727 this(SimpleWindow win, Image i) { 9728 this(win, i.width, i.height, i.enableAlpha); 9729 9730 version(X11) { 9731 auto display = XDisplayConnection.get(); 9732 auto gc = XCreateGC(display, this.handle, 0, null); 9733 scope(exit) XFreeGC(display, gc); 9734 if(i.usingXshm) 9735 XShmPutImage(display, cast(Drawable) handle, gc, i.handle, 0, 0, 0, 0, i.width, i.height, false); 9736 else 9737 XPutImage(display, cast(Drawable) handle, gc, i.handle, 0, 0, 0, 0, i.width, i.height); 9738 } else version(Windows) { 9739 auto itemsPerLine = enableAlpha ? (4 * width) : (((cast(int) width * 3 + 3) / 4) * 4); 9740 auto arrLength = itemsPerLine * height; 9741 rawData[0..arrLength] = i.rawData[0..arrLength]; 9742 } else version(OSXCocoa) { 9743 // FIXME: I have no idea if this is even any good 9744 auto colorSpace = CGColorSpaceCreateDeviceRGB(); 9745 handle = CGBitmapContextCreate(null, width, height, 8, 4*width, 9746 colorSpace, 9747 kCGImageAlphaPremultipliedLast 9748 |kCGBitmapByteOrder32Big); 9749 CGColorSpaceRelease(colorSpace); 9750 auto rawData = CGBitmapContextGetData(handle); 9751 9752 auto rdl = (width * height * 4); 9753 rawData[0 .. rdl] = i.rawData[0 .. rdl]; 9754 } else static assert(0); 9755 } 9756 9757 /++ 9758 Draws the image on the specified painter at the specified point. The point is the upper-left point where the image will be drawn. 9759 9760 Params: 9761 where = point on the window where the upper left corner of the image will be drawn 9762 imageUpperLeft = point on the image to start the slice to draw 9763 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. 9764 History: 9765 The `imageUpperLeft` and `sliceSize` parameters were added on March 11, 2021 (dub v9.3.0) 9766 +/ 9767 void drawAt(ScreenPainter painter, Point where, Point imageUpperLeft = Point(0, 0), Size sliceSize = Size(0, 0)) { 9768 painter.drawPixmap(this, where, imageUpperLeft, sliceSize); 9769 } 9770 9771 /// Call this when you're ready to get rid of it 9772 void dispose() { 9773 version(X11) { 9774 staticDispose(xrenderPicture, handle); 9775 xrenderPicture = None; 9776 handle = None; 9777 } else version(Windows) { 9778 staticDispose(handle); 9779 handle = null; 9780 } else version(OSXCocoa) { 9781 staticDispose(handle); 9782 handle = null; 9783 } else static assert(0); 9784 9785 } 9786 9787 version(X11) 9788 static void staticDispose(Picture xrenderPicture, Pixmap handle) { 9789 if(xrenderPicture) 9790 XRenderFreePicture(XDisplayConnection.get, xrenderPicture); 9791 if(handle) 9792 XFreePixmap(XDisplayConnection.get(), handle); 9793 } 9794 else version(Windows) 9795 static void staticDispose(HBITMAP handle) { 9796 if(handle) 9797 DeleteObject(handle); 9798 } 9799 else version(OSXCocoa) 9800 static void staticDispose(CGContextRef context) { 9801 if(context) 9802 CGContextRelease(context); 9803 } 9804 9805 ~this() { 9806 version(X11) { if(xrenderPicture || handle) 9807 cleanupQueue.queue!staticDispose(xrenderPicture, handle); 9808 } else version(Windows) { if(handle) 9809 cleanupQueue.queue!staticDispose(handle); 9810 } else version(OSXCocoa) { if(handle) 9811 cleanupQueue.queue!staticDispose(handle); 9812 } else static assert(0); 9813 } 9814 9815 /// 9816 final @property int width() { return _width; } 9817 9818 /// 9819 final @property int height() { return _height; } 9820 9821 /// 9822 static Sprite fromMemoryImage(SimpleWindow win, MemoryImage img, bool enableAlpha = false) { 9823 return new Sprite(win, Image.fromMemoryImage(img, enableAlpha)); 9824 } 9825 9826 auto nativeHandle() { 9827 return handle; 9828 } 9829 9830 private: 9831 9832 int _width; 9833 int _height; 9834 bool enableAlpha; 9835 version(X11) 9836 Pixmap handle; 9837 else version(Windows) 9838 HBITMAP handle; 9839 else version(OSXCocoa) 9840 CGContextRef handle; 9841 else static assert(0); 9842 } 9843 9844 /++ 9845 Represents a display-side gradient pseudo-image. Actually construct it with [LinearGradient], [RadialGradient], or [ConicalGradient]. 9846 9847 History: 9848 Added November 20, 2021 (dub v10.4) 9849 +/ 9850 version(OSXCocoa) {} else // NotYetImplementedException 9851 abstract class Gradient : Sprite { 9852 protected this(int w, int h) { 9853 version(X11) { 9854 Sprite.requireXRender(); 9855 9856 super(); 9857 enableAlpha = true; 9858 _width = w; 9859 _height = h; 9860 } else version(Windows) { 9861 super(null, w, h, true); // on Windows i'm just making a bitmap myself 9862 } 9863 } 9864 9865 version(Windows) 9866 final void forEachPixel(scope Color delegate(int x, int y) dg) { 9867 auto ptr = rawData; 9868 foreach(j; 0 .. _height) 9869 foreach(i; 0 .. _width) { 9870 auto color = dg(i, _height - j - 1); // cuz of upside down bitmap 9871 *rawData = (color.a * color.b) / 255; rawData++; 9872 *rawData = (color.a * color.g) / 255; rawData++; 9873 *rawData = (color.a * color.r) / 255; rawData++; 9874 *rawData = color.a; rawData++; 9875 } 9876 } 9877 9878 version(X11) 9879 protected void helper(scope Stop[] stops, scope Picture delegate(scope XFixed[] stopsPositions, scope XRenderColor[] colors) dg) { 9880 assert(stops.length > 0); 9881 assert(stops.length <= 16, "I got lazy with buffers"); 9882 9883 XFixed[16] stopsPositions = void; 9884 XRenderColor[16] colors = void; 9885 9886 foreach(idx, stop; stops) { 9887 stopsPositions[idx] = cast(int)(stop.percentage * ushort.max); 9888 auto c = stop.c; 9889 colors[idx] = XRenderColor( 9890 cast(ushort)(c.r * ushort.max / 255), 9891 cast(ushort)(c.g * ushort.max / 255), 9892 cast(ushort)(c.b * ushort.max / 255), 9893 cast(ushort)(c.a * ubyte.max) // max value here is fractional 9894 ); 9895 } 9896 9897 xrenderPicture = dg(stopsPositions, colors); 9898 } 9899 9900 /// 9901 static struct Stop { 9902 float percentage; /// between 0 and 1.0 9903 Color c; 9904 } 9905 } 9906 9907 /++ 9908 Creates a linear gradient between p1 and p2. 9909 9910 X ONLY RIGHT NOW 9911 9912 History: 9913 Added November 20, 2021 (dub v10.4) 9914 9915 Bugs: 9916 Not yet implemented on Windows. 9917 +/ 9918 version(OSXCocoa) {} else // NotYetImplementedException 9919 class LinearGradient : Gradient { 9920 /++ 9921 9922 +/ 9923 this(Point p1, Point p2, Stop[] stops...) { 9924 super(p2.x, p2.y); 9925 9926 version(X11) { 9927 XLinearGradient gradient; 9928 gradient.p1 = XPointFixed(p1.x * ushort.max, p1.y * ushort.max); 9929 gradient.p2 = XPointFixed(p2.x * ushort.max, p2.y * ushort.max); 9930 9931 helper(stops, (scope XFixed[] stopsPositions, scope XRenderColor[] colors) { 9932 return XRenderCreateLinearGradient( 9933 XDisplayConnection.get, 9934 &gradient, 9935 stopsPositions.ptr, 9936 colors.ptr, 9937 cast(int) stops.length); 9938 }); 9939 } else version(Windows) { 9940 // FIXME 9941 forEachPixel((int x, int y) { 9942 import core.stdc.math; 9943 9944 //sqrtf( 9945 9946 return Color.transparent; 9947 // return Color(x * 2, y * 2, 0, 128); // this result is so beautiful 9948 }); 9949 } 9950 } 9951 } 9952 9953 /++ 9954 A conical gradient goes from color to color around a circumference from a center point. 9955 9956 X ONLY RIGHT NOW 9957 9958 History: 9959 Added November 20, 2021 (dub v10.4) 9960 9961 Bugs: 9962 Not yet implemented on Windows. 9963 +/ 9964 version(OSXCocoa) {} else // NotYetImplementedException 9965 class ConicalGradient : Gradient { 9966 /++ 9967 9968 +/ 9969 this(Point center, float angleInDegrees, Stop[] stops...) { 9970 super(center.x * 2, center.y * 2); 9971 9972 version(X11) { 9973 XConicalGradient gradient; 9974 gradient.center = XPointFixed(center.x * ushort.max, center.y * ushort.max); 9975 gradient.angle = cast(int)(angleInDegrees * ushort.max); 9976 9977 helper(stops, (scope XFixed[] stopsPositions, scope XRenderColor[] colors) { 9978 return XRenderCreateConicalGradient( 9979 XDisplayConnection.get, 9980 &gradient, 9981 stopsPositions.ptr, 9982 colors.ptr, 9983 cast(int) stops.length); 9984 }); 9985 } else version(Windows) { 9986 // FIXME 9987 forEachPixel((int x, int y) { 9988 import core.stdc.math; 9989 9990 //sqrtf( 9991 9992 return Color.transparent; 9993 // return Color(x * 2, y * 2, 0, 128); // this result is so beautiful 9994 }); 9995 9996 } 9997 } 9998 } 9999 10000 /++ 10001 A radial gradient goes from color to color based on distance from the center. 10002 It is like rings of color. 10003 10004 X ONLY RIGHT NOW 10005 10006 10007 More specifically, you create two circles: an inner circle and an outer circle. 10008 The gradient is only drawn in the area outside the inner circle but inside the outer 10009 circle. The closest line between those two circles forms the line for the gradient 10010 and the stops are calculated the same as the [LinearGradient]. Then it just sweeps around. 10011 10012 History: 10013 Added November 20, 2021 (dub v10.4) 10014 10015 Bugs: 10016 Not yet implemented on Windows. 10017 +/ 10018 version(OSXCocoa) {} else // NotYetImplementedException 10019 class RadialGradient : Gradient { 10020 /++ 10021 10022 +/ 10023 this(Point innerCenter, float innerRadius, Point outerCenter, float outerRadius, Stop[] stops...) { 10024 super(cast(int)(outerCenter.x + outerRadius + 0.5), cast(int)(outerCenter.y + outerRadius + 0.5)); 10025 10026 version(X11) { 10027 XRadialGradient gradient; 10028 gradient.inner = XCircle(innerCenter.x * ushort.max, innerCenter.y * ushort.max, cast(int) (innerRadius * ushort.max)); 10029 gradient.outer = XCircle(outerCenter.x * ushort.max, outerCenter.y * ushort.max, cast(int) (outerRadius * ushort.max)); 10030 10031 helper(stops, (scope XFixed[] stopsPositions, scope XRenderColor[] colors) { 10032 return XRenderCreateRadialGradient( 10033 XDisplayConnection.get, 10034 &gradient, 10035 stopsPositions.ptr, 10036 colors.ptr, 10037 cast(int) stops.length); 10038 }); 10039 } else version(Windows) { 10040 // FIXME 10041 forEachPixel((int x, int y) { 10042 import core.stdc.math; 10043 10044 //sqrtf( 10045 10046 return Color.transparent; 10047 // return Color(x * 2, y * 2, 0, 128); // this result is so beautiful 10048 }); 10049 } 10050 } 10051 } 10052 10053 10054 10055 /+ 10056 NOT IMPLEMENTED 10057 10058 A display-stored image optimized for relatively quick drawing, like 10059 [Sprite], but this one supports alpha channel blending and does NOT 10060 support direct drawing upon it with a [ScreenPainter]. 10061 10062 You can think of it as an [arsd.game.OpenGlTexture] for usage with a 10063 plain [ScreenPainter]... sort of. 10064 10065 On X11, it requires the Xrender extension and library. This is available 10066 almost everywhere though. 10067 10068 History: 10069 Added November 14, 2020 but NOT ACTUALLY IMPLEMENTED 10070 +/ 10071 version(none) 10072 class AlphaSprite { 10073 /++ 10074 Copies the given image into it. 10075 +/ 10076 this(MemoryImage img) { 10077 10078 if(!XRenderLibrary.loadAttempted) { 10079 XRenderLibrary.loadDynamicLibrary(); 10080 10081 // FIXME: this needs to be reconstructed when the X server changes 10082 repopulateX(); 10083 } 10084 if(!XRenderLibrary.loadSuccessful) 10085 throw new Exception("XRender library load failure"); 10086 10087 // I probably need to put the alpha mask in a separate Picture 10088 // ugh 10089 // maybe the Sprite itself can have an alpha bitmask anyway 10090 10091 10092 auto display = XDisplayConnection.get(); 10093 pixmap = XCreatePixmap(display, cast(Drawable) win.window, width, height, DefaultDepthOfDisplay(display)); 10094 10095 10096 XRenderPictureAttributes attrs; 10097 10098 handle = XRenderCreatePicture( 10099 XDisplayConnection.get, 10100 pixmap, 10101 RGBA, 10102 0, 10103 &attrs 10104 ); 10105 10106 } 10107 10108 // maybe i'll use the create gradient functions too with static factories.. 10109 10110 void drawAt(ScreenPainter painter, Point where) { 10111 //painter.drawPixmap(this, where); 10112 10113 XRenderPictureAttributes attrs; 10114 10115 auto pic = XRenderCreatePicture( 10116 XDisplayConnection.get, 10117 painter.impl.d, 10118 RGB, 10119 0, 10120 &attrs 10121 ); 10122 10123 XRenderComposite( 10124 XDisplayConnection.get, 10125 3, // PictOpOver 10126 handle, 10127 None, 10128 pic, 10129 0, // src 10130 0, 10131 0, // mask 10132 0, 10133 10, // dest 10134 10, 10135 100, // width 10136 100 10137 ); 10138 10139 /+ 10140 XRenderFreePicture( 10141 XDisplayConnection.get, 10142 pic 10143 ); 10144 10145 XRenderFreePicture( 10146 XDisplayConnection.get, 10147 fill 10148 ); 10149 +/ 10150 // on Windows you can stretch but Xrender still can't :( 10151 } 10152 10153 static XRenderPictFormat* RGB; 10154 static XRenderPictFormat* RGBA; 10155 static void repopulateX() { 10156 auto display = XDisplayConnection.get; 10157 RGB = XRenderFindStandardFormat(display, PictStandardRGB24); 10158 RGBA = XRenderFindStandardFormat(display, PictStandardARGB32); 10159 } 10160 10161 XPixmap pixmap; 10162 Picture handle; 10163 } 10164 10165 /// 10166 interface CapableOfBeingDrawnUpon { 10167 /// 10168 ScreenPainter draw(); 10169 /// 10170 int width(); 10171 /// 10172 int height(); 10173 protected ScreenPainterImplementation* activeScreenPainter(); 10174 protected void activeScreenPainter(ScreenPainterImplementation*); 10175 bool closed(); 10176 10177 void delegate() paintingFinishedDg(); 10178 10179 /// Be warned: this can be a very slow operation 10180 TrueColorImage takeScreenshot(); 10181 } 10182 10183 /// 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]. 10184 void flushGui() { 10185 version(X11) { 10186 auto dpy = XDisplayConnection.get(); 10187 XLockDisplay(dpy); 10188 scope(exit) XUnlockDisplay(dpy); 10189 XFlush(dpy); 10190 } 10191 } 10192 10193 /++ 10194 Runs the given code in the GUI thread when its event loop 10195 is available, blocking until it completes. This allows you 10196 to create and manipulate windows from another thread without 10197 invoking undefined behavior. 10198 10199 If this is the gui thread, it runs the code immediately. 10200 10201 If no gui thread exists yet, the current thread is assumed 10202 to be it. Attempting to create windows or run the event loop 10203 in any other thread will cause an assertion failure. 10204 10205 10206 $(TIP 10207 Did you know you can use UFCS on delegate literals? 10208 10209 () { 10210 // code here 10211 }.runInGuiThread; 10212 ) 10213 10214 Returns: 10215 `true` if the function was called, `false` if it was not. 10216 The function may not be called because the gui thread had 10217 already terminated by the time you called this. 10218 10219 History: 10220 Added April 10, 2020 (v7.2.0) 10221 10222 Return value added and implementation tweaked to avoid locking 10223 at program termination on February 24, 2021 (v9.2.1). 10224 +/ 10225 bool runInGuiThread(scope void delegate() dg) @trusted { 10226 claimGuiThread(); 10227 10228 if(thisIsGuiThread) { 10229 dg(); 10230 return true; 10231 } 10232 10233 if(guiThreadTerminating) 10234 return false; 10235 10236 import core.sync.semaphore; 10237 static Semaphore sc; 10238 if(sc is null) 10239 sc = new Semaphore(); 10240 10241 static RunQueueMember* rqm; 10242 if(rqm is null) 10243 rqm = new RunQueueMember; 10244 rqm.dg = cast(typeof(rqm.dg)) dg; 10245 rqm.signal = sc; 10246 rqm.thrown = null; 10247 10248 synchronized(runInGuiThreadLock) { 10249 runInGuiThreadQueue ~= rqm; 10250 } 10251 10252 if(!SimpleWindow.eventWakeUp()) 10253 throw new Error("runInGuiThread impossible; eventWakeUp failed"); 10254 10255 rqm.signal.wait(); 10256 auto t = rqm.thrown; 10257 10258 if(t) 10259 throw t; 10260 10261 return true; 10262 } 10263 10264 // note it runs sync if this is the gui thread.... 10265 void runInGuiThreadAsync(void delegate() dg, void delegate(Exception) nothrow handleError = null) nothrow { 10266 claimGuiThread(); 10267 10268 try { 10269 10270 if(thisIsGuiThread) { 10271 dg(); 10272 return; 10273 } 10274 10275 if(guiThreadTerminating) 10276 return; 10277 10278 RunQueueMember* rqm = new RunQueueMember; 10279 rqm.dg = cast(typeof(rqm.dg)) dg; 10280 rqm.signal = null; 10281 rqm.thrown = null; 10282 10283 synchronized(runInGuiThreadLock) { 10284 runInGuiThreadQueue ~= rqm; 10285 } 10286 10287 if(!SimpleWindow.eventWakeUp()) 10288 throw new Error("runInGuiThread impossible; eventWakeUp failed"); 10289 } catch(Exception e) { 10290 if(handleError) 10291 handleError(e); 10292 } 10293 } 10294 10295 private void runPendingRunInGuiThreadDelegates() { 10296 more: 10297 RunQueueMember* next; 10298 synchronized(runInGuiThreadLock) { 10299 if(runInGuiThreadQueue.length) { 10300 next = runInGuiThreadQueue[0]; 10301 runInGuiThreadQueue = runInGuiThreadQueue[1 .. $]; 10302 } else { 10303 next = null; 10304 } 10305 } 10306 10307 if(next) { 10308 try { 10309 next.dg(); 10310 next.thrown = null; 10311 } catch(Throwable t) { 10312 next.thrown = t; 10313 } 10314 10315 if(next.signal) 10316 next.signal.notify(); 10317 10318 goto more; 10319 } 10320 } 10321 10322 private void claimGuiThread() nothrow { 10323 import core.atomic; 10324 if(cas(&guiThreadExists_, false, true)) 10325 thisIsGuiThread = true; 10326 } 10327 10328 private struct RunQueueMember { 10329 void delegate() dg; 10330 import core.sync.semaphore; 10331 Semaphore signal; 10332 Throwable thrown; 10333 } 10334 10335 private __gshared RunQueueMember*[] runInGuiThreadQueue; 10336 private __gshared Object runInGuiThreadLock = new Object; // intentional CTFE 10337 private bool thisIsGuiThread = false; 10338 private shared bool guiThreadExists_ = false; 10339 private shared bool guiThreadTerminating = false; 10340 10341 /++ 10342 Returns `true` if a gui thread exists, that is, a thread running the simpledisplay.d 10343 event loop. All windows must be exclusively created and managed by a single thread. 10344 10345 If no gui thread exists, simpledisplay.d will automatically adopt the current thread 10346 when you call one of its constructors. 10347 10348 If a gui thread exists, you should check [thisThreadRunningGui] to see if it is this 10349 one. If so, you can run gui functions on it. If not, don't. The helper functions 10350 [runInGuiThread] and [runInGuiThreadAsync] can be used to help you with this automatically. 10351 10352 The reason this function is available is in case you want to message pass between a gui 10353 thread and your current thread. If no gui thread exists or if this is the gui thread, 10354 you're liable to deadlock when trying to communicate since you'd end up talking to yourself. 10355 10356 History: 10357 Added December 3, 2021 (dub v10.5) 10358 +/ 10359 public bool guiThreadExists() { 10360 return guiThreadExists_; 10361 } 10362 10363 /++ 10364 Returns `true` if this thread is either running or set to be running the 10365 simpledisplay.d gui core event loop because it owns windows. 10366 10367 It is important to keep gui-related functionality in the right thread, so you will 10368 want to `runInGuiThread` when you call them (with some specific exceptions called 10369 out in those specific functions' documentation). Notably, all windows must be 10370 created and managed only from the gui thread. 10371 10372 Will return false if simpledisplay's other functions haven't been called 10373 yet; check [guiThreadExists] in addition to this. 10374 10375 History: 10376 Added December 3, 2021 (dub v10.5) 10377 +/ 10378 public bool thisThreadRunningGui() { 10379 return thisIsGuiThread; 10380 } 10381 10382 /++ 10383 Function to help temporarily print debugging info. It will bypass any stdout/err redirection 10384 and go to the controlling tty or console (attaching to the parent and/or allocating one as 10385 needed on Windows. Please note it may overwrite output from other programs in the parent and the 10386 allocated one will not survive if your program crashes. Use the `fileOverride` to print to a log 10387 file instead if you are in one of those situations). 10388 10389 It does not support outputting very many types; just strings and ints are likely to actually work. 10390 10391 It will perform very slowly and swallows any errors that may occur. Moreover, the specific output 10392 is unspecified meaning I can change it at any time. The only point of this function is to help 10393 in temporary use for printf-style debugging. It is NOT nogc, but you can use the `debug` keyword 10394 and the compiler will cheat for you. It is, however, formally nothrow and trusted to ease its use 10395 in those contexts. 10396 10397 $(WARNING 10398 I reserve the right to change this function at any time. You can use it if it helps you 10399 but do not rely on it for anything permanent. 10400 ) 10401 10402 History: 10403 Added December 3, 2021. Not formally supported under any stable tag. 10404 +/ 10405 void sdpyPrintDebugString(string fileOverride = null, T...)(T t) nothrow @trusted { 10406 try { 10407 version(Windows) { 10408 import core.sys.windows.wincon; 10409 if(!AttachConsole(ATTACH_PARENT_PROCESS)) 10410 AllocConsole(); 10411 const(char)* fn = "CONOUT$"; 10412 } else version(Posix) { 10413 const(char)* fn = "/dev/tty"; 10414 } else static assert(0, "Function not implemented for your system"); 10415 10416 if(fileOverride.length) 10417 fn = fileOverride.ptr; 10418 10419 import core.stdc.stdio; 10420 auto fp = fopen(fn, "wt"); 10421 if(fp is null) return; 10422 scope(exit) fclose(fp); 10423 10424 string str; 10425 foreach(item; t) { 10426 static if(is(typeof(item) : const(char)[])) 10427 str ~= item; 10428 else 10429 str ~= toInternal!string(item); 10430 str ~= " "; 10431 } 10432 str ~= "\n"; 10433 10434 fwrite(str.ptr, 1, str.length, fp); 10435 fflush(fp); 10436 } catch(Exception e) { 10437 // sorry no hope 10438 } 10439 } 10440 10441 private void guiThreadFinalize() { 10442 assert(thisIsGuiThread); 10443 10444 guiThreadTerminating = true; // don't add any more from this point on 10445 runPendingRunInGuiThreadDelegates(); 10446 } 10447 10448 /+ 10449 interface IPromise { 10450 void reportProgress(int current, int max, string message); 10451 10452 /+ // not formally in cuz of templates but still 10453 IPromise Then(); 10454 IPromise Catch(); 10455 IPromise Finally(); 10456 +/ 10457 } 10458 10459 /+ 10460 auto promise = async({ ... }); 10461 promise.Then(whatever). 10462 Then(whateverelse). 10463 Catch((exception) { }); 10464 10465 10466 A promise is run inside a fiber and it looks something like: 10467 10468 try { 10469 auto res = whatever(); 10470 auto res2 = whateverelse(res); 10471 } catch(Exception e) { 10472 { }(e); 10473 } 10474 10475 When a thing succeeds, it is passed as an arg to the next 10476 +/ 10477 class Promise(T) : IPromise { 10478 auto Then() { return null; } 10479 auto Catch() { return null; } 10480 auto Finally() { return null; } 10481 10482 // wait for it to resolve and return the value, or rethrow the error if that occurred. 10483 // cannot be called from the gui thread, but this is caught at runtime instead of compile time. 10484 T await(); 10485 } 10486 10487 interface Task { 10488 } 10489 10490 interface Resolvable(T) : Task { 10491 void run(); 10492 10493 void resolve(T); 10494 10495 Resolvable!T then(void delegate(T)); // returns a new promise 10496 Resolvable!T error(Throwable); // js catch 10497 Resolvable!T completed(); // js finally 10498 10499 } 10500 10501 /++ 10502 Runs `work` in a helper thread and sends its return value back to the main gui 10503 thread as the argument to `uponCompletion`. If `work` throws, the exception is 10504 sent to the `uponThrown` if given, or if null, rethrown from the event loop to 10505 kill the program. 10506 10507 You can call reportProgress(position, max, message) to update your parent window 10508 on your progress. 10509 10510 I should also use `shared` methods. FIXME 10511 10512 History: 10513 Added March 6, 2021 (dub version 9.3). 10514 +/ 10515 void runInWorkerThread(T)(T delegate(Task) work, void delegate(T) uponCompletion) { 10516 uponCompletion(work(null)); 10517 } 10518 10519 +/ 10520 10521 /// Used internal to dispatch events to various classes. 10522 interface CapableOfHandlingNativeEvent { 10523 NativeEventHandler getNativeEventHandler(); 10524 10525 /*private*//*protected*/ __gshared CapableOfHandlingNativeEvent[NativeWindowHandle] nativeHandleMapping; 10526 10527 version(X11) { 10528 // if this is impossible, you are allowed to just throw from it 10529 // Note: if you call it from another object, set a flag cuz the manger will call you again 10530 void recreateAfterDisconnect(); 10531 // discard any *connection specific* state, but keep enough that you 10532 // can be recreated if possible. discardConnectionState() is always called immediately 10533 // before recreateAfterDisconnect(), so you can set a flag there to decide if 10534 // you need initialization order 10535 void discardConnectionState(); 10536 } 10537 } 10538 10539 version(X11) 10540 /++ 10541 State of keys on mouse events, especially motion. 10542 10543 Do not trust the actual integer values in this, they are platform-specific. Always use the names. 10544 +/ 10545 enum ModifierState : uint { 10546 shift = 1, /// 10547 capsLock = 2, /// 10548 ctrl = 4, /// 10549 alt = 8, /// Not always available on Windows 10550 windows = 64, /// ditto 10551 numLock = 16, /// 10552 10553 leftButtonDown = 256, /// these aren't available on Windows for key events, so don't use them for that unless your app is X only. 10554 middleButtonDown = 512, /// ditto 10555 rightButtonDown = 1024, /// ditto 10556 } 10557 else version(Windows) 10558 /// ditto 10559 enum ModifierState : uint { 10560 shift = 4, /// 10561 ctrl = 8, /// 10562 10563 // i'm not sure if the next two are available 10564 alt = 256, /// not always available on Windows 10565 windows = 512, /// ditto 10566 10567 capsLock = 1024, /// 10568 numLock = 2048, /// 10569 10570 leftButtonDown = 1, /// not available on key events 10571 middleButtonDown = 16, /// ditto 10572 rightButtonDown = 2, /// ditto 10573 10574 backButtonDown = 0x20, /// not available on X 10575 forwardButtonDown = 0x40, /// ditto 10576 } 10577 else version(OSXCocoa) 10578 // FIXME FIXME NotYetImplementedException 10579 enum ModifierState : uint { 10580 shift = 1, /// 10581 capsLock = 2, /// 10582 ctrl = 4, /// 10583 alt = 8, /// Not always available on Windows 10584 windows = 64, /// ditto 10585 numLock = 16, /// 10586 10587 leftButtonDown = 256, /// these aren't available on Windows for key events, so don't use them for that unless your app is X only. 10588 middleButtonDown = 512, /// ditto 10589 rightButtonDown = 1024, /// ditto 10590 } 10591 10592 /// The names assume a right-handed mouse. These are bitwise combined on the events that use them. 10593 enum MouseButton : int { 10594 none = 0, 10595 left = 1, /// 10596 right = 2, /// 10597 middle = 4, /// 10598 wheelUp = 8, /// 10599 wheelDown = 16, /// 10600 backButton = 32, /// often found on the thumb and used for back in browsers 10601 forwardButton = 64, /// often found on the thumb and used for forward in browsers 10602 } 10603 10604 /// Corresponds to the values found in MouseEvent.buttonLinear, being equal to `core.bitop.bsf(button) + 1` 10605 enum MouseButtonLinear : ubyte { 10606 left = 1, /// 10607 right, /// 10608 middle, /// 10609 wheelUp, /// 10610 wheelDown, /// 10611 backButton, /// often found on the thumb and used for back in browsers 10612 forwardButton, /// often found on the thumb and used for forward in browsers 10613 } 10614 10615 version(X11) { 10616 // FIXME: match ASCII whenever we can. Most of it is already there, 10617 // but there's a few exceptions and mismatches with Windows 10618 10619 /// Do not trust the numeric values as they are platform-specific. Always use the symbolic name. 10620 enum Key { 10621 Escape = 0xff1b, /// 10622 F1 = 0xffbe, /// 10623 F2 = 0xffbf, /// 10624 F3 = 0xffc0, /// 10625 F4 = 0xffc1, /// 10626 F5 = 0xffc2, /// 10627 F6 = 0xffc3, /// 10628 F7 = 0xffc4, /// 10629 F8 = 0xffc5, /// 10630 F9 = 0xffc6, /// 10631 F10 = 0xffc7, /// 10632 F11 = 0xffc8, /// 10633 F12 = 0xffc9, /// 10634 PrintScreen = 0xff61, /// 10635 ScrollLock = 0xff14, /// 10636 Pause = 0xff13, /// 10637 Grave = 0x60, /// The $(BACKTICK) ~ key 10638 // number keys across the top of the keyboard 10639 N1 = 0x31, /// Number key atop the keyboard 10640 N2 = 0x32, /// 10641 N3 = 0x33, /// 10642 N4 = 0x34, /// 10643 N5 = 0x35, /// 10644 N6 = 0x36, /// 10645 N7 = 0x37, /// 10646 N8 = 0x38, /// 10647 N9 = 0x39, /// 10648 N0 = 0x30, /// 10649 Dash = 0x2d, /// 10650 Equals = 0x3d, /// 10651 Backslash = 0x5c, /// The \ | key 10652 Backspace = 0xff08, /// 10653 Insert = 0xff63, /// 10654 Home = 0xff50, /// 10655 PageUp = 0xff55, /// 10656 Delete = 0xffff, /// 10657 End = 0xff57, /// 10658 PageDown = 0xff56, /// 10659 Up = 0xff52, /// 10660 Down = 0xff54, /// 10661 Left = 0xff51, /// 10662 Right = 0xff53, /// 10663 10664 Tab = 0xff09, /// 10665 Q = 0x71, /// 10666 W = 0x77, /// 10667 E = 0x65, /// 10668 R = 0x72, /// 10669 T = 0x74, /// 10670 Y = 0x79, /// 10671 U = 0x75, /// 10672 I = 0x69, /// 10673 O = 0x6f, /// 10674 P = 0x70, /// 10675 LeftBracket = 0x5b, /// the [ { key 10676 RightBracket = 0x5d, /// the ] } key 10677 CapsLock = 0xffe5, /// 10678 A = 0x61, /// 10679 S = 0x73, /// 10680 D = 0x64, /// 10681 F = 0x66, /// 10682 G = 0x67, /// 10683 H = 0x68, /// 10684 J = 0x6a, /// 10685 K = 0x6b, /// 10686 L = 0x6c, /// 10687 Semicolon = 0x3b, /// 10688 Apostrophe = 0x27, /// 10689 Enter = 0xff0d, /// 10690 Shift = 0xffe1, /// 10691 Z = 0x7a, /// 10692 X = 0x78, /// 10693 C = 0x63, /// 10694 V = 0x76, /// 10695 B = 0x62, /// 10696 N = 0x6e, /// 10697 M = 0x6d, /// 10698 Comma = 0x2c, /// 10699 Period = 0x2e, /// 10700 Slash = 0x2f, /// the / ? key 10701 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 10702 Ctrl = 0xffe3, /// 10703 Windows = 0xffeb, /// 10704 Alt = 0xffe9, /// 10705 Space = 0x20, /// 10706 Alt_r = 0xffea, /// ditto of shift_r 10707 Windows_r = 0xffec, /// 10708 Menu = 0xff67, /// 10709 Ctrl_r = 0xffe4, /// 10710 10711 NumLock = 0xff7f, /// 10712 Divide = 0xffaf, /// The / key on the number pad 10713 Multiply = 0xffaa, /// The * key on the number pad 10714 Minus = 0xffad, /// The - key on the number pad 10715 Plus = 0xffab, /// The + key on the number pad 10716 PadEnter = 0xff8d, /// Numberpad enter key 10717 Pad1 = 0xff9c, /// Numberpad keys 10718 Pad2 = 0xff99, /// 10719 Pad3 = 0xff9b, /// 10720 Pad4 = 0xff96, /// 10721 Pad5 = 0xff9d, /// 10722 Pad6 = 0xff98, /// 10723 Pad7 = 0xff95, /// 10724 Pad8 = 0xff97, /// 10725 Pad9 = 0xff9a, /// 10726 Pad0 = 0xff9e, /// 10727 PadDot = 0xff9f, /// 10728 } 10729 } else version(Windows) { 10730 // the character here is for en-us layouts and for illustration only 10731 // if you actually want to get characters, wait for character events 10732 // (the argument to your event handler is simply a dchar) 10733 // those will be converted by the OS for the right locale. 10734 10735 enum Key { 10736 Escape = 0x1b, 10737 F1 = 0x70, 10738 F2 = 0x71, 10739 F3 = 0x72, 10740 F4 = 0x73, 10741 F5 = 0x74, 10742 F6 = 0x75, 10743 F7 = 0x76, 10744 F8 = 0x77, 10745 F9 = 0x78, 10746 F10 = 0x79, 10747 F11 = 0x7a, 10748 F12 = 0x7b, 10749 PrintScreen = 0x2c, 10750 ScrollLock = 0x91, 10751 Pause = 0x13, 10752 Grave = 0xc0, 10753 // number keys across the top of the keyboard 10754 N1 = 0x31, 10755 N2 = 0x32, 10756 N3 = 0x33, 10757 N4 = 0x34, 10758 N5 = 0x35, 10759 N6 = 0x36, 10760 N7 = 0x37, 10761 N8 = 0x38, 10762 N9 = 0x39, 10763 N0 = 0x30, 10764 Dash = 0xbd, 10765 Equals = 0xbb, 10766 Backslash = 0xdc, 10767 Backspace = 0x08, 10768 Insert = 0x2d, 10769 Home = 0x24, 10770 PageUp = 0x21, 10771 Delete = 0x2e, 10772 End = 0x23, 10773 PageDown = 0x22, 10774 Up = 0x26, 10775 Down = 0x28, 10776 Left = 0x25, 10777 Right = 0x27, 10778 10779 Tab = 0x09, 10780 Q = 0x51, 10781 W = 0x57, 10782 E = 0x45, 10783 R = 0x52, 10784 T = 0x54, 10785 Y = 0x59, 10786 U = 0x55, 10787 I = 0x49, 10788 O = 0x4f, 10789 P = 0x50, 10790 LeftBracket = 0xdb, 10791 RightBracket = 0xdd, 10792 CapsLock = 0x14, 10793 A = 0x41, 10794 S = 0x53, 10795 D = 0x44, 10796 F = 0x46, 10797 G = 0x47, 10798 H = 0x48, 10799 J = 0x4a, 10800 K = 0x4b, 10801 L = 0x4c, 10802 Semicolon = 0xba, 10803 Apostrophe = 0xde, 10804 Enter = 0x0d, 10805 Shift = 0x10, 10806 Z = 0x5a, 10807 X = 0x58, 10808 C = 0x43, 10809 V = 0x56, 10810 B = 0x42, 10811 N = 0x4e, 10812 M = 0x4d, 10813 Comma = 0xbc, 10814 Period = 0xbe, 10815 Slash = 0xbf, 10816 Shift_r = 0xa1, // FIXME Note: this isn't sent on all computers, sometimes it just sends Shift, so don't rely on it 10817 Ctrl = 0x11, 10818 Windows = 0x5b, 10819 Alt = -5, // FIXME 10820 Space = 0x20, 10821 Alt_r = 0xffea, // ditto of shift_r 10822 Windows_r = 0x5c, // ditto of shift_r 10823 Menu = 0x5d, 10824 Ctrl_r = 0xa3, // ditto of shift_r 10825 10826 NumLock = 0x90, 10827 Divide = 0x6f, 10828 Multiply = 0x6a, 10829 Minus = 0x6d, 10830 Plus = 0x6b, 10831 PadEnter = -8, // FIXME 10832 Pad1 = 0x61, 10833 Pad2 = 0x62, 10834 Pad3 = 0x63, 10835 Pad4 = 0x64, 10836 Pad5 = 0x65, 10837 Pad6 = 0x66, 10838 Pad7 = 0x67, 10839 Pad8 = 0x68, 10840 Pad9 = 0x69, 10841 Pad0 = 0x60, 10842 PadDot = 0x6e, 10843 } 10844 10845 // I'm keeping this around for reference purposes 10846 // ideally all these buttons will be listed for all platforms, 10847 // but now now I'm just focusing on my US keyboard 10848 version(none) 10849 enum Key { 10850 LBUTTON = 0x01, 10851 RBUTTON = 0x02, 10852 CANCEL = 0x03, 10853 MBUTTON = 0x04, 10854 //static if (_WIN32_WINNT > = 0x500) { 10855 XBUTTON1 = 0x05, 10856 XBUTTON2 = 0x06, 10857 //} 10858 BACK = 0x08, 10859 TAB = 0x09, 10860 CLEAR = 0x0C, 10861 RETURN = 0x0D, 10862 SHIFT = 0x10, 10863 CONTROL = 0x11, 10864 MENU = 0x12, 10865 PAUSE = 0x13, 10866 CAPITAL = 0x14, 10867 KANA = 0x15, 10868 HANGEUL = 0x15, 10869 HANGUL = 0x15, 10870 JUNJA = 0x17, 10871 FINAL = 0x18, 10872 HANJA = 0x19, 10873 KANJI = 0x19, 10874 ESCAPE = 0x1B, 10875 CONVERT = 0x1C, 10876 NONCONVERT = 0x1D, 10877 ACCEPT = 0x1E, 10878 MODECHANGE = 0x1F, 10879 SPACE = 0x20, 10880 PRIOR = 0x21, 10881 NEXT = 0x22, 10882 END = 0x23, 10883 HOME = 0x24, 10884 LEFT = 0x25, 10885 UP = 0x26, 10886 RIGHT = 0x27, 10887 DOWN = 0x28, 10888 SELECT = 0x29, 10889 PRINT = 0x2A, 10890 EXECUTE = 0x2B, 10891 SNAPSHOT = 0x2C, 10892 INSERT = 0x2D, 10893 DELETE = 0x2E, 10894 HELP = 0x2F, 10895 LWIN = 0x5B, 10896 RWIN = 0x5C, 10897 APPS = 0x5D, 10898 SLEEP = 0x5F, 10899 NUMPAD0 = 0x60, 10900 NUMPAD1 = 0x61, 10901 NUMPAD2 = 0x62, 10902 NUMPAD3 = 0x63, 10903 NUMPAD4 = 0x64, 10904 NUMPAD5 = 0x65, 10905 NUMPAD6 = 0x66, 10906 NUMPAD7 = 0x67, 10907 NUMPAD8 = 0x68, 10908 NUMPAD9 = 0x69, 10909 MULTIPLY = 0x6A, 10910 ADD = 0x6B, 10911 SEPARATOR = 0x6C, 10912 SUBTRACT = 0x6D, 10913 DECIMAL = 0x6E, 10914 DIVIDE = 0x6F, 10915 F1 = 0x70, 10916 F2 = 0x71, 10917 F3 = 0x72, 10918 F4 = 0x73, 10919 F5 = 0x74, 10920 F6 = 0x75, 10921 F7 = 0x76, 10922 F8 = 0x77, 10923 F9 = 0x78, 10924 F10 = 0x79, 10925 F11 = 0x7A, 10926 F12 = 0x7B, 10927 F13 = 0x7C, 10928 F14 = 0x7D, 10929 F15 = 0x7E, 10930 F16 = 0x7F, 10931 F17 = 0x80, 10932 F18 = 0x81, 10933 F19 = 0x82, 10934 F20 = 0x83, 10935 F21 = 0x84, 10936 F22 = 0x85, 10937 F23 = 0x86, 10938 F24 = 0x87, 10939 NUMLOCK = 0x90, 10940 SCROLL = 0x91, 10941 LSHIFT = 0xA0, 10942 RSHIFT = 0xA1, 10943 LCONTROL = 0xA2, 10944 RCONTROL = 0xA3, 10945 LMENU = 0xA4, 10946 RMENU = 0xA5, 10947 //static if (_WIN32_WINNT > = 0x500) { 10948 BROWSER_BACK = 0xA6, 10949 BROWSER_FORWARD = 0xA7, 10950 BROWSER_REFRESH = 0xA8, 10951 BROWSER_STOP = 0xA9, 10952 BROWSER_SEARCH = 0xAA, 10953 BROWSER_FAVORITES = 0xAB, 10954 BROWSER_HOME = 0xAC, 10955 VOLUME_MUTE = 0xAD, 10956 VOLUME_DOWN = 0xAE, 10957 VOLUME_UP = 0xAF, 10958 MEDIA_NEXT_TRACK = 0xB0, 10959 MEDIA_PREV_TRACK = 0xB1, 10960 MEDIA_STOP = 0xB2, 10961 MEDIA_PLAY_PAUSE = 0xB3, 10962 LAUNCH_MAIL = 0xB4, 10963 LAUNCH_MEDIA_SELECT = 0xB5, 10964 LAUNCH_APP1 = 0xB6, 10965 LAUNCH_APP2 = 0xB7, 10966 //} 10967 OEM_1 = 0xBA, 10968 //static if (_WIN32_WINNT > = 0x500) { 10969 OEM_PLUS = 0xBB, 10970 OEM_COMMA = 0xBC, 10971 OEM_MINUS = 0xBD, 10972 OEM_PERIOD = 0xBE, 10973 //} 10974 OEM_2 = 0xBF, 10975 OEM_3 = 0xC0, 10976 OEM_4 = 0xDB, 10977 OEM_5 = 0xDC, 10978 OEM_6 = 0xDD, 10979 OEM_7 = 0xDE, 10980 OEM_8 = 0xDF, 10981 //static if (_WIN32_WINNT > = 0x500) { 10982 OEM_102 = 0xE2, 10983 //} 10984 PROCESSKEY = 0xE5, 10985 //static if (_WIN32_WINNT > = 0x500) { 10986 PACKET = 0xE7, 10987 //} 10988 ATTN = 0xF6, 10989 CRSEL = 0xF7, 10990 EXSEL = 0xF8, 10991 EREOF = 0xF9, 10992 PLAY = 0xFA, 10993 ZOOM = 0xFB, 10994 NONAME = 0xFC, 10995 PA1 = 0xFD, 10996 OEM_CLEAR = 0xFE, 10997 } 10998 10999 } else version(OSXCocoa) { 11000 enum Key { 11001 Escape = 53, 11002 F1 = 122, 11003 F2 = 120, 11004 F3 = 99, 11005 F4 = 118, 11006 F5 = 96, 11007 F6 = 97, 11008 F7 = 98, 11009 F8 = 100, 11010 F9 = 101, 11011 F10 = 109, 11012 F11 = 103, 11013 F12 = 111, 11014 PrintScreen = 105, 11015 ScrollLock = 107, 11016 Pause = 113, 11017 Grave = 50, 11018 // number keys across the top of the keyboard 11019 N1 = 18, 11020 N2 = 19, 11021 N3 = 20, 11022 N4 = 21, 11023 N5 = 23, 11024 N6 = 22, 11025 N7 = 26, 11026 N8 = 28, 11027 N9 = 25, 11028 N0 = 29, 11029 Dash = 27, 11030 Equals = 24, 11031 Backslash = 42, 11032 Backspace = 51, 11033 Insert = 114, 11034 Home = 115, 11035 PageUp = 116, 11036 Delete = 117, 11037 End = 119, 11038 PageDown = 121, 11039 Up = 126, 11040 Down = 125, 11041 Left = 123, 11042 Right = 124, 11043 11044 Tab = 48, 11045 Q = 12, 11046 W = 13, 11047 E = 14, 11048 R = 15, 11049 T = 17, 11050 Y = 16, 11051 U = 32, 11052 I = 34, 11053 O = 31, 11054 P = 35, 11055 LeftBracket = 33, 11056 RightBracket = 30, 11057 CapsLock = 57, 11058 A = 0, 11059 S = 1, 11060 D = 2, 11061 F = 3, 11062 G = 5, 11063 H = 4, 11064 J = 38, 11065 K = 40, 11066 L = 37, 11067 Semicolon = 41, 11068 Apostrophe = 39, 11069 Enter = 36, 11070 Shift = 56, 11071 Z = 6, 11072 X = 7, 11073 C = 8, 11074 V = 9, 11075 B = 11, 11076 N = 45, 11077 M = 46, 11078 Comma = 43, 11079 Period = 47, 11080 Slash = 44, 11081 Shift_r = -4, // FIXME Note: this isn't sent on all computers, sometimes it just sends Shift, so don't rely on it 11082 Ctrl = 59, 11083 Windows = 55, 11084 Alt = 58, 11085 Space = 49, 11086 Alt_r = -3, // ditto of shift_r 11087 Windows_r = -2, 11088 Menu = 110, 11089 Ctrl_r = -1, 11090 11091 NumLock = 1, 11092 Divide = 75, 11093 Multiply = 67, 11094 Minus = 78, 11095 Plus = 69, 11096 PadEnter = 76, 11097 Pad1 = 83, 11098 Pad2 = 84, 11099 Pad3 = 85, 11100 Pad4 = 86, 11101 Pad5 = 87, 11102 Pad6 = 88, 11103 Pad7 = 89, 11104 Pad8 = 91, 11105 Pad9 = 92, 11106 Pad0 = 82, 11107 PadDot = 65, 11108 } 11109 11110 } 11111 11112 /* Additional utilities */ 11113 11114 11115 Color fromHsl(real h, real s, real l) { 11116 return arsd.color.fromHsl([h,s,l]); 11117 } 11118 11119 11120 11121 /* ********** What follows is the system-specific implementations *********/ 11122 version(Windows) { 11123 11124 11125 // helpers for making HICONs from MemoryImages 11126 class WindowsIcon { 11127 struct Win32Icon { 11128 align(1): 11129 uint biSize; 11130 int biWidth; 11131 int biHeight; 11132 ushort biPlanes; 11133 ushort biBitCount; 11134 uint biCompression; 11135 uint biSizeImage; 11136 int biXPelsPerMeter; 11137 int biYPelsPerMeter; 11138 uint biClrUsed; 11139 uint biClrImportant; 11140 // RGBQUAD[colorCount] biColors; 11141 /* Pixels: 11142 Uint8 pixels[] 11143 */ 11144 /* Mask: 11145 Uint8 mask[] 11146 */ 11147 } 11148 11149 ubyte[] fromMemoryImage(MemoryImage mi, out int icon_len, out int width, out int height) { 11150 11151 assert(mi.width <= 256, "image too wide"); 11152 assert(mi.height <= 256, "image too tall"); 11153 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 11154 assert(mi.height % 4 == 0, "image not multiple of 4 height"); 11155 11156 int icon_plen = mi.width * mi.height * 4; 11157 int icon_mlen = mi.width * mi.height / 8; 11158 11159 int colorCount = 0; 11160 icon_len = 40 + icon_plen + icon_mlen + cast(int) RGBQUAD.sizeof * colorCount; 11161 11162 ubyte[] memory = new ubyte[](Win32Icon.sizeof + icon_plen + icon_mlen); 11163 Win32Icon* icon_win32 = cast(Win32Icon*) memory.ptr; 11164 11165 auto data = memory[Win32Icon.sizeof .. $]; 11166 11167 width = mi.width; 11168 height = mi.height; 11169 11170 auto trueColorImage = mi.getAsTrueColorImage(); 11171 11172 icon_win32.biSize = 40; 11173 icon_win32.biWidth = mi.width; 11174 icon_win32.biHeight = mi.height*2; 11175 icon_win32.biPlanes = 1; 11176 icon_win32.biBitCount = 32; 11177 icon_win32.biSizeImage = icon_plen + icon_mlen; 11178 11179 int offset = 0; 11180 int andOff = icon_plen * 8; // the and offset is in bits 11181 11182 // leaving the and mask as the default 0 so the rgba alpha blend 11183 // does its thing instead 11184 for(int y = height - 1; y >= 0; y--) { 11185 int off2 = y * width * 4; 11186 foreach(x; 0 .. width) { 11187 data[offset + 2] = trueColorImage.imageData.bytes[off2 + 0]; 11188 data[offset + 1] = trueColorImage.imageData.bytes[off2 + 1]; 11189 data[offset + 0] = trueColorImage.imageData.bytes[off2 + 2]; 11190 data[offset + 3] = trueColorImage.imageData.bytes[off2 + 3]; 11191 11192 offset += 4; 11193 off2 += 4; 11194 } 11195 } 11196 11197 return memory; 11198 } 11199 11200 this(MemoryImage mi) { 11201 int icon_len, width, height; 11202 11203 auto icon_win32 = fromMemoryImage(mi, icon_len, width, height); 11204 11205 /* 11206 PNG* png = readPnpngData); 11207 PNGHeader pngh = getHeader(png); 11208 void* icon_win32; 11209 if(pngh.depth == 4) { 11210 auto i = new Win32Icon!(16); 11211 i.fromPNG(png, pngh, icon_len, width, height); 11212 icon_win32 = i; 11213 } 11214 else if(pngh.depth == 8) { 11215 auto i = new Win32Icon!(256); 11216 i.fromPNG(png, pngh, icon_len, width, height); 11217 icon_win32 = i; 11218 } else assert(0); 11219 */ 11220 11221 hIcon = CreateIconFromResourceEx(icon_win32.ptr, icon_len, true, 0x00030000, width, height, 0); 11222 11223 if(hIcon is null) throw new WindowsApiException("CreateIconFromResourceEx", GetLastError()); 11224 } 11225 11226 ~this() { 11227 if(!thisIsGuiThread) return; // FIXME: leaks if multithreaded gc 11228 DestroyIcon(hIcon); 11229 } 11230 11231 HICON hIcon; 11232 } 11233 11234 11235 11236 11237 11238 11239 alias int delegate(HWND, UINT, WPARAM, LPARAM, out int) NativeEventHandler; 11240 alias HWND NativeWindowHandle; 11241 11242 extern(Windows) 11243 LRESULT WndProc(HWND hWnd, UINT iMessage, WPARAM wParam, LPARAM lParam) nothrow { 11244 try { 11245 if(SimpleWindow.handleNativeGlobalEvent !is null) { 11246 // it returns zero if the message is handled, so we won't do anything more there 11247 // do I like that though? 11248 int mustReturn; 11249 auto ret = SimpleWindow.handleNativeGlobalEvent(hWnd, iMessage, wParam, lParam, mustReturn); 11250 if(mustReturn) 11251 return ret; 11252 } 11253 11254 if(auto window = hWnd in CapableOfHandlingNativeEvent.nativeHandleMapping) { 11255 if(window.getNativeEventHandler !is null) { 11256 int mustReturn; 11257 auto ret = window.getNativeEventHandler()(hWnd, iMessage, wParam, lParam, mustReturn); 11258 if(mustReturn) 11259 return ret; 11260 } 11261 if(auto w = cast(SimpleWindow) (*window)) 11262 return w.windowProcedure(hWnd, iMessage, wParam, lParam); 11263 else 11264 return DefWindowProc(hWnd, iMessage, wParam, lParam); 11265 } else { 11266 return DefWindowProc(hWnd, iMessage, wParam, lParam); 11267 } 11268 } catch (Exception e) { 11269 try { 11270 sdpy_abort(e); 11271 return 0; 11272 } catch(Exception e) { assert(0); } 11273 } 11274 } 11275 11276 void sdpy_abort(Throwable e) nothrow { 11277 try 11278 MessageBoxA(null, (e.toString() ~ "\0").ptr, "Exception caught in WndProc", 0); 11279 catch(Exception e) 11280 MessageBoxA(null, "Exception.toString threw too!", "Exception caught in WndProc", 0); 11281 ExitProcess(1); 11282 } 11283 11284 mixin template NativeScreenPainterImplementation() { 11285 HDC hdc; 11286 HWND hwnd; 11287 //HDC windowHdc; 11288 HBITMAP oldBmp; 11289 11290 void create(PaintingHandle window) { 11291 hwnd = window; 11292 11293 if(auto sw = cast(SimpleWindow) this.window) { 11294 // drawing on a window, double buffer 11295 auto windowHdc = GetDC(hwnd); 11296 11297 auto buffer = sw.impl.buffer; 11298 if(buffer is null) { 11299 hdc = windowHdc; 11300 windowDc = true; 11301 } else { 11302 hdc = CreateCompatibleDC(windowHdc); 11303 11304 ReleaseDC(hwnd, windowHdc); 11305 11306 oldBmp = SelectObject(hdc, buffer); 11307 } 11308 } else { 11309 // drawing on something else, draw directly 11310 hdc = CreateCompatibleDC(null); 11311 SelectObject(hdc, window); 11312 } 11313 11314 // X doesn't draw a text background, so neither should we 11315 SetBkMode(hdc, TRANSPARENT); 11316 11317 ensureDefaultFontLoaded(); 11318 11319 if(defaultGuiFont) { 11320 SelectObject(hdc, defaultGuiFont); 11321 // DeleteObject(defaultGuiFont); 11322 } 11323 } 11324 11325 static HFONT defaultGuiFont; 11326 static void ensureDefaultFontLoaded() { 11327 static bool triedDefaultGuiFont = false; 11328 if(!triedDefaultGuiFont) { 11329 NONCLIENTMETRICS params; 11330 params.cbSize = params.sizeof; 11331 if(SystemParametersInfo(SPI_GETNONCLIENTMETRICS, params.sizeof, ¶ms, 0)) { 11332 defaultGuiFont = CreateFontIndirect(¶ms.lfMessageFont); 11333 } 11334 triedDefaultGuiFont = true; 11335 } 11336 } 11337 11338 private OperatingSystemFont _activeFont; 11339 11340 void setFont(OperatingSystemFont font) { 11341 _activeFont = font; 11342 if(font && font.font) { 11343 if(SelectObject(hdc, font.font) == HGDI_ERROR) { 11344 // error... how to handle tho? 11345 } else { 11346 11347 } 11348 } 11349 else if(defaultGuiFont) 11350 SelectObject(hdc, defaultGuiFont); 11351 } 11352 11353 arsd.color.Rectangle _clipRectangle; 11354 11355 void setClipRectangle(int x, int y, int width, int height) { 11356 auto old = _clipRectangle; 11357 _clipRectangle = arsd.color.Rectangle(Point(x, y), Size(width, height)); 11358 if(old == _clipRectangle) 11359 return; 11360 11361 if(width == 0 || height == 0) { 11362 SelectClipRgn(hdc, null); 11363 } else { 11364 auto region = CreateRectRgn(x, y, x + width, y + height); 11365 SelectClipRgn(hdc, region); 11366 DeleteObject(region); 11367 } 11368 } 11369 11370 11371 // just because we can on Windows... 11372 //void create(Image image); 11373 11374 void invalidateRect(Rectangle invalidRect) { 11375 RECT rect; 11376 rect.left = invalidRect.left; 11377 rect.right = invalidRect.right; 11378 rect.top = invalidRect.top; 11379 rect.bottom = invalidRect.bottom; 11380 InvalidateRect(hwnd, &rect, false); 11381 } 11382 bool manualInvalidations; 11383 11384 void dispose() { 11385 // FIXME: this.window.width/height is probably wrong 11386 // BitBlt(windowHdc, 0, 0, this.window.width, this.window.height, hdc, 0, 0, SRCCOPY); 11387 // ReleaseDC(hwnd, windowHdc); 11388 11389 // FIXME: it shouldn't invalidate the whole thing in all cases... it would be ideal to do this right 11390 if(cast(SimpleWindow) this.window) { 11391 if(!manualInvalidations) 11392 InvalidateRect(hwnd, cast(RECT*)null, false); // no need to erase bg as the whole thing gets bitblt'd ove 11393 } 11394 11395 if(originalPen !is null) 11396 SelectObject(hdc, originalPen); 11397 if(currentPen !is null) 11398 DeleteObject(currentPen); 11399 if(originalBrush !is null) 11400 SelectObject(hdc, originalBrush); 11401 if(currentBrush !is null) 11402 DeleteObject(currentBrush); 11403 11404 SelectObject(hdc, oldBmp); 11405 11406 if(windowDc) 11407 ReleaseDC(hwnd, hdc); 11408 else 11409 DeleteDC(hdc); 11410 11411 if(window.paintingFinishedDg !is null) 11412 window.paintingFinishedDg()(); 11413 } 11414 11415 bool windowDc; 11416 HPEN originalPen; 11417 HPEN currentPen; 11418 11419 Pen _activePen; 11420 11421 Color _outlineColor; 11422 11423 @property void pen(Pen p) { 11424 _activePen = p; 11425 _outlineColor = p.color; 11426 11427 HPEN pen; 11428 if(p.color.a == 0) { 11429 pen = GetStockObject(NULL_PEN); 11430 } else { 11431 int style = PS_SOLID; 11432 final switch(p.style) { 11433 case Pen.Style.Solid: 11434 style = PS_SOLID; 11435 break; 11436 case Pen.Style.Dashed: 11437 style = PS_DASH; 11438 break; 11439 case Pen.Style.Dotted: 11440 style = PS_DOT; 11441 break; 11442 } 11443 pen = CreatePen(style, p.width, RGB(p.color.r, p.color.g, p.color.b)); 11444 } 11445 auto orig = SelectObject(hdc, pen); 11446 if(originalPen is null) 11447 originalPen = orig; 11448 11449 if(currentPen !is null) 11450 DeleteObject(currentPen); 11451 11452 currentPen = pen; 11453 11454 // the outline is like a foreground since it's done that way on X 11455 SetTextColor(hdc, RGB(p.color.r, p.color.g, p.color.b)); 11456 11457 } 11458 11459 @property void rasterOp(RasterOp op) { 11460 int mode; 11461 final switch(op) { 11462 case RasterOp.normal: 11463 mode = R2_COPYPEN; 11464 break; 11465 case RasterOp.xor: 11466 mode = R2_XORPEN; 11467 break; 11468 } 11469 SetROP2(hdc, mode); 11470 } 11471 11472 HBRUSH originalBrush; 11473 HBRUSH currentBrush; 11474 Color _fillColor = Color(1, 1, 1, 1); // what are the odds that they'd set this?? 11475 @property void fillColor(Color c) { 11476 if(c == _fillColor) 11477 return; 11478 _fillColor = c; 11479 HBRUSH brush; 11480 if(c.a == 0) { 11481 brush = GetStockObject(HOLLOW_BRUSH); 11482 } else { 11483 brush = CreateSolidBrush(RGB(c.r, c.g, c.b)); 11484 } 11485 auto orig = SelectObject(hdc, brush); 11486 if(originalBrush is null) 11487 originalBrush = orig; 11488 11489 if(currentBrush !is null) 11490 DeleteObject(currentBrush); 11491 11492 currentBrush = brush; 11493 11494 // background color is NOT set because X doesn't draw text backgrounds 11495 // SetBkColor(hdc, RGB(255, 255, 255)); 11496 } 11497 11498 void drawImage(int x, int y, Image i, int ix, int iy, int w, int h) { 11499 BITMAP bm; 11500 11501 HDC hdcMem = CreateCompatibleDC(hdc); 11502 HBITMAP hbmOld = SelectObject(hdcMem, i.handle); 11503 11504 GetObject(i.handle, bm.sizeof, &bm); 11505 11506 // or should I AlphaBlend!??!?! 11507 BitBlt(hdc, x, y, w /* bm.bmWidth */, /*bm.bmHeight*/ h, hdcMem, ix, iy, SRCCOPY); 11508 11509 SelectObject(hdcMem, hbmOld); 11510 DeleteDC(hdcMem); 11511 } 11512 11513 void drawPixmap(Sprite s, int x, int y, int ix, int iy, int w, int h) { 11514 BITMAP bm; 11515 11516 HDC hdcMem = CreateCompatibleDC(hdc); 11517 HBITMAP hbmOld = SelectObject(hdcMem, s.handle); 11518 11519 GetObject(s.handle, bm.sizeof, &bm); 11520 11521 version(CRuntime_DigitalMars) goto noalpha; 11522 11523 // or should I AlphaBlend!??!?! note it is supposed to be premultiplied http://www.fengyuan.com/article/alphablend.html 11524 if(s.enableAlpha) { 11525 auto dw = w ? w : bm.bmWidth; 11526 auto dh = h ? h : bm.bmHeight; 11527 BLENDFUNCTION bf; 11528 bf.BlendOp = AC_SRC_OVER; 11529 bf.SourceConstantAlpha = 255; 11530 bf.AlphaFormat = AC_SRC_ALPHA; 11531 AlphaBlend(hdc, x, y, dw, dh, hdcMem, ix, iy, dw, dh, bf); 11532 } else { 11533 noalpha: 11534 BitBlt(hdc, x, y, w ? w : bm.bmWidth, h ? h : bm.bmHeight, hdcMem, ix, iy, SRCCOPY); 11535 } 11536 11537 SelectObject(hdcMem, hbmOld); 11538 DeleteDC(hdcMem); 11539 } 11540 11541 Size textSize(scope const(char)[] text) { 11542 bool dummyX; 11543 if(text.length == 0) { 11544 text = " "; 11545 dummyX = true; 11546 } 11547 RECT rect; 11548 WCharzBuffer buffer = WCharzBuffer(text); 11549 DrawTextW(hdc, buffer.ptr, cast(int) buffer.length, &rect, DT_CALCRECT | DT_NOPREFIX); 11550 return Size(dummyX ? 0 : rect.right, rect.bottom); 11551 } 11552 11553 void drawText(int x, int y, int x2, int y2, scope const(char)[] text, uint alignment) { 11554 if(text.length && text[$-1] == '\n') 11555 text = text[0 .. $-1]; // tailing newlines are weird on windows... 11556 if(text.length && text[$-1] == '\r') 11557 text = text[0 .. $-1]; 11558 11559 WCharzBuffer buffer = WCharzBuffer(text, WindowsStringConversionFlags.convertNewLines); 11560 if(x2 == 0 && y2 == 0) { 11561 TextOutW(hdc, x, y, buffer.ptr, cast(int) buffer.length); 11562 } else { 11563 RECT rect; 11564 rect.left = x; 11565 rect.top = y; 11566 rect.right = x2; 11567 rect.bottom = y2; 11568 11569 uint mode = DT_LEFT; 11570 if(alignment & TextAlignment.Right) 11571 mode = DT_RIGHT; 11572 else if(alignment & TextAlignment.Center) 11573 mode = DT_CENTER; 11574 11575 // FIXME: vcenter on windows only works with single line, but I want it to work in all cases 11576 if(alignment & TextAlignment.VerticalCenter) 11577 mode |= DT_VCENTER | DT_SINGLELINE; 11578 11579 DrawTextW(hdc, buffer.ptr, cast(int) buffer.length, &rect, mode | DT_NOPREFIX); 11580 } 11581 11582 /* 11583 uint mode; 11584 11585 if(alignment & TextAlignment.Center) 11586 mode = TA_CENTER; 11587 11588 SetTextAlign(hdc, mode); 11589 */ 11590 } 11591 11592 int fontHeight() { 11593 TEXTMETRIC metric; 11594 if(GetTextMetricsW(hdc, &metric)) { 11595 return metric.tmHeight; 11596 } 11597 11598 return 16; // idk just guessing here, maybe we should throw 11599 } 11600 11601 void drawPixel(int x, int y) { 11602 SetPixel(hdc, x, y, RGB(_activePen.color.r, _activePen.color.g, _activePen.color.b)); 11603 } 11604 11605 // The basic shapes, outlined 11606 11607 void drawLine(int x1, int y1, int x2, int y2) { 11608 MoveToEx(hdc, x1, y1, null); 11609 LineTo(hdc, x2, y2); 11610 } 11611 11612 void drawRectangle(int x, int y, int width, int height) { 11613 // FIXME: with a wider pen this might not draw quite right. im not sure. 11614 gdi.Rectangle(hdc, x, y, x + width, y + height); 11615 } 11616 11617 /// Arguments are the points of the bounding rectangle 11618 void drawEllipse(int x1, int y1, int x2, int y2) { 11619 Ellipse(hdc, x1, y1, x2, y2); 11620 } 11621 11622 void drawArc(int x1, int y1, int width, int height, int start, int finish) { 11623 if((start % (360*64)) == (finish % (360*64))) 11624 drawEllipse(x1, y1, x1 + width, y1 + height); 11625 else { 11626 import core.stdc.math; 11627 float startAngle = cast(float) start / 64.0 / 180.0 * 3.14159265358979323; 11628 float endAngle = cast(float) finish / 64.0 / 180.0 * 3.14159265358979323; 11629 11630 auto c1 = cast(int) roundf(cos(startAngle) * width / 2 + x1 + width / 2); 11631 auto c2 = cast(int) roundf(-sin(startAngle) * height / 2 + y1 + height / 2); 11632 auto c3 = cast(int) roundf(cos(endAngle) * width / 2 + x1 + width / 2); 11633 auto c4 = cast(int) roundf(-sin(endAngle) * height / 2 + y1 + height / 2); 11634 11635 if(_activePen.color.a) 11636 Arc(hdc, x1, y1, x1 + width + 1, y1 + height + 1, c1, c2, c3, c4); 11637 if(_fillColor.a) 11638 Pie(hdc, x1, y1, x1 + width + 1, y1 + height + 1, c1, c2, c3, c4); 11639 } 11640 } 11641 11642 void drawPolygon(Point[] vertexes) { 11643 POINT[] points; 11644 points.length = vertexes.length; 11645 11646 foreach(i, p; vertexes) { 11647 points[i].x = p.x; 11648 points[i].y = p.y; 11649 } 11650 11651 Polygon(hdc, points.ptr, cast(int) points.length); 11652 } 11653 } 11654 11655 11656 // Mix this into the SimpleWindow class 11657 mixin template NativeSimpleWindowImplementation() { 11658 int curHidden = 0; // counter 11659 __gshared static bool[string] knownWinClasses; 11660 static bool altPressed = false; 11661 11662 HANDLE oldCursor; 11663 11664 void hideCursor () { 11665 if(curHidden == 0) 11666 oldCursor = SetCursor(null); 11667 ++curHidden; 11668 } 11669 11670 void showCursor () { 11671 --curHidden; 11672 if(curHidden == 0) { 11673 SetCursor(currentCursor is null ? oldCursor : currentCursor); // show it immediately without waiting for mouse movement 11674 } 11675 } 11676 11677 11678 int minWidth = 0, minHeight = 0, maxWidth = int.max, maxHeight = int.max; 11679 11680 void setMinSize (int minwidth, int minheight) { 11681 minWidth = minwidth; 11682 minHeight = minheight; 11683 } 11684 void setMaxSize (int maxwidth, int maxheight) { 11685 maxWidth = maxwidth; 11686 maxHeight = maxheight; 11687 } 11688 11689 // FIXME i'm not sure that Windows has this functionality 11690 // though it is nonessential anyway. 11691 void setResizeGranularity (int granx, int grany) {} 11692 11693 ScreenPainter getPainter(bool manualInvalidations) { 11694 return ScreenPainter(this, hwnd, manualInvalidations); 11695 } 11696 11697 HBITMAP buffer; 11698 11699 void setTitle(string title) { 11700 WCharzBuffer bfr = WCharzBuffer(title); 11701 SetWindowTextW(hwnd, bfr.ptr); 11702 } 11703 11704 string getTitle() { 11705 auto len = GetWindowTextLengthW(hwnd); 11706 if (!len) 11707 return null; 11708 wchar[256] tmpBuffer; 11709 wchar[] buffer = (len <= tmpBuffer.length) ? tmpBuffer[] : new wchar[len]; 11710 auto len2 = GetWindowTextW(hwnd, buffer.ptr, cast(int) buffer.length); 11711 auto str = buffer[0 .. len2]; 11712 return makeUtf8StringFromWindowsString(str); 11713 } 11714 11715 void move(int x, int y) { 11716 RECT rect; 11717 GetWindowRect(hwnd, &rect); 11718 // move it while maintaining the same size... 11719 MoveWindow(hwnd, x, y, rect.right - rect.left, rect.bottom - rect.top, true); 11720 } 11721 11722 void resize(int w, int h) { 11723 RECT rect; 11724 GetWindowRect(hwnd, &rect); 11725 11726 RECT client; 11727 GetClientRect(hwnd, &client); 11728 11729 rect.right = rect.right - client.right + w; 11730 rect.bottom = rect.bottom - client.bottom + h; 11731 11732 // same position, new size for the client rectangle 11733 MoveWindow(hwnd, rect.left, rect.top, rect.right, rect.bottom, true); 11734 11735 updateOpenglViewportIfNeeded(w, h); 11736 } 11737 11738 void moveResize (int x, int y, int w, int h) { 11739 // what's given is the client rectangle, we need to adjust 11740 11741 RECT rect; 11742 rect.left = x; 11743 rect.top = y; 11744 rect.right = w + x; 11745 rect.bottom = h + y; 11746 if(!AdjustWindowRect(&rect, GetWindowLong(hwnd, GWL_STYLE), GetMenu(hwnd) !is null)) 11747 throw new WindowsApiException("AdjustWindowRect", GetLastError()); 11748 11749 MoveWindow(hwnd, rect.left, rect.top, rect.right - rect.left, rect.bottom - rect.top, true); 11750 updateOpenglViewportIfNeeded(w, h); 11751 if (windowResized !is null) windowResized(w, h); 11752 } 11753 11754 version(without_opengl) {} else { 11755 HGLRC ghRC; 11756 HDC ghDC; 11757 } 11758 11759 void createWindow(int width, int height, string title, OpenGlOptions opengl, SimpleWindow parent) { 11760 string cnamec; 11761 if (sdpyWindowClassStr is null) loadBinNameToWindowClassName(); 11762 if (sdpyWindowClassStr is null || sdpyWindowClassStr[0] == 0) { 11763 cnamec = "DSimpleWindow"; 11764 } else { 11765 cnamec = sdpyWindowClass; 11766 } 11767 11768 WCharzBuffer cn = WCharzBuffer(cnamec); 11769 11770 HINSTANCE hInstance = cast(HINSTANCE) GetModuleHandle(null); 11771 11772 if(cnamec !in knownWinClasses) { 11773 WNDCLASSEX wc; 11774 11775 // FIXME: I might be able to use cbWndExtra to hold the pointer back 11776 // to the object. Maybe. 11777 wc.cbSize = wc.sizeof; 11778 wc.cbClsExtra = 0; 11779 wc.cbWndExtra = 0; 11780 wc.hbrBackground = cast(HBRUSH) (COLOR_WINDOW+1); // GetStockObject(WHITE_BRUSH); 11781 wc.hCursor = LoadCursorW(null, IDC_ARROW); 11782 wc.hIcon = LoadIcon(hInstance, null); 11783 wc.hInstance = hInstance; 11784 wc.lpfnWndProc = &WndProc; 11785 wc.lpszClassName = cn.ptr; 11786 wc.hIconSm = null; 11787 wc.style = CS_HREDRAW | CS_VREDRAW | CS_DBLCLKS; 11788 if(!RegisterClassExW(&wc)) 11789 throw new WindowsApiException("RegisterClassExW", GetLastError()); 11790 knownWinClasses[cnamec] = true; 11791 } 11792 11793 int style; 11794 uint flags = WS_EX_ACCEPTFILES; // accept drag-drop files 11795 11796 // FIXME: windowType and customizationFlags 11797 final switch(windowType) { 11798 case WindowTypes.normal: 11799 if(resizability == Resizability.fixedSize) { 11800 style = WS_SYSMENU | WS_OVERLAPPED | WS_CAPTION; 11801 } else { 11802 style = WS_OVERLAPPEDWINDOW; 11803 } 11804 break; 11805 case WindowTypes.undecorated: 11806 style = WS_POPUP | WS_SYSMENU; 11807 break; 11808 case WindowTypes.eventOnly: 11809 _hidden = true; 11810 break; 11811 case WindowTypes.dropdownMenu: 11812 case WindowTypes.popupMenu: 11813 case WindowTypes.notification: 11814 style = WS_POPUP; 11815 flags |= WS_EX_NOACTIVATE; 11816 break; 11817 case WindowTypes.nestedChild: 11818 style = WS_CHILD; 11819 break; 11820 case WindowTypes.minimallyWrapped: 11821 assert(0, "construct minimally wrapped through the other ctor overlad"); 11822 } 11823 11824 if ((customizationFlags & WindowFlags.extraComposite) != 0) 11825 flags |= WS_EX_LAYERED; // composite window for better performance and effects support 11826 11827 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 11828 CW_USEDEFAULT, CW_USEDEFAULT, width, height, 11829 parent is null ? null : parent.impl.hwnd, null, hInstance, null); 11830 11831 if ((customizationFlags & WindowFlags.extraComposite) != 0) 11832 setOpacity(255); 11833 11834 SimpleWindow.nativeMapping[hwnd] = this; 11835 CapableOfHandlingNativeEvent.nativeHandleMapping[hwnd] = this; 11836 11837 if(windowType == WindowTypes.eventOnly) 11838 return; 11839 11840 HDC hdc = GetDC(hwnd); 11841 11842 11843 version(without_opengl) {} 11844 else { 11845 if(opengl == OpenGlOptions.yes) { 11846 if(!openGlLibrariesSuccessfullyLoaded) throw new Exception("OpenGL libraries did not load"); 11847 static if (SdpyIsUsingIVGLBinds) {if (glbindGetProcAddress("glHint") is null) assert(0, "GL: error loading OpenGL"); } // loads all necessary functions 11848 static if (SdpyIsUsingIVGLBinds) import iv.glbinds; // override druntime windows imports 11849 ghDC = hdc; 11850 PIXELFORMATDESCRIPTOR pfd; 11851 11852 pfd.nSize = PIXELFORMATDESCRIPTOR.sizeof; 11853 pfd.nVersion = 1; 11854 pfd.dwFlags = PFD_DRAW_TO_WINDOW | PFD_SUPPORT_OPENGL | PFD_DOUBLEBUFFER; 11855 pfd.dwLayerMask = PFD_MAIN_PLANE; 11856 pfd.iPixelType = PFD_TYPE_RGBA; 11857 pfd.cColorBits = 24; 11858 pfd.cDepthBits = 24; 11859 pfd.cAccumBits = 0; 11860 pfd.cStencilBits = 8; // any reasonable OpenGL implementation should support this anyway 11861 11862 auto pixelformat = ChoosePixelFormat(hdc, &pfd); 11863 11864 if (pixelformat == 0) 11865 throw new WindowsApiException("ChoosePixelFormat", GetLastError()); 11866 11867 if (SetPixelFormat(hdc, pixelformat, &pfd) == 0) 11868 throw new WindowsApiException("SetPixelFormat", GetLastError()); 11869 11870 if (sdpyOpenGLContextVersion && wglCreateContextAttribsARB is null) { 11871 // windoze is idiotic: we have to have OpenGL context to get function addresses 11872 // so we will create fake context to get that stupid address 11873 auto tmpcc = wglCreateContext(ghDC); 11874 if (tmpcc !is null) { 11875 scope(exit) { wglMakeCurrent(ghDC, null); wglDeleteContext(tmpcc); } 11876 wglMakeCurrent(ghDC, tmpcc); 11877 wglInitOtherFunctions(); 11878 } 11879 } 11880 11881 if (wglCreateContextAttribsARB !is null && sdpyOpenGLContextVersion) { 11882 int[9] contextAttribs = [ 11883 WGL_CONTEXT_MAJOR_VERSION_ARB, (sdpyOpenGLContextVersion>>8), 11884 WGL_CONTEXT_MINOR_VERSION_ARB, (sdpyOpenGLContextVersion&0xff), 11885 WGL_CONTEXT_PROFILE_MASK_ARB, (sdpyOpenGLContextCompatible ? WGL_CONTEXT_COMPATIBILITY_PROFILE_BIT_ARB : WGL_CONTEXT_CORE_PROFILE_BIT_ARB), 11886 // for modern context, set "forward compatibility" flag too 11887 (sdpyOpenGLContextCompatible ? 0/*None*/ : WGL_CONTEXT_FLAGS_ARB), WGL_CONTEXT_FORWARD_COMPATIBLE_BIT_ARB, 11888 0/*None*/, 11889 ]; 11890 ghRC = wglCreateContextAttribsARB(ghDC, null, contextAttribs.ptr); 11891 if (ghRC is null && sdpyOpenGLContextAllowFallback) { 11892 // activate fallback mode 11893 // 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; 11894 ghRC = wglCreateContext(ghDC); 11895 } 11896 if (ghRC is null) 11897 throw new WindowsApiException("wglCreateContextAttribsARB", GetLastError()); 11898 } else { 11899 // try to do at least something 11900 if (sdpyOpenGLContextAllowFallback || sdpyOpenGLContextVersion == 0) { 11901 sdpyOpenGLContextVersion = 0; 11902 ghRC = wglCreateContext(ghDC); 11903 } 11904 if (ghRC is null) 11905 throw new WindowsApiException("wglCreateContext", GetLastError()); 11906 } 11907 } 11908 } 11909 11910 if(opengl == OpenGlOptions.no) { 11911 buffer = CreateCompatibleBitmap(hdc, width, height); 11912 11913 auto hdcBmp = CreateCompatibleDC(hdc); 11914 // make sure it's filled with a blank slate 11915 auto oldBmp = SelectObject(hdcBmp, buffer); 11916 auto oldBrush = SelectObject(hdcBmp, GetStockObject(WHITE_BRUSH)); 11917 auto oldPen = SelectObject(hdcBmp, GetStockObject(WHITE_PEN)); 11918 gdi.Rectangle(hdcBmp, 0, 0, width, height); 11919 SelectObject(hdcBmp, oldBmp); 11920 SelectObject(hdcBmp, oldBrush); 11921 SelectObject(hdcBmp, oldPen); 11922 DeleteDC(hdcBmp); 11923 11924 bmpWidth = width; 11925 bmpHeight = height; 11926 11927 ReleaseDC(hwnd, hdc); // we keep this in opengl mode since it is a class member now 11928 } 11929 11930 // We want the window's client area to match the image size 11931 RECT rcClient, rcWindow; 11932 POINT ptDiff; 11933 GetClientRect(hwnd, &rcClient); 11934 GetWindowRect(hwnd, &rcWindow); 11935 ptDiff.x = (rcWindow.right - rcWindow.left) - rcClient.right; 11936 ptDiff.y = (rcWindow.bottom - rcWindow.top) - rcClient.bottom; 11937 MoveWindow(hwnd,rcWindow.left, rcWindow.top, width + ptDiff.x, height + ptDiff.y, true); 11938 11939 if ((customizationFlags&WindowFlags.dontAutoShow) == 0) { 11940 ShowWindow(hwnd, SW_SHOWNORMAL); 11941 } else { 11942 _hidden = true; 11943 } 11944 this._visibleForTheFirstTimeCalled = false; // hack! 11945 } 11946 11947 11948 void dispose() { 11949 if(buffer) 11950 DeleteObject(buffer); 11951 } 11952 11953 void closeWindow() { 11954 if(ghRC) { 11955 wglDeleteContext(ghRC); 11956 ghRC = null; 11957 } 11958 DestroyWindow(hwnd); 11959 } 11960 11961 bool setOpacity(ubyte alpha) { 11962 return SetLayeredWindowAttributes(hwnd, 0, alpha, LWA_ALPHA) == TRUE; 11963 } 11964 11965 HANDLE currentCursor; 11966 11967 // returns zero if it recognized the event 11968 static int triggerEvents(HWND hwnd, uint msg, WPARAM wParam, LPARAM lParam, int offsetX, int offsetY, SimpleWindow wind) { 11969 MouseEvent mouse; 11970 11971 void mouseEvent(bool isScreen, ulong mods) { 11972 auto x = LOWORD(lParam); 11973 auto y = HIWORD(lParam); 11974 if(isScreen) { 11975 POINT p; 11976 p.x = x; 11977 p.y = y; 11978 ScreenToClient(hwnd, &p); 11979 x = cast(ushort) p.x; 11980 y = cast(ushort) p.y; 11981 } 11982 11983 if(wind.resizability == Resizability.automaticallyScaleIfPossible) { 11984 x = cast(ushort)( x * wind._virtualWidth / wind._width ); 11985 y = cast(ushort)( y * wind._virtualHeight / wind._height ); 11986 } 11987 11988 mouse.x = x + offsetX; 11989 mouse.y = y + offsetY; 11990 11991 wind.mdx(mouse); 11992 mouse.modifierState = cast(int) mods; 11993 mouse.window = wind; 11994 11995 if(wind.handleMouseEvent) 11996 wind.handleMouseEvent(mouse); 11997 } 11998 11999 switch(msg) { 12000 case WM_GETMINMAXINFO: 12001 MINMAXINFO* mmi = cast(MINMAXINFO*) lParam; 12002 12003 if(wind.minWidth > 0) { 12004 RECT rect; 12005 rect.left = 100; 12006 rect.top = 100; 12007 rect.right = wind.minWidth + 100; 12008 rect.bottom = wind.minHeight + 100; 12009 if(!AdjustWindowRect(&rect, GetWindowLong(wind.hwnd, GWL_STYLE), GetMenu(wind.hwnd) !is null)) 12010 throw new WindowsApiException("AdjustWindowRect", GetLastError()); 12011 12012 mmi.ptMinTrackSize.x = rect.right - rect.left; 12013 mmi.ptMinTrackSize.y = rect.bottom - rect.top; 12014 } 12015 12016 if(wind.maxWidth < int.max) { 12017 RECT rect; 12018 rect.left = 100; 12019 rect.top = 100; 12020 rect.right = wind.maxWidth + 100; 12021 rect.bottom = wind.maxHeight + 100; 12022 if(!AdjustWindowRect(&rect, GetWindowLong(wind.hwnd, GWL_STYLE), GetMenu(wind.hwnd) !is null)) 12023 throw new WindowsApiException("AdjustWindowRect", GetLastError()); 12024 12025 mmi.ptMaxTrackSize.x = rect.right - rect.left; 12026 mmi.ptMaxTrackSize.y = rect.bottom - rect.top; 12027 } 12028 break; 12029 case WM_CHAR: 12030 wchar c = cast(wchar) wParam; 12031 if(wind.handleCharEvent) 12032 wind.handleCharEvent(cast(dchar) c); 12033 break; 12034 case WM_SETFOCUS: 12035 case WM_KILLFOCUS: 12036 wind._focused = (msg == WM_SETFOCUS); 12037 if (msg == WM_SETFOCUS) altPressed = false; //k8: reset alt state on defocus (it is better than nothing...) 12038 if(wind.onFocusChange) 12039 wind.onFocusChange(msg == WM_SETFOCUS); 12040 break; 12041 12042 case WM_SYSKEYDOWN: 12043 goto case; 12044 case WM_SYSKEYUP: 12045 if(lParam & (1 << 29)) { 12046 goto case; 12047 } else { 12048 // no window has keyboard focus 12049 goto default; 12050 } 12051 case WM_KEYDOWN: 12052 case WM_KEYUP: 12053 KeyEvent ev; 12054 ev.key = cast(Key) wParam; 12055 ev.pressed = (msg == WM_KEYDOWN || msg == WM_SYSKEYDOWN); 12056 if (wParam == 0x12) ev.key = Key.Alt; // windows does it this way 12057 12058 ev.hardwareCode = (lParam & 0xff0000) >> 16; 12059 12060 if(GetKeyState(Key.Shift)&0x8000 || GetKeyState(Key.Shift_r)&0x8000) 12061 ev.modifierState |= ModifierState.shift; 12062 //k8: this doesn't work; thanks for nothing, windows 12063 /*if(GetKeyState(Key.Alt)&0x8000 || GetKeyState(Key.Alt_r)&0x8000) 12064 ev.modifierState |= ModifierState.alt;*/ 12065 // this never seems to actually be set 12066 // if (lParam & 0x2000 /* KF_ALTDOWN */) ev.modifierState |= ModifierState.alt; else ev.modifierState &= ~ModifierState.alt; 12067 12068 if (wParam == 0x12) { 12069 altPressed = (msg == WM_SYSKEYDOWN); 12070 } 12071 12072 if(msg == WM_KEYDOWN || msg == WM_KEYUP) { 12073 altPressed = false; 12074 } 12075 // sdpyPrintDebugString(altPressed ? "alt down" : " up "); 12076 12077 if (altPressed) ev.modifierState |= ModifierState.alt; else ev.modifierState &= ~ModifierState.alt; 12078 if(GetKeyState(Key.Ctrl)&0x8000 || GetKeyState(Key.Ctrl_r)&0x8000) 12079 ev.modifierState |= ModifierState.ctrl; 12080 if(GetKeyState(Key.Windows)&0x8000 || GetKeyState(Key.Windows_r)&0x8000) 12081 ev.modifierState |= ModifierState.windows; 12082 if(GetKeyState(Key.NumLock)) 12083 ev.modifierState |= ModifierState.numLock; 12084 if(GetKeyState(Key.CapsLock)) 12085 ev.modifierState |= ModifierState.capsLock; 12086 12087 /+ 12088 // we always want to send the character too, so let's convert it 12089 ubyte[256] state; 12090 wchar[16] buffer; 12091 GetKeyboardState(state.ptr); 12092 ToUnicodeEx(wParam, lParam, state.ptr, buffer.ptr, buffer.length, 0, null); 12093 12094 foreach(dchar d; buffer) { 12095 ev.character = d; 12096 break; 12097 } 12098 +/ 12099 12100 ev.window = wind; 12101 if(wind.handleKeyEvent) 12102 wind.handleKeyEvent(ev); 12103 break; 12104 case 0x020a /*WM_MOUSEWHEEL*/: 12105 // send click 12106 mouse.type = cast(MouseEventType) 1; 12107 mouse.button = ((GET_WHEEL_DELTA_WPARAM(wParam) > 0) ? MouseButton.wheelUp : MouseButton.wheelDown); 12108 mouseEvent(true, LOWORD(wParam)); 12109 12110 // also send release 12111 mouse.type = cast(MouseEventType) 2; 12112 mouse.button = ((GET_WHEEL_DELTA_WPARAM(wParam) > 0) ? MouseButton.wheelUp : MouseButton.wheelDown); 12113 mouseEvent(true, LOWORD(wParam)); 12114 break; 12115 case WM_MOUSEMOVE: 12116 mouse.type = cast(MouseEventType) 0; 12117 mouseEvent(false, wParam); 12118 break; 12119 case WM_LBUTTONDOWN: 12120 case WM_LBUTTONDBLCLK: 12121 mouse.type = cast(MouseEventType) 1; 12122 mouse.button = MouseButton.left; 12123 mouse.doubleClick = msg == WM_LBUTTONDBLCLK; 12124 mouseEvent(false, wParam); 12125 break; 12126 case WM_LBUTTONUP: 12127 mouse.type = cast(MouseEventType) 2; 12128 mouse.button = MouseButton.left; 12129 mouseEvent(false, wParam); 12130 break; 12131 case WM_RBUTTONDOWN: 12132 case WM_RBUTTONDBLCLK: 12133 mouse.type = cast(MouseEventType) 1; 12134 mouse.button = MouseButton.right; 12135 mouse.doubleClick = msg == WM_RBUTTONDBLCLK; 12136 mouseEvent(false, wParam); 12137 break; 12138 case WM_RBUTTONUP: 12139 mouse.type = cast(MouseEventType) 2; 12140 mouse.button = MouseButton.right; 12141 mouseEvent(false, wParam); 12142 break; 12143 case WM_MBUTTONDOWN: 12144 case WM_MBUTTONDBLCLK: 12145 mouse.type = cast(MouseEventType) 1; 12146 mouse.button = MouseButton.middle; 12147 mouse.doubleClick = msg == WM_MBUTTONDBLCLK; 12148 mouseEvent(false, wParam); 12149 break; 12150 case WM_MBUTTONUP: 12151 mouse.type = cast(MouseEventType) 2; 12152 mouse.button = MouseButton.middle; 12153 mouseEvent(false, wParam); 12154 break; 12155 case WM_XBUTTONDOWN: 12156 case WM_XBUTTONDBLCLK: 12157 mouse.type = cast(MouseEventType) 1; 12158 mouse.button = HIWORD(wParam) == 1 ? MouseButton.backButton : MouseButton.forwardButton; 12159 mouse.doubleClick = msg == WM_XBUTTONDBLCLK; 12160 mouseEvent(false, wParam); 12161 return 1; // MSDN says special treatment here, return TRUE to bypass simulation programs 12162 case WM_XBUTTONUP: 12163 mouse.type = cast(MouseEventType) 2; 12164 mouse.button = HIWORD(wParam) == 1 ? MouseButton.backButton : MouseButton.forwardButton; 12165 mouseEvent(false, wParam); 12166 return 1; // see: https://msdn.microsoft.com/en-us/library/windows/desktop/ms646246(v=vs.85).aspx 12167 12168 default: return 1; 12169 } 12170 return 0; 12171 } 12172 12173 HWND hwnd; 12174 private int oldWidth; 12175 private int oldHeight; 12176 private bool inSizeMove; 12177 12178 /++ 12179 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. 12180 12181 History: 12182 Added November 23, 2021 12183 12184 Not fully stable, may be moved out of the impl struct. 12185 12186 Default value changed to `true` on February 15, 2021 12187 +/ 12188 bool doLiveResizing = true; 12189 12190 package int bmpWidth; 12191 package int bmpHeight; 12192 12193 // the extern(Windows) wndproc should just forward to this 12194 LRESULT windowProcedure(HWND hwnd, uint msg, WPARAM wParam, LPARAM lParam) { 12195 try { 12196 assert(hwnd is this.hwnd); 12197 12198 if(triggerEvents(hwnd, msg, wParam, lParam, 0, 0, this)) 12199 switch(msg) { 12200 case WM_MENUCHAR: // menu active but key not associated with a thing. 12201 // you would ideally use this for like a search function but sdpy not that ideally designed. alas. 12202 // The main things we can do are select, execute, close, or ignore 12203 // the default is ignore, but it doesn't *just* ignore it - it also dings an audio alert to 12204 // the user. This can be a bit annoying for sdpy things so instead im overriding and setting it 12205 // to close, which can be really annoying when you hit the wrong button. but meh i think for sdpy 12206 // that's the lesser bad choice rn. Can always override by returning true in triggerEvents.... 12207 12208 // returns the value in the *high order word* of the return value 12209 // hence the << 16 12210 return 1 << 16; // MNC_CLOSE, close the menu without dinging at the user 12211 case WM_SETCURSOR: 12212 if(cast(HWND) wParam !is hwnd) 12213 return 0; // further processing elsewhere 12214 12215 if(LOWORD(lParam) == HTCLIENT && (this.curHidden > 0 || currentCursor !is null)) { 12216 SetCursor(this.curHidden > 0 ? null : currentCursor); 12217 return 1; 12218 } else { 12219 return DefWindowProc(hwnd, msg, wParam, lParam); 12220 } 12221 //break; 12222 12223 case WM_CLOSE: 12224 if (this.closeQuery !is null) this.closeQuery(); else this.close(); 12225 break; 12226 case WM_DESTROY: 12227 if (this.onDestroyed !is null) try { this.onDestroyed(); } catch (Exception e) {} // sorry 12228 SimpleWindow.nativeMapping.remove(hwnd); 12229 CapableOfHandlingNativeEvent.nativeHandleMapping.remove(hwnd); 12230 12231 bool anyImportant = false; 12232 foreach(SimpleWindow w; SimpleWindow.nativeMapping) 12233 if(w.beingOpenKeepsAppOpen) { 12234 anyImportant = true; 12235 break; 12236 } 12237 if(!anyImportant) { 12238 PostQuitMessage(0); 12239 } 12240 break; 12241 case 0x02E0 /*WM_DPICHANGED*/: 12242 this.actualDpi_ = LOWORD(wParam); // hiword is the y param but it is the same per docs 12243 12244 RECT* prcNewWindow = cast(RECT*)lParam; 12245 // docs say this is the recommended position and we should honor it 12246 SetWindowPos(hwnd, 12247 null, 12248 prcNewWindow.left, 12249 prcNewWindow.top, 12250 prcNewWindow.right - prcNewWindow.left, 12251 prcNewWindow.bottom - prcNewWindow.top, 12252 SWP_NOZORDER | SWP_NOACTIVATE); 12253 12254 // doing this because of https://github.com/microsoft/Windows-classic-samples/blob/main/Samples/DPIAwarenessPerWindow/client/DpiAwarenessContext.cpp 12255 // im not sure it is completely correct 12256 // but without it the tabs and such do look weird as things change. 12257 if(SystemParametersInfoForDpi) { 12258 LOGFONT lfText; 12259 SystemParametersInfoForDpi(SPI_GETICONTITLELOGFONT, lfText.sizeof, &lfText, FALSE, this.actualDpi_); 12260 HFONT hFontNew = CreateFontIndirect(&lfText); 12261 if (hFontNew) 12262 { 12263 //DeleteObject(hFontOld); 12264 static extern(Windows) BOOL helper(HWND hWnd, LPARAM lParam) { 12265 SendMessage(hWnd, WM_SETFONT, cast(WPARAM)lParam, MAKELPARAM(TRUE, 0)); 12266 return TRUE; 12267 } 12268 EnumChildWindows(hwnd, &helper, cast(LPARAM) hFontNew); 12269 } 12270 } 12271 12272 if(this.onDpiChanged) 12273 this.onDpiChanged(); 12274 break; 12275 case WM_ENTERIDLE: 12276 // when a menu is up, it stops normal event processing (modal message loop) 12277 // but this at least gives us a chance to SOMETIMES catch up 12278 // FIXME: I can use SetTimer while idle to keep working i think... but idk when i'd destroy it. 12279 SimpleWindow.processAllCustomEvents; 12280 SimpleWindow.processAllCustomEvents; 12281 SleepEx(0, true); 12282 break; 12283 case WM_SIZE: 12284 if(wParam == 1 /* SIZE_MINIMIZED */) 12285 break; 12286 _width = LOWORD(lParam); 12287 _height = HIWORD(lParam); 12288 12289 // I want to avoid tearing in the windows (my code is inefficient 12290 // so this is a hack around that) so while sizing, we don't trigger, 12291 // but we do want to trigger on events like mazimize. 12292 if(!inSizeMove || doLiveResizing) 12293 goto size_changed; 12294 break; 12295 /+ 12296 case WM_SIZING: 12297 writeln("size"); 12298 break; 12299 +/ 12300 // I don't like the tearing I get when redrawing on WM_SIZE 12301 // (I know there's other ways to fix that but I don't like that behavior anyway) 12302 // so instead it is going to redraw only at the end of a size. 12303 case 0x0231: /* WM_ENTERSIZEMOVE */ 12304 inSizeMove = true; 12305 break; 12306 case 0x0232: /* WM_EXITSIZEMOVE */ 12307 inSizeMove = false; 12308 12309 size_changed: 12310 12311 // nothing relevant changed, don't bother redrawing 12312 if(oldWidth == _width && oldHeight == _height) { 12313 if(msg == 0x0232) 12314 goto finalize_resize; 12315 break; 12316 } 12317 12318 // note: OpenGL windows don't use a backing bmp, so no need to change them 12319 // if resizability is anything other than allowResizing, it is meant to either stretch the one image or just do nothing 12320 if(openglMode == OpenGlOptions.no) { // && resizability == Resizability.allowResizing) { 12321 // gotta get the double buffer bmp to match the window 12322 // FIXME: could this be more efficient? it never relinquishes a large bitmap 12323 12324 // if it is auto-scaled, we keep the backing bitmap the same size all the time 12325 if(resizability != Resizability.automaticallyScaleIfPossible) 12326 if(_width > bmpWidth || _height > bmpHeight) { 12327 auto hdc = GetDC(hwnd); 12328 auto oldBuffer = buffer; 12329 buffer = CreateCompatibleBitmap(hdc, _width, _height); 12330 12331 auto hdcBmp = CreateCompatibleDC(hdc); 12332 auto oldBmp = SelectObject(hdcBmp, buffer); 12333 12334 auto hdcOldBmp = CreateCompatibleDC(hdc); 12335 auto oldOldBmp = SelectObject(hdcOldBmp, oldBuffer); 12336 12337 /+ 12338 RECT r; 12339 r.left = 0; 12340 r.top = 0; 12341 r.right = width; 12342 r.bottom = height; 12343 auto c = Color.green; 12344 auto brush = CreateSolidBrush(RGB(c.r, c.g, c.b)); 12345 FillRect(hdcBmp, &r, brush); 12346 DeleteObject(brush); 12347 +/ 12348 12349 BitBlt(hdcBmp, 0, 0, bmpWidth, bmpHeight, hdcOldBmp, 0, 0, SRCCOPY); 12350 12351 bmpWidth = _width; 12352 bmpHeight = _height; 12353 12354 SelectObject(hdcOldBmp, oldOldBmp); 12355 DeleteDC(hdcOldBmp); 12356 12357 SelectObject(hdcBmp, oldBmp); 12358 DeleteDC(hdcBmp); 12359 12360 ReleaseDC(hwnd, hdc); 12361 12362 DeleteObject(oldBuffer); 12363 } 12364 } 12365 12366 updateOpenglViewportIfNeeded(_width, _height); 12367 12368 if(resizability != Resizability.automaticallyScaleIfPossible) 12369 if(windowResized !is null) 12370 windowResized(_width, _height); 12371 12372 /+ 12373 if(inSizeMove) { 12374 // SimpleWindow.processAllCustomEvents(); 12375 // SimpleWindow.processAllCustomEvents(); 12376 12377 //RedrawWindow(hwnd, null, null, RDW_ERASE | RDW_INVALIDATE | RDW_ALLCHILDREN); 12378 //sdpyPrintDebugString("redraw b"); 12379 } else { 12380 +/ { 12381 finalize_resize: 12382 // when it is all done, make sure everything is freshly drawn or there might be 12383 // weird bugs left. 12384 SimpleWindow.processAllCustomEvents(); 12385 SimpleWindow.processAllCustomEvents(); 12386 12387 RedrawWindow(hwnd, null, null, RDW_ERASE | RDW_INVALIDATE | RDW_ALLCHILDREN); 12388 // sdpyPrintDebugString("redraw"); 12389 } 12390 12391 oldWidth = this._width; 12392 oldHeight = this._height; 12393 break; 12394 case WM_ERASEBKGND: 12395 // call `visibleForTheFirstTime` here, so we can do initialization as early as possible 12396 if (!this._visibleForTheFirstTimeCalled) { 12397 this._visibleForTheFirstTimeCalled = true; 12398 if (this.visibleForTheFirstTime !is null) { 12399 this.visibleForTheFirstTime(); 12400 } 12401 } 12402 // block it in OpenGL mode, 'cause no sane person will (or should) draw windows controls over OpenGL scene 12403 version(without_opengl) {} else { 12404 if (openglMode == OpenGlOptions.yes) return 1; 12405 } 12406 // call windows default handler, so it can paint standard controls 12407 goto default; 12408 case WM_CTLCOLORBTN: 12409 case WM_CTLCOLORSTATIC: 12410 SetBkMode(cast(HDC) wParam, TRANSPARENT); 12411 return cast(typeof(return)) //GetStockObject(NULL_BRUSH); 12412 GetSysColorBrush(COLOR_3DFACE); 12413 //break; 12414 case WM_SHOWWINDOW: 12415 this._visible = (wParam != 0); 12416 if (!this._visibleForTheFirstTimeCalled && this._visible) { 12417 this._visibleForTheFirstTimeCalled = true; 12418 if (this.visibleForTheFirstTime !is null) { 12419 this.visibleForTheFirstTime(); 12420 } 12421 } 12422 if (this.visibilityChanged !is null) this.visibilityChanged(this._visible); 12423 break; 12424 case WM_PAINT: { 12425 if (!this._visibleForTheFirstTimeCalled) { 12426 this._visibleForTheFirstTimeCalled = true; 12427 if (this.visibleForTheFirstTime !is null) { 12428 this.visibleForTheFirstTime(); 12429 } 12430 } 12431 12432 BITMAP bm; 12433 PAINTSTRUCT ps; 12434 12435 HDC hdc = BeginPaint(hwnd, &ps); 12436 12437 if(openglMode == OpenGlOptions.no) { 12438 12439 HDC hdcMem = CreateCompatibleDC(hdc); 12440 HBITMAP hbmOld = SelectObject(hdcMem, buffer); 12441 12442 GetObject(buffer, bm.sizeof, &bm); 12443 12444 // FIXME: only BitBlt the invalidated rectangle, not the whole thing 12445 if(resizability == Resizability.automaticallyScaleIfPossible) 12446 StretchBlt(hdc, 0, 0, this._width, this._height, hdcMem, 0, 0, bm.bmWidth, bm.bmHeight, SRCCOPY); 12447 else 12448 BitBlt(hdc, 0, 0, bm.bmWidth, bm.bmHeight, hdcMem, 0, 0, SRCCOPY); 12449 //BitBlt(hdc, ps.rcPaint.left, ps.rcPaint.top, ps.rcPaint.right - ps.rcPaint.left, ps.rcPaint.top - ps.rcPaint.bottom, hdcMem, 0, 0, SRCCOPY); 12450 12451 SelectObject(hdcMem, hbmOld); 12452 DeleteDC(hdcMem); 12453 EndPaint(hwnd, &ps); 12454 } else { 12455 EndPaint(hwnd, &ps); 12456 version(without_opengl) {} else 12457 redrawOpenGlSceneSoon(); 12458 } 12459 } break; 12460 default: 12461 return DefWindowProc(hwnd, msg, wParam, lParam); 12462 } 12463 return 0; 12464 12465 } 12466 catch(Throwable t) { 12467 sdpyPrintDebugString(t.toString); 12468 return 0; 12469 } 12470 } 12471 } 12472 12473 mixin template NativeImageImplementation() { 12474 HBITMAP handle; 12475 ubyte* rawData; 12476 12477 final: 12478 12479 Color getPixel(int x, int y) { 12480 auto itemsPerLine = ((cast(int) width * 3 + 3) / 4) * 4; 12481 // remember, bmps are upside down 12482 auto offset = itemsPerLine * (height - y - 1) + x * 3; 12483 12484 Color c; 12485 if(enableAlpha) 12486 c.a = rawData[offset + 3]; 12487 else 12488 c.a = 255; 12489 c.b = rawData[offset + 0]; 12490 c.g = rawData[offset + 1]; 12491 c.r = rawData[offset + 2]; 12492 c.unPremultiply(); 12493 return c; 12494 } 12495 12496 void setPixel(int x, int y, Color c) { 12497 auto itemsPerLine = ((cast(int) width * 3 + 3) / 4) * 4; 12498 // remember, bmps are upside down 12499 auto offset = itemsPerLine * (height - y - 1) + x * 3; 12500 12501 if(enableAlpha) 12502 c.premultiply(); 12503 12504 rawData[offset + 0] = c.b; 12505 rawData[offset + 1] = c.g; 12506 rawData[offset + 2] = c.r; 12507 if(enableAlpha) 12508 rawData[offset + 3] = c.a; 12509 } 12510 12511 void convertToRgbaBytes(ubyte[] where) { 12512 assert(where.length == this.width * this.height * 4); 12513 12514 auto itemsPerLine = ((cast(int) width * 3 + 3) / 4) * 4; 12515 int idx = 0; 12516 int offset = itemsPerLine * (height - 1); 12517 // remember, bmps are upside down 12518 for(int y = height - 1; y >= 0; y--) { 12519 auto offsetStart = offset; 12520 for(int x = 0; x < width; x++) { 12521 where[idx + 0] = rawData[offset + 2]; // r 12522 where[idx + 1] = rawData[offset + 1]; // g 12523 where[idx + 2] = rawData[offset + 0]; // b 12524 if(enableAlpha) { 12525 where[idx + 3] = rawData[offset + 3]; // a 12526 unPremultiplyRgba(where[idx .. idx + 4]); 12527 offset++; 12528 } else 12529 where[idx + 3] = 255; // a 12530 idx += 4; 12531 offset += 3; 12532 } 12533 12534 offset = offsetStart - itemsPerLine; 12535 } 12536 } 12537 12538 void setFromRgbaBytes(in ubyte[] what) { 12539 assert(what.length == this.width * this.height * 4); 12540 12541 auto itemsPerLine = enableAlpha ? (width * 4) : (((cast(int) width * 3 + 3) / 4) * 4); 12542 int idx = 0; 12543 int offset = itemsPerLine * (height - 1); 12544 // remember, bmps are upside down 12545 for(int y = height - 1; y >= 0; y--) { 12546 auto offsetStart = offset; 12547 for(int x = 0; x < width; x++) { 12548 if(enableAlpha) { 12549 auto a = what[idx + 3]; 12550 12551 rawData[offset + 2] = (a * what[idx + 0]) / 255; // r 12552 rawData[offset + 1] = (a * what[idx + 1]) / 255; // g 12553 rawData[offset + 0] = (a * what[idx + 2]) / 255; // b 12554 rawData[offset + 3] = a; // a 12555 //premultiplyBgra(rawData[offset .. offset + 4]); 12556 offset++; 12557 } else { 12558 rawData[offset + 2] = what[idx + 0]; // r 12559 rawData[offset + 1] = what[idx + 1]; // g 12560 rawData[offset + 0] = what[idx + 2]; // b 12561 } 12562 idx += 4; 12563 offset += 3; 12564 } 12565 12566 offset = offsetStart - itemsPerLine; 12567 } 12568 } 12569 12570 12571 void createImage(int width, int height, bool forcexshm=false, bool enableAlpha = false) { 12572 BITMAPINFO infoheader; 12573 infoheader.bmiHeader.biSize = infoheader.bmiHeader.sizeof; 12574 infoheader.bmiHeader.biWidth = width; 12575 infoheader.bmiHeader.biHeight = height; 12576 infoheader.bmiHeader.biPlanes = 1; 12577 infoheader.bmiHeader.biBitCount = enableAlpha ? 32: 24; 12578 infoheader.bmiHeader.biCompression = BI_RGB; 12579 12580 handle = CreateDIBSection( 12581 null, 12582 &infoheader, 12583 DIB_RGB_COLORS, 12584 cast(void**) &rawData, 12585 null, 12586 0); 12587 if(handle is null) 12588 throw new WindowsApiException("create image failed", GetLastError()); 12589 12590 } 12591 12592 void dispose() { 12593 DeleteObject(handle); 12594 } 12595 } 12596 12597 enum KEY_ESCAPE = 27; 12598 } 12599 version(X11) { 12600 /// This is the default font used. You might change this before doing anything else with 12601 /// the library if you want to try something else. Surround that in `static if(UsingSimpledisplayX11)` 12602 /// for cross-platform compatibility. 12603 //__gshared string xfontstr = "-*-dejavu sans-medium-r-*-*-12-*-*-*-*-*-*-*"; 12604 //__gshared string xfontstr = "-*-dejavu sans-medium-r-*-*-12-*-*-*-*-*-*-*"; 12605 __gshared string xfontstr = "-*-lucida-medium-r-normal-sans-12-*-*-*-*-*-*-*"; 12606 //__gshared string xfontstr = "-*-fixed-medium-r-*-*-14-*-*-*-*-*-*-*"; 12607 12608 alias int delegate(XEvent) NativeEventHandler; 12609 alias Window NativeWindowHandle; 12610 12611 enum KEY_ESCAPE = 9; 12612 12613 mixin template NativeScreenPainterImplementation() { 12614 Display* display; 12615 Drawable d; 12616 Drawable destiny; 12617 12618 // FIXME: should the gc be static too so it isn't recreated every time draw is called? 12619 GC gc; 12620 12621 __gshared bool fontAttempted; 12622 12623 __gshared XFontStruct* defaultfont; 12624 __gshared XFontSet defaultfontset; 12625 12626 XFontStruct* font; 12627 XFontSet fontset; 12628 12629 void create(PaintingHandle window) { 12630 this.display = XDisplayConnection.get(); 12631 12632 Drawable buffer = None; 12633 if(auto sw = cast(SimpleWindow) this.window) { 12634 buffer = sw.impl.buffer; 12635 this.destiny = cast(Drawable) window; 12636 } else { 12637 buffer = cast(Drawable) window; 12638 this.destiny = None; 12639 } 12640 12641 this.d = cast(Drawable) buffer; 12642 12643 auto dgc = DefaultGC(display, DefaultScreen(display)); 12644 12645 this.gc = XCreateGC(display, d, 0, null); 12646 12647 XCopyGC(display, dgc, 0xffffffff, this.gc); 12648 12649 ensureDefaultFontLoaded(); 12650 12651 font = defaultfont; 12652 fontset = defaultfontset; 12653 12654 if(font) { 12655 XSetFont(display, gc, font.fid); 12656 } 12657 } 12658 12659 static void ensureDefaultFontLoaded() { 12660 if(!fontAttempted) { 12661 auto display = XDisplayConnection.get; 12662 auto font = XLoadQueryFont(display, xfontstr.ptr); 12663 // if the user font choice fails, fixed is pretty reliable (required by X to start!) and not bad either 12664 if(font is null) { 12665 xfontstr = "-*-fixed-medium-r-*-*-13-*-*-*-*-*-*-*"; 12666 font = XLoadQueryFont(display, xfontstr.ptr); 12667 } 12668 12669 char** lol; 12670 int lol2; 12671 char* lol3; 12672 auto fontset = XCreateFontSet(display, xfontstr.ptr, &lol, &lol2, &lol3); 12673 12674 fontAttempted = true; 12675 12676 defaultfont = font; 12677 defaultfontset = fontset; 12678 } 12679 } 12680 12681 arsd.color.Rectangle _clipRectangle; 12682 void setClipRectangle(int x, int y, int width, int height) { 12683 auto old = _clipRectangle; 12684 _clipRectangle = arsd.color.Rectangle(Point(x, y), Size(width, height)); 12685 if(old == _clipRectangle) 12686 return; 12687 12688 if(width == 0 || height == 0) { 12689 XSetClipMask(display, gc, None); 12690 12691 if(xrenderPicturePainter) { 12692 12693 XRectangle[1] rects; 12694 rects[0] = XRectangle(short.min, short.min, short.max, short.max); 12695 XRenderSetPictureClipRectangles(display, xrenderPicturePainter, 0, 0, rects.ptr, cast(int) rects.length); 12696 } 12697 12698 version(with_xft) { 12699 if(xftFont is null || xftDraw is null) 12700 return; 12701 XftDrawSetClip(xftDraw, null); 12702 } 12703 } else { 12704 XRectangle[1] rects; 12705 rects[0] = XRectangle(cast(short)(x), cast(short)(y), cast(short) width, cast(short) height); 12706 XSetClipRectangles(XDisplayConnection.get, gc, 0, 0, rects.ptr, 1, 0); 12707 12708 if(xrenderPicturePainter) 12709 XRenderSetPictureClipRectangles(display, xrenderPicturePainter, 0, 0, rects.ptr, cast(int) rects.length); 12710 12711 version(with_xft) { 12712 if(xftFont is null || xftDraw is null) 12713 return; 12714 XftDrawSetClipRectangles(xftDraw, 0, 0, rects.ptr, 1); 12715 } 12716 } 12717 } 12718 12719 version(with_xft) { 12720 XftFont* xftFont; 12721 XftDraw* xftDraw; 12722 12723 XftColor xftColor; 12724 12725 void updateXftColor() { 12726 if(xftFont is null) 12727 return; 12728 12729 // not bothering with XftColorFree since p sure i don't need it on 24 bit displays.... 12730 XRenderColor colorIn = XRenderColor(_outlineColor.r * 255, _outlineColor.g * 255, _outlineColor.b * 255, _outlineColor.a * 255); 12731 12732 XftColorAllocValue( 12733 display, 12734 DefaultVisual(display, DefaultScreen(display)), 12735 DefaultColormap(display, 0), 12736 &colorIn, 12737 &xftColor 12738 ); 12739 } 12740 } 12741 12742 private OperatingSystemFont _activeFont; 12743 void setFont(OperatingSystemFont font) { 12744 _activeFont = font; 12745 version(with_xft) { 12746 if(font && font.isXft && font.xftFont) 12747 this.xftFont = font.xftFont; 12748 else 12749 this.xftFont = null; 12750 12751 if(this.xftFont) { 12752 if(xftDraw is null) { 12753 xftDraw = XftDrawCreate( 12754 display, 12755 d, 12756 DefaultVisual(display, DefaultScreen(display)), 12757 DefaultColormap(display, 0) 12758 ); 12759 12760 updateXftColor(); 12761 } 12762 12763 return; 12764 } 12765 } 12766 12767 if(font && font.font) { 12768 this.font = font.font; 12769 this.fontset = font.fontset; 12770 XSetFont(display, gc, font.font.fid); 12771 } else { 12772 this.font = defaultfont; 12773 this.fontset = defaultfontset; 12774 } 12775 12776 } 12777 12778 private Picture xrenderPicturePainter; 12779 12780 bool manualInvalidations; 12781 void invalidateRect(Rectangle invalidRect) { 12782 // FIXME if manualInvalidations 12783 } 12784 12785 void dispose() { 12786 this.rasterOp = RasterOp.normal; 12787 12788 if(xrenderPicturePainter) { 12789 XRenderFreePicture(display, xrenderPicturePainter); 12790 xrenderPicturePainter = None; 12791 } 12792 12793 // FIXME: this.window.width/height is probably wrong 12794 12795 // src x,y then dest x, y 12796 if(destiny != None) { 12797 // FIXME: if manual invalidations we can actually only copy some of the area. 12798 // if(manualInvalidations) 12799 XSetClipMask(display, gc, None); 12800 XCopyArea(display, d, destiny, gc, 0, 0, this.window.width, this.window.height, 0, 0); 12801 } 12802 12803 XFreeGC(display, gc); 12804 12805 version(with_xft) 12806 if(xftDraw) { 12807 XftDrawDestroy(xftDraw); 12808 xftDraw = null; 12809 } 12810 12811 /+ 12812 // this should prolly legit never be used since if it destroys the font handle from a OperatingSystemFont, it also ruins a reusable resource. 12813 if(font && font !is defaultfont) { 12814 XFreeFont(display, font); 12815 font = null; 12816 } 12817 if(fontset && fontset !is defaultfontset) { 12818 XFreeFontSet(display, fontset); 12819 fontset = null; 12820 } 12821 +/ 12822 XFlush(display); 12823 12824 if(window.paintingFinishedDg !is null) 12825 window.paintingFinishedDg()(); 12826 } 12827 12828 bool backgroundIsNotTransparent = true; 12829 bool foregroundIsNotTransparent = true; 12830 12831 bool _penInitialized = false; 12832 Pen _activePen; 12833 12834 Color _outlineColor; 12835 Color _fillColor; 12836 12837 @property void pen(Pen p) { 12838 if(_penInitialized && p == _activePen) { 12839 return; 12840 } 12841 _penInitialized = true; 12842 _activePen = p; 12843 _outlineColor = p.color; 12844 12845 int style; 12846 12847 byte dashLength; 12848 12849 final switch(p.style) { 12850 case Pen.Style.Solid: 12851 style = 0 /*LineSolid*/; 12852 break; 12853 case Pen.Style.Dashed: 12854 style = 1 /*LineOnOffDash*/; 12855 dashLength = 4; 12856 break; 12857 case Pen.Style.Dotted: 12858 style = 1 /*LineOnOffDash*/; 12859 dashLength = 1; 12860 break; 12861 } 12862 12863 XSetLineAttributes(display, gc, p.width, style, style == 0 ? 3 : 0, 0); 12864 if(dashLength) 12865 XSetDashes(display, gc, 0, &dashLength, 1); 12866 12867 if(p.color.a == 0) { 12868 foregroundIsNotTransparent = false; 12869 return; 12870 } 12871 12872 foregroundIsNotTransparent = true; 12873 12874 XSetForeground(display, gc, colorToX(p.color, display)); 12875 12876 version(with_xft) 12877 updateXftColor(); 12878 } 12879 12880 RasterOp _currentRasterOp; 12881 bool _currentRasterOpInitialized = false; 12882 @property void rasterOp(RasterOp op) { 12883 if(_currentRasterOpInitialized && _currentRasterOp == op) 12884 return; 12885 _currentRasterOp = op; 12886 _currentRasterOpInitialized = true; 12887 int mode; 12888 final switch(op) { 12889 case RasterOp.normal: 12890 mode = GXcopy; 12891 break; 12892 case RasterOp.xor: 12893 mode = GXxor; 12894 break; 12895 } 12896 XSetFunction(display, gc, mode); 12897 } 12898 12899 12900 bool _fillColorInitialized = false; 12901 12902 @property void fillColor(Color c) { 12903 if(_fillColorInitialized && _fillColor == c) 12904 return; // already good, no need to waste time calling it 12905 _fillColor = c; 12906 _fillColorInitialized = true; 12907 if(c.a == 0) { 12908 backgroundIsNotTransparent = false; 12909 return; 12910 } 12911 12912 backgroundIsNotTransparent = true; 12913 12914 XSetBackground(display, gc, colorToX(c, display)); 12915 12916 } 12917 12918 void swapColors() { 12919 auto tmp = _fillColor; 12920 fillColor = _outlineColor; 12921 auto newPen = _activePen; 12922 newPen.color = tmp; 12923 pen(newPen); 12924 } 12925 12926 uint colorToX(Color c, Display* display) { 12927 auto visual = DefaultVisual(display, DefaultScreen(display)); 12928 import core.bitop; 12929 uint color = 0; 12930 { 12931 auto startBit = bsf(visual.red_mask); 12932 auto lastBit = bsr(visual.red_mask); 12933 auto r = cast(uint) c.r; 12934 r >>= 7 - (lastBit - startBit); 12935 r <<= startBit; 12936 color |= r; 12937 } 12938 { 12939 auto startBit = bsf(visual.green_mask); 12940 auto lastBit = bsr(visual.green_mask); 12941 auto g = cast(uint) c.g; 12942 g >>= 7 - (lastBit - startBit); 12943 g <<= startBit; 12944 color |= g; 12945 } 12946 { 12947 auto startBit = bsf(visual.blue_mask); 12948 auto lastBit = bsr(visual.blue_mask); 12949 auto b = cast(uint) c.b; 12950 b >>= 7 - (lastBit - startBit); 12951 b <<= startBit; 12952 color |= b; 12953 } 12954 12955 12956 12957 return color; 12958 } 12959 12960 void drawImage(int x, int y, Image i, int ix, int iy, int w, int h) { 12961 // source x, source y 12962 if(ix >= i.width) return; 12963 if(iy >= i.height) return; 12964 if(ix + w > i.width) w = i.width - ix; 12965 if(iy + h > i.height) h = i.height - iy; 12966 if(i.usingXshm) 12967 XShmPutImage(display, d, gc, i.handle, ix, iy, x, y, w, h, false); 12968 else 12969 XPutImage(display, d, gc, i.handle, ix, iy, x, y, w, h); 12970 } 12971 12972 void drawPixmap(Sprite s, int x, int y, int ix, int iy, int w, int h) { 12973 if(s.enableAlpha) { 12974 // the Sprite must be created first, meaning if we're here, XRender is already loaded 12975 if(this.xrenderPicturePainter == None) { 12976 XRenderPictureAttributes attrs; 12977 // FIXME: I can prolly reuse this as long as the pixmap itself is valid. 12978 xrenderPicturePainter = XRenderCreatePicture(display, d, Sprite.RGB24, 0, &attrs); 12979 12980 // need to initialize the clip 12981 XRectangle[1] rects; 12982 rects[0] = XRectangle(cast(short)(_clipRectangle.left), cast(short)(_clipRectangle.top), cast(short) _clipRectangle.width, cast(short) _clipRectangle.height); 12983 12984 if(_clipRectangle != Rectangle.init) 12985 XRenderSetPictureClipRectangles(display, xrenderPicturePainter, 0, 0, rects.ptr, cast(int) rects.length); 12986 } 12987 12988 XRenderComposite( 12989 display, 12990 3, // PicOpOver 12991 s.xrenderPicture, 12992 None, 12993 this.xrenderPicturePainter, 12994 ix, 12995 iy, 12996 0, 12997 0, 12998 x, 12999 y, 13000 w ? w : s.width, 13001 h ? h : s.height 13002 ); 13003 } else { 13004 XCopyArea(display, s.handle, d, gc, ix, iy, w ? w : s.width, h ? h : s.height, x, y); 13005 } 13006 } 13007 13008 int fontHeight() { 13009 version(with_xft) 13010 if(xftFont !is null) 13011 return xftFont.height; 13012 if(font) 13013 return font.max_bounds.ascent + font.max_bounds.descent; 13014 return 12; // pretty common default... 13015 } 13016 13017 int textWidth(in char[] line) { 13018 version(with_xft) 13019 if(xftFont) { 13020 if(line.length == 0) 13021 return 0; 13022 XGlyphInfo extents; 13023 XftTextExtentsUtf8(display, xftFont, line.ptr, cast(int) line.length, &extents); 13024 return extents.width; 13025 } 13026 13027 if(fontset) { 13028 if(line.length == 0) 13029 return 0; 13030 XRectangle rect; 13031 Xutf8TextExtents(fontset, line.ptr, cast(int) line.length, null, &rect); 13032 13033 return rect.width; 13034 } 13035 13036 if(font) 13037 // FIXME: unicode 13038 return XTextWidth( font, line.ptr, cast(int) line.length); 13039 else 13040 return fontHeight / 2 * cast(int) line.length; // if no font is loaded, it is prolly Fixed, which is a 2:1 ratio 13041 } 13042 13043 Size textSize(in char[] text) { 13044 auto maxWidth = 0; 13045 auto lineHeight = fontHeight; 13046 int h = text.length ? 0 : lineHeight + 4; // if text is empty, it still gives the line height 13047 foreach(line; text.split('\n')) { 13048 int textWidth = this.textWidth(line); 13049 if(textWidth > maxWidth) 13050 maxWidth = textWidth; 13051 h += lineHeight + 4; 13052 } 13053 return Size(maxWidth, h); 13054 } 13055 13056 void drawText(in int x, in int y, in int x2, in int y2, in char[] originalText, in uint alignment) { 13057 const(char)[] text; 13058 version(with_xft) 13059 if(xftFont) { 13060 text = originalText; 13061 goto loaded; 13062 } 13063 13064 if(fontset) 13065 text = originalText; 13066 else { 13067 text.reserve(originalText.length); 13068 // the first 256 unicode codepoints are the same as ascii and latin-1, which is what X expects, so we can keep all those 13069 // then strip the rest so there isn't garbage 13070 foreach(dchar ch; originalText) 13071 if(ch < 256) 13072 text ~= cast(ubyte) ch; 13073 else 13074 text ~= 191; // FIXME: using a random character (upside down question mark) to fill the space 13075 } 13076 loaded: 13077 if(text.length == 0) 13078 return; 13079 13080 // FIXME: should we clip it to the bounding box? 13081 int textHeight = fontHeight; 13082 13083 auto lines = text.split('\n'); 13084 13085 const lineHeight = textHeight; 13086 textHeight *= lines.length; 13087 13088 int cy = y; 13089 13090 if(alignment & TextAlignment.VerticalBottom) { 13091 if(y2 <= 0) 13092 return; 13093 auto h = y2 - y; 13094 if(h > textHeight) { 13095 cy += h - textHeight; 13096 cy -= lineHeight / 2; 13097 } 13098 } else if(alignment & TextAlignment.VerticalCenter) { 13099 if(y2 <= 0) 13100 return; 13101 auto h = y2 - y; 13102 if(textHeight < h) { 13103 cy += (h - textHeight) / 2; 13104 //cy -= lineHeight / 4; 13105 } 13106 } 13107 13108 foreach(line; text.split('\n')) { 13109 int textWidth = this.textWidth(line); 13110 13111 int px = x, py = cy; 13112 13113 if(alignment & TextAlignment.Center) { 13114 if(x2 <= 0) 13115 return; 13116 auto w = x2 - x; 13117 if(w > textWidth) 13118 px += (w - textWidth) / 2; 13119 } else if(alignment & TextAlignment.Right) { 13120 if(x2 <= 0) 13121 return; 13122 auto pos = x2 - textWidth; 13123 if(pos > x) 13124 px = pos; 13125 } 13126 13127 version(with_xft) 13128 if(xftFont) { 13129 XftDrawStringUtf8(xftDraw, &xftColor, xftFont, px, py + xftFont.ascent, line.ptr, cast(int) line.length); 13130 13131 goto carry_on; 13132 } 13133 13134 if(fontset) 13135 Xutf8DrawString(display, d, fontset, gc, px, py + (font ? font.max_bounds.ascent : lineHeight), line.ptr, cast(int) line.length); 13136 else 13137 XDrawString(display, d, gc, px, py + (font ? font.max_bounds.ascent : lineHeight), line.ptr, cast(int) line.length); 13138 carry_on: 13139 cy += lineHeight + 4; 13140 } 13141 } 13142 13143 void drawPixel(int x, int y) { 13144 XDrawPoint(display, d, gc, x, y); 13145 } 13146 13147 // The basic shapes, outlined 13148 13149 void drawLine(int x1, int y1, int x2, int y2) { 13150 if(foregroundIsNotTransparent) 13151 XDrawLine(display, d, gc, x1, y1, x2, y2); 13152 } 13153 13154 void drawRectangle(int x, int y, int width, int height) { 13155 if(backgroundIsNotTransparent) { 13156 swapColors(); 13157 XFillRectangle(display, d, gc, x+1, y+1, width-2, height-2); // Need to ensure pixels are only drawn once... 13158 swapColors(); 13159 } 13160 // 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 13161 if(foregroundIsNotTransparent) 13162 XDrawRectangle(display, d, gc, x + _activePen.width / 2, y + _activePen.width / 2, width - 1 - _activePen.width / 2, height - 1 - _activePen.width / 2); 13163 } 13164 13165 /// Arguments are the points of the bounding rectangle 13166 void drawEllipse(int x1, int y1, int x2, int y2) { 13167 drawArc(x1, y1, x2 - x1, y2 - y1, 0, 360 * 64); 13168 } 13169 13170 // NOTE: start and finish are in units of degrees * 64 13171 void drawArc(int x1, int y1, int width, int height, int start, int finish) { 13172 if(backgroundIsNotTransparent) { 13173 swapColors(); 13174 XFillArc(display, d, gc, x1, y1, width, height, start, finish); 13175 swapColors(); 13176 } 13177 if(foregroundIsNotTransparent) { 13178 XDrawArc(display, d, gc, x1, y1, width, height, start, finish); 13179 13180 // Windows draws the straight lines on the edges too so FIXME sort of 13181 } 13182 } 13183 13184 void drawPolygon(Point[] vertexes) { 13185 XPoint[16] pointsBuffer; 13186 XPoint[] points; 13187 if(vertexes.length <= pointsBuffer.length) 13188 points = pointsBuffer[0 .. vertexes.length]; 13189 else 13190 points.length = vertexes.length; 13191 13192 foreach(i, p; vertexes) { 13193 points[i].x = cast(short) p.x; 13194 points[i].y = cast(short) p.y; 13195 } 13196 13197 if(backgroundIsNotTransparent) { 13198 swapColors(); 13199 XFillPolygon(display, d, gc, points.ptr, cast(int) points.length, PolygonShape.Complex, CoordMode.CoordModeOrigin); 13200 swapColors(); 13201 } 13202 if(foregroundIsNotTransparent) { 13203 XDrawLines(display, d, gc, points.ptr, cast(int) points.length, CoordMode.CoordModeOrigin); 13204 } 13205 } 13206 } 13207 13208 /* XRender { */ 13209 13210 struct XRenderColor { 13211 ushort red; 13212 ushort green; 13213 ushort blue; 13214 ushort alpha; 13215 } 13216 13217 alias Picture = XID; 13218 alias PictFormat = XID; 13219 13220 struct XGlyphInfo { 13221 ushort width; 13222 ushort height; 13223 short x; 13224 short y; 13225 short xOff; 13226 short yOff; 13227 } 13228 13229 struct XRenderDirectFormat { 13230 short red; 13231 short redMask; 13232 short green; 13233 short greenMask; 13234 short blue; 13235 short blueMask; 13236 short alpha; 13237 short alphaMask; 13238 } 13239 13240 struct XRenderPictFormat { 13241 PictFormat id; 13242 int type; 13243 int depth; 13244 XRenderDirectFormat direct; 13245 Colormap colormap; 13246 } 13247 13248 enum PictFormatID = (1 << 0); 13249 enum PictFormatType = (1 << 1); 13250 enum PictFormatDepth = (1 << 2); 13251 enum PictFormatRed = (1 << 3); 13252 enum PictFormatRedMask =(1 << 4); 13253 enum PictFormatGreen = (1 << 5); 13254 enum PictFormatGreenMask=(1 << 6); 13255 enum PictFormatBlue = (1 << 7); 13256 enum PictFormatBlueMask =(1 << 8); 13257 enum PictFormatAlpha = (1 << 9); 13258 enum PictFormatAlphaMask=(1 << 10); 13259 enum PictFormatColormap =(1 << 11); 13260 13261 struct XRenderPictureAttributes { 13262 int repeat; 13263 Picture alpha_map; 13264 int alpha_x_origin; 13265 int alpha_y_origin; 13266 int clip_x_origin; 13267 int clip_y_origin; 13268 Pixmap clip_mask; 13269 Bool graphics_exposures; 13270 int subwindow_mode; 13271 int poly_edge; 13272 int poly_mode; 13273 Atom dither; 13274 Bool component_alpha; 13275 } 13276 13277 alias int XFixed; 13278 13279 struct XPointFixed { 13280 XFixed x, y; 13281 } 13282 13283 struct XCircle { 13284 XFixed x; 13285 XFixed y; 13286 XFixed radius; 13287 } 13288 13289 struct XTransform { 13290 XFixed[3][3] matrix; 13291 } 13292 13293 struct XFilters { 13294 int nfilter; 13295 char **filter; 13296 int nalias; 13297 short *alias_; 13298 } 13299 13300 struct XIndexValue { 13301 c_ulong pixel; 13302 ushort red, green, blue, alpha; 13303 } 13304 13305 struct XAnimCursor { 13306 Cursor cursor; 13307 c_ulong delay; 13308 } 13309 13310 struct XLinearGradient { 13311 XPointFixed p1; 13312 XPointFixed p2; 13313 } 13314 13315 struct XRadialGradient { 13316 XCircle inner; 13317 XCircle outer; 13318 } 13319 13320 struct XConicalGradient { 13321 XPointFixed center; 13322 XFixed angle; /* in degrees */ 13323 } 13324 13325 enum PictStandardARGB32 = 0; 13326 enum PictStandardRGB24 = 1; 13327 enum PictStandardA8 = 2; 13328 enum PictStandardA4 = 3; 13329 enum PictStandardA1 = 4; 13330 enum PictStandardNUM = 5; 13331 13332 interface XRender { 13333 extern(C) @nogc: 13334 13335 Bool XRenderQueryExtension (Display *dpy, int *event_basep, int *error_basep); 13336 13337 Status XRenderQueryVersion (Display *dpy, 13338 int *major_versionp, 13339 int *minor_versionp); 13340 13341 Status XRenderQueryFormats (Display *dpy); 13342 13343 int XRenderQuerySubpixelOrder (Display *dpy, int screen); 13344 13345 Bool XRenderSetSubpixelOrder (Display *dpy, int screen, int subpixel); 13346 13347 XRenderPictFormat * 13348 XRenderFindVisualFormat (Display *dpy, const Visual *visual); 13349 13350 XRenderPictFormat * 13351 XRenderFindFormat (Display *dpy, 13352 c_ulong mask, 13353 const XRenderPictFormat *templ, 13354 int count); 13355 XRenderPictFormat * 13356 XRenderFindStandardFormat (Display *dpy, 13357 int format); 13358 13359 XIndexValue * 13360 XRenderQueryPictIndexValues(Display *dpy, 13361 const XRenderPictFormat *format, 13362 int *num); 13363 13364 Picture XRenderCreatePicture( 13365 Display *dpy, 13366 Drawable drawable, 13367 const XRenderPictFormat *format, 13368 c_ulong valuemask, 13369 const XRenderPictureAttributes *attributes); 13370 13371 void XRenderChangePicture (Display *dpy, 13372 Picture picture, 13373 c_ulong valuemask, 13374 const XRenderPictureAttributes *attributes); 13375 13376 void 13377 XRenderSetPictureClipRectangles (Display *dpy, 13378 Picture picture, 13379 int xOrigin, 13380 int yOrigin, 13381 const XRectangle *rects, 13382 int n); 13383 13384 void 13385 XRenderSetPictureClipRegion (Display *dpy, 13386 Picture picture, 13387 Region r); 13388 13389 void 13390 XRenderSetPictureTransform (Display *dpy, 13391 Picture picture, 13392 XTransform *transform); 13393 13394 void 13395 XRenderFreePicture (Display *dpy, 13396 Picture picture); 13397 13398 void 13399 XRenderComposite (Display *dpy, 13400 int op, 13401 Picture src, 13402 Picture mask, 13403 Picture dst, 13404 int src_x, 13405 int src_y, 13406 int mask_x, 13407 int mask_y, 13408 int dst_x, 13409 int dst_y, 13410 uint width, 13411 uint height); 13412 13413 13414 Picture XRenderCreateSolidFill (Display *dpy, 13415 const XRenderColor *color); 13416 13417 Picture XRenderCreateLinearGradient (Display *dpy, 13418 const XLinearGradient *gradient, 13419 const XFixed *stops, 13420 const XRenderColor *colors, 13421 int nstops); 13422 13423 Picture XRenderCreateRadialGradient (Display *dpy, 13424 const XRadialGradient *gradient, 13425 const XFixed *stops, 13426 const XRenderColor *colors, 13427 int nstops); 13428 13429 Picture XRenderCreateConicalGradient (Display *dpy, 13430 const XConicalGradient *gradient, 13431 const XFixed *stops, 13432 const XRenderColor *colors, 13433 int nstops); 13434 13435 13436 13437 Cursor 13438 XRenderCreateCursor (Display *dpy, 13439 Picture source, 13440 uint x, 13441 uint y); 13442 13443 XFilters * 13444 XRenderQueryFilters (Display *dpy, Drawable drawable); 13445 13446 void 13447 XRenderSetPictureFilter (Display *dpy, 13448 Picture picture, 13449 const char *filter, 13450 XFixed *params, 13451 int nparams); 13452 13453 Cursor 13454 XRenderCreateAnimCursor (Display *dpy, 13455 int ncursor, 13456 XAnimCursor *cursors); 13457 } 13458 13459 __gshared bool XRenderLibrarySuccessfullyLoaded = true; 13460 mixin DynamicLoad!(XRender, "Xrender", 1, XRenderLibrarySuccessfullyLoaded) XRenderLibrary; 13461 13462 /* XRender } */ 13463 13464 /* Xrandr { */ 13465 13466 struct XRRMonitorInfo { 13467 Atom name; 13468 Bool primary; 13469 Bool automatic; 13470 int noutput; 13471 int x; 13472 int y; 13473 int width; 13474 int height; 13475 int mwidth; 13476 int mheight; 13477 /*RROutput*/ void *outputs; 13478 } 13479 13480 struct XRRScreenChangeNotifyEvent { 13481 int type; /* event base */ 13482 c_ulong serial; /* # of last request processed by server */ 13483 Bool send_event; /* true if this came from a SendEvent request */ 13484 Display *display; /* Display the event was read from */ 13485 Window window; /* window which selected for this event */ 13486 Window root; /* Root window for changed screen */ 13487 Time timestamp; /* when the screen change occurred */ 13488 Time config_timestamp; /* when the last configuration change */ 13489 ushort/*SizeID*/ size_index; 13490 ushort/*SubpixelOrder*/ subpixel_order; 13491 ushort/*Rotation*/ rotation; 13492 int width; 13493 int height; 13494 int mwidth; 13495 int mheight; 13496 } 13497 13498 enum RRScreenChangeNotify = 0; 13499 13500 enum RRScreenChangeNotifyMask = 1; 13501 13502 __gshared int xrrEventBase = -1; 13503 13504 13505 interface XRandr { 13506 extern(C) @nogc: 13507 Bool XRRQueryExtension (Display *dpy, int *event_base_return, int *error_base_return); 13508 Status XRRQueryVersion (Display *dpy, int *major_version_return, int *minor_version_return); 13509 13510 XRRMonitorInfo * XRRGetMonitors(Display *dpy, Window window, Bool get_active, int *nmonitors); 13511 void XRRFreeMonitors(XRRMonitorInfo *monitors); 13512 13513 void XRRSelectInput(Display *dpy, Window window, int mask); 13514 } 13515 13516 __gshared bool XRandrLibrarySuccessfullyLoaded = true; 13517 mixin DynamicLoad!(XRandr, "Xrandr", 2, XRandrLibrarySuccessfullyLoaded) XRandrLibrary; 13518 /* Xrandr } */ 13519 13520 /* Xft { */ 13521 13522 // actually freetype 13523 alias void FT_Face; 13524 13525 // actually fontconfig 13526 private alias FcBool = int; 13527 alias void FcCharSet; 13528 alias void FcPattern; 13529 alias void FcResult; 13530 enum FcEndian { FcEndianBig, FcEndianLittle } 13531 struct FcFontSet { 13532 int nfont; 13533 int sfont; 13534 FcPattern** fonts; 13535 } 13536 13537 // actually XRegion 13538 struct BOX { 13539 short x1, x2, y1, y2; 13540 } 13541 struct _XRegion { 13542 c_long size; 13543 c_long numRects; 13544 BOX* rects; 13545 BOX extents; 13546 } 13547 13548 alias Region = _XRegion*; 13549 13550 // ok actually Xft 13551 13552 struct XftFontInfo; 13553 13554 struct XftFont { 13555 int ascent; 13556 int descent; 13557 int height; 13558 int max_advance_width; 13559 FcCharSet* charset; 13560 FcPattern* pattern; 13561 } 13562 13563 struct XftDraw; 13564 13565 struct XftColor { 13566 c_ulong pixel; 13567 XRenderColor color; 13568 } 13569 13570 struct XftCharSpec { 13571 dchar ucs4; 13572 short x; 13573 short y; 13574 } 13575 13576 struct XftCharFontSpec { 13577 XftFont *font; 13578 dchar ucs4; 13579 short x; 13580 short y; 13581 } 13582 13583 struct XftGlyphSpec { 13584 uint glyph; 13585 short x; 13586 short y; 13587 } 13588 13589 struct XftGlyphFontSpec { 13590 XftFont *font; 13591 uint glyph; 13592 short x; 13593 short y; 13594 } 13595 13596 interface Xft { 13597 extern(C) @nogc pure: 13598 13599 Bool XftColorAllocName (Display *dpy, 13600 const Visual *visual, 13601 Colormap cmap, 13602 const char *name, 13603 XftColor *result); 13604 13605 Bool XftColorAllocValue (Display *dpy, 13606 Visual *visual, 13607 Colormap cmap, 13608 const XRenderColor *color, 13609 XftColor *result); 13610 13611 void XftColorFree (Display *dpy, 13612 Visual *visual, 13613 Colormap cmap, 13614 XftColor *color); 13615 13616 Bool XftDefaultHasRender (Display *dpy); 13617 13618 Bool XftDefaultSet (Display *dpy, FcPattern *defaults); 13619 13620 void XftDefaultSubstitute (Display *dpy, int screen, FcPattern *pattern); 13621 13622 XftDraw * XftDrawCreate (Display *dpy, 13623 Drawable drawable, 13624 Visual *visual, 13625 Colormap colormap); 13626 13627 XftDraw * XftDrawCreateBitmap (Display *dpy, 13628 Pixmap bitmap); 13629 13630 XftDraw * XftDrawCreateAlpha (Display *dpy, 13631 Pixmap pixmap, 13632 int depth); 13633 13634 void XftDrawChange (XftDraw *draw, 13635 Drawable drawable); 13636 13637 Display * XftDrawDisplay (XftDraw *draw); 13638 13639 Drawable XftDrawDrawable (XftDraw *draw); 13640 13641 Colormap XftDrawColormap (XftDraw *draw); 13642 13643 Visual * XftDrawVisual (XftDraw *draw); 13644 13645 void XftDrawDestroy (XftDraw *draw); 13646 13647 Picture XftDrawPicture (XftDraw *draw); 13648 13649 Picture XftDrawSrcPicture (XftDraw *draw, const XftColor *color); 13650 13651 void XftDrawGlyphs (XftDraw *draw, 13652 const XftColor *color, 13653 XftFont *pub, 13654 int x, 13655 int y, 13656 const uint *glyphs, 13657 int nglyphs); 13658 13659 void XftDrawString8 (XftDraw *draw, 13660 const XftColor *color, 13661 XftFont *pub, 13662 int x, 13663 int y, 13664 const char *string, 13665 int len); 13666 13667 void XftDrawString16 (XftDraw *draw, 13668 const XftColor *color, 13669 XftFont *pub, 13670 int x, 13671 int y, 13672 const wchar *string, 13673 int len); 13674 13675 void XftDrawString32 (XftDraw *draw, 13676 const XftColor *color, 13677 XftFont *pub, 13678 int x, 13679 int y, 13680 const dchar *string, 13681 int len); 13682 13683 void XftDrawStringUtf8 (XftDraw *draw, 13684 const XftColor *color, 13685 XftFont *pub, 13686 int x, 13687 int y, 13688 const char *string, 13689 int len); 13690 void XftDrawStringUtf16 (XftDraw *draw, 13691 const XftColor *color, 13692 XftFont *pub, 13693 int x, 13694 int y, 13695 const char *string, 13696 FcEndian endian, 13697 int len); 13698 13699 void XftDrawCharSpec (XftDraw *draw, 13700 const XftColor *color, 13701 XftFont *pub, 13702 const XftCharSpec *chars, 13703 int len); 13704 13705 void XftDrawCharFontSpec (XftDraw *draw, 13706 const XftColor *color, 13707 const XftCharFontSpec *chars, 13708 int len); 13709 13710 void XftDrawGlyphSpec (XftDraw *draw, 13711 const XftColor *color, 13712 XftFont *pub, 13713 const XftGlyphSpec *glyphs, 13714 int len); 13715 13716 void XftDrawGlyphFontSpec (XftDraw *draw, 13717 const XftColor *color, 13718 const XftGlyphFontSpec *glyphs, 13719 int len); 13720 13721 void XftDrawRect (XftDraw *draw, 13722 const XftColor *color, 13723 int x, 13724 int y, 13725 uint width, 13726 uint height); 13727 13728 Bool XftDrawSetClip (XftDraw *draw, 13729 Region r); 13730 13731 13732 Bool XftDrawSetClipRectangles (XftDraw *draw, 13733 int xOrigin, 13734 int yOrigin, 13735 const XRectangle *rects, 13736 int n); 13737 13738 void XftDrawSetSubwindowMode (XftDraw *draw, 13739 int mode); 13740 13741 void XftGlyphExtents (Display *dpy, 13742 XftFont *pub, 13743 const uint *glyphs, 13744 int nglyphs, 13745 XGlyphInfo *extents); 13746 13747 void XftTextExtents8 (Display *dpy, 13748 XftFont *pub, 13749 const char *string, 13750 int len, 13751 XGlyphInfo *extents); 13752 13753 void XftTextExtents16 (Display *dpy, 13754 XftFont *pub, 13755 const wchar *string, 13756 int len, 13757 XGlyphInfo *extents); 13758 13759 void XftTextExtents32 (Display *dpy, 13760 XftFont *pub, 13761 const dchar *string, 13762 int len, 13763 XGlyphInfo *extents); 13764 13765 void XftTextExtentsUtf8 (Display *dpy, 13766 XftFont *pub, 13767 const char *string, 13768 int len, 13769 XGlyphInfo *extents); 13770 13771 void XftTextExtentsUtf16 (Display *dpy, 13772 XftFont *pub, 13773 const char *string, 13774 FcEndian endian, 13775 int len, 13776 XGlyphInfo *extents); 13777 13778 FcPattern * XftFontMatch (Display *dpy, 13779 int screen, 13780 const FcPattern *pattern, 13781 FcResult *result); 13782 13783 XftFont * XftFontOpen (Display *dpy, int screen, ...); 13784 13785 XftFont * XftFontOpenName (Display *dpy, int screen, const char *name); 13786 13787 XftFont * XftFontOpenXlfd (Display *dpy, int screen, const char *xlfd); 13788 13789 FT_Face XftLockFace (XftFont *pub); 13790 13791 void XftUnlockFace (XftFont *pub); 13792 13793 XftFontInfo * XftFontInfoCreate (Display *dpy, const FcPattern *pattern); 13794 13795 void XftFontInfoDestroy (Display *dpy, XftFontInfo *fi); 13796 13797 dchar XftFontInfoHash (const XftFontInfo *fi); 13798 13799 FcBool XftFontInfoEqual (const XftFontInfo *a, const XftFontInfo *b); 13800 13801 XftFont * XftFontOpenInfo (Display *dpy, 13802 FcPattern *pattern, 13803 XftFontInfo *fi); 13804 13805 XftFont * XftFontOpenPattern (Display *dpy, FcPattern *pattern); 13806 13807 XftFont * XftFontCopy (Display *dpy, XftFont *pub); 13808 13809 void XftFontClose (Display *dpy, XftFont *pub); 13810 13811 FcBool XftInitFtLibrary(); 13812 void XftFontLoadGlyphs (Display *dpy, 13813 XftFont *pub, 13814 FcBool need_bitmaps, 13815 const uint *glyphs, 13816 int nglyph); 13817 13818 void XftFontUnloadGlyphs (Display *dpy, 13819 XftFont *pub, 13820 const uint *glyphs, 13821 int nglyph); 13822 13823 FcBool XftFontCheckGlyph (Display *dpy, 13824 XftFont *pub, 13825 FcBool need_bitmaps, 13826 uint glyph, 13827 uint *missing, 13828 int *nmissing); 13829 13830 FcBool XftCharExists (Display *dpy, 13831 XftFont *pub, 13832 dchar ucs4); 13833 13834 uint XftCharIndex (Display *dpy, 13835 XftFont *pub, 13836 dchar ucs4); 13837 FcBool XftInit (const char *config); 13838 13839 int XftGetVersion (); 13840 13841 FcFontSet * XftListFonts (Display *dpy, 13842 int screen, 13843 ...); 13844 13845 FcPattern *XftNameParse (const char *name); 13846 13847 void XftGlyphRender (Display *dpy, 13848 int op, 13849 Picture src, 13850 XftFont *pub, 13851 Picture dst, 13852 int srcx, 13853 int srcy, 13854 int x, 13855 int y, 13856 const uint *glyphs, 13857 int nglyphs); 13858 13859 void XftGlyphSpecRender (Display *dpy, 13860 int op, 13861 Picture src, 13862 XftFont *pub, 13863 Picture dst, 13864 int srcx, 13865 int srcy, 13866 const XftGlyphSpec *glyphs, 13867 int nglyphs); 13868 13869 void XftCharSpecRender (Display *dpy, 13870 int op, 13871 Picture src, 13872 XftFont *pub, 13873 Picture dst, 13874 int srcx, 13875 int srcy, 13876 const XftCharSpec *chars, 13877 int len); 13878 void XftGlyphFontSpecRender (Display *dpy, 13879 int op, 13880 Picture src, 13881 Picture dst, 13882 int srcx, 13883 int srcy, 13884 const XftGlyphFontSpec *glyphs, 13885 int nglyphs); 13886 13887 void XftCharFontSpecRender (Display *dpy, 13888 int op, 13889 Picture src, 13890 Picture dst, 13891 int srcx, 13892 int srcy, 13893 const XftCharFontSpec *chars, 13894 int len); 13895 13896 void XftTextRender8 (Display *dpy, 13897 int op, 13898 Picture src, 13899 XftFont *pub, 13900 Picture dst, 13901 int srcx, 13902 int srcy, 13903 int x, 13904 int y, 13905 const char *string, 13906 int len); 13907 void XftTextRender16 (Display *dpy, 13908 int op, 13909 Picture src, 13910 XftFont *pub, 13911 Picture dst, 13912 int srcx, 13913 int srcy, 13914 int x, 13915 int y, 13916 const wchar *string, 13917 int len); 13918 13919 void XftTextRender16BE (Display *dpy, 13920 int op, 13921 Picture src, 13922 XftFont *pub, 13923 Picture dst, 13924 int srcx, 13925 int srcy, 13926 int x, 13927 int y, 13928 const char *string, 13929 int len); 13930 13931 void XftTextRender16LE (Display *dpy, 13932 int op, 13933 Picture src, 13934 XftFont *pub, 13935 Picture dst, 13936 int srcx, 13937 int srcy, 13938 int x, 13939 int y, 13940 const char *string, 13941 int len); 13942 13943 void XftTextRender32 (Display *dpy, 13944 int op, 13945 Picture src, 13946 XftFont *pub, 13947 Picture dst, 13948 int srcx, 13949 int srcy, 13950 int x, 13951 int y, 13952 const dchar *string, 13953 int len); 13954 13955 void XftTextRender32BE (Display *dpy, 13956 int op, 13957 Picture src, 13958 XftFont *pub, 13959 Picture dst, 13960 int srcx, 13961 int srcy, 13962 int x, 13963 int y, 13964 const char *string, 13965 int len); 13966 13967 void XftTextRender32LE (Display *dpy, 13968 int op, 13969 Picture src, 13970 XftFont *pub, 13971 Picture dst, 13972 int srcx, 13973 int srcy, 13974 int x, 13975 int y, 13976 const char *string, 13977 int len); 13978 13979 void XftTextRenderUtf8 (Display *dpy, 13980 int op, 13981 Picture src, 13982 XftFont *pub, 13983 Picture dst, 13984 int srcx, 13985 int srcy, 13986 int x, 13987 int y, 13988 const char *string, 13989 int len); 13990 13991 void XftTextRenderUtf16 (Display *dpy, 13992 int op, 13993 Picture src, 13994 XftFont *pub, 13995 Picture dst, 13996 int srcx, 13997 int srcy, 13998 int x, 13999 int y, 14000 const char *string, 14001 FcEndian endian, 14002 int len); 14003 FcPattern * XftXlfdParse (const char *xlfd_orig, Bool ignore_scalable, Bool complete); 14004 14005 } 14006 14007 interface FontConfig { 14008 extern(C) @nogc pure: 14009 int FcPatternGetString(const FcPattern *p, const char *object, int n, char ** s); 14010 void FcFontSetDestroy(FcFontSet*); 14011 char* FcNameUnparse(const FcPattern *); 14012 } 14013 14014 mixin DynamicLoad!(Xft, "Xft", 2, librariesSuccessfullyLoaded) XftLibrary; 14015 mixin DynamicLoad!(FontConfig, "fontconfig", 1, librariesSuccessfullyLoaded) FontConfigLibrary; 14016 14017 14018 /* Xft } */ 14019 14020 class XDisconnectException : Exception { 14021 bool userRequested; 14022 this(bool userRequested = true) { 14023 this.userRequested = userRequested; 14024 super("X disconnected"); 14025 } 14026 } 14027 14028 /++ 14029 Platform-specific for X11. Traps errors for the duration of `dg`. Avoid calling this from inside a call to this. 14030 14031 Please note that it returns 14032 +/ 14033 XErrorEvent[] trapXErrors(scope void delegate() dg) { 14034 14035 static XErrorEvent[] errorBuffer; 14036 14037 static extern(C) int handler (Display* dpy, XErrorEvent* evt) nothrow { 14038 errorBuffer ~= *evt; 14039 return 0; 14040 } 14041 14042 auto savedErrorHandler = XSetErrorHandler(&handler); 14043 14044 try { 14045 dg(); 14046 } finally { 14047 XSync(XDisplayConnection.get, 0/*False*/); 14048 XSetErrorHandler(savedErrorHandler); 14049 } 14050 14051 auto bfr = errorBuffer; 14052 errorBuffer = null; 14053 14054 return bfr; 14055 } 14056 14057 /// Platform-specific for X11. A singleton class (well, all its methods are actually static... so more like a namespace) wrapping a `Display*`. 14058 class XDisplayConnection { 14059 private __gshared Display* display; 14060 private __gshared XIM xim; 14061 private __gshared char* displayName; 14062 14063 private __gshared int connectionSequence_; 14064 private __gshared bool isLocal_; 14065 14066 /// use this for lazy caching when reconnection 14067 static int connectionSequenceNumber() { return connectionSequence_; } 14068 14069 /++ 14070 Guesses if the connection appears to be local. 14071 14072 History: 14073 Added June 3, 2021 14074 +/ 14075 static @property bool isLocal() nothrow @trusted @nogc { 14076 return isLocal_; 14077 } 14078 14079 /// Attempts recreation of state, may require application assistance 14080 /// You MUST call this OUTSIDE the event loop. Let the exception kill the loop, 14081 /// then call this, and if successful, reenter the loop. 14082 static void discardAndRecreate(string newDisplayString = null) { 14083 if(insideXEventLoop) 14084 throw new Error("You MUST call discardAndRecreate from OUTSIDE the event loop"); 14085 14086 // 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 14087 auto chnenhm = CapableOfHandlingNativeEvent.nativeHandleMapping.dup; 14088 14089 foreach(handle; chnenhm) { 14090 handle.discardConnectionState(); 14091 } 14092 14093 discardState(); 14094 14095 if(newDisplayString !is null) 14096 setDisplayName(newDisplayString); 14097 14098 auto display = get(); 14099 14100 foreach(handle; chnenhm) { 14101 handle.recreateAfterDisconnect(); 14102 } 14103 } 14104 14105 private __gshared EventMask rootEventMask; 14106 14107 /++ 14108 Requests the specified input from the root window on the connection, in addition to any other request. 14109 14110 14111 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. 14112 14113 $(WARNING it calls XSelectInput itself, which will override any other root window input you have!) 14114 +/ 14115 static void addRootInput(EventMask mask) { 14116 auto old = rootEventMask; 14117 rootEventMask |= mask; 14118 get(); // to ensure display connected 14119 if(display !is null && rootEventMask != old) 14120 XSelectInput(display, RootWindow(display, DefaultScreen(display)), rootEventMask); 14121 } 14122 14123 static void discardState() { 14124 freeImages(); 14125 14126 foreach(atomPtr; interredAtoms) 14127 *atomPtr = 0; 14128 interredAtoms = null; 14129 interredAtoms.assumeSafeAppend(); 14130 14131 ScreenPainterImplementation.fontAttempted = false; 14132 ScreenPainterImplementation.defaultfont = null; 14133 ScreenPainterImplementation.defaultfontset = null; 14134 14135 Image.impl.xshmQueryCompleted = false; 14136 Image.impl._xshmAvailable = false; 14137 14138 SimpleWindow.nativeMapping = null; 14139 CapableOfHandlingNativeEvent.nativeHandleMapping = null; 14140 // GlobalHotkeyManager 14141 14142 display = null; 14143 xim = null; 14144 } 14145 14146 // Do you want to know why do we need all this horrible-looking code? See comment at the bottom. 14147 private static void createXIM () { 14148 import core.stdc.locale : setlocale, LC_ALL; 14149 import core.stdc.stdio : stderr, fprintf; 14150 import core.stdc.stdlib : free; 14151 import core.stdc.string : strdup; 14152 14153 static immutable string[3] mtry = [ "", "@im=local", "@im=" ]; 14154 14155 auto olocale = strdup(setlocale(LC_ALL, null)); 14156 setlocale(LC_ALL, (sdx_isUTF8Locale ? "" : "en_US.UTF-8")); 14157 scope(exit) { setlocale(LC_ALL, olocale); free(olocale); } 14158 14159 //fprintf(stderr, "opening IM...\n"); 14160 foreach (string s; mtry) { 14161 XSetLocaleModifiers(s.ptr); // it's safe, as `s` is string literal 14162 if ((xim = XOpenIM(display, null, null, null)) !is null) return; 14163 } 14164 fprintf(stderr, "createXIM: XOpenIM failed!\n"); 14165 } 14166 14167 // for X11 we will keep all XShm-allocated images in this list, so we can free 'em on connection closing. 14168 // we'll use glibc malloc()/free(), 'cause `unregisterImage()` can be called from object dtor. 14169 static struct ImgList { 14170 size_t img; // class; hide it from GC 14171 ImgList* next; 14172 } 14173 14174 static __gshared ImgList* imglist = null; 14175 static __gshared bool imglistLocked = false; // true: don't register and unregister images 14176 14177 static void registerImage (Image img) { 14178 if (!imglistLocked && img !is null) { 14179 import core.stdc.stdlib : malloc; 14180 auto it = cast(ImgList*)malloc(ImgList.sizeof); 14181 assert(it !is null); // do proper checks 14182 it.img = cast(size_t)cast(void*)img; 14183 it.next = imglist; 14184 imglist = it; 14185 version(sdpy_debug_xshm) { import core.stdc.stdio : printf; printf("registering image %p\n", cast(void*)img); } 14186 } 14187 } 14188 14189 static void unregisterImage (Image img) { 14190 if (!imglistLocked && img !is null) { 14191 import core.stdc.stdlib : free; 14192 ImgList* prev = null; 14193 ImgList* cur = imglist; 14194 while (cur !is null) { 14195 if (cur.img == cast(size_t)cast(void*)img) break; // i found her! 14196 prev = cur; 14197 cur = cur.next; 14198 } 14199 if (cur !is null) { 14200 if (prev is null) imglist = cur.next; else prev.next = cur.next; 14201 free(cur); 14202 version(sdpy_debug_xshm) { import core.stdc.stdio : printf; printf("unregistering image %p\n", cast(void*)img); } 14203 } else { 14204 version(sdpy_debug_xshm) { import core.stdc.stdio : printf; printf("trying to unregister unknown image %p\n", cast(void*)img); } 14205 } 14206 } 14207 } 14208 14209 static void freeImages () { // needed for discardAndRecreate 14210 imglistLocked = true; 14211 scope(exit) imglistLocked = false; 14212 ImgList* cur = imglist; 14213 ImgList* next = null; 14214 while (cur !is null) { 14215 import core.stdc.stdlib : free; 14216 next = cur.next; 14217 version(sdpy_debug_xshm) { import core.stdc.stdio : printf; printf("disposing image %p\n", cast(void*)cur.img); } 14218 (cast(Image)cast(void*)cur.img).dispose(); 14219 free(cur); 14220 cur = next; 14221 } 14222 imglist = null; 14223 } 14224 14225 /// can be used to override normal handling of display name 14226 /// from environment and/or command line 14227 static setDisplayName(string newDisplayName) { 14228 displayName = cast(char*) (newDisplayName ~ '\0'); 14229 } 14230 14231 /// resets to the default display string 14232 static resetDisplayName() { 14233 displayName = null; 14234 } 14235 14236 /// 14237 static Display* get() { 14238 if(display is null) { 14239 if(!librariesSuccessfullyLoaded) 14240 throw new Exception("Unable to load X11 client libraries"); 14241 display = XOpenDisplay(displayName); 14242 14243 isLocal_ = false; 14244 14245 connectionSequence_++; 14246 if(display is null) 14247 throw new Exception("Unable to open X display"); 14248 14249 auto str = display.display_name; 14250 // this is a bit of a hack but like if it looks like a unix socket we assume it is local 14251 // and otherwise it probably isn't 14252 if(str is null || (str[0] != ':' && str[0] != '/')) 14253 isLocal_ = false; 14254 else 14255 isLocal_ = true; 14256 14257 debug(sdpy_x_errors) { 14258 XSetErrorHandler(&adrlogger); 14259 XSynchronize(display, true); 14260 14261 extern(C) int wtf() { 14262 if(errorHappened) { 14263 asm { int 3; } 14264 errorHappened = false; 14265 } 14266 return 0; 14267 } 14268 XSetAfterFunction(display, &wtf); 14269 } 14270 14271 14272 XSetIOErrorHandler(&x11ioerrCB); 14273 Bool sup; 14274 XkbSetDetectableAutoRepeat(display, 1, &sup); // so we will not receive KeyRelease until key is really released 14275 createXIM(); 14276 version(with_eventloop) { 14277 import arsd.eventloop; 14278 addFileEventListeners(display.fd, &eventListener, null, null); 14279 } 14280 } 14281 14282 return display; 14283 } 14284 14285 extern(C) 14286 static int x11ioerrCB(Display* dpy) { 14287 throw new XDisconnectException(false); 14288 } 14289 14290 version(with_eventloop) { 14291 import arsd.eventloop; 14292 static void eventListener(OsFileHandle fd) { 14293 //this.mtLock(); 14294 //scope(exit) this.mtUnlock(); 14295 while(XPending(display)) 14296 doXNextEvent(display); 14297 } 14298 } 14299 14300 // close connection on program exit -- we need this to properly free all images 14301 static ~this () { 14302 // the gui thread must clean up after itself or else Xlib might deadlock 14303 // using this flag on any thread destruction is the easiest way i know of 14304 // (shared static this is run by the LAST thread to exit, which may not be 14305 // the gui thread, and normal static this run by ALL threads, so we gotta check.) 14306 if(thisIsGuiThread) 14307 close(); 14308 } 14309 14310 /// 14311 static void close() { 14312 if(display is null) 14313 return; 14314 14315 version(with_eventloop) { 14316 import arsd.eventloop; 14317 removeFileEventListeners(display.fd); 14318 } 14319 14320 // now remove all registered images to prevent shared memory leaks 14321 freeImages(); 14322 14323 // tbh I don't know why it is doing this but like if this happens to run 14324 // from the other thread there's frequent hanging inside here. 14325 if(thisIsGuiThread) 14326 XCloseDisplay(display); 14327 display = null; 14328 } 14329 } 14330 14331 mixin template NativeImageImplementation() { 14332 XImage* handle; 14333 ubyte* rawData; 14334 14335 XShmSegmentInfo shminfo; 14336 bool premultiply = true; 14337 14338 __gshared bool xshmQueryCompleted; 14339 __gshared bool _xshmAvailable; 14340 public static @property bool xshmAvailable() { 14341 if(!xshmQueryCompleted) { 14342 int i1, i2, i3; 14343 xshmQueryCompleted = true; 14344 14345 if(!XDisplayConnection.isLocal) 14346 _xshmAvailable = false; 14347 else 14348 _xshmAvailable = XQueryExtension(XDisplayConnection.get(), "MIT-SHM", &i1, &i2, &i3) != 0; 14349 } 14350 return _xshmAvailable; 14351 } 14352 14353 bool usingXshm; 14354 final: 14355 14356 private __gshared bool xshmfailed; 14357 14358 void createImage(int width, int height, bool forcexshm=false, bool enableAlpha = false) { 14359 auto display = XDisplayConnection.get(); 14360 assert(display !is null); 14361 auto screen = DefaultScreen(display); 14362 14363 // it will only use shared memory for somewhat largish images, 14364 // since otherwise we risk wasting shared memory handles on a lot of little ones 14365 if (xshmAvailable && (forcexshm || (width > 100 && height > 100))) { 14366 14367 14368 // it is possible for the query extension to return true, the DISPLAY check to pass, yet 14369 // the actual use still fails. For example, if the program is in a container and permission denied 14370 // on shared memory, or if it is a local thing forwarded to a remote server, etc. 14371 // 14372 // If it does fail, we need to detect it now, abort the xshm and fall back to core protocol. 14373 14374 14375 // synchronize so preexisting buffers are clear 14376 XSync(display, false); 14377 xshmfailed = false; 14378 14379 auto oldErrorHandler = XSetErrorHandler(&XShmErrorHandler); 14380 14381 14382 usingXshm = true; 14383 handle = XShmCreateImage( 14384 display, 14385 DefaultVisual(display, screen), 14386 enableAlpha ? 32: 24, 14387 ImageFormat.ZPixmap, 14388 null, 14389 &shminfo, 14390 width, height); 14391 if(handle is null) 14392 goto abortXshm1; 14393 14394 if(handle.bytes_per_line != 4 * width) 14395 goto abortXshm2; 14396 14397 shminfo.shmid = shmget(IPC_PRIVATE, handle.bytes_per_line * height, IPC_CREAT | 511 /* 0777 */); 14398 if(shminfo.shmid < 0) 14399 goto abortXshm3; 14400 handle.data = shminfo.shmaddr = rawData = cast(ubyte*) shmat(shminfo.shmid, null, 0); 14401 if(rawData == cast(ubyte*) -1) 14402 goto abortXshm4; 14403 shminfo.readOnly = 0; 14404 XShmAttach(display, &shminfo); 14405 14406 // and now to the final error check to ensure it actually worked. 14407 XSync(display, false); 14408 if(xshmfailed) 14409 goto abortXshm5; 14410 14411 XSetErrorHandler(oldErrorHandler); 14412 14413 XDisplayConnection.registerImage(this); 14414 // if I don't flush here there's a chance the dtor will run before the 14415 // ctor and lead to a bad value X error. While this hurts the efficiency 14416 // it is local anyway so prolly better to keep it simple 14417 XFlush(display); 14418 14419 return; 14420 14421 abortXshm5: 14422 shmdt(shminfo.shmaddr); 14423 rawData = null; 14424 14425 abortXshm4: 14426 shmctl(shminfo.shmid, IPC_RMID, null); 14427 14428 abortXshm3: 14429 // nothing needed, the shmget failed so there's nothing to free 14430 14431 abortXshm2: 14432 XDestroyImage(handle); 14433 handle = null; 14434 14435 abortXshm1: 14436 XSetErrorHandler(oldErrorHandler); 14437 usingXshm = false; 14438 handle = null; 14439 14440 shminfo = typeof(shminfo).init; 14441 14442 _xshmAvailable = false; // don't try again in the future 14443 14444 // writeln("fallingback"); 14445 14446 goto fallback; 14447 14448 } else { 14449 fallback: 14450 14451 if (forcexshm) throw new Exception("can't create XShm Image"); 14452 // This actually needs to be malloc to avoid a double free error when XDestroyImage is called 14453 import core.stdc.stdlib : malloc; 14454 rawData = cast(ubyte*) malloc(width * height * 4); 14455 14456 handle = XCreateImage( 14457 display, 14458 DefaultVisual(display, screen), 14459 enableAlpha ? 32 : 24, // bpp 14460 ImageFormat.ZPixmap, 14461 0, // offset 14462 rawData, 14463 width, height, 14464 enableAlpha ? 32 : 8 /* FIXME */, 4 * width); // padding, bytes per line 14465 } 14466 } 14467 14468 void dispose() { 14469 // note: this calls free(rawData) for us 14470 if(handle) { 14471 if (usingXshm) { 14472 XDisplayConnection.unregisterImage(this); 14473 if (XDisplayConnection.get()) XShmDetach(XDisplayConnection.get(), &shminfo); 14474 } 14475 XDestroyImage(handle); 14476 if(usingXshm) { 14477 shmdt(shminfo.shmaddr); 14478 shmctl(shminfo.shmid, IPC_RMID, null); 14479 } 14480 handle = null; 14481 } 14482 } 14483 14484 Color getPixel(int x, int y) { 14485 auto offset = (y * width + x) * 4; 14486 Color c; 14487 c.a = enableAlpha ? rawData[offset + 3] : 255; 14488 c.b = rawData[offset + 0]; 14489 c.g = rawData[offset + 1]; 14490 c.r = rawData[offset + 2]; 14491 if(enableAlpha && premultiply) 14492 c.unPremultiply; 14493 return c; 14494 } 14495 14496 void setPixel(int x, int y, Color c) { 14497 if(enableAlpha && premultiply) 14498 c.premultiply(); 14499 auto offset = (y * width + x) * 4; 14500 rawData[offset + 0] = c.b; 14501 rawData[offset + 1] = c.g; 14502 rawData[offset + 2] = c.r; 14503 if(enableAlpha) 14504 rawData[offset + 3] = c.a; 14505 } 14506 14507 void convertToRgbaBytes(ubyte[] where) { 14508 assert(where.length == this.width * this.height * 4); 14509 14510 // if rawData had a length.... 14511 //assert(rawData.length == where.length); 14512 for(int idx = 0; idx < where.length; idx += 4) { 14513 where[idx + 0] = rawData[idx + 2]; // r 14514 where[idx + 1] = rawData[idx + 1]; // g 14515 where[idx + 2] = rawData[idx + 0]; // b 14516 where[idx + 3] = enableAlpha ? rawData[idx + 3] : 255; // a 14517 14518 if(enableAlpha && premultiply) 14519 unPremultiplyRgba(where[idx .. idx + 4]); 14520 } 14521 } 14522 14523 void setFromRgbaBytes(in ubyte[] where) { 14524 assert(where.length == this.width * this.height * 4); 14525 14526 // if rawData had a length.... 14527 //assert(rawData.length == where.length); 14528 for(int idx = 0; idx < where.length; idx += 4) { 14529 rawData[idx + 2] = where[idx + 0]; // r 14530 rawData[idx + 1] = where[idx + 1]; // g 14531 rawData[idx + 0] = where[idx + 2]; // b 14532 if(enableAlpha) { 14533 rawData[idx + 3] = where[idx + 3]; // a 14534 if(premultiply) 14535 premultiplyBgra(rawData[idx .. idx + 4]); 14536 } 14537 } 14538 } 14539 14540 } 14541 14542 mixin template NativeSimpleWindowImplementation() { 14543 GC gc; 14544 Window window; 14545 Display* display; 14546 14547 Pixmap buffer; 14548 int bufferw, bufferh; // size of the buffer; can be bigger than window 14549 XIC xic; // input context 14550 int curHidden = 0; // counter 14551 Cursor blankCurPtr = 0; 14552 int cursorSequenceNumber = 0; 14553 int warpEventCount = 0; // number of mouse movement events to eat 14554 14555 __gshared X11SetSelectionHandler[Atom] setSelectionHandlers; 14556 X11GetSelectionHandler[Atom] getSelectionHandlers; 14557 14558 version(without_opengl) {} else 14559 GLXContext glc; 14560 14561 private void fixFixedSize(bool forced=false) (int width, int height) { 14562 if (forced || this.resizability == Resizability.fixedSize) { 14563 //{ import core.stdc.stdio; printf("fixing size to: %dx%d\n", width, height); } 14564 XSizeHints sh; 14565 static if (!forced) { 14566 c_long spr; 14567 XGetWMNormalHints(display, window, &sh, &spr); 14568 sh.flags |= PMaxSize | PMinSize; 14569 } else { 14570 sh.flags = PMaxSize | PMinSize; 14571 } 14572 sh.min_width = width; 14573 sh.min_height = height; 14574 sh.max_width = width; 14575 sh.max_height = height; 14576 XSetWMNormalHints(display, window, &sh); 14577 //XFlush(display); 14578 } 14579 } 14580 14581 ScreenPainter getPainter(bool manualInvalidations) { 14582 return ScreenPainter(this, window, manualInvalidations); 14583 } 14584 14585 void move(int x, int y) { 14586 XMoveWindow(display, window, x, y); 14587 } 14588 14589 void resize(int w, int h) { 14590 if (w < 1) w = 1; 14591 if (h < 1) h = 1; 14592 XResizeWindow(display, window, w, h); 14593 14594 // calling this now to avoid waiting for the server to 14595 // acknowledge the resize; draws without returning to the 14596 // event loop will thus actually work. the server's event 14597 // btw might overrule this and resize it again 14598 recordX11Resize(display, this, w, h); 14599 14600 updateOpenglViewportIfNeeded(w, h); 14601 } 14602 14603 void moveResize (int x, int y, int w, int h) { 14604 if (w < 1) w = 1; 14605 if (h < 1) h = 1; 14606 XMoveResizeWindow(display, window, x, y, w, h); 14607 updateOpenglViewportIfNeeded(w, h); 14608 } 14609 14610 void hideCursor () { 14611 if (curHidden++ == 0) { 14612 if (!blankCurPtr || cursorSequenceNumber != XDisplayConnection.connectionSequenceNumber) { 14613 static const(char)[1] cmbmp = 0; 14614 XColor blackcolor = { 0, 0, 0, 0, 0, 0 }; 14615 Pixmap pm = XCreateBitmapFromData(display, window, cmbmp.ptr, 1, 1); 14616 blankCurPtr = XCreatePixmapCursor(display, pm, pm, &blackcolor, &blackcolor, 0, 0); 14617 cursorSequenceNumber = XDisplayConnection.connectionSequenceNumber; 14618 XFreePixmap(display, pm); 14619 } 14620 XDefineCursor(display, window, blankCurPtr); 14621 } 14622 } 14623 14624 void showCursor () { 14625 if (--curHidden == 0) XUndefineCursor(display, window); 14626 } 14627 14628 void warpMouse (int x, int y) { 14629 // here i will send dummy "ignore next mouse motion" event, 14630 // 'cause `XWarpPointer()` sends synthesised mouse motion, 14631 // and we don't need to report it to the user (as warping is 14632 // used when the user needs movement deltas). 14633 //XClientMessageEvent xclient; 14634 XEvent e; 14635 e.xclient.type = EventType.ClientMessage; 14636 e.xclient.window = window; 14637 e.xclient.message_type = GetAtom!("_X11SDPY_INSMME_FLAG_EVENT_", true)(display); // let's hope nobody else will use such stupid name ;-) 14638 e.xclient.format = 32; 14639 e.xclient.data.l[0] = 0; 14640 debug(x11sdpy_warp_debug) { import core.stdc.stdio : printf; printf("X11: sending \"INSMME\"...\n"); } 14641 //{ 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]); } 14642 XSendEvent(display, window, false, EventMask.NoEventMask, /*cast(XEvent*)&xclient*/&e); 14643 // now warp pointer... 14644 debug(x11sdpy_warp_debug) { import core.stdc.stdio : printf; printf("X11: sending \"warp\"...\n"); } 14645 XWarpPointer(display, None, window, 0, 0, 0, 0, x, y); 14646 // ...and flush 14647 debug(x11sdpy_warp_debug) { import core.stdc.stdio : printf; printf("X11: flushing...\n"); } 14648 XFlush(display); 14649 } 14650 14651 void sendDummyEvent () { 14652 // here i will send dummy event to ping event queue 14653 XEvent e; 14654 e.xclient.type = EventType.ClientMessage; 14655 e.xclient.window = window; 14656 e.xclient.message_type = GetAtom!("_X11SDPY_DUMMY_EVENT_", true)(display); // let's hope nobody else will use such stupid name ;-) 14657 e.xclient.format = 32; 14658 e.xclient.data.l[0] = 0; 14659 XSendEvent(display, window, false, EventMask.NoEventMask, /*cast(XEvent*)&xclient*/&e); 14660 XFlush(display); 14661 } 14662 14663 void setTitle(string title) { 14664 if (title.ptr is null) title = ""; 14665 auto XA_UTF8 = XInternAtom(display, "UTF8_STRING".ptr, false); 14666 auto XA_NETWM_NAME = XInternAtom(display, "_NET_WM_NAME".ptr, false); 14667 XTextProperty windowName; 14668 windowName.value = title.ptr; 14669 windowName.encoding = XA_UTF8; //XA_STRING; 14670 windowName.format = 8; 14671 windowName.nitems = cast(uint)title.length; 14672 XSetWMName(display, window, &windowName); 14673 char[1024] namebuf = 0; 14674 auto maxlen = namebuf.length-1; 14675 if (maxlen > title.length) maxlen = title.length; 14676 namebuf[0..maxlen] = title[0..maxlen]; 14677 XStoreName(display, window, namebuf.ptr); 14678 XChangeProperty(display, window, XA_NETWM_NAME, XA_UTF8, 8, PropModeReplace, title.ptr, cast(uint)title.length); 14679 flushGui(); // without this OpenGL windows has a LONG delay before changing title 14680 } 14681 14682 string[] getTitles() { 14683 auto XA_UTF8 = XInternAtom(display, "UTF8_STRING".ptr, false); 14684 auto XA_NETWM_NAME = XInternAtom(display, "_NET_WM_NAME".ptr, false); 14685 XTextProperty textProp; 14686 if (XGetTextProperty(display, window, &textProp, XA_NETWM_NAME) != 0 || XGetWMName(display, window, &textProp) != 0) { 14687 if ((textProp.encoding == XA_UTF8 || textProp.encoding == XA_STRING) && textProp.format == 8) { 14688 return textProp.value[0 .. textProp.nitems].idup.split('\0'); 14689 } else 14690 return []; 14691 } else 14692 return null; 14693 } 14694 14695 string getTitle() { 14696 auto titles = getTitles(); 14697 return titles.length ? titles[0] : null; 14698 } 14699 14700 void setMinSize (int minwidth, int minheight) { 14701 import core.stdc.config : c_long; 14702 if (minwidth < 1) minwidth = 1; 14703 if (minheight < 1) minheight = 1; 14704 XSizeHints sh; 14705 c_long spr; 14706 XGetWMNormalHints(display, window, &sh, &spr); 14707 sh.min_width = minwidth; 14708 sh.min_height = minheight; 14709 sh.flags |= PMinSize; 14710 XSetWMNormalHints(display, window, &sh); 14711 flushGui(); 14712 } 14713 14714 void setMaxSize (int maxwidth, int maxheight) { 14715 import core.stdc.config : c_long; 14716 if (maxwidth < 1) maxwidth = 1; 14717 if (maxheight < 1) maxheight = 1; 14718 XSizeHints sh; 14719 c_long spr; 14720 XGetWMNormalHints(display, window, &sh, &spr); 14721 sh.max_width = maxwidth; 14722 sh.max_height = maxheight; 14723 sh.flags |= PMaxSize; 14724 XSetWMNormalHints(display, window, &sh); 14725 flushGui(); 14726 } 14727 14728 void setResizeGranularity (int granx, int grany) { 14729 import core.stdc.config : c_long; 14730 if (granx < 1) granx = 1; 14731 if (grany < 1) grany = 1; 14732 XSizeHints sh; 14733 c_long spr; 14734 XGetWMNormalHints(display, window, &sh, &spr); 14735 sh.width_inc = granx; 14736 sh.height_inc = grany; 14737 sh.flags |= PResizeInc; 14738 XSetWMNormalHints(display, window, &sh); 14739 flushGui(); 14740 } 14741 14742 void setOpacity (uint opacity) { 14743 arch_ulong o = opacity; 14744 if (opacity == uint.max) 14745 XDeleteProperty(display, window, XInternAtom(display, "_NET_WM_WINDOW_OPACITY".ptr, false)); 14746 else 14747 XChangeProperty(display, window, XInternAtom(display, "_NET_WM_WINDOW_OPACITY".ptr, false), 14748 XA_CARDINAL, 32, PropModeReplace, &o, 1); 14749 } 14750 14751 void createWindow(int width, int height, string title, in OpenGlOptions opengl, SimpleWindow parent) { 14752 version(without_opengl) {} else if(opengl == OpenGlOptions.yes && !openGlLibrariesSuccessfullyLoaded) throw new Exception("OpenGL libraries did not load"); 14753 display = XDisplayConnection.get(); 14754 auto screen = DefaultScreen(display); 14755 14756 bool overrideRedirect = false; 14757 if(windowType == WindowTypes.dropdownMenu || windowType == WindowTypes.popupMenu || windowType == WindowTypes.notification)// || windowType == WindowTypes.nestedChild) 14758 overrideRedirect = true; 14759 14760 version(without_opengl) {} 14761 else { 14762 if(opengl == OpenGlOptions.yes) { 14763 GLXFBConfig fbconf = null; 14764 XVisualInfo* vi = null; 14765 bool useLegacy = false; 14766 static if (SdpyIsUsingIVGLBinds) {if (glbindGetProcAddress("glHint") is null) assert(0, "GL: error loading OpenGL"); } // loads all necessary functions 14767 if (sdpyOpenGLContextVersion != 0 && glXCreateContextAttribsARB_present()) { 14768 int[23] visualAttribs = [ 14769 GLX_X_RENDERABLE , 1/*True*/, 14770 GLX_DRAWABLE_TYPE, GLX_WINDOW_BIT, 14771 GLX_RENDER_TYPE , GLX_RGBA_BIT, 14772 GLX_X_VISUAL_TYPE, GLX_TRUE_COLOR, 14773 GLX_RED_SIZE , 8, 14774 GLX_GREEN_SIZE , 8, 14775 GLX_BLUE_SIZE , 8, 14776 GLX_ALPHA_SIZE , 8, 14777 GLX_DEPTH_SIZE , 24, 14778 GLX_STENCIL_SIZE , 8, 14779 GLX_DOUBLEBUFFER , 1/*True*/, 14780 0/*None*/, 14781 ]; 14782 int fbcount; 14783 GLXFBConfig* fbc = glXChooseFBConfig(display, screen, visualAttribs.ptr, &fbcount); 14784 if (fbcount == 0) { 14785 useLegacy = true; // try to do at least something 14786 } else { 14787 // pick the FB config/visual with the most samples per pixel 14788 int bestidx = -1, bestns = -1; 14789 foreach (int fbi; 0..fbcount) { 14790 int sb, samples; 14791 glXGetFBConfigAttrib(display, fbc[fbi], GLX_SAMPLE_BUFFERS, &sb); 14792 glXGetFBConfigAttrib(display, fbc[fbi], GLX_SAMPLES, &samples); 14793 if (bestidx < 0 || sb && samples > bestns) { bestidx = fbi; bestns = samples; } 14794 } 14795 //{ import core.stdc.stdio; printf("found gl visual with %d samples\n", bestns); } 14796 fbconf = fbc[bestidx]; 14797 // Be sure to free the FBConfig list allocated by glXChooseFBConfig() 14798 XFree(fbc); 14799 vi = cast(XVisualInfo*)glXGetVisualFromFBConfig(display, fbconf); 14800 } 14801 } 14802 if (vi is null || useLegacy) { 14803 static immutable GLint[5] attrs = [ GLX_RGBA, GLX_DEPTH_SIZE, 24, GLX_DOUBLEBUFFER, None ]; 14804 vi = cast(XVisualInfo*)glXChooseVisual(display, 0, attrs.ptr); 14805 useLegacy = true; 14806 } 14807 if (vi is null) throw new Exception("no open gl visual found"); 14808 14809 XSetWindowAttributes swa; 14810 auto root = RootWindow(display, screen); 14811 swa.colormap = XCreateColormap(display, root, vi.visual, AllocNone); 14812 14813 swa.override_redirect = overrideRedirect; 14814 14815 window = XCreateWindow(display, (windowType != WindowTypes.nestedChild || parent is null) ? root : parent.impl.window, 14816 0, 0, width, height, 14817 0, vi.depth, 1 /* InputOutput */, vi.visual, CWColormap | CWOverrideRedirect, &swa); 14818 14819 // now try to use `glXCreateContextAttribsARB()` if it's here 14820 if (!useLegacy) { 14821 // request fairly advanced context, even with stencil buffer! 14822 int[9] contextAttribs = [ 14823 GLX_CONTEXT_MAJOR_VERSION_ARB, (sdpyOpenGLContextVersion>>8), 14824 GLX_CONTEXT_MINOR_VERSION_ARB, (sdpyOpenGLContextVersion&0xff), 14825 /*GLX_CONTEXT_PROFILE_MASK_ARB*/0x9126, (sdpyOpenGLContextCompatible ? /*GLX_CONTEXT_COMPATIBILITY_PROFILE_BIT_ARB*/0x02 : /*GLX_CONTEXT_CORE_PROFILE_BIT_ARB*/ 0x01), 14826 // for modern context, set "forward compatibility" flag too 14827 (sdpyOpenGLContextCompatible ? None : /*GLX_CONTEXT_FLAGS_ARB*/ 0x2094), /*GLX_CONTEXT_FORWARD_COMPATIBLE_BIT_ARB*/ 0x02, 14828 0/*None*/, 14829 ]; 14830 glc = glXCreateContextAttribsARB(display, fbconf, null, 1/*True*/, contextAttribs.ptr); 14831 if (glc is null && sdpyOpenGLContextAllowFallback) { 14832 sdpyOpenGLContextVersion = 0; 14833 glc = glXCreateContext(display, vi, null, /*GL_TRUE*/1); 14834 } 14835 //{ import core.stdc.stdio; printf("using modern ogl v%d.%d\n", contextAttribs[1], contextAttribs[3]); } 14836 } else { 14837 // fallback to old GLX call 14838 if (sdpyOpenGLContextAllowFallback || sdpyOpenGLContextVersion == 0) { 14839 sdpyOpenGLContextVersion = 0; 14840 glc = glXCreateContext(display, vi, null, /*GL_TRUE*/1); 14841 } 14842 } 14843 // sync to ensure any errors generated are processed 14844 XSync(display, 0/*False*/); 14845 //{ import core.stdc.stdio; printf("ogl is here\n"); } 14846 if(glc is null) 14847 throw new Exception("glc"); 14848 } 14849 } 14850 14851 if(opengl == OpenGlOptions.no) { 14852 14853 XSetWindowAttributes swa; 14854 swa.background_pixel = WhitePixel(display, screen); 14855 swa.border_pixel = BlackPixel(display, screen); 14856 swa.override_redirect = overrideRedirect; 14857 auto root = RootWindow(display, screen); 14858 swa.colormap = XCreateColormap(display, root, DefaultVisual(display, screen), AllocNone); 14859 14860 window = XCreateWindow(display, (windowType != WindowTypes.nestedChild || parent is null) ? root : parent.impl.window, 14861 0, 0, width, height, 14862 // I'm commenting that CWBackPixel thing just because it actually causes flicker for no apparent benefit. 14863 0, CopyFromParent, 1 /* InputOutput */, cast(Visual*) CopyFromParent, CWColormap /*| CWBackPixel*/ | CWBorderPixel | CWOverrideRedirect, &swa); 14864 14865 14866 14867 /* 14868 window = XCreateSimpleWindow( 14869 display, 14870 parent is null ? RootWindow(display, screen) : parent.impl.window, 14871 0, 0, // x, y 14872 width, height, 14873 1, // border width 14874 BlackPixel(display, screen), // border 14875 WhitePixel(display, screen)); // background 14876 */ 14877 14878 buffer = XCreatePixmap(display, cast(Drawable) window, width, height, DefaultDepthOfDisplay(display)); 14879 bufferw = width; 14880 bufferh = height; 14881 14882 gc = DefaultGC(display, screen); 14883 14884 // clear out the buffer to get us started... 14885 XSetForeground(display, gc, WhitePixel(display, screen)); 14886 XFillRectangle(display, cast(Drawable) buffer, gc, 0, 0, width, height); 14887 XSetForeground(display, gc, BlackPixel(display, screen)); 14888 } 14889 14890 // input context 14891 //TODO: create this only for top-level windows, and reuse that? 14892 populateXic(); 14893 14894 if (sdpyWindowClassStr is null) loadBinNameToWindowClassName(); 14895 if (sdpyWindowClassStr is null) sdpyWindowClass = "DSimpleWindow"; 14896 // window class 14897 if (sdpyWindowClassStr !is null && sdpyWindowClassStr[0]) { 14898 //{ import core.stdc.stdio; printf("winclass: [%s]\n", sdpyWindowClassStr); } 14899 XClassHint klass; 14900 XWMHints wh; 14901 if(this.customizationFlags & WindowFlags.managesChildWindowFocus) { 14902 wh.input = true; 14903 wh.flags |= InputHint; 14904 } 14905 XSizeHints size; 14906 klass.res_name = sdpyWindowClassStr; 14907 klass.res_class = sdpyWindowClassStr; 14908 XSetWMProperties(display, window, null, null, null, 0, &size, &wh, &klass); 14909 } 14910 14911 setTitle(title); 14912 SimpleWindow.nativeMapping[window] = this; 14913 CapableOfHandlingNativeEvent.nativeHandleMapping[window] = this; 14914 14915 // This gives our window a close button 14916 if (windowType != WindowTypes.eventOnly) { 14917 Atom[2] atoms = [GetAtom!"WM_DELETE_WINDOW"(display), GetAtom!"WM_TAKE_FOCUS"(display)]; 14918 int useAtoms; 14919 if(this.customizationFlags & WindowFlags.managesChildWindowFocus) { 14920 useAtoms = 2; 14921 } else { 14922 useAtoms = 1; 14923 } 14924 assert(useAtoms <= atoms.length); 14925 XSetWMProtocols(display, window, atoms.ptr, useAtoms); 14926 } 14927 14928 // FIXME: windowType and customizationFlags 14929 Atom[8] wsatoms; // here, due to goto 14930 int wmsacount = 0; // here, due to goto 14931 14932 try 14933 final switch(windowType) { 14934 case WindowTypes.normal: 14935 setNetWMWindowType(GetAtom!"_NET_WM_WINDOW_TYPE_NORMAL"(display)); 14936 break; 14937 case WindowTypes.undecorated: 14938 motifHideDecorations(); 14939 setNetWMWindowType(GetAtom!"_NET_WM_WINDOW_TYPE_NORMAL"(display)); 14940 break; 14941 case WindowTypes.eventOnly: 14942 _hidden = true; 14943 XSelectInput(display, window, EventMask.StructureNotifyMask); // without this, we won't get destroy notification 14944 goto hiddenWindow; 14945 //break; 14946 case WindowTypes.nestedChild: 14947 // handled in XCreateWindow calls 14948 break; 14949 14950 case WindowTypes.dropdownMenu: 14951 motifHideDecorations(); 14952 setNetWMWindowType(GetAtom!"_NET_WM_WINDOW_TYPE_DROPDOWN_MENU"(display)); 14953 customizationFlags |= WindowFlags.skipTaskbar | WindowFlags.alwaysOnTop; 14954 break; 14955 case WindowTypes.popupMenu: 14956 motifHideDecorations(); 14957 setNetWMWindowType(GetAtom!"_NET_WM_WINDOW_TYPE_POPUP_MENU"(display)); 14958 customizationFlags |= WindowFlags.skipTaskbar | WindowFlags.alwaysOnTop; 14959 break; 14960 case WindowTypes.notification: 14961 motifHideDecorations(); 14962 setNetWMWindowType(GetAtom!"_NET_WM_WINDOW_TYPE_NOTIFICATION"(display)); 14963 customizationFlags |= WindowFlags.skipTaskbar | WindowFlags.alwaysOnTop; 14964 break; 14965 case WindowTypes.minimallyWrapped: 14966 assert(0, "don't create a minimallyWrapped thing explicitly!"); 14967 /+ 14968 case WindowTypes.menu: 14969 atoms[0] = GetAtom!"_NET_WM_WINDOW_TYPE_MENU"(display); 14970 motifHideDecorations(); 14971 break; 14972 case WindowTypes.desktop: 14973 atoms[0] = GetAtom!"_NET_WM_WINDOW_TYPE_DESKTOP"(display); 14974 break; 14975 case WindowTypes.dock: 14976 atoms[0] = GetAtom!"_NET_WM_WINDOW_TYPE_DOCK"(display); 14977 break; 14978 case WindowTypes.toolbar: 14979 atoms[0] = GetAtom!"_NET_WM_WINDOW_TYPE_TOOLBAR"(display); 14980 break; 14981 case WindowTypes.menu: 14982 atoms[0] = GetAtom!"_NET_WM_WINDOW_TYPE_MENU"(display); 14983 break; 14984 case WindowTypes.utility: 14985 atoms[0] = GetAtom!"_NET_WM_WINDOW_TYPE_UTILITY"(display); 14986 break; 14987 case WindowTypes.splash: 14988 atoms[0] = GetAtom!"_NET_WM_WINDOW_TYPE_SPLASH"(display); 14989 break; 14990 case WindowTypes.dialog: 14991 atoms[0] = GetAtom!"_NET_WM_WINDOW_TYPE_DIALOG"(display); 14992 break; 14993 case WindowTypes.tooltip: 14994 atoms[0] = GetAtom!"_NET_WM_WINDOW_TYPE_TOOLTIP"(display); 14995 break; 14996 case WindowTypes.notification: 14997 atoms[0] = GetAtom!"_NET_WM_WINDOW_TYPE_NOTIFICATION"(display); 14998 break; 14999 case WindowTypes.combo: 15000 atoms[0] = GetAtom!"_NET_WM_WINDOW_TYPE_COMBO"(display); 15001 break; 15002 case WindowTypes.dnd: 15003 atoms[0] = GetAtom!"_NET_WM_WINDOW_TYPE_DND"(display); 15004 break; 15005 +/ 15006 } 15007 catch(Exception e) { 15008 // XInternAtom failed, prolly a WM 15009 // that doesn't support these things 15010 } 15011 15012 if (customizationFlags&WindowFlags.skipTaskbar) wsatoms[wmsacount++] = GetAtom!("_NET_WM_STATE_SKIP_TASKBAR", true)(display); 15013 // the two following flags may be ignored by WM 15014 if (customizationFlags&WindowFlags.alwaysOnTop) wsatoms[wmsacount++] = GetAtom!("_NET_WM_STATE_ABOVE", true)(display); 15015 if (customizationFlags&WindowFlags.alwaysOnBottom) wsatoms[wmsacount++] = GetAtom!("_NET_WM_STATE_BELOW", true)(display); 15016 15017 if (wmsacount != 0) XChangeProperty(display, window, GetAtom!("_NET_WM_STATE", true)(display), XA_ATOM, 32 /* bits */,0 /*PropModeReplace*/, wsatoms.ptr, wmsacount); 15018 15019 if (this.resizability == Resizability.fixedSize || (opengl == OpenGlOptions.no && this.resizability != Resizability.allowResizing)) fixFixedSize!true(width, height); 15020 15021 // What would be ideal here is if they only were 15022 // selected if there was actually an event handler 15023 // for them... 15024 15025 selectDefaultInput((customizationFlags & WindowFlags.alwaysRequestMouseMotionEvents)?true:false); 15026 15027 hiddenWindow: 15028 15029 // set the pid property for lookup later by window managers 15030 // a standard convenience 15031 import core.sys.posix.unistd; 15032 arch_ulong pid = getpid(); 15033 15034 XChangeProperty( 15035 display, 15036 impl.window, 15037 GetAtom!("_NET_WM_PID", true)(display), 15038 XA_CARDINAL, 15039 32 /* bits */, 15040 0 /*PropModeReplace*/, 15041 &pid, 15042 1); 15043 15044 if(isTransient && parent) { // customizationFlags & WindowFlags.transient) { 15045 if(parent is null) assert(0); 15046 XChangeProperty( 15047 display, 15048 impl.window, 15049 GetAtom!("WM_TRANSIENT_FOR", true)(display), 15050 XA_WINDOW, 15051 32 /* bits */, 15052 0 /*PropModeReplace*/, 15053 &parent.impl.window, 15054 1); 15055 15056 } 15057 15058 if(windowType != WindowTypes.eventOnly && (customizationFlags&WindowFlags.dontAutoShow) == 0) { 15059 XMapWindow(display, window); 15060 } else { 15061 _hidden = true; 15062 } 15063 } 15064 15065 void populateXic() { 15066 if (XDisplayConnection.xim !is null) { 15067 xic = XCreateIC(XDisplayConnection.xim, 15068 /*XNInputStyle*/"inputStyle".ptr, XIMPreeditNothing|XIMStatusNothing, 15069 /*XNClientWindow*/"clientWindow".ptr, window, 15070 /*XNFocusWindow*/"focusWindow".ptr, window, 15071 null); 15072 if (xic is null) { 15073 import core.stdc.stdio : stderr, fprintf; 15074 fprintf(stderr, "XCreateIC failed for window %u\n", cast(uint)window); 15075 } 15076 } 15077 } 15078 15079 void selectDefaultInput(bool forceIncludeMouseMotion) { 15080 auto mask = EventMask.ExposureMask | 15081 EventMask.KeyPressMask | 15082 EventMask.KeyReleaseMask | 15083 EventMask.PropertyChangeMask | 15084 EventMask.FocusChangeMask | 15085 EventMask.StructureNotifyMask | 15086 EventMask.SubstructureNotifyMask | 15087 EventMask.VisibilityChangeMask 15088 | EventMask.ButtonPressMask 15089 | EventMask.ButtonReleaseMask 15090 ; 15091 15092 // xshm is our shortcut for local connections 15093 if(XDisplayConnection.isLocal || forceIncludeMouseMotion) 15094 mask |= EventMask.PointerMotionMask; 15095 else 15096 mask |= EventMask.ButtonMotionMask; 15097 15098 XSelectInput(display, window, mask); 15099 } 15100 15101 15102 void setNetWMWindowType(Atom type) { 15103 Atom[2] atoms; 15104 15105 atoms[0] = type; 15106 // generic fallback 15107 atoms[1] = GetAtom!"_NET_WM_WINDOW_TYPE_NORMAL"(display); 15108 15109 XChangeProperty( 15110 display, 15111 impl.window, 15112 GetAtom!"_NET_WM_WINDOW_TYPE"(display), 15113 XA_ATOM, 15114 32 /* bits */, 15115 0 /*PropModeReplace*/, 15116 atoms.ptr, 15117 cast(int) atoms.length); 15118 } 15119 15120 void motifHideDecorations(bool hide = true) { 15121 MwmHints hints; 15122 hints.flags = MWM_HINTS_DECORATIONS; 15123 hints.decorations = hide ? 0 : 1; 15124 15125 XChangeProperty( 15126 display, 15127 impl.window, 15128 GetAtom!"_MOTIF_WM_HINTS"(display), 15129 GetAtom!"_MOTIF_WM_HINTS"(display), 15130 32 /* bits */, 15131 0 /*PropModeReplace*/, 15132 &hints, 15133 hints.sizeof / 4); 15134 } 15135 15136 /*k8: unused 15137 void createOpenGlContext() { 15138 15139 } 15140 */ 15141 15142 void closeWindow() { 15143 // I can't close this or a child window closing will 15144 // break events for everyone. So I'm just leaking it right 15145 // now and that is probably perfectly fine... 15146 version(none) 15147 if (customEventFDRead != -1) { 15148 import core.sys.posix.unistd : close; 15149 auto same = customEventFDRead == customEventFDWrite; 15150 15151 close(customEventFDRead); 15152 if(!same) 15153 close(customEventFDWrite); 15154 customEventFDRead = -1; 15155 customEventFDWrite = -1; 15156 } 15157 15158 version(without_opengl) {} else 15159 if(glc !is null) { 15160 glXDestroyContext(display, glc); 15161 glc = null; 15162 } 15163 15164 if(buffer) 15165 XFreePixmap(display, buffer); 15166 bufferw = bufferh = 0; 15167 if (blankCurPtr && cursorSequenceNumber == XDisplayConnection.connectionSequenceNumber) XFreeCursor(display, blankCurPtr); 15168 XDestroyWindow(display, window); 15169 XFlush(display); 15170 } 15171 15172 void dispose() { 15173 } 15174 15175 bool destroyed = false; 15176 } 15177 15178 bool insideXEventLoop; 15179 } 15180 15181 version(X11) { 15182 15183 int mouseDoubleClickTimeout = 350; /// Double click timeout. X only, you probably shouldn't change this. 15184 15185 private class ResizeEvent { 15186 int width, height; 15187 } 15188 15189 void recordX11ResizeAsync(Display* display, SimpleWindow win, int width, int height) { 15190 if(win.windowType == WindowTypes.minimallyWrapped) 15191 return; 15192 15193 if(win.pendingResizeEvent is null) { 15194 win.pendingResizeEvent = new ResizeEvent(); 15195 win.addEventListener((ResizeEvent re) { 15196 recordX11Resize(XDisplayConnection.get, win, re.width, re.height); 15197 }); 15198 } 15199 win.pendingResizeEvent.width = width; 15200 win.pendingResizeEvent.height = height; 15201 if(!win.eventQueued!ResizeEvent) { 15202 win.postEvent(win.pendingResizeEvent); 15203 } 15204 } 15205 15206 void recordX11Resize(Display* display, SimpleWindow win, int width, int height) { 15207 if(win.windowType == WindowTypes.minimallyWrapped) 15208 return; 15209 if(win.closed) 15210 return; 15211 15212 if(width != win.width || height != win.height) { 15213 15214 // writeln("RESIZE: ", width, "x", height, " was ", win._width, "x", win._height, " window: ", win.windowType, "-", win.title, " ", win.window); 15215 win._width = width; 15216 win._height = height; 15217 15218 if(win.openglMode == OpenGlOptions.no) { 15219 // FIXME: could this be more efficient? 15220 15221 if (win.bufferw < width || win.bufferh < height) { 15222 //{ 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); } 15223 // grow the internal buffer to match the window... 15224 auto newPixmap = XCreatePixmap(display, cast(Drawable) win.window, width, height, DefaultDepthOfDisplay(display)); 15225 { 15226 GC xgc = XCreateGC(win.display, cast(Drawable)win.window, 0, null); 15227 XCopyGC(win.display, win.gc, 0xffffffff, xgc); 15228 scope(exit) XFreeGC(win.display, xgc); 15229 XSetClipMask(win.display, xgc, None); 15230 XSetForeground(win.display, xgc, 0); 15231 XFillRectangle(display, cast(Drawable)newPixmap, xgc, 0, 0, width, height); 15232 } 15233 XCopyArea(display, 15234 cast(Drawable) win.buffer, 15235 cast(Drawable) newPixmap, 15236 win.gc, 0, 0, 15237 win.bufferw < width ? win.bufferw : win.width, 15238 win.bufferh < height ? win.bufferh : win.height, 15239 0, 0); 15240 15241 XFreePixmap(display, win.buffer); 15242 win.buffer = newPixmap; 15243 win.bufferw = width; 15244 win.bufferh = height; 15245 } 15246 15247 // clear unused parts of the buffer 15248 if (win.bufferw > width || win.bufferh > height) { 15249 GC xgc = XCreateGC(win.display, cast(Drawable)win.window, 0, null); 15250 XCopyGC(win.display, win.gc, 0xffffffff, xgc); 15251 scope(exit) XFreeGC(win.display, xgc); 15252 XSetClipMask(win.display, xgc, None); 15253 XSetForeground(win.display, xgc, 0); 15254 immutable int maxw = (win.bufferw > width ? win.bufferw : width); 15255 immutable int maxh = (win.bufferh > height ? win.bufferh : height); 15256 XFillRectangle(win.display, cast(Drawable)win.buffer, xgc, width, 0, maxw, maxh); // let X11 do clipping 15257 XFillRectangle(win.display, cast(Drawable)win.buffer, xgc, 0, height, maxw, maxh); // let X11 do clipping 15258 } 15259 15260 } 15261 15262 win.updateOpenglViewportIfNeeded(width, height); 15263 15264 win.fixFixedSize(width, height); //k8: this does nothing on my FluxBox; wtf?! 15265 15266 if(win.resizability != Resizability.automaticallyScaleIfPossible) 15267 if(win.windowResized !is null) { 15268 XUnlockDisplay(display); 15269 scope(exit) XLockDisplay(display); 15270 win.windowResized(width, height); 15271 } 15272 } 15273 } 15274 15275 15276 /// Platform-specific, you might use it when doing a custom event loop. 15277 bool doXNextEvent(Display* display) { 15278 bool done; 15279 XEvent e; 15280 XNextEvent(display, &e); 15281 version(sddddd) { 15282 if(auto win = e.xany.window in CapableOfHandlingNativeEvent.nativeHandleMapping) { 15283 if(typeid(cast(Object) *win) == NotificationAreaIcon.classinfo) 15284 writeln("event for: ", e.xany.window, "; type is ", to!string(cast(EventType)e.type)); 15285 } 15286 } 15287 15288 // filter out compose events 15289 if (XFilterEvent(&e, None)) { 15290 //{ import core.stdc.stdio : printf; printf("XFilterEvent filtered!\n"); } 15291 //NOTE: we should ungrab keyboard here, but simpledisplay doesn't use keyboard grabbing (yet) 15292 return false; 15293 } 15294 // process keyboard mapping changes 15295 if (e.type == EventType.KeymapNotify) { 15296 //{ import core.stdc.stdio : printf; printf("KeymapNotify processed!\n"); } 15297 XRefreshKeyboardMapping(&e.xmapping); 15298 return false; 15299 } 15300 15301 version(with_eventloop) 15302 import arsd.eventloop; 15303 15304 if(SimpleWindow.handleNativeGlobalEvent !is null) { 15305 // see windows impl's comments 15306 XUnlockDisplay(display); 15307 scope(exit) XLockDisplay(display); 15308 auto ret = SimpleWindow.handleNativeGlobalEvent(e); 15309 if(ret == 0) 15310 return done; 15311 } 15312 15313 15314 if(auto win = e.xany.window in CapableOfHandlingNativeEvent.nativeHandleMapping) { 15315 if(win.getNativeEventHandler !is null) { 15316 XUnlockDisplay(display); 15317 scope(exit) XLockDisplay(display); 15318 auto ret = win.getNativeEventHandler()(e); 15319 if(ret == 0) 15320 return done; 15321 } 15322 } 15323 15324 if(xrrEventBase != -1 && e.type == xrrEventBase + RRScreenChangeNotify) { 15325 if(auto win = e.xany.window in SimpleWindow.nativeMapping) { 15326 // we get this because of the RRScreenChangeNotifyMask 15327 15328 // this isn't actually an ideal way to do it since it wastes time 15329 // but meh it is simple and it works. 15330 win.actualDpiLoadAttempted = false; 15331 SimpleWindow.xRandrInfoLoadAttemped = false; 15332 win.updateActualDpi(); // trigger a reload 15333 } 15334 } 15335 15336 switch(e.type) { 15337 case EventType.SelectionClear: 15338 if(auto win = e.xselectionclear.window in SimpleWindow.nativeMapping) { 15339 // FIXME so it is supposed to finish any in progress transfers... but idk... 15340 // writeln("SelectionClear"); 15341 SimpleWindow.impl.setSelectionHandlers.remove(e.xselectionclear.selection); 15342 } 15343 break; 15344 case EventType.SelectionRequest: 15345 if(auto win = e.xselectionrequest.owner in SimpleWindow.nativeMapping) 15346 if(auto ssh = e.xselectionrequest.selection in SimpleWindow.impl.setSelectionHandlers) { 15347 // printf("SelectionRequest %s\n", XGetAtomName(e.xselectionrequest.display, e.xselectionrequest.target)); 15348 XUnlockDisplay(display); 15349 scope(exit) XLockDisplay(display); 15350 (*ssh).handleRequest(e); 15351 } 15352 break; 15353 case EventType.PropertyNotify: 15354 // printf("PropertyNotify %s %d\n", XGetAtomName(e.xproperty.display, e.xproperty.atom), e.xproperty.state); 15355 15356 foreach(ssh; SimpleWindow.impl.setSelectionHandlers) { 15357 if(ssh.matchesIncr(e.xproperty.window, e.xproperty.atom) && e.xproperty.state == PropertyNotification.PropertyDelete) 15358 ssh.sendMoreIncr(&e.xproperty); 15359 } 15360 15361 15362 if(auto win = e.xproperty.window in SimpleWindow.nativeMapping) 15363 if(auto handler = e.xproperty.atom in win.getSelectionHandlers) { 15364 if(handler.matchesIncr(e.xproperty.window, e.xproperty.atom) && e.xproperty.state == PropertyNotification.PropertyNewValue) { 15365 Atom target; 15366 int format; 15367 arch_ulong bytesafter, length; 15368 void* value; 15369 15370 ubyte[] s; 15371 Atom targetToKeep; 15372 15373 XGetWindowProperty( 15374 e.xproperty.display, 15375 e.xproperty.window, 15376 e.xproperty.atom, 15377 0, 15378 100000 /* length */, 15379 true, /* erase it to signal we got it and want more */ 15380 0 /*AnyPropertyType*/, 15381 &target, &format, &length, &bytesafter, &value); 15382 15383 if(!targetToKeep) 15384 targetToKeep = target; 15385 15386 auto id = (cast(ubyte*) value)[0 .. length]; 15387 15388 handler.handleIncrData(targetToKeep, id); 15389 15390 XFree(value); 15391 } 15392 } 15393 break; 15394 case EventType.SelectionNotify: 15395 if(auto win = e.xselection.requestor in SimpleWindow.nativeMapping) 15396 if(auto handler = e.xproperty.atom in win.getSelectionHandlers) { 15397 if(e.xselection.property == None) { // || e.xselection.property == GetAtom!("NULL", true)(e.xselection.display)) { 15398 XUnlockDisplay(display); 15399 scope(exit) XLockDisplay(display); 15400 handler.handleData(None, null); 15401 } else { 15402 Atom target; 15403 int format; 15404 arch_ulong bytesafter, length; 15405 void* value; 15406 XGetWindowProperty( 15407 e.xselection.display, 15408 e.xselection.requestor, 15409 e.xselection.property, 15410 0, 15411 100000 /* length */, 15412 //false, /* don't erase it */ 15413 true, /* do erase it lol */ 15414 0 /*AnyPropertyType*/, 15415 &target, &format, &length, &bytesafter, &value); 15416 15417 // FIXME: I don't have to copy it now since it is in char[] instead of string 15418 15419 { 15420 XUnlockDisplay(display); 15421 scope(exit) XLockDisplay(display); 15422 15423 if(target == XA_ATOM) { 15424 // initial request, see what they are able to work with and request the best one 15425 // we can handle, if available 15426 15427 Atom[] answer = (cast(Atom*) value)[0 .. length]; 15428 Atom best = handler.findBestFormat(answer); 15429 15430 /+ 15431 writeln("got ", answer); 15432 foreach(a; answer) 15433 printf("%s\n", XGetAtomName(display, a)); 15434 writeln("best ", best); 15435 +/ 15436 15437 if(best != None) { 15438 // actually request the best format 15439 XConvertSelection(e.xselection.display, e.xselection.selection, best, GetAtom!("SDD_DATA", true)(display), e.xselection.requestor, 0 /*CurrentTime*/); 15440 } 15441 } else if(target == GetAtom!"INCR"(display)) { 15442 // incremental 15443 15444 handler.prepareIncremental(e.xselection.requestor, e.xselection.property); 15445 15446 // signal the sending program that we see 15447 // the incr and are ready to receive more. 15448 XDeleteProperty( 15449 e.xselection.display, 15450 e.xselection.requestor, 15451 e.xselection.property); 15452 } else { 15453 // unsupported type... maybe, forward 15454 handler.handleData(target, cast(ubyte[]) value[0 .. length]); 15455 } 15456 } 15457 XFree(value); 15458 /* 15459 XDeleteProperty( 15460 e.xselection.display, 15461 e.xselection.requestor, 15462 e.xselection.property); 15463 */ 15464 } 15465 } 15466 break; 15467 case EventType.ConfigureNotify: 15468 auto event = e.xconfigure; 15469 if(auto win = event.window in SimpleWindow.nativeMapping) { 15470 if(win.windowType == WindowTypes.minimallyWrapped) 15471 break; 15472 //version(sdddd) { writeln(" w=", event.width, "; h=", event.height); } 15473 15474 /+ 15475 The ICCCM says window managers must send a synthetic event when the window 15476 is moved but NOT when it is resized. In the resize case, an event is sent 15477 with position (0, 0) which can be wrong and break the dpi calculations. 15478 15479 So we only consider the synthetic events from the WM and otherwise 15480 need to wait for some other event to get the position which... sucks. 15481 15482 I'd rather not have windows changing their layout on mouse motion after 15483 switching monitors... might be forced to but for now just ignoring it. 15484 15485 Easiest way to switch monitors without sending a size position is by 15486 maximize or fullscreen in a setup like mine, but on most setups those 15487 work on the monitor it is already living on, so it should be ok most the 15488 time. 15489 +/ 15490 if(event.send_event) { 15491 win.screenPositionKnown = true; 15492 win.screenPositionX = event.x; 15493 win.screenPositionY = event.y; 15494 win.updateActualDpi(); 15495 } 15496 15497 win.updateIMEPopupLocation(); 15498 recordX11ResizeAsync(display, *win, event.width, event.height); 15499 } 15500 break; 15501 case EventType.Expose: 15502 if(auto win = e.xexpose.window in SimpleWindow.nativeMapping) { 15503 if(win.windowType == WindowTypes.minimallyWrapped) 15504 break; 15505 // if it is closing from a popup menu, it can get 15506 // an Expose event right by the end and trigger a 15507 // BadDrawable error ... we'll just check 15508 // closed to handle that. 15509 if((*win).closed) break; 15510 if((*win).openglMode == OpenGlOptions.no) { 15511 bool doCopy = true;// e.xexpose.count == 0; // the count is better if we copy all area but meh 15512 if (win.handleExpose !is null) doCopy = !win.handleExpose(e.xexpose.x, e.xexpose.y, e.xexpose.width, e.xexpose.height, e.xexpose.count); 15513 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); 15514 } else { 15515 // need to redraw the scene somehow 15516 if(e.xexpose.count == 0) { // only do the last one since redrawOpenGlSceneNow always does it all 15517 XUnlockDisplay(display); 15518 scope(exit) XLockDisplay(display); 15519 version(without_opengl) {} else 15520 win.redrawOpenGlSceneSoon(); 15521 } 15522 } 15523 } 15524 break; 15525 case EventType.FocusIn: 15526 case EventType.FocusOut: 15527 15528 if(auto win = e.xfocus.window in SimpleWindow.nativeMapping) { 15529 /+ 15530 15531 void info(string detail) { 15532 string s; 15533 // import std.conv; 15534 // import std.datetime; 15535 s ~= to!string(Clock.currTime); 15536 s ~= " "; 15537 s ~= e.type == EventType.FocusIn ? "in " : "out"; 15538 s ~= " "; 15539 s ~= win.windowType == WindowTypes.nestedChild ? "child " : "main "; 15540 s ~= e.xfocus.mode == NotifyModes.NotifyNormal ? " normal ": " grabbed "; 15541 s ~= detail; 15542 s ~= " "; 15543 15544 sdpyPrintDebugString(s); 15545 15546 } 15547 15548 switch(e.xfocus.detail) { 15549 case NotifyDetail.NotifyAncestor: info("Ancestor"); break; 15550 case NotifyDetail.NotifyVirtual: info("Virtual"); break; 15551 case NotifyDetail.NotifyInferior: info("Inferior"); break; 15552 case NotifyDetail.NotifyNonlinear: info("Nonlinear"); break; 15553 case NotifyDetail.NotifyNonlinearVirtual: info("nlinearvirtual"); break; 15554 case NotifyDetail.NotifyPointer: info("pointer"); break; 15555 case NotifyDetail.NotifyPointerRoot: info("pointerroot"); break; 15556 case NotifyDetail.NotifyDetailNone: info("none"); break; 15557 default: 15558 15559 } 15560 +/ 15561 15562 15563 if(e.xfocus.detail == NotifyDetail.NotifyPointer) 15564 break; // just ignore these they seem irrelevant 15565 15566 auto old = win._focused; 15567 win._focused = e.type == EventType.FocusIn; 15568 15569 // yes, we are losing the focus, but to our own child. that's actually kinda keeping it. 15570 if(e.type == EventType.FocusOut && e.xfocus.detail == NotifyDetail.NotifyInferior) 15571 win._focused = true; 15572 15573 if(win.demandingAttention) 15574 demandAttention(*win, false); 15575 15576 win.updateIMEFocused(); 15577 15578 if(old != win._focused && win.onFocusChange) { 15579 XUnlockDisplay(display); 15580 scope(exit) XLockDisplay(display); 15581 win.onFocusChange(win._focused); 15582 } 15583 } 15584 break; 15585 case EventType.VisibilityNotify: 15586 if(auto win = e.xfocus.window in SimpleWindow.nativeMapping) { 15587 if (e.xvisibility.state == VisibilityNotify.VisibilityFullyObscured) { 15588 if (win.visibilityChanged !is null) { 15589 XUnlockDisplay(display); 15590 scope(exit) XLockDisplay(display); 15591 win.visibilityChanged(false); 15592 } 15593 } else { 15594 if (win.visibilityChanged !is null) { 15595 XUnlockDisplay(display); 15596 scope(exit) XLockDisplay(display); 15597 win.visibilityChanged(true); 15598 } 15599 } 15600 } 15601 break; 15602 case EventType.ClientMessage: 15603 if (e.xclient.message_type == GetAtom!("_X11SDPY_INSMME_FLAG_EVENT_", true)(e.xany.display)) { 15604 // "ignore next mouse motion" event, increment ignore counter for teh window 15605 if (auto win = e.xclient.window in SimpleWindow.nativeMapping) { 15606 ++(*win).warpEventCount; 15607 debug(x11sdpy_warp_debug) { import core.stdc.stdio : printf; printf("X11: got \"INSMME\" message, new count=%d\n", (*win).warpEventCount); } 15608 } else { 15609 debug(x11sdpy_warp_debug) { import core.stdc.stdio : printf; printf("X11: got \"INSMME\" WTF?!!\n"); } 15610 } 15611 } else if(e.xclient.data.l[0] == GetAtom!"WM_DELETE_WINDOW"(e.xany.display)) { 15612 // user clicked the close button on the window manager 15613 if(auto win = e.xclient.window in SimpleWindow.nativeMapping) { 15614 XUnlockDisplay(display); 15615 scope(exit) XLockDisplay(display); 15616 if ((*win).closeQuery !is null) (*win).closeQuery(); else (*win).close(); 15617 } 15618 15619 } else if(e.xclient.data.l[0] == GetAtom!"WM_TAKE_FOCUS"(e.xany.display)) { 15620 // writeln("HAPPENED"); 15621 // user clicked the close button on the window manager 15622 if(auto win = e.xclient.window in SimpleWindow.nativeMapping) { 15623 XUnlockDisplay(display); 15624 scope(exit) XLockDisplay(display); 15625 15626 auto setTo = *win; 15627 15628 if(win.setRequestedInputFocus !is null) { 15629 auto s = win.setRequestedInputFocus(); 15630 if(s !is null) { 15631 setTo = s; 15632 } 15633 } 15634 15635 assert(setTo !is null); 15636 15637 // FIXME: so this is actually supposed to focus to a relevant child window if appropriate 15638 15639 XSetInputFocus(display, setTo.impl.window, RevertToParent, e.xclient.data.l[1]); 15640 } 15641 } else if(e.xclient.message_type == GetAtom!"MANAGER"(e.xany.display)) { 15642 foreach(nai; NotificationAreaIcon.activeIcons) 15643 nai.newManager(); 15644 } else if(auto win = e.xclient.window in SimpleWindow.nativeMapping) { 15645 15646 bool xDragWindow = true; 15647 if(xDragWindow && e.xclient.message_type == GetAtom!"XdndStatus"(e.xany.display)) { 15648 //XDefineCursor(display, xDragWindow.impl.window, 15649 //writeln("XdndStatus ", e.xclient.data.l); 15650 } 15651 if(auto dh = win.dropHandler) { 15652 15653 static Atom[3] xFormatsBuffer; 15654 static Atom[] xFormats; 15655 15656 void resetXFormats() { 15657 xFormatsBuffer[] = 0; 15658 xFormats = xFormatsBuffer[]; 15659 } 15660 15661 if(e.xclient.message_type == GetAtom!"XdndEnter"(e.xany.display)) { 15662 // on Windows it is supposed to return the effect you actually do FIXME 15663 15664 auto sourceWindow = e.xclient.data.l[0]; 15665 15666 xFormatsBuffer[0] = e.xclient.data.l[2]; 15667 xFormatsBuffer[1] = e.xclient.data.l[3]; 15668 xFormatsBuffer[2] = e.xclient.data.l[4]; 15669 15670 if(e.xclient.data.l[1] & 1) { 15671 // can just grab it all but like we don't necessarily need them... 15672 xFormats = cast(Atom[]) getX11PropertyData(sourceWindow, GetAtom!"XdndTypeList"(display), XA_ATOM); 15673 } else { 15674 int len; 15675 foreach(fmt; xFormatsBuffer) 15676 if(fmt) len++; 15677 xFormats = xFormatsBuffer[0 .. len]; 15678 } 15679 15680 auto pkg = DropPackage(*win, e.xclient.data.l[0], 0, xFormats); 15681 15682 dh.dragEnter(&pkg); 15683 } else if(e.xclient.message_type == GetAtom!"XdndPosition"(e.xany.display)) { 15684 15685 auto pack = e.xclient.data.l[2]; 15686 15687 auto result = dh.dragOver(Point((pack & 0xffff0000) >> 16, pack & 0xffff)); // FIXME: translate screen coordinates back to window coords 15688 15689 15690 XClientMessageEvent xclient; 15691 15692 xclient.type = EventType.ClientMessage; 15693 xclient.window = e.xclient.data.l[0]; 15694 xclient.message_type = GetAtom!"XdndStatus"(display); 15695 xclient.format = 32; 15696 xclient.data.l[0] = win.impl.window; 15697 xclient.data.l[1] = (result.action != DragAndDropAction.none) ? 1 : 0; // will accept 15698 auto r = result.consistentWithin; 15699 xclient.data.l[2] = ((cast(short) r.left) << 16) | (cast(short) r.top); 15700 xclient.data.l[3] = ((cast(short) r.width) << 16) | (cast(short) r.height); 15701 xclient.data.l[4] = dndActionAtom(e.xany.display, result.action); 15702 15703 XSendEvent( 15704 display, 15705 e.xclient.data.l[0], 15706 false, 15707 EventMask.NoEventMask, 15708 cast(XEvent*) &xclient 15709 ); 15710 15711 15712 } else if(e.xclient.message_type == GetAtom!"XdndLeave"(e.xany.display)) { 15713 //writeln("XdndLeave"); 15714 // drop cancelled. 15715 // data.l[0] is the source window 15716 dh.dragLeave(); 15717 15718 resetXFormats(); 15719 } else if(e.xclient.message_type == GetAtom!"XdndDrop"(e.xany.display)) { 15720 // drop happening, should fetch data, then send finished 15721 // writeln("XdndDrop"); 15722 15723 auto pkg = DropPackage(*win, e.xclient.data.l[0], e.xclient.data.l[2], xFormats); 15724 15725 dh.drop(&pkg); 15726 15727 resetXFormats(); 15728 } else if(e.xclient.message_type == GetAtom!"XdndFinished"(e.xany.display)) { 15729 // writeln("XdndFinished"); 15730 15731 dh.finish(); 15732 } 15733 15734 } 15735 } 15736 break; 15737 case EventType.MapNotify: 15738 if(auto win = e.xmap.window in SimpleWindow.nativeMapping) { 15739 (*win)._visible = true; 15740 if (!(*win)._visibleForTheFirstTimeCalled) { 15741 (*win)._visibleForTheFirstTimeCalled = true; 15742 if ((*win).visibleForTheFirstTime !is null) { 15743 XUnlockDisplay(display); 15744 scope(exit) XLockDisplay(display); 15745 (*win).visibleForTheFirstTime(); 15746 } 15747 } 15748 if ((*win).visibilityChanged !is null) { 15749 XUnlockDisplay(display); 15750 scope(exit) XLockDisplay(display); 15751 (*win).visibilityChanged(true); 15752 } 15753 } 15754 break; 15755 case EventType.UnmapNotify: 15756 if(auto win = e.xunmap.window in SimpleWindow.nativeMapping) { 15757 win._visible = false; 15758 if (win.visibilityChanged !is null) { 15759 XUnlockDisplay(display); 15760 scope(exit) XLockDisplay(display); 15761 win.visibilityChanged(false); 15762 } 15763 } 15764 break; 15765 case EventType.DestroyNotify: 15766 if(auto win = e.xdestroywindow.window in SimpleWindow.nativeMapping) { 15767 if(win.destroyed) 15768 break; // might get a notification both for itself and from its parent 15769 if (win.onDestroyed !is null) try { win.onDestroyed(); } catch (Exception e) {} // sorry 15770 win._closed = true; // just in case 15771 win.destroyed = true; 15772 if (win.xic !is null) { 15773 XDestroyIC(win.xic); 15774 win.xic = null; // just in case 15775 } 15776 SimpleWindow.nativeMapping.remove(e.xdestroywindow.window); 15777 bool anyImportant = false; 15778 foreach(SimpleWindow w; SimpleWindow.nativeMapping) 15779 if(w.beingOpenKeepsAppOpen) { 15780 anyImportant = true; 15781 break; 15782 } 15783 if(!anyImportant) { 15784 EventLoop.quitApplication(); 15785 done = true; 15786 } 15787 } 15788 auto window = e.xdestroywindow.window; 15789 if(window in CapableOfHandlingNativeEvent.nativeHandleMapping) 15790 CapableOfHandlingNativeEvent.nativeHandleMapping.remove(window); 15791 15792 version(with_eventloop) { 15793 if(done) exit(); 15794 } 15795 break; 15796 15797 case EventType.MotionNotify: 15798 MouseEvent mouse; 15799 auto event = e.xmotion; 15800 15801 mouse.type = MouseEventType.motion; 15802 mouse.x = event.x; 15803 mouse.y = event.y; 15804 mouse.modifierState = event.state; 15805 15806 mouse.timestamp = event.time; 15807 15808 if(auto win = e.xmotion.window in SimpleWindow.nativeMapping) { 15809 mouse.window = *win; 15810 if (win.warpEventCount > 0) { 15811 debug(x11sdpy_warp_debug) { import core.stdc.stdio : printf; printf("X11: got \"warp motion\" message, current count=%d\n", (*win).warpEventCount); } 15812 --(*win).warpEventCount; 15813 (*win).mdx(mouse); // so deltas will be correctly updated 15814 } else { 15815 win.warpEventCount = 0; // just in case 15816 (*win).mdx(mouse); 15817 if((*win).handleMouseEvent) { 15818 XUnlockDisplay(display); 15819 scope(exit) XLockDisplay(display); 15820 (*win).handleMouseEvent(mouse); 15821 } 15822 } 15823 } 15824 15825 version(with_eventloop) 15826 send(mouse); 15827 break; 15828 case EventType.ButtonPress: 15829 case EventType.ButtonRelease: 15830 MouseEvent mouse; 15831 auto event = e.xbutton; 15832 15833 mouse.timestamp = event.time; 15834 15835 mouse.type = cast(MouseEventType) (e.type == EventType.ButtonPress ? 1 : 2); 15836 mouse.x = event.x; 15837 mouse.y = event.y; 15838 15839 static Time lastMouseDownTime = 0; 15840 static int lastMouseDownButton = -1; 15841 15842 mouse.doubleClick = e.type == EventType.ButtonPress && event.button == lastMouseDownButton && (event.time - lastMouseDownTime) < mouseDoubleClickTimeout; 15843 if(e.type == EventType.ButtonPress) { 15844 lastMouseDownTime = event.time; 15845 lastMouseDownButton = event.button; 15846 } 15847 15848 switch(event.button) { 15849 case 1: mouse.button = MouseButton.left; break; // left 15850 case 2: mouse.button = MouseButton.middle; break; // middle 15851 case 3: mouse.button = MouseButton.right; break; // right 15852 case 4: mouse.button = MouseButton.wheelUp; break; // scroll up 15853 case 5: mouse.button = MouseButton.wheelDown; break; // scroll down 15854 case 6: break; // idk 15855 case 7: break; // idk 15856 case 8: mouse.button = MouseButton.backButton; break; 15857 case 9: mouse.button = MouseButton.forwardButton; break; 15858 default: 15859 } 15860 15861 // FIXME: double check this 15862 mouse.modifierState = event.state; 15863 15864 //mouse.modifierState = event.detail; 15865 15866 if(auto win = e.xbutton.window in SimpleWindow.nativeMapping) { 15867 mouse.window = *win; 15868 (*win).mdx(mouse); 15869 if((*win).handleMouseEvent) { 15870 XUnlockDisplay(display); 15871 scope(exit) XLockDisplay(display); 15872 (*win).handleMouseEvent(mouse); 15873 } 15874 } 15875 version(with_eventloop) 15876 send(mouse); 15877 break; 15878 15879 case EventType.KeyPress: 15880 case EventType.KeyRelease: 15881 //if (e.type == EventType.KeyPress) { import core.stdc.stdio : stderr, fprintf; fprintf(stderr, "X11 keyboard event!\n"); } 15882 KeyEvent ke; 15883 ke.pressed = e.type == EventType.KeyPress; 15884 ke.hardwareCode = cast(ubyte) e.xkey.keycode; 15885 15886 auto sym = XKeycodeToKeysym( 15887 XDisplayConnection.get(), 15888 e.xkey.keycode, 15889 0); 15890 15891 ke.key = cast(Key) sym;//e.xkey.keycode; 15892 15893 ke.modifierState = e.xkey.state; 15894 15895 // writefln("%x", sym); 15896 wchar_t[128] charbuf = void; // buffer for XwcLookupString; composed value can consist of many chars! 15897 int charbuflen = 0; // return value of XwcLookupString 15898 if (ke.pressed) { 15899 auto win = e.xkey.window in SimpleWindow.nativeMapping; 15900 if (win !is null && win.xic !is null) { 15901 //{ import core.stdc.stdio : printf; printf("using xic!\n"); } 15902 Status status; 15903 charbuflen = XwcLookupString(win.xic, &e.xkey, charbuf.ptr, cast(int)charbuf.length, &sym, &status); 15904 //{ import core.stdc.stdio : printf; printf("charbuflen=%d\n", charbuflen); } 15905 } else { 15906 //{ import core.stdc.stdio : printf; printf("NOT using xic!\n"); } 15907 // If XIM initialization failed, don't process intl chars. Sorry, boys and girls. 15908 char[16] buffer; 15909 auto res = XLookupString(&e.xkey, buffer.ptr, buffer.length, null, null); 15910 if (res && buffer[0] < 128) charbuf[charbuflen++] = cast(wchar_t)buffer[0]; 15911 } 15912 } 15913 15914 // if there's no char, subst one 15915 if (charbuflen == 0) { 15916 switch (sym) { 15917 case 0xff09: charbuf[charbuflen++] = '\t'; break; 15918 case 0xff8d: // keypad enter 15919 case 0xff0d: charbuf[charbuflen++] = '\n'; break; 15920 default : // ignore 15921 } 15922 } 15923 15924 if (auto win = e.xkey.window in SimpleWindow.nativeMapping) { 15925 ke.window = *win; 15926 15927 15928 if(win.inputProxy) 15929 win = &win.inputProxy; 15930 15931 // char events are separate since they are on Windows too 15932 // also, xcompose can generate long char sequences 15933 // don't send char events if Meta and/or Hyper is pressed 15934 // TODO: ctrl+char should only send control chars; not yet 15935 if ((e.xkey.state&ModifierState.ctrl) != 0) { 15936 if (charbuflen > 1 || charbuf[0] >= ' ') charbuflen = 0; 15937 } 15938 15939 dchar[32] charsComingBuffer; 15940 int charsComingPosition; 15941 dchar[] charsComing = charsComingBuffer[]; 15942 15943 if (ke.pressed && charbuflen > 0) { 15944 // FIXME: I think Windows sends these on releases... we should try to match that, but idk about repeats. 15945 foreach (immutable dchar ch; charbuf[0..charbuflen]) { 15946 if(charsComingPosition >= charsComing.length) 15947 charsComing.length = charsComingPosition + 8; 15948 15949 charsComing[charsComingPosition++] = ch; 15950 } 15951 15952 charsComing = charsComing[0 .. charsComingPosition]; 15953 } else { 15954 charsComing = null; 15955 } 15956 15957 ke.charsPossible = charsComing; 15958 15959 if (win.handleKeyEvent) { 15960 XUnlockDisplay(display); 15961 scope(exit) XLockDisplay(display); 15962 win.handleKeyEvent(ke); 15963 } 15964 15965 // Super and alt modifier keys never actually send the chars, they are assumed to be special. 15966 if ((e.xkey.state&(ModifierState.alt|ModifierState.windows)) == 0 && win.handleCharEvent) { 15967 XUnlockDisplay(display); 15968 scope(exit) XLockDisplay(display); 15969 foreach(ch; charsComing) 15970 win.handleCharEvent(ch); 15971 } 15972 } 15973 15974 version(with_eventloop) 15975 send(ke); 15976 break; 15977 default: 15978 } 15979 15980 return done; 15981 } 15982 } 15983 15984 /* *************************************** */ 15985 /* Done with simpledisplay stuff */ 15986 /* *************************************** */ 15987 15988 // Necessary C library bindings follow 15989 version(Windows) {} else 15990 version(X11) { 15991 15992 extern(C) int eventfd (uint initval, int flags) nothrow @trusted @nogc; 15993 15994 // X11 bindings needed here 15995 /* 15996 A little of this is from the bindings project on 15997 D Source and some of it is copy/paste from the C 15998 header. 15999 16000 The DSource listing consistently used D's long 16001 where C used long. That's wrong - C long is 32 bit, so 16002 it should be int in D. I changed that here. 16003 16004 Note: 16005 This isn't complete, just took what I needed for myself. 16006 */ 16007 16008 import core.stdc.stddef : wchar_t; 16009 16010 interface XLib { 16011 extern(C) nothrow @nogc { 16012 char* XResourceManagerString(Display*); 16013 void XrmInitialize(); 16014 XrmDatabase XrmGetStringDatabase(char* data); 16015 bool XrmGetResource(XrmDatabase, const char*, const char*, char**, XrmValue*); 16016 16017 Cursor XCreateFontCursor(Display*, uint shape); 16018 int XDefineCursor(Display* display, Window w, Cursor cursor); 16019 int XUndefineCursor(Display* display, Window w); 16020 16021 Pixmap XCreateBitmapFromData(Display* display, Drawable d, const(char)* data, uint width, uint height); 16022 Cursor XCreatePixmapCursor(Display* display, Pixmap source, Pixmap mask, XColor* foreground_color, XColor* background_color, uint x, uint y); 16023 int XFreeCursor(Display* display, Cursor cursor); 16024 16025 int XLookupString(XKeyEvent *event_struct, char *buffer_return, int bytes_buffer, KeySym *keysym_return, void *status_in_out); 16026 16027 int XwcLookupString(XIC ic, XKeyPressedEvent* event, wchar_t* buffer_return, int wchars_buffer, KeySym* keysym_return, Status* status_return); 16028 16029 XVaNestedList XVaCreateNestedList(int unused, ...); 16030 16031 char *XKeysymToString(KeySym keysym); 16032 KeySym XKeycodeToKeysym( 16033 Display* /* display */, 16034 KeyCode /* keycode */, 16035 int /* index */ 16036 ); 16037 16038 int XConvertSelection(Display *display, Atom selection, Atom target, Atom property, Window requestor, Time time); 16039 16040 int XFree(void*); 16041 int XDeleteProperty(Display *display, Window w, Atom property); 16042 16043 int XChangeProperty(Display *display, Window w, Atom property, Atom type, int format, int mode, scope const void *data, int nelements); 16044 16045 int XGetWindowProperty(Display *display, Window w, Atom property, arch_long 16046 long_offset, arch_long long_length, Bool del, Atom req_type, Atom 16047 *actual_type_return, int *actual_format_return, arch_ulong 16048 *nitems_return, arch_ulong *bytes_after_return, void** prop_return); 16049 Atom* XListProperties(Display *display, Window w, int *num_prop_return); 16050 Status XGetTextProperty(Display *display, Window w, XTextProperty *text_prop_return, Atom property); 16051 Status XQueryTree(Display *display, Window w, Window *root_return, Window *parent_return, Window **children_return, uint *nchildren_return); 16052 16053 int XSetSelectionOwner(Display *display, Atom selection, Window owner, Time time); 16054 16055 Window XGetSelectionOwner(Display *display, Atom selection); 16056 16057 XVisualInfo* XGetVisualInfo(Display*, c_long, XVisualInfo*, int*); 16058 16059 char** XListFonts(Display*, const char*, int, int*); 16060 void XFreeFontNames(char**); 16061 16062 Display* XOpenDisplay(const char*); 16063 int XCloseDisplay(Display*); 16064 16065 int function() XSynchronize(Display*, bool); 16066 int function() XSetAfterFunction(Display*, int function() proc); 16067 16068 Bool XQueryExtension(Display*, const char*, int*, int*, int*); 16069 16070 Bool XSupportsLocale(); 16071 char* XSetLocaleModifiers(const(char)* modifier_list); 16072 XOM XOpenOM(Display* display, _XrmHashBucketRec* rdb, const(char)* res_name, const(char)* res_class); 16073 Status XCloseOM(XOM om); 16074 16075 XIM XOpenIM(Display* dpy, _XrmHashBucketRec* rdb, const(char)* res_name, const(char)* res_class); 16076 Status XCloseIM(XIM im); 16077 16078 char* XGetIMValues(XIM im, ...) /*_X_SENTINEL(0)*/; 16079 char* XSetIMValues(XIM im, ...) /*_X_SENTINEL(0)*/; 16080 Display* XDisplayOfIM(XIM im); 16081 char* XLocaleOfIM(XIM im); 16082 XIC XCreateIC(XIM im, ...) /*_X_SENTINEL(0)*/; 16083 void XDestroyIC(XIC ic); 16084 void XSetICFocus(XIC ic); 16085 void XUnsetICFocus(XIC ic); 16086 //wchar_t* XwcResetIC(XIC ic); 16087 char* XmbResetIC(XIC ic); 16088 char* Xutf8ResetIC(XIC ic); 16089 char* XSetICValues(XIC ic, ...) /*_X_SENTINEL(0)*/; 16090 char* XGetICValues(XIC ic, ...) /*_X_SENTINEL(0)*/; 16091 XIM XIMOfIC(XIC ic); 16092 16093 uint XSendEvent(Display* display, Window w, Bool propagate, arch_long event_mask, XEvent* event_send); 16094 16095 16096 XFontStruct *XLoadQueryFont(Display *display, scope const char *name); 16097 int XFreeFont(Display *display, XFontStruct *font_struct); 16098 int XSetFont(Display* display, GC gc, Font font); 16099 int XTextWidth(XFontStruct*, scope const char*, int); 16100 16101 int XSetLineAttributes(Display *display, GC gc, uint line_width, int line_style, int cap_style, int join_style); 16102 int XSetDashes(Display *display, GC gc, int dash_offset, scope const byte* dash_list, int n); 16103 16104 Window XCreateSimpleWindow( 16105 Display* /* display */, 16106 Window /* parent */, 16107 int /* x */, 16108 int /* y */, 16109 uint /* width */, 16110 uint /* height */, 16111 uint /* border_width */, 16112 uint /* border */, 16113 uint /* background */ 16114 ); 16115 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); 16116 16117 int XReparentWindow(Display*, Window, Window, int, int); 16118 int XClearWindow(Display*, Window); 16119 int XMoveResizeWindow(Display*, Window, int, int, uint, uint); 16120 int XMoveWindow(Display*, Window, int, int); 16121 int XResizeWindow(Display *display, Window w, uint width, uint height); 16122 16123 Colormap XCreateColormap(Display *display, Window w, Visual *visual, int alloc); 16124 16125 Status XMatchVisualInfo(Display *display, int screen, int depth, int class_, XVisualInfo *vinfo_return); 16126 16127 Status XGetWindowAttributes(Display*, Window, XWindowAttributes*); 16128 16129 XImage *XCreateImage( 16130 Display* /* display */, 16131 Visual* /* visual */, 16132 uint /* depth */, 16133 int /* format */, 16134 int /* offset */, 16135 ubyte* /* data */, 16136 uint /* width */, 16137 uint /* height */, 16138 int /* bitmap_pad */, 16139 int /* bytes_per_line */ 16140 ); 16141 16142 Status XInitImage (XImage* image); 16143 16144 Atom XInternAtom( 16145 Display* /* display */, 16146 const char* /* atom_name */, 16147 Bool /* only_if_exists */ 16148 ); 16149 16150 Status XInternAtoms(Display*, const char**, int, Bool, Atom*); 16151 char* XGetAtomName(Display*, Atom); 16152 Status XGetAtomNames(Display*, Atom*, int count, char**); 16153 16154 int XPutImage( 16155 Display* /* display */, 16156 Drawable /* d */, 16157 GC /* gc */, 16158 XImage* /* image */, 16159 int /* src_x */, 16160 int /* src_y */, 16161 int /* dest_x */, 16162 int /* dest_y */, 16163 uint /* width */, 16164 uint /* height */ 16165 ); 16166 16167 XImage *XGetImage(Display *display, Drawable d, int x, int y, uint width, uint height, c_ulong plane_mask, int format); 16168 16169 16170 int XDestroyWindow( 16171 Display* /* display */, 16172 Window /* w */ 16173 ); 16174 16175 int XDestroyImage(XImage*); 16176 16177 int XSelectInput( 16178 Display* /* display */, 16179 Window /* w */, 16180 EventMask /* event_mask */ 16181 ); 16182 16183 int XMapWindow( 16184 Display* /* display */, 16185 Window /* w */ 16186 ); 16187 16188 Status XIconifyWindow(Display*, Window, int); 16189 int XMapRaised(Display*, Window); 16190 int XMapSubwindows(Display*, Window); 16191 16192 int XNextEvent( 16193 Display* /* display */, 16194 XEvent* /* event_return */ 16195 ); 16196 16197 int XMaskEvent(Display*, arch_long, XEvent*); 16198 16199 Bool XFilterEvent(XEvent *event, Window window); 16200 int XRefreshKeyboardMapping(XMappingEvent *event_map); 16201 16202 Status XSetWMProtocols( 16203 Display* /* display */, 16204 Window /* w */, 16205 Atom* /* protocols */, 16206 int /* count */ 16207 ); 16208 16209 void XSetWMNormalHints(Display *display, Window w, XSizeHints *hints); 16210 Status XGetWMNormalHints(Display *display, Window w, XSizeHints *hints, c_long* supplied_return); 16211 16212 16213 Status XInitThreads(); 16214 void XLockDisplay (Display* display); 16215 void XUnlockDisplay (Display* display); 16216 16217 void XSetWMProperties(Display*, Window, XTextProperty*, XTextProperty*, char**, int, XSizeHints*, XWMHints*, XClassHint*); 16218 16219 int XSetWindowBackground (Display* display, Window w, c_ulong background_pixel); 16220 int XSetWindowBackgroundPixmap (Display* display, Window w, Pixmap background_pixmap); 16221 //int XSetWindowBorder (Display* display, Window w, c_ulong border_pixel); 16222 //int XSetWindowBorderPixmap (Display* display, Window w, Pixmap border_pixmap); 16223 //int XSetWindowBorderWidth (Display* display, Window w, uint width); 16224 16225 16226 // check out Xft too: http://www.keithp.com/~keithp/render/Xft.tutorial 16227 int XDrawString(Display*, Drawable, GC, int, int, scope const char*, int); 16228 int XDrawLine(Display*, Drawable, GC, int, int, int, int); 16229 int XDrawRectangle(Display*, Drawable, GC, int, int, uint, uint); 16230 int XDrawArc(Display*, Drawable, GC, int, int, uint, uint, int, int); 16231 int XFillRectangle(Display*, Drawable, GC, int, int, uint, uint); 16232 int XFillArc(Display*, Drawable, GC, int, int, uint, uint, int, int); 16233 int XDrawPoint(Display*, Drawable, GC, int, int); 16234 int XSetForeground(Display*, GC, uint); 16235 int XSetBackground(Display*, GC, uint); 16236 16237 XFontSet XCreateFontSet(Display*, const char*, char***, int*, char**); 16238 void XFreeFontSet(Display*, XFontSet); 16239 void Xutf8DrawString(Display*, Drawable, XFontSet, GC, int, int, scope const char*, int); 16240 void Xutf8DrawText(Display*, Drawable, GC, int, int, XmbTextItem*, int); 16241 16242 int Xutf8TextExtents(XFontSet font_set, const char *, int num_bytes, XRectangle *overall_ink_return, XRectangle *overall_logical_return); 16243 16244 16245 //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); 16246 16247 void XDrawText(Display*, Drawable, GC, int, int, XTextItem*, int); 16248 int XSetFunction(Display*, GC, int); 16249 16250 GC XCreateGC(Display*, Drawable, uint, void*); 16251 int XCopyGC(Display*, GC, uint, GC); 16252 int XFreeGC(Display*, GC); 16253 16254 bool XCheckWindowEvent(Display*, Window, int, XEvent*); 16255 bool XCheckMaskEvent(Display*, int, XEvent*); 16256 16257 int XPending(Display*); 16258 int XEventsQueued(Display* display, int mode); 16259 16260 Pixmap XCreatePixmap(Display*, Drawable, uint, uint, uint); 16261 int XFreePixmap(Display*, Pixmap); 16262 int XCopyArea(Display*, Drawable, Drawable, GC, int, int, uint, uint, int, int); 16263 int XFlush(Display*); 16264 int XBell(Display*, int); 16265 int XSync(Display*, bool); 16266 16267 int XGrabKey (Display* display, int keycode, uint modifiers, Window grab_window, Bool owner_events, int pointer_mode, int keyboard_mode); 16268 int XUngrabKey (Display* display, int keycode, uint modifiers, Window grab_window); 16269 16270 int XGrabKeyboard(Display*, Window, Bool, int, int, Time); 16271 int XUngrabKeyboard(Display*, Time); 16272 16273 KeyCode XKeysymToKeycode (Display* display, KeySym keysym); 16274 16275 KeySym XStringToKeysym(const char *string); 16276 16277 Bool XCheckTypedEvent(Display *display, int event_type, XEvent *event_return); 16278 16279 Window XDefaultRootWindow(Display*); 16280 16281 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); 16282 16283 int XUngrabButton(Display *display, uint button, uint modifiers, Window grab_window); 16284 16285 int XDrawLines(Display*, Drawable, GC, XPoint*, int, CoordMode); 16286 int XFillPolygon(Display*, Drawable, GC, XPoint*, int, PolygonShape, CoordMode); 16287 16288 Status XAllocColor(Display*, Colormap, XColor*); 16289 16290 int XWithdrawWindow(Display*, Window, int); 16291 int XUnmapWindow(Display*, Window); 16292 int XLowerWindow(Display*, Window); 16293 int XRaiseWindow(Display*, Window); 16294 16295 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); 16296 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); 16297 16298 int XGetInputFocus(Display*, Window*, int*); 16299 int XSetInputFocus(Display*, Window, int, Time); 16300 16301 XErrorHandler XSetErrorHandler(XErrorHandler); 16302 16303 int XGetErrorText(Display*, int, char*, int); 16304 16305 Bool XkbSetDetectableAutoRepeat(Display* dpy, Bool detectable, Bool* supported); 16306 16307 16308 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); 16309 int XUngrabPointer(Display *display, Time time); 16310 int XChangeActivePointerGrab(Display *display, uint event_mask, Cursor cursor, Time time); 16311 16312 int XCopyPlane(Display*, Drawable, Drawable, GC, int, int, uint, uint, int, int, arch_ulong); 16313 16314 Status XGetGeometry(Display*, Drawable, Window*, int*, int*, uint*, uint*, uint*, uint*); 16315 int XSetClipMask(Display*, GC, Pixmap); 16316 int XSetClipOrigin(Display*, GC, int, int); 16317 16318 void XSetClipRectangles(Display*, GC, int, int, XRectangle*, int, int); 16319 16320 void XSetWMName(Display*, Window, XTextProperty*); 16321 Status XGetWMName(Display*, Window, XTextProperty*); 16322 int XStoreName(Display* display, Window w, const(char)* window_name); 16323 16324 XIOErrorHandler XSetIOErrorHandler (XIOErrorHandler handler); 16325 16326 } 16327 } 16328 16329 interface Xext { 16330 extern(C) nothrow @nogc { 16331 Status XShmAttach(Display*, XShmSegmentInfo*); 16332 Status XShmDetach(Display*, XShmSegmentInfo*); 16333 Status XShmPutImage( 16334 Display* /* dpy */, 16335 Drawable /* d */, 16336 GC /* gc */, 16337 XImage* /* image */, 16338 int /* src_x */, 16339 int /* src_y */, 16340 int /* dst_x */, 16341 int /* dst_y */, 16342 uint /* src_width */, 16343 uint /* src_height */, 16344 Bool /* send_event */ 16345 ); 16346 16347 Status XShmQueryExtension(Display*); 16348 16349 XImage *XShmCreateImage( 16350 Display* /* dpy */, 16351 Visual* /* visual */, 16352 uint /* depth */, 16353 int /* format */, 16354 char* /* data */, 16355 XShmSegmentInfo* /* shminfo */, 16356 uint /* width */, 16357 uint /* height */ 16358 ); 16359 16360 Pixmap XShmCreatePixmap( 16361 Display* /* dpy */, 16362 Drawable /* d */, 16363 char* /* data */, 16364 XShmSegmentInfo* /* shminfo */, 16365 uint /* width */, 16366 uint /* height */, 16367 uint /* depth */ 16368 ); 16369 16370 } 16371 } 16372 16373 // this requires -lXpm 16374 //int XpmCreatePixmapFromData(Display*, Drawable, scope const char**, Pixmap*, Pixmap*, void*); // FIXME: void* should be XpmAttributes 16375 16376 16377 mixin DynamicLoad!(XLib, "X11", 6, librariesSuccessfullyLoaded) xlib; 16378 mixin DynamicLoad!(Xext, "Xext", 6, librariesSuccessfullyLoaded) xext; 16379 shared static this() { 16380 xlib.loadDynamicLibrary(); 16381 xext.loadDynamicLibrary(); 16382 } 16383 16384 16385 extern(C) nothrow @nogc { 16386 16387 alias XrmDatabase = void*; 16388 struct XrmValue { 16389 uint size; 16390 void* addr; 16391 } 16392 16393 struct XVisualInfo { 16394 Visual* visual; 16395 VisualID visualid; 16396 int screen; 16397 uint depth; 16398 int c_class; 16399 c_ulong red_mask; 16400 c_ulong green_mask; 16401 c_ulong blue_mask; 16402 int colormap_size; 16403 int bits_per_rgb; 16404 } 16405 16406 enum VisualNoMask= 0x0; 16407 enum VisualIDMask= 0x1; 16408 enum VisualScreenMask=0x2; 16409 enum VisualDepthMask= 0x4; 16410 enum VisualClassMask= 0x8; 16411 enum VisualRedMaskMask=0x10; 16412 enum VisualGreenMaskMask=0x20; 16413 enum VisualBlueMaskMask=0x40; 16414 enum VisualColormapSizeMask=0x80; 16415 enum VisualBitsPerRGBMask=0x100; 16416 enum VisualAllMask= 0x1FF; 16417 16418 enum AnyKey = 0; 16419 enum AnyModifier = 1 << 15; 16420 16421 // XIM and other crap 16422 struct _XOM {} 16423 struct _XIM {} 16424 struct _XIC {} 16425 alias XOM = _XOM*; 16426 alias XIM = _XIM*; 16427 alias XIC = _XIC*; 16428 16429 alias XVaNestedList = void*; 16430 16431 alias XIMStyle = arch_ulong; 16432 enum : arch_ulong { 16433 XIMPreeditArea = 0x0001, 16434 XIMPreeditCallbacks = 0x0002, 16435 XIMPreeditPosition = 0x0004, 16436 XIMPreeditNothing = 0x0008, 16437 XIMPreeditNone = 0x0010, 16438 XIMStatusArea = 0x0100, 16439 XIMStatusCallbacks = 0x0200, 16440 XIMStatusNothing = 0x0400, 16441 XIMStatusNone = 0x0800, 16442 } 16443 16444 16445 /* X Shared Memory Extension functions */ 16446 //pragma(lib, "Xshm"); 16447 alias arch_ulong ShmSeg; 16448 struct XShmSegmentInfo { 16449 ShmSeg shmseg; 16450 int shmid; 16451 ubyte* shmaddr; 16452 Bool readOnly; 16453 } 16454 16455 // and the necessary OS functions 16456 int shmget(int, size_t, int); 16457 void* shmat(int, scope const void*, int); 16458 int shmdt(scope const void*); 16459 int shmctl (int shmid, int cmd, void* ptr /*struct shmid_ds *buf*/); 16460 16461 enum IPC_PRIVATE = 0; 16462 enum IPC_CREAT = 512; 16463 enum IPC_RMID = 0; 16464 16465 /* MIT-SHM end */ 16466 16467 16468 enum MappingType:int { 16469 MappingModifier =0, 16470 MappingKeyboard =1, 16471 MappingPointer =2 16472 } 16473 16474 /* ImageFormat -- PutImage, GetImage */ 16475 enum ImageFormat:int { 16476 XYBitmap =0, /* depth 1, XYFormat */ 16477 XYPixmap =1, /* depth == drawable depth */ 16478 ZPixmap =2 /* depth == drawable depth */ 16479 } 16480 16481 enum ModifierName:int { 16482 ShiftMapIndex =0, 16483 LockMapIndex =1, 16484 ControlMapIndex =2, 16485 Mod1MapIndex =3, 16486 Mod2MapIndex =4, 16487 Mod3MapIndex =5, 16488 Mod4MapIndex =6, 16489 Mod5MapIndex =7 16490 } 16491 16492 enum ButtonMask:int { 16493 Button1Mask =1<<8, 16494 Button2Mask =1<<9, 16495 Button3Mask =1<<10, 16496 Button4Mask =1<<11, 16497 Button5Mask =1<<12, 16498 AnyModifier =1<<15/* used in GrabButton, GrabKey */ 16499 } 16500 16501 enum KeyOrButtonMask:uint { 16502 ShiftMask =1<<0, 16503 LockMask =1<<1, 16504 ControlMask =1<<2, 16505 Mod1Mask =1<<3, 16506 Mod2Mask =1<<4, 16507 Mod3Mask =1<<5, 16508 Mod4Mask =1<<6, 16509 Mod5Mask =1<<7, 16510 Button1Mask =1<<8, 16511 Button2Mask =1<<9, 16512 Button3Mask =1<<10, 16513 Button4Mask =1<<11, 16514 Button5Mask =1<<12, 16515 AnyModifier =1<<15/* used in GrabButton, GrabKey */ 16516 } 16517 16518 enum ButtonName:int { 16519 Button1 =1, 16520 Button2 =2, 16521 Button3 =3, 16522 Button4 =4, 16523 Button5 =5 16524 } 16525 16526 /* Notify modes */ 16527 enum NotifyModes:int 16528 { 16529 NotifyNormal =0, 16530 NotifyGrab =1, 16531 NotifyUngrab =2, 16532 NotifyWhileGrabbed =3 16533 } 16534 enum NotifyHint = 1; /* for MotionNotify events */ 16535 16536 /* Notify detail */ 16537 enum NotifyDetail:int 16538 { 16539 NotifyAncestor =0, 16540 NotifyVirtual =1, 16541 NotifyInferior =2, 16542 NotifyNonlinear =3, 16543 NotifyNonlinearVirtual =4, 16544 NotifyPointer =5, 16545 NotifyPointerRoot =6, 16546 NotifyDetailNone =7 16547 } 16548 16549 /* Visibility notify */ 16550 16551 enum VisibilityNotify:int 16552 { 16553 VisibilityUnobscured =0, 16554 VisibilityPartiallyObscured =1, 16555 VisibilityFullyObscured =2 16556 } 16557 16558 16559 enum WindowStackingMethod:int 16560 { 16561 Above =0, 16562 Below =1, 16563 TopIf =2, 16564 BottomIf =3, 16565 Opposite =4 16566 } 16567 16568 /* Circulation request */ 16569 enum CirculationRequest:int 16570 { 16571 PlaceOnTop =0, 16572 PlaceOnBottom =1 16573 } 16574 16575 enum PropertyNotification:int 16576 { 16577 PropertyNewValue =0, 16578 PropertyDelete =1 16579 } 16580 16581 enum ColorMapNotification:int 16582 { 16583 ColormapUninstalled =0, 16584 ColormapInstalled =1 16585 } 16586 16587 16588 struct _XPrivate {} 16589 struct _XrmHashBucketRec {} 16590 16591 alias void* XPointer; 16592 alias void* XExtData; 16593 16594 version( X86_64 ) { 16595 alias ulong XID; 16596 alias ulong arch_ulong; 16597 alias long arch_long; 16598 } else version (AArch64) { 16599 alias ulong XID; 16600 alias ulong arch_ulong; 16601 alias long arch_long; 16602 } else { 16603 alias uint XID; 16604 alias uint arch_ulong; 16605 alias int arch_long; 16606 } 16607 16608 alias XID Window; 16609 alias XID Drawable; 16610 alias XID Pixmap; 16611 16612 alias arch_ulong Atom; 16613 alias int Bool; 16614 alias Display XDisplay; 16615 16616 alias int ByteOrder; 16617 alias arch_ulong Time; 16618 alias void ScreenFormat; 16619 16620 struct XImage { 16621 int width, height; /* size of image */ 16622 int xoffset; /* number of pixels offset in X direction */ 16623 ImageFormat format; /* XYBitmap, XYPixmap, ZPixmap */ 16624 void *data; /* pointer to image data */ 16625 ByteOrder byte_order; /* data byte order, LSBFirst, MSBFirst */ 16626 int bitmap_unit; /* quant. of scanline 8, 16, 32 */ 16627 int bitmap_bit_order; /* LSBFirst, MSBFirst */ 16628 int bitmap_pad; /* 8, 16, 32 either XY or ZPixmap */ 16629 int depth; /* depth of image */ 16630 int bytes_per_line; /* accelarator to next line */ 16631 int bits_per_pixel; /* bits per pixel (ZPixmap) */ 16632 arch_ulong red_mask; /* bits in z arrangment */ 16633 arch_ulong green_mask; 16634 arch_ulong blue_mask; 16635 XPointer obdata; /* hook for the object routines to hang on */ 16636 static struct F { /* image manipulation routines */ 16637 XImage* function( 16638 XDisplay* /* display */, 16639 Visual* /* visual */, 16640 uint /* depth */, 16641 int /* format */, 16642 int /* offset */, 16643 ubyte* /* data */, 16644 uint /* width */, 16645 uint /* height */, 16646 int /* bitmap_pad */, 16647 int /* bytes_per_line */) create_image; 16648 int function(XImage *) destroy_image; 16649 arch_ulong function(XImage *, int, int) get_pixel; 16650 int function(XImage *, int, int, arch_ulong) put_pixel; 16651 XImage* function(XImage *, int, int, uint, uint) sub_image; 16652 int function(XImage *, arch_long) add_pixel; 16653 } 16654 F f; 16655 } 16656 version(X86_64) static assert(XImage.sizeof == 136); 16657 else version(X86) static assert(XImage.sizeof == 88); 16658 16659 struct XCharStruct { 16660 short lbearing; /* origin to left edge of raster */ 16661 short rbearing; /* origin to right edge of raster */ 16662 short width; /* advance to next char's origin */ 16663 short ascent; /* baseline to top edge of raster */ 16664 short descent; /* baseline to bottom edge of raster */ 16665 ushort attributes; /* per char flags (not predefined) */ 16666 } 16667 16668 /* 16669 * To allow arbitrary information with fonts, there are additional properties 16670 * returned. 16671 */ 16672 struct XFontProp { 16673 Atom name; 16674 arch_ulong card32; 16675 } 16676 16677 alias Atom Font; 16678 16679 struct XFontStruct { 16680 XExtData *ext_data; /* Hook for extension to hang data */ 16681 Font fid; /* Font ID for this font */ 16682 uint direction; /* Direction the font is painted */ 16683 uint min_char_or_byte2; /* First character */ 16684 uint max_char_or_byte2; /* Last character */ 16685 uint min_byte1; /* First row that exists (for two-byte fonts) */ 16686 uint max_byte1; /* Last row that exists (for two-byte fonts) */ 16687 Bool all_chars_exist; /* Flag if all characters have nonzero size */ 16688 uint default_char; /* Char to print for undefined character */ 16689 int n_properties; /* How many properties there are */ 16690 XFontProp *properties; /* Pointer to array of additional properties*/ 16691 XCharStruct min_bounds; /* Minimum bounds over all existing char*/ 16692 XCharStruct max_bounds; /* Maximum bounds over all existing char*/ 16693 XCharStruct *per_char; /* first_char to last_char information */ 16694 int ascent; /* Max extent above baseline for spacing */ 16695 int descent; /* Max descent below baseline for spacing */ 16696 } 16697 16698 16699 /* 16700 * Definitions of specific events. 16701 */ 16702 struct XKeyEvent 16703 { 16704 int type; /* of event */ 16705 arch_ulong serial; /* # of last request processed by server */ 16706 Bool send_event; /* true if this came from a SendEvent request */ 16707 Display *display; /* Display the event was read from */ 16708 Window window; /* "event" window it is reported relative to */ 16709 Window root; /* root window that the event occurred on */ 16710 Window subwindow; /* child window */ 16711 Time time; /* milliseconds */ 16712 int x, y; /* pointer x, y coordinates in event window */ 16713 int x_root, y_root; /* coordinates relative to root */ 16714 KeyOrButtonMask state; /* key or button mask */ 16715 uint keycode; /* detail */ 16716 Bool same_screen; /* same screen flag */ 16717 } 16718 version(X86_64) static assert(XKeyEvent.sizeof == 96); 16719 alias XKeyEvent XKeyPressedEvent; 16720 alias XKeyEvent XKeyReleasedEvent; 16721 16722 struct XButtonEvent 16723 { 16724 int type; /* of event */ 16725 arch_ulong serial; /* # of last request processed by server */ 16726 Bool send_event; /* true if this came from a SendEvent request */ 16727 Display *display; /* Display the event was read from */ 16728 Window window; /* "event" window it is reported relative to */ 16729 Window root; /* root window that the event occurred on */ 16730 Window subwindow; /* child window */ 16731 Time time; /* milliseconds */ 16732 int x, y; /* pointer x, y coordinates in event window */ 16733 int x_root, y_root; /* coordinates relative to root */ 16734 KeyOrButtonMask state; /* key or button mask */ 16735 uint button; /* detail */ 16736 Bool same_screen; /* same screen flag */ 16737 } 16738 alias XButtonEvent XButtonPressedEvent; 16739 alias XButtonEvent XButtonReleasedEvent; 16740 16741 struct XMotionEvent{ 16742 int type; /* of event */ 16743 arch_ulong serial; /* # of last request processed by server */ 16744 Bool send_event; /* true if this came from a SendEvent request */ 16745 Display *display; /* Display the event was read from */ 16746 Window window; /* "event" window reported relative to */ 16747 Window root; /* root window that the event occurred on */ 16748 Window subwindow; /* child window */ 16749 Time time; /* milliseconds */ 16750 int x, y; /* pointer x, y coordinates in event window */ 16751 int x_root, y_root; /* coordinates relative to root */ 16752 KeyOrButtonMask state; /* key or button mask */ 16753 byte is_hint; /* detail */ 16754 Bool same_screen; /* same screen flag */ 16755 } 16756 alias XMotionEvent XPointerMovedEvent; 16757 16758 struct XCrossingEvent{ 16759 int type; /* of event */ 16760 arch_ulong serial; /* # of last request processed by server */ 16761 Bool send_event; /* true if this came from a SendEvent request */ 16762 Display *display; /* Display the event was read from */ 16763 Window window; /* "event" window reported relative to */ 16764 Window root; /* root window that the event occurred on */ 16765 Window subwindow; /* child window */ 16766 Time time; /* milliseconds */ 16767 int x, y; /* pointer x, y coordinates in event window */ 16768 int x_root, y_root; /* coordinates relative to root */ 16769 NotifyModes mode; /* NotifyNormal, NotifyGrab, NotifyUngrab */ 16770 NotifyDetail detail; 16771 /* 16772 * NotifyAncestor, NotifyVirtual, NotifyInferior, 16773 * NotifyNonlinear,NotifyNonlinearVirtual 16774 */ 16775 Bool same_screen; /* same screen flag */ 16776 Bool focus; /* Boolean focus */ 16777 KeyOrButtonMask state; /* key or button mask */ 16778 } 16779 alias XCrossingEvent XEnterWindowEvent; 16780 alias XCrossingEvent XLeaveWindowEvent; 16781 16782 struct XFocusChangeEvent{ 16783 int type; /* FocusIn or FocusOut */ 16784 arch_ulong serial; /* # of last request processed by server */ 16785 Bool send_event; /* true if this came from a SendEvent request */ 16786 Display *display; /* Display the event was read from */ 16787 Window window; /* window of event */ 16788 NotifyModes mode; /* NotifyNormal, NotifyWhileGrabbed, 16789 NotifyGrab, NotifyUngrab */ 16790 NotifyDetail detail; 16791 /* 16792 * NotifyAncestor, NotifyVirtual, NotifyInferior, 16793 * NotifyNonlinear,NotifyNonlinearVirtual, NotifyPointer, 16794 * NotifyPointerRoot, NotifyDetailNone 16795 */ 16796 } 16797 alias XFocusChangeEvent XFocusInEvent; 16798 alias XFocusChangeEvent XFocusOutEvent; 16799 16800 enum CWBackPixmap = (1L<<0); 16801 enum CWBackPixel = (1L<<1); 16802 enum CWBorderPixmap = (1L<<2); 16803 enum CWBorderPixel = (1L<<3); 16804 enum CWBitGravity = (1L<<4); 16805 enum CWWinGravity = (1L<<5); 16806 enum CWBackingStore = (1L<<6); 16807 enum CWBackingPlanes = (1L<<7); 16808 enum CWBackingPixel = (1L<<8); 16809 enum CWOverrideRedirect = (1L<<9); 16810 enum CWSaveUnder = (1L<<10); 16811 enum CWEventMask = (1L<<11); 16812 enum CWDontPropagate = (1L<<12); 16813 enum CWColormap = (1L<<13); 16814 enum CWCursor = (1L<<14); 16815 16816 struct XWindowAttributes { 16817 int x, y; /* location of window */ 16818 int width, height; /* width and height of window */ 16819 int border_width; /* border width of window */ 16820 int depth; /* depth of window */ 16821 Visual *visual; /* the associated visual structure */ 16822 Window root; /* root of screen containing window */ 16823 int class_; /* InputOutput, InputOnly*/ 16824 int bit_gravity; /* one of the bit gravity values */ 16825 int win_gravity; /* one of the window gravity values */ 16826 int backing_store; /* NotUseful, WhenMapped, Always */ 16827 arch_ulong backing_planes; /* planes to be preserved if possible */ 16828 arch_ulong backing_pixel; /* value to be used when restoring planes */ 16829 Bool save_under; /* boolean, should bits under be saved? */ 16830 Colormap colormap; /* color map to be associated with window */ 16831 Bool map_installed; /* boolean, is color map currently installed*/ 16832 int map_state; /* IsUnmapped, IsUnviewable, IsViewable */ 16833 arch_long all_event_masks; /* set of events all people have interest in*/ 16834 arch_long your_event_mask; /* my event mask */ 16835 arch_long do_not_propagate_mask; /* set of events that should not propagate */ 16836 Bool override_redirect; /* boolean value for override-redirect */ 16837 Screen *screen; /* back pointer to correct screen */ 16838 } 16839 16840 enum IsUnmapped = 0; 16841 enum IsUnviewable = 1; 16842 enum IsViewable = 2; 16843 16844 struct XSetWindowAttributes { 16845 Pixmap background_pixmap;/* background, None, or ParentRelative */ 16846 arch_ulong background_pixel;/* background pixel */ 16847 Pixmap border_pixmap; /* border of the window or CopyFromParent */ 16848 arch_ulong border_pixel;/* border pixel value */ 16849 int bit_gravity; /* one of bit gravity values */ 16850 int win_gravity; /* one of the window gravity values */ 16851 int backing_store; /* NotUseful, WhenMapped, Always */ 16852 arch_ulong backing_planes;/* planes to be preserved if possible */ 16853 arch_ulong backing_pixel;/* value to use in restoring planes */ 16854 Bool save_under; /* should bits under be saved? (popups) */ 16855 arch_long event_mask; /* set of events that should be saved */ 16856 arch_long do_not_propagate_mask;/* set of events that should not propagate */ 16857 Bool override_redirect; /* boolean value for override_redirect */ 16858 Colormap colormap; /* color map to be associated with window */ 16859 Cursor cursor; /* cursor to be displayed (or None) */ 16860 } 16861 16862 16863 alias int Status; 16864 16865 16866 enum EventMask:int 16867 { 16868 NoEventMask =0, 16869 KeyPressMask =1<<0, 16870 KeyReleaseMask =1<<1, 16871 ButtonPressMask =1<<2, 16872 ButtonReleaseMask =1<<3, 16873 EnterWindowMask =1<<4, 16874 LeaveWindowMask =1<<5, 16875 PointerMotionMask =1<<6, 16876 PointerMotionHintMask =1<<7, 16877 Button1MotionMask =1<<8, 16878 Button2MotionMask =1<<9, 16879 Button3MotionMask =1<<10, 16880 Button4MotionMask =1<<11, 16881 Button5MotionMask =1<<12, 16882 ButtonMotionMask =1<<13, 16883 KeymapStateMask =1<<14, 16884 ExposureMask =1<<15, 16885 VisibilityChangeMask =1<<16, 16886 StructureNotifyMask =1<<17, 16887 ResizeRedirectMask =1<<18, 16888 SubstructureNotifyMask =1<<19, 16889 SubstructureRedirectMask=1<<20, 16890 FocusChangeMask =1<<21, 16891 PropertyChangeMask =1<<22, 16892 ColormapChangeMask =1<<23, 16893 OwnerGrabButtonMask =1<<24 16894 } 16895 16896 struct MwmHints { 16897 c_ulong flags; 16898 c_ulong functions; 16899 c_ulong decorations; 16900 c_long input_mode; 16901 c_ulong status; 16902 } 16903 16904 enum { 16905 MWM_HINTS_FUNCTIONS = (1L << 0), 16906 MWM_HINTS_DECORATIONS = (1L << 1), 16907 16908 MWM_FUNC_ALL = (1L << 0), 16909 MWM_FUNC_RESIZE = (1L << 1), 16910 MWM_FUNC_MOVE = (1L << 2), 16911 MWM_FUNC_MINIMIZE = (1L << 3), 16912 MWM_FUNC_MAXIMIZE = (1L << 4), 16913 MWM_FUNC_CLOSE = (1L << 5), 16914 16915 MWM_DECOR_ALL = (1L << 0), 16916 MWM_DECOR_BORDER = (1L << 1), 16917 MWM_DECOR_RESIZEH = (1L << 2), 16918 MWM_DECOR_TITLE = (1L << 3), 16919 MWM_DECOR_MENU = (1L << 4), 16920 MWM_DECOR_MINIMIZE = (1L << 5), 16921 MWM_DECOR_MAXIMIZE = (1L << 6), 16922 } 16923 16924 import core.stdc.config : c_long, c_ulong; 16925 16926 /* Size hints mask bits */ 16927 16928 enum USPosition = (1L << 0) /* user specified x, y */; 16929 enum USSize = (1L << 1) /* user specified width, height */; 16930 enum PPosition = (1L << 2) /* program specified position */; 16931 enum PSize = (1L << 3) /* program specified size */; 16932 enum PMinSize = (1L << 4) /* program specified minimum size */; 16933 enum PMaxSize = (1L << 5) /* program specified maximum size */; 16934 enum PResizeInc = (1L << 6) /* program specified resize increments */; 16935 enum PAspect = (1L << 7) /* program specified min and max aspect ratios */; 16936 enum PBaseSize = (1L << 8); 16937 enum PWinGravity = (1L << 9); 16938 enum PAllHints = (PPosition|PSize| PMinSize|PMaxSize| PResizeInc|PAspect); 16939 struct XSizeHints { 16940 arch_long flags; /* marks which fields in this structure are defined */ 16941 int x, y; /* Obsolete */ 16942 int width, height; /* Obsolete */ 16943 int min_width, min_height; 16944 int max_width, max_height; 16945 int width_inc, height_inc; 16946 struct Aspect { 16947 int x; /* numerator */ 16948 int y; /* denominator */ 16949 } 16950 16951 Aspect min_aspect; 16952 Aspect max_aspect; 16953 int base_width, base_height; 16954 int win_gravity; 16955 /* this structure may be extended in the future */ 16956 } 16957 16958 16959 16960 enum EventType:int 16961 { 16962 KeyPress =2, 16963 KeyRelease =3, 16964 ButtonPress =4, 16965 ButtonRelease =5, 16966 MotionNotify =6, 16967 EnterNotify =7, 16968 LeaveNotify =8, 16969 FocusIn =9, 16970 FocusOut =10, 16971 KeymapNotify =11, 16972 Expose =12, 16973 GraphicsExpose =13, 16974 NoExpose =14, 16975 VisibilityNotify =15, 16976 CreateNotify =16, 16977 DestroyNotify =17, 16978 UnmapNotify =18, 16979 MapNotify =19, 16980 MapRequest =20, 16981 ReparentNotify =21, 16982 ConfigureNotify =22, 16983 ConfigureRequest =23, 16984 GravityNotify =24, 16985 ResizeRequest =25, 16986 CirculateNotify =26, 16987 CirculateRequest =27, 16988 PropertyNotify =28, 16989 SelectionClear =29, 16990 SelectionRequest =30, 16991 SelectionNotify =31, 16992 ColormapNotify =32, 16993 ClientMessage =33, 16994 MappingNotify =34, 16995 LASTEvent =35 /* must be bigger than any event # */ 16996 } 16997 /* generated on EnterWindow and FocusIn when KeyMapState selected */ 16998 struct XKeymapEvent 16999 { 17000 int type; 17001 arch_ulong serial; /* # of last request processed by server */ 17002 Bool send_event; /* true if this came from a SendEvent request */ 17003 Display *display; /* Display the event was read from */ 17004 Window window; 17005 byte[32] key_vector; 17006 } 17007 17008 struct XExposeEvent 17009 { 17010 int type; 17011 arch_ulong serial; /* # of last request processed by server */ 17012 Bool send_event; /* true if this came from a SendEvent request */ 17013 Display *display; /* Display the event was read from */ 17014 Window window; 17015 int x, y; 17016 int width, height; 17017 int count; /* if non-zero, at least this many more */ 17018 } 17019 17020 struct XGraphicsExposeEvent{ 17021 int type; 17022 arch_ulong serial; /* # of last request processed by server */ 17023 Bool send_event; /* true if this came from a SendEvent request */ 17024 Display *display; /* Display the event was read from */ 17025 Drawable drawable; 17026 int x, y; 17027 int width, height; 17028 int count; /* if non-zero, at least this many more */ 17029 int major_code; /* core is CopyArea or CopyPlane */ 17030 int minor_code; /* not defined in the core */ 17031 } 17032 17033 struct XNoExposeEvent{ 17034 int type; 17035 arch_ulong serial; /* # of last request processed by server */ 17036 Bool send_event; /* true if this came from a SendEvent request */ 17037 Display *display; /* Display the event was read from */ 17038 Drawable drawable; 17039 int major_code; /* core is CopyArea or CopyPlane */ 17040 int minor_code; /* not defined in the core */ 17041 } 17042 17043 struct XVisibilityEvent{ 17044 int type; 17045 arch_ulong serial; /* # of last request processed by server */ 17046 Bool send_event; /* true if this came from a SendEvent request */ 17047 Display *display; /* Display the event was read from */ 17048 Window window; 17049 VisibilityNotify state; /* Visibility state */ 17050 } 17051 17052 struct XCreateWindowEvent{ 17053 int type; 17054 arch_ulong serial; /* # of last request processed by server */ 17055 Bool send_event; /* true if this came from a SendEvent request */ 17056 Display *display; /* Display the event was read from */ 17057 Window parent; /* parent of the window */ 17058 Window window; /* window id of window created */ 17059 int x, y; /* window location */ 17060 int width, height; /* size of window */ 17061 int border_width; /* border width */ 17062 Bool override_redirect; /* creation should be overridden */ 17063 } 17064 17065 struct XDestroyWindowEvent 17066 { 17067 int type; 17068 arch_ulong serial; /* # of last request processed by server */ 17069 Bool send_event; /* true if this came from a SendEvent request */ 17070 Display *display; /* Display the event was read from */ 17071 Window event; 17072 Window window; 17073 } 17074 17075 struct XUnmapEvent 17076 { 17077 int type; 17078 arch_ulong serial; /* # of last request processed by server */ 17079 Bool send_event; /* true if this came from a SendEvent request */ 17080 Display *display; /* Display the event was read from */ 17081 Window event; 17082 Window window; 17083 Bool from_configure; 17084 } 17085 17086 struct XMapEvent 17087 { 17088 int type; 17089 arch_ulong serial; /* # of last request processed by server */ 17090 Bool send_event; /* true if this came from a SendEvent request */ 17091 Display *display; /* Display the event was read from */ 17092 Window event; 17093 Window window; 17094 Bool override_redirect; /* Boolean, is override set... */ 17095 } 17096 17097 struct XMapRequestEvent 17098 { 17099 int type; 17100 arch_ulong serial; /* # of last request processed by server */ 17101 Bool send_event; /* true if this came from a SendEvent request */ 17102 Display *display; /* Display the event was read from */ 17103 Window parent; 17104 Window window; 17105 } 17106 17107 struct XReparentEvent 17108 { 17109 int type; 17110 arch_ulong serial; /* # of last request processed by server */ 17111 Bool send_event; /* true if this came from a SendEvent request */ 17112 Display *display; /* Display the event was read from */ 17113 Window event; 17114 Window window; 17115 Window parent; 17116 int x, y; 17117 Bool override_redirect; 17118 } 17119 17120 struct XConfigureEvent 17121 { 17122 int type; 17123 arch_ulong serial; /* # of last request processed by server */ 17124 Bool send_event; /* true if this came from a SendEvent request */ 17125 Display *display; /* Display the event was read from */ 17126 Window event; 17127 Window window; 17128 int x, y; 17129 int width, height; 17130 int border_width; 17131 Window above; 17132 Bool override_redirect; 17133 } 17134 17135 struct XGravityEvent 17136 { 17137 int type; 17138 arch_ulong serial; /* # of last request processed by server */ 17139 Bool send_event; /* true if this came from a SendEvent request */ 17140 Display *display; /* Display the event was read from */ 17141 Window event; 17142 Window window; 17143 int x, y; 17144 } 17145 17146 struct XResizeRequestEvent 17147 { 17148 int type; 17149 arch_ulong serial; /* # of last request processed by server */ 17150 Bool send_event; /* true if this came from a SendEvent request */ 17151 Display *display; /* Display the event was read from */ 17152 Window window; 17153 int width, height; 17154 } 17155 17156 struct XConfigureRequestEvent 17157 { 17158 int type; 17159 arch_ulong serial; /* # of last request processed by server */ 17160 Bool send_event; /* true if this came from a SendEvent request */ 17161 Display *display; /* Display the event was read from */ 17162 Window parent; 17163 Window window; 17164 int x, y; 17165 int width, height; 17166 int border_width; 17167 Window above; 17168 WindowStackingMethod detail; /* Above, Below, TopIf, BottomIf, Opposite */ 17169 arch_ulong value_mask; 17170 } 17171 17172 struct XCirculateEvent 17173 { 17174 int type; 17175 arch_ulong serial; /* # of last request processed by server */ 17176 Bool send_event; /* true if this came from a SendEvent request */ 17177 Display *display; /* Display the event was read from */ 17178 Window event; 17179 Window window; 17180 CirculationRequest place; /* PlaceOnTop, PlaceOnBottom */ 17181 } 17182 17183 struct XCirculateRequestEvent 17184 { 17185 int type; 17186 arch_ulong serial; /* # of last request processed by server */ 17187 Bool send_event; /* true if this came from a SendEvent request */ 17188 Display *display; /* Display the event was read from */ 17189 Window parent; 17190 Window window; 17191 CirculationRequest place; /* PlaceOnTop, PlaceOnBottom */ 17192 } 17193 17194 struct XPropertyEvent 17195 { 17196 int type; 17197 arch_ulong serial; /* # of last request processed by server */ 17198 Bool send_event; /* true if this came from a SendEvent request */ 17199 Display *display; /* Display the event was read from */ 17200 Window window; 17201 Atom atom; 17202 Time time; 17203 PropertyNotification state; /* NewValue, Deleted */ 17204 } 17205 17206 struct XSelectionClearEvent 17207 { 17208 int type; 17209 arch_ulong serial; /* # of last request processed by server */ 17210 Bool send_event; /* true if this came from a SendEvent request */ 17211 Display *display; /* Display the event was read from */ 17212 Window window; 17213 Atom selection; 17214 Time time; 17215 } 17216 17217 struct XSelectionRequestEvent 17218 { 17219 int type; 17220 arch_ulong serial; /* # of last request processed by server */ 17221 Bool send_event; /* true if this came from a SendEvent request */ 17222 Display *display; /* Display the event was read from */ 17223 Window owner; 17224 Window requestor; 17225 Atom selection; 17226 Atom target; 17227 Atom property; 17228 Time time; 17229 } 17230 17231 struct XSelectionEvent 17232 { 17233 int type; 17234 arch_ulong serial; /* # of last request processed by server */ 17235 Bool send_event; /* true if this came from a SendEvent request */ 17236 Display *display; /* Display the event was read from */ 17237 Window requestor; 17238 Atom selection; 17239 Atom target; 17240 Atom property; /* ATOM or None */ 17241 Time time; 17242 } 17243 version(X86_64) static assert(XSelectionClearEvent.sizeof == 56); 17244 17245 struct XColormapEvent 17246 { 17247 int type; 17248 arch_ulong serial; /* # of last request processed by server */ 17249 Bool send_event; /* true if this came from a SendEvent request */ 17250 Display *display; /* Display the event was read from */ 17251 Window window; 17252 Colormap colormap; /* COLORMAP or None */ 17253 Bool new_; /* C++ */ 17254 ColorMapNotification state; /* ColormapInstalled, ColormapUninstalled */ 17255 } 17256 version(X86_64) static assert(XColormapEvent.sizeof == 56); 17257 17258 struct XClientMessageEvent 17259 { 17260 int type; 17261 arch_ulong serial; /* # of last request processed by server */ 17262 Bool send_event; /* true if this came from a SendEvent request */ 17263 Display *display; /* Display the event was read from */ 17264 Window window; 17265 Atom message_type; 17266 int format; 17267 union Data{ 17268 byte[20] b; 17269 short[10] s; 17270 arch_ulong[5] l; 17271 } 17272 Data data; 17273 17274 } 17275 version(X86_64) static assert(XClientMessageEvent.sizeof == 96); 17276 17277 struct XMappingEvent 17278 { 17279 int type; 17280 arch_ulong serial; /* # of last request processed by server */ 17281 Bool send_event; /* true if this came from a SendEvent request */ 17282 Display *display; /* Display the event was read from */ 17283 Window window; /* unused */ 17284 MappingType request; /* one of MappingModifier, MappingKeyboard, 17285 MappingPointer */ 17286 int first_keycode; /* first keycode */ 17287 int count; /* defines range of change w. first_keycode*/ 17288 } 17289 17290 struct XErrorEvent 17291 { 17292 int type; 17293 Display *display; /* Display the event was read from */ 17294 XID resourceid; /* resource id */ 17295 arch_ulong serial; /* serial number of failed request */ 17296 ubyte error_code; /* error code of failed request */ 17297 ubyte request_code; /* Major op-code of failed request */ 17298 ubyte minor_code; /* Minor op-code of failed request */ 17299 } 17300 17301 struct XAnyEvent 17302 { 17303 int type; 17304 arch_ulong serial; /* # of last request processed by server */ 17305 Bool send_event; /* true if this came from a SendEvent request */ 17306 Display *display;/* Display the event was read from */ 17307 Window window; /* window on which event was requested in event mask */ 17308 } 17309 17310 union XEvent{ 17311 int type; /* must not be changed; first element */ 17312 XAnyEvent xany; 17313 XKeyEvent xkey; 17314 XButtonEvent xbutton; 17315 XMotionEvent xmotion; 17316 XCrossingEvent xcrossing; 17317 XFocusChangeEvent xfocus; 17318 XExposeEvent xexpose; 17319 XGraphicsExposeEvent xgraphicsexpose; 17320 XNoExposeEvent xnoexpose; 17321 XVisibilityEvent xvisibility; 17322 XCreateWindowEvent xcreatewindow; 17323 XDestroyWindowEvent xdestroywindow; 17324 XUnmapEvent xunmap; 17325 XMapEvent xmap; 17326 XMapRequestEvent xmaprequest; 17327 XReparentEvent xreparent; 17328 XConfigureEvent xconfigure; 17329 XGravityEvent xgravity; 17330 XResizeRequestEvent xresizerequest; 17331 XConfigureRequestEvent xconfigurerequest; 17332 XCirculateEvent xcirculate; 17333 XCirculateRequestEvent xcirculaterequest; 17334 XPropertyEvent xproperty; 17335 XSelectionClearEvent xselectionclear; 17336 XSelectionRequestEvent xselectionrequest; 17337 XSelectionEvent xselection; 17338 XColormapEvent xcolormap; 17339 XClientMessageEvent xclient; 17340 XMappingEvent xmapping; 17341 XErrorEvent xerror; 17342 XKeymapEvent xkeymap; 17343 arch_ulong[24] pad; 17344 } 17345 17346 17347 struct Display { 17348 XExtData *ext_data; /* hook for extension to hang data */ 17349 _XPrivate *private1; 17350 int fd; /* Network socket. */ 17351 int private2; 17352 int proto_major_version;/* major version of server's X protocol */ 17353 int proto_minor_version;/* minor version of servers X protocol */ 17354 char *vendor; /* vendor of the server hardware */ 17355 XID private3; 17356 XID private4; 17357 XID private5; 17358 int private6; 17359 XID function(Display*)resource_alloc;/* allocator function */ 17360 ByteOrder byte_order; /* screen byte order, LSBFirst, MSBFirst */ 17361 int bitmap_unit; /* padding and data requirements */ 17362 int bitmap_pad; /* padding requirements on bitmaps */ 17363 ByteOrder bitmap_bit_order; /* LeastSignificant or MostSignificant */ 17364 int nformats; /* number of pixmap formats in list */ 17365 ScreenFormat *pixmap_format; /* pixmap format list */ 17366 int private8; 17367 int release; /* release of the server */ 17368 _XPrivate *private9; 17369 _XPrivate *private10; 17370 int qlen; /* Length of input event queue */ 17371 arch_ulong last_request_read; /* seq number of last event read */ 17372 arch_ulong request; /* sequence number of last request. */ 17373 XPointer private11; 17374 XPointer private12; 17375 XPointer private13; 17376 XPointer private14; 17377 uint max_request_size; /* maximum number 32 bit words in request*/ 17378 _XrmHashBucketRec *db; 17379 int function (Display*)private15; 17380 char *display_name; /* "host:display" string used on this connect*/ 17381 int default_screen; /* default screen for operations */ 17382 int nscreens; /* number of screens on this server*/ 17383 Screen *screens; /* pointer to list of screens */ 17384 arch_ulong motion_buffer; /* size of motion buffer */ 17385 arch_ulong private16; 17386 int min_keycode; /* minimum defined keycode */ 17387 int max_keycode; /* maximum defined keycode */ 17388 XPointer private17; 17389 XPointer private18; 17390 int private19; 17391 byte *xdefaults; /* contents of defaults from server */ 17392 /* there is more to this structure, but it is private to Xlib */ 17393 } 17394 17395 // I got these numbers from a C program as a sanity test 17396 version(X86_64) { 17397 static assert(Display.sizeof == 296); 17398 static assert(XPointer.sizeof == 8); 17399 static assert(XErrorEvent.sizeof == 40); 17400 static assert(XAnyEvent.sizeof == 40); 17401 static assert(XMappingEvent.sizeof == 56); 17402 static assert(XEvent.sizeof == 192); 17403 } else version (AArch64) { 17404 // omit check for aarch64 17405 } else { 17406 static assert(Display.sizeof == 176); 17407 static assert(XPointer.sizeof == 4); 17408 static assert(XEvent.sizeof == 96); 17409 } 17410 17411 struct Depth 17412 { 17413 int depth; /* this depth (Z) of the depth */ 17414 int nvisuals; /* number of Visual types at this depth */ 17415 Visual *visuals; /* list of visuals possible at this depth */ 17416 } 17417 17418 alias void* GC; 17419 alias c_ulong VisualID; 17420 alias XID Colormap; 17421 alias XID Cursor; 17422 alias XID KeySym; 17423 alias uint KeyCode; 17424 enum None = 0; 17425 } 17426 17427 version(without_opengl) {} 17428 else { 17429 extern(C) nothrow @nogc { 17430 17431 17432 static if(!SdpyIsUsingIVGLBinds) { 17433 enum GLX_USE_GL= 1; /* support GLX rendering */ 17434 enum GLX_BUFFER_SIZE= 2; /* depth of the color buffer */ 17435 enum GLX_LEVEL= 3; /* level in plane stacking */ 17436 enum GLX_RGBA= 4; /* true if RGBA mode */ 17437 enum GLX_DOUBLEBUFFER= 5; /* double buffering supported */ 17438 enum GLX_STEREO= 6; /* stereo buffering supported */ 17439 enum GLX_AUX_BUFFERS= 7; /* number of aux buffers */ 17440 enum GLX_RED_SIZE= 8; /* number of red component bits */ 17441 enum GLX_GREEN_SIZE= 9; /* number of green component bits */ 17442 enum GLX_BLUE_SIZE= 10; /* number of blue component bits */ 17443 enum GLX_ALPHA_SIZE= 11; /* number of alpha component bits */ 17444 enum GLX_DEPTH_SIZE= 12; /* number of depth bits */ 17445 enum GLX_STENCIL_SIZE= 13; /* number of stencil bits */ 17446 enum GLX_ACCUM_RED_SIZE= 14; /* number of red accum bits */ 17447 enum GLX_ACCUM_GREEN_SIZE= 15; /* number of green accum bits */ 17448 enum GLX_ACCUM_BLUE_SIZE= 16; /* number of blue accum bits */ 17449 enum GLX_ACCUM_ALPHA_SIZE= 17; /* number of alpha accum bits */ 17450 17451 17452 //XVisualInfo* glXChooseVisual(Display *dpy, int screen, in int *attrib_list); 17453 17454 17455 17456 enum GL_TRUE = 1; 17457 enum GL_FALSE = 0; 17458 } 17459 17460 alias XID GLXContextID; 17461 alias XID GLXPixmap; 17462 alias XID GLXDrawable; 17463 alias XID GLXPbuffer; 17464 alias XID GLXWindow; 17465 alias XID GLXFBConfigID; 17466 alias void* GLXContext; 17467 17468 } 17469 } 17470 17471 enum AllocNone = 0; 17472 17473 extern(C) { 17474 /* WARNING, this type not in Xlib spec */ 17475 extern(C) alias XIOErrorHandler = int function (Display* display); 17476 } 17477 17478 extern(C) nothrow 17479 alias XErrorHandler = int function(Display*, XErrorEvent*); 17480 17481 extern(C) nothrow @nogc { 17482 struct Screen{ 17483 XExtData *ext_data; /* hook for extension to hang data */ 17484 Display *display; /* back pointer to display structure */ 17485 Window root; /* Root window id. */ 17486 int width, height; /* width and height of screen */ 17487 int mwidth, mheight; /* width and height of in millimeters */ 17488 int ndepths; /* number of depths possible */ 17489 Depth *depths; /* list of allowable depths on the screen */ 17490 int root_depth; /* bits per pixel */ 17491 Visual *root_visual; /* root visual */ 17492 GC default_gc; /* GC for the root root visual */ 17493 Colormap cmap; /* default color map */ 17494 uint white_pixel; 17495 uint black_pixel; /* White and Black pixel values */ 17496 int max_maps, min_maps; /* max and min color maps */ 17497 int backing_store; /* Never, WhenMapped, Always */ 17498 bool save_unders; 17499 int root_input_mask; /* initial root input mask */ 17500 } 17501 17502 struct Visual 17503 { 17504 XExtData *ext_data; /* hook for extension to hang data */ 17505 VisualID visualid; /* visual id of this visual */ 17506 int class_; /* class of screen (monochrome, etc.) */ 17507 c_ulong red_mask, green_mask, blue_mask; /* mask values */ 17508 int bits_per_rgb; /* log base 2 of distinct color values */ 17509 int map_entries; /* color map entries */ 17510 } 17511 17512 alias Display* _XPrivDisplay; 17513 17514 extern(D) Screen* ScreenOfDisplay(Display* dpy, int scr) { 17515 assert(dpy !is null); 17516 return &dpy.screens[scr]; 17517 } 17518 17519 extern(D) Window RootWindow(Display *dpy,int scr) { 17520 return ScreenOfDisplay(dpy,scr).root; 17521 } 17522 17523 struct XWMHints { 17524 arch_long flags; 17525 Bool input; 17526 int initial_state; 17527 Pixmap icon_pixmap; 17528 Window icon_window; 17529 int icon_x, icon_y; 17530 Pixmap icon_mask; 17531 XID window_group; 17532 } 17533 17534 struct XClassHint { 17535 char* res_name; 17536 char* res_class; 17537 } 17538 17539 extern(D) int DefaultScreen(Display *dpy) { 17540 return dpy.default_screen; 17541 } 17542 17543 extern(D) int DefaultDepth(Display* dpy, int scr) { return ScreenOfDisplay(dpy, scr).root_depth; } 17544 extern(D) int DisplayWidth(Display* dpy, int scr) { return ScreenOfDisplay(dpy, scr).width; } 17545 extern(D) int DisplayHeight(Display* dpy, int scr) { return ScreenOfDisplay(dpy, scr).height; } 17546 extern(D) int DisplayWidthMM(Display* dpy, int scr) { return ScreenOfDisplay(dpy, scr).mwidth; } 17547 extern(D) int DisplayHeightMM(Display* dpy, int scr) { return ScreenOfDisplay(dpy, scr).mheight; } 17548 extern(D) auto DefaultColormap(Display* dpy, int scr) { return ScreenOfDisplay(dpy, scr).cmap; } 17549 17550 extern(D) int ConnectionNumber(Display* dpy) { return dpy.fd; } 17551 17552 enum int AnyPropertyType = 0; 17553 enum int Success = 0; 17554 17555 enum int RevertToNone = None; 17556 enum int PointerRoot = 1; 17557 enum Time CurrentTime = 0; 17558 enum int RevertToPointerRoot = PointerRoot; 17559 enum int RevertToParent = 2; 17560 17561 extern(D) int DefaultDepthOfDisplay(Display* dpy) { 17562 return ScreenOfDisplay(dpy, DefaultScreen(dpy)).root_depth; 17563 } 17564 17565 extern(D) Visual* DefaultVisual(Display *dpy,int scr) { 17566 return ScreenOfDisplay(dpy,scr).root_visual; 17567 } 17568 17569 extern(D) GC DefaultGC(Display *dpy,int scr) { 17570 return ScreenOfDisplay(dpy,scr).default_gc; 17571 } 17572 17573 extern(D) uint BlackPixel(Display *dpy,int scr) { 17574 return ScreenOfDisplay(dpy,scr).black_pixel; 17575 } 17576 17577 extern(D) uint WhitePixel(Display *dpy,int scr) { 17578 return ScreenOfDisplay(dpy,scr).white_pixel; 17579 } 17580 17581 alias void* XFontSet; // i think 17582 struct XmbTextItem { 17583 char* chars; 17584 int nchars; 17585 int delta; 17586 XFontSet font_set; 17587 } 17588 17589 struct XTextItem { 17590 char* chars; 17591 int nchars; 17592 int delta; 17593 Font font; 17594 } 17595 17596 enum { 17597 GXclear = 0x0, /* 0 */ 17598 GXand = 0x1, /* src AND dst */ 17599 GXandReverse = 0x2, /* src AND NOT dst */ 17600 GXcopy = 0x3, /* src */ 17601 GXandInverted = 0x4, /* NOT src AND dst */ 17602 GXnoop = 0x5, /* dst */ 17603 GXxor = 0x6, /* src XOR dst */ 17604 GXor = 0x7, /* src OR dst */ 17605 GXnor = 0x8, /* NOT src AND NOT dst */ 17606 GXequiv = 0x9, /* NOT src XOR dst */ 17607 GXinvert = 0xa, /* NOT dst */ 17608 GXorReverse = 0xb, /* src OR NOT dst */ 17609 GXcopyInverted = 0xc, /* NOT src */ 17610 GXorInverted = 0xd, /* NOT src OR dst */ 17611 GXnand = 0xe, /* NOT src OR NOT dst */ 17612 GXset = 0xf, /* 1 */ 17613 } 17614 enum QueueMode : int { 17615 QueuedAlready, 17616 QueuedAfterReading, 17617 QueuedAfterFlush 17618 } 17619 17620 enum GrabMode { GrabModeSync = 0, GrabModeAsync = 1 } 17621 17622 struct XPoint { 17623 short x; 17624 short y; 17625 } 17626 17627 enum CoordMode:int { 17628 CoordModeOrigin = 0, 17629 CoordModePrevious = 1 17630 } 17631 17632 enum PolygonShape:int { 17633 Complex = 0, 17634 Nonconvex = 1, 17635 Convex = 2 17636 } 17637 17638 struct XTextProperty { 17639 const(char)* value; /* same as Property routines */ 17640 Atom encoding; /* prop type */ 17641 int format; /* prop data format: 8, 16, or 32 */ 17642 arch_ulong nitems; /* number of data items in value */ 17643 } 17644 17645 version( X86_64 ) { 17646 static assert(XTextProperty.sizeof == 32); 17647 } 17648 17649 17650 struct XGCValues { 17651 int function_; /* logical operation */ 17652 arch_ulong plane_mask;/* plane mask */ 17653 arch_ulong foreground;/* foreground pixel */ 17654 arch_ulong background;/* background pixel */ 17655 int line_width; /* line width */ 17656 int line_style; /* LineSolid, LineOnOffDash, LineDoubleDash */ 17657 int cap_style; /* CapNotLast, CapButt, 17658 CapRound, CapProjecting */ 17659 int join_style; /* JoinMiter, JoinRound, JoinBevel */ 17660 int fill_style; /* FillSolid, FillTiled, 17661 FillStippled, FillOpaeueStippled */ 17662 int fill_rule; /* EvenOddRule, WindingRule */ 17663 int arc_mode; /* ArcChord, ArcPieSlice */ 17664 Pixmap tile; /* tile pixmap for tiling operations */ 17665 Pixmap stipple; /* stipple 1 plane pixmap for stipping */ 17666 int ts_x_origin; /* offset for tile or stipple operations */ 17667 int ts_y_origin; 17668 Font font; /* default text font for text operations */ 17669 int subwindow_mode; /* ClipByChildren, IncludeInferiors */ 17670 Bool graphics_exposures;/* boolean, should exposures be generated */ 17671 int clip_x_origin; /* origin for clipping */ 17672 int clip_y_origin; 17673 Pixmap clip_mask; /* bitmap clipping; other calls for rects */ 17674 int dash_offset; /* patterned/dashed line information */ 17675 char dashes; 17676 } 17677 17678 struct XColor { 17679 arch_ulong pixel; 17680 ushort red, green, blue; 17681 byte flags; 17682 byte pad; 17683 } 17684 17685 struct XRectangle { 17686 short x; 17687 short y; 17688 ushort width; 17689 ushort height; 17690 } 17691 17692 enum ClipByChildren = 0; 17693 enum IncludeInferiors = 1; 17694 17695 enum Atom XA_PRIMARY = 1; 17696 enum Atom XA_SECONDARY = 2; 17697 enum Atom XA_STRING = 31; 17698 enum Atom XA_CARDINAL = 6; 17699 enum Atom XA_WM_NAME = 39; 17700 enum Atom XA_ATOM = 4; 17701 enum Atom XA_WINDOW = 33; 17702 enum Atom XA_WM_HINTS = 35; 17703 enum int PropModeAppend = 2; 17704 enum int PropModeReplace = 0; 17705 enum int PropModePrepend = 1; 17706 17707 enum int CopyFromParent = 0; 17708 enum int InputOutput = 1; 17709 17710 // XWMHints 17711 enum InputHint = 1 << 0; 17712 enum StateHint = 1 << 1; 17713 enum IconPixmapHint = (1L << 2); 17714 enum IconWindowHint = (1L << 3); 17715 enum IconPositionHint = (1L << 4); 17716 enum IconMaskHint = (1L << 5); 17717 enum WindowGroupHint = (1L << 6); 17718 enum AllHints = (InputHint|StateHint|IconPixmapHint|IconWindowHint|IconPositionHint|IconMaskHint|WindowGroupHint); 17719 enum XUrgencyHint = (1L << 8); 17720 17721 // GC Components 17722 enum GCFunction = (1L<<0); 17723 enum GCPlaneMask = (1L<<1); 17724 enum GCForeground = (1L<<2); 17725 enum GCBackground = (1L<<3); 17726 enum GCLineWidth = (1L<<4); 17727 enum GCLineStyle = (1L<<5); 17728 enum GCCapStyle = (1L<<6); 17729 enum GCJoinStyle = (1L<<7); 17730 enum GCFillStyle = (1L<<8); 17731 enum GCFillRule = (1L<<9); 17732 enum GCTile = (1L<<10); 17733 enum GCStipple = (1L<<11); 17734 enum GCTileStipXOrigin = (1L<<12); 17735 enum GCTileStipYOrigin = (1L<<13); 17736 enum GCFont = (1L<<14); 17737 enum GCSubwindowMode = (1L<<15); 17738 enum GCGraphicsExposures= (1L<<16); 17739 enum GCClipXOrigin = (1L<<17); 17740 enum GCClipYOrigin = (1L<<18); 17741 enum GCClipMask = (1L<<19); 17742 enum GCDashOffset = (1L<<20); 17743 enum GCDashList = (1L<<21); 17744 enum GCArcMode = (1L<<22); 17745 enum GCLastBit = 22; 17746 17747 17748 enum int WithdrawnState = 0; 17749 enum int NormalState = 1; 17750 enum int IconicState = 3; 17751 17752 } 17753 } else version (OSXCocoa) { 17754 17755 /+ 17756 DON'T FORGET TO MARK THE CLASSES `extern`!! can cause "unrecognized selector sent to class" errors if you do. 17757 +/ 17758 17759 private __gshared AppDelegate globalAppDelegate; 17760 17761 extern(Objective-C) 17762 class AppDelegate : NSObject, NSApplicationDelegate { 17763 override static AppDelegate alloc() @selector("alloc"); 17764 17765 17766 void sdpyCustomEventWakeup(NSid arg) @selector("sdpyCustomEventWakeup:") { 17767 SimpleWindow.processAllCustomEvents(); 17768 } 17769 17770 override void applicationWillFinishLaunching(NSNotification notification) @selector("applicationWillFinishLaunching:") { 17771 immutable style = NSWindowStyleMask.resizable | 17772 NSWindowStyleMask.closable | 17773 NSWindowStyleMask.miniaturizable | 17774 NSWindowStyleMask.titled; 17775 17776 NSMenu mainMenu = NSMenu.alloc.init(MacString("Main").borrow); 17777 17778 { 17779 auto item = mainMenu.addItem(MacString("Test").borrow, null, MacString("").borrow); 17780 auto menu = NSMenu.alloc.init(MacString("Test2").borrow); 17781 mainMenu.setSubmenu(menu, item); 17782 17783 auto newItem = menu.addItem(MacString("Quit").borrow, sel_registerName("terminate:"), MacString("q").borrow); 17784 newItem.target = NSApp; 17785 auto newItem2 = menu.addItem(MacString("Disabled").borrow, sel_registerName("doesnotexist:"), MacString("x").borrow); 17786 newItem2.target = NSApp; 17787 } 17788 17789 { 17790 auto item = mainMenu.addItem(MacString("Test3").borrow, null, MacString("").borrow); 17791 auto menu = NSMenu.alloc.init(MacString("Test4").borrow); // this is the title actually used 17792 mainMenu.setSubmenu(menu, item); 17793 17794 auto newItem = menu.addItem(MacString("Quit2").borrow, sel_registerName("stop:"), MacString("s").borrow); 17795 menu.addItem(MacString("Pulse").borrow, sel_registerName("simpledisplay_pulse:"), MacString("p").borrow); 17796 } 17797 17798 17799 NSApp.menu = mainMenu; 17800 17801 17802 // auto controller = ViewController.alloc.init; 17803 17804 // auto timer = NSTimer.schedule(1.0, cast(NSid) view, sel_registerName("simpledisplay_pulse:"), null, true); 17805 17806 /+ 17807 this.window = window; 17808 this.controller = controller; 17809 +/ 17810 } 17811 17812 override void applicationDidFinishLaunching(NSNotification notification) @selector("applicationDidFinishLaunching:") { 17813 NSApplication.shared_.activateIgnoringOtherApps(true); 17814 } 17815 override bool applicationShouldTerminateAfterLastWindowClosed(NSNotification notification) @selector("applicationShouldTerminateAfterLastWindowClosed:") { 17816 return true; 17817 } 17818 } 17819 17820 extern(Objective-C) 17821 class SDWindowDelegate : NSObject, NSWindowDelegate { 17822 override static SDWindowDelegate alloc() @selector("alloc"); 17823 override SDWindowDelegate init() @selector("init"); 17824 17825 SimpleWindow simpleWindow; 17826 17827 override void windowWillClose(NSNotification notification) @selector("windowWillClose:") { 17828 auto window = cast(void*) notification.object; 17829 17830 // FIXME: do i need to release it? 17831 SimpleWindow.nativeMapping.remove(window); 17832 } 17833 17834 override NSSize windowWillResize(NSWindow sender, NSSize frameSize) @selector("windowWillResize:toSize:") { 17835 if(simpleWindow.windowResized) { 17836 // FIXME: automaticallyScaleIfPossible behaviors 17837 17838 simpleWindow._width = cast(int) frameSize.width; 17839 simpleWindow._height = cast(int) frameSize.height; 17840 17841 simpleWindow.view.setFrameSize(frameSize); 17842 17843 /+ 17844 auto size = simpleWindow.view.frame.size; 17845 writeln(cast(int) size.width, "x", cast(int) size.height); 17846 +/ 17847 17848 simpleWindow.createNewDrawingContext(simpleWindow._width, simpleWindow._height); 17849 17850 simpleWindow.windowResized(simpleWindow._width, simpleWindow._height); 17851 17852 // simpleWindow.view.setNeedsDisplay(true); 17853 } 17854 17855 return frameSize; 17856 } 17857 17858 /+ 17859 override void windowDidResize(NSNotification notification) @selector("windowDidResize:") { 17860 if(simpleWindow.windowResized) { 17861 auto window = simpleWindow.window; 17862 auto rect = window.contentRectForFrameRect(window.frame); 17863 import std.stdio; writeln(window.frame.size); 17864 simpleWindow.windowResized(cast(int) rect.size.width, cast(int) rect.size.height); 17865 } 17866 } 17867 +/ 17868 } 17869 17870 extern(Objective-C) 17871 class SDGraphicsView : NSView { 17872 SimpleWindow simpleWindow; 17873 17874 override static SDGraphicsView alloc() @selector("alloc"); 17875 override SDGraphicsView init() @selector("init") { 17876 super.init(); 17877 return this; 17878 } 17879 17880 override void drawRect(NSRect rect) @selector("drawRect:") { 17881 auto curCtx = NSGraphicsContext.currentContext.graphicsPort; 17882 auto cgImage = CGBitmapContextCreateImage(simpleWindow.drawingContext); 17883 auto size = CGSize(CGBitmapContextGetWidth(simpleWindow.drawingContext), CGBitmapContextGetHeight(simpleWindow.drawingContext)); 17884 CGContextDrawImage(curCtx, CGRect(CGPoint(0, 0), size), cgImage); 17885 CGImageRelease(cgImage); 17886 } 17887 17888 private void mouseHelper(NSEvent event, MouseEventType type, MouseButton button) { 17889 MouseEvent me; 17890 me.type = type; 17891 17892 auto pos = event.locationInWindow; 17893 17894 me.x = cast(int) pos.x; 17895 me.y = cast(int) (simpleWindow.height - pos.y); 17896 17897 me.dx = 0; // FIXME 17898 me.dy = 0; // FIXME 17899 17900 me.button = button; 17901 me.modifierState = cast(uint) event.modifierFlags; 17902 me.window = simpleWindow; 17903 17904 me.doubleClick = false; 17905 17906 if(simpleWindow && simpleWindow.handleMouseEvent) 17907 simpleWindow.handleMouseEvent(me); 17908 } 17909 17910 override void mouseDown(NSEvent event) @selector("mouseDown:") { 17911 // writeln(event.pressedMouseButtons); 17912 17913 mouseHelper(event, MouseEventType.buttonPressed, MouseButton.left); 17914 } 17915 override void mouseDragged(NSEvent event) @selector("mouseDragged:") { 17916 mouseHelper(event, MouseEventType.motion, MouseButton.left); 17917 } 17918 override void mouseUp(NSEvent event) @selector("mouseUp:") { 17919 mouseHelper(event, MouseEventType.buttonReleased, MouseButton.left); 17920 } 17921 override void mouseMoved(NSEvent event) @selector("mouseMoved:") { 17922 mouseHelper(event, MouseEventType.motion, MouseButton.left); // button wrong prolly 17923 } 17924 /+ 17925 // FIXME 17926 override void mouseEntered(NSEvent event) @selector("mouseEntered:") { 17927 mouseHelper(event, MouseEventType.buttonPressed, MouseButton.left); 17928 } 17929 override void mouseExited(NSEvent event) @selector("mouseExited:") { 17930 mouseHelper(event, MouseEventType.buttonPressed, MouseButton.left); 17931 } 17932 +/ 17933 17934 override void rightMouseDown(NSEvent event) @selector("rightMouseDown:") { 17935 mouseHelper(event, MouseEventType.buttonPressed, MouseButton.right); 17936 } 17937 override void rightMouseDragged(NSEvent event) @selector("rightMouseDragged:") { 17938 mouseHelper(event, MouseEventType.motion, MouseButton.right); 17939 } 17940 override void rightMouseUp(NSEvent event) @selector("rightMouseUp:") { 17941 mouseHelper(event, MouseEventType.buttonReleased, MouseButton.right); 17942 } 17943 17944 override void otherMouseDown(NSEvent event) @selector("otherMouseDown:") { 17945 mouseHelper(event, MouseEventType.buttonPressed, MouseButton.middle); 17946 } 17947 override void otherMouseDragged(NSEvent event) @selector("otherMouseDragged:") { 17948 mouseHelper(event, MouseEventType.motion, MouseButton.middle); 17949 } 17950 override void otherMouseUp(NSEvent event) @selector("otherMouseUp:") { 17951 mouseHelper(event, MouseEventType.buttonReleased, MouseButton.middle); 17952 } 17953 17954 override void scrollWheel(NSEvent event) @selector("scrollWheel:") { 17955 import std.stdio; 17956 writeln(event.deltaY); 17957 } 17958 17959 override void keyDown(NSEvent event) @selector("keyDown:") { 17960 // the event may have multiple characters, and we send them all at once. 17961 if (simpleWindow.handleCharEvent) { 17962 auto chars = DeifiedNSString(event.characters); 17963 foreach (dchar dc; chars.str) 17964 simpleWindow.handleCharEvent(dc); 17965 } 17966 17967 keyHelper(event, true); 17968 } 17969 17970 override void keyUp(NSEvent event) @selector("keyUp:") { 17971 keyHelper(event, false); 17972 } 17973 17974 private void keyHelper(NSEvent event, bool pressed) { 17975 if(simpleWindow.handleKeyEvent) { 17976 KeyEvent ev; 17977 ev.key = cast(Key) event.keyCode;// (event.specialKey ? event.specialKey : event.keyCode); 17978 ev.pressed = pressed; 17979 ev.hardwareCode = cast(ubyte) event.keyCode; 17980 ev.modifierState = cast(uint) event.modifierFlags; 17981 ev.window = simpleWindow; 17982 17983 simpleWindow.handleKeyEvent(ev); 17984 } 17985 } 17986 17987 override bool isFlipped() @selector("isFlipped") { 17988 return true; 17989 } 17990 override bool acceptsFirstResponder() @selector("acceptsFirstResponder") { 17991 return true; 17992 } 17993 17994 void simpledisplay_pulse(NSTimer timer) @selector("simpledisplay_pulse:") { 17995 if(simpleWindow && simpleWindow.handlePulse) 17996 simpleWindow.handlePulse(); 17997 /+ 17998 setNeedsDisplay = true; 17999 +/ 18000 } 18001 } 18002 18003 private: 18004 alias const(void)* CFStringRef; 18005 alias const(void)* CFAllocatorRef; 18006 alias const(void)* CFTypeRef; 18007 alias const(void)* CGColorSpaceRef; 18008 alias const(void)* CGImageRef; 18009 alias ulong CGBitmapInfo; 18010 alias NSGraphicsContext CGContextRef; 18011 18012 alias NSPoint CGPoint; 18013 alias NSSize CGSize; 18014 alias NSRect CGRect; 18015 18016 struct CGAffineTransform { 18017 double a, b, c, d, tx, ty; 18018 } 18019 18020 enum NSApplicationActivationPolicyRegular = 0; 18021 enum NSBackingStoreBuffered = 2; 18022 enum kCFStringEncodingUTF8 = 0x08000100; 18023 18024 enum : size_t { 18025 NSBorderlessWindowMask = 0, 18026 NSTitledWindowMask = 1 << 0, 18027 NSClosableWindowMask = 1 << 1, 18028 NSMiniaturizableWindowMask = 1 << 2, 18029 NSResizableWindowMask = 1 << 3, 18030 NSTexturedBackgroundWindowMask = 1 << 8 18031 } 18032 18033 enum : ulong { 18034 kCGImageAlphaNone, 18035 kCGImageAlphaPremultipliedLast, 18036 kCGImageAlphaPremultipliedFirst, 18037 kCGImageAlphaLast, 18038 kCGImageAlphaFirst, 18039 kCGImageAlphaNoneSkipLast, 18040 kCGImageAlphaNoneSkipFirst 18041 } 18042 enum : ulong { 18043 kCGBitmapAlphaInfoMask = 0x1F, 18044 kCGBitmapFloatComponents = (1 << 8), 18045 kCGBitmapByteOrderMask = 0x7000, 18046 kCGBitmapByteOrderDefault = (0 << 12), 18047 kCGBitmapByteOrder16Little = (1 << 12), 18048 kCGBitmapByteOrder32Little = (2 << 12), 18049 kCGBitmapByteOrder16Big = (3 << 12), 18050 kCGBitmapByteOrder32Big = (4 << 12) 18051 } 18052 enum CGPathDrawingMode { 18053 kCGPathFill, 18054 kCGPathEOFill, 18055 kCGPathStroke, 18056 kCGPathFillStroke, 18057 kCGPathEOFillStroke 18058 } 18059 enum objc_AssociationPolicy : size_t { 18060 OBJC_ASSOCIATION_ASSIGN = 0, 18061 OBJC_ASSOCIATION_RETAIN_NONATOMIC = 1, 18062 OBJC_ASSOCIATION_COPY_NONATOMIC = 3, 18063 OBJC_ASSOCIATION_RETAIN = 0x301, //01401, 18064 OBJC_ASSOCIATION_COPY = 0x303 //01403 18065 } 18066 18067 extern(C) { 18068 CGContextRef CGBitmapContextCreate(void* data, size_t width, size_t height, size_t bitsPerComponent, size_t bytesPerRow, CGColorSpaceRef colorspace, CGBitmapInfo bitmapInfo); 18069 void CGContextRelease(CGContextRef c); 18070 ubyte* CGBitmapContextGetData(CGContextRef c); 18071 CGImageRef CGBitmapContextCreateImage(CGContextRef c); 18072 size_t CGBitmapContextGetWidth(CGContextRef c); 18073 size_t CGBitmapContextGetHeight(CGContextRef c); 18074 18075 CGColorSpaceRef CGColorSpaceCreateDeviceRGB(); 18076 void CGColorSpaceRelease(CGColorSpaceRef cs); 18077 18078 void CGContextSetRGBStrokeColor(CGContextRef c, double red, double green, double blue, double alpha); 18079 void CGContextSetRGBFillColor(CGContextRef c, double red, double green, double blue, double alpha); 18080 void CGContextDrawImage(CGContextRef c, CGRect rect, CGImageRef image); 18081 void CGContextShowTextAtPoint(CGContextRef c, double x, double y, const(char)* str, size_t length); 18082 void CGContextStrokeLineSegments(CGContextRef c, const(CGPoint)* points, size_t count); 18083 18084 void CGContextBeginPath(CGContextRef c); 18085 void CGContextDrawPath(CGContextRef c, CGPathDrawingMode mode); 18086 void CGContextAddEllipseInRect(CGContextRef c, CGRect rect); 18087 void CGContextAddArc(CGContextRef c, double x, double y, double radius, double startAngle, double endAngle, long clockwise); 18088 void CGContextAddRect(CGContextRef c, CGRect rect); 18089 void CGContextAddLines(CGContextRef c, const(CGPoint)* points, size_t count); 18090 void CGContextSaveGState(CGContextRef c); 18091 void CGContextRestoreGState(CGContextRef c); 18092 void CGContextSelectFont(CGContextRef c, const(char)* name, double size, ulong textEncoding); 18093 CGAffineTransform CGContextGetTextMatrix(CGContextRef c); 18094 void CGContextSetTextMatrix(CGContextRef c, CGAffineTransform t); 18095 18096 void CGImageRelease(CGImageRef image); 18097 } 18098 } else static assert(0, "Unsupported operating system"); 18099 18100 18101 version(OSXCocoa) { 18102 // I don't know anything about the Mac, but a couple years ago, KennyTM on the newsgroup wrote this for me 18103 // 18104 // http://forum.dlang.org/thread/innr0v$1deh$1@digitalmars.com?page=4#post-int88l:24uaf:241:40digitalmars.com 18105 // https://github.com/kennytm/simpledisplay.d/blob/osx/simpledisplay.d 18106 // 18107 // and it is about time I merged it in here. It is available with -version=OSXCocoa until someone tests it for me! 18108 // Probably won't even fully compile right now 18109 18110 private enum double PI = 3.14159265358979323; 18111 18112 alias NSWindow NativeWindowHandle; 18113 alias void delegate(NSid) NativeEventHandler; 18114 18115 enum KEY_ESCAPE = 27; 18116 18117 mixin template NativeImageImplementation() { 18118 CGContextRef context; 18119 ubyte* rawData; 18120 18121 final: 18122 18123 void convertToRgbaBytes(ubyte[] where) { 18124 assert(where.length == this.width * this.height * 4); 18125 18126 // if rawData had a length.... 18127 //assert(rawData.length == where.length); 18128 for(long idx = 0; idx < where.length; idx += 4) { 18129 auto alpha = rawData[idx + 3]; 18130 if(alpha == 255) { 18131 where[idx + 0] = rawData[idx + 0]; // r 18132 where[idx + 1] = rawData[idx + 1]; // g 18133 where[idx + 2] = rawData[idx + 2]; // b 18134 where[idx + 3] = rawData[idx + 3]; // a 18135 } else { 18136 where[idx + 0] = cast(ubyte)(rawData[idx + 0] * 255 / alpha); // r 18137 where[idx + 1] = cast(ubyte)(rawData[idx + 1] * 255 / alpha); // g 18138 where[idx + 2] = cast(ubyte)(rawData[idx + 2] * 255 / alpha); // b 18139 where[idx + 3] = rawData[idx + 3]; // a 18140 18141 } 18142 } 18143 } 18144 18145 void setFromRgbaBytes(in ubyte[] where) { 18146 // FIXME: this is probably wrong 18147 assert(where.length == this.width * this.height * 4); 18148 18149 // if rawData had a length.... 18150 //assert(rawData.length == where.length); 18151 for(long idx = 0; idx < where.length; idx += 4) { 18152 auto alpha = where[idx + 3]; 18153 if(alpha == 255) { 18154 rawData[idx + 0] = where[idx + 0]; // r 18155 rawData[idx + 1] = where[idx + 1]; // g 18156 rawData[idx + 2] = where[idx + 2]; // b 18157 rawData[idx + 3] = where[idx + 3]; // a 18158 } else if(alpha == 0) { 18159 rawData[idx + 0] = 0; 18160 rawData[idx + 1] = 0; 18161 rawData[idx + 2] = 0; 18162 rawData[idx + 3] = 0; 18163 } else { 18164 rawData[idx + 0] = cast(ubyte)(where[idx + 0] * 255 / alpha); // r 18165 rawData[idx + 1] = cast(ubyte)(where[idx + 1] * 255 / alpha); // g 18166 rawData[idx + 2] = cast(ubyte)(where[idx + 2] * 255 / alpha); // b 18167 rawData[idx + 3] = where[idx + 3]; // a 18168 } 18169 } 18170 } 18171 18172 18173 void createImage(int width, int height, bool forcexshm=false, bool ignored = false) { 18174 auto colorSpace = CGColorSpaceCreateDeviceRGB(); 18175 context = CGBitmapContextCreate(null, width, height, 8, 4*width, colorSpace, kCGImageAlphaPremultipliedLast |kCGBitmapByteOrder32Big); 18176 CGColorSpaceRelease(colorSpace); 18177 rawData = CGBitmapContextGetData(context); 18178 } 18179 void dispose() { 18180 CGContextRelease(context); 18181 } 18182 18183 void setPixel(int x, int y, Color c) { 18184 auto offset = (y * width + x) * 4; 18185 if (c.a == 255) { 18186 rawData[offset + 0] = c.r; 18187 rawData[offset + 1] = c.g; 18188 rawData[offset + 2] = c.b; 18189 rawData[offset + 3] = c.a; 18190 } else { 18191 rawData[offset + 0] = cast(ubyte)(c.r*c.a/255); 18192 rawData[offset + 1] = cast(ubyte)(c.g*c.a/255); 18193 rawData[offset + 2] = cast(ubyte)(c.b*c.a/255); 18194 rawData[offset + 3] = c.a; 18195 } 18196 } 18197 } 18198 18199 mixin template NativeScreenPainterImplementation() { 18200 CGContextRef context; 18201 ubyte[4] _outlineComponents; 18202 NSView view; 18203 18204 void create(PaintingHandle window) { 18205 // this.destiny = window; 18206 if(auto sw = cast(SimpleWindow) this.window) { 18207 context = sw.drawingContext; 18208 view = sw.view; 18209 } else { 18210 throw new NotYetImplementedException(); 18211 } 18212 } 18213 18214 void dispose() { 18215 view.setNeedsDisplay(true); 18216 } 18217 18218 bool manualInvalidations; 18219 void invalidateRect(Rectangle invalidRect) { } 18220 18221 // NotYetImplementedException 18222 Size textSize(in char[] txt) { 18223 return Size(32, 16); /*throw new NotYetImplementedException();*/ 18224 } 18225 void rasterOp(RasterOp op) { 18226 } 18227 Pen _activePen; 18228 Color _fillColor; 18229 Rectangle _clipRectangle; 18230 void setClipRectangle(int, int, int, int) { 18231 } 18232 void setFont(OperatingSystemFont) { 18233 } 18234 int fontHeight() { 18235 return 14; 18236 } 18237 18238 // end 18239 18240 void pen(Pen pen) { 18241 _activePen = pen; 18242 auto color = pen.color; // FIXME 18243 double alphaComponent = color.a/255.0f; 18244 CGContextSetRGBStrokeColor(context, color.r/255.0f, color.g/255.0f, color.b/255.0f, alphaComponent); 18245 18246 if (color.a != 255) { 18247 _outlineComponents[0] = cast(ubyte)(color.r*color.a/255); 18248 _outlineComponents[1] = cast(ubyte)(color.g*color.a/255); 18249 _outlineComponents[2] = cast(ubyte)(color.b*color.a/255); 18250 _outlineComponents[3] = color.a; 18251 } else { 18252 _outlineComponents[0] = color.r; 18253 _outlineComponents[1] = color.g; 18254 _outlineComponents[2] = color.b; 18255 _outlineComponents[3] = color.a; 18256 } 18257 } 18258 18259 @property void fillColor(Color color) { 18260 CGContextSetRGBFillColor(context, color.r/255.0f, color.g/255.0f, color.b/255.0f, color.a/255.0f); 18261 } 18262 18263 void drawImage(int x, int y, Image image, int ulx, int upy, int width, int height) { 18264 // NotYetImplementedException for upper left/width/height 18265 auto cgImage = CGBitmapContextCreateImage(image.context); 18266 auto size = CGSize(CGBitmapContextGetWidth(image.context), CGBitmapContextGetHeight(image.context)); 18267 CGContextDrawImage(context, CGRect(CGPoint(x, y), size), cgImage); 18268 CGImageRelease(cgImage); 18269 } 18270 18271 void drawPixmap(Sprite s, int x, int y, int ix, int iy, int w, int h) { 18272 // FIXME: is this efficient? 18273 auto cgImage = CGBitmapContextCreateImage(s.handle); 18274 auto size = CGSize(CGBitmapContextGetWidth(s.handle), CGBitmapContextGetHeight(s.handle)); 18275 CGContextDrawImage(context, CGRect(CGPoint(x, y), size), cgImage); 18276 CGImageRelease(cgImage); 18277 } 18278 18279 18280 void drawText(int x, int y, int x2, int y2, in char[] text, uint alignment) { 18281 // FIXME: alignment 18282 if (_outlineComponents[3] != 0) { 18283 CGContextSaveGState(context); 18284 auto invAlpha = 1.0f/_outlineComponents[3]; 18285 CGContextSetRGBFillColor(context, _outlineComponents[0]*invAlpha, 18286 _outlineComponents[1]*invAlpha, 18287 _outlineComponents[2]*invAlpha, 18288 _outlineComponents[3]/255.0f); 18289 CGContextShowTextAtPoint(context, x, y + 12 /* this is cuz this picks baseline but i want bounding box */, text.ptr, text.length); 18290 // auto cfstr = cast(NSid)createCFString(text); 18291 // objc_msgSend(cfstr, sel_registerName("drawAtPoint:withAttributes:"), 18292 // NSPoint(x, y), null); 18293 // CFRelease(cfstr); 18294 CGContextRestoreGState(context); 18295 } 18296 } 18297 18298 void drawPixel(int x, int y) { 18299 auto rawData = CGBitmapContextGetData(context); 18300 auto width = CGBitmapContextGetWidth(context); 18301 auto height = CGBitmapContextGetHeight(context); 18302 auto offset = ((height - y - 1) * width + x) * 4; 18303 rawData[offset .. offset+4] = _outlineComponents; 18304 } 18305 18306 void drawLine(int x1, int y1, int x2, int y2) { 18307 CGPoint[2] linePoints; 18308 linePoints[0] = CGPoint(x1, y1); 18309 linePoints[1] = CGPoint(x2, y2); 18310 CGContextStrokeLineSegments(context, linePoints.ptr, linePoints.length); 18311 } 18312 18313 void drawRectangle(int x, int y, int width, int height) { 18314 CGContextBeginPath(context); 18315 auto rect = CGRect(CGPoint(x, y), CGSize(width, height)); 18316 CGContextAddRect(context, rect); 18317 CGContextDrawPath(context, CGPathDrawingMode.kCGPathFillStroke); 18318 } 18319 18320 void drawEllipse(int x1, int y1, int x2, int y2) { 18321 CGContextBeginPath(context); 18322 auto rect = CGRect(CGPoint(x1, y1), CGSize(x2-x1, y2-y1)); 18323 CGContextAddEllipseInRect(context, rect); 18324 CGContextDrawPath(context, CGPathDrawingMode.kCGPathFillStroke); 18325 } 18326 18327 void drawArc(int x1, int y1, int width, int height, int start, int finish) { 18328 // @@@BUG@@@ Does not support elliptic arc (width != height). 18329 CGContextBeginPath(context); 18330 CGContextAddArc(context, x1+width*0.5f, y1+height*0.5f, width, 18331 start*PI/(180*64), finish*PI/(180*64), 0); 18332 CGContextDrawPath(context, CGPathDrawingMode.kCGPathFillStroke); 18333 } 18334 18335 void drawPolygon(Point[] intPoints) { 18336 CGContextBeginPath(context); 18337 CGPoint[16] pointsBuffer; 18338 CGPoint[] points; 18339 if(intPoints.length <= pointsBuffer.length) 18340 points = pointsBuffer[0 .. intPoints.length]; 18341 else 18342 points = new CGPoint[](intPoints.length); 18343 18344 foreach(idx, pt; intPoints) 18345 points[idx] = CGPoint(pt.x, pt.y); 18346 18347 CGContextAddLines(context, points.ptr, points.length); 18348 CGContextDrawPath(context, CGPathDrawingMode.kCGPathFillStroke); 18349 } 18350 } 18351 18352 private bool appInitialized = false; 18353 void initializeApp() { 18354 if(appInitialized) 18355 return; 18356 synchronized { 18357 if(appInitialized) 18358 return; 18359 18360 auto app = NSApp(); // ensure the is initialized 18361 18362 auto dg = AppDelegate.alloc; 18363 globalAppDelegate = dg; 18364 NSApp.delegate_ = dg; 18365 18366 NSApp.setActivationPolicy(NSApplicationActivationPolicy.regular); 18367 18368 appInitialized = true; 18369 } 18370 } 18371 18372 mixin template NativeSimpleWindowImplementation() { 18373 void createWindow(int width, int height, string title, OpenGlOptions opengl, SimpleWindow parent) { 18374 initializeApp(); 18375 18376 auto contentRect = NSRect(NSPoint(0, 0), NSSize(width, height)); 18377 18378 auto window = NSWindow.alloc.initWithContentRect( 18379 contentRect, 18380 NSWindowStyleMask.resizable | NSWindowStyleMask.closable | NSWindowStyleMask.miniaturizable | NSWindowStyleMask.titled, 18381 NSBackingStoreType.buffered, 18382 true 18383 ); 18384 18385 SimpleWindow.nativeMapping[cast(void*) window] = this; 18386 18387 window.title = MacString(title).borrow; 18388 18389 auto dg = SDWindowDelegate.alloc.init; 18390 dg.simpleWindow = this; 18391 window.delegate_ = dg; 18392 18393 auto view = SDGraphicsView.alloc.init; 18394 assert(view !is null); 18395 window.contentView = view; 18396 this.view = view; 18397 view.simpleWindow = this; 18398 18399 window.center(); 18400 18401 window.makeKeyAndOrderFront(null); 18402 18403 // no need to make a bitmap on mac since everything is double buffered already 18404 18405 // create area to draw on. 18406 createNewDrawingContext(width, height); 18407 18408 window.setBackgroundColor(NSColor.whiteColor); 18409 } 18410 18411 void createNewDrawingContext(int width, int height) { 18412 // FIXME need to preserve info from the old context too i think... maybe. or at least setNeedsDisplay 18413 if(this.drawingContext) 18414 CGContextRelease(this.drawingContext); 18415 auto colorSpace = CGColorSpaceCreateDeviceRGB(); 18416 this.drawingContext = CGBitmapContextCreate(null, width, height, 8, 4*width, colorSpace, kCGImageAlphaPremultipliedLast |kCGBitmapByteOrder32Big); 18417 CGColorSpaceRelease(colorSpace); 18418 CGContextSelectFont(drawingContext, "Lucida Grande", 12.0f, 1); 18419 auto matrix = CGContextGetTextMatrix(drawingContext); 18420 matrix.c = -matrix.c; 18421 matrix.d = -matrix.d; 18422 CGContextSetTextMatrix(drawingContext, matrix); 18423 18424 } 18425 18426 void dispose() { 18427 closeWindow(); 18428 // window.release(); // closing the window does this automatically i think 18429 } 18430 void closeWindow() { 18431 if(timer) 18432 timer.invalidate(); 18433 window.close(); 18434 } 18435 18436 ScreenPainter getPainter(bool manualInvalidations) { 18437 return ScreenPainter(this, this.window, manualInvalidations); 18438 } 18439 18440 NSWindow window; 18441 NSTimer timer; 18442 NSView view; 18443 CGContextRef drawingContext; 18444 } 18445 } 18446 18447 version(without_opengl) {} else 18448 extern(System) nothrow @nogc { 18449 //enum uint GL_VERSION = 0x1F02; 18450 //const(char)* glGetString (/*GLenum*/uint); 18451 version(X11) { 18452 static if (!SdpyIsUsingIVGLBinds) { 18453 18454 enum GLX_X_RENDERABLE = 0x8012; 18455 enum GLX_DRAWABLE_TYPE = 0x8010; 18456 enum GLX_RENDER_TYPE = 0x8011; 18457 enum GLX_X_VISUAL_TYPE = 0x22; 18458 enum GLX_TRUE_COLOR = 0x8002; 18459 enum GLX_WINDOW_BIT = 0x00000001; 18460 enum GLX_RGBA_BIT = 0x00000001; 18461 enum GLX_COLOR_INDEX_BIT = 0x00000002; 18462 enum GLX_SAMPLE_BUFFERS = 0x186a0; 18463 enum GLX_SAMPLES = 0x186a1; 18464 enum GLX_CONTEXT_MAJOR_VERSION_ARB = 0x2091; 18465 enum GLX_CONTEXT_MINOR_VERSION_ARB = 0x2092; 18466 } 18467 18468 // GLX_EXT_swap_control 18469 alias glXSwapIntervalEXT = void function (Display* dpy, /*GLXDrawable*/Drawable drawable, int interval); 18470 private __gshared glXSwapIntervalEXT _glx_swapInterval_fn = null; 18471 18472 //k8: ugly code to prevent warnings when sdpy is compiled into .a 18473 extern(System) { 18474 alias glXCreateContextAttribsARB_fna = GLXContext function (Display *dpy, GLXFBConfig config, GLXContext share_context, /*Bool*/int direct, const(int)* attrib_list); 18475 } 18476 private __gshared /*glXCreateContextAttribsARB_fna*/void* glXCreateContextAttribsARBFn = cast(void*)1; //HACK! 18477 18478 // this made public so we don't have to get it again and again 18479 public bool glXCreateContextAttribsARB_present () { 18480 if (glXCreateContextAttribsARBFn is cast(void*)1) { 18481 // get it 18482 glXCreateContextAttribsARBFn = cast(void*)glbindGetProcAddress("glXCreateContextAttribsARB"); 18483 //{ import core.stdc.stdio; printf("checking glXCreateContextAttribsARB: %shere\n", (glXCreateContextAttribsARBFn !is null ? "".ptr : "not ".ptr)); } 18484 } 18485 return (glXCreateContextAttribsARBFn !is null); 18486 } 18487 18488 // this made public so we don't have to get it again and again 18489 public GLXContext glXCreateContextAttribsARB (Display *dpy, GLXFBConfig config, GLXContext share_context, /*Bool*/int direct, const(int)* attrib_list) { 18490 if (!glXCreateContextAttribsARB_present()) assert(0, "glXCreateContextAttribsARB is not present"); 18491 return (cast(glXCreateContextAttribsARB_fna)glXCreateContextAttribsARBFn)(dpy, config, share_context, direct, attrib_list); 18492 } 18493 18494 // extern(C) private __gshared int function(int) glXSwapIntervalSGI; // seems totally redundant to the tohers 18495 extern(C) private __gshared int function(int) glXSwapIntervalMESA; 18496 18497 void glxSetVSync (Display* dpy, /*GLXDrawable*/Drawable drawable, bool wait) { 18498 if (cast(void*)_glx_swapInterval_fn is cast(void*)1) return; 18499 if (_glx_swapInterval_fn is null) { 18500 _glx_swapInterval_fn = cast(glXSwapIntervalEXT)glXGetProcAddress("glXSwapIntervalEXT"); 18501 if (_glx_swapInterval_fn is null) { 18502 _glx_swapInterval_fn = cast(glXSwapIntervalEXT)1; 18503 return; 18504 } 18505 version(sdddd) { debug writeln("glXSwapIntervalEXT found!"); } 18506 } 18507 18508 if(glXSwapIntervalMESA is null) { 18509 // it seems to require both to actually take effect on many computers 18510 // idk why 18511 glXSwapIntervalMESA = cast(typeof(glXSwapIntervalMESA)) glXGetProcAddress("glXSwapIntervalMESA"); 18512 if(glXSwapIntervalMESA is null) 18513 glXSwapIntervalMESA = cast(typeof(glXSwapIntervalMESA)) 1; 18514 } 18515 18516 if(cast(void*) glXSwapIntervalMESA > cast(void*) 1) 18517 glXSwapIntervalMESA(wait ? 1 : 0); 18518 18519 _glx_swapInterval_fn(dpy, drawable, (wait ? 1 : 0)); 18520 } 18521 } else version(Windows) { 18522 static if (!SdpyIsUsingIVGLBinds) { 18523 enum GL_TRUE = 1; 18524 enum GL_FALSE = 0; 18525 18526 public void* glbindGetProcAddress (const(char)* name) { 18527 void* res = wglGetProcAddress(name); 18528 if (res is null) { 18529 /+ 18530 //{ import core.stdc.stdio; printf("GL: '%s' not found (0)\n", name); } 18531 import core.sys.windows.windef, core.sys.windows.winbase; 18532 __gshared HINSTANCE dll = null; 18533 if (dll is null) { 18534 dll = LoadLibraryA("opengl32.dll"); 18535 if (dll is null) return null; // <32, but idc 18536 } 18537 res = GetProcAddress(dll, name); 18538 +/ 18539 res = GetProcAddress(gl.libHandle, name); 18540 } 18541 //{ import core.stdc.stdio; printf(" GL: '%s' is 0x%08x\n", name, cast(uint)res); } 18542 return res; 18543 } 18544 } 18545 18546 18547 private __gshared extern(System) BOOL function(int) wglSwapIntervalEXT; 18548 void wglSetVSync(bool wait) { 18549 if(wglSwapIntervalEXT is null) { 18550 wglSwapIntervalEXT = cast(typeof(wglSwapIntervalEXT)) wglGetProcAddress("wglSwapIntervalEXT"); 18551 if(wglSwapIntervalEXT is null) 18552 wglSwapIntervalEXT = cast(typeof(wglSwapIntervalEXT)) 1; 18553 } 18554 if(cast(void*) wglSwapIntervalEXT is cast(void*) 1) 18555 return; 18556 18557 wglSwapIntervalEXT(wait ? 1 : 0); 18558 } 18559 18560 enum WGL_CONTEXT_MAJOR_VERSION_ARB = 0x2091; 18561 enum WGL_CONTEXT_MINOR_VERSION_ARB = 0x2092; 18562 enum WGL_CONTEXT_LAYER_PLANE_ARB = 0x2093; 18563 enum WGL_CONTEXT_FLAGS_ARB = 0x2094; 18564 enum WGL_CONTEXT_PROFILE_MASK_ARB = 0x9126; 18565 18566 enum WGL_CONTEXT_DEBUG_BIT_ARB = 0x0001; 18567 enum WGL_CONTEXT_FORWARD_COMPATIBLE_BIT_ARB = 0x0002; 18568 18569 enum WGL_CONTEXT_CORE_PROFILE_BIT_ARB = 0x00000001; 18570 enum WGL_CONTEXT_COMPATIBILITY_PROFILE_BIT_ARB = 0x00000002; 18571 18572 alias wglCreateContextAttribsARB_fna = HGLRC function (HDC hDC, HGLRC hShareContext, const(int)* attribList); 18573 __gshared wglCreateContextAttribsARB_fna wglCreateContextAttribsARB = null; 18574 18575 void wglInitOtherFunctions () { 18576 if (wglCreateContextAttribsARB is null) { 18577 wglCreateContextAttribsARB = cast(wglCreateContextAttribsARB_fna)glbindGetProcAddress("wglCreateContextAttribsARB"); 18578 } 18579 } 18580 } 18581 18582 static if (!SdpyIsUsingIVGLBinds) { 18583 18584 interface GL { 18585 extern(System) @nogc nothrow: 18586 18587 void glGetIntegerv(int, void*); 18588 void glMatrixMode(int); 18589 void glPushMatrix(); 18590 void glLoadIdentity(); 18591 void glOrtho(double, double, double, double, double, double); 18592 void glFrustum(double, double, double, double, double, double); 18593 18594 void glPopMatrix(); 18595 void glEnable(int); 18596 void glDisable(int); 18597 void glClear(int); 18598 void glBegin(int); 18599 void glVertex2f(float, float); 18600 void glVertex3f(float, float, float); 18601 void glEnd(); 18602 void glColor3b(byte, byte, byte); 18603 void glColor3ub(ubyte, ubyte, ubyte); 18604 void glColor4b(byte, byte, byte, byte); 18605 void glColor4ub(ubyte, ubyte, ubyte, ubyte); 18606 void glColor3i(int, int, int); 18607 void glColor3ui(uint, uint, uint); 18608 void glColor4i(int, int, int, int); 18609 void glColor4ui(uint, uint, uint, uint); 18610 void glColor3f(float, float, float); 18611 void glColor4f(float, float, float, float); 18612 void glTranslatef(float, float, float); 18613 void glScalef(float, float, float); 18614 version(X11) { 18615 void glSecondaryColor3b(byte, byte, byte); 18616 void glSecondaryColor3ub(ubyte, ubyte, ubyte); 18617 void glSecondaryColor3i(int, int, int); 18618 void glSecondaryColor3ui(uint, uint, uint); 18619 void glSecondaryColor3f(float, float, float); 18620 } 18621 18622 void glDrawElements(int, int, int, void*); 18623 18624 void glRotatef(float, float, float, float); 18625 18626 uint glGetError(); 18627 18628 void glDeleteTextures(int, uint*); 18629 18630 18631 void glRasterPos2i(int, int); 18632 void glDrawPixels(int, int, uint, uint, void*); 18633 void glClearColor(float, float, float, float); 18634 18635 18636 void glPixelStorei(uint, int); 18637 18638 void glGenTextures(uint, uint*); 18639 void glBindTexture(int, int); 18640 void glTexParameteri(uint, uint, int); 18641 void glTexParameterf(uint/*GLenum*/ target, uint/*GLenum*/ pname, float param); 18642 void glTexImage2D(int, int, int, int, int, int, int, int, scope const void*); 18643 void glTexSubImage2D(uint/*GLenum*/ target, int level, int xoffset, int yoffset, 18644 /*GLsizei*/int width, /*GLsizei*/int height, 18645 uint/*GLenum*/ format, uint/*GLenum*/ type, scope const void* pixels); 18646 void glTexEnvf(uint/*GLenum*/ target, uint/*GLenum*/ pname, float param); 18647 18648 void glLineWidth(int); 18649 18650 18651 void glTexCoord2f(float, float); 18652 void glVertex2i(int, int); 18653 void glBlendFunc (int, int); 18654 void glDepthFunc (int); 18655 void glViewport(int, int, int, int); 18656 18657 void glClearDepth(double); 18658 18659 void glReadBuffer(uint); 18660 void glReadPixels(int, int, int, int, int, int, void*); 18661 18662 void glScissor(GLint x, GLint y, GLsizei width, GLsizei height); 18663 18664 void glFlush(); 18665 void glFinish(); 18666 18667 version(Windows) { 18668 BOOL wglCopyContext(HGLRC, HGLRC, UINT); 18669 HGLRC wglCreateContext(HDC); 18670 HGLRC wglCreateLayerContext(HDC, int); 18671 BOOL wglDeleteContext(HGLRC); 18672 BOOL wglDescribeLayerPlane(HDC, int, int, UINT, LPLAYERPLANEDESCRIPTOR); 18673 HGLRC wglGetCurrentContext(); 18674 HDC wglGetCurrentDC(); 18675 int wglGetLayerPaletteEntries(HDC, int, int, int, COLORREF*); 18676 PROC wglGetProcAddress(LPCSTR); 18677 BOOL wglMakeCurrent(HDC, HGLRC); 18678 BOOL wglRealizeLayerPalette(HDC, int, BOOL); 18679 int wglSetLayerPaletteEntries(HDC, int, int, int, const(COLORREF)*); 18680 BOOL wglShareLists(HGLRC, HGLRC); 18681 BOOL wglSwapLayerBuffers(HDC, UINT); 18682 BOOL wglUseFontBitmapsA(HDC, DWORD, DWORD, DWORD); 18683 BOOL wglUseFontBitmapsW(HDC, DWORD, DWORD, DWORD); 18684 BOOL wglUseFontOutlinesA(HDC, DWORD, DWORD, DWORD, FLOAT, FLOAT, int, LPGLYPHMETRICSFLOAT); 18685 BOOL wglUseFontOutlinesW(HDC, DWORD, DWORD, DWORD, FLOAT, FLOAT, int, LPGLYPHMETRICSFLOAT); 18686 } 18687 18688 } 18689 18690 interface GL3 { 18691 extern(System) @nogc nothrow: 18692 18693 void glGenVertexArrays(GLsizei, GLuint*); 18694 void glBindVertexArray(GLuint); 18695 void glDeleteVertexArrays(GLsizei, const(GLuint)*); 18696 void glGenerateMipmap(GLenum); 18697 void glBufferSubData(GLenum, GLintptr, GLsizeiptr, const(GLvoid)*); 18698 void glStencilMask(GLuint); 18699 void glStencilFunc(GLenum, GLint, GLuint); 18700 void glGetShaderInfoLog(GLuint, GLsizei, GLsizei*, GLchar*); 18701 void glGetProgramInfoLog(GLuint, GLsizei, GLsizei*, GLchar*); 18702 GLuint glCreateProgram(); 18703 GLuint glCreateShader(GLenum); 18704 void glShaderSource(GLuint, GLsizei, const(GLchar*)*, const(GLint)*); 18705 void glCompileShader(GLuint); 18706 void glGetShaderiv(GLuint, GLenum, GLint*); 18707 void glAttachShader(GLuint, GLuint); 18708 void glBindAttribLocation(GLuint, GLuint, const(GLchar)*); 18709 void glLinkProgram(GLuint); 18710 void glGetProgramiv(GLuint, GLenum, GLint*); 18711 void glDeleteProgram(GLuint); 18712 void glDeleteShader(GLuint); 18713 GLint glGetUniformLocation(GLuint, const(GLchar)*); 18714 void glGenBuffers(GLsizei, GLuint*); 18715 18716 void glUniform1f(GLint location, GLfloat v0); 18717 void glUniform2f(GLint location, GLfloat v0, GLfloat v1); 18718 void glUniform3f(GLint location, GLfloat v0, GLfloat v1, GLfloat v2); 18719 void glUniform4f(GLint location, GLfloat v0, GLfloat v1, GLfloat v2, GLfloat v3); 18720 void glUniform1i(GLint location, GLint v0); 18721 void glUniform2i(GLint location, GLint v0, GLint v1); 18722 void glUniform3i(GLint location, GLint v0, GLint v1, GLint v2); 18723 void glUniform4i(GLint location, GLint v0, GLint v1, GLint v2, GLint v3); 18724 void glUniform1ui(GLint location, GLuint v0); 18725 void glUniform2ui(GLint location, GLuint v0, GLuint v1); 18726 void glUniform3ui(GLint location, GLuint v0, GLuint v1, GLuint v2); 18727 void glUniform4ui(GLint location, GLuint v0, GLuint v1, GLuint v2, GLuint v3); 18728 void glUniform1fv(GLint location, GLsizei count, const GLfloat *value); 18729 void glUniform2fv(GLint location, GLsizei count, const GLfloat *value); 18730 void glUniform3fv(GLint location, GLsizei count, const GLfloat *value); 18731 void glUniform4fv(GLint location, GLsizei count, const GLfloat *value); 18732 void glUniform1iv(GLint location, GLsizei count, const GLint *value); 18733 void glUniform2iv(GLint location, GLsizei count, const GLint *value); 18734 void glUniform3iv(GLint location, GLsizei count, const GLint *value); 18735 void glUniform4iv(GLint location, GLsizei count, const GLint *value); 18736 void glUniform1uiv(GLint location, GLsizei count, const GLuint *value); 18737 void glUniform2uiv(GLint location, GLsizei count, const GLuint *value); 18738 void glUniform3uiv(GLint location, GLsizei count, const GLuint *value); 18739 void glUniform4uiv(GLint location, GLsizei count, const GLuint *value); 18740 void glUniformMatrix2fv(GLint location, GLsizei count, GLboolean transpose, const GLfloat *value); 18741 void glUniformMatrix3fv(GLint location, GLsizei count, GLboolean transpose, const GLfloat *value); 18742 void glUniformMatrix4fv(GLint location, GLsizei count, GLboolean transpose, const GLfloat *value); 18743 void glUniformMatrix2x3fv(GLint location, GLsizei count, GLboolean transpose, const GLfloat *value); 18744 void glUniformMatrix3x2fv(GLint location, GLsizei count, GLboolean transpose, const GLfloat *value); 18745 void glUniformMatrix2x4fv(GLint location, GLsizei count, GLboolean transpose, const GLfloat *value); 18746 void glUniformMatrix4x2fv(GLint location, GLsizei count, GLboolean transpose, const GLfloat *value); 18747 void glUniformMatrix3x4fv(GLint location, GLsizei count, GLboolean transpose, const GLfloat *value); 18748 void glUniformMatrix4x3fv(GLint location, GLsizei count, GLboolean transpose, const GLfloat *value); 18749 18750 void glColorMask(GLboolean, GLboolean, GLboolean, GLboolean); 18751 void glStencilOpSeparate(GLenum, GLenum, GLenum, GLenum); 18752 void glDrawArrays(GLenum, GLint, GLsizei); 18753 void glStencilOp(GLenum, GLenum, GLenum); 18754 void glUseProgram(GLuint); 18755 void glCullFace(GLenum); 18756 void glFrontFace(GLenum); 18757 void glActiveTexture(GLenum); 18758 void glBindBuffer(GLenum, GLuint); 18759 void glBufferData(GLenum, GLsizeiptr, const(void)*, GLenum); 18760 void glEnableVertexAttribArray(GLuint); 18761 void glVertexAttribPointer(GLuint, GLint, GLenum, GLboolean, GLsizei, const(void)*); 18762 void glUniform1i(GLint, GLint); 18763 void glUniform2fv(GLint, GLsizei, const(GLfloat)*); 18764 void glDisableVertexAttribArray(GLuint); 18765 void glDeleteBuffers(GLsizei, const(GLuint)*); 18766 void glBlendFuncSeparate(GLenum, GLenum, GLenum, GLenum); 18767 void glLogicOp (GLenum opcode); 18768 void glFramebufferTexture2D (GLenum target, GLenum attachment, GLenum textarget, GLuint texture, GLint level); 18769 void glDeleteFramebuffers (GLsizei n, const(GLuint)* framebuffers); 18770 void glGenFramebuffers (GLsizei n, GLuint* framebuffers); 18771 GLenum glCheckFramebufferStatus (GLenum target); 18772 void glBindFramebuffer (GLenum target, GLuint framebuffer); 18773 } 18774 18775 interface GL4 { 18776 extern(System) @nogc nothrow: 18777 18778 void glTextureSubImage2D(uint texture, int level, int xoffset, int yoffset, 18779 /*GLsizei*/int width, /*GLsizei*/int height, 18780 uint/*GLenum*/ format, uint/*GLenum*/ type, scope const void* pixels); 18781 } 18782 18783 interface GLU { 18784 extern(System) @nogc nothrow: 18785 18786 void gluLookAt(double, double, double, double, double, double, double, double, double); 18787 void gluPerspective(double, double, double, double); 18788 18789 char* gluErrorString(uint); 18790 } 18791 18792 18793 enum GL_RED = 0x1903; 18794 enum GL_ALPHA = 0x1906; 18795 18796 enum uint GL_FRONT = 0x0404; 18797 18798 enum uint GL_BLEND = 0x0be2; 18799 enum uint GL_LEQUAL = 0x0203; 18800 18801 18802 enum uint GL_RGB = 0x1907; 18803 enum uint GL_BGRA = 0x80e1; 18804 enum uint GL_RGBA = 0x1908; 18805 enum uint GL_RGBA8 = 0x8058; 18806 enum uint GL_TEXTURE_2D = 0x0DE1; 18807 enum uint GL_TEXTURE_MIN_FILTER = 0x2801; 18808 enum uint GL_NEAREST = 0x2600; 18809 enum uint GL_LINEAR = 0x2601; 18810 enum uint GL_TEXTURE_MAG_FILTER = 0x2800; 18811 enum uint GL_TEXTURE_WRAP_S = 0x2802; 18812 enum uint GL_TEXTURE_WRAP_T = 0x2803; 18813 enum uint GL_REPEAT = 0x2901; 18814 enum uint GL_CLAMP = 0x2900; 18815 enum uint GL_CLAMP_TO_EDGE = 0x812F; 18816 enum uint GL_CLAMP_TO_BORDER = 0x812D; 18817 enum uint GL_DECAL = 0x2101; 18818 enum uint GL_MODULATE = 0x2100; 18819 enum uint GL_TEXTURE_ENV = 0x2300; 18820 enum uint GL_TEXTURE_ENV_MODE = 0x2200; 18821 enum uint GL_REPLACE = 0x1E01; 18822 enum uint GL_LIGHTING = 0x0B50; 18823 enum uint GL_DITHER = 0x0BD0; 18824 18825 enum uint GL_NO_ERROR = 0; 18826 18827 18828 18829 enum int GL_VIEWPORT = 0x0BA2; 18830 enum int GL_MODELVIEW = 0x1700; 18831 enum int GL_TEXTURE = 0x1702; 18832 enum int GL_PROJECTION = 0x1701; 18833 enum int GL_DEPTH_TEST = 0x0B71; 18834 18835 enum int GL_COLOR_BUFFER_BIT = 0x00004000; 18836 enum int GL_ACCUM_BUFFER_BIT = 0x00000200; 18837 enum int GL_DEPTH_BUFFER_BIT = 0x00000100; 18838 enum uint GL_STENCIL_BUFFER_BIT = 0x00000400; 18839 18840 enum int GL_POINTS = 0x0000; 18841 enum int GL_LINES = 0x0001; 18842 enum int GL_LINE_LOOP = 0x0002; 18843 enum int GL_LINE_STRIP = 0x0003; 18844 enum int GL_TRIANGLES = 0x0004; 18845 enum int GL_TRIANGLE_STRIP = 5; 18846 enum int GL_TRIANGLE_FAN = 6; 18847 enum int GL_QUADS = 7; 18848 enum int GL_QUAD_STRIP = 8; 18849 enum int GL_POLYGON = 9; 18850 18851 alias GLvoid = void; 18852 alias GLboolean = ubyte; 18853 alias GLint = int; 18854 alias GLuint = uint; 18855 alias GLenum = uint; 18856 alias GLchar = char; 18857 alias GLsizei = int; 18858 alias GLfloat = float; 18859 alias GLintptr = size_t; 18860 alias GLsizeiptr = ptrdiff_t; 18861 18862 18863 enum uint GL_INVALID_ENUM = 0x0500; 18864 18865 enum uint GL_ZERO = 0; 18866 enum uint GL_ONE = 1; 18867 18868 enum uint GL_BYTE = 0x1400; 18869 enum uint GL_UNSIGNED_BYTE = 0x1401; 18870 enum uint GL_SHORT = 0x1402; 18871 enum uint GL_UNSIGNED_SHORT = 0x1403; 18872 enum uint GL_INT = 0x1404; 18873 enum uint GL_UNSIGNED_INT = 0x1405; 18874 enum uint GL_FLOAT = 0x1406; 18875 enum uint GL_2_BYTES = 0x1407; 18876 enum uint GL_3_BYTES = 0x1408; 18877 enum uint GL_4_BYTES = 0x1409; 18878 enum uint GL_DOUBLE = 0x140A; 18879 18880 enum uint GL_STREAM_DRAW = 0x88E0; 18881 18882 enum uint GL_CCW = 0x0901; 18883 18884 enum uint GL_STENCIL_TEST = 0x0B90; 18885 enum uint GL_SCISSOR_TEST = 0x0C11; 18886 18887 enum uint GL_EQUAL = 0x0202; 18888 enum uint GL_NOTEQUAL = 0x0205; 18889 18890 enum uint GL_ALWAYS = 0x0207; 18891 enum uint GL_KEEP = 0x1E00; 18892 18893 enum uint GL_INCR = 0x1E02; 18894 18895 enum uint GL_INCR_WRAP = 0x8507; 18896 enum uint GL_DECR_WRAP = 0x8508; 18897 18898 enum uint GL_CULL_FACE = 0x0B44; 18899 enum uint GL_BACK = 0x0405; 18900 18901 enum uint GL_FRAGMENT_SHADER = 0x8B30; 18902 enum uint GL_VERTEX_SHADER = 0x8B31; 18903 18904 enum uint GL_COMPILE_STATUS = 0x8B81; 18905 enum uint GL_LINK_STATUS = 0x8B82; 18906 18907 enum uint GL_ELEMENT_ARRAY_BUFFER = 0x8893; 18908 18909 enum uint GL_STATIC_DRAW = 0x88E4; 18910 18911 enum uint GL_UNPACK_ALIGNMENT = 0x0CF5; 18912 enum uint GL_UNPACK_ROW_LENGTH = 0x0CF2; 18913 enum uint GL_UNPACK_SKIP_PIXELS = 0x0CF4; 18914 enum uint GL_UNPACK_SKIP_ROWS = 0x0CF3; 18915 18916 enum uint GL_GENERATE_MIPMAP = 0x8191; 18917 enum uint GL_LINEAR_MIPMAP_LINEAR = 0x2703; 18918 18919 enum uint GL_TEXTURE0 = 0x84C0U; 18920 enum uint GL_TEXTURE1 = 0x84C1U; 18921 18922 enum uint GL_ARRAY_BUFFER = 0x8892; 18923 18924 enum uint GL_SRC_COLOR = 0x0300; 18925 enum uint GL_ONE_MINUS_SRC_COLOR = 0x0301; 18926 enum uint GL_SRC_ALPHA = 0x0302; 18927 enum uint GL_ONE_MINUS_SRC_ALPHA = 0x0303; 18928 enum uint GL_DST_ALPHA = 0x0304; 18929 enum uint GL_ONE_MINUS_DST_ALPHA = 0x0305; 18930 enum uint GL_DST_COLOR = 0x0306; 18931 enum uint GL_ONE_MINUS_DST_COLOR = 0x0307; 18932 enum uint GL_SRC_ALPHA_SATURATE = 0x0308; 18933 18934 enum uint GL_INVERT = 0x150AU; 18935 18936 enum uint GL_DEPTH_STENCIL = 0x84F9U; 18937 enum uint GL_UNSIGNED_INT_24_8 = 0x84FAU; 18938 18939 enum uint GL_FRAMEBUFFER = 0x8D40U; 18940 enum uint GL_COLOR_ATTACHMENT0 = 0x8CE0U; 18941 enum uint GL_DEPTH_STENCIL_ATTACHMENT = 0x821AU; 18942 18943 enum uint GL_FRAMEBUFFER_COMPLETE = 0x8CD5U; 18944 enum uint GL_FRAMEBUFFER_INCOMPLETE_ATTACHMENT = 0x8CD6U; 18945 enum uint GL_FRAMEBUFFER_INCOMPLETE_MISSING_ATTACHMENT = 0x8CD7U; 18946 enum uint GL_FRAMEBUFFER_INCOMPLETE_DIMENSIONS = 0x8CD9U; 18947 enum uint GL_FRAMEBUFFER_UNSUPPORTED = 0x8CDDU; 18948 18949 enum uint GL_COLOR_LOGIC_OP = 0x0BF2U; 18950 enum uint GL_CLEAR = 0x1500U; 18951 enum uint GL_COPY = 0x1503U; 18952 enum uint GL_XOR = 0x1506U; 18953 18954 enum uint GL_FRAMEBUFFER_BINDING = 0x8CA6U; 18955 18956 enum uint GL_TEXTURE_LOD_BIAS = 0x8501; 18957 18958 } 18959 } 18960 18961 /++ 18962 History: 18963 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. 18964 +/ 18965 __gshared bool gluSuccessfullyLoaded = true; 18966 18967 version(without_opengl) {} else { 18968 static if(!SdpyIsUsingIVGLBinds) { 18969 version(Windows) { 18970 mixin DynamicLoad!(GL, "opengl32", 1, openGlLibrariesSuccessfullyLoaded) gl; 18971 mixin DynamicLoad!(GLU, "glu32", 1, gluSuccessfullyLoaded) glu; 18972 } else { 18973 mixin DynamicLoad!(GL, "GL", 1, openGlLibrariesSuccessfullyLoaded) gl; 18974 mixin DynamicLoad!(GLU, "GLU", 3, gluSuccessfullyLoaded) glu; 18975 } 18976 mixin DynamicLoadSupplementalOpenGL!(GL3) gl3; 18977 18978 18979 shared static this() { 18980 gl.loadDynamicLibrary(); 18981 18982 // FIXME: this is NOT actually required and should NOT fail if it is not loaded 18983 // unless those functions are actually used 18984 // go to mark b openGlLibrariesSuccessfullyLoaded = false; 18985 glu.loadDynamicLibrary(); 18986 } 18987 } 18988 } 18989 18990 /++ 18991 Convenience method for converting D arrays to opengl buffer data 18992 18993 I would LOVE to overload it with the original glBufferData, but D won't 18994 let me since glBufferData is a function pointer :( 18995 18996 Added: August 25, 2020 (version 8.5) 18997 +/ 18998 version(without_opengl) {} else 18999 void glBufferDataSlice(GLenum target, const(void[]) data, GLenum usage) { 19000 glBufferData(target, data.length, data.ptr, usage); 19001 } 19002 19003 /+ 19004 /++ 19005 A matrix for simple uses that easily integrates with [OpenGlShader]. 19006 19007 Might not be useful to you since it only as some simple functions and 19008 probably isn't that fast. 19009 19010 Note it uses an inline static array for its storage, so copying it 19011 may be expensive. 19012 +/ 19013 struct BasicMatrix(int columns, int rows, T = float) { 19014 import core.stdc.math; 19015 19016 T[columns * rows] data = 0.0; 19017 19018 /++ 19019 Basic operations that operate *in place*. 19020 +/ 19021 void translate() { 19022 19023 } 19024 19025 /// ditto 19026 void scale() { 19027 19028 } 19029 19030 /// ditto 19031 void rotate() { 19032 19033 } 19034 19035 /++ 19036 19037 +/ 19038 static if(columns == rows) 19039 static BasicMatrix identity() { 19040 BasicMatrix m; 19041 foreach(i; 0 .. columns) 19042 data[0 + i + i * columns] = 1.0; 19043 return m; 19044 } 19045 19046 static BasicMatrix ortho() { 19047 return BasicMatrix.init; 19048 } 19049 } 19050 +/ 19051 19052 /++ 19053 Convenience class for using opengl shaders. 19054 19055 Ensure that you've loaded opengl 3+ and set your active 19056 context before trying to use this. 19057 19058 Added: August 25, 2020 (version 8.5) 19059 +/ 19060 version(without_opengl) {} else 19061 final class OpenGlShader { 19062 private int shaderProgram_; 19063 private @property void shaderProgram(int a) { 19064 shaderProgram_ = a; 19065 } 19066 /// Get the program ID for use in OpenGL functions. 19067 public @property int shaderProgram() { 19068 return shaderProgram_; 19069 } 19070 19071 /++ 19072 19073 +/ 19074 static struct Source { 19075 uint type; /// GL_FRAGMENT_SHADER, GL_VERTEX_SHADER, etc. 19076 string code; /// 19077 } 19078 19079 /++ 19080 Helper method to just compile some shader code and check for errors 19081 while you do glCreateShader, etc. on the outside yourself. 19082 19083 This just does `glShaderSource` and `glCompileShader` for the given code. 19084 19085 If you the OpenGlShader class constructor, you never need to call this yourself. 19086 +/ 19087 static void compile(int sid, Source code) { 19088 const(char)*[1] buffer; 19089 int[1] lengthBuffer; 19090 19091 buffer[0] = code.code.ptr; 19092 lengthBuffer[0] = cast(int) code.code.length; 19093 19094 glShaderSource(sid, cast(int) buffer.length, buffer.ptr, lengthBuffer.ptr); 19095 glCompileShader(sid); 19096 19097 int success; 19098 glGetShaderiv(sid, GL_COMPILE_STATUS, &success); 19099 if(!success) { 19100 char[512] info; 19101 int len; 19102 glGetShaderInfoLog(sid, info.length, &len, info.ptr); 19103 19104 throw new Exception("Shader compile failure: " ~ cast(immutable) info[0 .. len]); 19105 } 19106 } 19107 19108 /++ 19109 Calls `glLinkProgram` and throws if error a occurs. 19110 19111 If you the OpenGlShader class constructor, you never need to call this yourself. 19112 +/ 19113 static void link(int shaderProgram) { 19114 glLinkProgram(shaderProgram); 19115 int success; 19116 glGetProgramiv(shaderProgram, GL_LINK_STATUS, &success); 19117 if(!success) { 19118 char[512] info; 19119 int len; 19120 glGetProgramInfoLog(shaderProgram, info.length, &len, info.ptr); 19121 19122 throw new Exception("Shader link failure: " ~ cast(immutable) info[0 .. len]); 19123 } 19124 } 19125 19126 /++ 19127 Constructs the shader object by calling `glCreateProgram`, then 19128 compiling each given [Source], and finally, linking them together. 19129 19130 Throws: on compile or link failure. 19131 +/ 19132 this(Source[] codes...) { 19133 shaderProgram = glCreateProgram(); 19134 19135 int[16] shadersBufferStack; 19136 19137 int[] shadersBuffer = codes.length <= shadersBufferStack.length ? 19138 shadersBufferStack[0 .. codes.length] : 19139 new int[](codes.length); 19140 19141 foreach(idx, code; codes) { 19142 shadersBuffer[idx] = glCreateShader(code.type); 19143 19144 compile(shadersBuffer[idx], code); 19145 19146 glAttachShader(shaderProgram, shadersBuffer[idx]); 19147 } 19148 19149 link(shaderProgram); 19150 19151 foreach(s; shadersBuffer) 19152 glDeleteShader(s); 19153 } 19154 19155 /// Calls `glUseProgram(this.shaderProgram)` 19156 void use() { 19157 glUseProgram(this.shaderProgram); 19158 } 19159 19160 /// Deletes the program. 19161 void delete_() { 19162 glDeleteProgram(shaderProgram); 19163 shaderProgram = 0; 19164 } 19165 19166 /++ 19167 [OpenGlShader.uniforms].name gives you one of these. 19168 19169 You can get the id out of it or just assign 19170 +/ 19171 static struct Uniform { 19172 /// the id passed to glUniform* 19173 int id; 19174 19175 /// Assigns the 4 floats. You will probably have to call this via the .opAssign name 19176 void opAssign(float x, float y, float z, float w) { 19177 if(id != -1) 19178 glUniform4f(id, x, y, z, w); 19179 } 19180 19181 void opAssign(float x) { 19182 if(id != -1) 19183 glUniform1f(id, x); 19184 } 19185 19186 void opAssign(float x, float y) { 19187 if(id != -1) 19188 glUniform2f(id, x, y); 19189 } 19190 19191 void opAssign(T)(T t) { 19192 t.glUniform(id); 19193 } 19194 } 19195 19196 static struct UniformsHelper { 19197 OpenGlShader _shader; 19198 19199 @property Uniform opDispatch(string name)() { 19200 auto i = glGetUniformLocation(_shader.shaderProgram, name.ptr); 19201 // FIXME: decide what to do here; the exception is liable to be swallowed by the event syste 19202 //if(i == -1) 19203 //throw new Exception("Could not find uniform " ~ name); 19204 return Uniform(i); 19205 } 19206 19207 @property void opDispatch(string name, T)(T t) { 19208 Uniform f = this.opDispatch!name; 19209 t.glUniform(f); 19210 } 19211 } 19212 19213 /++ 19214 Gives access to the uniforms through dot access. 19215 `OpenGlShader.Uniform = shader.uniforms.foo; // calls glGetUniformLocation(this, "foo"); 19216 +/ 19217 @property UniformsHelper uniforms() { return UniformsHelper(this); } 19218 } 19219 19220 version(without_opengl) {} else { 19221 /++ 19222 A static container of experimental types and value constructors for opengl 3+ shaders. 19223 19224 19225 You can declare variables like: 19226 19227 ``` 19228 OGL.vec3f something; 19229 ``` 19230 19231 But generally it would be used with [OpenGlShader]'s uniform helpers like 19232 19233 ``` 19234 shader.uniforms.mouse = OGL.vec(mouseX, mouseY); // or OGL.vec2f if you want to be more specific 19235 ``` 19236 19237 This is still extremely experimental, not very useful at this point, and thus subject to change at random. 19238 19239 19240 History: 19241 Added December 7, 2021. Not yet stable. 19242 +/ 19243 final class OGL { 19244 static: 19245 19246 private template typeFromSpecifier(string specifier) { 19247 static if(specifier == "f") 19248 alias typeFromSpecifier = GLfloat; 19249 else static if(specifier == "i") 19250 alias typeFromSpecifier = GLint; 19251 else static if(specifier == "ui") 19252 alias typeFromSpecifier = GLuint; 19253 else static assert(0, "I don't know this ogl type suffix " ~ specifier); 19254 } 19255 19256 private template CommonType(T...) { 19257 static if(T.length == 1) 19258 alias CommonType = T[0]; 19259 else static if(is(typeof(true ? T[0].init : T[1].init) C)) 19260 alias CommonType = CommonType!(C, T[2 .. $]); 19261 } 19262 19263 private template typesToSpecifier(T...) { 19264 static if(is(CommonType!T == float)) 19265 enum typesToSpecifier = "f"; 19266 else static if(is(CommonType!T == int)) 19267 enum typesToSpecifier = "i"; 19268 else static if(is(CommonType!T == uint)) 19269 enum typesToSpecifier = "ui"; 19270 else static assert(0, "I can't find a gl type suffix for common type " ~ CommonType!T.stringof); 19271 } 19272 19273 private template genNames(size_t dim, size_t dim2 = 0) { 19274 string helper() { 19275 string s; 19276 if(dim2) { 19277 s ~= "type["~(dim + '0')~"]["~(dim2 + '0')~"] matrix;"; 19278 } else { 19279 if(dim > 0) s ~= "type x = 0;"; 19280 if(dim > 1) s ~= "type y = 0;"; 19281 if(dim > 2) s ~= "type z = 0;"; 19282 if(dim > 3) s ~= "type w = 0;"; 19283 } 19284 return s; 19285 } 19286 19287 enum genNames = helper(); 19288 } 19289 19290 // there's vec, arrays of vec, mat, and arrays of mat 19291 template opDispatch(string name) 19292 if(name.length > 4 && (name[0 .. 3] == "vec" || name[0 .. 3] == "mat")) 19293 { 19294 static if(name[4] == 'x') { 19295 enum dimX = cast(int) (name[3] - '0'); 19296 static assert(dimX > 0 && dimX <= 4, "Bad dimension for OGL X type " ~ name[3]); 19297 19298 enum dimY = cast(int) (name[5] - '0'); 19299 static assert(dimY > 0 && dimY <= 4, "Bad dimension for OGL Y type " ~ name[5]); 19300 19301 enum isArray = name[$ - 1] == 'v'; 19302 enum typeSpecifier = isArray ? name[6 .. $ - 1] : name[6 .. $]; 19303 alias type = typeFromSpecifier!typeSpecifier; 19304 } else { 19305 enum dim = cast(int) (name[3] - '0'); 19306 static assert(dim > 0 && dim <= 4, "Bad dimension for OGL type " ~ name[3]); 19307 enum isArray = name[$ - 1] == 'v'; 19308 enum typeSpecifier = isArray ? name[4 .. $ - 1] : name[4 .. $]; 19309 alias type = typeFromSpecifier!typeSpecifier; 19310 } 19311 19312 align(1) 19313 struct opDispatch { 19314 align(1): 19315 static if(name[4] == 'x') 19316 mixin(genNames!(dimX, dimY)); 19317 else 19318 mixin(genNames!dim); 19319 19320 private void glUniform(OpenGlShader.Uniform assignTo) { 19321 glUniform(assignTo.id); 19322 } 19323 private void glUniform(int assignTo) { 19324 static if(name[4] == 'x') { 19325 // FIXME 19326 pragma(msg, "This matrix uniform helper has never been tested!!!!"); 19327 mixin("glUniformMatrix" ~ name[3 .. $] ~ "v")(assignTo, dimX * dimY, false, this.matrix.ptr); 19328 } else 19329 mixin("glUniform" ~ name[3 .. $])(assignTo, this.tupleof); 19330 } 19331 } 19332 } 19333 19334 auto vec(T...)(T members) { 19335 return typeof(this).opDispatch!("vec" ~ toInternal!string(cast(int) T.length)~ typesToSpecifier!T)(members); 19336 } 19337 } 19338 } 19339 19340 version(linux) { 19341 version(with_eventloop) {} else { 19342 private int epollFd = -1; 19343 void prepareEventLoop() { 19344 if(epollFd != -1) 19345 return; // already initialized, no need to do it again 19346 import ep = core.sys.linux.epoll; 19347 19348 epollFd = ep.epoll_create1(ep.EPOLL_CLOEXEC); 19349 if(epollFd == -1) 19350 throw new Exception("epoll create failure"); 19351 } 19352 } 19353 } else version(Posix) { 19354 void prepareEventLoop() {} 19355 } 19356 19357 version(X11) { 19358 import core.stdc.locale : LC_ALL; // rdmd fix 19359 __gshared bool sdx_isUTF8Locale; 19360 19361 // This whole crap is used to initialize X11 locale, so that you can use XIM methods later. 19362 // Yes, there are people with non-utf locale (it's me, Ketmar!), but XIM (composing) will 19363 // not work right if app/X11 locale is not utf. This sux. That's why all that "utf detection" 19364 // anal magic is here. I (Ketmar) hope you like it. 19365 // We will use `sdx_isUTF8Locale` on XIM creation to enforce UTF-8 locale, so XCompose will 19366 // always return correct unicode symbols. The detection is here 'cause user can change locale 19367 // later. 19368 19369 // NOTE: IT IS VERY IMPORTANT THAT THIS BE THE LAST STATIC CTOR OF THE FILE since it tests librariesSuccessfullyLoaded 19370 shared static this () { 19371 if(!librariesSuccessfullyLoaded) 19372 return; 19373 19374 import core.stdc.locale : setlocale, LC_ALL, LC_CTYPE; 19375 19376 // this doesn't hurt; it may add some locking, but the speed is still 19377 // allows doing 60 FPS videogames; also, ignore the result, as most 19378 // users will probably won't do mulththreaded X11 anyway (and I (ketmar) 19379 // never seen this failing). 19380 if (XInitThreads() == 0) { import core.stdc.stdio; fprintf(stderr, "XInitThreads() failed!\n"); } 19381 19382 setlocale(LC_ALL, ""); 19383 // check if out locale is UTF-8 19384 auto lct = setlocale(LC_CTYPE, null); 19385 if (lct is null) { 19386 sdx_isUTF8Locale = false; 19387 } else { 19388 for (size_t idx = 0; lct[idx] && lct[idx+1] && lct[idx+2]; ++idx) { 19389 if ((lct[idx+0] == 'u' || lct[idx+0] == 'U') && 19390 (lct[idx+1] == 't' || lct[idx+1] == 'T') && 19391 (lct[idx+2] == 'f' || lct[idx+2] == 'F')) 19392 { 19393 sdx_isUTF8Locale = true; 19394 break; 19395 } 19396 } 19397 } 19398 //{ import core.stdc.stdio : stderr, fprintf; fprintf(stderr, "UTF8: %s\n", sdx_isUTF8Locale ? "tan".ptr : "ona".ptr); } 19399 } 19400 } 19401 19402 class ExperimentalTextComponent2 { 19403 /+ 19404 Stage 1: get it working monospace 19405 Stage 2: use proportional font 19406 Stage 3: allow changes in inline style 19407 Stage 4: allow new fonts and sizes in the middle 19408 Stage 5: optimize gap buffer 19409 Stage 6: optimize layout 19410 Stage 7: word wrap 19411 Stage 8: justification 19412 Stage 9: editing, selection, etc. 19413 19414 Operations: 19415 insert text 19416 overstrike text 19417 select 19418 cut 19419 modify 19420 +/ 19421 19422 /++ 19423 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. 19424 +/ 19425 this(SimpleWindow window) { 19426 this.window = window; 19427 } 19428 19429 private SimpleWindow window; 19430 19431 19432 /++ 19433 When you render a [ComponentInFlow], it returns an arbitrary number of these interfaces 19434 representing the internal parts. The first pass is focused on the x parameter, then the 19435 renderer is responsible for going back to the parts in the current line and calling 19436 adjustDownForAscent to change the y params. 19437 +/ 19438 static interface ComponentRenderHelper { 19439 19440 /+ 19441 When you do an edit, possibly stuff on the same line previously need to move (to adjust 19442 the baseline), stuff subsequent needs to move (adjust x) and possibly stuff below needs 19443 to move (adjust y to make room for new line) until you get back to the same position, 19444 then you can stop - if one thing is unchanged, nothing after it is changed too. 19445 19446 Word wrap might change this as if can rewrap tons of stuff, but the same idea applies, 19447 once you reach something that is unchanged, you can stop. 19448 +/ 19449 19450 void adjustDownForAscent(int amount); // at the end of the line it needs to do these 19451 19452 int ascent() const; 19453 int descent() const; 19454 19455 int advance() const; 19456 19457 bool endsWithExplititLineBreak() const; 19458 } 19459 19460 static interface RenderResult { 19461 /++ 19462 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. 19463 +/ 19464 void popFront(); 19465 @property bool empty() const; 19466 @property ComponentRenderHelper front() const; 19467 19468 void repositionForNextLine(Point baseline, int availableWidth); 19469 } 19470 19471 static interface ComponentInFlow { 19472 void draw(ScreenPainter painter); 19473 //RenderResult render(Point baseline, int availableWidth); // FIXME: it needs to be able to say "my cache is good, nothing different" 19474 19475 bool startsWithExplicitLineBreak() const; 19476 } 19477 19478 static class TextFlowComponent : ComponentInFlow { 19479 bool startsWithExplicitLineBreak() const { return false; } // FIXME: if it is block this can return true 19480 19481 Color foreground; 19482 Color background; 19483 19484 OperatingSystemFont font; // should NEVER be null 19485 19486 ubyte attributes; // underline, strike through, display on new block 19487 19488 version(Windows) 19489 const(wchar)[] content; 19490 else 19491 const(char)[] content; // this should NEVER have a newline, except at the end 19492 19493 RenderedComponent[] rendered; // entirely controlled by [rerender] 19494 19495 // could prolly put some spacing around it too like margin / padding 19496 19497 this(Color f, Color b, OperatingSystemFont font, ubyte attr, const(char)[] c) 19498 in { assert(font !is null); 19499 assert(!font.isNull); } 19500 do 19501 { 19502 this.foreground = f; 19503 this.background = b; 19504 this.font = font; 19505 19506 this.attributes = attr; 19507 version(Windows) { 19508 auto conversionFlags = 0;//WindowsStringConversionFlags.convertNewLines; 19509 auto sz = sizeOfConvertedWstring(c, conversionFlags); 19510 auto buffer = new wchar[](sz); 19511 this.content = makeWindowsString(c, buffer, conversionFlags); 19512 } else { 19513 this.content = c.dup; 19514 } 19515 } 19516 19517 void draw(ScreenPainter painter) { 19518 painter.setFont(this.font); 19519 painter.outlineColor = this.foreground; 19520 painter.fillColor = Color.transparent; 19521 foreach(rendered; this.rendered) { 19522 // the component works in term of baseline, 19523 // but the painter works in term of upper left bounding box 19524 // so need to translate that 19525 19526 if(this.background.a) { 19527 painter.fillColor = this.background; 19528 painter.outlineColor = this.background; 19529 19530 painter.drawRectangle(Point(rendered.startX, rendered.startY - this.font.ascent), Size(rendered.width, this.font.height)); 19531 19532 painter.outlineColor = this.foreground; 19533 painter.fillColor = Color.transparent; 19534 } 19535 19536 painter.drawText(Point(rendered.startX, rendered.startY - this.font.ascent), rendered.slice); 19537 19538 // FIXME: strike through, underline, highlight selection, etc. 19539 } 19540 } 19541 } 19542 19543 // I could split the parts into words on render 19544 // for easier word-wrap, each one being an unbreakable "inline-block" 19545 private TextFlowComponent[] parts; 19546 private int needsRerenderFrom; 19547 19548 void addPart(Color f, Color b, OperatingSystemFont font, ubyte attr, const(char)[] c) { 19549 // FIXME: needsRerenderFrom. Basically if the bounding box and baseline is the same as the previous thing, it can prolly just stop. 19550 parts ~= new TextFlowComponent(f, b, font, attr, c); 19551 } 19552 19553 static struct RenderedComponent { 19554 int startX; 19555 int startY; 19556 short width; 19557 // 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! 19558 // for individual chars in here you've gotta process on demand 19559 version(Windows) 19560 const(wchar)[] slice; 19561 else 19562 const(char)[] slice; 19563 } 19564 19565 19566 void rerender(Rectangle boundingBox) { 19567 Point baseline = boundingBox.upperLeft; 19568 19569 this.boundingBox.left = boundingBox.left; 19570 this.boundingBox.top = boundingBox.top; 19571 19572 auto remainingParts = parts; 19573 19574 int largestX; 19575 19576 19577 foreach(part; parts) 19578 part.font.prepareContext(window); 19579 scope(exit) 19580 foreach(part; parts) 19581 part.font.releaseContext(); 19582 19583 calculateNextLine: 19584 19585 int nextLineHeight = 0; 19586 int nextBiggestDescent = 0; 19587 19588 foreach(part; remainingParts) { 19589 auto height = part.font.ascent; 19590 if(height > nextLineHeight) 19591 nextLineHeight = height; 19592 if(part.font.descent > nextBiggestDescent) 19593 nextBiggestDescent = part.font.descent; 19594 if(part.content.length && part.content[$-1] == '\n') 19595 break; 19596 } 19597 19598 baseline.y += nextLineHeight; 19599 auto lineStart = baseline; 19600 19601 while(remainingParts.length) { 19602 remainingParts[0].rendered = null; 19603 19604 bool eol; 19605 if(remainingParts[0].content.length && remainingParts[0].content[$-1] == '\n') 19606 eol = true; 19607 19608 // FIXME: word wrap 19609 auto font = remainingParts[0].font; 19610 auto slice = remainingParts[0].content[0 .. $ - (eol ? 1 : 0)]; 19611 auto width = font.stringWidth(slice, window); 19612 remainingParts[0].rendered ~= RenderedComponent(baseline.x, baseline.y, cast(short) width, slice); 19613 19614 remainingParts = remainingParts[1 .. $]; 19615 baseline.x += width; 19616 19617 if(eol) { 19618 baseline.y += nextBiggestDescent; 19619 if(baseline.x > largestX) 19620 largestX = baseline.x; 19621 baseline.x = lineStart.x; 19622 goto calculateNextLine; 19623 } 19624 } 19625 19626 if(baseline.x > largestX) 19627 largestX = baseline.x; 19628 19629 this.boundingBox.right = largestX; 19630 this.boundingBox.bottom = baseline.y; 19631 } 19632 19633 // you must call rerender first! 19634 void draw(ScreenPainter painter) { 19635 foreach(part; parts) { 19636 part.draw(painter); 19637 } 19638 } 19639 19640 struct IdentifyResult { 19641 TextFlowComponent part; 19642 int charIndexInPart; 19643 int totalCharIndex = -1; // if this is -1, it just means the end 19644 19645 Rectangle boundingBox; 19646 } 19647 19648 IdentifyResult identify(Point pt, bool exact = false) { 19649 if(parts.length == 0) 19650 return IdentifyResult(null, 0); 19651 19652 if(pt.y < boundingBox.top) { 19653 if(exact) 19654 return IdentifyResult(null, 1); 19655 return IdentifyResult(parts[0], 0); 19656 } 19657 if(pt.y > boundingBox.bottom) { 19658 if(exact) 19659 return IdentifyResult(null, 2); 19660 return IdentifyResult(parts[$-1], cast(int) parts[$-1].content.length); 19661 } 19662 19663 int tci = 0; 19664 19665 // I should probably like binary search this or something... 19666 foreach(ref part; parts) { 19667 foreach(rendered; part.rendered) { 19668 auto rect = Rectangle(rendered.startX, rendered.startY - part.font.ascent, rendered.startX + rendered.width, rendered.startY + part.font.descent); 19669 if(rect.contains(pt)) { 19670 auto x = pt.x - rendered.startX; 19671 auto estimatedIdx = x / part.font.averageWidth; 19672 19673 if(estimatedIdx < 0) 19674 estimatedIdx = 0; 19675 19676 if(estimatedIdx > rendered.slice.length) 19677 estimatedIdx = cast(int) rendered.slice.length; 19678 19679 int idx; 19680 int x1, x2; 19681 if(part.font.isMonospace) { 19682 auto w = part.font.averageWidth; 19683 if(!exact && x > (estimatedIdx + 1) * w) 19684 return IdentifyResult(null, 4); 19685 idx = estimatedIdx; 19686 x1 = idx * w; 19687 x2 = (idx + 1) * w; 19688 } else { 19689 idx = estimatedIdx; 19690 19691 part.font.prepareContext(window); 19692 scope(exit) part.font.releaseContext(); 19693 19694 // int iterations; 19695 19696 while(true) { 19697 // iterations++; 19698 x1 = idx ? part.font.stringWidth(rendered.slice[0 .. idx - 1]) : 0; 19699 x2 = part.font.stringWidth(rendered.slice[0 .. idx]); // should be the maximum since `averageWidth` kinda lies. 19700 19701 x1 += rendered.startX; 19702 x2 += rendered.startX; 19703 19704 if(pt.x < x1) { 19705 if(idx == 0) { 19706 if(exact) 19707 return IdentifyResult(null, 6); 19708 else 19709 break; 19710 } 19711 idx--; 19712 } else if(pt.x > x2) { 19713 idx++; 19714 if(idx > rendered.slice.length) { 19715 if(exact) 19716 return IdentifyResult(null, 5); 19717 else 19718 break; 19719 } 19720 } else if(pt.x >= x1 && pt.x <= x2) { 19721 if(idx) 19722 idx--; // point it at the original index 19723 break; // we fit 19724 } 19725 } 19726 19727 // writeln(iterations) 19728 } 19729 19730 19731 return IdentifyResult(part, idx, tci + idx, Rectangle(x1, rect.top, x2, rect.bottom)); // FIXME: utf-8? 19732 } 19733 } 19734 tci += cast(int) part.content.length; // FIXME: utf-8? 19735 } 19736 return IdentifyResult(null, 3); 19737 } 19738 19739 Rectangle boundingBox; // only set after [rerender] 19740 19741 // text will be positioned around the exclusion zone 19742 static struct ExclusionZone { 19743 19744 } 19745 19746 ExclusionZone[] exclusionZones; 19747 } 19748 19749 19750 // Don't use this yet. When I'm happy with it, I will move it to the 19751 // regular module namespace. 19752 mixin template ExperimentalTextComponent() { 19753 19754 static: 19755 19756 alias Rectangle = arsd.color.Rectangle; 19757 19758 struct ForegroundColor { 19759 Color color; 19760 alias color this; 19761 19762 this(Color c) { 19763 color = c; 19764 } 19765 19766 this(int r, int g, int b, int a = 255) { 19767 color = Color(r, g, b, a); 19768 } 19769 19770 static ForegroundColor opDispatch(string s)() if(__traits(compiles, ForegroundColor(mixin("Color." ~ s)))) { 19771 return ForegroundColor(mixin("Color." ~ s)); 19772 } 19773 } 19774 19775 struct BackgroundColor { 19776 Color color; 19777 alias color this; 19778 19779 this(Color c) { 19780 color = c; 19781 } 19782 19783 this(int r, int g, int b, int a = 255) { 19784 color = Color(r, g, b, a); 19785 } 19786 19787 static BackgroundColor opDispatch(string s)() if(__traits(compiles, BackgroundColor(mixin("Color." ~ s)))) { 19788 return BackgroundColor(mixin("Color." ~ s)); 19789 } 19790 } 19791 19792 static class InlineElement { 19793 string text; 19794 19795 BlockElement containingBlock; 19796 19797 Color color = Color.black; 19798 Color backgroundColor = Color.transparent; 19799 ushort styles; 19800 19801 string font; 19802 int fontSize; 19803 19804 int lineHeight; 19805 19806 void* identifier; 19807 19808 Rectangle boundingBox; 19809 int[] letterXs; // FIXME: maybe i should do bounding boxes for every character 19810 19811 bool isMergeCompatible(InlineElement other) { 19812 return 19813 containingBlock is other.containingBlock && 19814 color == other.color && 19815 backgroundColor == other.backgroundColor && 19816 styles == other.styles && 19817 font == other.font && 19818 fontSize == other.fontSize && 19819 lineHeight == other.lineHeight && 19820 true; 19821 } 19822 19823 int xOfIndex(size_t index) { 19824 if(index < letterXs.length) 19825 return letterXs[index]; 19826 else 19827 return boundingBox.right; 19828 } 19829 19830 InlineElement clone() { 19831 auto ie = new InlineElement(); 19832 ie.tupleof = this.tupleof; 19833 return ie; 19834 } 19835 19836 InlineElement getPreviousInlineElement() { 19837 InlineElement prev = null; 19838 foreach(ie; this.containingBlock.parts) { 19839 if(ie is this) 19840 break; 19841 prev = ie; 19842 } 19843 if(prev is null) { 19844 BlockElement pb; 19845 BlockElement cb = this.containingBlock; 19846 moar: 19847 foreach(ie; this.containingBlock.containingLayout.blocks) { 19848 if(ie is cb) 19849 break; 19850 pb = ie; 19851 } 19852 if(pb is null) 19853 return null; 19854 if(pb.parts.length == 0) { 19855 cb = pb; 19856 goto moar; 19857 } 19858 19859 prev = pb.parts[$-1]; 19860 19861 } 19862 return prev; 19863 } 19864 19865 InlineElement getNextInlineElement() { 19866 InlineElement next = null; 19867 foreach(idx, ie; this.containingBlock.parts) { 19868 if(ie is this) { 19869 if(idx + 1 < this.containingBlock.parts.length) 19870 next = this.containingBlock.parts[idx + 1]; 19871 break; 19872 } 19873 } 19874 if(next is null) { 19875 BlockElement n; 19876 foreach(idx, ie; this.containingBlock.containingLayout.blocks) { 19877 if(ie is this.containingBlock) { 19878 if(idx + 1 < this.containingBlock.containingLayout.blocks.length) 19879 n = this.containingBlock.containingLayout.blocks[idx + 1]; 19880 break; 19881 } 19882 } 19883 if(n is null) 19884 return null; 19885 19886 if(n.parts.length) 19887 next = n.parts[0]; 19888 else {} // FIXME 19889 19890 } 19891 return next; 19892 } 19893 19894 } 19895 19896 // Block elements are used entirely for positioning inline elements, 19897 // which are the things that are actually drawn. 19898 class BlockElement { 19899 InlineElement[] parts; 19900 uint alignment; 19901 19902 int whiteSpace; // pre, pre-wrap, wrap 19903 19904 TextLayout containingLayout; 19905 19906 // inputs 19907 Point where; 19908 Size minimumSize; 19909 Size maximumSize; 19910 Rectangle[] excludedBoxes; // like if you want it to write around a floated image or something. Coordinates are relative to the bounding box. 19911 void* identifier; 19912 19913 Rectangle margin; 19914 Rectangle padding; 19915 19916 // outputs 19917 Rectangle[] boundingBoxes; 19918 } 19919 19920 struct TextIdentifyResult { 19921 InlineElement element; 19922 int offset; 19923 19924 private TextIdentifyResult fixupNewline() { 19925 if(element !is null && offset < element.text.length && element.text[offset] == '\n') { 19926 offset--; 19927 } else if(element !is null && offset == element.text.length && element.text.length > 1 && element.text[$-1] == '\n') { 19928 offset--; 19929 } 19930 return this; 19931 } 19932 } 19933 19934 class TextLayout { 19935 BlockElement[] blocks; 19936 Rectangle boundingBox_; 19937 Rectangle boundingBox() { return boundingBox_; } 19938 void boundingBox(Rectangle r) { 19939 if(r != boundingBox_) { 19940 boundingBox_ = r; 19941 layoutInvalidated = true; 19942 } 19943 } 19944 19945 Rectangle contentBoundingBox() { 19946 Rectangle r; 19947 foreach(block; blocks) 19948 foreach(ie; block.parts) { 19949 if(ie.boundingBox.right > r.right) 19950 r.right = ie.boundingBox.right; 19951 if(ie.boundingBox.bottom > r.bottom) 19952 r.bottom = ie.boundingBox.bottom; 19953 } 19954 return r; 19955 } 19956 19957 BlockElement[] getBlocks() { 19958 return blocks; 19959 } 19960 19961 InlineElement[] getTexts() { 19962 InlineElement[] elements; 19963 foreach(block; blocks) 19964 elements ~= block.parts; 19965 return elements; 19966 } 19967 19968 string getPlainText() { 19969 string text; 19970 foreach(block; blocks) 19971 foreach(part; block.parts) 19972 text ~= part.text; 19973 return text; 19974 } 19975 19976 string getHtml() { 19977 return null; // FIXME 19978 } 19979 19980 this(Rectangle boundingBox) { 19981 this.boundingBox = boundingBox; 19982 } 19983 19984 BlockElement addBlock(InlineElement after = null, Rectangle margin = Rectangle(0, 0, 0, 0), Rectangle padding = Rectangle(0, 0, 0, 0)) { 19985 auto be = new BlockElement(); 19986 be.containingLayout = this; 19987 if(after is null) 19988 blocks ~= be; 19989 else { 19990 foreach(idx, b; blocks) { 19991 if(b is after.containingBlock) { 19992 blocks = blocks[0 .. idx + 1] ~ be ~ blocks[idx + 1 .. $]; 19993 break; 19994 } 19995 } 19996 } 19997 return be; 19998 } 19999 20000 void clear() { 20001 blocks = null; 20002 selectionStart = selectionEnd = caret = Caret.init; 20003 } 20004 20005 void addText(Args...)(Args args) { 20006 if(blocks.length == 0) 20007 addBlock(); 20008 20009 InlineElement ie = new InlineElement(); 20010 foreach(idx, arg; args) { 20011 static if(is(typeof(arg) == ForegroundColor)) 20012 ie.color = arg; 20013 else static if(is(typeof(arg) == TextFormat)) { 20014 if(arg & 0x8000) // ~TextFormat.something turns it off 20015 ie.styles &= arg; 20016 else 20017 ie.styles |= arg; 20018 } else static if(is(typeof(arg) == string)) { 20019 static if(idx == 0 && args.length > 1) 20020 static assert(0, "Put styles before the string."); 20021 size_t lastLineIndex; 20022 foreach(cidx, char a; arg) { 20023 if(a == '\n') { 20024 ie.text = arg[lastLineIndex .. cidx + 1]; 20025 lastLineIndex = cidx + 1; 20026 ie.containingBlock = blocks[$-1]; 20027 blocks[$-1].parts ~= ie.clone; 20028 ie.text = null; 20029 } else { 20030 20031 } 20032 } 20033 20034 ie.text = arg[lastLineIndex .. $]; 20035 ie.containingBlock = blocks[$-1]; 20036 blocks[$-1].parts ~= ie.clone; 20037 caret = Caret(this, blocks[$-1].parts[$-1], cast(int) blocks[$-1].parts[$-1].text.length); 20038 } 20039 } 20040 20041 invalidateLayout(); 20042 } 20043 20044 void tryMerge(InlineElement into, InlineElement what) { 20045 if(!into.isMergeCompatible(what)) { 20046 return; // cannot merge, different configs 20047 } 20048 20049 // cool, can merge, bring text together... 20050 into.text ~= what.text; 20051 20052 // and remove what 20053 for(size_t a = 0; a < what.containingBlock.parts.length; a++) { 20054 if(what.containingBlock.parts[a] is what) { 20055 for(size_t i = a; i < what.containingBlock.parts.length - 1; i++) 20056 what.containingBlock.parts[i] = what.containingBlock.parts[i + 1]; 20057 what.containingBlock.parts = what.containingBlock.parts[0 .. $-1]; 20058 20059 } 20060 } 20061 20062 // FIXME: ensure no other carets have a reference to it 20063 } 20064 20065 /// exact = true means return null if no match. otherwise, get the closest one that makes sense for a mouse click. 20066 TextIdentifyResult identify(int x, int y, bool exact = false) { 20067 TextIdentifyResult inexactMatch; 20068 foreach(block; blocks) { 20069 foreach(part; block.parts) { 20070 if(x >= part.boundingBox.left && x < part.boundingBox.right && y >= part.boundingBox.top && y < part.boundingBox.bottom) { 20071 20072 // FIXME binary search 20073 int tidx; 20074 int lastX; 20075 foreach_reverse(idxo, lx; part.letterXs) { 20076 int idx = cast(int) idxo; 20077 if(lx <= x) { 20078 if(lastX && lastX - x < x - lx) 20079 tidx = idx + 1; 20080 else 20081 tidx = idx; 20082 break; 20083 } 20084 lastX = lx; 20085 } 20086 20087 return TextIdentifyResult(part, tidx).fixupNewline; 20088 } else if(!exact) { 20089 // we're not in the box, but are we on the same line? 20090 if(y >= part.boundingBox.top && y < part.boundingBox.bottom) 20091 inexactMatch = TextIdentifyResult(part, x == 0 ? 0 : cast(int) part.text.length); 20092 } 20093 } 20094 } 20095 20096 if(!exact && inexactMatch is TextIdentifyResult.init && blocks.length && blocks[$-1].parts.length) 20097 return TextIdentifyResult(blocks[$-1].parts[$-1], cast(int) blocks[$-1].parts[$-1].text.length).fixupNewline; 20098 20099 return exact ? TextIdentifyResult.init : inexactMatch.fixupNewline; 20100 } 20101 20102 void moveCaretToPixelCoordinates(int x, int y) { 20103 auto result = identify(x, y); 20104 caret.inlineElement = result.element; 20105 caret.offset = result.offset; 20106 } 20107 20108 void selectToPixelCoordinates(int x, int y) { 20109 auto result = identify(x, y); 20110 20111 if(y < caretLastDrawnY1) { 20112 // on a previous line, carat is selectionEnd 20113 selectionEnd = caret; 20114 20115 selectionStart = Caret(this, result.element, result.offset); 20116 } else if(y > caretLastDrawnY2) { 20117 // on a later line 20118 selectionStart = caret; 20119 20120 selectionEnd = Caret(this, result.element, result.offset); 20121 } else { 20122 // on the same line... 20123 if(x <= caretLastDrawnX) { 20124 selectionEnd = caret; 20125 selectionStart = Caret(this, result.element, result.offset); 20126 } else { 20127 selectionStart = caret; 20128 selectionEnd = Caret(this, result.element, result.offset); 20129 } 20130 20131 } 20132 } 20133 20134 20135 /// Call this if the inputs change. It will reflow everything 20136 void redoLayout(ScreenPainter painter) { 20137 //painter.setClipRectangle(boundingBox); 20138 auto pos = Point(boundingBox.left, boundingBox.top); 20139 20140 int lastHeight; 20141 void nl() { 20142 pos.x = boundingBox.left; 20143 pos.y += lastHeight; 20144 } 20145 foreach(block; blocks) { 20146 nl(); 20147 foreach(part; block.parts) { 20148 part.letterXs = null; 20149 20150 auto size = painter.textSize(part.text); 20151 version(Windows) 20152 if(part.text.length && part.text[$-1] == '\n') 20153 size.height /= 2; // windows counts the new line at the end, but we don't want that 20154 20155 part.boundingBox = Rectangle(pos.x, pos.y, pos.x + size.width, pos.y + size.height); 20156 20157 foreach(idx, char c; part.text) { 20158 // FIXME: unicode 20159 part.letterXs ~= painter.textSize(part.text[0 .. idx]).width + pos.x; 20160 } 20161 20162 pos.x += size.width; 20163 if(pos.x >= boundingBox.right) { 20164 pos.y += size.height; 20165 pos.x = boundingBox.left; 20166 lastHeight = 0; 20167 } else { 20168 lastHeight = size.height; 20169 } 20170 20171 if(part.text.length && part.text[$-1] == '\n') 20172 nl(); 20173 } 20174 } 20175 20176 layoutInvalidated = false; 20177 } 20178 20179 bool layoutInvalidated = true; 20180 void invalidateLayout() { 20181 layoutInvalidated = true; 20182 } 20183 20184 // FIXME: caret can remain sometimes when inserting 20185 // FIXME: inserting at the beginning once you already have something can eff it up. 20186 void drawInto(ScreenPainter painter, bool focused = false) { 20187 if(layoutInvalidated) 20188 redoLayout(painter); 20189 foreach(block; blocks) { 20190 foreach(part; block.parts) { 20191 painter.outlineColor = part.color; 20192 painter.fillColor = part.backgroundColor; 20193 20194 auto pos = part.boundingBox.upperLeft; 20195 auto size = part.boundingBox.size; 20196 20197 painter.drawText(pos, part.text); 20198 if(part.styles & TextFormat.underline) 20199 painter.drawLine(Point(pos.x, pos.y + size.height - 4), Point(pos.x + size.width, pos.y + size.height - 4)); 20200 if(part.styles & TextFormat.strikethrough) 20201 painter.drawLine(Point(pos.x, pos.y + size.height/2), Point(pos.x + size.width, pos.y + size.height/2)); 20202 } 20203 } 20204 20205 // on every redraw, I will force the caret to be 20206 // redrawn too, in order to eliminate perceived lag 20207 // when moving around with the mouse. 20208 eraseCaret(painter); 20209 20210 if(focused) { 20211 highlightSelection(painter); 20212 drawCaret(painter); 20213 } 20214 } 20215 20216 Color selectionXorColor = Color(255, 255, 127); 20217 20218 void highlightSelection(ScreenPainter painter) { 20219 if(selectionStart is selectionEnd) 20220 return; // no selection 20221 20222 if(selectionStart.inlineElement is null) return; 20223 if(selectionEnd.inlineElement is null) return; 20224 20225 assert(selectionStart.inlineElement !is null); 20226 assert(selectionEnd.inlineElement !is null); 20227 20228 painter.rasterOp = RasterOp.xor; 20229 painter.outlineColor = Color.transparent; 20230 painter.fillColor = selectionXorColor; 20231 20232 auto at = selectionStart.inlineElement; 20233 auto atOffset = selectionStart.offset; 20234 bool done; 20235 while(at) { 20236 auto box = at.boundingBox; 20237 if(atOffset < at.letterXs.length) 20238 box.left = at.letterXs[atOffset]; 20239 20240 if(at is selectionEnd.inlineElement) { 20241 if(selectionEnd.offset < at.letterXs.length) 20242 box.right = at.letterXs[selectionEnd.offset]; 20243 done = true; 20244 } 20245 20246 painter.drawRectangle(box.upperLeft, box.width, box.height); 20247 20248 if(done) 20249 break; 20250 20251 at = at.getNextInlineElement(); 20252 atOffset = 0; 20253 } 20254 } 20255 20256 int caretLastDrawnX, caretLastDrawnY1, caretLastDrawnY2; 20257 bool caretShowingOnScreen = false; 20258 void drawCaret(ScreenPainter painter) { 20259 //painter.setClipRectangle(boundingBox); 20260 int x, y1, y2; 20261 if(caret.inlineElement is null) { 20262 x = boundingBox.left; 20263 y1 = boundingBox.top + 2; 20264 y2 = boundingBox.top + painter.fontHeight; 20265 } else { 20266 x = caret.inlineElement.xOfIndex(caret.offset); 20267 y1 = caret.inlineElement.boundingBox.top + 2; 20268 y2 = caret.inlineElement.boundingBox.bottom - 2; 20269 } 20270 20271 if(caretShowingOnScreen && (x != caretLastDrawnX || y1 != caretLastDrawnY1 || y2 != caretLastDrawnY2)) 20272 eraseCaret(painter); 20273 20274 painter.pen = Pen(Color.white, 1); 20275 painter.rasterOp = RasterOp.xor; 20276 painter.drawLine( 20277 Point(x, y1), 20278 Point(x, y2) 20279 ); 20280 painter.rasterOp = RasterOp.normal; 20281 caretShowingOnScreen = !caretShowingOnScreen; 20282 20283 if(caretShowingOnScreen) { 20284 caretLastDrawnX = x; 20285 caretLastDrawnY1 = y1; 20286 caretLastDrawnY2 = y2; 20287 } 20288 } 20289 20290 Rectangle caretBoundingBox() { 20291 int x, y1, y2; 20292 if(caret.inlineElement is null) { 20293 x = boundingBox.left; 20294 y1 = boundingBox.top + 2; 20295 y2 = boundingBox.top + 16; 20296 } else { 20297 x = caret.inlineElement.xOfIndex(caret.offset); 20298 y1 = caret.inlineElement.boundingBox.top + 2; 20299 y2 = caret.inlineElement.boundingBox.bottom - 2; 20300 } 20301 20302 return Rectangle(x, y1, x + 1, y2); 20303 } 20304 20305 void eraseCaret(ScreenPainter painter) { 20306 //painter.setClipRectangle(boundingBox); 20307 if(!caretShowingOnScreen) return; 20308 painter.pen = Pen(Color.white, 1); 20309 painter.rasterOp = RasterOp.xor; 20310 painter.drawLine( 20311 Point(caretLastDrawnX, caretLastDrawnY1), 20312 Point(caretLastDrawnX, caretLastDrawnY2) 20313 ); 20314 20315 caretShowingOnScreen = false; 20316 painter.rasterOp = RasterOp.normal; 20317 } 20318 20319 /// Caret movement api 20320 /// These should give the user a logical result based on what they see on screen... 20321 /// thus they locate predominately by *pixels* not char index. (These will generally coincide with monospace fonts tho!) 20322 void moveUp() { 20323 if(caret.inlineElement is null) return; 20324 auto x = caret.inlineElement.xOfIndex(caret.offset); 20325 auto y = caret.inlineElement.boundingBox.top + 2; 20326 20327 y -= caret.inlineElement.boundingBox.bottom - caret.inlineElement.boundingBox.top; 20328 if(y < 0) 20329 return; 20330 20331 auto i = identify(x, y); 20332 20333 if(i.element) { 20334 caret.inlineElement = i.element; 20335 caret.offset = i.offset; 20336 } 20337 } 20338 void moveDown() { 20339 if(caret.inlineElement is null) return; 20340 auto x = caret.inlineElement.xOfIndex(caret.offset); 20341 auto y = caret.inlineElement.boundingBox.bottom - 2; 20342 20343 y += caret.inlineElement.boundingBox.bottom - caret.inlineElement.boundingBox.top; 20344 20345 auto i = identify(x, y); 20346 if(i.element) { 20347 caret.inlineElement = i.element; 20348 caret.offset = i.offset; 20349 } 20350 } 20351 void moveLeft() { 20352 if(caret.inlineElement is null) return; 20353 if(caret.offset) 20354 caret.offset--; 20355 else { 20356 auto p = caret.inlineElement.getPreviousInlineElement(); 20357 if(p) { 20358 caret.inlineElement = p; 20359 if(p.text.length && p.text[$-1] == '\n') 20360 caret.offset = cast(int) p.text.length - 1; 20361 else 20362 caret.offset = cast(int) p.text.length; 20363 } 20364 } 20365 } 20366 void moveRight() { 20367 if(caret.inlineElement is null) return; 20368 if(caret.offset < caret.inlineElement.text.length && caret.inlineElement.text[caret.offset] != '\n') { 20369 caret.offset++; 20370 } else { 20371 auto p = caret.inlineElement.getNextInlineElement(); 20372 if(p) { 20373 caret.inlineElement = p; 20374 caret.offset = 0; 20375 } 20376 } 20377 } 20378 void moveHome() { 20379 if(caret.inlineElement is null) return; 20380 auto x = 0; 20381 auto y = caret.inlineElement.boundingBox.top + 2; 20382 20383 auto i = identify(x, y); 20384 20385 if(i.element) { 20386 caret.inlineElement = i.element; 20387 caret.offset = i.offset; 20388 } 20389 } 20390 void moveEnd() { 20391 if(caret.inlineElement is null) return; 20392 auto x = int.max; 20393 auto y = caret.inlineElement.boundingBox.top + 2; 20394 20395 auto i = identify(x, y); 20396 20397 if(i.element) { 20398 caret.inlineElement = i.element; 20399 caret.offset = i.offset; 20400 } 20401 20402 } 20403 void movePageUp(ref Caret caret) {} 20404 void movePageDown(ref Caret caret) {} 20405 20406 void moveDocumentStart(ref Caret caret) { 20407 if(blocks.length && blocks[0].parts.length) 20408 caret = Caret(this, blocks[0].parts[0], 0); 20409 else 20410 caret = Caret.init; 20411 } 20412 20413 void moveDocumentEnd(ref Caret caret) { 20414 if(blocks.length) { 20415 auto parts = blocks[$-1].parts; 20416 if(parts.length) { 20417 caret = Caret(this, parts[$-1], cast(int) parts[$-1].text.length); 20418 } else { 20419 caret = Caret.init; 20420 } 20421 } else 20422 caret = Caret.init; 20423 } 20424 20425 void deleteSelection() { 20426 if(selectionStart is selectionEnd) 20427 return; 20428 20429 if(selectionStart.inlineElement is null) return; 20430 if(selectionEnd.inlineElement is null) return; 20431 20432 assert(selectionStart.inlineElement !is null); 20433 assert(selectionEnd.inlineElement !is null); 20434 20435 auto at = selectionStart.inlineElement; 20436 20437 if(selectionEnd.inlineElement is at) { 20438 // same element, need to chop out 20439 at.text = at.text[0 .. selectionStart.offset] ~ at.text[selectionEnd.offset .. $]; 20440 at.letterXs = at.letterXs[0 .. selectionStart.offset] ~ at.letterXs[selectionEnd.offset .. $]; 20441 selectionEnd.offset -= selectionEnd.offset - selectionStart.offset; 20442 } else { 20443 // different elements, we can do it with slicing 20444 at.text = at.text[0 .. selectionStart.offset]; 20445 if(selectionStart.offset < at.letterXs.length) 20446 at.letterXs = at.letterXs[0 .. selectionStart.offset]; 20447 20448 at = at.getNextInlineElement(); 20449 20450 while(at) { 20451 if(at is selectionEnd.inlineElement) { 20452 at.text = at.text[selectionEnd.offset .. $]; 20453 if(selectionEnd.offset < at.letterXs.length) 20454 at.letterXs = at.letterXs[selectionEnd.offset .. $]; 20455 selectionEnd.offset = 0; 20456 break; 20457 } else { 20458 auto cfd = at; 20459 cfd.text = null; // delete the whole thing 20460 20461 at = at.getNextInlineElement(); 20462 20463 if(cfd.text.length == 0) { 20464 // and remove cfd 20465 for(size_t a = 0; a < cfd.containingBlock.parts.length; a++) { 20466 if(cfd.containingBlock.parts[a] is cfd) { 20467 for(size_t i = a; i < cfd.containingBlock.parts.length - 1; i++) 20468 cfd.containingBlock.parts[i] = cfd.containingBlock.parts[i + 1]; 20469 cfd.containingBlock.parts = cfd.containingBlock.parts[0 .. $-1]; 20470 20471 } 20472 } 20473 } 20474 } 20475 } 20476 } 20477 20478 caret = selectionEnd; 20479 selectNone(); 20480 20481 invalidateLayout(); 20482 20483 } 20484 20485 /// Plain text editing api. These work at the current caret inside the selected inline element. 20486 void insert(in char[] text) { 20487 foreach(dchar ch; text) 20488 insert(ch); 20489 } 20490 /// ditto 20491 void insert(dchar ch) { 20492 20493 bool selectionDeleted = false; 20494 if(selectionStart !is selectionEnd) { 20495 deleteSelection(); 20496 selectionDeleted = true; 20497 } 20498 20499 if(ch == 127) { 20500 delete_(); 20501 return; 20502 } 20503 if(ch == 8) { 20504 if(!selectionDeleted) 20505 backspace(); 20506 return; 20507 } 20508 20509 invalidateLayout(); 20510 20511 if(ch == 13) ch = 10; 20512 auto e = caret.inlineElement; 20513 if(e is null) { 20514 addText("" ~ cast(char) ch) ; // FIXME 20515 return; 20516 } 20517 20518 if(caret.offset == e.text.length) { 20519 e.text ~= cast(char) ch; // FIXME 20520 caret.offset++; 20521 if(ch == 10) { 20522 auto c = caret.inlineElement.clone; 20523 c.text = null; 20524 c.letterXs = null; 20525 insertPartAfter(c,e); 20526 caret = Caret(this, c, 0); 20527 } 20528 } else { 20529 // FIXME cast char sucks 20530 if(ch == 10) { 20531 auto c = caret.inlineElement.clone; 20532 c.text = e.text[caret.offset .. $]; 20533 if(caret.offset < c.letterXs.length) 20534 c.letterXs = e.letterXs[caret.offset .. $]; // FIXME boundingBox 20535 e.text = e.text[0 .. caret.offset] ~ cast(char) ch; 20536 if(caret.offset <= e.letterXs.length) { 20537 e.letterXs = e.letterXs[0 .. caret.offset] ~ 0; // FIXME bounding box 20538 } 20539 insertPartAfter(c,e); 20540 caret = Caret(this, c, 0); 20541 } else { 20542 e.text = e.text[0 .. caret.offset] ~ cast(char) ch ~ e.text[caret.offset .. $]; 20543 caret.offset++; 20544 } 20545 } 20546 } 20547 20548 void insertPartAfter(InlineElement what, InlineElement where) { 20549 foreach(idx, p; where.containingBlock.parts) { 20550 if(p is where) { 20551 if(idx + 1 == where.containingBlock.parts.length) 20552 where.containingBlock.parts ~= what; 20553 else 20554 where.containingBlock.parts = where.containingBlock.parts[0 .. idx + 1] ~ what ~ where.containingBlock.parts[idx + 1 .. $]; 20555 return; 20556 } 20557 } 20558 } 20559 20560 void cleanupStructures() { 20561 for(size_t i = 0; i < blocks.length; i++) { 20562 auto block = blocks[i]; 20563 for(size_t a = 0; a < block.parts.length; a++) { 20564 auto part = block.parts[a]; 20565 if(part.text.length == 0) { 20566 for(size_t b = a; b < block.parts.length - 1; b++) 20567 block.parts[b] = block.parts[b+1]; 20568 block.parts = block.parts[0 .. $-1]; 20569 } 20570 } 20571 if(block.parts.length == 0) { 20572 for(size_t a = i; a < blocks.length - 1; a++) 20573 blocks[a] = blocks[a+1]; 20574 blocks = blocks[0 .. $-1]; 20575 } 20576 } 20577 } 20578 20579 void backspace() { 20580 try_again: 20581 auto e = caret.inlineElement; 20582 if(e is null) 20583 return; 20584 if(caret.offset == 0) { 20585 auto prev = e.getPreviousInlineElement(); 20586 if(prev is null) 20587 return; 20588 auto newOffset = cast(int) prev.text.length; 20589 tryMerge(prev, e); 20590 caret.inlineElement = prev; 20591 caret.offset = prev is null ? 0 : newOffset; 20592 20593 goto try_again; 20594 } else if(caret.offset == e.text.length) { 20595 e.text = e.text[0 .. $-1]; 20596 caret.offset--; 20597 } else { 20598 e.text = e.text[0 .. caret.offset - 1] ~ e.text[caret.offset .. $]; 20599 caret.offset--; 20600 } 20601 //cleanupStructures(); 20602 20603 invalidateLayout(); 20604 } 20605 void delete_() { 20606 if(selectionStart !is selectionEnd) 20607 deleteSelection(); 20608 else { 20609 auto before = caret; 20610 moveRight(); 20611 if(caret != before) { 20612 backspace(); 20613 } 20614 } 20615 20616 invalidateLayout(); 20617 } 20618 void overstrike() {} 20619 20620 /// Selection API. See also: caret movement. 20621 void selectAll() { 20622 moveDocumentStart(selectionStart); 20623 moveDocumentEnd(selectionEnd); 20624 } 20625 bool selectNone() { 20626 if(selectionStart != selectionEnd) { 20627 selectionStart = selectionEnd = Caret.init; 20628 return true; 20629 } 20630 return false; 20631 } 20632 20633 /// Rich text editing api. These allow you to manipulate the meta data of the current element and add new elements. 20634 /// They will modify the current selection if there is one and will splice one in if needed. 20635 void changeAttributes() {} 20636 20637 20638 /// Text search api. They manipulate the selection and/or caret. 20639 void findText(string text) {} 20640 void findIndex(size_t textIndex) {} 20641 20642 // sample event handlers 20643 20644 void handleEvent(KeyEvent event) { 20645 //if(event.type == KeyEvent.Type.KeyPressed) { 20646 20647 //} 20648 } 20649 20650 void handleEvent(dchar ch) { 20651 20652 } 20653 20654 void handleEvent(MouseEvent event) { 20655 20656 } 20657 20658 bool contentEditable; // can it be edited? 20659 bool contentCaretable; // is there a caret/cursor that moves around in there? 20660 bool contentSelectable; // selectable? 20661 20662 Caret caret; 20663 Caret selectionStart; 20664 Caret selectionEnd; 20665 20666 bool insertMode; 20667 } 20668 20669 struct Caret { 20670 TextLayout layout; 20671 InlineElement inlineElement; 20672 int offset; 20673 } 20674 20675 enum TextFormat : ushort { 20676 // decorations 20677 underline = 1, 20678 strikethrough = 2, 20679 20680 // font selectors 20681 20682 bold = 0x4000 | 1, // weight 700 20683 light = 0x4000 | 2, // weight 300 20684 veryBoldOrLight = 0x4000 | 4, // weight 100 with light, weight 900 with bold 20685 // bold | light is really invalid but should give weight 500 20686 // veryBoldOrLight without one of the others should just give the default for the font; it should be ignored. 20687 20688 italic = 0x4000 | 8, 20689 smallcaps = 0x4000 | 16, 20690 } 20691 20692 void* findFont(string family, int weight, TextFormat formats) { 20693 return null; 20694 } 20695 20696 } 20697 20698 /++ 20699 $(PITFALL This is not yet stable and may break in future versions without notice.) 20700 20701 History: 20702 Added February 19, 2021 20703 +/ 20704 /// Group: drag_and_drop 20705 interface DropHandler { 20706 /++ 20707 Called when the drag enters the handler's area. 20708 +/ 20709 DragAndDropAction dragEnter(DropPackage*); 20710 /++ 20711 Called when the drag leaves the handler's area or is 20712 cancelled. You should free your resources when this is called. 20713 +/ 20714 void dragLeave(); 20715 /++ 20716 Called continually as the drag moves over the handler's area. 20717 20718 Returns: feedback to the dragger 20719 +/ 20720 DropParameters dragOver(Point pt); 20721 /++ 20722 The user dropped the data and you should process it now. You can 20723 access the data through the given [DropPackage]. 20724 +/ 20725 void drop(scope DropPackage*); 20726 /++ 20727 Called when the drop is complete. You should free whatever temporary 20728 resources you were using. It is often reasonable to simply forward 20729 this call to [dragLeave]. 20730 +/ 20731 void finish(); 20732 20733 /++ 20734 Parameters returned by [DropHandler.drop]. 20735 +/ 20736 static struct DropParameters { 20737 /++ 20738 Acceptable action over this area. 20739 +/ 20740 DragAndDropAction action; 20741 /++ 20742 Rectangle, in client coordinates, where the dragger can expect the same result during this drag session and thus need not ask again. 20743 20744 If you leave this as Rectangle.init, the dragger will continue to ask and this can waste resources. 20745 +/ 20746 Rectangle consistentWithin; 20747 } 20748 } 20749 20750 /++ 20751 History: 20752 Added February 19, 2021 20753 +/ 20754 /// Group: drag_and_drop 20755 enum DragAndDropAction { 20756 none = 0, 20757 copy, 20758 move, 20759 link, 20760 ask, 20761 custom 20762 } 20763 20764 /++ 20765 An opaque structure representing dropped data. It contains 20766 private, platform-specific data that your `drop` function 20767 should simply forward. 20768 20769 $(PITFALL This is not yet stable and may break in future versions without notice.) 20770 20771 History: 20772 Added February 19, 2021 20773 +/ 20774 /// Group: drag_and_drop 20775 struct DropPackage { 20776 /++ 20777 Lists the available formats as magic numbers. You should compare these 20778 against looked-up formats (see [DraggableData.getFormatId]) you know you support and can 20779 understand the passed data. 20780 +/ 20781 DraggableData.FormatId[] availableFormats() { 20782 version(X11) { 20783 return xFormats; 20784 } else version(Windows) { 20785 if(pDataObj is null) 20786 return null; 20787 20788 typeof(return) ret; 20789 20790 IEnumFORMATETC ef; 20791 if(pDataObj.EnumFormatEtc(DATADIR.DATADIR_GET, &ef) == S_OK) { 20792 FORMATETC fmt; 20793 ULONG fetched; 20794 while(ef.Next(1, &fmt, &fetched) == S_OK) { 20795 if(fetched == 0) 20796 break; 20797 20798 if(fmt.lindex != -1) 20799 continue; 20800 if(fmt.dwAspect != DVASPECT.DVASPECT_CONTENT) 20801 continue; 20802 if(!(fmt.tymed & TYMED.TYMED_HGLOBAL)) 20803 continue; 20804 20805 ret ~= fmt.cfFormat; 20806 } 20807 } 20808 20809 return ret; 20810 } else throw new NotYetImplementedException(); 20811 } 20812 20813 /++ 20814 Gets data from the drop and optionally accepts it. 20815 20816 Returns: 20817 void because the data is fed asynchronously through the `dg` parameter. 20818 20819 Params: 20820 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. 20821 20822 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. 20823 20824 Calling `getData` again after accepting a drop is not permitted. 20825 20826 format = the format you want, from [availableFormats]. Use [DraggableData.getFormatId] to convert from a MIME string or well-known standard format. 20827 20828 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. 20829 20830 Throws: 20831 if `format` was not compatible with the [availableFormats] or if the drop has already been accepted. 20832 20833 History: 20834 Included in first release of [DropPackage]. 20835 +/ 20836 void getData(DragAndDropAction acceptedAction, DraggableData.FormatId format, void delegate(scope ubyte[] data) dg) { 20837 version(X11) { 20838 20839 auto display = XDisplayConnection.get(); 20840 auto selectionAtom = GetAtom!"XdndSelection"(display); 20841 auto best = format; 20842 20843 static class X11GetSelectionHandler_Drop : X11GetSelectionHandler { 20844 20845 XDisplay* display; 20846 Atom selectionAtom; 20847 DraggableData.FormatId best; 20848 DraggableData.FormatId format; 20849 void delegate(scope ubyte[] data) dg; 20850 DragAndDropAction acceptedAction; 20851 Window sourceWindow; 20852 SimpleWindow win; 20853 this(XDisplay* display, SimpleWindow win, Window sourceWindow, DraggableData.FormatId format, Atom selectionAtom, DraggableData.FormatId best, void delegate(scope ubyte[] data) dg, DragAndDropAction acceptedAction) { 20854 this.display = display; 20855 this.win = win; 20856 this.sourceWindow = sourceWindow; 20857 this.format = format; 20858 this.selectionAtom = selectionAtom; 20859 this.best = best; 20860 this.dg = dg; 20861 this.acceptedAction = acceptedAction; 20862 } 20863 20864 20865 mixin X11GetSelectionHandler_Basics; 20866 20867 void handleData(Atom target, in ubyte[] data) { 20868 //if(target == GetAtom!"UTF8_STRING"(XDisplayConnection.get) || target == XA_STRING || target == GetAtom!"text/plain"(XDisplayConnection.get)) 20869 20870 dg(cast(ubyte[]) data); 20871 20872 if(acceptedAction != DragAndDropAction.none) { 20873 auto display = XDisplayConnection.get; 20874 20875 XClientMessageEvent xclient; 20876 20877 xclient.type = EventType.ClientMessage; 20878 xclient.window = sourceWindow; 20879 xclient.message_type = GetAtom!"XdndFinished"(display); 20880 xclient.format = 32; 20881 xclient.data.l[0] = win.impl.window; 20882 xclient.data.l[1] = 1; // drop successful 20883 xclient.data.l[2] = dndActionAtom(display, acceptedAction); 20884 20885 XSendEvent( 20886 display, 20887 sourceWindow, 20888 false, 20889 EventMask.NoEventMask, 20890 cast(XEvent*) &xclient 20891 ); 20892 20893 XFlush(display); 20894 } 20895 } 20896 20897 Atom findBestFormat(Atom[] answer) { 20898 Atom best = None; 20899 foreach(option; answer) { 20900 if(option == format) { 20901 best = option; 20902 break; 20903 } 20904 /* 20905 if(option == GetAtom!"UTF8_STRING"(XDisplayConnection.get)) { 20906 best = option; 20907 break; 20908 } else if(option == XA_STRING) { 20909 best = option; 20910 } else if(option == GetAtom!"text/plain"(XDisplayConnection.get)) { 20911 best = option; 20912 } 20913 */ 20914 } 20915 return best; 20916 } 20917 } 20918 20919 win.impl.getSelectionHandlers[selectionAtom] = new X11GetSelectionHandler_Drop(display, win, sourceWindow, format, selectionAtom, best, dg, acceptedAction); 20920 20921 XConvertSelection(display, selectionAtom, best, GetAtom!("SDD_DATA", true)(display), win.impl.window, dataTimestamp); 20922 20923 } else version(Windows) { 20924 20925 // clean up like DragLeave 20926 // pass effect back up 20927 20928 FORMATETC t; 20929 assert(format >= 0 && format <= ushort.max); 20930 t.cfFormat = cast(ushort) format; 20931 t.lindex = -1; 20932 t.dwAspect = DVASPECT.DVASPECT_CONTENT; 20933 t.tymed = TYMED.TYMED_HGLOBAL; 20934 20935 STGMEDIUM m; 20936 20937 if(pDataObj.GetData(&t, &m) != S_OK) { 20938 // fail 20939 } else { 20940 // succeed, take the data and clean up 20941 20942 // FIXME: ensure it is legit HGLOBAL 20943 auto handle = m.hGlobal; 20944 20945 if(handle) { 20946 auto sz = GlobalSize(handle); 20947 if(auto ptr = cast(ubyte*) GlobalLock(handle)) { 20948 scope(exit) GlobalUnlock(handle); 20949 scope(exit) GlobalFree(handle); 20950 20951 auto data = ptr[0 .. sz]; 20952 20953 dg(data); 20954 } 20955 } 20956 } 20957 } 20958 } 20959 20960 private: 20961 20962 version(X11) { 20963 SimpleWindow win; 20964 Window sourceWindow; 20965 Time dataTimestamp; 20966 20967 Atom[] xFormats; 20968 } 20969 version(Windows) { 20970 IDataObject pDataObj; 20971 } 20972 } 20973 20974 /++ 20975 A generic helper base class for making a drop handler with a preference list of custom types. 20976 This is the base for [TextDropHandler] and [FilesDropHandler] and you can use it for your own 20977 droppers too. 20978 20979 It assumes the whole window it used, but you can subclass to change that. 20980 20981 $(PITFALL This is not yet stable and may break in future versions without notice.) 20982 20983 History: 20984 Added February 19, 2021 20985 +/ 20986 /// Group: drag_and_drop 20987 class GenericDropHandlerBase : DropHandler { 20988 // no fancy state here so no need to do anything here 20989 void finish() { } 20990 void dragLeave() { } 20991 20992 private DragAndDropAction acceptedAction; 20993 private DraggableData.FormatId acceptedFormat; 20994 private void delegate(scope ubyte[]) acceptedHandler; 20995 20996 struct FormatHandler { 20997 DraggableData.FormatId format; 20998 void delegate(scope ubyte[]) handler; 20999 } 21000 21001 protected abstract FormatHandler[] formatHandlers(); 21002 21003 DragAndDropAction dragEnter(DropPackage* pkg) { 21004 debug(sdpy_dnd) { foreach(fmt; pkg.availableFormats()) writeln(fmt, " ", DraggableData.getFormatName(fmt)); } 21005 foreach(fmt; formatHandlers()) 21006 foreach(f; pkg.availableFormats()) 21007 if(f == fmt.format) { 21008 acceptedFormat = f; 21009 acceptedHandler = fmt.handler; 21010 return acceptedAction = DragAndDropAction.copy; 21011 } 21012 return acceptedAction = DragAndDropAction.none; 21013 } 21014 DropParameters dragOver(Point pt) { 21015 return DropParameters(acceptedAction); 21016 } 21017 21018 void drop(scope DropPackage* dropPackage) { 21019 if(!acceptedFormat || acceptedHandler is null) { 21020 debug(sdpy_dnd) { writeln("drop called w/ handler ", acceptedHandler, " and format ", acceptedFormat); } 21021 return; // prolly shouldn't happen anyway... 21022 } 21023 21024 dropPackage.getData(acceptedAction, acceptedFormat, acceptedHandler); 21025 } 21026 } 21027 21028 /++ 21029 A simple handler for making your window accept drops of plain text. 21030 21031 $(PITFALL This is not yet stable and may break in future versions without notice.) 21032 21033 History: 21034 Added February 22, 2021 21035 +/ 21036 /// Group: drag_and_drop 21037 class TextDropHandler : GenericDropHandlerBase { 21038 private void delegate(in char[] text) dg; 21039 21040 /++ 21041 21042 +/ 21043 this(void delegate(in char[] text) dg) { 21044 this.dg = dg; 21045 } 21046 21047 protected override FormatHandler[] formatHandlers() { 21048 version(X11) 21049 return [ 21050 FormatHandler(GetAtom!"UTF8_STRING"(XDisplayConnection.get), &translator), 21051 FormatHandler(GetAtom!"text/plain;charset=utf-8"(XDisplayConnection.get), &translator), 21052 ]; 21053 else version(Windows) 21054 return [ 21055 FormatHandler(CF_UNICODETEXT, &translator), 21056 ]; 21057 else throw new NotYetImplementedException(); 21058 } 21059 21060 private void translator(scope ubyte[] data) { 21061 version(X11) 21062 dg(cast(char[]) data); 21063 else version(Windows) 21064 dg(makeUtf8StringFromWindowsString(cast(wchar[]) data)); 21065 } 21066 } 21067 21068 /++ 21069 A simple handler for making your window accept drops of files, issued to you as file names. 21070 21071 $(PITFALL This is not yet stable and may break in future versions without notice.) 21072 21073 History: 21074 Added February 22, 2021 21075 +/ 21076 /// Group: drag_and_drop 21077 21078 class FilesDropHandler : GenericDropHandlerBase { 21079 private void delegate(in char[][]) dg; 21080 21081 /++ 21082 21083 +/ 21084 this(void delegate(in char[][] fileNames) dg) { 21085 this.dg = dg; 21086 } 21087 21088 protected override FormatHandler[] formatHandlers() { 21089 version(X11) 21090 return [ 21091 FormatHandler(GetAtom!"text/uri-list"(XDisplayConnection.get), &translator), 21092 ]; 21093 else version(Windows) 21094 return [ 21095 FormatHandler(CF_HDROP, &translator), 21096 ]; 21097 else throw new NotYetImplementedException(); 21098 } 21099 21100 private void translator(scope ubyte[] data) { 21101 version(X11) { 21102 char[] listString = cast(char[]) data; 21103 char[][16] buffer; 21104 int count; 21105 char[][] result = buffer[]; 21106 21107 void commit(char[] s) { 21108 if(count == result.length) 21109 result.length += 16; 21110 if(s.length > 7 && s[0 ..7] == "file://") 21111 s = s[7 .. $]; // FIXME: also may need to trim out the host and do some entity decoding 21112 result[count++] = s; 21113 } 21114 21115 size_t last; 21116 foreach(idx, char c; listString) { 21117 if(c == '\n') { 21118 commit(listString[last .. idx - 1]); // a \r 21119 last = idx + 1; // a \n 21120 } 21121 } 21122 21123 if(last < listString.length) { 21124 commit(listString[last .. $]); 21125 } 21126 21127 // FIXME: they are uris now, should I translate it to local file names? 21128 // of course the host name is supposed to be there cuz of X rokking... 21129 21130 dg(result[0 .. count]); 21131 } else version(Windows) { 21132 21133 static struct DROPFILES { 21134 DWORD pFiles; 21135 POINT pt; 21136 BOOL fNC; 21137 BOOL fWide; 21138 } 21139 21140 21141 const(char)[][16] buffer; 21142 int count; 21143 const(char)[][] result = buffer[]; 21144 size_t last; 21145 21146 void commitA(in char[] stuff) { 21147 if(count == result.length) 21148 result.length += 16; 21149 result[count++] = stuff; 21150 } 21151 21152 void commitW(in wchar[] stuff) { 21153 commitA(makeUtf8StringFromWindowsString(stuff)); 21154 } 21155 21156 void magic(T)(T chars) { 21157 size_t idx; 21158 while(chars[idx]) { 21159 last = idx; 21160 while(chars[idx]) { 21161 idx++; 21162 } 21163 static if(is(T == char*)) 21164 commitA(chars[last .. idx]); 21165 else 21166 commitW(chars[last .. idx]); 21167 idx++; 21168 } 21169 } 21170 21171 auto df = cast(DROPFILES*) data.ptr; 21172 if(df.fWide) { 21173 wchar* chars = cast(wchar*) (data.ptr + df.pFiles); 21174 magic(chars); 21175 } else { 21176 char* chars = cast(char*) (data.ptr + df.pFiles); 21177 magic(chars); 21178 } 21179 dg(result[0 .. count]); 21180 } 21181 else throw new NotYetImplementedException(); 21182 } 21183 } 21184 21185 /++ 21186 Interface to describe data being dragged. See also [draggable] helper function. 21187 21188 $(PITFALL This is not yet stable and may break in future versions without notice.) 21189 21190 History: 21191 Added February 19, 2021 21192 +/ 21193 interface DraggableData { 21194 version(X11) 21195 alias FormatId = Atom; 21196 else 21197 alias FormatId = uint; 21198 /++ 21199 Gets the platform-specific FormatId associated with the given named format. 21200 21201 This may be a MIME type, but may also be other various strings defined by the 21202 programs you want to interoperate with. 21203 21204 FIXME: sdpy needs to offer data adapter things that look for compatible formats 21205 and convert it to some particular type for you. 21206 +/ 21207 static FormatId getFormatId(string name)() { 21208 version(X11) 21209 return GetAtom!name(XDisplayConnection.get); 21210 else version(Windows) { 21211 static UINT cache; 21212 if(!cache) 21213 cache = RegisterClipboardFormatA(name); 21214 return cache; 21215 } else 21216 throw new NotYetImplementedException(); 21217 } 21218 21219 /++ 21220 Looks up a string to represent the name for the given format, if there is one. 21221 21222 You should avoid using this function because it is slow. It is provided more for 21223 debugging than for primary use. 21224 +/ 21225 static string getFormatName(FormatId format) { 21226 version(X11) { 21227 if(format == 0) 21228 return "None"; 21229 else 21230 return getAtomName(format, XDisplayConnection.get); 21231 } else version(Windows) { 21232 switch(format) { 21233 case CF_UNICODETEXT: return "CF_UNICODETEXT"; 21234 case CF_DIBV5: return "CF_DIBV5"; 21235 case CF_RIFF: return "CF_RIFF"; 21236 case CF_WAVE: return "CF_WAVE"; 21237 case CF_HDROP: return "CF_HDROP"; 21238 default: 21239 char[1024] name; 21240 auto count = GetClipboardFormatNameA(format, name.ptr, name.length); 21241 return name[0 .. count].idup; 21242 } 21243 } else throw new NotYetImplementedException(); 21244 } 21245 21246 FormatId[] availableFormats(); 21247 // Return the slice of data you filled, empty slice if done. 21248 // this is to support the incremental thing 21249 ubyte[] getData(FormatId format, return scope ubyte[] data); 21250 21251 size_t dataLength(FormatId format); 21252 } 21253 21254 /++ 21255 $(PITFALL This is not yet stable and may break in future versions without notice.) 21256 21257 History: 21258 Added February 19, 2021 21259 +/ 21260 DraggableData draggable(string s) { 21261 version(X11) 21262 return new class X11SetSelectionHandler_Text, DraggableData { 21263 this() { 21264 super(s); 21265 } 21266 21267 override FormatId[] availableFormats() { 21268 return X11SetSelectionHandler_Text.availableFormats(); 21269 } 21270 21271 override ubyte[] getData(FormatId format, return scope ubyte[] data) { 21272 return X11SetSelectionHandler_Text.getData(format, data); 21273 } 21274 21275 size_t dataLength(FormatId format) { 21276 return s.length; 21277 } 21278 }; 21279 else version(Windows) 21280 return new class DraggableData { 21281 FormatId[] availableFormats() { 21282 return [CF_UNICODETEXT]; 21283 } 21284 21285 ubyte[] getData(FormatId format, return scope ubyte[] data) { 21286 return cast(ubyte[]) makeWindowsString(s, cast(wchar[]) data, WindowsStringConversionFlags.convertNewLines | WindowsStringConversionFlags.zeroTerminate); 21287 } 21288 21289 size_t dataLength(FormatId format) { 21290 return sizeOfConvertedWstring(s, WindowsStringConversionFlags.convertNewLines | WindowsStringConversionFlags.zeroTerminate) * wchar.sizeof; 21291 } 21292 }; 21293 else 21294 throw new NotYetImplementedException(); 21295 } 21296 21297 /++ 21298 $(PITFALL This is not yet stable and may break in future versions without notice.) 21299 21300 History: 21301 Added February 19, 2021 21302 +/ 21303 /// Group: drag_and_drop 21304 int doDragDrop(SimpleWindow window, DraggableData handler, DragAndDropAction action = DragAndDropAction.copy) 21305 in { 21306 assert(window !is null); 21307 assert(handler !is null); 21308 } 21309 do 21310 { 21311 version(X11) { 21312 auto sh = cast(X11SetSelectionHandler) handler; 21313 if(sh is null) { 21314 // gotta make my own adapter. 21315 sh = new class X11SetSelectionHandler { 21316 mixin X11SetSelectionHandler_Basics; 21317 21318 Atom[] availableFormats() { return handler.availableFormats(); } 21319 ubyte[] getData(Atom format, return scope ubyte[] data) { 21320 return handler.getData(format, data); 21321 } 21322 21323 // since the drop selection is only ever used once it isn't important 21324 // to reset it. 21325 void done() {} 21326 }; 21327 } 21328 return doDragDropX11(window, sh, action); 21329 } else version(Windows) { 21330 return doDragDropWindows(window, handler, action); 21331 } else throw new NotYetImplementedException(); 21332 } 21333 21334 version(Windows) 21335 private int doDragDropWindows(SimpleWindow window, DraggableData handler, DragAndDropAction action = DragAndDropAction.copy) { 21336 IDataObject obj = new class IDataObject { 21337 ULONG refCount; 21338 ULONG AddRef() { 21339 return ++refCount; 21340 } 21341 ULONG Release() { 21342 return --refCount; 21343 } 21344 HRESULT QueryInterface(const (IID)*riid, LPVOID *ppv) { 21345 if (IID_IUnknown == *riid) { 21346 *ppv = cast(void*) cast(IUnknown) this; 21347 } 21348 else if (IID_IDataObject == *riid) { 21349 *ppv = cast(void*) cast(IDataObject) this; 21350 } 21351 else { 21352 *ppv = null; 21353 return E_NOINTERFACE; 21354 } 21355 21356 AddRef(); 21357 return NOERROR; 21358 } 21359 21360 HRESULT DAdvise(FORMATETC* pformatetc, DWORD advf, IAdviseSink pAdvSink, DWORD* pdwConnection) { 21361 // writeln("Advise"); 21362 return E_NOTIMPL; 21363 } 21364 HRESULT DUnadvise(DWORD dwConnection) { 21365 return E_NOTIMPL; 21366 } 21367 HRESULT EnumDAdvise(IEnumSTATDATA* ppenumAdvise) { 21368 // writeln("EnumDAdvise"); 21369 return OLE_E_ADVISENOTSUPPORTED; 21370 } 21371 // tell what formats it supports 21372 21373 FORMATETC[] types; 21374 this() { 21375 FORMATETC t; 21376 foreach(ty; handler.availableFormats()) { 21377 assert(ty <= ushort.max && ty >= 0); 21378 t.cfFormat = cast(ushort) ty; 21379 t.lindex = -1; 21380 t.dwAspect = DVASPECT.DVASPECT_CONTENT; 21381 t.tymed = TYMED.TYMED_HGLOBAL; 21382 } 21383 types ~= t; 21384 } 21385 HRESULT EnumFormatEtc(DWORD dwDirection, IEnumFORMATETC* ppenumFormatEtc) { 21386 if(dwDirection == DATADIR.DATADIR_GET) { 21387 *ppenumFormatEtc = new class IEnumFORMATETC { 21388 ULONG refCount; 21389 ULONG AddRef() { 21390 return ++refCount; 21391 } 21392 ULONG Release() { 21393 return --refCount; 21394 } 21395 HRESULT QueryInterface(const (IID)*riid, LPVOID *ppv) { 21396 if (IID_IUnknown == *riid) { 21397 *ppv = cast(void*) cast(IUnknown) this; 21398 } 21399 else if (IID_IEnumFORMATETC == *riid) { 21400 *ppv = cast(void*) cast(IEnumFORMATETC) this; 21401 } 21402 else { 21403 *ppv = null; 21404 return E_NOINTERFACE; 21405 } 21406 21407 AddRef(); 21408 return NOERROR; 21409 } 21410 21411 21412 int pos; 21413 this() { 21414 pos = 0; 21415 } 21416 21417 HRESULT Clone(IEnumFORMATETC* ppenum) { 21418 // writeln("clone"); 21419 return E_NOTIMPL; // FIXME 21420 } 21421 21422 // Caller is responsible for freeing memory 21423 HRESULT Next(ULONG celt, FORMATETC* rgelt, ULONG* pceltFetched) { 21424 // fetched may be null if celt is one 21425 if(celt != 1) 21426 return E_NOTIMPL; // FIXME 21427 21428 if(celt + pos > types.length) 21429 return S_FALSE; 21430 21431 *rgelt = types[pos++]; 21432 21433 if(pceltFetched !is null) 21434 *pceltFetched = 1; 21435 21436 // writeln("ok celt ", celt); 21437 return S_OK; 21438 } 21439 21440 HRESULT Reset() { 21441 pos = 0; 21442 return S_OK; 21443 } 21444 21445 HRESULT Skip(ULONG celt) { 21446 if(celt + pos <= types.length) { 21447 pos += celt; 21448 return S_OK; 21449 } 21450 return S_FALSE; 21451 } 21452 }; 21453 21454 return S_OK; 21455 } else 21456 return E_NOTIMPL; 21457 } 21458 // given a format, return the format you'd prefer to use cuz it is identical 21459 HRESULT GetCanonicalFormatEtc(FORMATETC* pformatectIn, FORMATETC* pformatetcOut) { 21460 // FIXME: prolly could be better but meh 21461 // writeln("gcf: ", *pformatectIn); 21462 *pformatetcOut = *pformatectIn; 21463 return S_OK; 21464 } 21465 HRESULT GetData(FORMATETC* pformatetcIn, STGMEDIUM* pmedium) { 21466 foreach(ty; types) { 21467 if(ty == *pformatetcIn) { 21468 auto format = ty.cfFormat; 21469 // writeln("A: ", *pformatetcIn, "\nB: ", ty); 21470 STGMEDIUM medium; 21471 medium.tymed = TYMED.TYMED_HGLOBAL; 21472 21473 auto sz = handler.dataLength(format); 21474 auto handle = GlobalAlloc(GMEM_MOVEABLE, sz); 21475 if(handle is null) throw new WindowsApiException("GlobalAlloc", GetLastError()); 21476 if(auto data = cast(wchar*) GlobalLock(handle)) { 21477 auto slice = data[0 .. sz]; 21478 scope(exit) 21479 GlobalUnlock(handle); 21480 21481 handler.getData(format, cast(ubyte[]) slice[]); 21482 } 21483 21484 21485 medium.hGlobal = handle; // FIXME 21486 *pmedium = medium; 21487 return S_OK; 21488 } 21489 } 21490 return DV_E_FORMATETC; 21491 } 21492 HRESULT GetDataHere(FORMATETC* pformatetcIn, STGMEDIUM* pmedium) { 21493 // writeln("GDH: ", *pformatetcIn); 21494 return E_NOTIMPL; // FIXME 21495 } 21496 HRESULT QueryGetData(FORMATETC* pformatetc) { 21497 auto search = *pformatetc; 21498 search.tymed &= TYMED.TYMED_HGLOBAL; 21499 foreach(ty; types) 21500 if(ty == search) { 21501 // writeln("QueryGetData ", search, " ", types[0]); 21502 return S_OK; 21503 } 21504 if(pformatetc.cfFormat==CF_UNICODETEXT) { 21505 //writeln("QueryGetData FALSE ", search, " ", types[0]); 21506 } 21507 return S_FALSE; 21508 } 21509 HRESULT SetData(FORMATETC* pformatetc, STGMEDIUM* pmedium, BOOL fRelease) { 21510 // writeln("SetData: "); 21511 return E_NOTIMPL; 21512 } 21513 }; 21514 21515 21516 IDropSource src = new class IDropSource { 21517 ULONG refCount; 21518 ULONG AddRef() { 21519 return ++refCount; 21520 } 21521 ULONG Release() { 21522 return --refCount; 21523 } 21524 HRESULT QueryInterface(const (IID)*riid, LPVOID *ppv) { 21525 if (IID_IUnknown == *riid) { 21526 *ppv = cast(void*) cast(IUnknown) this; 21527 } 21528 else if (IID_IDropSource == *riid) { 21529 *ppv = cast(void*) cast(IDropSource) this; 21530 } 21531 else { 21532 *ppv = null; 21533 return E_NOINTERFACE; 21534 } 21535 21536 AddRef(); 21537 return NOERROR; 21538 } 21539 21540 int QueryContinueDrag(int fEscapePressed, uint grfKeyState) { 21541 if(fEscapePressed) 21542 return DRAGDROP_S_CANCEL; 21543 if(!(grfKeyState & MK_LBUTTON)) 21544 return DRAGDROP_S_DROP; 21545 return S_OK; 21546 } 21547 21548 int GiveFeedback(uint dwEffect) { 21549 return DRAGDROP_S_USEDEFAULTCURSORS; 21550 } 21551 }; 21552 21553 DWORD effect; 21554 21555 if(action == DragAndDropAction.none) assert(0, "Don't drag something with a none effect."); 21556 21557 DROPEFFECT de = win32DragAndDropAction(action); 21558 21559 // I'm not as concerned about the GC here since DoDragDrop blocks so the stack frame still sane the whole time 21560 // but still prolly a FIXME 21561 21562 auto ret = DoDragDrop(obj, src, de, &effect); 21563 /+ 21564 if(ret == DRAGDROP_S_DROP) 21565 writeln("drop ", effect); 21566 else if(ret == DRAGDROP_S_CANCEL) 21567 writeln("cancel"); 21568 else if(ret == S_OK) 21569 writeln("ok"); 21570 else writeln(ret); 21571 +/ 21572 21573 return ret; 21574 } 21575 21576 version(Windows) 21577 DROPEFFECT win32DragAndDropAction(DragAndDropAction action) { 21578 DROPEFFECT de; 21579 21580 with(DragAndDropAction) 21581 with(DROPEFFECT) 21582 final switch(action) { 21583 case none: de = DROPEFFECT_NONE; break; 21584 case copy: de = DROPEFFECT_COPY; break; 21585 case move: de = DROPEFFECT_MOVE; break; 21586 case link: de = DROPEFFECT_LINK; break; 21587 case ask: throw new Exception("ask not implemented yet"); 21588 case custom: throw new Exception("custom not implemented yet"); 21589 } 21590 21591 return de; 21592 } 21593 21594 21595 /++ 21596 History: 21597 Added February 19, 2021 21598 +/ 21599 /// Group: drag_and_drop 21600 void enableDragAndDrop(SimpleWindow window, DropHandler handler) { 21601 version(X11) { 21602 auto display = XDisplayConnection.get; 21603 21604 Atom atom = 5; // right??? 21605 21606 XChangeProperty( 21607 display, 21608 window.impl.window, 21609 GetAtom!"XdndAware"(display), 21610 XA_ATOM, 21611 32 /* bits */, 21612 PropModeReplace, 21613 &atom, 21614 1); 21615 21616 window.dropHandler = handler; 21617 } else version(Windows) { 21618 21619 initDnd(); 21620 21621 auto dropTarget = new class (handler) IDropTarget { 21622 DropHandler handler; 21623 this(DropHandler handler) { 21624 this.handler = handler; 21625 } 21626 ULONG refCount; 21627 ULONG AddRef() { 21628 return ++refCount; 21629 } 21630 ULONG Release() { 21631 return --refCount; 21632 } 21633 HRESULT QueryInterface(const (IID)*riid, LPVOID *ppv) { 21634 if (IID_IUnknown == *riid) { 21635 *ppv = cast(void*) cast(IUnknown) this; 21636 } 21637 else if (IID_IDropTarget == *riid) { 21638 *ppv = cast(void*) cast(IDropTarget) this; 21639 } 21640 else { 21641 *ppv = null; 21642 return E_NOINTERFACE; 21643 } 21644 21645 AddRef(); 21646 return NOERROR; 21647 } 21648 21649 21650 // /////////////////// 21651 21652 HRESULT DragEnter(IDataObject pDataObj, DWORD grfKeyState, POINTL pt, DWORD* pdwEffect) { 21653 DropPackage dropPackage = DropPackage(pDataObj); 21654 *pdwEffect = win32DragAndDropAction(handler.dragEnter(&dropPackage)); 21655 return S_OK; // https://docs.microsoft.com/en-us/windows/win32/api/oleidl/nf-oleidl-idroptarget-dragenter 21656 } 21657 21658 HRESULT DragLeave() { 21659 handler.dragLeave(); 21660 // release the IDataObject if needed 21661 return S_OK; 21662 } 21663 21664 HRESULT DragOver(DWORD grfKeyState, POINTL pt, DWORD* pdwEffect) { 21665 auto res = handler.dragOver(Point(pt.x, pt.y)); // FIXME: translate screen coordinates back to window coordinates 21666 21667 *pdwEffect = win32DragAndDropAction(res.action); 21668 // same as DragEnter basically 21669 return S_OK; 21670 } 21671 21672 HRESULT Drop(IDataObject pDataObj, DWORD grfKeyState, POINTL pt, DWORD* pdwEffect) { 21673 DropPackage pkg = DropPackage(pDataObj); 21674 handler.drop(&pkg); 21675 21676 return S_OK; 21677 } 21678 }; 21679 // Windows can hold on to the handler and try to call it 21680 // during which time the GC can't see it. so important to 21681 // manually manage this. At some point i'll FIXME and make 21682 // all my com instances manually managed since they supposed 21683 // to respect the refcount. 21684 import core.memory; 21685 GC.addRoot(cast(void*) dropTarget); 21686 21687 if(RegisterDragDrop(window.impl.hwnd, dropTarget) != S_OK) 21688 throw new WindowsApiException("RegisterDragDrop", GetLastError()); 21689 21690 window.dropHandler = handler; 21691 } else throw new NotYetImplementedException(); 21692 } 21693 21694 21695 21696 static if(UsingSimpledisplayX11) { 21697 21698 enum _NET_WM_STATE_ADD = 1; 21699 enum _NET_WM_STATE_REMOVE = 0; 21700 enum _NET_WM_STATE_TOGGLE = 2; 21701 21702 /// X-specific. Use [SimpleWindow.requestAttention] instead for most cases. 21703 void demandAttention(SimpleWindow window, bool needs = true) { 21704 demandAttention(window.impl.window, needs); 21705 } 21706 21707 /// ditto 21708 void demandAttention(Window window, bool needs = true) { 21709 setNetWmStateAtom(window, GetAtom!("_NET_WM_STATE_DEMANDS_ATTENTION", false)(XDisplayConnection.get), needs); 21710 } 21711 21712 void setNetWmStateAtom(Window window, Atom atom, bool set = true, Atom atom2 = None) { 21713 auto display = XDisplayConnection.get(); 21714 if(atom == None) 21715 return; // non-failure error 21716 //auto atom2 = GetAtom!"_NET_WM_STATE_SHADED"(display); 21717 21718 XClientMessageEvent xclient; 21719 21720 xclient.type = EventType.ClientMessage; 21721 xclient.window = window; 21722 xclient.message_type = GetAtom!"_NET_WM_STATE"(display); 21723 xclient.format = 32; 21724 xclient.data.l[0] = set ? _NET_WM_STATE_ADD : _NET_WM_STATE_REMOVE; 21725 xclient.data.l[1] = atom; 21726 xclient.data.l[2] = atom2; 21727 xclient.data.l[3] = 1; 21728 // [3] == source. 0 == unknown, 1 == app, 2 == else 21729 21730 XSendEvent( 21731 display, 21732 RootWindow(display, DefaultScreen(display)), 21733 false, 21734 EventMask.SubstructureRedirectMask | EventMask.SubstructureNotifyMask, 21735 cast(XEvent*) &xclient 21736 ); 21737 21738 /+ 21739 XChangeProperty( 21740 display, 21741 window.impl.window, 21742 GetAtom!"_NET_WM_STATE"(display), 21743 XA_ATOM, 21744 32 /* bits */, 21745 PropModeAppend, 21746 &atom, 21747 1); 21748 +/ 21749 } 21750 21751 private Atom dndActionAtom(Display* display, DragAndDropAction action) { 21752 Atom actionAtom; 21753 with(DragAndDropAction) 21754 final switch(action) { 21755 case none: actionAtom = None; break; 21756 case copy: actionAtom = GetAtom!"XdndActionCopy"(display); break; 21757 case move: actionAtom = GetAtom!"XdndActionMove"(display); break; 21758 case link: actionAtom = GetAtom!"XdndActionLink"(display); break; 21759 case ask: actionAtom = GetAtom!"XdndActionAsk"(display); break; 21760 case custom: actionAtom = GetAtom!"XdndActionCustom"(display); break; 21761 } 21762 21763 return actionAtom; 21764 } 21765 21766 private int doDragDropX11(SimpleWindow window, X11SetSelectionHandler handler, DragAndDropAction action) { 21767 // FIXME: I need to show user feedback somehow. 21768 auto display = XDisplayConnection.get; 21769 21770 auto actionAtom = dndActionAtom(display, action); 21771 assert(actionAtom, "Don't use action none to accept a drop"); 21772 21773 setX11Selection!"XdndSelection"(window, handler, null); 21774 21775 auto oldKeyHandler = window.handleKeyEvent; 21776 scope(exit) window.handleKeyEvent = oldKeyHandler; 21777 21778 auto oldCharHandler = window.handleCharEvent; 21779 scope(exit) window.handleCharEvent = oldCharHandler; 21780 21781 auto oldMouseHandler = window.handleMouseEvent; 21782 scope(exit) window.handleMouseEvent = oldMouseHandler; 21783 21784 Window[Window] eligibility; // 0 == not eligible, otherwise it is the window id of an eligible child 21785 21786 import core.sys.posix.sys.time; 21787 timeval tv; 21788 gettimeofday(&tv, null); 21789 21790 Time dataTimestamp = tv.tv_sec * 1000 + tv.tv_usec / 1000; 21791 21792 Time lastMouseTimestamp; 21793 21794 bool dnding = true; 21795 Window lastIn = None; 21796 21797 void leave() { 21798 if(lastIn == None) 21799 return; 21800 21801 XEvent ev; 21802 ev.xclient.type = EventType.ClientMessage; 21803 ev.xclient.window = lastIn; 21804 ev.xclient.message_type = GetAtom!("XdndLeave", true)(display); 21805 ev.xclient.format = 32; 21806 ev.xclient.data.l[0] = window.impl.window; 21807 21808 XSendEvent(XDisplayConnection.get, lastIn, false, EventMask.NoEventMask, &ev); 21809 XFlush(display); 21810 21811 lastIn = None; 21812 } 21813 21814 void enter(Window w) { 21815 assert(lastIn == None); 21816 21817 lastIn = w; 21818 21819 XEvent ev; 21820 ev.xclient.type = EventType.ClientMessage; 21821 ev.xclient.window = lastIn; 21822 ev.xclient.message_type = GetAtom!("XdndEnter", true)(display); 21823 ev.xclient.format = 32; 21824 ev.xclient.data.l[0] = window.impl.window; 21825 ev.xclient.data.l[1] = (5 << 24) | 0; // version 5, no more sources. FIXME source types 21826 21827 auto types = handler.availableFormats(); 21828 assert(types.length > 0); 21829 21830 ev.xclient.data.l[2] = types[0]; 21831 if(types.length > 1) 21832 ev.xclient.data.l[3] = types[1]; 21833 if(types.length > 2) 21834 ev.xclient.data.l[4] = types[2]; 21835 21836 // FIXME: other types?!?!? and make sure we skip TARGETS 21837 21838 XSendEvent(XDisplayConnection.get, lastIn, false, EventMask.NoEventMask, &ev); 21839 XFlush(display); 21840 } 21841 21842 void position(int rootX, int rootY) { 21843 assert(lastIn != None); 21844 21845 XEvent ev; 21846 ev.xclient.type = EventType.ClientMessage; 21847 ev.xclient.window = lastIn; 21848 ev.xclient.message_type = GetAtom!("XdndPosition", true)(display); 21849 ev.xclient.format = 32; 21850 ev.xclient.data.l[0] = window.impl.window; 21851 ev.xclient.data.l[1] = 0; // reserved 21852 ev.xclient.data.l[2] = (rootX << 16) | rootY; 21853 ev.xclient.data.l[3] = dataTimestamp; 21854 ev.xclient.data.l[4] = actionAtom; 21855 21856 XSendEvent(XDisplayConnection.get, lastIn, false, EventMask.NoEventMask, &ev); 21857 XFlush(display); 21858 21859 } 21860 21861 void drop() { 21862 XEvent ev; 21863 ev.xclient.type = EventType.ClientMessage; 21864 ev.xclient.window = lastIn; 21865 ev.xclient.message_type = GetAtom!("XdndDrop", true)(display); 21866 ev.xclient.format = 32; 21867 ev.xclient.data.l[0] = window.impl.window; 21868 ev.xclient.data.l[1] = 0; // reserved 21869 ev.xclient.data.l[2] = dataTimestamp; 21870 21871 XSendEvent(XDisplayConnection.get, lastIn, false, EventMask.NoEventMask, &ev); 21872 XFlush(display); 21873 21874 lastIn = None; 21875 dnding = false; 21876 } 21877 21878 // fyi nativeEventHandler can return 0 if it handles it, or otherwise it goes back to the normal handler 21879 // but idk if i should... 21880 21881 window.setEventHandlers( 21882 delegate(KeyEvent ev) { 21883 if(ev.pressed == true && ev.key == Key.Escape) { 21884 // cancel 21885 dnding = false; 21886 } 21887 }, 21888 delegate(MouseEvent ev) { 21889 if(ev.timestamp < lastMouseTimestamp) 21890 return; 21891 21892 lastMouseTimestamp = ev.timestamp; 21893 21894 if(ev.type == MouseEventType.motion) { 21895 auto display = XDisplayConnection.get; 21896 auto root = RootWindow(display, DefaultScreen(display)); 21897 21898 Window topWindow; 21899 int rootX, rootY; 21900 21901 XTranslateCoordinates(display, window.impl.window, root, ev.x, ev.y, &rootX, &rootY, &topWindow); 21902 21903 if(topWindow == None) 21904 return; 21905 21906 top: 21907 if(auto result = topWindow in eligibility) { 21908 auto dropWindow = *result; 21909 if(dropWindow == None) { 21910 leave(); 21911 return; 21912 } 21913 21914 if(dropWindow != lastIn) { 21915 leave(); 21916 enter(dropWindow); 21917 position(rootX, rootY); 21918 } else { 21919 position(rootX, rootY); 21920 } 21921 } else { 21922 // determine eligibility 21923 auto data = cast(Atom[]) getX11PropertyData(topWindow, GetAtom!"XdndAware"(display), XA_ATOM); 21924 if(data.length == 1) { 21925 // in case there is no WM or it isn't reparenting 21926 eligibility[topWindow] = (data[0] == 5) ? topWindow : None; // FIXME I'm supposed to handle older versions too but meh 21927 } else { 21928 21929 Window tryScanChildren(Window search, int maxRecurse) { 21930 // could be reparenting window manager, so gotta check the next few children too 21931 Window child; 21932 int x; 21933 int y; 21934 XTranslateCoordinates(display, window.impl.window, search, ev.x, ev.y, &x, &y, &child); 21935 21936 if(child == None) 21937 return None; 21938 auto data = cast(Atom[]) getX11PropertyData(child, GetAtom!"XdndAware"(display), XA_ATOM); 21939 if(data.length == 1) { 21940 return (data[0] == 5) ? child : None; // FIXME I'm supposed to handle older versions too but meh 21941 } else { 21942 if(maxRecurse) 21943 return tryScanChildren(child, maxRecurse - 1); 21944 else 21945 return None; 21946 } 21947 21948 } 21949 21950 // if a WM puts more than 3 layers on it, like wtf is it doing, screw that. 21951 auto topResult = tryScanChildren(topWindow, 3); 21952 // it is easy to have a false negative due to the mouse going over a WM 21953 // child window like the close button if separate from the frame... so I 21954 // can't really cache negatives, :( 21955 if(topResult != None) { 21956 eligibility[topWindow] = topResult; 21957 goto top; // reload to do the positioning iff eligibility changed lest we endless loop 21958 } 21959 } 21960 21961 } 21962 21963 } else if(ev.type == MouseEventType.buttonReleased) { 21964 drop(); 21965 dnding = false; 21966 } 21967 } 21968 ); 21969 21970 window.grabInput(); 21971 scope(exit) 21972 window.releaseInputGrab(); 21973 21974 21975 EventLoop.get.run(() => dnding); 21976 21977 return 0; 21978 } 21979 21980 /// X-specific 21981 TrueColorImage getWindowNetWmIcon(Window window) { 21982 try { 21983 auto display = XDisplayConnection.get; 21984 21985 auto data = getX11PropertyData (window, GetAtom!"_NET_WM_ICON"(display), XA_CARDINAL); 21986 21987 if (data.length > arch_ulong.sizeof * 2) { 21988 auto meta = cast(arch_ulong[]) (data[0 .. arch_ulong.sizeof * 2]); 21989 // these are an array of rgba images that we have to convert into pixmaps ourself 21990 21991 int width = cast(int) meta[0]; 21992 int height = cast(int) meta[1]; 21993 21994 auto bytes = cast(ubyte[]) (data[arch_ulong.sizeof * 2 .. $]); 21995 21996 static if(arch_ulong.sizeof == 4) { 21997 bytes = bytes[0 .. width * height * 4]; 21998 alias imageData = bytes; 21999 } else static if(arch_ulong.sizeof == 8) { 22000 bytes = bytes[0 .. width * height * 8]; 22001 auto imageData = new ubyte[](4 * width * height); 22002 } else static assert(0); 22003 22004 22005 22006 // this returns ARGB. Remember it is little-endian so 22007 // we have BGRA 22008 // our thing uses RGBA, which in little endian, is ABGR 22009 for(int idx = 0, idx2 = 0; idx < bytes.length; idx += arch_ulong.sizeof, idx2 += 4) { 22010 auto r = bytes[idx + 2]; 22011 auto g = bytes[idx + 1]; 22012 auto b = bytes[idx + 0]; 22013 auto a = bytes[idx + 3]; 22014 22015 imageData[idx2 + 0] = r; 22016 imageData[idx2 + 1] = g; 22017 imageData[idx2 + 2] = b; 22018 imageData[idx2 + 3] = a; 22019 } 22020 22021 return new TrueColorImage(width, height, imageData); 22022 } 22023 22024 return null; 22025 } catch(Exception e) { 22026 return null; 22027 } 22028 } 22029 22030 } /* UsingSimpledisplayX11 */ 22031 22032 22033 void loadBinNameToWindowClassName () { 22034 import core.stdc.stdlib : realloc; 22035 version(linux) { 22036 // args[0] MAY be empty, so we'll just use this 22037 import core.sys.posix.unistd : readlink; 22038 char[1024] ebuf = void; // 1KB should be enough for everyone! 22039 auto len = readlink("/proc/self/exe", ebuf.ptr, ebuf.length); 22040 if (len < 1) return; 22041 } else /*version(Windows)*/ { 22042 import core.runtime : Runtime; 22043 if (Runtime.args.length == 0 || Runtime.args[0].length == 0) return; 22044 auto ebuf = Runtime.args[0]; 22045 auto len = ebuf.length; 22046 } 22047 auto pos = len; 22048 while (pos > 0 && ebuf[pos-1] != '/') --pos; 22049 sdpyWindowClassStr = cast(char*)realloc(sdpyWindowClassStr, len-pos+1); 22050 if (sdpyWindowClassStr is null) return; // oops 22051 sdpyWindowClassStr[0..len-pos+1] = 0; // just in case 22052 sdpyWindowClassStr[0..len-pos] = ebuf[pos..len]; 22053 } 22054 22055 /++ 22056 An interface representing a font that is drawn with custom facilities. 22057 22058 You might want [OperatingSystemFont] instead, which represents 22059 a font loaded and drawn by functions native to the operating system. 22060 22061 WARNING: I might still change this. 22062 +/ 22063 interface DrawableFont : MeasurableFont { 22064 /++ 22065 Please note the point is upperLeft, NOT baseline! This is the point of a bounding box of the string. 22066 22067 Implementations must use the painter's fillColor to draw a rectangle behind the string, 22068 then use the outlineColor to draw the string. It might alpha composite if there's a transparent 22069 fill color, but that's up to the implementation. 22070 +/ 22071 void drawString(ScreenPainter painter, Point upperLeft, in char[] text); 22072 22073 /++ 22074 Requests that the given string is added to the image cache. You should only do this rarely, but 22075 if you have a string that you know will be used over and over again, adding it to a cache can 22076 improve things (assuming the implementation actually has a cache; it is also valid for an implementation 22077 to implement this as a do-nothing method). 22078 +/ 22079 void cacheString(SimpleWindow window, Color foreground, Color background, string text); 22080 } 22081 22082 /++ 22083 Loads a true type font using [arsd.ttf] that can be drawn as images on windows 22084 through a [ScreenPainter]. That module must be compiled in if you choose to use this function. 22085 22086 You should also consider [OperatingSystemFont], which loads and draws a font with 22087 facilities native to the user's operating system. You might also consider 22088 [arsd.ttf.OpenGlLimitedFont] or using [arsd.nanovega] if you are making some kind 22089 of game, as they have their own ways to draw text too. 22090 22091 Be warned: this can be slow, especially on remote connections to the X server, since 22092 it needs to create and transfer bitmaps instead of just text. The [DrawableFont] interface 22093 offers [DrawableFont.cacheString] which can help with this, sometimes. You might want to 22094 experiment in your specific case. 22095 22096 Please note that the return type of [DrawableFont] also includes an implementation of 22097 [MeasurableFont]. 22098 +/ 22099 DrawableFont arsdTtfFont()(in ubyte[] data, int size) { 22100 import arsd.ttf; 22101 static class ArsdTtfFont : DrawableFont { 22102 TtfFont font; 22103 int size; 22104 this(in ubyte[] data, int size) { 22105 font = TtfFont(data); 22106 this.size = size; 22107 22108 22109 auto scale = stbtt_ScaleForPixelHeight(&font.font, size); 22110 int ascent_, descent_, line_gap; 22111 stbtt_GetFontVMetrics(&font.font, &ascent_, &descent_, &line_gap); 22112 22113 int advance, lsb; 22114 stbtt_GetCodepointHMetrics(&font.font, 'x', &advance, &lsb); 22115 xWidth = cast(int) (advance * scale); 22116 stbtt_GetCodepointHMetrics(&font.font, 'M', &advance, &lsb); 22117 MWidth = cast(int) (advance * scale); 22118 } 22119 22120 private int ascent_; 22121 private int descent_; 22122 private int xWidth; 22123 private int MWidth; 22124 22125 bool isMonospace() { 22126 return xWidth == MWidth; 22127 } 22128 int averageWidth() { 22129 return xWidth; 22130 } 22131 int height() { 22132 return size; 22133 } 22134 int ascent() { 22135 return ascent_; 22136 } 22137 int descent() { 22138 return descent_; 22139 } 22140 22141 int stringWidth(scope const(char)[] s, SimpleWindow window = null) { 22142 int width, height; 22143 font.getStringSize(s, size, width, height); 22144 return width; 22145 } 22146 22147 22148 22149 Sprite[string] cache; 22150 22151 void cacheString(SimpleWindow window, Color foreground, Color background, string text) { 22152 auto sprite = new Sprite(window, stringToImage(foreground, background, text)); 22153 cache[text] = sprite; 22154 } 22155 22156 Image stringToImage(Color fg, Color bg, in char[] text) { 22157 int width, height; 22158 auto data = font.renderString(text, size, width, height); 22159 auto image = new TrueColorImage(width, height); 22160 int pos = 0; 22161 foreach(y; 0 .. height) 22162 foreach(x; 0 .. width) { 22163 fg.a = data[0]; 22164 bg.a = 255; 22165 auto color = alphaBlend(fg, bg); 22166 image.imageData.bytes[pos++] = color.r; 22167 image.imageData.bytes[pos++] = color.g; 22168 image.imageData.bytes[pos++] = color.b; 22169 image.imageData.bytes[pos++] = data[0]; 22170 data = data[1 .. $]; 22171 } 22172 assert(data.length == 0); 22173 22174 return Image.fromMemoryImage(image); 22175 } 22176 22177 void drawString(ScreenPainter painter, Point upperLeft, in char[] text) { 22178 Sprite sprite = (text in cache) ? *(text in cache) : null; 22179 22180 auto fg = painter.impl._outlineColor; 22181 auto bg = painter.impl._fillColor; 22182 22183 if(sprite !is null) { 22184 auto w = cast(SimpleWindow) painter.window; 22185 assert(w !is null); 22186 22187 sprite.drawAt(painter, upperLeft); 22188 } else { 22189 painter.drawImage(upperLeft, stringToImage(fg, bg, text)); 22190 } 22191 } 22192 } 22193 22194 return new ArsdTtfFont(data, size); 22195 } 22196 22197 class NotYetImplementedException : Exception { 22198 this(string file = __FILE__, size_t line = __LINE__) { 22199 super("Not yet implemented", file, line); 22200 } 22201 } 22202 22203 /// 22204 __gshared bool librariesSuccessfullyLoaded = true; 22205 /// 22206 __gshared bool openGlLibrariesSuccessfullyLoaded = true; 22207 22208 private mixin template DynamicLoadSupplementalOpenGL(Iface) { 22209 mixin(staticForeachReplacement!Iface); 22210 22211 void loadDynamicLibrary() @nogc { 22212 (cast(void function() @nogc) &loadDynamicLibraryForReal)(); 22213 } 22214 22215 void loadDynamicLibraryForReal() { 22216 foreach(name; __traits(derivedMembers, Iface)) { 22217 mixin("alias tmp = " ~ name ~ ";"); 22218 tmp = cast(typeof(tmp)) glbindGetProcAddress(name); 22219 if(tmp is null) throw new Exception("load failure of function " ~ name ~ " from supplemental OpenGL"); 22220 } 22221 } 22222 } 22223 22224 private const(char)[] staticForeachReplacement(Iface)() pure { 22225 /* 22226 // just this for gdc 9.... 22227 // when i drop support for it and switch to gdc10, we can put this original back for a slight compile time ram decrease 22228 22229 static foreach(name; __traits(derivedMembers, Iface)) 22230 mixin("__gshared typeof(&__traits(getMember, Iface, name)) " ~ name ~ ";"); 22231 */ 22232 22233 char[] code = new char[](__traits(derivedMembers, Iface).length * 64); 22234 size_t pos; 22235 22236 void append(in char[] what) { 22237 if(pos + what.length > code.length) 22238 code.length = (code.length * 3) / 2; 22239 code[pos .. pos + what.length] = what[]; 22240 pos += what.length; 22241 } 22242 22243 foreach(name; __traits(derivedMembers, Iface)) { 22244 append(`__gshared typeof(&__traits(getMember, Iface, "`); 22245 append(name); 22246 append(`")) `); 22247 append(name); 22248 append(";"); 22249 } 22250 22251 return code[0 .. pos]; 22252 } 22253 22254 private mixin template DynamicLoad(Iface, string library, int majorVersion, alias success) { 22255 mixin(staticForeachReplacement!Iface); 22256 22257 private __gshared void* libHandle; 22258 private __gshared bool attempted; 22259 22260 void loadDynamicLibrary() @nogc { 22261 (cast(void function() @nogc) &loadDynamicLibraryForReal)(); 22262 } 22263 22264 bool loadAttempted() { 22265 return attempted; 22266 } 22267 bool loadSuccessful() { 22268 return libHandle !is null; 22269 } 22270 22271 void loadDynamicLibraryForReal() { 22272 attempted = true; 22273 version(Posix) { 22274 import core.sys.posix.dlfcn; 22275 version(OSX) { 22276 version(X11) 22277 libHandle = dlopen("/usr/X11/lib/lib" ~ library ~ ".dylib", RTLD_NOW); 22278 else 22279 libHandle = dlopen(library ~ ".dylib", RTLD_NOW); 22280 } else { 22281 libHandle = dlopen("lib" ~ library ~ ".so", RTLD_NOW); 22282 if(libHandle is null) 22283 libHandle = dlopen(("lib" ~ library ~ ".so." ~ toInternal!string(majorVersion) ~ "\0").ptr, RTLD_NOW); 22284 } 22285 22286 static void* loadsym(void* l, const char* name) { 22287 import core.stdc.stdlib; 22288 if(l is null) 22289 return &abort; 22290 return dlsym(l, name); 22291 } 22292 } else version(Windows) { 22293 import core.sys.windows.winbase; 22294 libHandle = LoadLibrary(library ~ ".dll"); 22295 static void* loadsym(void* l, const char* name) { 22296 import core.stdc.stdlib; 22297 if(l is null) 22298 return &abort; 22299 return GetProcAddress(l, name); 22300 } 22301 } 22302 if(libHandle is null) { 22303 success = false; 22304 //throw new Exception("load failure of library " ~ library); 22305 } 22306 foreach(name; __traits(derivedMembers, Iface)) { 22307 mixin("alias tmp = " ~ name ~ ";"); 22308 tmp = cast(typeof(tmp)) loadsym(libHandle, name); 22309 if(tmp is null) throw new Exception("load failure of function " ~ name ~ " from " ~ library); 22310 } 22311 } 22312 22313 void unloadDynamicLibrary() { 22314 version(Posix) { 22315 import core.sys.posix.dlfcn; 22316 dlclose(libHandle); 22317 } else version(Windows) { 22318 import core.sys.windows.winbase; 22319 FreeLibrary(libHandle); 22320 } 22321 foreach(name; __traits(derivedMembers, Iface)) 22322 mixin(name ~ " = null;"); 22323 } 22324 } 22325 22326 /+ 22327 The GC can be called from any thread, and a lot of cleanup must be done 22328 on the gui thread. Since the GC can interrupt any locks - including being 22329 triggered inside a critical section - it is vital to avoid deadlocks to get 22330 these functions called from the right place. 22331 22332 If the buffer overflows, things are going to get leaked. I'm kinda ok with that 22333 right now. 22334 22335 The cleanup function is run when the event loop gets around to it, which is just 22336 whenever there's something there after it has been woken up for other work. It does 22337 NOT wake up the loop itself - can't risk doing that from inside the GC in another thread. 22338 (Well actually it might be ok but i don't wanna mess with it right now.) 22339 +/ 22340 private struct CleanupQueue { 22341 import core.stdc.stdlib; 22342 22343 void queue(alias func, T...)(T args) { 22344 static struct Args { 22345 T args; 22346 } 22347 static struct RealJob { 22348 Job j; 22349 Args a; 22350 } 22351 static void call(Job* data) { 22352 auto rj = cast(RealJob*) data; 22353 func(rj.a.args); 22354 } 22355 22356 RealJob* thing = cast(RealJob*) malloc(RealJob.sizeof); 22357 thing.j.call = &call; 22358 thing.a.args = args; 22359 22360 buffer[tail++] = cast(Job*) thing; 22361 22362 // FIXME: set overflowed 22363 } 22364 22365 void process() { 22366 const tail = this.tail; 22367 22368 while(tail != head) { 22369 Job* job = cast(Job*) buffer[head++]; 22370 job.call(job); 22371 free(job); 22372 } 22373 22374 if(overflowed) 22375 throw new Exception("cleanup overflowed"); 22376 } 22377 22378 private: 22379 22380 ubyte tail; // must ONLY be written by queue 22381 ubyte head; // must ONLY be written by process 22382 bool overflowed; 22383 22384 static struct Job { 22385 void function(Job*) call; 22386 } 22387 22388 void*[256] buffer; 22389 } 22390 private __gshared CleanupQueue cleanupQueue; 22391 22392 version(X11) 22393 /++ 22394 Returns the custom scaling factor read out of environment["ARSD_SCALING_FACTOR"]. 22395 22396 $(WARNING 22397 This function is exempted from stability guarantees. 22398 ) 22399 +/ 22400 float customScalingFactorForMonitor(int monitorNumber) { 22401 import core.stdc.stdlib; 22402 auto val = getenv("ARSD_SCALING_FACTOR"); 22403 22404 // FIXME: maybe we should assume a default nbased on the dpi thing if this isn't given 22405 if(val is null) 22406 return 1.0; 22407 22408 char[16] buffer = 0; 22409 int pos; 22410 22411 const(char)* at = val; 22412 22413 foreach(item; 0 .. monitorNumber + 1) { 22414 if(*at == 0) 22415 break; // reuse the last number when we at the end of the string 22416 pos = 0; 22417 while(pos + 1 < buffer.length && *at && *at != ';') { 22418 buffer[pos++] = *at; 22419 at++; 22420 } 22421 if(*at) 22422 at++; // skip the semicolon 22423 buffer[pos] = 0; 22424 } 22425 22426 //sdpyPrintDebugString(buffer[0 .. pos]); 22427 22428 import core.stdc.math; 22429 auto f = atof(buffer.ptr); 22430 22431 if(f <= 0.0 || isnan(f) || isinf(f)) 22432 return 1.0; 22433 22434 return f; 22435 } 22436 22437 void guiAbortProcess(string msg) { 22438 import core.stdc.stdlib; 22439 version(Windows) { 22440 WCharzBuffer t = WCharzBuffer(msg); 22441 MessageBoxW(null, t.ptr, "Program Termination"w.ptr, 0); 22442 } else { 22443 import core.stdc.stdio; 22444 fwrite(msg.ptr, 1, msg.length, stderr); 22445 msg = "\n"; 22446 fwrite(msg.ptr, 1, msg.length, stderr); 22447 fflush(stderr); 22448 } 22449 22450 abort(); 22451 } 22452 22453 private int minInternal(int a, int b) { 22454 return (a < b) ? a : b; 22455 } 22456 22457 private alias scriptable = arsd_jsvar_compatible;