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 } else version(OSXCocoa) { 1964 actualDpi_ = cast(int)(96 * customScalingFactorForMonitor(0)); // FIXME 1965 } 1966 actualDpiLoadAttempted = true; 1967 } else version(X11) if(MonitorInfo.info.length == 0) { 1968 useFallbackDpi = true; 1969 } 1970 1971 version(X11) 1972 if(useFallbackDpi) 1973 actualDpi_ = cast(int) (getDpi()[0] * customScalingFactorForMonitor(0)); 1974 1975 return actualDpi_; 1976 } 1977 1978 private int actualDpi_; 1979 private bool actualDpiLoadAttempted; 1980 1981 version(X11) private { 1982 bool requestedInput; 1983 static bool xRandrInfoLoadAttemped; 1984 struct MonitorInfo { 1985 Rectangle position; 1986 Size size; 1987 int dpi; 1988 1989 static MonitorInfo[] info; 1990 } 1991 bool screenPositionKnown; 1992 int screenPositionX; 1993 int screenPositionY; 1994 void updateActualDpi(bool loadingNow = false) { 1995 if(!loadingNow && !actualDpiLoadAttempted) 1996 actualDpi(); // just to make it do the load 1997 foreach(idx, m; MonitorInfo.info) { 1998 if(m.position.contains(Point(screenPositionX + this.width / 2, screenPositionY + this.height / 2))) { 1999 bool changed = actualDpi_ && actualDpi_ != m.dpi; 2000 actualDpi_ = m.dpi; 2001 // writeln("monitor ", idx); 2002 if(changed && onDpiChanged) 2003 onDpiChanged(); 2004 break; 2005 } 2006 } 2007 } 2008 } 2009 2010 /++ 2011 Sent when the window is moved to a new DPI context, for example, when it is dragged between monitors 2012 or if the window is moved to a new remote connection or a monitor is hot-swapped. 2013 2014 History: 2015 Added November 26, 2021 (dub v10.4) 2016 2017 See_Also: 2018 [actualDpi] 2019 +/ 2020 void delegate() onDpiChanged; 2021 2022 version(X11) { 2023 void recreateAfterDisconnect() { 2024 if(!stateDiscarded) return; 2025 2026 if(_parent !is null && _parent.stateDiscarded) 2027 _parent.recreateAfterDisconnect(); 2028 2029 bool wasHidden = hidden; 2030 2031 activeScreenPainter = null; // should already be done but just to confirm 2032 2033 actualDpi_ = 0; 2034 actualDpiLoadAttempted = false; 2035 xRandrInfoLoadAttemped = false; 2036 2037 impl.createWindow(_width, _height, _title, openglMode, _parent); 2038 2039 if(auto dh = dropHandler) { 2040 dropHandler = null; 2041 enableDragAndDrop(this, dh); 2042 } 2043 2044 if(recreateAdditionalConnectionState) 2045 recreateAdditionalConnectionState(); 2046 2047 hidden = wasHidden; 2048 stateDiscarded = false; 2049 } 2050 2051 bool stateDiscarded; 2052 void discardConnectionState() { 2053 if(XDisplayConnection.display) 2054 impl.dispose(); // if display is already null, it is hopeless to try to destroy stuff on it anyway 2055 if(discardAdditionalConnectionState) 2056 discardAdditionalConnectionState(); 2057 stateDiscarded = true; 2058 } 2059 2060 void delegate() discardAdditionalConnectionState; 2061 void delegate() recreateAdditionalConnectionState; 2062 2063 } 2064 2065 private DropHandler dropHandler; 2066 2067 SimpleWindow _parent; 2068 bool beingOpenKeepsAppOpen = true; 2069 /++ 2070 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. 2071 2072 The constructor tries to have sane default arguments, so for many cases, you only need to provide a few of them. 2073 2074 Params: 2075 2076 width = the width of the window's client area, in pixels 2077 height = the height of the window's client area, in pixels 2078 title = the title of the window (seen in the title bar, taskbar, etc.). You can change it after construction with the [SimpleWindow.title] property. 2079 opengl = [OpenGlOptions] are yes and no. If yes, it creates an OpenGL context on the window. 2080 resizable = [Resizability] has three options: 2081 $(P `allowResizing`, which allows the window to be resized by the user. The `windowResized` delegate will be called when the size is changed.) 2082 $(P `fixedSize` will not allow the user to resize the window.) 2083 $(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.) 2084 windowType = The type of window you want to make. 2085 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. 2086 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". 2087 +/ 2088 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) { 2089 claimGuiThread(); 2090 version(sdpy_thread_checks) assert(thisIsGuiThread); 2091 this._width = this._virtualWidth = width; 2092 this._height = this._virtualHeight = height; 2093 this.openglMode = opengl; 2094 version(X11) { 2095 // auto scale not implemented except with opengl and even there it is kinda weird 2096 if(resizable == Resizability.automaticallyScaleIfPossible && opengl == OpenGlOptions.no) 2097 resizable = Resizability.fixedSize; 2098 } 2099 this.resizability = resizable; 2100 this.windowType = windowType; 2101 this.customizationFlags = customizationFlags; 2102 this._title = (title is null ? "D Application" : title); 2103 this._parent = parent; 2104 impl.createWindow(width, height, this._title, opengl, parent); 2105 2106 if(windowType == WindowTypes.dropdownMenu || windowType == WindowTypes.popupMenu || windowType == WindowTypes.nestedChild || (customizationFlags & WindowFlags.transient)) 2107 beingOpenKeepsAppOpen = false; 2108 } 2109 2110 /// ditto 2111 this(int width, int height, string title, Resizability resizable, OpenGlOptions opengl = OpenGlOptions.no, WindowTypes windowType = WindowTypes.normal, int customizationFlags = WindowFlags.normal, SimpleWindow parent = null) { 2112 this(width, height, title, opengl, resizable, windowType, customizationFlags, parent); 2113 } 2114 2115 /// Same as above, except using the `Size` struct instead of separate width and height. 2116 this(Size size, string title = null, OpenGlOptions opengl = OpenGlOptions.no, Resizability resizable = Resizability.automaticallyScaleIfPossible) { 2117 this(size.width, size.height, title, opengl, resizable); 2118 } 2119 2120 /// ditto 2121 this(Size size, string title, Resizability resizable, OpenGlOptions opengl = OpenGlOptions.no) { 2122 this(size, title, opengl, resizable); 2123 } 2124 2125 2126 /++ 2127 Creates a window based on the given [Image]. It's client area 2128 width and height is equal to the image. (A window's client area 2129 is the drawable space inside; it excludes the title bar, etc.) 2130 2131 Windows based on images will not be resizable and do not use OpenGL. 2132 2133 It will draw the image in upon creation, but this will be overwritten 2134 upon any draws, including the initial window visible event. 2135 2136 You probably do not want to use this and it may be removed from 2137 the library eventually, or I might change it to be a "permanent" 2138 background image; one that is automatically drawn on it before any 2139 other drawing event. idk. 2140 +/ 2141 this(Image image, string title = null) { 2142 this(image.width, image.height, title); 2143 this.image = image; 2144 } 2145 2146 /++ 2147 Wraps a native window handle with very little additional processing - notably no destruction 2148 this is incomplete so don't use it for much right now. The purpose of this is to make native 2149 windows created through the low level API (so you can use platform-specific options and 2150 other details SimpleWindow does not expose) available to the event loop wrappers. 2151 +/ 2152 this(NativeWindowHandle nativeWindow) { 2153 windowType = WindowTypes.minimallyWrapped; 2154 version(Windows) 2155 impl.hwnd = nativeWindow; 2156 else version(X11) { 2157 impl.window = nativeWindow; 2158 if(nativeWindow) 2159 display = XDisplayConnection.get(); // get initial display to not segfault 2160 } else version(OSXCocoa) { 2161 if(nativeWindow !is NullWindow) throw new NotYetImplementedException(); 2162 } else featureNotImplemented(); 2163 // FIXME: set the size correctly 2164 _width = 1; 2165 _height = 1; 2166 if(nativeWindow) 2167 nativeMapping[cast(void*) nativeWindow] = this; 2168 2169 beingOpenKeepsAppOpen = false; 2170 2171 if(nativeWindow) 2172 CapableOfHandlingNativeEvent.nativeHandleMapping[nativeWindow] = this; 2173 _suppressDestruction = true; // so it doesn't try to close 2174 } 2175 2176 /++ 2177 Used iff [WindowFlags.managesChildWindowFocus] is set when the window is created. 2178 The delegate will be called when the window manager asks you to take focus. 2179 2180 This is currently only used for the WM_TAKE_FOCUS protocol on X11 at this time. 2181 2182 History: 2183 Added April 1, 2022 (dub v10.8) 2184 +/ 2185 SimpleWindow delegate() setRequestedInputFocus; 2186 2187 /// Experimental, do not use yet 2188 /++ 2189 Grabs exclusive input from the user until you release it with 2190 [releaseInputGrab]. 2191 2192 2193 Note: it is extremely rude to do this without good reason. 2194 Reasons may include doing some kind of mouse drag operation 2195 or popping up a temporary menu that should get events and will 2196 be dismissed at ease by the user clicking away. 2197 2198 Params: 2199 keyboard = do you want to grab keyboard input? 2200 mouse = grab mouse input? 2201 confine = confine the mouse cursor to inside this window? 2202 2203 History: 2204 Prior to March 11, 2021, grabbing the keyboard would always also 2205 set the X input focus. Now, it only focuses if it is a non-transient 2206 window and otherwise manages the input direction internally. 2207 2208 This means spurious focus/blur events will no longer be sent and the 2209 application will not steal focus from other applications (which the 2210 window manager may have rejected anyway). 2211 +/ 2212 void grabInput(bool keyboard = true, bool mouse = true, bool confine = false) { 2213 static if(UsingSimpledisplayX11) { 2214 XSync(XDisplayConnection.get, 0); 2215 if(keyboard) { 2216 if(isTransient && _parent) { 2217 /* 2218 FIXME: 2219 setting the keyboard focus is not actually that helpful, what I more likely want 2220 is the events from the parent window to be sent over here if we're transient. 2221 */ 2222 2223 _parent.inputProxy = this; 2224 } else { 2225 2226 SimpleWindow setTo; 2227 if(setRequestedInputFocus !is null) 2228 setTo = setRequestedInputFocus(); 2229 if(setTo is null) 2230 setTo = this; 2231 XSetInputFocus(XDisplayConnection.get, setTo.impl.window, RevertToParent, CurrentTime); 2232 } 2233 } 2234 if(mouse) { 2235 if(auto res = XGrabPointer(XDisplayConnection.get, this.impl.window, false /* owner_events */, 2236 EventMask.PointerMotionMask // FIXME: not efficient 2237 | EventMask.ButtonPressMask 2238 | EventMask.ButtonReleaseMask 2239 /* event mask */, GrabMode.GrabModeAsync, GrabMode.GrabModeAsync, confine ? this.impl.window : None, None, CurrentTime) 2240 ) 2241 { 2242 XSync(XDisplayConnection.get, 0); 2243 import core.stdc.stdio; 2244 printf("Grab input failed %d\n", res); 2245 //throw new Exception("Grab input failed"); 2246 } else { 2247 // cool 2248 } 2249 } 2250 2251 } else version(Windows) { 2252 // FIXME: keyboard? 2253 SetCapture(impl.hwnd); 2254 if(confine) { 2255 RECT rcClip; 2256 //RECT rcOldClip; 2257 //GetClipCursor(&rcOldClip); 2258 GetWindowRect(hwnd, &rcClip); 2259 ClipCursor(&rcClip); 2260 } 2261 } else version(OSXCocoa) { 2262 // throw new NotYetImplementedException(); 2263 } else static assert(0); 2264 } 2265 2266 private Point imePopupLocation = Point(0, 0); 2267 2268 /++ 2269 Sets the location for the IME (input method editor) to pop up when the user activates it. 2270 2271 Bugs: 2272 Not implemented outside X11. 2273 +/ 2274 void setIMEPopupLocation(Point location) { 2275 static if(UsingSimpledisplayX11) { 2276 imePopupLocation = location; 2277 updateIMEPopupLocation(); 2278 } else { 2279 // this is non-fatal at this point... but still wanna find it when i search for NotYetImplementedException at least 2280 // throw new NotYetImplementedException(); 2281 } 2282 } 2283 2284 /// ditto 2285 void setIMEPopupLocation(int x, int y) { 2286 return setIMEPopupLocation(Point(x, y)); 2287 } 2288 2289 // we need to remind XIM of where we wanted to place the IME whenever the window moves 2290 // so this function gets called in setIMEPopupLocation as well as whenever the window 2291 // receives a ConfigureNotify event 2292 private void updateIMEPopupLocation() { 2293 static if(UsingSimpledisplayX11) { 2294 if (xic is null) { 2295 return; 2296 } 2297 2298 XPoint nspot; 2299 nspot.x = cast(short) imePopupLocation.x; 2300 nspot.y = cast(short) imePopupLocation.y; 2301 XVaNestedList preeditAttr = XVaCreateNestedList(0, /*XNSpotLocation*/"spotLocation".ptr, &nspot, null); 2302 XSetICValues(xic, /*XNPreeditAttributes*/"preeditAttributes".ptr, preeditAttr, null); 2303 XFree(preeditAttr); 2304 } 2305 } 2306 2307 private bool imeFocused = true; 2308 2309 /++ 2310 Tells the IME whether or not an input field is currently focused in the window. 2311 2312 Bugs: 2313 Not implemented outside X11. 2314 +/ 2315 void setIMEFocused(bool value) { 2316 imeFocused = value; 2317 updateIMEFocused(); 2318 } 2319 2320 // used to focus/unfocus the IC if necessary when the window gains/loses focus 2321 private void updateIMEFocused() { 2322 static if(UsingSimpledisplayX11) { 2323 if (xic is null) { 2324 return; 2325 } 2326 2327 if (focused && imeFocused) { 2328 XSetICFocus(xic); 2329 } else { 2330 XUnsetICFocus(xic); 2331 } 2332 } 2333 } 2334 2335 /++ 2336 Returns the native window. 2337 2338 History: 2339 Added November 5, 2021 (dub v10.4). Prior to that, you'd have 2340 to access it through the `impl` member (which is semi-supported 2341 but platform specific and here it is simple enough to offer an accessor). 2342 2343 Bugs: 2344 Not implemented outside Windows or X11. 2345 +/ 2346 NativeWindowHandle nativeWindowHandle() { 2347 version(X11) 2348 return impl.window; 2349 else version(Windows) 2350 return impl.hwnd; 2351 else 2352 throw new NotYetImplementedException(); 2353 } 2354 2355 private bool isTransient() { 2356 with(WindowTypes) 2357 final switch(windowType) { 2358 case normal, undecorated, eventOnly: 2359 case nestedChild, minimallyWrapped: 2360 return (customizationFlags & WindowFlags.transient) ? true : false; 2361 case dropdownMenu, popupMenu, notification: 2362 return true; 2363 } 2364 } 2365 2366 private SimpleWindow inputProxy; 2367 2368 /++ 2369 Releases the grab acquired by [grabInput]. 2370 +/ 2371 void releaseInputGrab() { 2372 static if(UsingSimpledisplayX11) { 2373 XUngrabPointer(XDisplayConnection.get, CurrentTime); 2374 if(_parent) 2375 _parent.inputProxy = null; 2376 } else version(Windows) { 2377 ReleaseCapture(); 2378 ClipCursor(null); 2379 } else version(OSXCocoa) { 2380 // throw new NotYetImplementedException(); 2381 } else static assert(0); 2382 } 2383 2384 /++ 2385 Sets the input focus to this window. 2386 2387 You shouldn't call this very often - please let the user control the input focus. 2388 +/ 2389 void focus() { 2390 static if(UsingSimpledisplayX11) { 2391 SimpleWindow setTo; 2392 if(setRequestedInputFocus !is null) 2393 setTo = setRequestedInputFocus(); 2394 if(setTo is null) 2395 setTo = this; 2396 XSetInputFocus(XDisplayConnection.get, setTo.impl.window, RevertToParent, CurrentTime); 2397 } else version(Windows) { 2398 SetFocus(this.impl.hwnd); 2399 } else version(OSXCocoa) { 2400 throw new NotYetImplementedException(); 2401 } else static assert(0); 2402 } 2403 2404 /++ 2405 Requests attention from the user for this window. 2406 2407 2408 The typical result of this function is to change the color 2409 of the taskbar icon, though it may be tweaked on specific 2410 platforms. 2411 2412 It is meant to unobtrusively tell the user that something 2413 relevant to them happened in the background and they should 2414 check the window when they get a chance. Upon receiving the 2415 keyboard focus, the window will automatically return to its 2416 natural state. 2417 2418 If the window already has the keyboard focus, this function 2419 may do nothing, because the user is presumed to already be 2420 giving the window attention. 2421 2422 Implementation_note: 2423 2424 `requestAttention` uses the _NET_WM_STATE_DEMANDS_ATTENTION 2425 atom on X11 and the FlashWindow function on Windows. 2426 +/ 2427 void requestAttention() { 2428 if(_focused) 2429 return; 2430 2431 version(Windows) { 2432 FLASHWINFO info; 2433 info.cbSize = info.sizeof; 2434 info.hwnd = impl.hwnd; 2435 info.dwFlags = FLASHW_TRAY; 2436 info.uCount = 1; 2437 2438 FlashWindowEx(&info); 2439 2440 } else version(X11) { 2441 demandingAttention = true; 2442 demandAttention(this, true); 2443 } else version(OSXCocoa) { 2444 throw new NotYetImplementedException(); 2445 } else static assert(0); 2446 } 2447 2448 private bool _focused; 2449 2450 version(X11) private bool demandingAttention; 2451 2452 /// This will be called when WM wants to close your window (i.e. user clicked "close" icon, for example). 2453 /// You'll have to call `close()` manually if you set this delegate. 2454 void delegate () closeQuery; 2455 2456 /// This will be called when window visibility was changed. 2457 void delegate (bool becomesVisible) visibilityChanged; 2458 2459 /// This will be called when window becomes visible for the first time. 2460 /// You can do OpenGL initialization here. Note that in X11 you can't call 2461 /// [setAsCurrentOpenGlContext] right after window creation, or X11 may 2462 /// fail to send reparent and map events (hit that with proprietary NVidia drivers). 2463 /// So you need to wait until this is called and call setAsCurrentOpenGlContext in there, then do the OpenGL initialization. 2464 private bool _visibleForTheFirstTimeCalled; 2465 void delegate () visibleForTheFirstTime; 2466 2467 /// Returns true if the window has been closed. 2468 final @property bool closed() { return _closed; } 2469 2470 private final @property bool notClosed() { return !_closed; } 2471 2472 /// Returns true if the window is focused. 2473 final @property bool focused() { return _focused; } 2474 2475 private bool _visible; 2476 /// Returns true if the window is visible (mapped). 2477 final @property bool visible() { return _visible; } 2478 2479 /// Closes the window. If there are no more open windows, the event loop will terminate. 2480 void close() { 2481 if (!_closed) { 2482 runInGuiThread( { 2483 if(_closed) return; // another thread got to it first. this isn't a big deal, it just means our message was queued 2484 if (onClosing !is null) onClosing(); 2485 impl.closeWindow(); 2486 _closed = true; 2487 } ); 2488 } 2489 } 2490 2491 /++ 2492 `close` is one of the few methods that can be called from other threads. This `shared` overload reflects that. 2493 2494 History: 2495 Overload added on March 7, 2021. 2496 +/ 2497 void close() shared { 2498 (cast() this).close(); 2499 } 2500 2501 /++ 2502 2503 +/ 2504 void maximize() { 2505 version(Windows) 2506 ShowWindow(impl.hwnd, SW_MAXIMIZE); 2507 else version(X11) { 2508 setNetWmStateAtom(this.impl.window, GetAtom!("_NET_WM_STATE_MAXIMIZED_VERT", false)(XDisplayConnection.get), true, GetAtom!("_NET_WM_STATE_MAXIMIZED_HORZ", false)(XDisplayConnection.get)); 2509 2510 // also note _NET_WM_STATE_FULLSCREEN 2511 } 2512 2513 } 2514 2515 private bool _fullscreen; 2516 version(Windows) 2517 private WINDOWPLACEMENT g_wpPrev; 2518 2519 /// not fully implemented but planned for a future release 2520 void fullscreen(bool yes) { 2521 version(Windows) { 2522 g_wpPrev.length = WINDOWPLACEMENT.sizeof; 2523 DWORD dwStyle = GetWindowLong(hwnd, GWL_STYLE); 2524 if (dwStyle & WS_OVERLAPPEDWINDOW) { 2525 MONITORINFO mi; 2526 mi.cbSize = MONITORINFO.sizeof; 2527 if (GetWindowPlacement(hwnd, &g_wpPrev) && 2528 GetMonitorInfo(MonitorFromWindow(hwnd, 2529 MONITOR_DEFAULTTOPRIMARY), &mi)) { 2530 SetWindowLong(hwnd, GWL_STYLE, 2531 dwStyle & ~WS_OVERLAPPEDWINDOW); 2532 SetWindowPos(hwnd, HWND_TOP, 2533 mi.rcMonitor.left, mi.rcMonitor.top, 2534 mi.rcMonitor.right - mi.rcMonitor.left, 2535 mi.rcMonitor.bottom - mi.rcMonitor.top, 2536 SWP_NOOWNERZORDER | SWP_FRAMECHANGED); 2537 } 2538 } else { 2539 SetWindowLong(hwnd, GWL_STYLE, 2540 dwStyle | WS_OVERLAPPEDWINDOW); 2541 SetWindowPlacement(hwnd, &g_wpPrev); 2542 SetWindowPos(hwnd, null, 0, 0, 0, 0, 2543 SWP_NOMOVE | SWP_NOSIZE | SWP_NOZORDER | 2544 SWP_NOOWNERZORDER | SWP_FRAMECHANGED); 2545 } 2546 2547 } else version(X11) { 2548 setNetWmStateAtom(this.impl.window, GetAtom!("_NET_WM_STATE_FULLSCREEN", false)(XDisplayConnection.get), yes); 2549 } 2550 2551 _fullscreen = yes; 2552 2553 } 2554 2555 bool fullscreen() { 2556 return _fullscreen; 2557 } 2558 2559 /++ 2560 Note: only implemented on Windows. No-op on other platforms. You may want to use [hide] instead. 2561 2562 +/ 2563 void minimize() { 2564 version(Windows) 2565 ShowWindow(impl.hwnd, SW_MINIMIZE); 2566 //else version(X11) 2567 //setNetWmStateAtom(this, GetAtom!("_NET_WM_STATE_MINIMIZED", false)(XDisplayConnection.get), true); 2568 } 2569 2570 /// Alias for `hidden = false` 2571 void show() { 2572 hidden = false; 2573 } 2574 2575 /// Alias for `hidden = true` 2576 void hide() { 2577 hidden = true; 2578 } 2579 2580 /// Hide cursor when it enters the window. 2581 void hideCursor() { 2582 version(OSXCocoa) throw new NotYetImplementedException(); else 2583 if (!_closed) impl.hideCursor(); 2584 } 2585 2586 /// Don't hide cursor when it enters the window. 2587 void showCursor() { 2588 version(OSXCocoa) throw new NotYetImplementedException(); else 2589 if (!_closed) impl.showCursor(); 2590 } 2591 2592 /** "Warp" mouse pointer to coordinates relative to window top-left corner. Return "success" flag. 2593 * 2594 * Please remember that the cursor is a shared resource that should usually be left to the user's 2595 * control. Try to think for other approaches before using this function. 2596 * 2597 * Note: "warping" pointer will not send any synthesised mouse events, so you probably doesn't want 2598 * to use it to move mouse pointer to some active GUI area, for example, as your window won't 2599 * receive "mouse moved here" event. 2600 */ 2601 bool warpMouse (int x, int y) { 2602 version(X11) { 2603 if (!_closed) { impl.warpMouse(x, y); return true; } 2604 } else version(Windows) { 2605 if (!_closed) { 2606 POINT point; 2607 point.x = x; 2608 point.y = y; 2609 if(ClientToScreen(impl.hwnd, &point)) { 2610 SetCursorPos(point.x, point.y); 2611 return true; 2612 } 2613 } 2614 } 2615 return false; 2616 } 2617 2618 /// Send dummy window event to ping event loop. Required to process NotificationIcon on X11, for example. 2619 void sendDummyEvent () { 2620 version(X11) { 2621 if (!_closed) { impl.sendDummyEvent(); } 2622 } 2623 } 2624 2625 /// Set window minimal size. 2626 void setMinSize (int minwidth, int minheight) { 2627 version(OSXCocoa) throw new NotYetImplementedException(); else 2628 if (!_closed) impl.setMinSize(minwidth, minheight); 2629 } 2630 2631 /// Set window maximal size. 2632 void setMaxSize (int maxwidth, int maxheight) { 2633 version(OSXCocoa) throw new NotYetImplementedException(); else 2634 if (!_closed) impl.setMaxSize(maxwidth, maxheight); 2635 } 2636 2637 /// Set window resize step (window size will be changed with the given granularity on supported platforms). 2638 /// Currently only supported on X11. 2639 void setResizeGranularity (int granx, int grany) { 2640 version(OSXCocoa) throw new NotYetImplementedException(); else 2641 if (!_closed) impl.setResizeGranularity(granx, grany); 2642 } 2643 2644 /// Move window. 2645 void move(int x, int y) { 2646 version(OSXCocoa) throw new NotYetImplementedException(); else 2647 if (!_closed) impl.move(x, y); 2648 } 2649 2650 /// ditto 2651 void move(Point p) { 2652 version(OSXCocoa) throw new NotYetImplementedException(); else 2653 if (!_closed) impl.move(p.x, p.y); 2654 } 2655 2656 /++ 2657 Resize window. 2658 2659 Note that the width and height of the window are NOT instantly 2660 updated - it waits for the window manager to approve the resize 2661 request, which means you must return to the event loop before the 2662 width and height are actually changed. 2663 +/ 2664 void resize(int w, int h) { 2665 if(!_closed && _fullscreen) fullscreen = false; 2666 version(OSXCocoa) throw new NotYetImplementedException(); else 2667 if (!_closed) impl.resize(w, h); 2668 } 2669 2670 /// Move and resize window (this can be faster and more visually pleasant than doing it separately). 2671 void moveResize (int x, int y, int w, int h) { 2672 if(!_closed && _fullscreen) fullscreen = false; 2673 version(OSXCocoa) throw new NotYetImplementedException(); else 2674 if (!_closed) impl.moveResize(x, y, w, h); 2675 } 2676 2677 private bool _hidden; 2678 2679 /// Returns true if the window is hidden. 2680 final @property bool hidden() { 2681 return _hidden; 2682 } 2683 2684 /// Shows or hides the window based on the bool argument. 2685 final @property void hidden(bool b) { 2686 _hidden = b; 2687 version(Windows) { 2688 ShowWindow(impl.hwnd, b ? SW_HIDE : SW_SHOW); 2689 } else version(X11) { 2690 if(b) 2691 //XUnmapWindow(impl.display, impl.window); 2692 XWithdrawWindow(impl.display, impl.window, DefaultScreen(impl.display)); 2693 else 2694 XMapWindow(impl.display, impl.window); 2695 } else version(OSXCocoa) { 2696 // throw new NotYetImplementedException(); 2697 } else static assert(0); 2698 } 2699 2700 /// Sets the window opacity. On X11 this requires a compositor to be running. On windows the WindowFlags.extraComposite must be set at window creation. 2701 void opacity(double opacity) @property 2702 in { 2703 assert(opacity >= 0 && opacity <= 1); 2704 } do { 2705 version (Windows) { 2706 impl.setOpacity(cast(ubyte)(255 * opacity)); 2707 } else version (X11) { 2708 impl.setOpacity(cast(uint)(uint.max * opacity)); 2709 } else throw new NotYetImplementedException(); 2710 } 2711 2712 /++ 2713 Sets your event handlers, without entering the event loop. Useful if you 2714 have multiple windows - set the handlers on each window, then only do 2715 [eventLoop] on your main window or call `EventLoop.get.run();`. 2716 2717 This assigns the given handlers to [handleKeyEvent], [handleCharEvent], 2718 [handlePulse], and [handleMouseEvent] automatically based on the provide 2719 delegate signatures. 2720 +/ 2721 void setEventHandlers(T...)(T eventHandlers) { 2722 // FIXME: add more events 2723 foreach(handler; eventHandlers) { 2724 static if(__traits(compiles, handleKeyEvent = handler)) { 2725 handleKeyEvent = handler; 2726 } else static if(__traits(compiles, handleCharEvent = handler)) { 2727 handleCharEvent = handler; 2728 } else static if(__traits(compiles, handlePulse = handler)) { 2729 handlePulse = handler; 2730 } else static if(__traits(compiles, handleMouseEvent = handler)) { 2731 handleMouseEvent = handler; 2732 } else static assert(0, "I can't use this event handler " ~ typeof(handler).stringof ~ "\nHave you tried using the delegate keyword?"); 2733 } 2734 } 2735 2736 /++ 2737 The event loop automatically returns when the window is closed 2738 pulseTimeout is given in milliseconds. If pulseTimeout == 0, no 2739 pulse timer is created. The event loop will block until an event 2740 arrives or the pulse timer goes off. 2741 2742 The given `eventHandlers` are passed to [setEventHandlers], which in turn 2743 assigns them to [handleKeyEvent], [handleCharEvent], [handlePulse], and 2744 [handleMouseEvent], based on the signature of delegates you provide. 2745 2746 Give one with no parameters to set a timer pulse handler. Give one that 2747 takes [KeyEvent] for a key handler, [MouseEvent], for a mouse handler, 2748 and one that takes `dchar` for a char event handler. You can use as many 2749 or as few handlers as you need for your application. 2750 2751 Bugs: 2752 2753 $(PITFALL 2754 You should always have one event loop live for your application. 2755 If you make two windows in sequence, the second call to eventLoop 2756 might fail: 2757 2758 --- 2759 // don't do this! 2760 auto window = new SimpleWindow(); 2761 window.eventLoop(0); 2762 2763 auto window2 = new SimpleWindow(); 2764 window2.eventLoop(0); // problematic! might crash 2765 --- 2766 2767 simpledisplay's current implementation assumes that final cleanup is 2768 done when the event loop refcount reaches zero. So after the first 2769 eventLoop returns, when there isn't already another one active, it assumes 2770 the program will exit soon and cleans up. 2771 2772 This is arguably a bug that it doesn't reinitialize, and I'll probably change 2773 it eventually, but in the mean time, there's an easy solution: 2774 2775 --- 2776 // do this 2777 EventLoop mainEventLoop = EventLoop.get; // just add this line 2778 2779 auto window = new SimpleWindow(); 2780 window.eventLoop(0); 2781 2782 auto window2 = new SimpleWindow(); 2783 window2.eventLoop(0); // perfectly fine since mainEventLoop still alive 2784 --- 2785 2786 By adding a top-level reference to the event loop, it ensures the final cleanup 2787 is not performed until it goes out of scope too, letting the individual window loops 2788 work without trouble despite the bug. 2789 ) 2790 2791 History: 2792 The overload without `pulseTimeout` was added on December 8, 2021. 2793 2794 On December 9, 2021, the default blocking mode (which is now configurable 2795 because [eventLoopWithBlockingMode] was added) switched from 2796 [BlockingMode.untilApplicationQuits] over to [BlockingMode.automatic]. This 2797 should almost never be noticeable to you since the typical simpledisplay 2798 paradigm has been (and I still recommend) to have one `eventLoop` call. 2799 2800 See_Also: 2801 [eventLoopWithBlockingMode] 2802 +/ 2803 final int eventLoop(T...)( 2804 long pulseTimeout, /// set to zero if you don't want a pulse. 2805 T eventHandlers) /// delegate list like std.concurrency.receive 2806 { 2807 return eventLoopWithBlockingMode(BlockingMode.automatic, pulseTimeout, eventHandlers); 2808 } 2809 2810 /// ditto 2811 final int eventLoop(T...)(T eventHandlers) if(T.length == 0 || is(T[0] == delegate)) 2812 { 2813 return eventLoopWithBlockingMode(BlockingMode.automatic, 0, eventHandlers); 2814 } 2815 2816 /++ 2817 This is the function [eventLoop] forwards to. It, in turn, forwards to `EventLoop.get.run`. 2818 2819 History: 2820 Added December 8, 2021 (dub v10.5) 2821 2822 Previously, this implementation was right inside [eventLoop], but when I wanted 2823 to add the new [BlockingMode] parameter, the compiler got in a trouble loop so I 2824 just renamed it instead of adding as an overload. Besides, the new name makes it 2825 easier to remember the order and avoids ambiguity between two int-like params anyway. 2826 2827 See_Also: 2828 [SimpleWindow.eventLoop], [EventLoop] 2829 2830 Bugs: 2831 The blocking mode is not implemented on OSX Cocoa nor on the (deprecated) arsd.eventloop. 2832 +/ 2833 final int eventLoopWithBlockingMode(T...)( 2834 BlockingMode blockingMode, /// when you want this function to block until 2835 long pulseTimeout, /// set to zero if you don't want a pulse. 2836 T eventHandlers) /// delegate list like std.concurrency.receive 2837 { 2838 setEventHandlers(eventHandlers); 2839 2840 version(with_eventloop) { 2841 // delegates event loop to my other module 2842 version(X11) 2843 XFlush(display); 2844 2845 import arsd.eventloop; 2846 auto handle = setInterval(handlePulse, cast(int) pulseTimeout); 2847 scope(exit) clearInterval(handle); 2848 2849 loop(); 2850 return 0; 2851 } else version(OSXCocoa) { 2852 // FIXME 2853 if (handlePulse !is null && pulseTimeout != 0) { 2854 timer = NSTimer.schedule(pulseTimeout*1e-3, 2855 cast(NSid) view, sel_registerName("simpledisplay_pulse:"), 2856 null, true); 2857 } 2858 2859 view.setNeedsDisplay(true); 2860 2861 NSApp.run(); 2862 return 0; 2863 } else { 2864 EventLoop el = EventLoop(pulseTimeout, handlePulse); 2865 2866 if((blockingMode & BlockingMode.onlyIfNotNested) && el.impl.refcount > 1) 2867 return 0; 2868 2869 return el.run( 2870 ((blockingMode & 0x0f) == BlockingMode.untilApplicationQuits) ? 2871 null : 2872 &this.notClosed 2873 ); 2874 } 2875 } 2876 2877 /++ 2878 This lets you draw on the window (or its backing buffer) using basic 2879 2D primitives. 2880 2881 Be sure to call this in a limited scope because your changes will not 2882 actually appear on the window until ScreenPainter's destructor runs. 2883 2884 Returns: an instance of [ScreenPainter], which has the drawing methods 2885 on it to draw on this window. 2886 2887 Params: 2888 manualInvalidations = if you set this to true, you will need to 2889 set the invalid rectangle on the painter yourself. If false, it 2890 assumes the whole window has been redrawn each time you draw. 2891 2892 Only invalidated rectangles are blitted back to the window when 2893 the destructor runs. Doing this yourself can reduce flickering 2894 of child windows. 2895 2896 History: 2897 The `manualInvalidations` parameter overload was added on 2898 December 30, 2021 (dub v10.5) 2899 +/ 2900 ScreenPainter draw() { 2901 return draw(false); 2902 } 2903 /// ditto 2904 ScreenPainter draw(bool manualInvalidations) { 2905 return impl.getPainter(manualInvalidations); 2906 } 2907 2908 // This is here to implement the interface we use for various native handlers. 2909 NativeEventHandler getNativeEventHandler() { return handleNativeEvent; } 2910 2911 // maps native window handles to SimpleWindow instances, if there are any 2912 // you shouldn't need this, but it is public in case you do in a native event handler or something 2913 // mac uses void* cuz NSObject opHash won't pick up in typeinfo 2914 version(OSXCocoa) 2915 public __gshared SimpleWindow[void*] nativeMapping; 2916 else 2917 public __gshared SimpleWindow[NativeWindowHandle] nativeMapping; 2918 2919 // the size the user requested in the constructor, in automatic scale modes it always pretends to be this size 2920 private int _virtualWidth; 2921 private int _virtualHeight; 2922 2923 /// Width of the window's drawable client area, in pixels. 2924 @scriptable 2925 final @property int width() const pure nothrow @safe @nogc { 2926 if(resizability == Resizability.automaticallyScaleIfPossible) 2927 return _virtualWidth; 2928 else 2929 return _width; 2930 } 2931 2932 /// Height of the window's drawable client area, in pixels. 2933 @scriptable 2934 final @property int height() const pure nothrow @safe @nogc { 2935 if(resizability == Resizability.automaticallyScaleIfPossible) 2936 return _virtualHeight; 2937 else 2938 return _height; 2939 } 2940 2941 /++ 2942 Returns the actual size of the window, bypassing the logical 2943 illusions of [Resizability.automaticallyScaleIfPossible]. 2944 2945 History: 2946 Added November 11, 2022 (dub v10.10) 2947 +/ 2948 final @property Size actualWindowSize() const pure nothrow @safe @nogc { 2949 return Size(_width, _height); 2950 } 2951 2952 2953 private int _width; 2954 private int _height; 2955 2956 // HACK: making the best of some copy constructor woes with refcounting 2957 private ScreenPainterImplementation* activeScreenPainter_; 2958 2959 protected ScreenPainterImplementation* activeScreenPainter() { return activeScreenPainter_; } 2960 protected void activeScreenPainter(ScreenPainterImplementation* i) { activeScreenPainter_ = i; } 2961 2962 private OpenGlOptions openglMode; 2963 private Resizability resizability; 2964 private WindowTypes windowType; 2965 private int customizationFlags; 2966 2967 /// `true` if OpenGL was initialized for this window. 2968 @property bool isOpenGL () const pure nothrow @safe @nogc { 2969 version(without_opengl) 2970 return false; 2971 else 2972 return (openglMode == OpenGlOptions.yes); 2973 } 2974 @property Resizability resizingMode () const pure nothrow @safe @nogc { return resizability; } /// Original resizability. 2975 @property WindowTypes type () const pure nothrow @safe @nogc { return windowType; } /// Original window type. 2976 @property int customFlags () const pure nothrow @safe @nogc { return customizationFlags; } /// Original customization flags. 2977 2978 /// "Lock" this window handle, to do multithreaded synchronization. You probably won't need 2979 /// to call this, as it's not recommended to share window between threads. 2980 void mtLock () { 2981 version(X11) { 2982 XLockDisplay(this.display); 2983 } 2984 } 2985 2986 /// "Unlock" this window handle, to do multithreaded synchronization. You probably won't need 2987 /// to call this, as it's not recommended to share window between threads. 2988 void mtUnlock () { 2989 version(X11) { 2990 XUnlockDisplay(this.display); 2991 } 2992 } 2993 2994 /// Emit a beep to get user's attention. 2995 void beep () { 2996 version(X11) { 2997 XBell(this.display, 100); 2998 } else version(Windows) { 2999 MessageBeep(0xFFFFFFFF); 3000 } 3001 } 3002 3003 3004 3005 version(without_opengl) {} else { 3006 3007 /// 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`. 3008 void delegate() redrawOpenGlScene; 3009 3010 /// This will allow you to change OpenGL vsync state. 3011 final @property void vsync (bool wait) { 3012 if (this._closed) return; // window may be closed, but timer is still firing; avoid GLXBadDrawable error 3013 version(X11) { 3014 setAsCurrentOpenGlContext(); 3015 glxSetVSync(display, impl.window, wait); 3016 } else version(Windows) { 3017 setAsCurrentOpenGlContext(); 3018 wglSetVSync(wait); 3019 } 3020 } 3021 3022 /// Set this to `false` if you don't need to do `glFinish()` after `swapOpenGlBuffers()`. 3023 /// Note that at least NVidia proprietary driver may segfault if you will modify texture fast 3024 /// enough without waiting 'em to finish their frame business. 3025 bool useGLFinish = true; 3026 3027 // FIXME: it should schedule it for the end of the current iteration of the event loop... 3028 /// 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. 3029 void redrawOpenGlSceneNow() { 3030 version(X11) if (!this._visible) return; // no need to do this if window is invisible 3031 if (this._closed) return; // window may be closed, but timer is still firing; avoid GLXBadDrawable error 3032 if(redrawOpenGlScene is null) 3033 return; 3034 3035 this.mtLock(); 3036 scope(exit) this.mtUnlock(); 3037 3038 this.setAsCurrentOpenGlContext(); 3039 3040 redrawOpenGlScene(); 3041 3042 this.swapOpenGlBuffers(); 3043 // 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. 3044 if (useGLFinish) glFinish(); 3045 } 3046 3047 private bool redrawOpenGlSceneSoonSet = false; 3048 private static class RedrawOpenGlSceneEvent { 3049 SimpleWindow w; 3050 this(SimpleWindow w) { this.w = w; } 3051 } 3052 private RedrawOpenGlSceneEvent redrawOpenGlSceneEvent; 3053 /++ 3054 Queues an opengl redraw as soon as the other pending events are cleared. 3055 +/ 3056 void redrawOpenGlSceneSoon() { 3057 if(redrawOpenGlScene is null) 3058 return; 3059 3060 if(!redrawOpenGlSceneSoonSet) { 3061 redrawOpenGlSceneEvent = new RedrawOpenGlSceneEvent(this); 3062 this.addEventListener((RedrawOpenGlSceneEvent e) { e.w.redrawOpenGlSceneNow(); }); 3063 redrawOpenGlSceneSoonSet = true; 3064 } 3065 this.postEvent(redrawOpenGlSceneEvent, true); 3066 } 3067 3068 3069 /// Makes all gl* functions target this window until changed. This is only valid if you passed `OpenGlOptions.yes` to the constructor. 3070 void setAsCurrentOpenGlContext() { 3071 assert(openglMode == OpenGlOptions.yes); 3072 version(X11) { 3073 if(glXMakeCurrent(display, impl.window, impl.glc) == 0) 3074 throw new Exception("glXMakeCurrent"); 3075 } else version(Windows) { 3076 static if (SdpyIsUsingIVGLBinds) import iv.glbinds; // override druntime windows imports 3077 if (!wglMakeCurrent(ghDC, ghRC)) 3078 throw new Exception("wglMakeCurrent " ~ toInternal!int(GetLastError())); // let windows users suffer too 3079 } 3080 } 3081 3082 /// Makes all gl* functions target this window until changed. This is only valid if you passed `OpenGlOptions.yes` to the constructor. 3083 /// This doesn't throw, returning success flag instead. 3084 bool setAsCurrentOpenGlContextNT() nothrow { 3085 assert(openglMode == OpenGlOptions.yes); 3086 version(X11) { 3087 return (glXMakeCurrent(display, impl.window, impl.glc) != 0); 3088 } else version(Windows) { 3089 static if (SdpyIsUsingIVGLBinds) import iv.glbinds; // override druntime windows imports 3090 return wglMakeCurrent(ghDC, ghRC) ? true : false; 3091 } 3092 } 3093 3094 /// 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. 3095 /// This doesn't throw, returning success flag instead. 3096 bool releaseCurrentOpenGlContext() nothrow { 3097 assert(openglMode == OpenGlOptions.yes); 3098 version(X11) { 3099 return (glXMakeCurrent(display, 0, null) != 0); 3100 } else version(Windows) { 3101 static if (SdpyIsUsingIVGLBinds) import iv.glbinds; // override druntime windows imports 3102 return wglMakeCurrent(ghDC, null) ? true : false; 3103 } 3104 } 3105 3106 /++ 3107 simpledisplay always uses double buffering, usually automatically. This 3108 manually swaps the OpenGL buffers. 3109 3110 3111 You should not need to call this yourself because simpledisplay will do it 3112 for you after calling your `redrawOpenGlScene`. 3113 3114 Remember that this may throw an exception, which you can catch in a multithreaded 3115 application to keep your thread from dying from an unhandled exception. 3116 +/ 3117 void swapOpenGlBuffers() { 3118 assert(openglMode == OpenGlOptions.yes); 3119 version(X11) { 3120 if (!this._visible) return; // no need to do this if window is invisible 3121 if (this._closed) return; // window may be closed, but timer is still firing; avoid GLXBadDrawable error 3122 glXSwapBuffers(display, impl.window); 3123 } else version(Windows) { 3124 SwapBuffers(ghDC); 3125 } 3126 } 3127 } 3128 3129 /++ 3130 Set the window title, which is visible on the window manager title bar, operating system taskbar, etc. 3131 3132 3133 --- 3134 auto window = new SimpleWindow(100, 100, "First title"); 3135 window.title = "A new title"; 3136 --- 3137 3138 You may call this function at any time. 3139 +/ 3140 @property void title(string title) { 3141 _title = title; 3142 version(OSXCocoa) throw new NotYetImplementedException(); else 3143 impl.setTitle(title); 3144 } 3145 3146 private string _title; 3147 3148 /// Gets the title 3149 @property string title() { 3150 if(_title is null) 3151 _title = getRealTitle(); 3152 return _title; 3153 } 3154 3155 /++ 3156 Get the title as set by the window manager. 3157 May not match what you attempted to set. 3158 +/ 3159 string getRealTitle() { 3160 static if(is(typeof(impl.getTitle()))) 3161 return impl.getTitle(); 3162 else 3163 return null; 3164 } 3165 3166 // don't use this generally it is not yet really released 3167 version(X11) 3168 @property Image secret_icon() { 3169 return secret_icon_inner; 3170 } 3171 private Image secret_icon_inner; 3172 3173 3174 /// Set the icon that is seen in the title bar or taskbar, etc., for the user. If passed `null`, does nothing. 3175 @property void icon(MemoryImage icon) { 3176 if(icon is null) 3177 return; 3178 auto tci = icon.getAsTrueColorImage(); 3179 version(Windows) { 3180 winIcon = new WindowsIcon(icon); 3181 SendMessageA(impl.hwnd, 0x0080 /*WM_SETICON*/, 0 /*ICON_SMALL*/, cast(LPARAM) winIcon.hIcon); // there is also 1 == ICON_BIG 3182 } else version(X11) { 3183 secret_icon_inner = Image.fromMemoryImage(icon); 3184 // FIXME: ensure this is correct 3185 auto display = XDisplayConnection.get; 3186 arch_ulong[] buffer; 3187 buffer ~= icon.width; 3188 buffer ~= icon.height; 3189 foreach(c; tci.imageData.colors) { 3190 arch_ulong b; 3191 b |= c.a << 24; 3192 b |= c.r << 16; 3193 b |= c.g << 8; 3194 b |= c.b; 3195 buffer ~= b; 3196 } 3197 3198 XChangeProperty( 3199 display, 3200 impl.window, 3201 GetAtom!("_NET_WM_ICON", true)(display), 3202 GetAtom!"CARDINAL"(display), 3203 32 /* bits */, 3204 0 /*PropModeReplace*/, 3205 buffer.ptr, 3206 cast(int) buffer.length); 3207 } else version(OSXCocoa) { 3208 throw new NotYetImplementedException(); 3209 } else static assert(0); 3210 } 3211 3212 version(Windows) 3213 private WindowsIcon winIcon; 3214 3215 bool _suppressDestruction; 3216 3217 ~this() { 3218 if(!thisIsGuiThread) return; // FIXME: leaks if multithreaded gc 3219 if(_suppressDestruction) 3220 return; 3221 impl.dispose(); 3222 } 3223 3224 private bool _closed; 3225 3226 // the idea here is to draw something temporary on top of the main picture e.g. a blinking cursor 3227 /* 3228 ScreenPainter drawTransiently() { 3229 return impl.getPainter(); 3230 } 3231 */ 3232 3233 /// Draws an image on the window. This is meant to provide quick look 3234 /// of a static image generated elsewhere. 3235 @property void image(Image i) { 3236 /+ 3237 version(Windows) { 3238 BITMAP bm; 3239 HDC hdc = GetDC(hwnd); 3240 HDC hdcMem = CreateCompatibleDC(hdc); 3241 HBITMAP hbmOld = SelectObject(hdcMem, i.handle); 3242 3243 GetObject(i.handle, bm.sizeof, &bm); 3244 3245 BitBlt(hdc, 0, 0, bm.bmWidth, bm.bmHeight, hdcMem, 0, 0, SRCCOPY); 3246 3247 SelectObject(hdcMem, hbmOld); 3248 DeleteDC(hdcMem); 3249 ReleaseDC(hwnd, hdc); 3250 3251 /* 3252 RECT r; 3253 r.right = i.width; 3254 r.bottom = i.height; 3255 InvalidateRect(hwnd, &r, false); 3256 */ 3257 } else 3258 version(X11) { 3259 if(!destroyed) { 3260 if(i.usingXshm) 3261 XShmPutImage(display, cast(Drawable) window, gc, i.handle, 0, 0, 0, 0, i.width, i.height, false); 3262 else 3263 XPutImage(display, cast(Drawable) window, gc, i.handle, 0, 0, 0, 0, i.width, i.height); 3264 } 3265 } else 3266 version(OSXCocoa) { 3267 draw().drawImage(Point(0, 0), i); 3268 setNeedsDisplay(view, true); 3269 } else static assert(0); 3270 +/ 3271 auto painter = this.draw; 3272 painter.drawImage(Point(0, 0), i); 3273 } 3274 3275 /++ 3276 Changes the cursor for the window. If the cursor is hidden via [hideCursor], this has no effect. 3277 3278 --- 3279 window.cursor = GenericCursor.Help; 3280 // now the window mouse cursor is set to a generic help 3281 --- 3282 3283 +/ 3284 @property void cursor(MouseCursor cursor) { 3285 version(OSXCocoa) 3286 {} // featureNotImplemented(); 3287 else 3288 if(this.impl.curHidden <= 0) { 3289 static if(UsingSimpledisplayX11) { 3290 auto ch = cursor.cursorHandle; 3291 XDefineCursor(XDisplayConnection.get(), this.impl.window, ch); 3292 } else version(Windows) { 3293 auto ch = cursor.cursorHandle; 3294 impl.currentCursor = ch; 3295 SetCursor(ch); // redraw without waiting for mouse movement to update 3296 } else featureNotImplemented(); 3297 } 3298 3299 } 3300 3301 /// What follows are the event handlers. These are set automatically 3302 /// by the eventLoop function, but are still public so you can change 3303 /// them later. wasPressed == true means key down. false == key up. 3304 3305 /// Handles a low-level keyboard event. Settable through setEventHandlers. 3306 void delegate(KeyEvent ke) handleKeyEvent; 3307 3308 /// Handles a higher level keyboard event - c is the character just pressed. Settable through setEventHandlers. 3309 void delegate(dchar c) handleCharEvent; 3310 3311 /// Handles a timer pulse. Settable through setEventHandlers. 3312 void delegate() handlePulse; 3313 3314 /// Called when the focus changes, param is if we have it (true) or are losing it (false). 3315 void delegate(bool) onFocusChange; 3316 3317 /** Called inside `close()` method. Our window is still alive, and we can free various resources. 3318 * Sometimes it is easier to setup the delegate instead of subclassing. */ 3319 void delegate() onClosing; 3320 3321 /** Called when we received destroy notification. At this stage we cannot do much with our window 3322 * (as it is already dead, and it's native handle cannot be used), but we still can do some 3323 * last minute cleanup. */ 3324 void delegate() onDestroyed; 3325 3326 static if (UsingSimpledisplayX11) 3327 /** Called when Expose event comes. See Xlib manual to understand the arguments. 3328 * Return `false` if you want Simpledisplay to copy backbuffer, or `true` if you did it yourself. 3329 * You will probably never need to setup this handler, it is for very low-level stuff. 3330 * 3331 * WARNING! Xlib is multithread-locked when this handles is called! */ 3332 bool delegate(int x, int y, int width, int height, int eventsLeft) handleExpose; 3333 3334 //version(Windows) 3335 //bool delegate(WPARAM wParam, LPARAM lParam) handleWM_PAINT; 3336 3337 private { 3338 int lastMouseX = int.min; 3339 int lastMouseY = int.min; 3340 void mdx(ref MouseEvent ev) { 3341 if(lastMouseX == int.min || lastMouseY == int.min) { 3342 ev.dx = 0; 3343 ev.dy = 0; 3344 } else { 3345 ev.dx = ev.x - lastMouseX; 3346 ev.dy = ev.y - lastMouseY; 3347 } 3348 3349 lastMouseX = ev.x; 3350 lastMouseY = ev.y; 3351 } 3352 } 3353 3354 /// Mouse event handler. Settable through setEventHandlers. 3355 void delegate(MouseEvent) handleMouseEvent; 3356 3357 /// use to redraw child widgets if you use system apis to add stuff 3358 void delegate() paintingFinished; 3359 3360 void delegate() paintingFinishedDg() { 3361 return paintingFinished; 3362 } 3363 3364 /// handle a resize, after it happens. You must construct the window with Resizability.allowResizing 3365 /// for this to ever happen. 3366 void delegate(int width, int height) windowResized; 3367 3368 /++ 3369 Platform specific - handle any native message this window gets. 3370 3371 Note: this is called *in addition to* other event handlers, unless you either: 3372 3373 1) On X11, return 0 indicating that you handled it. Any other return value is simply discarded. 3374 3375 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. 3376 3377 On Windows, your delegate takes the form of `int delegate(HWND hwnd, UINT message, WPARAM wParam, LPARAM lParam, out int mustReturn)`. 3378 3379 On X, it takes the form of `int delegate(XEvent)`. 3380 3381 History: 3382 In ancient versions, this was `static`. If you want a global hook, use [handleNativeGlobalEvent] instead. 3383 3384 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. 3385 +/ 3386 NativeEventHandler handleNativeEvent_; 3387 3388 @property NativeEventHandler handleNativeEvent() nothrow pure @nogc const @safe { 3389 return handleNativeEvent_; 3390 } 3391 @property void handleNativeEvent(NativeEventHandler neh) nothrow pure @nogc @safe { 3392 handleNativeEvent_ = neh; 3393 } 3394 3395 version(Windows) 3396 // compatibility shim with the old deprecated way 3397 // in this one, if you return 0, it means you must return. otherwise the ret value is ignored. 3398 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) { 3399 handleNativeEvent_ = delegate int(HWND h, UINT m, WPARAM w, LPARAM l, out int r) { 3400 auto ret = dg(h, m, w, l); 3401 if(ret == 0) 3402 r = 1; 3403 return ret; 3404 }; 3405 } 3406 3407 /// This is the same as handleNativeEvent, but static so it can hook ALL events in the loop. 3408 /// If you used to use handleNativeEvent depending on it being static, just change it to use 3409 /// this instead and it will work the same way. 3410 __gshared NativeEventHandler handleNativeGlobalEvent; 3411 3412 // private: 3413 /// The native implementation is available, but you shouldn't use it unless you are 3414 /// familiar with the underlying operating system, don't mind depending on it, and 3415 /// know simpledisplay.d's internals too. It is virtually private; you can hopefully 3416 /// do what you need to do with handleNativeEvent instead. 3417 /// 3418 /// This is likely to eventually change to be just a struct holding platform-specific 3419 /// handles instead of a template mixin at some point because I'm not happy with the 3420 /// code duplication here (ironically). 3421 mixin NativeSimpleWindowImplementation!() impl; 3422 3423 /** 3424 This is in-process one-way (from anything to window) event sending mechanics. 3425 It is thread-safe, so it can be used in multi-threaded applications to send, 3426 for example, "wake up and repaint" events when thread completed some operation. 3427 This will allow to avoid using timer pulse to check events with synchronization, 3428 'cause event handler will be called in UI thread. You can stop guessing which 3429 pulse frequency will be enough for your app. 3430 Note that events handlers may be called in arbitrary order, i.e. last registered 3431 handler can be called first, and vice versa. 3432 */ 3433 public: 3434 /** Is our custom event queue empty? Can be used in simple cases to prevent 3435 * "spamming" window with events it can't cope with. 3436 * It is safe to call this from non-UI threads. 3437 */ 3438 @property bool eventQueueEmpty() () { 3439 synchronized(this) { 3440 foreach (const ref o; eventQueue[0..eventQueueUsed]) if (!o.doProcess) return false; 3441 } 3442 return true; 3443 } 3444 3445 /** Does our custom event queue contains at least one with the given type? 3446 * Can be used in simple cases to prevent "spamming" window with events 3447 * it can't cope with. 3448 * It is safe to call this from non-UI threads. 3449 */ 3450 @property bool eventQueued(ET:Object) () { 3451 synchronized(this) { 3452 foreach (const ref o; eventQueue[0..eventQueueUsed]) { 3453 if (!o.doProcess) { 3454 if (cast(ET)(o.evt)) return true; 3455 } 3456 } 3457 } 3458 return false; 3459 } 3460 3461 /++ 3462 Event listeners added with [addEventListener] have their exceptions swallowed by the event loop. This delegate can handle them again before it proceeds. 3463 3464 History: 3465 Added May 12, 2021 3466 +/ 3467 void delegate(Exception e) nothrow eventUncaughtException; 3468 3469 /** Add listener for custom event. Can be used like this: 3470 * 3471 * --------------------- 3472 * auto eid = win.addEventListener((MyStruct evt) { ... }); 3473 * ... 3474 * win.removeEventListener(eid); 3475 * --------------------- 3476 * 3477 * Returns: 0 on failure (should never happen, so ignore it) 3478 * 3479 * $(WARNING Don't use this method in object destructors!) 3480 * 3481 * $(WARNING It is better to register all event handlers and don't remove 'em, 3482 * 'cause if event handler id counter will overflow, you won't be able 3483 * to register any more events.) 3484 */ 3485 uint addEventListener(ET:Object) (void delegate (ET) dg) { 3486 if (dg is null) return 0; // ignore empty handlers 3487 synchronized(this) { 3488 //FIXME: abort on overflow? 3489 if (++lastUsedHandlerId == 0) { --lastUsedHandlerId; return 0; } // alas, can't register more events. at all. 3490 EventHandlerEntry e; 3491 e.dg = delegate (Object o) { 3492 if (auto co = cast(ET)o) { 3493 try { 3494 dg(co); 3495 } catch (Exception e) { 3496 // sorry! 3497 if(eventUncaughtException) 3498 eventUncaughtException(e); 3499 } 3500 return true; 3501 } 3502 return false; 3503 }; 3504 e.id = lastUsedHandlerId; 3505 auto optr = eventHandlers.ptr; 3506 eventHandlers ~= e; 3507 if (eventHandlers.ptr !is optr) { 3508 import core.memory : GC; 3509 if (eventHandlers.ptr is GC.addrOf(eventHandlers.ptr)) GC.setAttr(eventHandlers.ptr, GC.BlkAttr.NO_INTERIOR); 3510 } 3511 return lastUsedHandlerId; 3512 } 3513 } 3514 3515 /// Remove event listener. It is safe to pass invalid event id here. 3516 /// $(WARNING Don't use this method in object destructors!) 3517 void removeEventListener() (uint id) { 3518 if (id == 0 || id > lastUsedHandlerId) return; 3519 synchronized(this) { 3520 foreach (immutable idx; 0..eventHandlers.length) { 3521 if (eventHandlers[idx].id == id) { 3522 foreach (immutable c; idx+1..eventHandlers.length) eventHandlers[c-1] = eventHandlers[c]; 3523 eventHandlers[$-1].dg = null; 3524 eventHandlers.length -= 1; 3525 eventHandlers.assumeSafeAppend; 3526 return; 3527 } 3528 } 3529 } 3530 } 3531 3532 /// Post event to queue. It is safe to call this from non-UI threads. 3533 /// If `timeoutmsecs` is greater than zero, the event will be delayed for at least `timeoutmsecs` milliseconds. 3534 /// if `replace` is `true`, replace all existing events typed `ET` with the new one (if `evt` is empty, remove 'em all) 3535 /// Returns `true` if event was queued. Always returns `false` if `evt` is null. 3536 bool postTimeout(ET:Object) (ET evt, uint timeoutmsecs, bool replace=false) { 3537 if (this.closed) return false; // closed windows can't handle events 3538 3539 // remove all events of type `ET` 3540 void removeAllET () { 3541 uint eidx = 0, ec = eventQueueUsed; 3542 auto eptr = eventQueue.ptr; 3543 while (eidx < ec) { 3544 if (eptr.doProcess) { ++eidx; ++eptr; continue; } 3545 if (cast(ET)eptr.evt !is null) { 3546 // i found her! 3547 if (inCustomEventProcessor) { 3548 // if we're in custom event processing loop, processor will clear it for us 3549 eptr.evt = null; 3550 ++eidx; 3551 ++eptr; 3552 } else { 3553 foreach (immutable c; eidx+1..ec) eventQueue.ptr[c-1] = eventQueue.ptr[c]; 3554 ec = --eventQueueUsed; 3555 // clear last event (it is already copied) 3556 eventQueue.ptr[ec].evt = null; 3557 } 3558 } else { 3559 ++eidx; 3560 ++eptr; 3561 } 3562 } 3563 } 3564 3565 if (evt is null) { 3566 if (replace) { synchronized(this) removeAllET(); } 3567 // ignore empty events, they can't be handled anyway 3568 return false; 3569 } 3570 3571 // add events even if no event FD/event object created yet 3572 synchronized(this) { 3573 if (replace) removeAllET(); 3574 if (eventQueueUsed == uint.max) return false; // just in case 3575 if (eventQueueUsed < eventQueue.length) { 3576 eventQueue[eventQueueUsed++] = QueuedEvent(evt, timeoutmsecs); 3577 } else { 3578 if (eventQueue.capacity == eventQueue.length) { 3579 // need to reallocate; do a trick to ensure that old array is cleared 3580 auto oarr = eventQueue; 3581 eventQueue ~= QueuedEvent(evt, timeoutmsecs); 3582 // just in case, do yet another check 3583 if (oarr.length != 0 && oarr.ptr !is eventQueue.ptr) foreach (ref e; oarr[0..eventQueueUsed]) e.evt = null; 3584 import core.memory : GC; 3585 if (eventQueue.ptr is GC.addrOf(eventQueue.ptr)) GC.setAttr(eventQueue.ptr, GC.BlkAttr.NO_INTERIOR); 3586 } else { 3587 auto optr = eventQueue.ptr; 3588 eventQueue ~= QueuedEvent(evt, timeoutmsecs); 3589 assert(eventQueue.ptr is optr); 3590 } 3591 ++eventQueueUsed; 3592 assert(eventQueueUsed == eventQueue.length); 3593 } 3594 if (!eventWakeUp()) { 3595 // can't wake up event processor, so there is no reason to keep the event 3596 assert(eventQueueUsed > 0); 3597 eventQueue[--eventQueueUsed].evt = null; 3598 return false; 3599 } 3600 return true; 3601 } 3602 } 3603 3604 /// Post event to queue. It is safe to call this from non-UI threads. 3605 /// if `replace` is `true`, replace all existing events typed `ET` with the new one (if `evt` is empty, remove 'em all) 3606 /// Returns `true` if event was queued. Always returns `false` if `evt` is null. 3607 bool postEvent(ET:Object) (ET evt, bool replace=false) { 3608 return postTimeout!ET(evt, 0, replace); 3609 } 3610 3611 private: 3612 private import core.time : MonoTime; 3613 3614 version(Posix) { 3615 __gshared int customEventFDRead = -1; 3616 __gshared int customEventFDWrite = -1; 3617 __gshared int customSignalFD = -1; 3618 } else version(Windows) { 3619 __gshared HANDLE customEventH = null; 3620 } 3621 3622 // wake up event processor 3623 static bool eventWakeUp () { 3624 version(X11) { 3625 import core.sys.posix.unistd : write; 3626 ulong n = 1; 3627 if (customEventFDWrite >= 0) write(customEventFDWrite, &n, n.sizeof); 3628 return true; 3629 } else version(Windows) { 3630 if (customEventH !is null) SetEvent(customEventH); 3631 return true; 3632 } else version(OSXCocoa) { 3633 if(globalAppDelegate) 3634 globalAppDelegate.performSelectorOnMainThread(sel_registerName("sdpyCustomEventWakeup:"), null, false); 3635 return true; 3636 } else { 3637 // not implemented for other OSes 3638 return false; 3639 } 3640 } 3641 3642 static struct QueuedEvent { 3643 Object evt; 3644 bool timed = false; 3645 MonoTime hittime = MonoTime.zero; 3646 bool doProcess = false; // process event at the current iteration (internal flag) 3647 3648 this (Object aevt, uint toutmsecs) { 3649 evt = aevt; 3650 if (toutmsecs > 0) { 3651 import core.time : msecs; 3652 timed = true; 3653 hittime = MonoTime.currTime+toutmsecs.msecs; 3654 } 3655 } 3656 } 3657 3658 alias CustomEventHandler = bool delegate (Object o) nothrow; 3659 static struct EventHandlerEntry { 3660 CustomEventHandler dg; 3661 uint id; 3662 } 3663 3664 uint lastUsedHandlerId; 3665 EventHandlerEntry[] eventHandlers; 3666 QueuedEvent[] eventQueue = null; 3667 uint eventQueueUsed = 0; // to avoid `.assumeSafeAppend` and length changes 3668 bool inCustomEventProcessor = false; // required to properly remove events 3669 3670 // process queued events and call custom event handlers 3671 // this will not process events posted from called handlers (such events are postponed for the next iteration) 3672 void processCustomEvents () { 3673 bool hasSomethingToDo = false; 3674 uint ecount; 3675 bool ocep; 3676 synchronized(this) { 3677 ocep = inCustomEventProcessor; 3678 inCustomEventProcessor = true; 3679 ecount = eventQueueUsed; // user may want to post new events from an event handler; process 'em on next iteration 3680 auto ctt = MonoTime.currTime; 3681 bool hasEmpty = false; 3682 // mark events to process (this is required for `eventQueued()`) 3683 foreach (ref qe; eventQueue[0..ecount]) { 3684 if (qe.evt is null) { hasEmpty = true; continue; } 3685 if (qe.timed) { 3686 qe.doProcess = (qe.hittime <= ctt); 3687 } else { 3688 qe.doProcess = true; 3689 } 3690 hasSomethingToDo = (hasSomethingToDo || qe.doProcess); 3691 } 3692 if (!hasSomethingToDo) { 3693 // remove empty events 3694 if (hasEmpty) { 3695 uint eidx = 0, ec = eventQueueUsed; 3696 auto eptr = eventQueue.ptr; 3697 while (eidx < ec) { 3698 if (eptr.evt is null) { 3699 foreach (immutable c; eidx+1..ec) eventQueue.ptr[c-1] = eventQueue.ptr[c]; 3700 ec = --eventQueueUsed; 3701 eventQueue.ptr[ec].evt = null; // make GC life easier 3702 } else { 3703 ++eidx; 3704 ++eptr; 3705 } 3706 } 3707 } 3708 inCustomEventProcessor = ocep; 3709 return; 3710 } 3711 } 3712 // process marked events 3713 uint efree = 0; // non-processed events will be put at this index 3714 EventHandlerEntry[] eh; 3715 Object evt; 3716 foreach (immutable eidx; 0..ecount) { 3717 synchronized(this) { 3718 if (!eventQueue[eidx].doProcess) { 3719 // skip this event 3720 assert(efree <= eidx); 3721 if (efree != eidx) { 3722 // copy this event to queue start 3723 eventQueue[efree] = eventQueue[eidx]; 3724 eventQueue[eidx].evt = null; // just in case 3725 } 3726 ++efree; 3727 continue; 3728 } 3729 evt = eventQueue[eidx].evt; 3730 eventQueue[eidx].evt = null; // in case event handler will hit GC 3731 if (evt is null) continue; // just in case 3732 // try all handlers; this can be slow, but meh... 3733 eh = eventHandlers; 3734 } 3735 foreach (ref evhan; eh) if (evhan.dg !is null) evhan.dg(evt); 3736 evt = null; 3737 eh = null; 3738 } 3739 synchronized(this) { 3740 // move all unprocessed events to queue top; efree holds first "free index" 3741 foreach (immutable eidx; ecount..eventQueueUsed) { 3742 assert(efree <= eidx); 3743 if (efree != eidx) eventQueue[efree] = eventQueue[eidx]; 3744 ++efree; 3745 } 3746 eventQueueUsed = efree; 3747 // wake up event processor on next event loop iteration if we have more queued events 3748 // also, remove empty events 3749 bool awaken = false; 3750 uint eidx = 0, ec = eventQueueUsed; 3751 auto eptr = eventQueue.ptr; 3752 while (eidx < ec) { 3753 if (eptr.evt is null) { 3754 foreach (immutable c; eidx+1..ec) eventQueue.ptr[c-1] = eventQueue.ptr[c]; 3755 ec = --eventQueueUsed; 3756 eventQueue.ptr[ec].evt = null; // make GC life easier 3757 } else { 3758 if (!awaken && !eptr.timed) { eventWakeUp(); awaken = true; } 3759 ++eidx; 3760 ++eptr; 3761 } 3762 } 3763 inCustomEventProcessor = ocep; 3764 } 3765 } 3766 3767 // for all windows in nativeMapping 3768 package static void processAllCustomEvents () { 3769 3770 cleanupQueue.process(); 3771 3772 justCommunication.processCustomEvents(); 3773 3774 foreach (SimpleWindow sw; SimpleWindow.nativeMapping.byValue) { 3775 if (sw is null || sw.closed) continue; 3776 sw.processCustomEvents(); 3777 } 3778 3779 runPendingRunInGuiThreadDelegates(); 3780 } 3781 3782 // 0: infinite (i.e. no scheduled events in queue) 3783 uint eventQueueTimeoutMSecs () { 3784 synchronized(this) { 3785 if (eventQueueUsed == 0) return 0; 3786 if (inCustomEventProcessor) assert(0, "WUTAFUUUUUUU..."); // the thing that should not be. ABSOLUTELY! (c) 3787 uint res = int.max; 3788 auto ctt = MonoTime.currTime; 3789 foreach (const ref qe; eventQueue[0..eventQueueUsed]) { 3790 if (qe.evt is null) assert(0, "WUTAFUUUUUUU..."); // the thing that should not be. ABSOLUTELY! (c) 3791 if (qe.doProcess) continue; // just in case 3792 if (!qe.timed) return 1; // minimal 3793 if (qe.hittime <= ctt) return 1; // minimal 3794 auto tms = (qe.hittime-ctt).total!"msecs"; 3795 if (tms < 1) tms = 1; // safety net 3796 if (tms >= int.max) tms = int.max-1; // and another safety net 3797 if (res > tms) res = cast(uint)tms; 3798 } 3799 return (res >= int.max ? 0 : res); 3800 } 3801 } 3802 3803 // for all windows in nativeMapping 3804 static uint eventAllQueueTimeoutMSecs () { 3805 uint res = uint.max; 3806 foreach (SimpleWindow sw; SimpleWindow.nativeMapping.byValue) { 3807 if (sw is null || sw.closed) continue; 3808 uint to = sw.eventQueueTimeoutMSecs(); 3809 if (to && to < res) { 3810 res = to; 3811 if (to == 1) break; // can't have less than this 3812 } 3813 } 3814 return (res >= int.max ? 0 : res); 3815 } 3816 3817 version(X11) { 3818 ResizeEvent pendingResizeEvent; 3819 } 3820 3821 /++ 3822 When in opengl mode and automatically resizing, it will set the opengl viewport to stretch. 3823 3824 If you work with multiple opengl contexts and/or threads, this might be more trouble than it is 3825 worth so you can disable it by setting this to `true`. 3826 3827 History: 3828 Added November 13, 2022. 3829 +/ 3830 public bool suppressAutoOpenglViewport = false; 3831 private void updateOpenglViewportIfNeeded(int width, int height) { 3832 if(suppressAutoOpenglViewport) return; 3833 3834 version(without_opengl) {} else 3835 if(openglMode == OpenGlOptions.yes && resizability == Resizability.automaticallyScaleIfPossible) { 3836 // writeln(width, " ", height); 3837 setAsCurrentOpenGlContextNT(); 3838 glViewport(0, 0, width, height); 3839 } 3840 } 3841 } 3842 3843 version(OSXCocoa) 3844 enum NSWindow NullWindow = null; 3845 else 3846 enum NullWindow = NativeWindowHandle.init; 3847 3848 /++ 3849 Magic pseudo-window for just posting events to a global queue. 3850 3851 Not entirely supported, I might delete it at any time. 3852 3853 Added Nov 5, 2021. 3854 +/ 3855 __gshared SimpleWindow justCommunication = new SimpleWindow(NullWindow); 3856 3857 /* Drag and drop support { */ 3858 version(X11) { 3859 3860 } else version(Windows) { 3861 import core.sys.windows.uuid; 3862 import core.sys.windows.ole2; 3863 import core.sys.windows.oleidl; 3864 import core.sys.windows.objidl; 3865 import core.sys.windows.wtypes; 3866 3867 pragma(lib, "ole32"); 3868 void initDnd() { 3869 auto err = OleInitialize(null); 3870 if(err != S_OK && err != S_FALSE) 3871 throw new Exception("init");//err); 3872 } 3873 } 3874 /* } End drag and drop support */ 3875 3876 3877 /// Represents a mouse cursor (aka the mouse pointer, the image seen on screen that indicates where the mouse is pointing). 3878 /// See [GenericCursor]. 3879 class MouseCursor { 3880 int osId; 3881 bool isStockCursor; 3882 private this(int osId) { 3883 this.osId = osId; 3884 this.isStockCursor = true; 3885 } 3886 3887 // https://msdn.microsoft.com/en-us/library/windows/desktop/ms648385(v=vs.85).aspx 3888 this(int xHotSpot, int yHotSpot, ubyte[] andMask, ubyte[] xorMask) {} 3889 3890 version(Windows) { 3891 HCURSOR cursor_; 3892 HCURSOR cursorHandle() { 3893 if(cursor_ is null) 3894 cursor_ = LoadCursor(null, MAKEINTRESOURCE(osId)); 3895 return cursor_; 3896 } 3897 3898 } else static if(UsingSimpledisplayX11) { 3899 Cursor cursor_ = None; 3900 int xDisplaySequence; 3901 3902 Cursor cursorHandle() { 3903 if(this.osId == None) 3904 return None; 3905 3906 // we need to reload if we on a new X connection 3907 if(cursor_ == None || XDisplayConnection.connectionSequenceNumber != xDisplaySequence) { 3908 cursor_ = XCreateFontCursor(XDisplayConnection.get(), this.osId); 3909 xDisplaySequence = XDisplayConnection.connectionSequenceNumber; 3910 } 3911 return cursor_; 3912 } 3913 } 3914 } 3915 3916 // https://developer.mozilla.org/en-US/docs/Web/CSS/cursor 3917 // https://tronche.com/gui/x/xlib/appendix/b/ 3918 // https://msdn.microsoft.com/en-us/library/windows/desktop/ms648391(v=vs.85).aspx 3919 /// 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. 3920 enum GenericCursorType { 3921 Default, /// The default arrow pointer. 3922 Wait, /// A cursor indicating something is loading and the user must wait. 3923 Hand, /// A pointing finger, like the one used hovering over hyperlinks in a web browser. 3924 Help, /// A cursor indicating the user can get help about the pointer location. 3925 Cross, /// A crosshair. 3926 Text, /// An i-beam shape, typically used to indicate text selection is possible. 3927 Move, /// Pointer indicating movement is possible. May also be used as SizeAll. 3928 UpArrow, /// An arrow pointing straight up. 3929 Progress, /// The hourglass and arrow, indicating the computer is working but the user can still work. Not great results on X11. 3930 NotAllowed, /// Indicates the current operation is not allowed. Not great results on X11. 3931 SizeNesw, /// Arrow pointing northeast and southwest (lower-left corner resize indicator). 3932 SizeNs, /// Arrow pointing north and south (upper/lower edge resize indicator). 3933 SizeNwse, /// Arrow pointing northwest and southeast (upper-left corner resize indicator). 3934 SizeWe, /// Arrow pointing west and east (left/right edge resize indicator). 3935 3936 } 3937 3938 /* 3939 X_plus == css cell == Windows ? 3940 */ 3941 3942 /// You get one by `GenericCursor.SomeTime`. See [GenericCursorType] for a list of types. 3943 static struct GenericCursor { 3944 static: 3945 /// 3946 MouseCursor opDispatch(string str)() if(__traits(hasMember, GenericCursorType, str)) { 3947 static MouseCursor mc; 3948 3949 auto type = __traits(getMember, GenericCursorType, str); 3950 3951 if(mc is null) { 3952 3953 version(Windows) { 3954 int osId; 3955 final switch(type) { 3956 case GenericCursorType.Default: osId = IDC_ARROW; break; 3957 case GenericCursorType.Wait: osId = IDC_WAIT; break; 3958 case GenericCursorType.Hand: osId = IDC_HAND; break; 3959 case GenericCursorType.Help: osId = IDC_HELP; break; 3960 case GenericCursorType.Cross: osId = IDC_CROSS; break; 3961 case GenericCursorType.Text: osId = IDC_IBEAM; break; 3962 case GenericCursorType.Move: osId = IDC_SIZEALL; break; 3963 case GenericCursorType.UpArrow: osId = IDC_UPARROW; break; 3964 case GenericCursorType.Progress: osId = IDC_APPSTARTING; break; 3965 case GenericCursorType.NotAllowed: osId = IDC_NO; break; 3966 case GenericCursorType.SizeNesw: osId = IDC_SIZENESW; break; 3967 case GenericCursorType.SizeNs: osId = IDC_SIZENS; break; 3968 case GenericCursorType.SizeNwse: osId = IDC_SIZENWSE; break; 3969 case GenericCursorType.SizeWe: osId = IDC_SIZEWE; break; 3970 } 3971 } else static if(UsingSimpledisplayX11) { 3972 int osId; 3973 final switch(type) { 3974 case GenericCursorType.Default: osId = None; break; 3975 case GenericCursorType.Wait: osId = 150 /* XC_watch */; break; 3976 case GenericCursorType.Hand: osId = 60 /* XC_hand2 */; break; 3977 case GenericCursorType.Help: osId = 92 /* XC_question_arrow */; break; 3978 case GenericCursorType.Cross: osId = 34 /* XC_crosshair */; break; 3979 case GenericCursorType.Text: osId = 152 /* XC_xterm */; break; 3980 case GenericCursorType.Move: osId = 52 /* XC_fleur */; break; 3981 case GenericCursorType.UpArrow: osId = 22 /* XC_center_ptr */; break; 3982 case GenericCursorType.Progress: osId = 150 /* XC_watch, best i can do i think */; break; 3983 3984 case GenericCursorType.NotAllowed: osId = 24 /* XC_circle. not great */; break; 3985 case GenericCursorType.SizeNesw: osId = 12 /* XC_bottom_left_corner */ ; break; 3986 case GenericCursorType.SizeNs: osId = 116 /* XC_sb_v_double_arrow */; break; 3987 case GenericCursorType.SizeNwse: osId = 14 /* XC_bottom_right_corner */; break; 3988 case GenericCursorType.SizeWe: osId = 108 /* XC_sb_h_double_arrow */; break; 3989 } 3990 3991 } else { 3992 int osId; 3993 // featureNotImplemented(); 3994 } 3995 3996 mc = new MouseCursor(osId); 3997 } 3998 return mc; 3999 } 4000 } 4001 4002 4003 /++ 4004 If you want to get more control over the event loop, you can use this. 4005 4006 Typically though, you can just call [SimpleWindow.eventLoop] which forwards 4007 to `EventLoop.get.run`. 4008 +/ 4009 struct EventLoop { 4010 @disable this(); 4011 4012 /// Gets a reference to an existing event loop 4013 static EventLoop get() { 4014 return EventLoop(0, null); 4015 } 4016 4017 static void quitApplication() { 4018 EventLoop.get().exit(); 4019 } 4020 4021 private __gshared static Object monitor = new Object(); // deliberate CTFE usage here fyi 4022 4023 /// Construct an application-global event loop for yourself 4024 /// See_Also: [SimpleWindow.setEventHandlers] 4025 this(long pulseTimeout, void delegate() handlePulse) { 4026 synchronized(monitor) { 4027 if(impl is null) { 4028 claimGuiThread(); 4029 version(sdpy_thread_checks) assert(thisIsGuiThread); 4030 impl = new EventLoopImpl(pulseTimeout, handlePulse); 4031 } else { 4032 if(pulseTimeout) { 4033 impl.pulseTimeout = pulseTimeout; 4034 impl.handlePulse = handlePulse; 4035 } 4036 } 4037 impl.refcount++; 4038 } 4039 } 4040 4041 ~this() { 4042 if(impl is null) 4043 return; 4044 impl.refcount--; 4045 if(impl.refcount == 0) { 4046 impl.dispose(); 4047 if(thisIsGuiThread) 4048 guiThreadFinalize(); 4049 } 4050 4051 } 4052 4053 this(this) { 4054 if(impl is null) 4055 return; 4056 impl.refcount++; 4057 } 4058 4059 /// Runs the event loop until the whileCondition, if present, returns false 4060 int run(bool delegate() whileCondition = null) { 4061 assert(impl !is null); 4062 impl.notExited = true; 4063 return impl.run(whileCondition); 4064 } 4065 4066 /// Exits the event loop 4067 void exit() { 4068 assert(impl !is null); 4069 impl.notExited = false; 4070 } 4071 4072 version(linux) 4073 ref void delegate(int) signalHandler() { 4074 assert(impl !is null); 4075 return impl.signalHandler; 4076 } 4077 4078 __gshared static EventLoopImpl* impl; 4079 } 4080 4081 version(linux) 4082 void delegate(int, int) globalHupHandler; 4083 4084 version(Posix) 4085 void makeNonBlocking(int fd) { 4086 import fcntl = core.sys.posix.fcntl; 4087 auto flags = fcntl.fcntl(fd, fcntl.F_GETFL, 0); 4088 if(flags == -1) 4089 throw new Exception("fcntl get"); 4090 flags |= fcntl.O_NONBLOCK; 4091 auto s = fcntl.fcntl(fd, fcntl.F_SETFL, flags); 4092 if(s == -1) 4093 throw new Exception("fcntl set"); 4094 } 4095 4096 struct EventLoopImpl { 4097 int refcount; 4098 4099 bool notExited = true; 4100 4101 version(linux) { 4102 static import ep = core.sys.linux.epoll; 4103 static import unix = core.sys.posix.unistd; 4104 static import err = core.stdc.errno; 4105 import core.sys.linux.timerfd; 4106 4107 void delegate(int) signalHandler; 4108 } 4109 4110 version(X11) { 4111 int pulseFd = -1; 4112 version(linux) ep.epoll_event[16] events = void; 4113 } else version(Windows) { 4114 Timer pulser; 4115 HANDLE[] handles; 4116 } 4117 4118 4119 /// "Lock" this window handle, to do multithreaded synchronization. You probably won't need 4120 /// to call this, as it's not recommended to share window between threads. 4121 void mtLock () { 4122 version(X11) { 4123 XLockDisplay(this.display); 4124 } 4125 } 4126 4127 version(X11) 4128 auto display() { return XDisplayConnection.get; } 4129 4130 /// "Unlock" this window handle, to do multithreaded synchronization. You probably won't need 4131 /// to call this, as it's not recommended to share window between threads. 4132 void mtUnlock () { 4133 version(X11) { 4134 XUnlockDisplay(this.display); 4135 } 4136 } 4137 4138 version(with_eventloop) 4139 void initialize(long pulseTimeout) {} 4140 else 4141 void initialize(long pulseTimeout) { 4142 version(Windows) { 4143 if(pulseTimeout && handlePulse !is null) 4144 pulser = new Timer(cast(int) pulseTimeout, handlePulse); 4145 4146 if (customEventH is null) { 4147 customEventH = CreateEvent(null, FALSE/*autoreset*/, FALSE/*initial state*/, null); 4148 if (customEventH !is null) { 4149 handles ~= customEventH; 4150 } else { 4151 // this is something that should not be; better be safe than sorry 4152 throw new Exception("can't create eventfd for custom event processing"); 4153 } 4154 } 4155 4156 SimpleWindow.processAllCustomEvents(); // process events added before event object creation 4157 } 4158 4159 version(linux) { 4160 prepareEventLoop(); 4161 { 4162 auto display = XDisplayConnection.get; 4163 // adding Xlib file 4164 ep.epoll_event ev = void; 4165 { import core.stdc.string : memset; memset(&ev, 0, ev.sizeof); } // this makes valgrind happy 4166 ev.events = ep.EPOLLIN; 4167 ev.data.fd = display.fd; 4168 if(ep.epoll_ctl(epollFd, ep.EPOLL_CTL_ADD, display.fd, &ev) == -1) 4169 throw new Exception("add x fd");// ~ to!string(epollFd)); 4170 displayFd = display.fd; 4171 } 4172 4173 if(pulseTimeout && handlePulse !is null) { 4174 pulseFd = timerfd_create(CLOCK_MONOTONIC, 0); 4175 if(pulseFd == -1) 4176 throw new Exception("pulse timer create failed"); 4177 4178 itimerspec value; 4179 value.it_value.tv_sec = cast(int) (pulseTimeout / 1000); 4180 value.it_value.tv_nsec = (pulseTimeout % 1000) * 1000_000; 4181 4182 value.it_interval.tv_sec = cast(int) (pulseTimeout / 1000); 4183 value.it_interval.tv_nsec = (pulseTimeout % 1000) * 1000_000; 4184 4185 if(timerfd_settime(pulseFd, 0, &value, null) == -1) 4186 throw new Exception("couldn't make pulse timer"); 4187 4188 ep.epoll_event ev = void; 4189 { import core.stdc.string : memset; memset(&ev, 0, ev.sizeof); } // this makes valgrind happy 4190 ev.events = ep.EPOLLIN; 4191 ev.data.fd = pulseFd; 4192 ep.epoll_ctl(epollFd, ep.EPOLL_CTL_ADD, pulseFd, &ev); 4193 } 4194 4195 // eventfd for custom events 4196 if (customEventFDWrite == -1) { 4197 customEventFDWrite = eventfd(0, 0); 4198 customEventFDRead = customEventFDWrite; 4199 if (customEventFDRead >= 0) { 4200 ep.epoll_event ev = void; 4201 { import core.stdc.string : memset; memset(&ev, 0, ev.sizeof); } // this makes valgrind happy 4202 ev.events = ep.EPOLLIN; 4203 ev.data.fd = customEventFDRead; 4204 ep.epoll_ctl(epollFd, ep.EPOLL_CTL_ADD, customEventFDRead, &ev); 4205 } else { 4206 // this is something that should not be; better be safe than sorry 4207 throw new Exception("can't create eventfd for custom event processing"); 4208 } 4209 } 4210 4211 if (customSignalFD == -1) { 4212 import core.sys.linux.sys.signalfd; 4213 4214 sigset_t sigset; 4215 auto err = sigemptyset(&sigset); 4216 assert(!err); 4217 err = sigaddset(&sigset, SIGINT); 4218 assert(!err); 4219 err = sigaddset(&sigset, SIGHUP); 4220 assert(!err); 4221 err = sigprocmask(SIG_BLOCK, &sigset, null); 4222 assert(!err); 4223 4224 customSignalFD = signalfd(-1, &sigset, SFD_NONBLOCK); 4225 assert(customSignalFD != -1); 4226 4227 ep.epoll_event ev = void; 4228 { import core.stdc.string : memset; memset(&ev, 0, ev.sizeof); } // this makes valgrind happy 4229 ev.events = ep.EPOLLIN; 4230 ev.data.fd = customSignalFD; 4231 ep.epoll_ctl(epollFd, ep.EPOLL_CTL_ADD, customSignalFD, &ev); 4232 } 4233 } else version(Posix) { 4234 prepareEventLoop(); 4235 if (customEventFDRead == -1) { 4236 int[2] bfr; 4237 import core.sys.posix.unistd; 4238 auto ret = pipe(bfr); 4239 if(ret == -1) throw new Exception("pipe"); 4240 customEventFDRead = bfr[0]; 4241 customEventFDWrite = bfr[1]; 4242 } 4243 4244 } 4245 4246 SimpleWindow.processAllCustomEvents(); // process events added before event FD creation 4247 4248 version(linux) { 4249 this.mtLock(); 4250 scope(exit) this.mtUnlock(); 4251 XPending(display); // no, really 4252 } 4253 4254 disposed = false; 4255 } 4256 4257 bool disposed = true; 4258 version(X11) 4259 int displayFd = -1; 4260 4261 version(with_eventloop) 4262 void dispose() {} 4263 else 4264 void dispose() { 4265 disposed = true; 4266 version(X11) { 4267 if(pulseFd != -1) { 4268 import unix = core.sys.posix.unistd; 4269 unix.close(pulseFd); 4270 pulseFd = -1; 4271 } 4272 4273 version(linux) 4274 if(displayFd != -1) { 4275 // 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 4276 ep.epoll_event ev = void; 4277 { import core.stdc.string : memset; memset(&ev, 0, ev.sizeof); } // this makes valgrind happy 4278 ev.events = ep.EPOLLIN; 4279 ev.data.fd = displayFd; 4280 ep.epoll_ctl(epollFd, ep.EPOLL_CTL_DEL, displayFd, &ev); 4281 displayFd = -1; 4282 } 4283 4284 } else version(Windows) { 4285 if(pulser !is null) { 4286 pulser.destroy(); 4287 pulser = null; 4288 } 4289 if (customEventH !is null) { 4290 CloseHandle(customEventH); 4291 customEventH = null; 4292 } 4293 } 4294 } 4295 4296 this(long pulseTimeout, void delegate() handlePulse) { 4297 this.pulseTimeout = pulseTimeout; 4298 this.handlePulse = handlePulse; 4299 initialize(pulseTimeout); 4300 } 4301 4302 private long pulseTimeout; 4303 void delegate() handlePulse; 4304 4305 ~this() { 4306 dispose(); 4307 } 4308 4309 version(Posix) 4310 ref int customEventFDRead() { return SimpleWindow.customEventFDRead; } 4311 version(Posix) 4312 ref int customEventFDWrite() { return SimpleWindow.customEventFDWrite; } 4313 version(linux) 4314 ref int customSignalFD() { return SimpleWindow.customSignalFD; } 4315 version(Windows) 4316 ref auto customEventH() { return SimpleWindow.customEventH; } 4317 4318 version(with_eventloop) { 4319 int loopHelper(bool delegate() whileCondition) { 4320 // FIXME: whileCondition 4321 import arsd.eventloop; 4322 loop(); 4323 return 0; 4324 } 4325 } else 4326 int loopHelper(bool delegate() whileCondition) { 4327 version(X11) { 4328 bool done = false; 4329 4330 XFlush(display); 4331 insideXEventLoop = true; 4332 scope(exit) insideXEventLoop = false; 4333 4334 version(linux) { 4335 while(!done && (whileCondition is null || whileCondition() == true) && notExited) { 4336 bool forceXPending = false; 4337 auto wto = SimpleWindow.eventAllQueueTimeoutMSecs(); 4338 // eh... some events may be queued for "squashing" (or "late delivery"), so we have to do the following magic 4339 { 4340 this.mtLock(); 4341 scope(exit) this.mtUnlock(); 4342 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 4343 } 4344 //{ import core.stdc.stdio; printf("*** wto=%d; force=%d\n", wto, (forceXPending ? 1 : 0)); } 4345 auto nfds = ep.epoll_wait(epollFd, events.ptr, events.length, (wto == 0 || wto >= int.max ? -1 : cast(int)wto)); 4346 if(nfds == -1) { 4347 if(err.errno == err.EINTR) { 4348 //if(forceXPending) goto xpending; 4349 continue; // interrupted by signal, just try again 4350 } 4351 throw new Exception("epoll wait failure"); 4352 } 4353 // writeln(nfds, " ", events[0].data.fd); 4354 4355 SimpleWindow.processAllCustomEvents(); // anyway 4356 //version(sdddd) { writeln("nfds=", nfds, "; [0]=", events[0].data.fd); } 4357 foreach(idx; 0 .. nfds) { 4358 if(done) break; 4359 auto fd = events[idx].data.fd; 4360 assert(fd != -1); // should never happen cuz the api doesn't do that but better to assert than assume. 4361 auto flags = events[idx].events; 4362 if(flags & ep.EPOLLIN) { 4363 if (fd == customSignalFD) { 4364 version(linux) { 4365 import core.sys.linux.sys.signalfd; 4366 import core.sys.posix.unistd : read; 4367 signalfd_siginfo info; 4368 read(customSignalFD, &info, info.sizeof); 4369 4370 auto sig = info.ssi_signo; 4371 4372 if(EventLoop.get.signalHandler !is null) { 4373 EventLoop.get.signalHandler()(sig); 4374 } else { 4375 EventLoop.get.exit(); 4376 } 4377 } 4378 } else if(fd == display.fd) { 4379 version(sdddd) { writeln("X EVENT PENDING!"); } 4380 this.mtLock(); 4381 scope(exit) this.mtUnlock(); 4382 while(!done && XPending(display)) { 4383 done = doXNextEvent(this.display); 4384 } 4385 forceXPending = false; 4386 } else if(fd == pulseFd) { 4387 long expirationCount; 4388 // 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... 4389 4390 handlePulse(); 4391 4392 // read just to clear the buffer so poll doesn't trigger again 4393 // BTW I read AFTER the pulse because if the pulse handler takes 4394 // a lot of time to execute, we don't want the app to get stuck 4395 // in a loop of timer hits without a chance to do anything else 4396 // 4397 // IOW handlePulse happens at most once per pulse interval. 4398 unix.read(pulseFd, &expirationCount, expirationCount.sizeof); 4399 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 4400 } else if (fd == customEventFDRead) { 4401 // we have some custom events; process 'em 4402 import core.sys.posix.unistd : read; 4403 ulong n; 4404 read(customEventFDRead, &n, n.sizeof); // reset counter value to zero again 4405 //{ import core.stdc.stdio; printf("custom event! count=%u\n", eventQueueUsed); } 4406 //SimpleWindow.processAllCustomEvents(); 4407 4408 forceXPending = true; 4409 } else { 4410 // some other timer 4411 version(sdddd) { writeln("unknown fd: ", fd); } 4412 4413 if(Timer* t = fd in Timer.mapping) 4414 (*t).trigger(); 4415 4416 if(PosixFdReader* pfr = fd in PosixFdReader.mapping) 4417 (*pfr).ready(flags); 4418 4419 // we don't know what the user did in this timer, so we need to assume that 4420 // there's X data to be flushed and potentially processed 4421 forceXPending = true; 4422 4423 // or i might add support for other FDs too 4424 // but for now it is just timer 4425 // (if you want other fds, use arsd.eventloop and compile with -version=with_eventloop), it offers a fuller api for arbitrary stuff. 4426 } 4427 } 4428 if(flags & ep.EPOLLHUP) { 4429 if(PosixFdReader* pfr = fd in PosixFdReader.mapping) 4430 (*pfr).hup(flags); 4431 if(globalHupHandler) 4432 globalHupHandler(fd, flags); 4433 } 4434 /+ 4435 } else { 4436 // not interested in OUT, we are just reading here. 4437 // 4438 // error or hup might also be reported 4439 // but it shouldn't here since we are only 4440 // using a few types of FD and Xlib will report 4441 // if it dies. 4442 // so instead of thoughtfully handling it, I'll 4443 // just throw. for now at least 4444 4445 throw new Exception("epoll did something else"); 4446 } 4447 +/ 4448 } 4449 // if we won't call `XPending()` here, libX may delay some internal event delivery. 4450 // i.e. we HAVE to repeatedly call `XPending()` even if libX fd wasn't signalled! 4451 xpending: 4452 if (!done && forceXPending) { 4453 this.mtLock(); 4454 scope(exit) this.mtUnlock(); 4455 //{ import core.stdc.stdio; printf("*** queued: %d\n", XEventsQueued(this.display, QueueMode.QueuedAlready)); } 4456 while(!done && XPending(display)) { 4457 done = doXNextEvent(this.display); 4458 } 4459 } 4460 } 4461 } else { 4462 // Generic fallback: yes to simple pulse support, 4463 // but NO timer support! 4464 4465 // FIXME: we could probably support the POSIX timer_create 4466 // signal-based option, but I'm in no rush to write it since 4467 // I prefer the fd-based functions. 4468 while (!done && (whileCondition is null || whileCondition() == true) && notExited) { 4469 4470 import core.sys.posix.poll; 4471 4472 pollfd[] pfds; 4473 pollfd[32] pfdsBuffer; 4474 auto len = PosixFdReader.mapping.length + 2; 4475 // FIXME: i should just reuse the buffer 4476 if(len < pfdsBuffer.length) 4477 pfds = pfdsBuffer[0 .. len]; 4478 else 4479 pfds = new pollfd[](len); 4480 4481 pfds[0].fd = display.fd; 4482 pfds[0].events = POLLIN; 4483 pfds[0].revents = 0; 4484 4485 int slot = 1; 4486 4487 if(customEventFDRead != -1) { 4488 pfds[slot].fd = customEventFDRead; 4489 pfds[slot].events = POLLIN; 4490 pfds[slot].revents = 0; 4491 4492 slot++; 4493 } 4494 4495 foreach(fd, obj; PosixFdReader.mapping) { 4496 if(!obj.enabled) continue; 4497 pfds[slot].fd = fd; 4498 pfds[slot].events = POLLIN; 4499 pfds[slot].revents = 0; 4500 4501 slot++; 4502 } 4503 4504 auto ret = poll(pfds.ptr, slot, pulseTimeout > 0 ? cast(int) pulseTimeout : -1); 4505 if(ret == -1) throw new Exception("poll"); 4506 4507 if(ret == 0) { 4508 // FIXME it may not necessarily time out if events keep coming 4509 if(handlePulse !is null) 4510 handlePulse(); 4511 } else { 4512 foreach(s; 0 .. slot) { 4513 if(pfds[s].revents == 0) continue; 4514 4515 if(pfds[s].fd == display.fd) { 4516 while(!done && XPending(display)) { 4517 this.mtLock(); 4518 scope(exit) this.mtUnlock(); 4519 done = doXNextEvent(this.display); 4520 } 4521 } else if(customEventFDRead != -1 && pfds[s].fd == customEventFDRead) { 4522 4523 import core.sys.posix.unistd : read; 4524 ulong n; 4525 read(customEventFDRead, &n, n.sizeof); 4526 SimpleWindow.processAllCustomEvents(); 4527 } else { 4528 auto obj = PosixFdReader.mapping[pfds[s].fd]; 4529 if(pfds[s].revents & POLLNVAL) { 4530 obj.dispose(); 4531 } else { 4532 obj.ready(pfds[s].revents); 4533 } 4534 } 4535 4536 ret--; 4537 if(ret == 0) break; 4538 } 4539 } 4540 } 4541 } 4542 } 4543 4544 version(Windows) { 4545 int ret = -1; 4546 MSG message; 4547 while(ret != 0 && (whileCondition is null || whileCondition() == true) && notExited) { 4548 eventLoopRound++; 4549 auto wto = SimpleWindow.eventAllQueueTimeoutMSecs(); 4550 auto waitResult = MsgWaitForMultipleObjectsEx( 4551 cast(int) handles.length, handles.ptr, 4552 (wto == 0 ? INFINITE : wto), /* timeout */ 4553 0x04FF, /* QS_ALLINPUT */ 4554 0x0002 /* MWMO_ALERTABLE */ | 0x0004 /* MWMO_INPUTAVAILABLE */); 4555 4556 SimpleWindow.processAllCustomEvents(); // anyway 4557 enum WAIT_OBJECT_0 = 0; 4558 if(waitResult >= WAIT_OBJECT_0 && waitResult < handles.length + WAIT_OBJECT_0) { 4559 auto h = handles[waitResult - WAIT_OBJECT_0]; 4560 if(auto e = h in WindowsHandleReader.mapping) { 4561 (*e).ready(); 4562 } 4563 } else if(waitResult == handles.length + WAIT_OBJECT_0) { 4564 // message ready 4565 int count; 4566 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 4567 ret = GetMessage(&message, null, 0, 0); 4568 if(ret == -1) 4569 throw new WindowsApiException("GetMessage", GetLastError()); 4570 TranslateMessage(&message); 4571 DispatchMessage(&message); 4572 4573 count++; 4574 if(count > 10) 4575 break; // take the opportunity to catch up on other events 4576 4577 if(ret == 0) { // WM_QUIT 4578 EventLoop.quitApplication(); 4579 break; 4580 } 4581 } 4582 } else if(waitResult == 0x000000C0L /* WAIT_IO_COMPLETION */) { 4583 SleepEx(0, true); // I call this to give it a chance to do stuff like async io 4584 } else if(waitResult == 258L /* WAIT_TIMEOUT */) { 4585 // timeout, should never happen since we aren't using it 4586 } else if(waitResult == 0xFFFFFFFF) { 4587 // failed 4588 throw new WindowsApiException("MsgWaitForMultipleObjectsEx", GetLastError()); 4589 } else { 4590 // idk.... 4591 } 4592 } 4593 4594 // return message.wParam; 4595 return 0; 4596 } else { 4597 return 0; 4598 } 4599 } 4600 4601 int run(bool delegate() whileCondition = null) { 4602 if(disposed) 4603 initialize(this.pulseTimeout); 4604 4605 version(X11) { 4606 try { 4607 return loopHelper(whileCondition); 4608 } catch(XDisconnectException e) { 4609 if(e.userRequested) { 4610 foreach(item; CapableOfHandlingNativeEvent.nativeHandleMapping) 4611 item.discardConnectionState(); 4612 XCloseDisplay(XDisplayConnection.display); 4613 } 4614 4615 XDisplayConnection.display = null; 4616 4617 this.dispose(); 4618 4619 throw e; 4620 } 4621 } else { 4622 return loopHelper(whileCondition); 4623 } 4624 } 4625 } 4626 4627 4628 /++ 4629 Provides an icon on the system notification area (also known as the system tray). 4630 4631 4632 If a notification area is not available with the NotificationIcon object is created, 4633 it will silently succeed and simply attempt to create one when an area becomes available. 4634 4635 4636 NotificationAreaIcon on Windows assumes you are on Windows Vista or later. Support for 4637 Windows XP was dropped on October 31, 2023. On the other hand, support for 32 bit transparency 4638 with true color was added at that time. I was just too lazy to write the fallback. 4639 4640 If this is an issue, let me know, it'd take about an hour to get it back in there, but I suggest 4641 you use arsd 10.x when targeting Windows XP. 4642 +/ 4643 version(OSXCocoa) {} else // NotYetImplementedException 4644 class NotificationAreaIcon : CapableOfHandlingNativeEvent { 4645 4646 version(X11) { 4647 void recreateAfterDisconnect() { 4648 stateDiscarded = false; 4649 clippixmap = None; 4650 throw new Exception("NOT IMPLEMENTED"); 4651 } 4652 4653 bool stateDiscarded; 4654 void discardConnectionState() { 4655 stateDiscarded = true; 4656 } 4657 } 4658 4659 4660 version(X11) { 4661 Image img; 4662 4663 NativeEventHandler getNativeEventHandler() { 4664 return delegate int(XEvent e) { 4665 switch(e.type) { 4666 case EventType.Expose: 4667 //case EventType.VisibilityNotify: 4668 redraw(); 4669 break; 4670 case EventType.ClientMessage: 4671 version(sddddd) { 4672 writeln("\t", e.xclient.message_type == GetAtom!("_XEMBED")(XDisplayConnection.get)); 4673 writeln("\t", e.xclient.format); 4674 writeln("\t", e.xclient.data.l); 4675 } 4676 break; 4677 case EventType.ButtonPress: 4678 auto event = e.xbutton; 4679 if (onClick !is null || onClickEx !is null) { 4680 MouseButton mb = cast(MouseButton)0; 4681 switch (event.button) { 4682 case 1: mb = MouseButton.left; break; // left 4683 case 2: mb = MouseButton.middle; break; // middle 4684 case 3: mb = MouseButton.right; break; // right 4685 case 4: mb = MouseButton.wheelUp; break; // scroll up 4686 case 5: mb = MouseButton.wheelDown; break; // scroll down 4687 case 6: break; // scroll left... 4688 case 7: break; // scroll right... 4689 case 8: mb = MouseButton.backButton; break; 4690 case 9: mb = MouseButton.forwardButton; break; 4691 default: 4692 } 4693 if (mb) { 4694 try { onClick()(mb); } catch (Exception) {} 4695 if (onClickEx !is null) try { onClickEx(event.x_root, event.y_root, mb, cast(ModifierState)event.state); } catch (Exception) {} 4696 } 4697 } 4698 break; 4699 case EventType.EnterNotify: 4700 if (onEnter !is null) { 4701 onEnter(e.xcrossing.x_root, e.xcrossing.y_root, cast(ModifierState)e.xcrossing.state); 4702 } 4703 break; 4704 case EventType.LeaveNotify: 4705 if (onLeave !is null) try { onLeave(); } catch (Exception) {} 4706 break; 4707 case EventType.DestroyNotify: 4708 active = false; 4709 CapableOfHandlingNativeEvent.nativeHandleMapping.remove(nativeHandle); 4710 break; 4711 case EventType.ConfigureNotify: 4712 auto event = e.xconfigure; 4713 this.width = event.width; 4714 this.height = event.height; 4715 // writeln(width, " x " , height, " @ ", event.x, " ", event.y); 4716 redraw(); 4717 break; 4718 default: return 1; 4719 } 4720 return 1; 4721 }; 4722 } 4723 4724 /* private */ void hideBalloon() { 4725 balloon.close(); 4726 version(with_timer) 4727 timer.destroy(); 4728 balloon = null; 4729 version(with_timer) 4730 timer = null; 4731 } 4732 4733 void redraw() { 4734 if (!active) return; 4735 4736 auto display = XDisplayConnection.get; 4737 GC gc; 4738 4739 // from https://stackoverflow.com/questions/10492275/how-to-upload-32-bit-image-to-server-side-pixmap 4740 4741 int gc_depth(int depth, Display *dpy, Window root, GC *gc) { 4742 Visual *visual; 4743 XVisualInfo vis_info; 4744 XSetWindowAttributes win_attr; 4745 c_ulong win_mask; 4746 4747 if(!XMatchVisualInfo(dpy, 0, depth, 4 /*TrueColor*/, &vis_info)) { 4748 assert(0); 4749 // return 1; 4750 } 4751 4752 visual = vis_info.visual; 4753 4754 win_attr.colormap = XCreateColormap(dpy, root, visual, AllocNone); 4755 win_attr.background_pixel = 0; 4756 win_attr.border_pixel = 0; 4757 4758 win_mask = CWBackPixel | CWColormap | CWBorderPixel; 4759 4760 *gc = XCreateGC(dpy, nativeHandle, 0, null); 4761 4762 return 0; 4763 } 4764 4765 if(useAlpha) 4766 gc_depth(32, display, RootWindow(display, DefaultScreen(display)), &gc); 4767 else 4768 gc = DefaultGC(display, DefaultScreen(display)); 4769 4770 XClearWindow(display, nativeHandle); 4771 4772 if(!useAlpha && img !is null) 4773 XSetClipMask(display, gc, clippixmap); 4774 4775 /+ 4776 XSetForeground(display, gc, 4777 cast(uint) 0 << 16 | 4778 cast(uint) 0 << 8 | 4779 cast(uint) 0); 4780 XFillRectangle(display, nativeHandle, gc, 0, 0, width, height); 4781 +/ 4782 4783 if (img is null) { 4784 XSetForeground(display, gc, 4785 cast(uint) 0 << 16 | 4786 cast(uint) 127 << 8 | 4787 cast(uint) 0); 4788 XFillArc(display, nativeHandle, 4789 gc, width / 4, height / 4, width * 2 / 4, height * 2 / 4, 0 * 64, 360 * 64); 4790 } else { 4791 int dx = 0; 4792 int dy = 0; 4793 if(width > img.width) 4794 dx = (width - img.width) / 2; 4795 if(height > img.height) 4796 dy = (height - img.height) / 2; 4797 // writeln(img.width, " ", img.height, " vs ", width, " ", height); 4798 XSetClipOrigin(display, gc, dx, dy); 4799 4800 int max(int a, int b) { 4801 if(a > b) return a; else return b; 4802 } 4803 4804 if (img.usingXshm) 4805 XShmPutImage(display, cast(Drawable)nativeHandle, gc, img.handle, 0, 0, dx, dy, max(img.width, width), max(img.height, height), false); 4806 else 4807 XPutImage(display, cast(Drawable)nativeHandle, gc, img.handle, 0, 0, dx, dy, max(img.width, width), max(img.height, height)); 4808 } 4809 XSetClipMask(display, gc, None); 4810 flushGui(); 4811 } 4812 4813 static Window getTrayOwner() { 4814 auto display = XDisplayConnection.get; 4815 auto i = cast(int) DefaultScreen(display); 4816 if(i < 10 && i >= 0) { 4817 static Atom atom; 4818 if(atom == None) 4819 atom = XInternAtom(display, cast(char*) ("_NET_SYSTEM_TRAY_S"~(cast(char) (i + '0')) ~ '\0').ptr, false); 4820 return XGetSelectionOwner(display, atom); 4821 } 4822 return None; 4823 } 4824 4825 static void sendTrayMessage(arch_long message, arch_long d1, arch_long d2, arch_long d3) { 4826 auto to = getTrayOwner(); 4827 auto display = XDisplayConnection.get; 4828 XEvent ev; 4829 ev.xclient.type = EventType.ClientMessage; 4830 ev.xclient.window = to; 4831 ev.xclient.message_type = GetAtom!("_NET_SYSTEM_TRAY_OPCODE", true)(display); 4832 ev.xclient.format = 32; 4833 ev.xclient.data.l[0] = CurrentTime; 4834 ev.xclient.data.l[1] = message; 4835 ev.xclient.data.l[2] = d1; 4836 ev.xclient.data.l[3] = d2; 4837 ev.xclient.data.l[4] = d3; 4838 4839 XSendEvent(XDisplayConnection.get, to, false, EventMask.NoEventMask, &ev); 4840 } 4841 4842 private static NotificationAreaIcon[] activeIcons; 4843 4844 // FIXME: possible leak with this stuff, should be able to clear it and stuff. 4845 private void newManager() { 4846 close(); 4847 createXWin(); 4848 4849 if(this.clippixmap) 4850 XFreePixmap(XDisplayConnection.get, clippixmap); 4851 if(this.originalMemoryImage) 4852 this.icon = this.originalMemoryImage; 4853 else if(this.img) 4854 this.icon = this.img; 4855 } 4856 4857 private bool useAlpha = false; 4858 4859 private void createXWin () { 4860 // create window 4861 auto display = XDisplayConnection.get; 4862 4863 // to check for MANAGER on root window to catch new/changed tray owners 4864 XDisplayConnection.addRootInput(EventMask.StructureNotifyMask); 4865 // so if a thing does appear, we can handle it 4866 foreach(ai; activeIcons) 4867 if(ai is this) 4868 goto alreadythere; 4869 activeIcons ~= this; 4870 alreadythere: 4871 4872 // and check for an existing tray 4873 auto trayOwner = getTrayOwner(); 4874 if(trayOwner == None) 4875 return; 4876 //throw new Exception("No notification area found"); 4877 4878 Visual* v = cast(Visual*) CopyFromParent; 4879 4880 // GNOME's default is 22x22 and KDE assumes all icons are going to match that then bitmap scales 4881 // from there. It is ugly and stupid but this gives the fewest artifacts. Good environments will send 4882 // a resize event later. 4883 width = 22; 4884 height = 22; 4885 4886 // if they system gave us a 32 bit visual we need to switch to it too 4887 int depth = 24; 4888 4889 auto visualProp = getX11PropertyData(trayOwner, GetAtom!("_NET_SYSTEM_TRAY_VISUAL", true)(display)); 4890 if(visualProp !is null) { 4891 c_ulong[] info = cast(c_ulong[]) visualProp; 4892 if(info.length == 1) { 4893 auto vid = info[0]; 4894 int returned; 4895 XVisualInfo t; 4896 t.visualid = vid; 4897 auto got = XGetVisualInfo(display, VisualIDMask, &t, &returned); 4898 if(got !is null) { 4899 if(returned == 1) { 4900 v = got.visual; 4901 depth = got.depth; 4902 // writeln("using special visual ", got.depth); 4903 // writeln(depth); 4904 } 4905 XFree(got); 4906 } 4907 } 4908 } 4909 4910 int CWFlags = CWBackPixel | CWBorderPixel | CWOverrideRedirect; 4911 XSetWindowAttributes attr; 4912 attr.background_pixel = 0; 4913 attr.border_pixel = 0; 4914 attr.override_redirect = 0; 4915 if(v !is cast(Visual*) CopyFromParent) { 4916 attr.colormap = XCreateColormap(display, RootWindow(display, DefaultScreen(display)), v, AllocNone); 4917 CWFlags |= CWColormap; 4918 if(depth == 32) 4919 useAlpha = true; 4920 else 4921 goto plain; 4922 } else { 4923 plain: 4924 attr.background_pixmap = 1 /* ParentRelative */; 4925 CWFlags |= CWBackPixmap; 4926 } 4927 4928 auto nativeWindow = XCreateWindow(display, RootWindow(display, DefaultScreen(display)), 0, 0, width, height, 0, depth, InputOutput, v, CWFlags, &attr); 4929 4930 assert(nativeWindow); 4931 4932 if(!useAlpha) 4933 XSetWindowBackgroundPixmap(display, nativeWindow, 1 /* ParentRelative */); 4934 4935 nativeHandle = nativeWindow; 4936 4937 ///+ 4938 arch_ulong[2] info; 4939 info[0] = 0; 4940 info[1] = 1; 4941 4942 string title = this.name is null ? "simpledisplay.d program" : this.name; 4943 auto XA_UTF8 = XInternAtom(display, "UTF8_STRING".ptr, false); 4944 auto XA_NETWM_NAME = XInternAtom(display, "_NET_WM_NAME".ptr, false); 4945 XChangeProperty(display, nativeWindow, XA_NETWM_NAME, XA_UTF8, 8, PropModeReplace, title.ptr, cast(uint)title.length); 4946 4947 XChangeProperty( 4948 display, 4949 nativeWindow, 4950 GetAtom!("_XEMBED_INFO", true)(display), 4951 GetAtom!("_XEMBED_INFO", true)(display), 4952 32 /* bits */, 4953 0 /*PropModeReplace*/, 4954 info.ptr, 4955 2); 4956 4957 import core.sys.posix.unistd; 4958 arch_ulong pid = getpid(); 4959 4960 XChangeProperty( 4961 display, 4962 nativeWindow, 4963 GetAtom!("_NET_WM_PID", true)(display), 4964 XA_CARDINAL, 4965 32 /* bits */, 4966 0 /*PropModeReplace*/, 4967 &pid, 4968 1); 4969 4970 updateNetWmIcon(); 4971 4972 if (sdpyWindowClassStr !is null && sdpyWindowClassStr[0]) { 4973 //{ import core.stdc.stdio; printf("winclass: [%s]\n", sdpyWindowClassStr); } 4974 XClassHint klass; 4975 XWMHints wh; 4976 XSizeHints size; 4977 klass.res_name = sdpyWindowClassStr; 4978 klass.res_class = sdpyWindowClassStr; 4979 XSetWMProperties(display, nativeWindow, null, null, null, 0, &size, &wh, &klass); 4980 } 4981 4982 // believe it or not, THIS is what xfce needed for the 9999 issue 4983 XSizeHints sh; 4984 c_long spr; 4985 XGetWMNormalHints(display, nativeWindow, &sh, &spr); 4986 sh.flags |= PMaxSize | PMinSize; 4987 // FIXME maybe nicer resizing 4988 sh.min_width = 16; 4989 sh.min_height = 16; 4990 sh.max_width = 22; 4991 sh.max_height = 22; 4992 XSetWMNormalHints(display, nativeWindow, &sh); 4993 4994 4995 //+/ 4996 4997 4998 XSelectInput(display, nativeWindow, 4999 EventMask.ButtonPressMask | EventMask.ExposureMask | EventMask.StructureNotifyMask | EventMask.VisibilityChangeMask | 5000 EventMask.EnterWindowMask | EventMask.LeaveWindowMask); 5001 5002 sendTrayMessage(SYSTEM_TRAY_REQUEST_DOCK, nativeWindow, 0, 0); 5003 // XMapWindow(display, nativeWindow); // to demo it w/o a tray 5004 5005 CapableOfHandlingNativeEvent.nativeHandleMapping[nativeWindow] = this; 5006 active = true; 5007 } 5008 5009 void updateNetWmIcon() { 5010 if(img is null) return; 5011 auto display = XDisplayConnection.get; 5012 // FIXME: ensure this is correct 5013 arch_ulong[] buffer; 5014 auto imgMi = img.toTrueColorImage; 5015 buffer ~= imgMi.width; 5016 buffer ~= imgMi.height; 5017 foreach(c; imgMi.imageData.colors) { 5018 arch_ulong b; 5019 b |= c.a << 24; 5020 b |= c.r << 16; 5021 b |= c.g << 8; 5022 b |= c.b; 5023 buffer ~= b; 5024 } 5025 5026 XChangeProperty( 5027 display, 5028 nativeHandle, 5029 GetAtom!"_NET_WM_ICON"(display), 5030 GetAtom!"CARDINAL"(display), 5031 32 /* bits */, 5032 0 /*PropModeReplace*/, 5033 buffer.ptr, 5034 cast(int) buffer.length); 5035 } 5036 5037 5038 5039 private SimpleWindow balloon; 5040 version(with_timer) 5041 private Timer timer; 5042 5043 private Window nativeHandle; 5044 private Pixmap clippixmap = None; 5045 private int width = 16; 5046 private int height = 16; 5047 private bool active = false; 5048 5049 void delegate (int x, int y, MouseButton button, ModifierState mods) onClickEx; /// x and y are globals (relative to root window). X11 only. 5050 void delegate (int x, int y, ModifierState mods) onEnter; /// x and y are global window coordinates. X11 only. 5051 void delegate () onLeave; /// X11 only. 5052 5053 @property bool closed () const pure nothrow @safe @nogc { return !active; } /// 5054 5055 /// X11 only. Get global window coordinates and size. This can be used to show various notifications. 5056 void getWindowRect (out int x, out int y, out int width, out int height) { 5057 if (!active) { width = 1; height = 1; return; } // 1: just in case 5058 Window dummyw; 5059 auto dpy = XDisplayConnection.get; 5060 //XWindowAttributes xwa; 5061 //XGetWindowAttributes(dpy, nativeHandle, &xwa); 5062 //XTranslateCoordinates(dpy, nativeHandle, RootWindow(dpy, DefaultScreen(dpy)), xwa.x, xwa.y, &x, &y, &dummyw); 5063 XTranslateCoordinates(dpy, nativeHandle, RootWindow(dpy, DefaultScreen(dpy)), x, y, &x, &y, &dummyw); 5064 width = this.width; 5065 height = this.height; 5066 } 5067 } 5068 5069 /+ 5070 What I actually want from this: 5071 5072 * set / change: icon, tooltip 5073 * handle: mouse click, right click 5074 * show: notification bubble. 5075 +/ 5076 5077 version(Windows) { 5078 WindowsIcon win32Icon; 5079 HWND hwnd; 5080 5081 NOTIFYICONDATAW data; 5082 5083 NativeEventHandler getNativeEventHandler() { 5084 return delegate int(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam, out int mustReturn) { 5085 if(msg == WM_USER) { 5086 auto event = LOWORD(lParam); 5087 auto iconId = HIWORD(lParam); 5088 //auto x = GET_X_LPARAM(wParam); 5089 //auto y = GET_Y_LPARAM(wParam); 5090 switch(event) { 5091 case WM_LBUTTONDOWN: 5092 onClick()(MouseButton.left); 5093 break; 5094 case WM_RBUTTONDOWN: 5095 onClick()(MouseButton.right); 5096 break; 5097 case WM_MBUTTONDOWN: 5098 onClick()(MouseButton.middle); 5099 break; 5100 case WM_MOUSEMOVE: 5101 // sent, we could use it. 5102 break; 5103 case WM_MOUSEWHEEL: 5104 // NOT SENT 5105 break; 5106 //case NIN_KEYSELECT: 5107 //case NIN_SELECT: 5108 //break; 5109 default: {} 5110 } 5111 } 5112 return 0; 5113 }; 5114 } 5115 5116 enum NIF_SHOWTIP = 0x00000080; 5117 5118 private static struct NOTIFYICONDATAW { 5119 DWORD cbSize; 5120 HWND hWnd; 5121 UINT uID; 5122 UINT uFlags; 5123 UINT uCallbackMessage; 5124 HICON hIcon; 5125 WCHAR[128] szTip; 5126 DWORD dwState; 5127 DWORD dwStateMask; 5128 WCHAR[256] szInfo; 5129 union { 5130 UINT uTimeout; 5131 UINT uVersion; 5132 } 5133 WCHAR[64] szInfoTitle; 5134 DWORD dwInfoFlags; 5135 GUID guidItem; 5136 HICON hBalloonIcon; 5137 } 5138 5139 } 5140 5141 /++ 5142 Note that on Windows, only left, right, and middle buttons are sent. 5143 Mouse wheel buttons are NOT set, so don't rely on those events if your 5144 program is meant to be used on Windows too. 5145 +/ 5146 this(string name, MemoryImage icon, void delegate(MouseButton button) onClick) { 5147 // The canonical constructor for Windows needs the MemoryImage, so it is here, 5148 // but on X, we need an Image, so its canonical ctor is there. They should 5149 // forward to each other though. 5150 version(X11) { 5151 this.name = name; 5152 this.onClick = onClick; 5153 createXWin(); 5154 this.icon = icon; 5155 } else version(Windows) { 5156 this.onClick = onClick; 5157 this.win32Icon = new WindowsIcon(icon); 5158 5159 HINSTANCE hInstance = cast(HINSTANCE) GetModuleHandle(null); 5160 5161 static bool registered = false; 5162 if(!registered) { 5163 WNDCLASSEX wc; 5164 wc.cbSize = wc.sizeof; 5165 wc.hInstance = hInstance; 5166 wc.lpfnWndProc = &WndProc; 5167 wc.lpszClassName = "arsd_simpledisplay_notification_icon"w.ptr; 5168 if(!RegisterClassExW(&wc)) 5169 throw new WindowsApiException("RegisterClass", GetLastError()); 5170 registered = true; 5171 } 5172 5173 this.hwnd = CreateWindowW("arsd_simpledisplay_notification_icon"w.ptr, "test"w.ptr /* name */, 0 /* dwStyle */, 0, 0, 0, 0, HWND_MESSAGE, null, hInstance, null); 5174 if(hwnd is null) 5175 throw new WindowsApiException("CreateWindow", GetLastError()); 5176 5177 data.cbSize = data.sizeof; 5178 data.hWnd = hwnd; 5179 data.uID = cast(uint) cast(void*) this; 5180 data.uFlags = NIF_MESSAGE | NIF_ICON | NIF_TIP | NIF_STATE | NIF_SHOWTIP /* use default tooltip, for now. */; 5181 // NIF_INFO means show balloon 5182 data.uCallbackMessage = WM_USER; 5183 data.hIcon = this.win32Icon.hIcon; 5184 data.szTip = ""; // FIXME 5185 data.dwState = 0; // NIS_HIDDEN; // windows vista 5186 data.dwStateMask = NIS_HIDDEN; // windows vista 5187 5188 data.uVersion = 4; // NOTIFYICON_VERSION_4; // Windows Vista and up 5189 5190 5191 Shell_NotifyIcon(NIM_ADD, cast(NOTIFYICONDATA*) &data); 5192 5193 CapableOfHandlingNativeEvent.nativeHandleMapping[this.hwnd] = this; 5194 } else version(OSXCocoa) { 5195 throw new NotYetImplementedException(); 5196 } else static assert(0); 5197 } 5198 5199 /// ditto 5200 this(string name, Image icon, void delegate(MouseButton button) onClick) { 5201 version(X11) { 5202 this.onClick = onClick; 5203 this.name = name; 5204 createXWin(); 5205 this.icon = icon; 5206 } else version(Windows) { 5207 this(name, icon is null ? null : icon.toTrueColorImage(), onClick); 5208 } else version(OSXCocoa) { 5209 throw new NotYetImplementedException(); 5210 } else static assert(0); 5211 } 5212 5213 version(X11) { 5214 /++ 5215 X-specific extension (for now at least) 5216 +/ 5217 this(string name, MemoryImage icon, void delegate(int x, int y, MouseButton button, ModifierState mods) onClickEx) { 5218 this.onClickEx = onClickEx; 5219 createXWin(); 5220 if (icon !is null) this.icon = icon; 5221 } 5222 5223 /// ditto 5224 this(string name, Image icon, void delegate(int x, int y, MouseButton button, ModifierState mods) onClickEx) { 5225 this.onClickEx = onClickEx; 5226 createXWin(); 5227 this.icon = icon; 5228 } 5229 } 5230 5231 private void delegate (MouseButton button) onClick_; 5232 5233 /// 5234 @property final void delegate(MouseButton) onClick() { 5235 if(onClick_ is null) 5236 onClick_ = delegate void(MouseButton) {}; 5237 return onClick_; 5238 } 5239 5240 /// ditto 5241 @property final void onClick(void delegate(MouseButton) handler) { 5242 // I made this a property setter so we can wrap smaller arg 5243 // delegates and just forward all to onClickEx or something. 5244 onClick_ = handler; 5245 } 5246 5247 5248 string name_; 5249 @property void name(string n) { 5250 name_ = n; 5251 } 5252 5253 @property string name() { 5254 return name_; 5255 } 5256 5257 private MemoryImage originalMemoryImage; 5258 5259 /// 5260 @property void icon(MemoryImage i) { 5261 version(X11) { 5262 this.originalMemoryImage = i; 5263 if (!active) return; 5264 if (i !is null) { 5265 this.img = Image.fromMemoryImage(i, useAlpha, false); 5266 if(!useAlpha) 5267 this.clippixmap = transparencyMaskFromMemoryImage(i, nativeHandle); 5268 // writeln("using pixmap ", clippixmap); 5269 updateNetWmIcon(); 5270 redraw(); 5271 } else { 5272 if (this.img !is null) { 5273 this.img = null; 5274 redraw(); 5275 } 5276 } 5277 } else version(Windows) { 5278 this.win32Icon = new WindowsIcon(i); 5279 5280 data.uFlags = NIF_ICON; 5281 data.hIcon = this.win32Icon.hIcon; 5282 5283 Shell_NotifyIcon(NIM_MODIFY, cast(NOTIFYICONDATA*) &data); 5284 } else version(OSXCocoa) { 5285 throw new NotYetImplementedException(); 5286 } else static assert(0); 5287 } 5288 5289 /// ditto 5290 @property void icon (Image i) { 5291 version(X11) { 5292 if (!active) return; 5293 if (i !is img) { 5294 originalMemoryImage = null; 5295 img = i; 5296 redraw(); 5297 } 5298 } else version(Windows) { 5299 this.icon(i is null ? null : i.toTrueColorImage()); 5300 } else version(OSXCocoa) { 5301 throw new NotYetImplementedException(); 5302 } else static assert(0); 5303 } 5304 5305 /++ 5306 Shows a balloon notification. You can only show one balloon at a time, if you call 5307 it twice while one is already up, the first balloon will be replaced. 5308 5309 5310 The user is free to block notifications and they will automatically disappear after 5311 a timeout period. 5312 5313 Params: 5314 title = Title of the notification. Must be 40 chars or less or the OS may truncate it. 5315 message = The message to pop up. Must be 220 chars or less or the OS may truncate it. 5316 icon = the icon to display with the notification. If null, it uses your existing icon. 5317 onclick = delegate called if the user clicks the balloon. (not yet implemented) 5318 timeout = your suggested timeout period. The operating system is free to ignore your suggestion. 5319 +/ 5320 void showBalloon(string title, string message, MemoryImage icon = null, void delegate() onclick = null, int timeout = 2_500) { 5321 bool useCustom = true; 5322 version(libnotify) { 5323 if(onclick is null) // libnotify impl doesn't support callbacks yet because it doesn't do a dbus message loop 5324 try { 5325 if(!active) return; 5326 5327 if(libnotify is null) { 5328 libnotify = new C_DynamicLibrary("libnotify.so"); 5329 libnotify.call!("notify_init", int, const char*)()((ApplicationName ~ "\0").ptr); 5330 } 5331 5332 auto n = libnotify.call!("notify_notification_new", void*, const char*, const char*, const char*)()((title~"\0").ptr, (message~"\0").ptr, null /* icon */); 5333 5334 libnotify.call!("notify_notification_set_timeout", void, void*, int)()(n, timeout); 5335 5336 if(onclick) { 5337 libnotify_action_delegates[libnotify_action_delegates_count] = onclick; 5338 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); 5339 libnotify_action_delegates_count++; 5340 } 5341 5342 // FIXME icon 5343 5344 // set hint image-data 5345 // set default action for onclick 5346 5347 void* error; 5348 libnotify.call!("notify_notification_show", bool, void*, void**)()(n, &error); 5349 5350 useCustom = false; 5351 } catch(Exception e) { 5352 5353 } 5354 } 5355 5356 version(X11) { 5357 if(useCustom) { 5358 if(!active) return; 5359 if(balloon) { 5360 hideBalloon(); 5361 } 5362 // I know there are two specs for this, but one is never 5363 // implemented by any window manager I have ever seen, and 5364 // the other is a bloated mess and too complicated for simpledisplay... 5365 // so doing my own little window instead. 5366 balloon = new SimpleWindow(380, 120, null, OpenGlOptions.no, Resizability.fixedSize, WindowTypes.notification, WindowFlags.dontAutoShow/*, window*/); 5367 5368 int x, y, width, height; 5369 getWindowRect(x, y, width, height); 5370 5371 int bx = x - balloon.width; 5372 int by = y - balloon.height; 5373 if(bx < 0) 5374 bx = x + width + balloon.width; 5375 if(by < 0) 5376 by = y + height; 5377 5378 // just in case, make sure it is actually on scren 5379 if(bx < 0) 5380 bx = 0; 5381 if(by < 0) 5382 by = 0; 5383 5384 balloon.move(bx, by); 5385 auto painter = balloon.draw(); 5386 painter.fillColor = Color(220, 220, 220); 5387 painter.outlineColor = Color.black; 5388 painter.drawRectangle(Point(0, 0), balloon.width, balloon.height); 5389 auto iconWidth = icon is null ? 0 : icon.width; 5390 if(icon) 5391 painter.drawImage(Point(4, 4), Image.fromMemoryImage(icon)); 5392 iconWidth += 6; // margin around the icon 5393 5394 // draw a close button 5395 painter.outlineColor = Color(44, 44, 44); 5396 painter.fillColor = Color(255, 255, 255); 5397 painter.drawRectangle(Point(balloon.width - 15, 3), 13, 13); 5398 painter.pen = Pen(Color.black, 3); 5399 painter.drawLine(Point(balloon.width - 14, 4), Point(balloon.width - 4, 14)); 5400 painter.drawLine(Point(balloon.width - 4, 4), Point(balloon.width - 14, 13)); 5401 painter.pen = Pen(Color.black, 1); 5402 painter.fillColor = Color(220, 220, 220); 5403 5404 // Draw the title and message 5405 painter.drawText(Point(4 + iconWidth, 4), title); 5406 painter.drawLine( 5407 Point(4 + iconWidth, 4 + painter.fontHeight + 1), 5408 Point(balloon.width - 4, 4 + painter.fontHeight + 1), 5409 ); 5410 painter.drawText(Point(4 + iconWidth, 4 + painter.fontHeight + 4), message); 5411 5412 balloon.setEventHandlers( 5413 (MouseEvent ev) { 5414 if(ev.type == MouseEventType.buttonPressed) { 5415 if(ev.x > balloon.width - 16 && ev.y < 16) 5416 hideBalloon(); 5417 else if(onclick) 5418 onclick(); 5419 } 5420 } 5421 ); 5422 balloon.show(); 5423 5424 version(with_timer) 5425 timer = new Timer(timeout, &hideBalloon); 5426 else {} // FIXME 5427 } 5428 } else version(Windows) { 5429 enum NIF_INFO = 0x00000010; 5430 5431 data.uFlags = NIF_INFO; 5432 5433 // FIXME: go back to the last valid unicode code point 5434 if(title.length > 40) 5435 title = title[0 .. 40]; 5436 if(message.length > 220) 5437 message = message[0 .. 220]; 5438 5439 enum NIIF_RESPECT_QUIET_TIME = 0x00000080; 5440 enum NIIF_LARGE_ICON = 0x00000020; 5441 enum NIIF_NOSOUND = 0x00000010; 5442 enum NIIF_USER = 0x00000004; 5443 enum NIIF_ERROR = 0x00000003; 5444 enum NIIF_WARNING = 0x00000002; 5445 enum NIIF_INFO = 0x00000001; 5446 enum NIIF_NONE = 0; 5447 5448 WCharzBuffer t = WCharzBuffer(title); 5449 WCharzBuffer m = WCharzBuffer(message); 5450 5451 t.copyInto(data.szInfoTitle); 5452 m.copyInto(data.szInfo); 5453 data.dwInfoFlags = NIIF_RESPECT_QUIET_TIME; 5454 5455 if(icon !is null) { 5456 auto i = new WindowsIcon(icon); 5457 data.hBalloonIcon = i.hIcon; 5458 data.dwInfoFlags |= NIIF_USER; 5459 } 5460 5461 Shell_NotifyIcon(NIM_MODIFY, cast(NOTIFYICONDATA*) &data); 5462 } else version(OSXCocoa) { 5463 throw new NotYetImplementedException(); 5464 } else static assert(0); 5465 } 5466 5467 /// 5468 //version(Windows) 5469 void show() { 5470 version(X11) { 5471 if(!hidden) 5472 return; 5473 sendTrayMessage(SYSTEM_TRAY_REQUEST_DOCK, nativeHandle, 0, 0); 5474 hidden = false; 5475 } else version(Windows) { 5476 data.uFlags = NIF_STATE; 5477 data.dwState = 0; // NIS_HIDDEN; // windows vista 5478 data.dwStateMask = NIS_HIDDEN; // windows vista 5479 Shell_NotifyIcon(NIM_MODIFY, cast(NOTIFYICONDATA*) &data); 5480 } else version(OSXCocoa) { 5481 throw new NotYetImplementedException(); 5482 } else static assert(0); 5483 } 5484 5485 version(X11) 5486 bool hidden = false; 5487 5488 /// 5489 //version(Windows) 5490 void hide() { 5491 version(X11) { 5492 if(hidden) 5493 return; 5494 hidden = true; 5495 XUnmapWindow(XDisplayConnection.get, nativeHandle); 5496 } else version(Windows) { 5497 data.uFlags = NIF_STATE; 5498 data.dwState = NIS_HIDDEN; // windows vista 5499 data.dwStateMask = NIS_HIDDEN; // windows vista 5500 Shell_NotifyIcon(NIM_MODIFY, cast(NOTIFYICONDATA*) &data); 5501 } else version(OSXCocoa) { 5502 throw new NotYetImplementedException(); 5503 } else static assert(0); 5504 } 5505 5506 /// 5507 void close () { 5508 version(X11) { 5509 if (active) { 5510 active = false; // event handler will set this too, but meh 5511 XUnmapWindow(XDisplayConnection.get, nativeHandle); // 'cause why not; let's be polite 5512 XDestroyWindow(XDisplayConnection.get, nativeHandle); 5513 flushGui(); 5514 } 5515 } else version(Windows) { 5516 Shell_NotifyIcon(NIM_DELETE, cast(NOTIFYICONDATA*) &data); 5517 } else version(OSXCocoa) { 5518 throw new NotYetImplementedException(); 5519 } else static assert(0); 5520 } 5521 5522 ~this() { 5523 if(!thisIsGuiThread) return; // FIXME: leaks if multithreaded gc 5524 version(X11) 5525 if(clippixmap != None) 5526 XFreePixmap(XDisplayConnection.get, clippixmap); 5527 close(); 5528 } 5529 } 5530 5531 version(X11) 5532 /// Call `XFreePixmap` on the return value. 5533 Pixmap transparencyMaskFromMemoryImage(MemoryImage i, Window window) { 5534 char[] data = new char[](i.width * i.height / 8 + 2); 5535 data[] = 0; 5536 5537 int bitOffset = 0; 5538 foreach(c; i.getAsTrueColorImage().imageData.colors) { // FIXME inefficient unnecessary conversion in palette cases 5539 ubyte v = c.a > 128 ? 1 : 0; 5540 data[bitOffset / 8] |= v << (bitOffset%8); 5541 bitOffset++; 5542 } 5543 auto handle = XCreateBitmapFromData(XDisplayConnection.get, cast(Drawable) window, data.ptr, i.width, i.height); 5544 return handle; 5545 } 5546 5547 5548 // basic functions to make timers 5549 /** 5550 A timer that will trigger your function on a given interval. 5551 5552 5553 You create a timer with an interval and a callback. It will continue 5554 to fire on the interval until it is destroyed. 5555 5556 There are currently no one-off timers (instead, just create one and 5557 destroy it when it is triggered) nor are there pause/resume functions - 5558 the timer must again be destroyed and recreated if you want to pause it. 5559 5560 --- 5561 auto timer = new Timer(50, { it happened!; }); 5562 timer.destroy(); 5563 --- 5564 5565 Timers can only be expected to fire when the event loop is running and only 5566 once per iteration through the event loop. 5567 5568 History: 5569 Prior to December 9, 2020, a timer pulse set too high with a handler too 5570 slow could lock up the event loop. It now guarantees other things will 5571 get a chance to run between timer calls, even if that means not keeping up 5572 with the requested interval. 5573 */ 5574 version(with_timer) { 5575 class Timer { 5576 // FIXME: needs pause and unpause 5577 // FIXME: I might add overloads for ones that take a count of 5578 // how many elapsed since last time (on Windows, it will divide 5579 // the ticks thing given, on Linux it is just available) and 5580 // maybe one that takes an instance of the Timer itself too 5581 /// Create a timer with a callback when it triggers. 5582 this(int intervalInMilliseconds, void delegate() onPulse) { 5583 assert(onPulse !is null); 5584 5585 this.intervalInMilliseconds = intervalInMilliseconds; 5586 this.onPulse = onPulse; 5587 5588 version(Windows) { 5589 /* 5590 handle = SetTimer(null, handle, intervalInMilliseconds, &timerCallback); 5591 if(handle == 0) 5592 throw new WindowsApiException("SetTimer", GetLastError()); 5593 */ 5594 5595 // thanks to Archival 998 for the WaitableTimer blocks 5596 handle = CreateWaitableTimer(null, false, null); 5597 long initialTime = -intervalInMilliseconds; 5598 if(handle is null || !SetWaitableTimer(handle, cast(LARGE_INTEGER*)&initialTime, intervalInMilliseconds, &timerCallback, handle, false)) 5599 throw new WindowsApiException("SetWaitableTimer", GetLastError()); 5600 5601 mapping[handle] = this; 5602 5603 } else version(linux) { 5604 static import ep = core.sys.linux.epoll; 5605 5606 import core.sys.linux.timerfd; 5607 5608 fd = timerfd_create(CLOCK_MONOTONIC, 0); 5609 if(fd == -1) 5610 throw new Exception("timer create failed"); 5611 5612 mapping[fd] = this; 5613 5614 itimerspec value = makeItimerspec(intervalInMilliseconds); 5615 5616 if(timerfd_settime(fd, 0, &value, null) == -1) 5617 throw new Exception("couldn't make pulse timer"); 5618 5619 version(with_eventloop) { 5620 import arsd.eventloop; 5621 addFileEventListeners(fd, &trigger, null, null); 5622 } else { 5623 prepareEventLoop(); 5624 5625 ep.epoll_event ev = void; 5626 ev.events = ep.EPOLLIN; 5627 ev.data.fd = fd; 5628 ep.epoll_ctl(epollFd, ep.EPOLL_CTL_ADD, fd, &ev); 5629 } 5630 } else featureNotImplemented(); 5631 } 5632 5633 private int intervalInMilliseconds; 5634 5635 // just cuz I sometimes call it this. 5636 alias dispose = destroy; 5637 5638 /// Stop and destroy the timer object. 5639 void destroy() { 5640 version(Windows) { 5641 staticDestroy(handle); 5642 handle = null; 5643 } else version(linux) { 5644 staticDestroy(fd); 5645 fd = -1; 5646 } else featureNotImplemented(); 5647 } 5648 5649 version(Windows) 5650 static void staticDestroy(HANDLE handle) { 5651 if(handle) { 5652 // KillTimer(null, handle); 5653 CancelWaitableTimer(cast(void*)handle); 5654 mapping.remove(handle); 5655 CloseHandle(handle); 5656 } 5657 } 5658 else version(linux) 5659 static void staticDestroy(int fd) { 5660 if(fd != -1) { 5661 import unix = core.sys.posix.unistd; 5662 static import ep = core.sys.linux.epoll; 5663 5664 version(with_eventloop) { 5665 import arsd.eventloop; 5666 removeFileEventListeners(fd); 5667 } else { 5668 ep.epoll_event ev = void; 5669 ev.events = ep.EPOLLIN; 5670 ev.data.fd = fd; 5671 5672 ep.epoll_ctl(epollFd, ep.EPOLL_CTL_DEL, fd, &ev); 5673 } 5674 unix.close(fd); 5675 mapping.remove(fd); 5676 } 5677 } 5678 5679 ~this() { 5680 version(Windows) { if(handle) 5681 cleanupQueue.queue!staticDestroy(handle); 5682 } else version(linux) { if(fd != -1) 5683 cleanupQueue.queue!staticDestroy(fd); 5684 } 5685 } 5686 5687 void changeTime(int intervalInMilliseconds) 5688 { 5689 this.intervalInMilliseconds = intervalInMilliseconds; 5690 version(Windows) 5691 { 5692 if(handle) 5693 { 5694 //handle = SetTimer(null, handle, intervalInMilliseconds, &timerCallback); 5695 long initialTime = -intervalInMilliseconds; 5696 if(handle is null || !SetWaitableTimer(handle, cast(LARGE_INTEGER*)&initialTime, intervalInMilliseconds, &timerCallback, handle, false)) 5697 throw new WindowsApiException("couldn't change pulse timer", GetLastError()); 5698 } 5699 } else version(linux) { 5700 import core.sys.linux.timerfd; 5701 5702 itimerspec value = makeItimerspec(intervalInMilliseconds); 5703 if(timerfd_settime(fd, 0, &value, null) == -1) { 5704 throw new Exception("couldn't change pulse timer"); 5705 } 5706 } else { 5707 assert(false, "Timer.changeTime(int) is not implemented for this platform"); 5708 } 5709 } 5710 5711 5712 private: 5713 5714 void delegate() onPulse; 5715 5716 int lastEventLoopRoundTriggered; 5717 5718 version(linux) { 5719 static auto makeItimerspec(int intervalInMilliseconds) { 5720 import core.sys.linux.timerfd; 5721 5722 itimerspec value; 5723 value.it_value.tv_sec = cast(int) (intervalInMilliseconds / 1000); 5724 value.it_value.tv_nsec = (intervalInMilliseconds % 1000) * 1000_000; 5725 5726 value.it_interval.tv_sec = cast(int) (intervalInMilliseconds / 1000); 5727 value.it_interval.tv_nsec = (intervalInMilliseconds % 1000) * 1000_000; 5728 5729 return value; 5730 } 5731 } 5732 5733 void trigger() { 5734 version(linux) { 5735 import unix = core.sys.posix.unistd; 5736 long val; 5737 unix.read(fd, &val, val.sizeof); // gotta clear the pipe 5738 } else version(Windows) { 5739 if(this.lastEventLoopRoundTriggered == eventLoopRound) 5740 return; // never try to actually run faster than the event loop 5741 lastEventLoopRoundTriggered = eventLoopRound; 5742 } else featureNotImplemented(); 5743 5744 onPulse(); 5745 } 5746 5747 version(Windows) 5748 void rearm() { 5749 5750 } 5751 5752 version(Windows) 5753 extern(Windows) 5754 //static void timerCallback(HWND, UINT, UINT_PTR timer, DWORD dwTime) nothrow { 5755 static void timerCallback(HANDLE timer, DWORD lowTime, DWORD hiTime) nothrow { 5756 if(Timer* t = timer in mapping) { 5757 try 5758 (*t).trigger(); 5759 catch(Exception e) { sdpy_abort(e); assert(0); } 5760 } 5761 } 5762 5763 version(Windows) { 5764 //UINT_PTR handle; 5765 //static Timer[UINT_PTR] mapping; 5766 HANDLE handle; 5767 __gshared Timer[HANDLE] mapping; 5768 } else version(linux) { 5769 int fd = -1; 5770 __gshared Timer[int] mapping; 5771 } else version(OSXCocoa) { 5772 } else static assert(0, "timer not supported"); 5773 } 5774 } 5775 5776 version(Windows) 5777 private int eventLoopRound; 5778 5779 version(Windows) 5780 /// 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 5781 class WindowsHandleReader { 5782 /// 5783 this(void delegate() onReady, HANDLE handle) { 5784 this.onReady = onReady; 5785 this.handle = handle; 5786 5787 mapping[handle] = this; 5788 5789 enable(); 5790 } 5791 5792 /// 5793 void enable() { 5794 auto el = EventLoop.get().impl; 5795 el.handles ~= handle; 5796 } 5797 5798 /// 5799 void disable() { 5800 auto el = EventLoop.get().impl; 5801 for(int i = 0; i < el.handles.length; i++) { 5802 if(el.handles[i] is handle) { 5803 el.handles[i] = el.handles[$-1]; 5804 el.handles = el.handles[0 .. $-1]; 5805 return; 5806 } 5807 } 5808 } 5809 5810 void dispose() { 5811 disable(); 5812 if(handle) 5813 mapping.remove(handle); 5814 handle = null; 5815 } 5816 5817 void ready() { 5818 if(onReady) 5819 onReady(); 5820 } 5821 5822 HANDLE handle; 5823 void delegate() onReady; 5824 5825 __gshared WindowsHandleReader[HANDLE] mapping; 5826 } 5827 5828 version(Posix) 5829 /// Lets you add files to the event loop for reading. Use at your own risk. 5830 class PosixFdReader { 5831 /// 5832 this(void delegate() onReady, int fd, bool captureReads = true, bool captureWrites = false) { 5833 this((int, bool, bool) { onReady(); }, fd, captureReads, captureWrites); 5834 } 5835 5836 /// 5837 this(void delegate(int) onReady, int fd, bool captureReads = true, bool captureWrites = false) { 5838 this((int fd, bool, bool) { onReady(fd); }, fd, captureReads, captureWrites); 5839 } 5840 5841 /// 5842 this(void delegate(int fd, bool read, bool write) onReady, int fd, bool captureReads = true, bool captureWrites = false) { 5843 this.onReady = onReady; 5844 this.fd = fd; 5845 this.captureWrites = captureWrites; 5846 this.captureReads = captureReads; 5847 5848 mapping[fd] = this; 5849 5850 version(with_eventloop) { 5851 import arsd.eventloop; 5852 addFileEventListeners(fd, &readyel); 5853 } else { 5854 enable(); 5855 } 5856 } 5857 5858 bool captureReads; 5859 bool captureWrites; 5860 5861 version(with_eventloop) {} else 5862 /// 5863 void enable() { 5864 prepareEventLoop(); 5865 5866 enabled = true; 5867 5868 version(linux) { 5869 static import ep = core.sys.linux.epoll; 5870 ep.epoll_event ev = void; 5871 ev.events = (captureReads ? ep.EPOLLIN : 0) | (captureWrites ? ep.EPOLLOUT : 0); 5872 // writeln("enable ", fd, " ", captureReads, " ", captureWrites); 5873 ev.data.fd = fd; 5874 ep.epoll_ctl(epollFd, ep.EPOLL_CTL_ADD, fd, &ev); 5875 } else { 5876 5877 } 5878 } 5879 5880 version(with_eventloop) {} else 5881 /// 5882 void disable() { 5883 prepareEventLoop(); 5884 5885 enabled = false; 5886 5887 version(linux) { 5888 static import ep = core.sys.linux.epoll; 5889 ep.epoll_event ev = void; 5890 ev.events = (captureReads ? ep.EPOLLIN : 0) | (captureWrites ? ep.EPOLLOUT : 0); 5891 // writeln("disable ", fd, " ", captureReads, " ", captureWrites); 5892 ev.data.fd = fd; 5893 ep.epoll_ctl(epollFd, ep.EPOLL_CTL_DEL, fd, &ev); 5894 } 5895 } 5896 5897 version(with_eventloop) {} else 5898 /// 5899 void dispose() { 5900 if(enabled) 5901 disable(); 5902 if(fd != -1) 5903 mapping.remove(fd); 5904 fd = -1; 5905 } 5906 5907 void delegate(int, bool, bool) onReady; 5908 5909 version(with_eventloop) 5910 void readyel() { 5911 onReady(fd, true, true); 5912 } 5913 5914 void ready(uint flags) { 5915 version(linux) { 5916 static import ep = core.sys.linux.epoll; 5917 onReady(fd, (flags & ep.EPOLLIN) ? true : false, (flags & ep.EPOLLOUT) ? true : false); 5918 } else { 5919 import core.sys.posix.poll; 5920 onReady(fd, (flags & POLLIN) ? true : false, (flags & POLLOUT) ? true : false); 5921 } 5922 } 5923 5924 void hup(uint flags) { 5925 if(onHup) 5926 onHup(); 5927 } 5928 5929 void delegate() onHup; 5930 5931 int fd = -1; 5932 private bool enabled; 5933 __gshared PosixFdReader[int] mapping; 5934 } 5935 5936 // basic functions to access the clipboard 5937 /+ 5938 5939 5940 http://msdn.microsoft.com/en-us/library/windows/desktop/ff729168%28v=vs.85%29.aspx 5941 http://msdn.microsoft.com/en-us/library/windows/desktop/ms649039%28v=vs.85%29.aspx 5942 http://msdn.microsoft.com/en-us/library/windows/desktop/ms649035%28v=vs.85%29.aspx 5943 http://msdn.microsoft.com/en-us/library/windows/desktop/ms649051%28v=vs.85%29.aspx 5944 http://msdn.microsoft.com/en-us/library/windows/desktop/ms649037%28v=vs.85%29.aspx 5945 http://msdn.microsoft.com/en-us/library/windows/desktop/ms649035%28v=vs.85%29.aspx 5946 http://msdn.microsoft.com/en-us/library/windows/desktop/ms649016%28v=vs.85%29.aspx 5947 5948 +/ 5949 5950 /++ 5951 this does a delegate because it is actually an async call on X... 5952 the receiver may never be called if the clipboard is empty or unavailable 5953 gets plain text from the clipboard. 5954 +/ 5955 void getClipboardText(SimpleWindow clipboardOwner, void delegate(in char[]) receiver) { 5956 version(Windows) { 5957 HWND hwndOwner = clipboardOwner ? clipboardOwner.impl.hwnd : null; 5958 if(OpenClipboard(hwndOwner) == 0) 5959 throw new WindowsApiException("OpenClipboard", GetLastError()); 5960 scope(exit) 5961 CloseClipboard(); 5962 // see: https://docs.microsoft.com/en-us/windows/win32/api/winuser/nf-winuser-getpriorityclipboardformat 5963 if(auto dataHandle = GetClipboardData(CF_UNICODETEXT)) { 5964 5965 if(auto data = cast(wchar*) GlobalLock(dataHandle)) { 5966 scope(exit) 5967 GlobalUnlock(dataHandle); 5968 5969 // FIXME: CR/LF conversions 5970 // FIXME: I might not have to copy it now that the receiver is in char[] instead of string 5971 int len = 0; 5972 auto d = data; 5973 while(*d) { 5974 d++; 5975 len++; 5976 } 5977 string s; 5978 s.reserve(len); 5979 foreach(dchar ch; data[0 .. len]) { 5980 s ~= ch; 5981 } 5982 receiver(s); 5983 } 5984 } 5985 } else version(X11) { 5986 getX11Selection!"CLIPBOARD"(clipboardOwner, receiver); 5987 } else version(OSXCocoa) { 5988 throw new NotYetImplementedException(); 5989 } else static assert(0); 5990 } 5991 5992 // FIXME: a clipboard listener might be cool btw 5993 5994 /++ 5995 this does a delegate because it is actually an async call on X... 5996 the receiver may never be called if the clipboard is empty or unavailable 5997 gets image from the clipboard. 5998 5999 templated because it introduces an optional dependency on arsd.bmp 6000 +/ 6001 void getClipboardImage()(SimpleWindow clipboardOwner, void delegate(MemoryImage) receiver) { 6002 version(Windows) { 6003 HWND hwndOwner = clipboardOwner ? clipboardOwner.impl.hwnd : null; 6004 if(OpenClipboard(hwndOwner) == 0) 6005 throw new WindowsApiException("OpenClipboard", GetLastError()); 6006 scope(exit) 6007 CloseClipboard(); 6008 if(auto dataHandle = GetClipboardData(CF_DIBV5)) { 6009 if(auto data = cast(ubyte*) GlobalLock(dataHandle)) { 6010 scope(exit) 6011 GlobalUnlock(dataHandle); 6012 6013 auto len = GlobalSize(dataHandle); 6014 6015 import arsd.bmp; 6016 auto img = readBmp(data[0 .. len], false); 6017 receiver(img); 6018 } 6019 } 6020 } else version(X11) { 6021 getX11Selection!"CLIPBOARD"(clipboardOwner, receiver); 6022 } else version(OSXCocoa) { 6023 throw new NotYetImplementedException(); 6024 } else static assert(0); 6025 } 6026 6027 /// Copies some text to the clipboard. 6028 void setClipboardText(SimpleWindow clipboardOwner, string text) { 6029 assert(clipboardOwner !is null); 6030 version(Windows) { 6031 if(OpenClipboard(clipboardOwner.impl.hwnd) == 0) 6032 throw new WindowsApiException("OpenClipboard", GetLastError()); 6033 scope(exit) 6034 CloseClipboard(); 6035 EmptyClipboard(); 6036 auto sz = sizeOfConvertedWstring(text, WindowsStringConversionFlags.convertNewLines | WindowsStringConversionFlags.zeroTerminate); 6037 auto handle = GlobalAlloc(GMEM_MOVEABLE, sz * 2); // zero terminated wchars 6038 if(handle is null) throw new WindowsApiException("GlobalAlloc", GetLastError()); 6039 if(auto data = cast(wchar*) GlobalLock(handle)) { 6040 auto slice = data[0 .. sz]; 6041 scope(failure) 6042 GlobalUnlock(handle); 6043 6044 auto str = makeWindowsString(text, slice, WindowsStringConversionFlags.convertNewLines | WindowsStringConversionFlags.zeroTerminate); 6045 6046 GlobalUnlock(handle); 6047 SetClipboardData(CF_UNICODETEXT, handle); 6048 } 6049 } else version(X11) { 6050 setX11Selection!"CLIPBOARD"(clipboardOwner, text); 6051 } else version(OSXCocoa) { 6052 throw new NotYetImplementedException(); 6053 } else static assert(0); 6054 } 6055 6056 void setClipboardImage()(SimpleWindow clipboardOwner, MemoryImage img) { 6057 assert(clipboardOwner !is null); 6058 version(Windows) { 6059 if(OpenClipboard(clipboardOwner.impl.hwnd) == 0) 6060 throw new WindowsApiException("OpenClipboard", GetLastError()); 6061 scope(exit) 6062 CloseClipboard(); 6063 EmptyClipboard(); 6064 6065 6066 import arsd.bmp; 6067 ubyte[] mdata; 6068 mdata.reserve(img.width * img.height); 6069 void sink(ubyte b) { 6070 mdata ~= b; 6071 } 6072 writeBmpIndirect(img, &sink, false); 6073 6074 auto handle = GlobalAlloc(GMEM_MOVEABLE, mdata.length); 6075 if(handle is null) throw new WindowsApiException("GlobalAlloc", GetLastError()); 6076 if(auto data = cast(ubyte*) GlobalLock(handle)) { 6077 auto slice = data[0 .. mdata.length]; 6078 scope(failure) 6079 GlobalUnlock(handle); 6080 6081 slice[] = mdata[]; 6082 6083 GlobalUnlock(handle); 6084 SetClipboardData(CF_DIB, handle); 6085 } 6086 } else version(X11) { 6087 static class X11SetSelectionHandler_Image : X11SetSelectionHandler { 6088 mixin X11SetSelectionHandler_Basics; 6089 private const(ubyte)[] mdata; 6090 private const(ubyte)[] mdata_original; 6091 this(MemoryImage img) { 6092 import arsd.bmp; 6093 6094 mdata.reserve(img.width * img.height); 6095 void sink(ubyte b) { 6096 mdata ~= b; 6097 } 6098 writeBmpIndirect(img, &sink, true); 6099 6100 mdata_original = mdata; 6101 } 6102 6103 Atom[] availableFormats() { 6104 auto display = XDisplayConnection.get; 6105 return [ 6106 GetAtom!"image/bmp"(display), 6107 GetAtom!"TARGETS"(display) 6108 ]; 6109 } 6110 6111 ubyte[] getData(Atom format, return scope ubyte[] data) { 6112 if(mdata.length < data.length) { 6113 data[0 .. mdata.length] = mdata[]; 6114 auto ret = data[0 .. mdata.length]; 6115 mdata = mdata[$..$]; 6116 return ret; 6117 } else { 6118 data[] = mdata[0 .. data.length]; 6119 mdata = mdata[data.length .. $]; 6120 return data[]; 6121 } 6122 } 6123 6124 void done() { 6125 mdata = mdata_original; 6126 } 6127 } 6128 6129 setX11Selection!"CLIPBOARD"(clipboardOwner, new X11SetSelectionHandler_Image(img)); 6130 } else version(OSXCocoa) { 6131 throw new NotYetImplementedException(); 6132 } else static assert(0); 6133 } 6134 6135 6136 version(X11) { 6137 // and the PRIMARY on X, be sure to put these in static if(UsingSimpledisplayX11) 6138 6139 private __gshared Atom*[] interredAtoms; // for discardAndRecreate 6140 6141 // FIXME: do a GetAtomUpfront too that just queues all at CT and combines it all. 6142 /// Platform-specific for X11. 6143 /// History: On February 21, 2021, I changed the default value of `create` to be true. 6144 @property Atom GetAtom(string name, bool create = true)(Display* display) { 6145 __gshared static Atom a; 6146 if(!a) { 6147 a = XInternAtom(display, name, !create); 6148 // FIXME: might need to synchronize this and attach it to the actual object 6149 interredAtoms ~= &a; 6150 } 6151 if(a == None) 6152 throw new Exception("XInternAtom " ~ name ~ " " ~ (create ? "true":"false")); 6153 return a; 6154 } 6155 6156 /// Platform-specific for X11 - gets atom names as a string. 6157 string getAtomName(Atom atom, Display* display) { 6158 auto got = XGetAtomName(display, atom); 6159 scope(exit) XFree(got); 6160 import core.stdc.string; 6161 string s = got[0 .. strlen(got)].idup; 6162 return s; 6163 } 6164 6165 /// Asserts ownership of PRIMARY and copies the text into a buffer that clients can request later. 6166 void setPrimarySelection(SimpleWindow window, string text) { 6167 setX11Selection!"PRIMARY"(window, text); 6168 } 6169 6170 /// Asserts ownership of SECONDARY and copies the text into a buffer that clients can request later. 6171 void setSecondarySelection(SimpleWindow window, string text) { 6172 setX11Selection!"SECONDARY"(window, text); 6173 } 6174 6175 interface X11SetSelectionHandler { 6176 // should include TARGETS right now 6177 Atom[] availableFormats(); 6178 // Return the slice of data you filled, empty slice if done. 6179 // this is to support the incremental thing 6180 ubyte[] getData(Atom format, return scope ubyte[] data); 6181 6182 void done(); 6183 6184 void handleRequest(XEvent); 6185 6186 bool matchesIncr(Window, Atom); 6187 void sendMoreIncr(XPropertyEvent*); 6188 } 6189 6190 mixin template X11SetSelectionHandler_Basics() { 6191 Window incrWindow; 6192 Atom incrAtom; 6193 Atom selectionAtom; 6194 Atom formatAtom; 6195 ubyte[] toSend; 6196 bool matchesIncr(Window w, Atom a) { 6197 return incrAtom && incrAtom == a && w == incrWindow; 6198 } 6199 void sendMoreIncr(XPropertyEvent* event) { 6200 auto display = XDisplayConnection.get; 6201 6202 XChangeProperty (display, 6203 incrWindow, 6204 incrAtom, 6205 formatAtom, 6206 8 /* bits */, PropModeReplace, 6207 toSend.ptr, cast(int) toSend.length); 6208 6209 if(toSend.length != 0) { 6210 toSend = this.getData(formatAtom, toSend[]); 6211 } else { 6212 this.done(); 6213 incrWindow = None; 6214 incrAtom = None; 6215 selectionAtom = None; 6216 formatAtom = None; 6217 toSend = null; 6218 } 6219 } 6220 void handleRequest(XEvent ev) { 6221 6222 auto display = XDisplayConnection.get; 6223 6224 XSelectionRequestEvent* event = &ev.xselectionrequest; 6225 XSelectionEvent selectionEvent; 6226 selectionEvent.type = EventType.SelectionNotify; 6227 selectionEvent.display = event.display; 6228 selectionEvent.requestor = event.requestor; 6229 selectionEvent.selection = event.selection; 6230 selectionEvent.time = event.time; 6231 selectionEvent.target = event.target; 6232 6233 bool supportedType() { 6234 foreach(t; this.availableFormats()) 6235 if(t == event.target) 6236 return true; 6237 return false; 6238 } 6239 6240 if(event.property == None) { 6241 selectionEvent.property = event.target; 6242 6243 XSendEvent(display, selectionEvent.requestor, false, 0, cast(XEvent*) &selectionEvent); 6244 XFlush(display); 6245 } if(event.target == GetAtom!"TARGETS"(display)) { 6246 /* respond with the supported types */ 6247 auto tlist = this.availableFormats(); 6248 XChangeProperty(display, event.requestor, event.property, XA_ATOM, 32, PropModeReplace, cast(void*)tlist.ptr, cast(int) tlist.length); 6249 selectionEvent.property = event.property; 6250 6251 XSendEvent(display, selectionEvent.requestor, false, 0, cast(XEvent*) &selectionEvent); 6252 XFlush(display); 6253 } else if(supportedType()) { 6254 auto buffer = new ubyte[](1024 * 64); 6255 auto toSend = this.getData(event.target, buffer[]); 6256 6257 if(toSend.length < 32 * 1024) { 6258 // small enough to send directly... 6259 selectionEvent.property = event.property; 6260 XChangeProperty (display, 6261 selectionEvent.requestor, 6262 selectionEvent.property, 6263 event.target, 6264 8 /* bits */, 0 /* PropModeReplace */, 6265 toSend.ptr, cast(int) toSend.length); 6266 6267 XSendEvent(display, selectionEvent.requestor, false, 0, cast(XEvent*) &selectionEvent); 6268 XFlush(display); 6269 } else { 6270 // large, let's send incrementally 6271 arch_ulong l = toSend.length; 6272 6273 // if I wanted other events from this window don't want to clear that out.... 6274 XWindowAttributes xwa; 6275 XGetWindowAttributes(display, selectionEvent.requestor, &xwa); 6276 6277 XSelectInput(display, selectionEvent.requestor, cast(EventMask) (xwa.your_event_mask | EventMask.PropertyChangeMask)); 6278 6279 incrWindow = event.requestor; 6280 incrAtom = event.property; 6281 formatAtom = event.target; 6282 selectionAtom = event.selection; 6283 this.toSend = toSend; 6284 6285 selectionEvent.property = event.property; 6286 XChangeProperty (display, 6287 selectionEvent.requestor, 6288 selectionEvent.property, 6289 GetAtom!"INCR"(display), 6290 32 /* bits */, PropModeReplace, 6291 &l, 1); 6292 6293 XSendEvent(display, selectionEvent.requestor, false, 0, cast(XEvent*) &selectionEvent); 6294 XFlush(display); 6295 } 6296 //if(after) 6297 //after(); 6298 } else { 6299 debug(sdpy_clip) { 6300 writeln("Unsupported data ", getAtomName(event.target, display)); 6301 } 6302 selectionEvent.property = None; // I don't know how to handle this type... 6303 XSendEvent(display, selectionEvent.requestor, false, EventMask.NoEventMask, cast(XEvent*) &selectionEvent); 6304 XFlush(display); 6305 } 6306 } 6307 } 6308 6309 class X11SetSelectionHandler_Text : X11SetSelectionHandler { 6310 mixin X11SetSelectionHandler_Basics; 6311 private const(ubyte)[] text; 6312 private const(ubyte)[] text_original; 6313 this(string text) { 6314 this.text = cast(const ubyte[]) text; 6315 this.text_original = this.text; 6316 } 6317 Atom[] availableFormats() { 6318 auto display = XDisplayConnection.get; 6319 return [ 6320 GetAtom!"UTF8_STRING"(display), 6321 GetAtom!"text/plain"(display), 6322 XA_STRING, 6323 GetAtom!"TARGETS"(display) 6324 ]; 6325 } 6326 6327 ubyte[] getData(Atom format, return scope ubyte[] data) { 6328 if(text.length < data.length) { 6329 data[0 .. text.length] = text[]; 6330 return data[0 .. text.length]; 6331 } else { 6332 data[] = text[0 .. data.length]; 6333 text = text[data.length .. $]; 6334 return data[]; 6335 } 6336 } 6337 6338 void done() { 6339 text = text_original; 6340 } 6341 } 6342 6343 /// 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?!) 6344 void setX11Selection(string atomName)(SimpleWindow window, string text, void delegate() after = null) { 6345 setX11Selection!atomName(window, new X11SetSelectionHandler_Text(text), after); 6346 } 6347 6348 void setX11Selection(string atomName)(SimpleWindow window, X11SetSelectionHandler data, void delegate() after = null) { 6349 assert(window !is null); 6350 6351 auto display = XDisplayConnection.get(); 6352 static if (atomName == "PRIMARY") Atom a = XA_PRIMARY; 6353 else static if (atomName == "SECONDARY") Atom a = XA_SECONDARY; 6354 else Atom a = GetAtom!atomName(display); 6355 6356 XSetSelectionOwner(display, a, window.impl.window, 0 /* CurrentTime */); 6357 6358 window.impl.setSelectionHandlers[a] = data; 6359 } 6360 6361 /// 6362 void getPrimarySelection(SimpleWindow window, void delegate(in char[]) handler) { 6363 getX11Selection!"PRIMARY"(window, handler); 6364 } 6365 6366 // added July 28, 2020 6367 // undocumented as experimental tho 6368 interface X11GetSelectionHandler { 6369 void handleData(Atom target, in ubyte[] data); 6370 Atom findBestFormat(Atom[] answer); 6371 6372 void prepareIncremental(Window, Atom); 6373 bool matchesIncr(Window, Atom); 6374 void handleIncrData(Atom, in ubyte[] data); 6375 } 6376 6377 mixin template X11GetSelectionHandler_Basics() { 6378 Window incrWindow; 6379 Atom incrAtom; 6380 6381 void prepareIncremental(Window w, Atom a) { 6382 incrWindow = w; 6383 incrAtom = a; 6384 } 6385 bool matchesIncr(Window w, Atom a) { 6386 return incrWindow == w && incrAtom == a; 6387 } 6388 6389 Atom incrFormatAtom; 6390 ubyte[] incrData; 6391 void handleIncrData(Atom format, in ubyte[] data) { 6392 incrFormatAtom = format; 6393 6394 if(data.length) 6395 incrData ~= data; 6396 else 6397 handleData(incrFormatAtom, incrData); 6398 6399 } 6400 } 6401 6402 /// 6403 void getX11Selection(string atomName)(SimpleWindow window, void delegate(in char[]) handler, Time timestamp = 0 /* CurrentTime */) { 6404 assert(window !is null); 6405 6406 auto display = XDisplayConnection.get(); 6407 auto atom = GetAtom!atomName(display); 6408 6409 static class X11GetSelectionHandler_Text : X11GetSelectionHandler { 6410 this(void delegate(in char[]) handler) { 6411 this.handler = handler; 6412 } 6413 6414 mixin X11GetSelectionHandler_Basics; 6415 6416 void delegate(in char[]) handler; 6417 6418 void handleData(Atom target, in ubyte[] data) { 6419 if(target == GetAtom!"UTF8_STRING"(XDisplayConnection.get) || target == XA_STRING || target == GetAtom!"text/plain"(XDisplayConnection.get)) 6420 handler(cast(const char[]) data); 6421 } 6422 6423 Atom findBestFormat(Atom[] answer) { 6424 Atom best = None; 6425 foreach(option; answer) { 6426 if(option == GetAtom!"UTF8_STRING"(XDisplayConnection.get)) { 6427 best = option; 6428 break; 6429 } else if(option == XA_STRING) { 6430 best = option; 6431 } else if(option == GetAtom!"text/plain"(XDisplayConnection.get)) { 6432 best = option; 6433 } 6434 } 6435 return best; 6436 } 6437 } 6438 6439 window.impl.getSelectionHandlers[atom] = new X11GetSelectionHandler_Text(handler); 6440 6441 auto target = GetAtom!"TARGETS"(display); 6442 6443 // SDD_DATA is "simpledisplay.d data" 6444 XConvertSelection(display, atom, target, GetAtom!("SDD_DATA", true)(display), window.impl.window, timestamp); 6445 } 6446 6447 /// Gets the image on the clipboard, if there is one. Added July 2020. 6448 void getX11Selection(string atomName)(SimpleWindow window, void delegate(MemoryImage) handler) { 6449 assert(window !is null); 6450 6451 auto display = XDisplayConnection.get(); 6452 auto atom = GetAtom!atomName(display); 6453 6454 static class X11GetSelectionHandler_Image : X11GetSelectionHandler { 6455 this(void delegate(MemoryImage) handler) { 6456 this.handler = handler; 6457 } 6458 6459 mixin X11GetSelectionHandler_Basics; 6460 6461 void delegate(MemoryImage) handler; 6462 6463 void handleData(Atom target, in ubyte[] data) { 6464 if(target == GetAtom!"image/bmp"(XDisplayConnection.get)) { 6465 import arsd.bmp; 6466 handler(readBmp(data)); 6467 } 6468 } 6469 6470 Atom findBestFormat(Atom[] answer) { 6471 Atom best = None; 6472 foreach(option; answer) { 6473 if(option == GetAtom!"image/bmp"(XDisplayConnection.get)) { 6474 best = option; 6475 } 6476 } 6477 return best; 6478 } 6479 6480 } 6481 6482 6483 window.impl.getSelectionHandlers[atom] = new X11GetSelectionHandler_Image(handler); 6484 6485 auto target = GetAtom!"TARGETS"(display); 6486 6487 // SDD_DATA is "simpledisplay.d data" 6488 XConvertSelection(display, atom, target, GetAtom!("SDD_DATA", true)(display), window.impl.window, 0 /*CurrentTime*/); 6489 } 6490 6491 6492 /// 6493 void[] getX11PropertyData(Window window, Atom property, Atom type = AnyPropertyType) { 6494 Atom actualType; 6495 int actualFormat; 6496 arch_ulong actualItems; 6497 arch_ulong bytesRemaining; 6498 void* data; 6499 6500 auto display = XDisplayConnection.get(); 6501 if(XGetWindowProperty(display, window, property, 0, 0x7fffffff, false, type, &actualType, &actualFormat, &actualItems, &bytesRemaining, &data) == Success) { 6502 if(actualFormat == 0) 6503 return null; 6504 else { 6505 int byteLength; 6506 if(actualFormat == 32) { 6507 // 32 means it is a C long... which is variable length 6508 actualFormat = cast(int) arch_long.sizeof * 8; 6509 } 6510 6511 // then it is just a bit count 6512 byteLength = cast(int) (actualItems * actualFormat / 8); 6513 6514 auto d = new ubyte[](byteLength); 6515 d[] = cast(ubyte[]) data[0 .. byteLength]; 6516 XFree(data); 6517 return d; 6518 } 6519 } 6520 return null; 6521 } 6522 6523 /* defined in the systray spec */ 6524 enum SYSTEM_TRAY_REQUEST_DOCK = 0; 6525 enum SYSTEM_TRAY_BEGIN_MESSAGE = 1; 6526 enum SYSTEM_TRAY_CANCEL_MESSAGE = 2; 6527 6528 6529 /** Global hotkey handler. Simpledisplay will usually create one for you, but if you want to use subclassing 6530 * instead of delegates, you can subclass this, and override `doHandle()` method. */ 6531 public class GlobalHotkey { 6532 KeyEvent key; 6533 void delegate () handler; 6534 6535 void doHandle () { if (handler !is null) handler(); } /// this will be called by hotkey manager 6536 6537 /// Create from initialzed KeyEvent object 6538 this (KeyEvent akey, void delegate () ahandler=null) { 6539 if (akey.key == 0 || !GlobalHotkeyManager.isGoodModifierMask(akey.modifierState)) throw new Exception("invalid global hotkey"); 6540 key = akey; 6541 handler = ahandler; 6542 } 6543 6544 /// Create from emacs-like key name ("C-M-Y", etc.) 6545 this (const(char)[] akey, void delegate () ahandler=null) { 6546 key = KeyEvent.parse(akey); 6547 if (key.key == 0 || !GlobalHotkeyManager.isGoodModifierMask(key.modifierState)) throw new Exception("invalid global hotkey"); 6548 handler = ahandler; 6549 } 6550 6551 } 6552 6553 private extern(C) int XGrabErrorHandler (Display* dpy, XErrorEvent* evt) nothrow @nogc { 6554 //conwriteln("failed to grab key"); 6555 GlobalHotkeyManager.ghfailed = true; 6556 return 0; 6557 } 6558 6559 private extern(C) int XShmErrorHandler (Display* dpy, XErrorEvent* evt) nothrow @nogc { 6560 Image.impl.xshmfailed = true; 6561 return 0; 6562 } 6563 6564 private __gshared int errorHappened; 6565 private extern(C) int adrlogger (Display* dpy, XErrorEvent* evt) nothrow @nogc { 6566 import core.stdc.stdio; 6567 char[265] buffer; 6568 XGetErrorText(dpy, evt.error_code, buffer.ptr, cast(int) buffer.length); 6569 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); 6570 errorHappened = true; 6571 return 0; 6572 } 6573 6574 /++ 6575 Global hotkey manager. It contains static methods to manage global hotkeys. 6576 6577 --- 6578 try { 6579 GlobalHotkeyManager.register("M-H-A", delegate () { hideShowWindows(); }); 6580 } catch (Exception e) { 6581 conwriteln("ERROR registering hotkey!"); 6582 } 6583 EventLoop.get.run(); 6584 --- 6585 6586 The key strings are based on Emacs. In practical terms, 6587 `M` means `alt` and `H` means the Windows logo key. `C` 6588 is `ctrl`. 6589 6590 $(WARNING 6591 This is X-specific right now. If you are on 6592 Windows, try [registerHotKey] instead. 6593 6594 We will probably merge these into a single 6595 interface later. 6596 ) 6597 +/ 6598 public class GlobalHotkeyManager : CapableOfHandlingNativeEvent { 6599 version(X11) { 6600 void recreateAfterDisconnect() { 6601 throw new Exception("NOT IMPLEMENTED"); 6602 } 6603 void discardConnectionState() { 6604 throw new Exception("NOT IMPLEMENTED"); 6605 } 6606 } 6607 6608 private static immutable uint[8] masklist = [ 0, 6609 KeyOrButtonMask.LockMask, 6610 KeyOrButtonMask.Mod2Mask, 6611 KeyOrButtonMask.Mod3Mask, 6612 KeyOrButtonMask.LockMask|KeyOrButtonMask.Mod2Mask, 6613 KeyOrButtonMask.LockMask|KeyOrButtonMask.Mod3Mask, 6614 KeyOrButtonMask.LockMask|KeyOrButtonMask.Mod2Mask|KeyOrButtonMask.Mod3Mask, 6615 KeyOrButtonMask.Mod2Mask|KeyOrButtonMask.Mod3Mask, 6616 ]; 6617 private __gshared GlobalHotkeyManager ghmanager; 6618 private __gshared bool ghfailed = false; 6619 6620 private static bool isGoodModifierMask (uint modmask) pure nothrow @safe @nogc { 6621 if (modmask == 0) return false; 6622 if (modmask&(KeyOrButtonMask.LockMask|KeyOrButtonMask.Mod2Mask|KeyOrButtonMask.Mod3Mask)) return false; 6623 if (modmask&~(KeyOrButtonMask.Mod5Mask-1)) return false; 6624 return true; 6625 } 6626 6627 private static uint cleanupModifiers (uint modmask) pure nothrow @safe @nogc { 6628 modmask &= ~(KeyOrButtonMask.LockMask|KeyOrButtonMask.Mod2Mask|KeyOrButtonMask.Mod3Mask); // remove caps, num, scroll 6629 modmask &= (KeyOrButtonMask.Mod5Mask-1); // and other modifiers 6630 return modmask; 6631 } 6632 6633 private static uint keyEvent2KeyCode() (scope auto ref const KeyEvent ke) { 6634 uint keycode = cast(uint)ke.key; 6635 auto dpy = XDisplayConnection.get; 6636 return XKeysymToKeycode(dpy, keycode); 6637 } 6638 6639 private static ulong keyCode2Hash() (uint keycode, uint modstate) pure nothrow @safe @nogc { return ((cast(ulong)modstate)<<32)|keycode; } 6640 6641 private __gshared GlobalHotkey[ulong] globalHotkeyList; 6642 6643 NativeEventHandler getNativeEventHandler () { 6644 return delegate int (XEvent e) { 6645 if (e.type != EventType.KeyPress) return 1; 6646 auto kev = cast(const(XKeyEvent)*)&e; 6647 auto hash = keyCode2Hash(e.xkey.keycode, cleanupModifiers(e.xkey.state)); 6648 if (auto ghkp = hash in globalHotkeyList) { 6649 try { 6650 ghkp.doHandle(); 6651 } catch (Exception e) { 6652 import core.stdc.stdio : stderr, fprintf; 6653 stderr.fprintf("HOTKEY HANDLER EXCEPTION: %.*s", cast(uint)e.msg.length, e.msg.ptr); 6654 } 6655 } 6656 return 1; 6657 }; 6658 } 6659 6660 private this () { 6661 auto dpy = XDisplayConnection.get; 6662 auto root = RootWindow(dpy, DefaultScreen(dpy)); 6663 CapableOfHandlingNativeEvent.nativeHandleMapping[root] = this; 6664 XDisplayConnection.addRootInput(EventMask.KeyPressMask); 6665 } 6666 6667 /// Register new global hotkey with initialized `GlobalHotkey` object. 6668 /// This function will throw if it failed to register hotkey (i.e. hotkey is invalid or already taken). 6669 static void register (GlobalHotkey gh) { 6670 if (gh is null) return; 6671 if (gh.key.key == 0 || !isGoodModifierMask(gh.key.modifierState)) throw new Exception("invalid global hotkey"); 6672 6673 auto dpy = XDisplayConnection.get; 6674 immutable keycode = keyEvent2KeyCode(gh.key); 6675 6676 auto hash = keyCode2Hash(keycode, gh.key.modifierState); 6677 if (hash in globalHotkeyList) throw new Exception("duplicate global hotkey"); 6678 if (ghmanager is null) ghmanager = new GlobalHotkeyManager(); 6679 XSync(dpy, 0/*False*/); 6680 6681 Window root = RootWindow(dpy, DefaultScreen(dpy)); 6682 XErrorHandler savedErrorHandler = XSetErrorHandler(&XGrabErrorHandler); 6683 ghfailed = false; 6684 foreach (immutable uint ormask; masklist[]) { 6685 XGrabKey(dpy, keycode, gh.key.modifierState|ormask, /*grab_window*/root, /*owner_events*/0/*False*/, GrabMode.GrabModeAsync, GrabMode.GrabModeAsync); 6686 } 6687 XSync(dpy, 0/*False*/); 6688 XSetErrorHandler(savedErrorHandler); 6689 6690 if (ghfailed) { 6691 savedErrorHandler = XSetErrorHandler(&XGrabErrorHandler); 6692 foreach (immutable uint ormask; masklist[]) XUngrabKey(dpy, keycode, gh.key.modifierState|ormask, /*grab_window*/root); 6693 XSync(dpy, 0/*False*/); 6694 XSetErrorHandler(savedErrorHandler); 6695 throw new Exception("cannot register global hotkey"); 6696 } 6697 6698 globalHotkeyList[hash] = gh; 6699 } 6700 6701 /// Ditto 6702 static void register (const(char)[] akey, void delegate () ahandler) { 6703 register(new GlobalHotkey(akey, ahandler)); 6704 } 6705 6706 private static void removeByHash (ulong hash) { 6707 if (auto ghp = hash in globalHotkeyList) { 6708 auto dpy = XDisplayConnection.get; 6709 immutable keycode = keyEvent2KeyCode(ghp.key); 6710 Window root = RootWindow(dpy, DefaultScreen(dpy)); 6711 XSync(dpy, 0/*False*/); 6712 XErrorHandler savedErrorHandler = XSetErrorHandler(&XGrabErrorHandler); 6713 foreach (immutable uint ormask; masklist[]) XUngrabKey(dpy, keycode, ghp.key.modifierState|ormask, /*grab_window*/root); 6714 XSync(dpy, 0/*False*/); 6715 XSetErrorHandler(savedErrorHandler); 6716 globalHotkeyList.remove(hash); 6717 } 6718 } 6719 6720 /// Register new global hotkey with previously used `GlobalHotkey` object. 6721 /// It is safe to unregister unknown or invalid hotkey. 6722 static void unregister (GlobalHotkey gh) { 6723 //TODO: add second AA for faster search? prolly doesn't worth it. 6724 if (gh is null) return; 6725 foreach (const ref kv; globalHotkeyList.byKeyValue) { 6726 if (kv.value is gh) { 6727 removeByHash(kv.key); 6728 return; 6729 } 6730 } 6731 } 6732 6733 /// Ditto. 6734 static void unregister (const(char)[] key) { 6735 auto kev = KeyEvent.parse(key); 6736 immutable keycode = keyEvent2KeyCode(kev); 6737 removeByHash(keyCode2Hash(keycode, kev.modifierState)); 6738 } 6739 } 6740 } 6741 6742 version(Windows) { 6743 /++ 6744 See [SyntheticInput.sendSyntheticInput] instead for cross-platform applications. 6745 6746 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). 6747 +/ 6748 void sendSyntheticInput(wstring s) { 6749 INPUT[] inputs; 6750 inputs.reserve(s.length * 2); 6751 6752 foreach(wchar c; s) { 6753 INPUT input; 6754 input.type = INPUT_KEYBOARD; 6755 input.ki.wScan = c; 6756 input.ki.dwFlags = KEYEVENTF_UNICODE; 6757 inputs ~= input; 6758 6759 input.ki.dwFlags |= KEYEVENTF_KEYUP; 6760 inputs ~= input; 6761 } 6762 6763 if(SendInput(cast(int) inputs.length, inputs.ptr, INPUT.sizeof) != inputs.length) { 6764 throw new WindowsApiException("SendInput", GetLastError()); 6765 } 6766 6767 } 6768 6769 6770 // global hotkey helper function 6771 6772 /// Platform-specific for Windows. Registers a global hotkey. Returns a registration ID. See [GlobalHotkeyManager] for Linux. Maybe some day I will merge these. 6773 int registerHotKey(SimpleWindow window, UINT modifiers, UINT vk, void delegate() handler) { 6774 __gshared int hotkeyId = 0; 6775 int id = ++hotkeyId; 6776 if(!RegisterHotKey(window.impl.hwnd, id, modifiers, vk)) 6777 throw new Exception("RegisterHotKey"); 6778 6779 __gshared void delegate()[WPARAM][HWND] handlers; 6780 6781 handlers[window.impl.hwnd][id] = handler; 6782 6783 int delegate(HWND, UINT, WPARAM, LPARAM, out int) oldHandler; 6784 6785 auto nativeEventHandler = delegate int(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam, out int mustReturn) { 6786 switch(msg) { 6787 // http://msdn.microsoft.com/en-us/library/windows/desktop/ms646279%28v=vs.85%29.aspx 6788 case WM_HOTKEY: 6789 if(auto list = hwnd in handlers) { 6790 if(auto h = wParam in *list) { 6791 (*h)(); 6792 return 0; 6793 } 6794 } 6795 goto default; 6796 default: 6797 } 6798 if(oldHandler) 6799 return oldHandler(hwnd, msg, wParam, lParam, mustReturn); 6800 return 1; // pass it on 6801 }; 6802 6803 if(window.handleNativeEvent.funcptr !is nativeEventHandler.funcptr) { 6804 oldHandler = window.handleNativeEvent; 6805 window.handleNativeEvent = nativeEventHandler; 6806 } 6807 6808 return id; 6809 } 6810 6811 /// Platform-specific for Windows. Unregisters a key. The id is the value returned by [registerHotKey]. 6812 void unregisterHotKey(SimpleWindow window, int id) { 6813 if(!UnregisterHotKey(window.impl.hwnd, id)) 6814 throw new WindowsApiException("UnregisterHotKey", GetLastError()); 6815 } 6816 } 6817 6818 version (X11) { 6819 pragma(lib, "dl"); 6820 import core.sys.posix.dlfcn; 6821 } 6822 6823 /++ 6824 Allows for sending synthetic input to the X server via the Xtst 6825 extension or on Windows using SendInput. 6826 6827 Please remember user input is meant to be user - don't use this 6828 if you have some other alternative! 6829 6830 History: 6831 Added May 17, 2020 with the X implementation. 6832 6833 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.) 6834 Bugs: 6835 All methods on OSX Cocoa will throw not yet implemented exceptions. 6836 +/ 6837 struct SyntheticInput { 6838 @disable this(); 6839 6840 private int* refcount; 6841 6842 version(X11) { 6843 private void* lib; 6844 6845 private extern(C) { 6846 void function(Display*, uint keycode, bool press, arch_ulong delay) XTestFakeKeyEvent; 6847 void function(Display*, uint button, bool press, arch_ulong delay) XTestFakeButtonEvent; 6848 } 6849 } 6850 6851 /// The dummy param must be 0. 6852 this(int dummy) { 6853 version(X11) { 6854 lib = dlopen("libXtst.so", RTLD_NOW); 6855 if(lib is null) 6856 throw new Exception("cannot load xtest lib extension"); 6857 scope(failure) 6858 dlclose(lib); 6859 6860 XTestFakeButtonEvent = cast(typeof(XTestFakeButtonEvent)) dlsym(lib, "XTestFakeButtonEvent"); 6861 XTestFakeKeyEvent = cast(typeof(XTestFakeKeyEvent)) dlsym(lib, "XTestFakeKeyEvent"); 6862 6863 if(XTestFakeKeyEvent is null) 6864 throw new Exception("No XTestFakeKeyEvent"); 6865 if(XTestFakeButtonEvent is null) 6866 throw new Exception("No XTestFakeButtonEvent"); 6867 } 6868 6869 refcount = new int; 6870 *refcount = 1; 6871 } 6872 6873 this(this) { 6874 if(refcount) 6875 *refcount += 1; 6876 } 6877 6878 ~this() { 6879 if(refcount) { 6880 *refcount -= 1; 6881 if(*refcount == 0) 6882 // I commented this because if I close the lib before 6883 // XCloseDisplay, it is liable to segfault... so just 6884 // gonna keep it loaded if it is loaded, no big deal 6885 // anyway. 6886 {} // dlclose(lib); 6887 } 6888 } 6889 6890 /++ 6891 Simulates typing a string into the keyboard. 6892 6893 Bugs: 6894 On X11, this ONLY works with basic ascii! On Windows, it can handle more. 6895 6896 Not implemented except on Windows and X11. 6897 +/ 6898 void sendSyntheticInput(string s) { 6899 version(Windows) { 6900 INPUT[] inputs; 6901 inputs.reserve(s.length * 2); 6902 6903 auto ei = GetMessageExtraInfo(); 6904 6905 foreach(wchar c; s) { 6906 INPUT input; 6907 input.type = INPUT_KEYBOARD; 6908 input.ki.wScan = c; 6909 input.ki.dwFlags = KEYEVENTF_UNICODE; 6910 input.ki.dwExtraInfo = ei; 6911 inputs ~= input; 6912 6913 input.ki.dwFlags |= KEYEVENTF_KEYUP; 6914 inputs ~= input; 6915 } 6916 6917 if(SendInput(cast(int) inputs.length, inputs.ptr, INPUT.sizeof) != inputs.length) { 6918 throw new WindowsApiException("SendInput", GetLastError()); 6919 } 6920 } else version(X11) { 6921 int delay = 0; 6922 foreach(ch; s) { 6923 pressKey(cast(Key) ch, true, delay); 6924 pressKey(cast(Key) ch, false, delay); 6925 delay += 5; 6926 } 6927 } else throw new NotYetImplementedException(); 6928 } 6929 6930 /++ 6931 Sends a fake press or release key event. 6932 6933 Please note you need to call [flushGui] or return to the event loop for this to actually be sent on X11. 6934 6935 Bugs: 6936 The `delay` parameter is not implemented yet on Windows. 6937 6938 Not implemented except on Windows and X11. 6939 +/ 6940 void pressKey(Key key, bool pressed, int delay = 0) { 6941 version(Windows) { 6942 INPUT input; 6943 input.type = INPUT_KEYBOARD; 6944 input.ki.wVk = cast(ushort) key; 6945 6946 input.ki.dwFlags = pressed ? 0 : KEYEVENTF_KEYUP; 6947 input.ki.dwExtraInfo = GetMessageExtraInfo(); 6948 6949 if(SendInput(1, &input, INPUT.sizeof) != 1) { 6950 throw new WindowsApiException("SendInput", GetLastError()); 6951 } 6952 } else version(X11) { 6953 XTestFakeKeyEvent(XDisplayConnection.get, XKeysymToKeycode(XDisplayConnection.get, key), pressed, delay + pressed ? 0 : 5); 6954 } else throw new NotYetImplementedException(); 6955 } 6956 6957 /++ 6958 Sends a fake mouse button press or release event. 6959 6960 Please note you need to call [flushGui] or return to the event loop for this to actually be sent on X11. 6961 6962 `pressed` param must be `true` if button is `wheelUp` or `wheelDown`. 6963 6964 Bugs: 6965 The `delay` parameter is not implemented yet on Windows. 6966 6967 The backButton and forwardButton will throw NotYetImplementedException on Windows. 6968 6969 All arguments will throw NotYetImplementedException on OSX Cocoa. 6970 +/ 6971 void pressMouseButton(MouseButton button, bool pressed, int delay = 0) { 6972 version(Windows) { 6973 INPUT input; 6974 input.type = INPUT_MOUSE; 6975 input.mi.dwExtraInfo = GetMessageExtraInfo(); 6976 6977 // input.mi.mouseData for a wheel event 6978 6979 switch(button) { 6980 case MouseButton.left: input.mi.dwFlags = pressed ? MOUSEEVENTF_LEFTDOWN : MOUSEEVENTF_LEFTUP; break; 6981 case MouseButton.middle: input.mi.dwFlags = pressed ? MOUSEEVENTF_MIDDLEDOWN : MOUSEEVENTF_MIDDLEUP; break; 6982 case MouseButton.right: input.mi.dwFlags = pressed ? MOUSEEVENTF_RIGHTDOWN : MOUSEEVENTF_RIGHTUP; break; 6983 case MouseButton.wheelUp: 6984 case MouseButton.wheelDown: 6985 input.mi.dwFlags = MOUSEEVENTF_WHEEL; 6986 input.mi.mouseData = button == MouseButton.wheelUp ? 120 : -120; 6987 break; 6988 case MouseButton.backButton: throw new NotYetImplementedException(); 6989 case MouseButton.forwardButton: throw new NotYetImplementedException(); 6990 default: 6991 } 6992 6993 if(SendInput(1, &input, INPUT.sizeof) != 1) { 6994 throw new WindowsApiException("SendInput", GetLastError()); 6995 } 6996 } else version(X11) { 6997 int btn; 6998 6999 switch(button) { 7000 case MouseButton.left: btn = 1; break; 7001 case MouseButton.middle: btn = 2; break; 7002 case MouseButton.right: btn = 3; break; 7003 case MouseButton.wheelUp: btn = 4; break; 7004 case MouseButton.wheelDown: btn = 5; break; 7005 case MouseButton.backButton: btn = 8; break; 7006 case MouseButton.forwardButton: btn = 9; break; 7007 default: 7008 } 7009 7010 assert(btn); 7011 7012 XTestFakeButtonEvent(XDisplayConnection.get, btn, pressed, delay); 7013 } else throw new NotYetImplementedException(); 7014 } 7015 7016 /// 7017 static void moveMouseArrowBy(int dx, int dy) { 7018 version(Windows) { 7019 INPUT input; 7020 input.type = INPUT_MOUSE; 7021 input.mi.dwExtraInfo = GetMessageExtraInfo(); 7022 input.mi.dx = dx; 7023 input.mi.dy = dy; 7024 input.mi.dwFlags = MOUSEEVENTF_MOVE; 7025 7026 if(SendInput(1, &input, INPUT.sizeof) != 1) { 7027 throw new WindowsApiException("SendInput", GetLastError()); 7028 } 7029 } else version(X11) { 7030 auto disp = XDisplayConnection.get(); 7031 XWarpPointer(disp, None, None, 0, 0, 0, 0, dx, dy); 7032 XFlush(disp); 7033 } else throw new NotYetImplementedException(); 7034 } 7035 7036 /// 7037 static void moveMouseArrowTo(int x, int y) { 7038 version(Windows) { 7039 INPUT input; 7040 input.type = INPUT_MOUSE; 7041 input.mi.dwExtraInfo = GetMessageExtraInfo(); 7042 input.mi.dx = x; 7043 input.mi.dy = y; 7044 input.mi.dwFlags = MOUSEEVENTF_MOVE | MOUSEEVENTF_ABSOLUTE; 7045 7046 if(SendInput(1, &input, INPUT.sizeof) != 1) { 7047 throw new WindowsApiException("SendInput", GetLastError()); 7048 } 7049 } else version(X11) { 7050 auto disp = XDisplayConnection.get(); 7051 auto root = RootWindow(disp, DefaultScreen(disp)); 7052 XWarpPointer(disp, None, root, 0, 0, 0, 0, x, y); 7053 XFlush(disp); 7054 } else throw new NotYetImplementedException(); 7055 } 7056 } 7057 7058 7059 7060 /++ 7061 [ScreenPainter] operations can use different operations to combine the color with the color on screen. 7062 7063 See_Also: 7064 $(LIST 7065 *[ScreenPainter] 7066 *[ScreenPainter.rasterOp] 7067 ) 7068 +/ 7069 enum RasterOp { 7070 normal, /// Replaces the pixel. 7071 xor, /// Uses bitwise xor to draw. 7072 } 7073 7074 // being phobos-free keeps the size WAY down 7075 private const(char)* toStringz(string s) { return (s ~ '\0').ptr; } 7076 package(arsd) const(wchar)* toWStringz(wstring s) { return (s ~ '\0').ptr; } 7077 package(arsd) const(wchar)* toWStringz(string s) { 7078 wstring r; 7079 foreach(dchar c; s) 7080 r ~= c; 7081 r ~= '\0'; 7082 return r.ptr; 7083 } 7084 private string[] split(in void[] a, char c) { 7085 string[] ret; 7086 size_t previous = 0; 7087 foreach(i, char ch; cast(ubyte[]) a) { 7088 if(ch == c) { 7089 ret ~= cast(string) a[previous .. i]; 7090 previous = i + 1; 7091 } 7092 } 7093 if(previous != a.length) 7094 ret ~= cast(string) a[previous .. $]; 7095 return ret; 7096 } 7097 7098 version(without_opengl) { 7099 enum OpenGlOptions { 7100 no, 7101 } 7102 } else { 7103 /++ 7104 Determines if you want an OpenGL context created on the new window. 7105 7106 7107 See more: [#topics-3d|in the 3d topic]. 7108 7109 --- 7110 import arsd.simpledisplay; 7111 void main() { 7112 auto window = new SimpleWindow(500, 500, "OpenGL Test", OpenGlOptions.yes); 7113 7114 // Set up the matrix 7115 window.setAsCurrentOpenGlContext(); // make this window active 7116 7117 // This is called on each frame, we will draw our scene 7118 window.redrawOpenGlScene = delegate() { 7119 7120 }; 7121 7122 window.eventLoop(0); 7123 } 7124 --- 7125 +/ 7126 enum OpenGlOptions { 7127 no, /// No OpenGL context is created 7128 yes, /// Yes, create an OpenGL context 7129 } 7130 7131 version(X11) { 7132 static if (!SdpyIsUsingIVGLBinds) { 7133 7134 7135 struct __GLXFBConfigRec {} 7136 alias GLXFBConfig = __GLXFBConfigRec*; 7137 7138 //pragma(lib, "GL"); 7139 //pragma(lib, "GLU"); 7140 interface GLX { 7141 extern(C) nothrow @nogc { 7142 XVisualInfo* glXChooseVisual(Display *dpy, int screen, 7143 const int *attrib_list); 7144 7145 void glXCopyContext(Display *dpy, GLXContext src, 7146 GLXContext dst, arch_ulong mask); 7147 7148 GLXContext glXCreateContext(Display *dpy, XVisualInfo *vis, 7149 GLXContext share_list, Bool direct); 7150 7151 GLXPixmap glXCreateGLXPixmap(Display *dpy, XVisualInfo *vis, 7152 Pixmap pixmap); 7153 7154 void glXDestroyContext(Display *dpy, GLXContext ctx); 7155 7156 void glXDestroyGLXPixmap(Display *dpy, GLXPixmap pix); 7157 7158 int glXGetConfig(Display *dpy, XVisualInfo *vis, 7159 int attrib, int *value); 7160 7161 GLXContext glXGetCurrentContext(); 7162 7163 GLXDrawable glXGetCurrentDrawable(); 7164 7165 Bool glXIsDirect(Display *dpy, GLXContext ctx); 7166 7167 Bool glXMakeCurrent(Display *dpy, GLXDrawable drawable, 7168 GLXContext ctx); 7169 7170 Bool glXQueryExtension(Display *dpy, int *error_base, int *event_base); 7171 7172 Bool glXQueryVersion(Display *dpy, int *major, int *minor); 7173 7174 void glXSwapBuffers(Display *dpy, GLXDrawable drawable); 7175 7176 void glXUseXFont(Font font, int first, int count, int list_base); 7177 7178 void glXWaitGL(); 7179 7180 void glXWaitX(); 7181 7182 7183 GLXFBConfig* glXChooseFBConfig (Display*, int, int*, int*); 7184 int glXGetFBConfigAttrib (Display*, GLXFBConfig, int, int*); 7185 XVisualInfo* glXGetVisualFromFBConfig (Display*, GLXFBConfig); 7186 7187 char* glXQueryExtensionsString (Display*, int); 7188 void* glXGetProcAddress (const(char)*); 7189 7190 } 7191 } 7192 7193 version(OSX) 7194 mixin DynamicLoad!(GLX, "GL", 0, openGlLibrariesSuccessfullyLoaded) glx; 7195 else 7196 mixin DynamicLoad!(GLX, "GLX", 0, openGlLibrariesSuccessfullyLoaded) glx; 7197 shared static this() { 7198 glx.loadDynamicLibrary(); 7199 } 7200 7201 alias glbindGetProcAddress = glXGetProcAddress; 7202 } 7203 } else version(Windows) { 7204 /* it is done below by interface GL */ 7205 } else 7206 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."); 7207 } 7208 7209 deprecated("Sorry, I misspelled it in the first version! Use `Resizability` instead.") 7210 alias Resizablity = Resizability; 7211 7212 /// When you create a SimpleWindow, you can see its resizability to be one of these via the constructor... 7213 enum Resizability { 7214 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. 7215 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. 7216 /++ 7217 $(PITFALL 7218 Planned for the future but not implemented. 7219 ) 7220 7221 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. 7222 7223 History: 7224 Added November 11, 2022, but not yet implemented and may not be for some time. 7225 +/ 7226 /*@__future*/ allowResizingMaintainingAspectRatio, 7227 /++ 7228 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. 7229 7230 History: 7231 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. 7232 7233 Your programs should not be affected, as they will continue to function as if the user simply never resized the window at all. 7234 +/ 7235 automaticallyScaleIfPossible, 7236 } 7237 /// ditto 7238 alias Resizeability = Resizability; 7239 7240 7241 /++ 7242 Alignment for [ScreenPainter.drawText]. Left, Center, or Right may be combined with VerticalTop, VerticalCenter, or VerticalBottom via bitwise or. 7243 +/ 7244 enum TextAlignment : uint { 7245 Left = 0, /// 7246 Center = 1, /// 7247 Right = 2, /// 7248 7249 VerticalTop = 0, /// 7250 VerticalCenter = 4, /// 7251 VerticalBottom = 8, /// 7252 } 7253 7254 public import arsd.color; // no longer stand alone... :-( but i need a common type for this to work with images easily. 7255 alias Rectangle = arsd.color.Rectangle; 7256 7257 7258 /++ 7259 Keyboard press and release events. 7260 +/ 7261 struct KeyEvent { 7262 /// see table below. Always use the symbolic names, even for ASCII characters, since the actual numbers vary across platforms. See [Key] 7263 Key key; 7264 ubyte hardwareCode; /// A platform and hardware specific code for the key 7265 bool pressed; /// true if the key was just pressed, false if it was just released. note: released events aren't always sent... 7266 7267 deprecated("This never actually worked anyway, you should do a character event handler instead.") dchar character; 7268 7269 uint modifierState; /// see enum [ModifierState]. They are bitwise combined together. 7270 7271 SimpleWindow window; /// associated Window 7272 7273 /++ 7274 A view into the upcoming buffer holding coming character events that are sent if and only if neither 7275 the alt or super modifier keys are pressed (check this with `!(modifierState & (ModifierState.window | ModifierState.alt))` 7276 to predict if char events are actually coming.. 7277 7278 Only available on X systems since this information is not given ahead of time elsewhere. 7279 (Well, you COULD probably dig it up, but as far as I know right now, it isn't terribly pretty.) 7280 7281 I'm adding this because it is useful to the terminal emulator, but given its platform specificness 7282 and potential quirks I'd recommend avoiding it. 7283 7284 History: 7285 Added April 26, 2021 (dub v9.5) 7286 +/ 7287 version(X11) 7288 dchar[] charsPossible; 7289 7290 // convert key event to simplified string representation a-la emacs 7291 const(char)[] toStrBuf(bool growdest=false) (char[] dest) const nothrow @trusted { 7292 uint dpos = 0; 7293 void put (const(char)[] s...) nothrow @trusted { 7294 static if (growdest) { 7295 foreach (char ch; s) if (dpos < dest.length) dest.ptr[dpos++] = ch; else { dest ~= ch; ++dpos; } 7296 } else { 7297 foreach (char ch; s) if (dpos < dest.length) dest.ptr[dpos++] = ch; 7298 } 7299 } 7300 7301 void putMod (ModifierState mod, Key key, string text) nothrow @trusted { 7302 if ((this.modifierState&mod) != 0 && (this.pressed || this.key != key)) put(text); 7303 } 7304 7305 if (!this.key && !(this.modifierState&(ModifierState.ctrl|ModifierState.alt|ModifierState.shift|ModifierState.windows))) return null; 7306 7307 // put modifiers 7308 // releasing modifier keys can produce bizarre things like "Ctrl+Ctrl", so hack around it 7309 putMod(ModifierState.ctrl, Key.Ctrl, "Ctrl+"); 7310 putMod(ModifierState.alt, Key.Alt, "Alt+"); 7311 putMod(ModifierState.windows, Key.Shift, "Windows+"); 7312 putMod(ModifierState.shift, Key.Shift, "Shift+"); 7313 7314 if (this.key) { 7315 foreach (string kn; __traits(allMembers, Key)) { 7316 if (this.key == __traits(getMember, Key, kn)) { 7317 // HACK! 7318 static if (kn == "N0") put("0"); 7319 else static if (kn == "N1") put("1"); 7320 else static if (kn == "N2") put("2"); 7321 else static if (kn == "N3") put("3"); 7322 else static if (kn == "N4") put("4"); 7323 else static if (kn == "N5") put("5"); 7324 else static if (kn == "N6") put("6"); 7325 else static if (kn == "N7") put("7"); 7326 else static if (kn == "N8") put("8"); 7327 else static if (kn == "N9") put("9"); 7328 else put(kn); 7329 return dest[0..dpos]; 7330 } 7331 } 7332 put("Unknown"); 7333 } else { 7334 if (dpos && dest[dpos-1] == '+') --dpos; 7335 } 7336 return dest[0..dpos]; 7337 } 7338 7339 string toStr() () { return cast(string)toStrBuf!true(null); } // it is safe to cast here 7340 7341 /** Parse string into key name with modifiers. It accepts things like: 7342 * 7343 * C-H-1 -- emacs style (ctrl, and windows, and 1) 7344 * 7345 * Ctrl+Win+1 -- windows style 7346 * 7347 * Ctrl-Win-1 -- '-' is a valid delimiter too 7348 * 7349 * Ctrl Win 1 -- and space 7350 * 7351 * and even "Win + 1 + Ctrl". 7352 */ 7353 static KeyEvent parse (const(char)[] name, bool* ignoreModsOut=null, int* updown=null) nothrow @trusted @nogc { 7354 auto nanchor = name; // keep it anchored, 'cause `name` may have NO_INTERIOR set 7355 7356 // remove trailing spaces 7357 while (name.length && name[$-1] <= ' ') name = name[0..$-1]; 7358 7359 // tokens delimited by blank, '+', or '-' 7360 // null on eol 7361 const(char)[] getToken () nothrow @trusted @nogc { 7362 // remove leading spaces and delimiters 7363 while (name.length && (name[0] <= ' ' || name[0] == '+' || name[0] == '-')) name = name[1..$]; 7364 if (name.length == 0) return null; // oops, no more tokens 7365 // get token 7366 size_t epos = 0; 7367 while (epos < name.length && name[epos] > ' ' && name[epos] != '+' && name[epos] != '-') ++epos; 7368 assert(epos > 0 && epos <= name.length); 7369 auto res = name[0..epos]; 7370 name = name[epos..$]; 7371 return res; 7372 } 7373 7374 static bool strEquCI (const(char)[] s0, const(char)[] s1) pure nothrow @trusted @nogc { 7375 if (s0.length != s1.length) return false; 7376 foreach (immutable ci, char c0; s0) { 7377 if (c0 >= 'A' && c0 <= 'Z') c0 += 32; // poor man's tolower 7378 char c1 = s1[ci]; 7379 if (c1 >= 'A' && c1 <= 'Z') c1 += 32; // poor man's tolower 7380 if (c0 != c1) return false; 7381 } 7382 return true; 7383 } 7384 7385 if (ignoreModsOut !is null) *ignoreModsOut = false; 7386 if (updown !is null) *updown = -1; 7387 KeyEvent res; 7388 res.key = cast(Key)0; // just in case 7389 const(char)[] tk, tkn; // last token 7390 bool allowEmascStyle = true; 7391 bool ignoreModifiers = false; 7392 tokenloop: for (;;) { 7393 tk = tkn; 7394 tkn = getToken(); 7395 //k8: yay, i took "Bloody Mess" trait from Fallout! 7396 if (tkn.length != 0 && tk.length == 0) { tk = tkn; continue tokenloop; } 7397 if (tkn.length == 0 && tk.length == 0) break; // no more tokens 7398 if (allowEmascStyle && tkn.length != 0) { 7399 if (tk.length == 1) { 7400 char mdc = tk[0]; 7401 if (mdc >= 'a' && mdc <= 'z') mdc -= 32; // poor man's toupper() 7402 if (mdc == 'C' && (res.modifierState&ModifierState.ctrl) == 0) {res.modifierState |= ModifierState.ctrl; continue tokenloop; } 7403 if (mdc == 'M' && (res.modifierState&ModifierState.alt) == 0) { res.modifierState |= ModifierState.alt; continue tokenloop; } 7404 if (mdc == 'H' && (res.modifierState&ModifierState.windows) == 0) { res.modifierState |= ModifierState.windows; continue tokenloop; } 7405 if (mdc == 'S' && (res.modifierState&ModifierState.shift) == 0) { res.modifierState |= ModifierState.shift; continue tokenloop; } 7406 if (mdc == '*') { ignoreModifiers = true; continue tokenloop; } 7407 if (mdc == 'U' || mdc == 'R') { if (updown !is null) *updown = 0; continue tokenloop; } 7408 if (mdc == 'D' || mdc == 'P') { if (updown !is null) *updown = 1; continue tokenloop; } 7409 } 7410 } 7411 allowEmascStyle = false; 7412 if (strEquCI(tk, "Ctrl")) { res.modifierState |= ModifierState.ctrl; continue tokenloop; } 7413 if (strEquCI(tk, "Alt")) { res.modifierState |= ModifierState.alt; continue tokenloop; } 7414 if (strEquCI(tk, "Win") || strEquCI(tk, "Windows")) { res.modifierState |= ModifierState.windows; continue tokenloop; } 7415 if (strEquCI(tk, "Shift")) { res.modifierState |= ModifierState.shift; continue tokenloop; } 7416 if (strEquCI(tk, "Release")) { if (updown !is null) *updown = 0; continue tokenloop; } 7417 if (strEquCI(tk, "Press")) { if (updown !is null) *updown = 1; continue tokenloop; } 7418 if (tk == "*") { ignoreModifiers = true; continue tokenloop; } 7419 if (tk.length == 0) continue; 7420 // try key name 7421 if (res.key == 0) { 7422 // little hack 7423 if (tk.length == 1 && tk[0] >= '0' && tk[0] <= '9') { 7424 final switch (tk[0]) { 7425 case '0': tk = "N0"; break; 7426 case '1': tk = "N1"; break; 7427 case '2': tk = "N2"; break; 7428 case '3': tk = "N3"; break; 7429 case '4': tk = "N4"; break; 7430 case '5': tk = "N5"; break; 7431 case '6': tk = "N6"; break; 7432 case '7': tk = "N7"; break; 7433 case '8': tk = "N8"; break; 7434 case '9': tk = "N9"; break; 7435 } 7436 } 7437 foreach (string kn; __traits(allMembers, Key)) { 7438 if (strEquCI(tk, kn)) { res.key = __traits(getMember, Key, kn); continue tokenloop; } 7439 } 7440 } 7441 // unknown or duplicate key name, get out of here 7442 break; 7443 } 7444 if (ignoreModsOut !is null) *ignoreModsOut = ignoreModifiers; 7445 return res; // something 7446 } 7447 7448 bool opEquals() (const(char)[] name) const nothrow @trusted @nogc { 7449 enum modmask = (ModifierState.ctrl|ModifierState.alt|ModifierState.shift|ModifierState.windows); 7450 void doModKey (ref uint mask, ref Key kk, Key k, ModifierState mst) { 7451 if (kk == k) { mask |= mst; kk = cast(Key)0; } 7452 } 7453 bool ignoreMods; 7454 int updown; 7455 auto ke = KeyEvent.parse(name, &ignoreMods, &updown); 7456 if ((updown == 0 && this.pressed) || (updown == 1 && !this.pressed)) return false; 7457 if (this.key != ke.key) { 7458 // things like "ctrl+alt" are complicated 7459 uint tkm = this.modifierState&modmask; 7460 uint kkm = ke.modifierState&modmask; 7461 Key tk = this.key; 7462 // ke 7463 doModKey(kkm, ke.key, Key.Ctrl, ModifierState.ctrl); 7464 doModKey(kkm, ke.key, Key.Alt, ModifierState.alt); 7465 doModKey(kkm, ke.key, Key.Windows, ModifierState.windows); 7466 doModKey(kkm, ke.key, Key.Shift, ModifierState.shift); 7467 // this 7468 doModKey(tkm, tk, Key.Ctrl, ModifierState.ctrl); 7469 doModKey(tkm, tk, Key.Alt, ModifierState.alt); 7470 doModKey(tkm, tk, Key.Windows, ModifierState.windows); 7471 doModKey(tkm, tk, Key.Shift, ModifierState.shift); 7472 return (tk == ke.key && tkm == kkm); 7473 } 7474 return (ignoreMods || ((this.modifierState&modmask) == (ke.modifierState&modmask))); 7475 } 7476 } 7477 7478 /// Sets the application name. 7479 @property string ApplicationName(string name) { 7480 return _applicationName = name; 7481 } 7482 7483 string _applicationName; 7484 7485 /// ditto 7486 @property string ApplicationName() { 7487 if(_applicationName is null) { 7488 import core.runtime; 7489 return Runtime.args[0]; 7490 } 7491 return _applicationName; 7492 } 7493 7494 7495 /// Type of a [MouseEvent]. 7496 enum MouseEventType : int { 7497 motion = 0, /// The mouse moved inside the window 7498 buttonPressed = 1, /// A mouse button was pressed or the wheel was spun 7499 buttonReleased = 2, /// A mouse button was released 7500 } 7501 7502 // FIXME: mouse move should be distinct from presses+releases, so we can avoid subscribing to those events in X unnecessarily 7503 /++ 7504 Listen for this on your event listeners if you are interested in mouse action. 7505 7506 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. 7507 7508 Examples: 7509 7510 This will draw boxes on the window with the mouse as you hold the left button. 7511 --- 7512 import arsd.simpledisplay; 7513 7514 void main() { 7515 auto window = new SimpleWindow(); 7516 7517 window.eventLoop(0, 7518 (MouseEvent ev) { 7519 if(ev.modifierState & ModifierState.leftButtonDown) { 7520 auto painter = window.draw(); 7521 painter.fillColor = Color.red; 7522 painter.outlineColor = Color.black; 7523 painter.drawRectangle(Point(ev.x / 16 * 16, ev.y / 16 * 16), 16, 16); 7524 } 7525 } 7526 ); 7527 } 7528 --- 7529 +/ 7530 struct MouseEvent { 7531 MouseEventType type; /// movement, press, release, double click. See [MouseEventType] 7532 7533 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. 7534 int y; /// Current Y position of the cursor when the event fired. 7535 7536 int dx; /// Change in X position since last report 7537 int dy; /// Change in Y position since last report 7538 7539 MouseButton button; /// See [MouseButton] 7540 int modifierState; /// See [ModifierState] 7541 7542 version(X11) 7543 private Time timestamp; 7544 7545 /// Returns a linear representation of mouse button, 7546 /// for use with static arrays. Guaranteed to be >= 0 && <= 15 7547 /// 7548 /// Its implementation is based on range-limiting `core.bitop.bsf(button) + 1`. 7549 @property ubyte buttonLinear() const { 7550 import core.bitop; 7551 if(button == 0) 7552 return 0; 7553 return (bsf(button) + 1) & 0b1111; 7554 } 7555 7556 bool doubleClick; /// was it a double click? Only set on type == [MouseEventType.buttonPressed] 7557 7558 SimpleWindow window; /// The window in which the event happened. 7559 7560 Point globalCoordinates() { 7561 Point p; 7562 if(window is null) 7563 throw new Exception("wtf"); 7564 static if(UsingSimpledisplayX11) { 7565 Window child; 7566 XTranslateCoordinates( 7567 XDisplayConnection.get, 7568 window.impl.window, 7569 RootWindow(XDisplayConnection.get, DefaultScreen(XDisplayConnection.get)), 7570 x, y, &p.x, &p.y, &child); 7571 return p; 7572 } else version(Windows) { 7573 POINT[1] points; 7574 points[0].x = x; 7575 points[0].y = y; 7576 MapWindowPoints( 7577 window.impl.hwnd, 7578 null, 7579 points.ptr, 7580 points.length 7581 ); 7582 p.x = points[0].x; 7583 p.y = points[0].y; 7584 7585 return p; 7586 } else version(OSXCocoa) { 7587 throw new NotYetImplementedException(); 7588 } else static assert(0); 7589 } 7590 7591 bool opEquals() (const(char)[] str) pure nothrow @trusted @nogc { return equStr(this, str); } 7592 7593 /** 7594 can contain emacs-like modifier prefix 7595 case-insensitive names: 7596 lmbX/leftX 7597 rmbX/rightX 7598 mmbX/middleX 7599 wheelX 7600 motion (no prefix allowed) 7601 'X' is either "up" or "down" (or "-up"/"-down"); if omited, means "down" 7602 */ 7603 static bool equStr() (scope auto ref const MouseEvent event, const(char)[] str) pure nothrow @trusted @nogc { 7604 if (str.length == 0) return false; // just in case 7605 debug(arsd_mevent_strcmp) { import iv.cmdcon; conwriteln("str=<", str, ">"); } 7606 enum Flag : uint { Up = 0x8000_0000U, Down = 0x4000_0000U, Any = 0x1000_0000U } 7607 auto anchor = str; 7608 uint mods = 0; // uint.max == any 7609 // interesting bits in kmod 7610 uint kmodmask = 7611 ModifierState.shift| 7612 ModifierState.ctrl| 7613 ModifierState.alt| 7614 ModifierState.windows| 7615 ModifierState.leftButtonDown| 7616 ModifierState.middleButtonDown| 7617 ModifierState.rightButtonDown| 7618 0; 7619 uint lastButt = uint.max; // otherwise, bit 31 means "down" 7620 bool wasButtons = false; 7621 while (str.length) { 7622 if (str.ptr[0] <= ' ') { 7623 while (str.length && str.ptr[0] <= ' ') str = str[1..$]; 7624 continue; 7625 } 7626 // one-letter modifier? 7627 if (str.length >= 2 && str.ptr[1] == '-') { 7628 switch (str.ptr[0]) { 7629 case '*': // "any" modifier (cannot be undone) 7630 mods = mods.max; 7631 break; 7632 case 'C': case 'c': // emacs "ctrl" 7633 if (mods != mods.max) mods |= ModifierState.ctrl; 7634 break; 7635 case 'M': case 'm': // emacs "meta" 7636 if (mods != mods.max) mods |= ModifierState.alt; 7637 break; 7638 case 'S': case 's': // emacs "shift" 7639 if (mods != mods.max) mods |= ModifierState.shift; 7640 break; 7641 case 'H': case 'h': // emacs "hyper" (aka winkey) 7642 if (mods != mods.max) mods |= ModifierState.windows; 7643 break; 7644 default: 7645 return false; // unknown modifier 7646 } 7647 str = str[2..$]; 7648 continue; 7649 } 7650 // word 7651 char[16] buf = void; // locased 7652 auto wep = 0; 7653 while (str.length) { 7654 immutable char ch = str.ptr[0]; 7655 if (ch <= ' ' || ch == '-') break; 7656 str = str[1..$]; 7657 if (wep > buf.length) return false; // too long 7658 if (ch >= 'A' && ch <= 'Z') buf.ptr[wep++] = cast(char)(ch+32); // poor man tolower 7659 else if (ch >= 'a' && ch <= 'z') buf.ptr[wep++] = ch; 7660 else return false; // invalid char 7661 } 7662 if (wep == 0) return false; // just in case 7663 uint bnum; 7664 enum UpDown { None = -1, Up, Down, Any } 7665 auto updown = UpDown.None; // 0: up; 1: down 7666 switch (buf[0..wep]) { 7667 // left button 7668 case "lmbup": case "leftup": updown = UpDown.Up; goto case "lmb"; 7669 case "lmbdown": case "leftdown": updown = UpDown.Down; goto case "lmb"; 7670 case "lmbany": case "leftany": updown = UpDown.Any; goto case "lmb"; 7671 case "lmb": case "left": bnum = 0; break; 7672 // middle button 7673 case "mmbup": case "middleup": updown = UpDown.Up; goto case "mmb"; 7674 case "mmbdown": case "middledown": updown = UpDown.Down; goto case "mmb"; 7675 case "mmbany": case "middleany": updown = UpDown.Any; goto case "mmb"; 7676 case "mmb": case "middle": bnum = 1; break; 7677 // right button 7678 case "rmbup": case "rightup": updown = UpDown.Up; goto case "rmb"; 7679 case "rmbdown": case "rightdown": updown = UpDown.Down; goto case "rmb"; 7680 case "rmbany": case "rightany": updown = UpDown.Any; goto case "rmb"; 7681 case "rmb": case "right": bnum = 2; break; 7682 // wheel 7683 case "wheelup": updown = UpDown.Up; goto case "wheel"; 7684 case "wheeldown": updown = UpDown.Down; goto case "wheel"; 7685 case "wheelany": updown = UpDown.Any; goto case "wheel"; 7686 case "wheel": bnum = 3; break; 7687 // motion 7688 case "motion": bnum = 7; break; 7689 // unknown 7690 default: return false; 7691 } 7692 debug(arsd_mevent_strcmp) { import iv.cmdcon; conprintfln(" 0: mods=0x%08x; bnum=%u; updown=%s [%s]", mods, bnum, updown, str); } 7693 // parse possible "-up" or "-down" 7694 if (updown == UpDown.None && bnum < 7 && str.length > 0 && str.ptr[0] == '-') { 7695 wep = 0; 7696 foreach (immutable idx, immutable char ch; str[1..$]) { 7697 if (ch <= ' ' || ch == '-') break; 7698 assert(idx == wep); // for now; trick 7699 if (wep > buf.length) { wep = 0; break; } // too long 7700 if (ch >= 'A' && ch <= 'Z') buf.ptr[wep++] = cast(char)(ch+32); // poor man tolower 7701 else if (ch >= 'a' && ch <= 'z') buf.ptr[wep++] = ch; 7702 else { wep = 0; break; } // invalid char 7703 } 7704 if (wep == 2 && buf[0..wep] == "up") updown = UpDown.Up; 7705 else if (wep == 4 && buf[0..wep] == "down") updown = UpDown.Down; 7706 else if (wep == 3 && buf[0..wep] == "any") updown = UpDown.Any; 7707 // remove parsed part 7708 if (updown != UpDown.None) str = str[wep+1..$]; 7709 } 7710 if (updown == UpDown.None) { 7711 updown = UpDown.Down; 7712 } 7713 wasButtons = wasButtons || (bnum <= 2); 7714 //assert(updown != UpDown.None); 7715 debug(arsd_mevent_strcmp) { import iv.cmdcon; conprintfln(" 1: mods=0x%08x; bnum=%u; updown=%s [%s]", mods, bnum, updown, str); } 7716 // if we have a previous button, it goes to modifiers (unless it is a wheel or motion) 7717 if (lastButt != lastButt.max) { 7718 if ((lastButt&0xff) >= 3) return false; // wheel or motion 7719 if (mods != mods.max) { 7720 uint butbit = 0; 7721 final switch (lastButt&0x03) { 7722 case 0: butbit = ModifierState.leftButtonDown; break; 7723 case 1: butbit = ModifierState.middleButtonDown; break; 7724 case 2: butbit = ModifierState.rightButtonDown; break; 7725 } 7726 if (lastButt&Flag.Down) mods |= butbit; 7727 else if (lastButt&Flag.Up) mods &= ~butbit; 7728 else if (lastButt&Flag.Any) kmodmask &= ~butbit; 7729 } 7730 } 7731 // remember last button 7732 lastButt = bnum|(updown == UpDown.Up ? Flag.Up : updown == UpDown.Any ? Flag.Any : Flag.Down); 7733 } 7734 // no button -- nothing to do 7735 if (lastButt == lastButt.max) return false; 7736 // done parsing, check if something's left 7737 foreach (immutable char ch; str) if (ch > ' ') return false; // oops 7738 // remove action button from mask 7739 if ((lastButt&0xff) < 3) { 7740 final switch (lastButt&0x03) { 7741 case 0: kmodmask &= ~cast(uint)ModifierState.leftButtonDown; break; 7742 case 1: kmodmask &= ~cast(uint)ModifierState.middleButtonDown; break; 7743 case 2: kmodmask &= ~cast(uint)ModifierState.rightButtonDown; break; 7744 } 7745 } 7746 // special case: "Motion" means "ignore buttons" 7747 if ((lastButt&0xff) == 7 && !wasButtons) { 7748 debug(arsd_mevent_strcmp) { import iv.cmdcon; conwriteln(" *: special motion"); } 7749 kmodmask &= ~cast(uint)(ModifierState.leftButtonDown|ModifierState.middleButtonDown|ModifierState.rightButtonDown); 7750 } 7751 uint kmod = event.modifierState&kmodmask; 7752 debug(arsd_mevent_strcmp) { import iv.cmdcon; conprintfln(" *: mods=0x%08x; lastButt=0x%08x; kmod=0x%08x; type=%s", mods, lastButt, kmod, event.type); } 7753 // check modifier state 7754 if (mods != mods.max) { 7755 if (kmod != mods) return false; 7756 } 7757 // now check type 7758 if ((lastButt&0xff) == 7) { 7759 // motion 7760 if (event.type != MouseEventType.motion) return false; 7761 } else if ((lastButt&0xff) == 3) { 7762 // wheel 7763 if (lastButt&Flag.Up) return (event.type == MouseEventType.buttonPressed && event.button == MouseButton.wheelUp); 7764 if (lastButt&Flag.Down) return (event.type == MouseEventType.buttonPressed && event.button == MouseButton.wheelDown); 7765 if (lastButt&Flag.Any) return (event.type == MouseEventType.buttonPressed && (event.button == MouseButton.wheelUp || event.button == MouseButton.wheelUp)); 7766 return false; 7767 } else { 7768 // buttons 7769 if (((lastButt&Flag.Down) != 0 && event.type != MouseEventType.buttonPressed) || 7770 ((lastButt&Flag.Up) != 0 && event.type != MouseEventType.buttonReleased)) 7771 { 7772 return false; 7773 } 7774 // button number 7775 switch (lastButt&0x03) { 7776 case 0: if (event.button != MouseButton.left) return false; break; 7777 case 1: if (event.button != MouseButton.middle) return false; break; 7778 case 2: if (event.button != MouseButton.right) return false; break; 7779 default: return false; 7780 } 7781 } 7782 return true; 7783 } 7784 } 7785 7786 version(arsd_mevent_strcmp_test) unittest { 7787 MouseEvent event; 7788 event.type = MouseEventType.buttonPressed; 7789 event.button = MouseButton.left; 7790 event.modifierState = ModifierState.ctrl; 7791 assert(event == "C-LMB"); 7792 assert(event != "C-LMBUP"); 7793 assert(event != "C-LMB-UP"); 7794 assert(event != "C-S-LMB"); 7795 assert(event == "*-LMB"); 7796 assert(event != "*-LMB-UP"); 7797 7798 event.type = MouseEventType.buttonReleased; 7799 assert(event != "C-LMB"); 7800 assert(event == "C-LMBUP"); 7801 assert(event == "C-LMB-UP"); 7802 assert(event != "C-S-LMB"); 7803 assert(event != "*-LMB"); 7804 assert(event == "*-LMB-UP"); 7805 7806 event.button = MouseButton.right; 7807 event.modifierState |= ModifierState.shift; 7808 event.type = MouseEventType.buttonPressed; 7809 assert(event != "C-LMB"); 7810 assert(event != "C-LMBUP"); 7811 assert(event != "C-LMB-UP"); 7812 assert(event != "C-S-LMB"); 7813 assert(event != "*-LMB"); 7814 assert(event != "*-LMB-UP"); 7815 7816 assert(event != "C-RMB"); 7817 assert(event != "C-RMBUP"); 7818 assert(event != "C-RMB-UP"); 7819 assert(event == "C-S-RMB"); 7820 assert(event == "*-RMB"); 7821 assert(event != "*-RMB-UP"); 7822 } 7823 7824 /// This gives a few more options to drawing lines and such 7825 struct Pen { 7826 Color color; /// the foreground color 7827 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. 7828 Style style; /// See [Style] 7829 /+ 7830 // From X.h 7831 7832 #define LineSolid 0 7833 #define LineOnOffDash 1 7834 #define LineDoubleDash 2 7835 LineDou- The full path of the line is drawn, but the 7836 bleDash even dashes are filled differently from the 7837 odd dashes (see fill-style) with CapButt 7838 style used where even and odd dashes meet. 7839 7840 7841 7842 /* capStyle */ 7843 7844 #define CapNotLast 0 7845 #define CapButt 1 7846 #define CapRound 2 7847 #define CapProjecting 3 7848 7849 /* joinStyle */ 7850 7851 #define JoinMiter 0 7852 #define JoinRound 1 7853 #define JoinBevel 2 7854 7855 /* fillStyle */ 7856 7857 #define FillSolid 0 7858 #define FillTiled 1 7859 #define FillStippled 2 7860 #define FillOpaqueStippled 3 7861 7862 7863 +/ 7864 /// Style of lines drawn 7865 enum Style { 7866 Solid, /// a solid line 7867 Dashed, /// a dashed line 7868 Dotted, /// a dotted line 7869 } 7870 } 7871 7872 7873 /++ 7874 Represents an in-memory image in the format that the GUI expects, but with its raw data available to your program. 7875 7876 7877 On Windows, this means a device-independent bitmap. On X11, it is an XImage. 7878 7879 $(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.) 7880 7881 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. 7882 7883 If you intend to draw an image to screen several times, you will want to convert it into a [Sprite]. 7884 7885 $(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. 7886 7887 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! 7888 7889 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!) 7890 7891 Please call `destroy(image);` when you are done with it. The easiest way to do this is with scope: 7892 7893 --- 7894 auto image = new Image(256, 256); 7895 scope(exit) destroy(image); 7896 --- 7897 7898 As long as you don't hold on to it outside the scope. 7899 7900 I might change it to be an owned pointer at some point in the future. 7901 7902 ) 7903 7904 Drawing pixels on the image may be simple, using the `opIndexAssign` function, but 7905 you can also often get a fair amount of speedup by getting the raw data format and 7906 writing some custom code. 7907 7908 FIXME INSERT EXAMPLES HERE 7909 7910 7911 +/ 7912 final class Image { 7913 /// 7914 this(int width, int height, bool forcexshm=false, bool enableAlpha = false) { 7915 this.width = width; 7916 this.height = height; 7917 this.enableAlpha = enableAlpha; 7918 7919 impl.createImage(width, height, forcexshm, enableAlpha); 7920 } 7921 7922 /// 7923 this(Size size, bool forcexshm=false, bool enableAlpha = false) { 7924 this(size.width, size.height, forcexshm, enableAlpha); 7925 } 7926 7927 private bool suppressDestruction; 7928 7929 version(X11) 7930 this(XImage* handle) { 7931 this.handle = handle; 7932 this.rawData = cast(ubyte*) handle.data; 7933 this.width = handle.width; 7934 this.height = handle.height; 7935 this.enableAlpha = handle.depth == 32; 7936 suppressDestruction = true; 7937 } 7938 7939 ~this() { 7940 if(suppressDestruction) return; 7941 impl.dispose(); 7942 } 7943 7944 // these numbers are used for working with rawData itself, skipping putPixel and getPixel 7945 /// if you do the math yourself you might be able to optimize it. Call these functions only once and cache the value. 7946 pure const @system nothrow { 7947 /* 7948 To use these to draw a blue rectangle with size WxH at position X,Y... 7949 7950 // make certain that it will fit before we proceed 7951 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! 7952 7953 // gather all the values you'll need up front. These can be kept until the image changes size if you want 7954 // (though calculating them isn't really that expensive). 7955 auto nextLineAdjustment = img.adjustmentForNextLine(); 7956 auto offR = img.redByteOffset(); 7957 auto offB = img.blueByteOffset(); 7958 auto offG = img.greenByteOffset(); 7959 auto bpp = img.bytesPerPixel(); 7960 7961 auto data = img.getDataPointer(); 7962 7963 // figure out the starting byte offset 7964 auto offset = img.offsetForTopLeftPixel() + nextLineAdjustment*Y + bpp * X; 7965 7966 auto startOfLine = data + offset; // get our pointer lined up on the first pixel 7967 7968 // and now our drawing loop for the rectangle 7969 foreach(y; 0 .. H) { 7970 auto data = startOfLine; // we keep the start of line separately so moving to the next line is simple and portable 7971 foreach(x; 0 .. W) { 7972 // write our color 7973 data[offR] = 0; 7974 data[offG] = 0; 7975 data[offB] = 255; 7976 7977 data += bpp; // moving to the next pixel is just an addition... 7978 } 7979 startOfLine += nextLineAdjustment; 7980 } 7981 7982 7983 As you can see, the loop itself was very simple thanks to the calculations being moved outside. 7984 7985 FIXME: I wonder if I can make the pixel formats consistently 32 bit across platforms, so the color offsets 7986 can be made into a bitmask or something so we can write them as *uint... 7987 */ 7988 7989 /// 7990 int offsetForTopLeftPixel() { 7991 version(X11) { 7992 return 0; 7993 } else version(Windows) { 7994 if(enableAlpha) { 7995 return (width * 4) * (height - 1); 7996 } else { 7997 return (((cast(int) width * 3 + 3) / 4) * 4) * (height - 1); 7998 } 7999 } else version(OSXCocoa) { 8000 return 0 ; //throw new NotYetImplementedException(); 8001 } else static assert(0, "fill in this info for other OSes"); 8002 } 8003 8004 /// 8005 int offsetForPixel(int x, int y) { 8006 version(X11) { 8007 auto offset = (y * width + x) * 4; 8008 return offset; 8009 } else version(Windows) { 8010 if(enableAlpha) { 8011 auto itemsPerLine = width * 4; 8012 // remember, bmps are upside down 8013 auto offset = itemsPerLine * (height - y - 1) + x * 4; 8014 return offset; 8015 } else { 8016 auto itemsPerLine = ((cast(int) width * 3 + 3) / 4) * 4; 8017 // remember, bmps are upside down 8018 auto offset = itemsPerLine * (height - y - 1) + x * 3; 8019 return offset; 8020 } 8021 } else version(OSXCocoa) { 8022 return 0 ; //throw new NotYetImplementedException(); 8023 } else static assert(0, "fill in this info for other OSes"); 8024 } 8025 8026 /// 8027 int adjustmentForNextLine() { 8028 version(X11) { 8029 return width * 4; 8030 } else version(Windows) { 8031 // windows bmps are upside down, so the adjustment is actually negative 8032 if(enableAlpha) 8033 return - (cast(int) width * 4); 8034 else 8035 return -((cast(int) width * 3 + 3) / 4) * 4; 8036 } else version(OSXCocoa) { 8037 return 0 ; //throw new NotYetImplementedException(); 8038 } else static assert(0, "fill in this info for other OSes"); 8039 } 8040 8041 /// once you have the position of a pixel, use these to get to the proper color 8042 int redByteOffset() { 8043 version(X11) { 8044 return 2; 8045 } else version(Windows) { 8046 return 2; 8047 } else version(OSXCocoa) { 8048 return 0 ; //throw new NotYetImplementedException(); 8049 } else static assert(0, "fill in this info for other OSes"); 8050 } 8051 8052 /// 8053 int greenByteOffset() { 8054 version(X11) { 8055 return 1; 8056 } else version(Windows) { 8057 return 1; 8058 } else version(OSXCocoa) { 8059 return 0 ; //throw new NotYetImplementedException(); 8060 } else static assert(0, "fill in this info for other OSes"); 8061 } 8062 8063 /// 8064 int blueByteOffset() { 8065 version(X11) { 8066 return 0; 8067 } else version(Windows) { 8068 return 0; 8069 } else version(OSXCocoa) { 8070 return 0 ; //throw new NotYetImplementedException(); 8071 } else static assert(0, "fill in this info for other OSes"); 8072 } 8073 8074 /// Only valid if [enableAlpha] is true 8075 int alphaByteOffset() { 8076 version(X11) { 8077 return 3; 8078 } else version(Windows) { 8079 return 3; 8080 } else version(OSXCocoa) { 8081 return 3; //throw new NotYetImplementedException(); 8082 } else static assert(0, "fill in this info for other OSes"); 8083 } 8084 } 8085 8086 /// 8087 final void putPixel(int x, int y, Color c) { 8088 if(x < 0 || x >= width) 8089 return; 8090 if(y < 0 || y >= height) 8091 return; 8092 8093 impl.setPixel(x, y, c); 8094 } 8095 8096 /// 8097 final Color getPixel(int x, int y) { 8098 if(x < 0 || x >= width) 8099 return Color.transparent; 8100 if(y < 0 || y >= height) 8101 return Color.transparent; 8102 8103 version(OSXCocoa) throw new NotYetImplementedException(); else 8104 return impl.getPixel(x, y); 8105 } 8106 8107 /// 8108 final void opIndexAssign(Color c, int x, int y) { 8109 putPixel(x, y, c); 8110 } 8111 8112 /// 8113 TrueColorImage toTrueColorImage() { 8114 auto tci = new TrueColorImage(width, height); 8115 convertToRgbaBytes(tci.imageData.bytes); 8116 return tci; 8117 } 8118 8119 /// 8120 static Image fromMemoryImage(MemoryImage i, bool enableAlpha = false, bool premultiply = true) { 8121 auto tci = i.getAsTrueColorImage(); 8122 auto img = new Image(tci.width, tci.height, false, enableAlpha); 8123 static if(UsingSimpledisplayX11) 8124 img.premultiply = premultiply; 8125 img.setRgbaBytes(tci.imageData.bytes); 8126 return img; 8127 } 8128 8129 /// this is here for interop with arsd.image. where can be a TrueColorImage's data member 8130 /// if you pass in a buffer, it will put it right there. length must be width*height*4 already 8131 /// if you pass null, it will allocate a new one. 8132 ubyte[] getRgbaBytes(ubyte[] where = null) { 8133 if(where is null) 8134 where = new ubyte[this.width*this.height*4]; 8135 convertToRgbaBytes(where); 8136 return where; 8137 } 8138 8139 /// this is here for interop with arsd.image. from can be a TrueColorImage's data member 8140 void setRgbaBytes(in ubyte[] from ) { 8141 assert(from.length == this.width * this.height * 4); 8142 setFromRgbaBytes(from); 8143 } 8144 8145 // FIXME: make properly cross platform by getting rgba right 8146 8147 /// warning: this is not portable across platforms because the data format can change 8148 ubyte* getDataPointer() { 8149 return impl.rawData; 8150 } 8151 8152 /// for use with getDataPointer 8153 final int bytesPerLine() const pure @safe nothrow { 8154 version(Windows) 8155 return enableAlpha ? (width * 4) : (((cast(int) width * 3 + 3) / 4) * 4); 8156 else version(X11) 8157 return 4 * width; 8158 else version(OSXCocoa) 8159 return 4 * width; 8160 else static assert(0); 8161 } 8162 8163 /// for use with getDataPointer 8164 final int bytesPerPixel() const pure @safe nothrow { 8165 version(Windows) 8166 return enableAlpha ? 4 : 3; 8167 else version(X11) 8168 return 4; 8169 else version(OSXCocoa) 8170 return 4; 8171 else static assert(0); 8172 } 8173 8174 /// 8175 immutable int width; 8176 8177 /// 8178 immutable int height; 8179 8180 /// 8181 immutable bool enableAlpha; 8182 //private: 8183 mixin NativeImageImplementation!() impl; 8184 } 8185 8186 /++ 8187 A convenience function to pop up a window displaying the image. 8188 If you pass a win, it will draw the image in it. Otherwise, it will 8189 create a window with the size of the image and run its event loop, closing 8190 when a key is pressed. 8191 8192 History: 8193 `BlockingMode` parameter added on December 8, 2021. Previously, it would 8194 always block until the application quit which could cause bizarre behavior 8195 inside a more complex application. Now, the default is to block until 8196 this window closes if it is the only event loop running, and otherwise, 8197 not to block at all and just pop up the display window asynchronously. 8198 +/ 8199 void displayImage(Image image, SimpleWindow win = null, BlockingMode bm = BlockingMode.untilWindowCloses | BlockingMode.onlyIfNotNested) { 8200 if(win is null) { 8201 win = new SimpleWindow(image); 8202 { 8203 auto p = win.draw; 8204 p.drawImage(Point(0, 0), image); 8205 } 8206 win.eventLoopWithBlockingMode( 8207 bm, 0, 8208 (KeyEvent ev) { 8209 if (ev.pressed && (ev.key == Key.Escape || ev.key == Key.Space)) win.close(); 8210 } ); 8211 } else { 8212 win.image = image; 8213 } 8214 } 8215 8216 enum FontWeight : int { 8217 dontcare = 0, 8218 thin = 100, 8219 extralight = 200, 8220 light = 300, 8221 regular = 400, 8222 medium = 500, 8223 semibold = 600, 8224 bold = 700, 8225 extrabold = 800, 8226 heavy = 900 8227 } 8228 8229 /++ 8230 Interface with the common functionality for font measurements between [OperatingSystemFont] and [DrawableFont]. 8231 8232 History: 8233 Added October 24, 2022. The methods were already on [OperatingSystemFont] before that. 8234 +/ 8235 interface MeasurableFont { 8236 /++ 8237 Returns true if it is a monospace font, meaning each of the 8238 glyphs (at least the ascii characters) have matching width 8239 and no kerning, so you can determine the display width of some 8240 strings by simply multiplying the string width by [averageWidth]. 8241 8242 (Please note that multiply doesn't $(I actually) work in general, 8243 consider characters like tab and newline, but it does sometimes.) 8244 +/ 8245 bool isMonospace(); 8246 8247 /++ 8248 The average width of glyphs in the font, traditionally equal to the 8249 width of the lowercase x. Can be used to estimate bounding boxes, 8250 especially if the font [isMonospace]. 8251 8252 Given in pixels. 8253 +/ 8254 int averageWidth(); 8255 /++ 8256 The height of the bounding box of a line. 8257 +/ 8258 int height(); 8259 /++ 8260 The maximum ascent of a glyph above the baseline. 8261 8262 Given in pixels. 8263 +/ 8264 int ascent(); 8265 /++ 8266 The maximum descent of a glyph below the baseline. For example, how low the g might go. 8267 8268 Given in pixels. 8269 +/ 8270 int descent(); 8271 /++ 8272 The display width of the given string, and if you provide a window, it will use it to 8273 make the pixel count on screen more accurate too, but this shouldn't generally be necessary. 8274 8275 Given in pixels. 8276 +/ 8277 int stringWidth(scope const(char)[] s, SimpleWindow window = null); 8278 8279 } 8280 8281 // FIXME: i need a font cache and it needs to handle disconnects. 8282 8283 /++ 8284 Represents a font loaded off the operating system or the X server. 8285 8286 8287 While the api here is unified cross platform, the fonts are not necessarily 8288 available, even across machines of the same platform, so be sure to always check 8289 for null (using [isNull]) and have a fallback plan. 8290 8291 When you have a font you like, use [ScreenPainter.setFont] to load it for drawing. 8292 8293 Worst case, a null font will automatically fall back to the default font loaded 8294 for your system. 8295 +/ 8296 class OperatingSystemFont : MeasurableFont { 8297 // FIXME: when the X Connection is lost, these need to be invalidated! 8298 // that means I need to store the original stuff again to reconstruct it too. 8299 8300 version(X11) { 8301 XFontStruct* font; 8302 XFontSet fontset; 8303 8304 version(with_xft) { 8305 XftFont* xftFont; 8306 bool isXft; 8307 } 8308 } else version(Windows) { 8309 HFONT font; 8310 int width_; 8311 int height_; 8312 } else version(OSXCocoa) { 8313 NSFont font; 8314 } else static assert(0); 8315 8316 /++ 8317 Constructs the class and immediately calls [load]. 8318 +/ 8319 this(string name, int size = 0, FontWeight weight = FontWeight.dontcare, bool italic = false) { 8320 load(name, size, weight, italic); 8321 } 8322 8323 /++ 8324 Constructs the object, but does nothing. Call one of [load] or [loadDefault] to populate the object. 8325 8326 You can also call the platform-specific [loadXft], [loadCoreX], or [loadWin32] functions if appropriate for you. 8327 8328 History: 8329 Added January 24, 2021. 8330 +/ 8331 this() { 8332 // this space intentionally left blank 8333 } 8334 8335 /++ 8336 Constructs a copy of the given font object. 8337 8338 History: 8339 Added January 7, 2023. 8340 +/ 8341 this(OperatingSystemFont font) { 8342 if(font is null || font.loadedInfo is LoadedInfo.init) 8343 loadDefault(); 8344 else 8345 load(font.loadedInfo.tupleof); 8346 } 8347 8348 /++ 8349 Loads specifically with the Xft library - a freetype font from a fontconfig string. 8350 8351 History: 8352 Added November 13, 2020. 8353 +/ 8354 version(with_xft) 8355 bool loadXft(string name, int size = 0, FontWeight weight = FontWeight.dontcare, bool italic = false) { 8356 unload(); 8357 8358 if(!XftLibrary.attempted) { 8359 XftLibrary.loadDynamicLibrary(); 8360 } 8361 8362 if(!XftLibrary.loadSuccessful) 8363 return false; 8364 8365 auto display = XDisplayConnection.get; 8366 8367 char[256] nameBuffer = void; 8368 int nbp = 0; 8369 8370 void add(in char[] a) { 8371 nameBuffer[nbp .. nbp + a.length] = a[]; 8372 nbp += a.length; 8373 } 8374 add(name); 8375 8376 if(size) { 8377 add(":size="); 8378 add(toInternal!string(size)); 8379 } 8380 if(weight != FontWeight.dontcare) { 8381 add(":weight="); 8382 add(weightToString(weight)); 8383 } 8384 if(italic) 8385 add(":slant=100"); 8386 8387 nameBuffer[nbp] = 0; 8388 8389 this.xftFont = XftFontOpenName( 8390 display, 8391 DefaultScreen(display), 8392 nameBuffer.ptr 8393 ); 8394 8395 this.isXft = true; 8396 8397 if(xftFont !is null) { 8398 isMonospace_ = stringWidth("x") == stringWidth("M"); 8399 ascent_ = xftFont.ascent; 8400 descent_ = xftFont.descent; 8401 } 8402 8403 return !isNull(); 8404 } 8405 8406 /++ 8407 Lists available fonts from the system that match the given pattern, finding names that are suitable for passing to [OperatingSystemFont]'s constructor. 8408 8409 8410 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. 8411 8412 If `pattern` is null, it returns all available font families. 8413 8414 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. 8415 8416 The format of the pattern is platform-specific. 8417 8418 History: 8419 Added May 1, 2021 (dub v9.5) 8420 +/ 8421 static void listFonts(string pattern, bool delegate(in char[] name) handler) { 8422 version(Windows) { 8423 auto hdc = GetDC(null); 8424 scope(exit) ReleaseDC(null, hdc); 8425 LOGFONT logfont; 8426 static extern(Windows) int proc(const LOGFONT* lf, const TEXTMETRIC* tm, DWORD type, LPARAM p) { 8427 auto localHandler = *(cast(typeof(handler)*) p); 8428 return localHandler(lf.lfFaceName[].sliceCString) ? 1 : 0; 8429 } 8430 EnumFontFamiliesEx(hdc, &logfont, &proc, cast(LPARAM) &handler, 0); 8431 } else version(X11) { 8432 //import core.stdc.stdio; 8433 bool done = false; 8434 version(with_xft) { 8435 if(!XftLibrary.attempted) { 8436 XftLibrary.loadDynamicLibrary(); 8437 } 8438 8439 if(!XftLibrary.loadSuccessful) 8440 goto skipXft; 8441 8442 if(!FontConfigLibrary.attempted) 8443 FontConfigLibrary.loadDynamicLibrary(); 8444 if(!FontConfigLibrary.loadSuccessful) 8445 goto skipXft; 8446 8447 { 8448 auto got = XftListFonts(XDisplayConnection.get, 0, null, "family".ptr, "style".ptr, null); 8449 if(got is null) 8450 goto skipXft; 8451 scope(exit) FcFontSetDestroy(got); 8452 8453 auto fontPatterns = got.fonts[0 .. got.nfont]; 8454 foreach(candidate; fontPatterns) { 8455 char* where, whereStyle; 8456 8457 char* pmg = FcNameUnparse(candidate); 8458 8459 //FcPatternGetString(candidate, "family", 0, &where); 8460 //FcPatternGetString(candidate, "style", 0, &whereStyle); 8461 //if(where && whereStyle) { 8462 if(pmg) { 8463 if(!handler(pmg.sliceCString)) 8464 return; 8465 //printf("%s || %s %s\n", pmg, where, whereStyle); 8466 } 8467 } 8468 } 8469 } 8470 8471 skipXft: 8472 8473 if(pattern is null) 8474 pattern = "*"; 8475 8476 int count; 8477 auto coreFontsRaw = XListFonts(XDisplayConnection.get, pattern.toStringz, 10000 /* max return */, &count); 8478 scope(exit) XFreeFontNames(coreFontsRaw); 8479 8480 auto coreFonts = coreFontsRaw[0 .. count]; 8481 8482 foreach(font; coreFonts) { 8483 char[128] tmp; 8484 tmp[0 ..5] = "core:"; 8485 auto cf = font.sliceCString; 8486 if(5 + cf.length > tmp.length) 8487 assert(0, "a font name was too long, sorry i didn't bother implementing a fallback"); 8488 tmp[5 .. 5 + cf.length] = cf; 8489 if(!handler(tmp[0 .. 5 + cf.length])) 8490 return; 8491 } 8492 } 8493 } 8494 8495 /++ 8496 Returns the raw content of the ttf file, if possible. This allows you to use OperatingSystemFont 8497 to look up fonts that you then pass to things like [arsd.ttf.OpenGlLimitedFont] or [arsd.nanovega]. 8498 8499 Returns null if impossible. It is impossible if the loaded font is not a local TTF file or if the 8500 underlying system doesn't support returning the raw bytes. 8501 8502 History: 8503 Added September 10, 2021 (dub v10.3) 8504 +/ 8505 ubyte[] getTtfBytes() { 8506 if(isNull) 8507 return null; 8508 8509 version(Windows) { 8510 auto dc = GetDC(null); 8511 auto orig = SelectObject(dc, font); 8512 8513 scope(exit) { 8514 SelectObject(dc, orig); 8515 ReleaseDC(null, dc); 8516 } 8517 8518 auto res = GetFontData(dc, 0 /* whole file */, 0 /* offset */, null, 0); 8519 if(res == GDI_ERROR) 8520 return null; 8521 8522 ubyte[] buffer = new ubyte[](res); 8523 res = GetFontData(dc, 0 /* whole file */, 0 /* offset */, buffer.ptr, cast(DWORD) buffer.length); 8524 if(res == GDI_ERROR) 8525 return null; // wtf really tbh 8526 8527 return buffer; 8528 } else version(with_xft) { 8529 if(isXft && xftFont) { 8530 if(!FontConfigLibrary.attempted) 8531 FontConfigLibrary.loadDynamicLibrary(); 8532 if(!FontConfigLibrary.loadSuccessful) 8533 return null; 8534 8535 char* file; 8536 if (FcPatternGetString(xftFont.pattern, "file", 0, &file) == 0 /*FcResultMatch*/) { 8537 if (file !is null && file[0]) { 8538 import core.stdc.stdio; 8539 auto fp = fopen(file, "rb"); 8540 if(fp is null) 8541 return null; 8542 scope(exit) 8543 fclose(fp); 8544 fseek(fp, 0, SEEK_END); 8545 ubyte[] buffer = new ubyte[](ftell(fp)); 8546 fseek(fp, 0, SEEK_SET); 8547 8548 auto got = fread(buffer.ptr, 1, buffer.length, fp); 8549 if(got != buffer.length) 8550 return null; 8551 8552 return buffer; 8553 } 8554 } 8555 } 8556 return null; 8557 } else throw new NotYetImplementedException(); 8558 } 8559 8560 // see also: XftLockFace(font) which gives a FT_Face. from /usr/include/X11/Xft/Xft.h line 352 8561 8562 private string weightToString(FontWeight weight) { 8563 with(FontWeight) 8564 final switch(weight) { 8565 case dontcare: return "*"; 8566 case thin: return "extralight"; 8567 case extralight: return "extralight"; 8568 case light: return "light"; 8569 case regular: return "regular"; 8570 case medium: return "medium"; 8571 case semibold: return "demibold"; 8572 case bold: return "bold"; 8573 case extrabold: return "demibold"; 8574 case heavy: return "black"; 8575 } 8576 } 8577 8578 /++ 8579 Loads specifically a Core X font - rendered on the X server without antialiasing. Best performance. 8580 8581 History: 8582 Added November 13, 2020. Before then, this code was integrated in the [load] function. 8583 +/ 8584 version(X11) 8585 bool loadCoreX(string name, int size = 0, FontWeight weight = FontWeight.dontcare, bool italic = false) { 8586 unload(); 8587 8588 string xfontstr; 8589 8590 if(name.length > 3 && name[0 .. 3] == "-*-") { 8591 // this is kinda a disgusting hack but if the user sends an exact 8592 // string I'd like to honor it... 8593 xfontstr = name; 8594 } else { 8595 string weightstr = weightToString(weight); 8596 string sizestr; 8597 if(size == 0) 8598 sizestr = "*"; 8599 else 8600 sizestr = toInternal!string(size); 8601 xfontstr = "-*-"~name~"-"~weightstr~"-"~(italic ? "i" : "r")~"-*-*-"~sizestr~"-*-*-*-*-*-*-*\0"; 8602 } 8603 8604 // writeln(xfontstr); 8605 8606 auto display = XDisplayConnection.get; 8607 8608 font = XLoadQueryFont(display, xfontstr.ptr); 8609 if(font is null) 8610 return false; 8611 8612 char** lol; 8613 int lol2; 8614 char* lol3; 8615 fontset = XCreateFontSet(display, xfontstr.ptr, &lol, &lol2, &lol3); 8616 8617 prepareFontInfo(); 8618 8619 return !isNull(); 8620 } 8621 8622 version(X11) 8623 private void prepareFontInfo() { 8624 if(font !is null) { 8625 isMonospace_ = stringWidth("l") == stringWidth("M"); 8626 ascent_ = font.max_bounds.ascent; 8627 descent_ = font.max_bounds.descent; 8628 } 8629 } 8630 8631 version(OSXCocoa) 8632 private void prepareFontInfo() { 8633 if(font !is null) { 8634 isMonospace_ = font.isFixedPitch; 8635 ascent_ = cast(int) font.ascender; 8636 descent_ = cast(int) - font.descender; 8637 } 8638 } 8639 8640 8641 /++ 8642 Loads a Windows font. You probably want to use [load] instead to be more generic. 8643 8644 History: 8645 Added November 13, 2020. Before then, this code was integrated in the [load] function. 8646 +/ 8647 version(Windows) 8648 bool loadWin32(string name, int size = 0, FontWeight weight = FontWeight.dontcare, bool italic = false, HDC hdc = null) { 8649 unload(); 8650 8651 WCharzBuffer buffer = WCharzBuffer(name); 8652 font = CreateFont(size, 0, 0, 0, cast(int) weight, italic, 0, 0, 0, 0, 0, 0, 0, buffer.ptr); 8653 8654 prepareFontInfo(hdc); 8655 8656 return !isNull(); 8657 } 8658 8659 version(Windows) 8660 void prepareFontInfo(HDC hdc = null) { 8661 if(font is null) 8662 return; 8663 8664 TEXTMETRIC tm; 8665 auto dc = hdc ? hdc : GetDC(null); 8666 auto orig = SelectObject(dc, font); 8667 GetTextMetrics(dc, &tm); 8668 SelectObject(dc, orig); 8669 if(hdc is null) 8670 ReleaseDC(null, dc); 8671 8672 width_ = tm.tmAveCharWidth; 8673 height_ = tm.tmHeight; 8674 ascent_ = tm.tmAscent; 8675 descent_ = tm.tmDescent; 8676 // 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. 8677 isMonospace_ = (tm.tmPitchAndFamily & TMPF_FIXED_PITCH) == 0; 8678 } 8679 8680 8681 /++ 8682 `name` is a font name, but it can also be a more complicated string parsed in an OS-specific way. 8683 8684 On X, you may prefix a name with `core:` to bypass the freetype engine causing this function to forward to [loadCoreX]. Otherwise, 8685 it calls [loadXft] if the library is available. If the library or font is not available on Xft, it falls back on [loadCoreX]. 8686 8687 On Windows, it forwards directly to [loadWin32]. 8688 8689 Params: 8690 name = font name. This is looked up by the operating system and may be interpreted differently across platforms or user machines and their preferences. 8691 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. 8692 weight = approximate boldness, results may vary. 8693 italic = try to get a slanted version of the given font. 8694 8695 History: 8696 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. 8697 +/ 8698 bool load(string name, int size = 0, FontWeight weight = FontWeight.dontcare, bool italic = false) { 8699 this.loadedInfo = LoadedInfo(name, size, weight, italic); 8700 version(X11) { 8701 version(with_xft) { 8702 if(name.length > 5 && name[0 .. 5] == "core:") { 8703 goto core; 8704 } 8705 8706 if(loadXft(name, size, weight, italic)) 8707 return true; 8708 // if xft fails, fallback to core to avoid breaking 8709 // code that already depended on this. 8710 } 8711 8712 core: 8713 8714 if(name.length > 5 && name[0 .. 5] == "core:") { 8715 name = name[5 .. $]; 8716 } 8717 8718 return loadCoreX(name, size, weight, italic); 8719 } else version(Windows) { 8720 return loadWin32(name, size, weight, italic); 8721 } else version(OSXCocoa) { 8722 return loadCocoa(name, size, weight, italic); 8723 } else static assert(0); 8724 } 8725 8726 version(OSXCocoa) 8727 bool loadCocoa(string name, int size, FontWeight weight, bool italic) { 8728 unload(); 8729 8730 font = NSFont.fontWithName(MacString(name).borrow, size); // FIXME: weight and italic? 8731 prepareFontInfo(); 8732 8733 return !isNull(); 8734 } 8735 8736 private struct LoadedInfo { 8737 string name; 8738 int size; 8739 FontWeight weight; 8740 bool italic; 8741 } 8742 private LoadedInfo loadedInfo; 8743 8744 /// 8745 void unload() { 8746 if(isNull()) 8747 return; 8748 8749 version(X11) { 8750 auto display = XDisplayConnection.display; 8751 8752 if(display is null) 8753 return; 8754 8755 version(with_xft) { 8756 if(isXft) { 8757 if(xftFont) 8758 XftFontClose(display, xftFont); 8759 isXft = false; 8760 xftFont = null; 8761 return; 8762 } 8763 } 8764 8765 if(font && font !is ScreenPainterImplementation.defaultfont) 8766 XFreeFont(display, font); 8767 if(fontset && fontset !is ScreenPainterImplementation.defaultfontset) 8768 XFreeFontSet(display, fontset); 8769 8770 font = null; 8771 fontset = null; 8772 } else version(Windows) { 8773 DeleteObject(font); 8774 font = null; 8775 } else version(OSXCocoa) { 8776 font.release(); 8777 font = null; 8778 } else static assert(0); 8779 } 8780 8781 private bool isMonospace_; 8782 8783 /++ 8784 History: 8785 Added January 16, 2021 8786 +/ 8787 bool isMonospace() { 8788 return isMonospace_; 8789 } 8790 8791 /++ 8792 Returns the average width of the font, conventionally defined as the width of the lowercase 'x' character. 8793 8794 History: 8795 Added March 26, 2020 8796 Documented January 16, 2021 8797 +/ 8798 int averageWidth() { 8799 version(X11) { 8800 return stringWidth("x"); 8801 } version(OSXCocoa) { 8802 return stringWidth("x"); 8803 } else version(Windows) 8804 return width_; 8805 else assert(0); 8806 } 8807 8808 /++ 8809 Returns the width of the string as drawn on the specified window, or the default screen if the window is null. 8810 8811 History: 8812 Added January 16, 2021 8813 +/ 8814 int stringWidth(scope const(char)[] s, SimpleWindow window = null) { 8815 // FIXME: what about tab? 8816 if(isNull) 8817 return 0; 8818 8819 version(X11) { 8820 version(with_xft) 8821 if(isXft && xftFont !is null) { 8822 //return xftFont.max_advance_width; 8823 XGlyphInfo extents; 8824 XftTextExtentsUtf8(XDisplayConnection.get, xftFont, s.ptr, cast(int) s.length, &extents); 8825 // writeln(extents); 8826 return extents.xOff; 8827 } 8828 if(font is null) 8829 return 0; 8830 else if(fontset) { 8831 XRectangle rect; 8832 Xutf8TextExtents(fontset, s.ptr, cast(int) s.length, null, &rect); 8833 8834 return rect.width; 8835 } else { 8836 return XTextWidth(font, s.ptr, cast(int) s.length); 8837 } 8838 } else version(Windows) { 8839 WCharzBuffer buffer = WCharzBuffer(s); 8840 8841 return stringWidth(buffer.slice, window); 8842 } else version(OSXCocoa) { 8843 /+ 8844 int charCount = [string length]; 8845 CGGlyph glyphs[charCount]; 8846 CGRect rects[charCount]; 8847 8848 CTFontGetGlyphsForCharacters(theCTFont, (const unichar*)[string cStringUsingEncoding:NSUnicodeStringEncoding], glyphs, charCount); 8849 CTFontGetBoundingRectsForGlyphs(theCTFont, kCTFontDefaultOrientation, glyphs, rects, charCount); 8850 8851 int totalwidth = 0, maxheight = 0; 8852 for (int i=0; i < charCount; i++) 8853 { 8854 totalwidth += rects[i].size.width; 8855 maxheight = maxheight < rects[i].size.height ? rects[i].size.height : maxheight; 8856 } 8857 8858 dim = CGSizeMake(totalwidth, maxheight); 8859 +/ 8860 8861 return 16; // FIXME 8862 } 8863 else assert(0); 8864 } 8865 8866 version(Windows) 8867 /// ditto 8868 int stringWidth(scope const(wchar)[] s, SimpleWindow window = null) { 8869 if(isNull) 8870 return 0; 8871 version(Windows) { 8872 SIZE size; 8873 8874 prepareContext(window); 8875 scope(exit) releaseContext(); 8876 8877 GetTextExtentPoint32W(dc, s.ptr, cast(int) s.length, &size); 8878 8879 return size.cx; 8880 } else { 8881 // std.conv can do this easily but it is slow to import and i don't think it is worth it 8882 static assert(0, "not implemented yet"); 8883 //return stringWidth(s, window); 8884 } 8885 } 8886 8887 private { 8888 int prepRefcount; 8889 8890 version(Windows) { 8891 HDC dc; 8892 HANDLE orig; 8893 HWND hwnd; 8894 } 8895 } 8896 /++ 8897 [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. 8898 8899 History: 8900 Added January 23, 2021 8901 +/ 8902 void prepareContext(SimpleWindow window = null) { 8903 prepRefcount++; 8904 if(prepRefcount == 1) { 8905 version(Windows) { 8906 hwnd = window is null ? null : window.impl.hwnd; 8907 dc = GetDC(hwnd); 8908 orig = SelectObject(dc, font); 8909 } 8910 } 8911 } 8912 /// ditto 8913 void releaseContext() { 8914 prepRefcount--; 8915 if(prepRefcount == 0) { 8916 version(Windows) { 8917 SelectObject(dc, orig); 8918 ReleaseDC(hwnd, dc); 8919 hwnd = null; 8920 dc = null; 8921 orig = null; 8922 } 8923 } 8924 } 8925 8926 /+ 8927 FIXME: I think I need advance and kerning pair 8928 8929 int advance(dchar from, dchar to) { } // use dchar.init for first item in string 8930 +/ 8931 8932 /++ 8933 Returns the height of the font. 8934 8935 History: 8936 Added March 26, 2020 8937 Documented January 16, 2021 8938 +/ 8939 int height() { 8940 version(X11) { 8941 version(with_xft) 8942 if(isXft && xftFont !is null) { 8943 return xftFont.ascent + xftFont.descent; // i don't use height here because it doesn't include the baseline pixel 8944 } 8945 if(font is null) 8946 return 0; 8947 return font.max_bounds.ascent + font.max_bounds.descent; 8948 } else version(Windows) { 8949 return height_; 8950 } else version(OSXCocoa) { 8951 if(font is null) 8952 return 0; 8953 return cast(int) (font.ascender + font.descender + 0.9 /* to round up */); // font.capHeight 8954 } 8955 else assert(0); 8956 } 8957 8958 private int ascent_; 8959 private int descent_; 8960 8961 /++ 8962 Max ascent above the baseline. 8963 8964 History: 8965 Added January 22, 2021 8966 +/ 8967 int ascent() { 8968 return ascent_; 8969 } 8970 8971 /++ 8972 Max descent below the baseline. 8973 8974 History: 8975 Added January 22, 2021 8976 +/ 8977 int descent() { 8978 return descent_; 8979 } 8980 8981 /++ 8982 Loads the default font used by [ScreenPainter] if none others are loaded. 8983 8984 Returns: 8985 This method mutates the `this` object, but then returns `this` for 8986 easy chaining like: 8987 8988 --- 8989 auto font = foo.isNull ? foo : foo.loadDefault 8990 --- 8991 8992 History: 8993 Added previously, but left unimplemented until January 24, 2021. 8994 +/ 8995 OperatingSystemFont loadDefault() { 8996 unload(); 8997 8998 loadedInfo = LoadedInfo.init; 8999 9000 version(X11) { 9001 // another option would be https://tronche.com/gui/x/xlib/graphics/font-metrics/XQueryFont.html 9002 // but meh since sdpy does its own thing, this should be ok too 9003 9004 ScreenPainterImplementation.ensureDefaultFontLoaded(); 9005 this.font = ScreenPainterImplementation.defaultfont; 9006 this.fontset = ScreenPainterImplementation.defaultfontset; 9007 9008 prepareFontInfo(); 9009 return this; 9010 } else version(Windows) { 9011 ScreenPainterImplementation.ensureDefaultFontLoaded(); 9012 this.font = ScreenPainterImplementation.defaultGuiFont; 9013 9014 prepareFontInfo(); 9015 return this; 9016 } else version(OSXCocoa) { 9017 this.font = NSFont.systemFontOfSize(15); 9018 9019 prepareFontInfo(); 9020 9021 // import std.stdio; writeln("Load default: ", this.height()); 9022 return this; 9023 } else throw new NotYetImplementedException(); 9024 } 9025 9026 /// 9027 bool isNull() { 9028 version(with_xft) 9029 if(isXft) 9030 return xftFont is null; 9031 return font is null; 9032 } 9033 9034 /* Metrics */ 9035 /+ 9036 GetABCWidth 9037 GetKerningPairs 9038 9039 if I do it right, I can size it all here, and match 9040 what happens when I draw the full string with the OS functions. 9041 9042 subclasses might do the same thing while getting the glyphs on images 9043 struct GlyphInfo { 9044 int glyph; 9045 9046 size_t stringIdxStart; 9047 size_t stringIdxEnd; 9048 9049 Rectangle boundingBox; 9050 } 9051 GlyphInfo[] getCharBoxes() { 9052 // XftTextExtentsUtf8 9053 return null; 9054 9055 } 9056 +/ 9057 9058 ~this() { 9059 if(!thisIsGuiThread) return; // FIXME: leaks if multithreaded gc 9060 unload(); 9061 } 9062 } 9063 9064 version(Windows) 9065 private string sliceCString(const(wchar)[] w) { 9066 return makeUtf8StringFromWindowsString(cast(wchar*) w.ptr); 9067 } 9068 9069 private inout(char)[] sliceCString(inout(char)* s) { 9070 import core.stdc.string; 9071 auto len = strlen(s); 9072 return s[0 .. len]; 9073 } 9074 9075 version(OSXCocoa) 9076 alias PaintingHandle = NSObject; 9077 else 9078 alias PaintingHandle = NativeWindowHandle; 9079 9080 /** 9081 The 2D drawing proxy. You acquire one of these with [SimpleWindow.draw] rather 9082 than constructing it directly. Then, it is reference counted so you can pass it 9083 at around and when the last ref goes out of scope, the buffered drawing activities 9084 are all carried out. 9085 9086 9087 Most functions use the outlineColor instead of taking a color themselves. 9088 ScreenPainter is reference counted and draws its buffer to the screen when its 9089 final reference goes out of scope. 9090 */ 9091 struct ScreenPainter { 9092 CapableOfBeingDrawnUpon window; 9093 this(CapableOfBeingDrawnUpon window, PaintingHandle handle, bool manualInvalidations) { 9094 this.window = window; 9095 if(window.closed) 9096 return; // null painter is now allowed so no need to throw anymore, this likely happens at the end of a program anyway 9097 //currentClipRectangle = arsd.color.Rectangle(0, 0, window.width, window.height); 9098 currentClipRectangle = arsd.color.Rectangle(short.min, short.min, short.max, short.max); 9099 if(window.activeScreenPainter !is null) { 9100 impl = window.activeScreenPainter; 9101 if(impl.referenceCount == 0) { 9102 impl.window = window; 9103 impl.create(handle); 9104 } 9105 impl.manualInvalidations = manualInvalidations; 9106 impl.referenceCount++; 9107 // writeln("refcount ++ ", impl.referenceCount); 9108 } else { 9109 impl = new ScreenPainterImplementation; 9110 impl.window = window; 9111 impl.create(handle); 9112 impl.referenceCount = 1; 9113 impl.manualInvalidations = manualInvalidations; 9114 window.activeScreenPainter = impl; 9115 // writeln("constructed"); 9116 } 9117 9118 copyActiveOriginals(); 9119 } 9120 9121 /++ 9122 EXPERIMENTAL. subject to change. 9123 9124 When you draw a cursor, you can draw this to notify your window of where it is, 9125 for IME systems to use. 9126 +/ 9127 void notifyCursorPosition(int x, int y, int width, int height) { 9128 if(auto w = cast(SimpleWindow) window) { 9129 w.setIMEPopupLocation(x + _originX + width, y + _originY + height); 9130 } 9131 } 9132 9133 /++ 9134 If you are using manual invalidations, this informs the 9135 window system that a section needs to be redrawn. 9136 9137 If you didn't opt into manual invalidation, you don't 9138 have to call this. 9139 9140 History: 9141 Added December 30, 2021 (dub v10.5) 9142 +/ 9143 void invalidateRect(Rectangle rect) { 9144 if(impl is null) return; 9145 9146 // transform(rect) 9147 rect.left += _originX; 9148 rect.right += _originX; 9149 rect.top += _originY; 9150 rect.bottom += _originY; 9151 9152 impl.invalidateRect(rect); 9153 } 9154 9155 private Pen originalPen; 9156 private Color originalFillColor; 9157 private arsd.color.Rectangle originalClipRectangle; 9158 private OperatingSystemFont originalFont; 9159 void copyActiveOriginals() { 9160 if(impl is null) return; 9161 originalPen = impl._activePen; 9162 originalFillColor = impl._fillColor; 9163 originalClipRectangle = impl._clipRectangle; 9164 version(OSXCocoa) {} else 9165 originalFont = impl._activeFont; 9166 } 9167 9168 ~this() { 9169 if(impl is null) return; 9170 impl.referenceCount--; 9171 //writeln("refcount -- ", impl.referenceCount); 9172 if(impl.referenceCount == 0) { 9173 // writeln("destructed"); 9174 impl.dispose(); 9175 *window.activeScreenPainter = ScreenPainterImplementation.init; 9176 // writeln("paint finished"); 9177 } else { 9178 // there is still an active reference, reset stuff so the 9179 // next user doesn't get weirdness via the reference 9180 this.rasterOp = RasterOp.normal; 9181 pen = originalPen; 9182 fillColor = originalFillColor; 9183 if(originalFont) 9184 setFont(originalFont); 9185 impl.setClipRectangle(originalClipRectangle.left, originalClipRectangle.top, originalClipRectangle.width, originalClipRectangle.height); 9186 } 9187 } 9188 9189 this(this) { 9190 if(impl is null) return; 9191 impl.referenceCount++; 9192 //writeln("refcount ++ ", impl.referenceCount); 9193 9194 copyActiveOriginals(); 9195 } 9196 9197 private int _originX; 9198 private int _originY; 9199 @property int originX() { return _originX; } 9200 @property int originY() { return _originY; } 9201 @property int originX(int a) { 9202 _originX = a; 9203 return _originX; 9204 } 9205 @property int originY(int a) { 9206 _originY = a; 9207 return _originY; 9208 } 9209 arsd.color.Rectangle currentClipRectangle; // set BEFORE doing any transformations 9210 private void transform(ref Point p) { 9211 if(impl is null) return; 9212 p.x += _originX; 9213 p.y += _originY; 9214 } 9215 9216 // this needs to be checked BEFORE the originX/Y transformation 9217 private bool isClipped(Point p) { 9218 return !currentClipRectangle.contains(p); 9219 } 9220 private bool isClipped(Point p, int width, int height) { 9221 return !currentClipRectangle.overlaps(arsd.color.Rectangle(p, Size(width + 1, height + 1))); 9222 } 9223 private bool isClipped(Point p, Size s) { 9224 return !currentClipRectangle.overlaps(arsd.color.Rectangle(p, Size(s.width + 1, s.height + 1))); 9225 } 9226 private bool isClipped(Point p, Point p2) { 9227 // need to ensure the end points are actually included inside, so the +1 does that 9228 return !currentClipRectangle.overlaps(arsd.color.Rectangle(p, p2 + Point(1, 1))); 9229 } 9230 9231 9232 /++ 9233 Sets the clipping region for drawing. If width == 0 && height == 0, disabled clipping. 9234 9235 Returns: 9236 The old clip rectangle. 9237 9238 History: 9239 Return value was `void` prior to May 10, 2021. 9240 9241 +/ 9242 arsd.color.Rectangle setClipRectangle(Point pt, int width, int height) { 9243 if(impl is null) return currentClipRectangle; 9244 if(pt == currentClipRectangle.upperLeft && width == currentClipRectangle.width && height == currentClipRectangle.height) 9245 return currentClipRectangle; // no need to do anything 9246 auto old = currentClipRectangle; 9247 currentClipRectangle = arsd.color.Rectangle(pt, Size(width, height)); 9248 transform(pt); 9249 9250 impl.setClipRectangle(pt.x, pt.y, width, height); 9251 9252 return old; 9253 } 9254 9255 /// ditto 9256 arsd.color.Rectangle setClipRectangle(arsd.color.Rectangle rect) { 9257 if(impl is null) return currentClipRectangle; 9258 return setClipRectangle(rect.upperLeft, rect.width, rect.height); 9259 } 9260 9261 /// 9262 void setFont(OperatingSystemFont font) { 9263 if(impl is null) return; 9264 impl.setFont(font); 9265 } 9266 9267 /// 9268 int fontHeight() { 9269 if(impl is null) return 0; 9270 return impl.fontHeight(); 9271 } 9272 9273 private Pen activePen; 9274 9275 /// 9276 @property void pen(Pen p) { 9277 if(impl is null) return; 9278 activePen = p; 9279 impl.pen(p); 9280 } 9281 9282 /// 9283 @scriptable 9284 @property void outlineColor(Color c) { 9285 if(impl is null) return; 9286 if(activePen.color == c) 9287 return; 9288 activePen.color = c; 9289 impl.pen(activePen); 9290 } 9291 9292 /// 9293 @scriptable 9294 @property void fillColor(Color c) { 9295 if(impl is null) return; 9296 impl.fillColor(c); 9297 } 9298 9299 /// 9300 @property void rasterOp(RasterOp op) { 9301 if(impl is null) return; 9302 impl.rasterOp(op); 9303 } 9304 9305 9306 void updateDisplay() { 9307 // FIXME this should do what the dtor does 9308 } 9309 9310 /// 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) 9311 void scrollArea(Point upperLeft, int width, int height, int dx, int dy) { 9312 if(impl is null) return; 9313 if(isClipped(upperLeft, width, height)) return; 9314 transform(upperLeft); 9315 version(Windows) { 9316 // http://msdn.microsoft.com/en-us/library/windows/desktop/bb787589%28v=vs.85%29.aspx 9317 RECT scroll = RECT(upperLeft.x, upperLeft.y, upperLeft.x + width, upperLeft.y + height); 9318 RECT clip = scroll; 9319 RECT uncovered; 9320 HRGN hrgn; 9321 if(!ScrollDC(impl.hdc, -dx, -dy, &scroll, &clip, hrgn, &uncovered)) 9322 throw new WindowsApiException("ScrollDC", GetLastError()); 9323 9324 } else version(X11) { 9325 // FIXME: clip stuff outside this rectangle 9326 XCopyArea(impl.display, impl.d, impl.d, impl.gc, upperLeft.x, upperLeft.y, width, height, upperLeft.x - dx, upperLeft.y - dy); 9327 } else version(OSXCocoa) { 9328 throw new NotYetImplementedException(); 9329 } else static assert(0); 9330 } 9331 9332 /// 9333 void clear(Color color = Color.white()) { 9334 if(impl is null) return; 9335 fillColor = color; 9336 outlineColor = color; 9337 drawRectangle(Point(0, 0), window.width, window.height); 9338 } 9339 9340 /++ 9341 Draws a pixmap (represented by the [Sprite] class) on the drawable. 9342 9343 Params: 9344 upperLeft = point on the window where the upper left corner of the image will be drawn 9345 imageUpperLeft = point on the image to start the slice to draw 9346 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. 9347 History: 9348 The `imageUpperLeft` and `sliceSize` parameters were added on March 11, 2021 (dub v9.3.0) 9349 +/ 9350 void drawPixmap(Sprite s, Point upperLeft, Point imageUpperLeft = Point(0, 0), Size sliceSize = Size(0, 0)) { 9351 if(impl is null) return; 9352 if(isClipped(upperLeft, s.width, s.height)) return; 9353 transform(upperLeft); 9354 impl.drawPixmap(s, upperLeft.x, upperLeft.y, imageUpperLeft.x, imageUpperLeft.y, sliceSize.width, sliceSize.height); 9355 } 9356 9357 /// 9358 void drawImage(Point upperLeft, Image i, Point upperLeftOfImage = Point(0, 0), int w = 0, int h = 0) { 9359 if(impl is null) return; 9360 //if(isClipped(upperLeft, w, h)) return; // FIXME 9361 transform(upperLeft); 9362 if(w == 0 || w > i.width) 9363 w = i.width; 9364 if(h == 0 || h > i.height) 9365 h = i.height; 9366 if(upperLeftOfImage.x < 0) 9367 upperLeftOfImage.x = 0; 9368 if(upperLeftOfImage.y < 0) 9369 upperLeftOfImage.y = 0; 9370 9371 impl.drawImage(upperLeft.x, upperLeft.y, i, upperLeftOfImage.x, upperLeftOfImage.y, w, h); 9372 } 9373 9374 /// 9375 Size textSize(in char[] text) { 9376 if(impl is null) return Size(0, 0); 9377 return impl.textSize(text); 9378 } 9379 9380 /++ 9381 Draws a string in the window with the set font (see [setFont] to change it). 9382 9383 Params: 9384 upperLeft = the upper left point of the bounding box of the text 9385 text = the string to draw 9386 lowerRight = the lower right point of the bounding box of the text. If 0, 0, there is no lower right bound. 9387 alignment = A [arsd.docs.general_concepts#bitflags|combination] of [TextAlignment] flags 9388 +/ 9389 @scriptable 9390 void drawText(Point upperLeft, in char[] text, Point lowerRight = Point(0, 0), uint alignment = 0) { 9391 if(impl is null) return; 9392 if(lowerRight.x != 0 || lowerRight.y != 0) { 9393 if(isClipped(upperLeft, lowerRight)) return; 9394 transform(lowerRight); 9395 } else { 9396 if(isClipped(upperLeft, textSize(text))) return; 9397 } 9398 transform(upperLeft); 9399 impl.drawText(upperLeft.x, upperLeft.y, lowerRight.x, lowerRight.y, text, alignment); 9400 } 9401 9402 /++ 9403 Draws text using a custom font. 9404 9405 This is still MAJOR work in progress. 9406 9407 Creating a [DrawableFont] can be tricky and require additional dependencies. 9408 +/ 9409 void drawText(DrawableFont font, Point upperLeft, in char[] text) { 9410 if(impl is null) return; 9411 if(isClipped(upperLeft, Point(int.max, int.max))) return; 9412 transform(upperLeft); 9413 font.drawString(this, upperLeft, text); 9414 } 9415 9416 version(Windows) 9417 void drawText(Point upperLeft, scope const(wchar)[] text) { 9418 if(impl is null) return; 9419 if(isClipped(upperLeft, Point(int.max, int.max))) return; 9420 transform(upperLeft); 9421 9422 if(text.length && text[$-1] == '\n') 9423 text = text[0 .. $-1]; // tailing newlines are weird on windows... 9424 9425 TextOutW(impl.hdc, upperLeft.x, upperLeft.y, text.ptr, cast(int) text.length); 9426 } 9427 9428 static struct TextDrawingContext { 9429 Point boundingBoxUpperLeft; 9430 Point boundingBoxLowerRight; 9431 9432 Point currentLocation; 9433 9434 Point lastDrewUpperLeft; 9435 Point lastDrewLowerRight; 9436 9437 // how do i do right aligned rich text? 9438 // i kinda want to do a pre-made drawing then right align 9439 // draw the whole block. 9440 // 9441 // That's exactly the diff: inline vs block stuff. 9442 9443 // I need to get coordinates of an inline section out too, 9444 // not just a bounding box, but a series of bounding boxes 9445 // should be ok. Consider what's needed to detect a click 9446 // on a link in the middle of a paragraph breaking a line. 9447 // 9448 // Generally, we should be able to get the rectangles of 9449 // any portion we draw. 9450 // 9451 // It also needs to tell what text is left if it overflows 9452 // out of the box, so we can do stuff like float images around 9453 // it. It should not attempt to draw a letter that would be 9454 // clipped. 9455 // 9456 // I might also turn off word wrap stuff. 9457 } 9458 9459 void drawText(TextDrawingContext context, in char[] text, uint alignment = 0) { 9460 if(impl is null) return; 9461 // FIXME 9462 } 9463 9464 /// Drawing an individual pixel is slow. Avoid it if possible. 9465 void drawPixel(Point where) { 9466 if(impl is null) return; 9467 if(isClipped(where)) return; 9468 transform(where); 9469 impl.drawPixel(where.x, where.y); 9470 } 9471 9472 9473 /// Draws a pen using the current pen / outlineColor 9474 @scriptable 9475 void drawLine(Point starting, Point ending) { 9476 if(impl is null) return; 9477 if(isClipped(starting, ending)) return; 9478 transform(starting); 9479 transform(ending); 9480 impl.drawLine(starting.x, starting.y, ending.x, ending.y); 9481 } 9482 9483 /// Draws a rectangle using the current pen/outline color for the border and brush/fill color for the insides 9484 /// The outer lines, inclusive of x = 0, y = 0, x = width - 1, and y = height - 1 are drawn with the outlineColor 9485 /// The rest of the pixels are drawn with the fillColor. If fillColor is transparent, those pixels are not drawn. 9486 @scriptable 9487 void drawRectangle(Point upperLeft, int width, int height) { 9488 if(impl is null) return; 9489 if(isClipped(upperLeft, width, height)) return; 9490 transform(upperLeft); 9491 impl.drawRectangle(upperLeft.x, upperLeft.y, width, height); 9492 } 9493 9494 /// ditto 9495 void drawRectangle(Point upperLeft, Size size) { 9496 if(impl is null) return; 9497 if(isClipped(upperLeft, size.width, size.height)) return; 9498 transform(upperLeft); 9499 impl.drawRectangle(upperLeft.x, upperLeft.y, size.width, size.height); 9500 } 9501 9502 /// ditto 9503 void drawRectangle(Point upperLeft, Point lowerRightInclusive) { 9504 if(impl is null) return; 9505 if(isClipped(upperLeft, lowerRightInclusive + Point(1, 1))) return; 9506 transform(upperLeft); 9507 transform(lowerRightInclusive); 9508 impl.drawRectangle(upperLeft.x, upperLeft.y, 9509 lowerRightInclusive.x - upperLeft.x + 1, lowerRightInclusive.y - upperLeft.y + 1); 9510 } 9511 9512 // overload added on May 12, 2021 9513 /// ditto 9514 void drawRectangle(Rectangle rect) { 9515 drawRectangle(rect.upperLeft, rect.size); 9516 } 9517 9518 /// Arguments are the points of the bounding rectangle 9519 void drawEllipse(Point upperLeft, Point lowerRight) { 9520 if(impl is null) return; 9521 if(isClipped(upperLeft, lowerRight)) return; 9522 transform(upperLeft); 9523 transform(lowerRight); 9524 impl.drawEllipse(upperLeft.x, upperLeft.y, lowerRight.x, lowerRight.y); 9525 } 9526 9527 /++ 9528 start and finish are units of degrees * 64 9529 9530 History: 9531 The Windows implementation didn't match the Linux implementation until September 24, 2021. 9532 9533 They still don't exactly match in outlining the arc with straight lines (Windows does, Linux doesn't for now). 9534 +/ 9535 void drawArc(Point upperLeft, int width, int height, int start, int finish) { 9536 if(impl is null) return; 9537 // FIXME: not actually implemented 9538 if(isClipped(upperLeft, width, height)) return; 9539 transform(upperLeft); 9540 impl.drawArc(upperLeft.x, upperLeft.y, width, height, start, finish); 9541 } 9542 9543 /// this function draws a circle with the drawEllipse() function above, it requires the upper left point and the radius 9544 void drawCircle(Point upperLeft, int diameter) { 9545 drawEllipse(upperLeft, Point(upperLeft.x + diameter, upperLeft.y + diameter)); 9546 } 9547 9548 /// . 9549 void drawPolygon(Point[] vertexes) { 9550 if(impl is null) return; 9551 assert(vertexes.length); 9552 int minX = int.max, minY = int.max, maxX = int.min, maxY = int.min; 9553 foreach(ref vertex; vertexes) { 9554 if(vertex.x < minX) 9555 minX = vertex.x; 9556 if(vertex.y < minY) 9557 minY = vertex.y; 9558 if(vertex.x > maxX) 9559 maxX = vertex.x; 9560 if(vertex.y > maxY) 9561 maxY = vertex.y; 9562 transform(vertex); 9563 } 9564 if(isClipped(Point(minX, maxY), Point(maxX + 1, maxY + 1))) return; 9565 impl.drawPolygon(vertexes); 9566 } 9567 9568 /// ditto 9569 void drawPolygon(Point[] vertexes...) { 9570 if(impl is null) return; 9571 drawPolygon(vertexes); 9572 } 9573 9574 9575 // and do a draw/fill in a single call maybe. Windows can do it... but X can't, though it could do two calls. 9576 9577 //mixin NativeScreenPainterImplementation!() impl; 9578 9579 9580 // HACK: if I mixin the impl directly, it won't let me override the copy 9581 // constructor! The linker complains about there being multiple definitions. 9582 // I'll make the best of it and reference count it though. 9583 ScreenPainterImplementation* impl; 9584 } 9585 9586 // HACK: I need a pointer to the implementation so it's separate 9587 struct ScreenPainterImplementation { 9588 CapableOfBeingDrawnUpon window; 9589 int referenceCount; 9590 mixin NativeScreenPainterImplementation!(); 9591 } 9592 9593 // FIXME: i haven't actually tested the sprite class on MS Windows 9594 9595 /** 9596 Sprites are optimized for fast drawing on the screen, but slow for direct pixel 9597 access. They are best for drawing a relatively unchanging image repeatedly on the screen. 9598 9599 9600 On X11, this corresponds to an `XPixmap`. On Windows, it still uses a bitmap, 9601 though I'm not sure that's ideal and the implementation might change. 9602 9603 You create one by giving a window and an image. It optimizes for that window, 9604 and copies the image into it to use as the initial picture. Creating a sprite 9605 can be quite slow (especially over a network connection) so you should do it 9606 as little as possible and just hold on to your sprite handles after making them. 9607 simpledisplay does try to do its best though, using the XSHM extension if available, 9608 but you should still write your code as if it will always be slow. 9609 9610 Then you can use `sprite.drawAt(painter, point);` to draw it, which should be 9611 a fast operation - much faster than drawing the Image itself every time. 9612 9613 `Sprite` represents a scarce resource which should be freed when you 9614 are done with it. Use the `dispose` method to do this. Do not use a `Sprite` 9615 after it has been disposed. If you are unsure about this, don't take chances, 9616 just let the garbage collector do it for you. But ideally, you can manage its 9617 lifetime more efficiently. 9618 9619 $(NOTE `Sprite`, like the rest of simpledisplay's `ScreenPainter`, does not 9620 support alpha blending in its drawing at this time. That might change in the 9621 future, but if you need alpha blending right now, use OpenGL instead. See 9622 `gamehelpers.d` for a similar class to `Sprite` that uses OpenGL: `OpenGlTexture`.) 9623 9624 Update: on April 23, 2021, I finally added alpha blending support. You must opt 9625 in by setting the enableAlpha = true in the constructor. 9626 */ 9627 class Sprite : CapableOfBeingDrawnUpon { 9628 9629 /// 9630 ScreenPainter draw() { 9631 return ScreenPainter(this, handle, false); 9632 } 9633 9634 /++ 9635 Copies the sprite's current state into a [TrueColorImage]. 9636 9637 Be warned: this can be a very slow operation 9638 9639 History: 9640 Actually implemented on March 14, 2021 9641 +/ 9642 TrueColorImage takeScreenshot() { 9643 return trueColorImageFromNativeHandle(handle, width, height); 9644 } 9645 9646 void delegate() paintingFinishedDg() { return null; } 9647 bool closed() { return false; } 9648 ScreenPainterImplementation* activeScreenPainter_; 9649 protected ScreenPainterImplementation* activeScreenPainter() { return activeScreenPainter_; } 9650 protected void activeScreenPainter(ScreenPainterImplementation* i) { activeScreenPainter_ = i; } 9651 9652 version(Windows) 9653 private ubyte* rawData; 9654 // FIXME: sprites are lost when disconnecting from X! We need some way to invalidate them... 9655 // ditto on the XPicture stuff 9656 9657 version(X11) { 9658 private static XRenderPictFormat* RGB24; 9659 private static XRenderPictFormat* ARGB32; 9660 9661 private Picture xrenderPicture; 9662 } 9663 9664 version(X11) 9665 private static void requireXRender() { 9666 if(!XRenderLibrary.loadAttempted) { 9667 XRenderLibrary.loadDynamicLibrary(); 9668 } 9669 9670 if(!XRenderLibrary.loadSuccessful) 9671 throw new Exception("XRender library load failure"); 9672 9673 auto display = XDisplayConnection.get; 9674 9675 // FIXME: if we migrate X displays, these need to be changed 9676 if(RGB24 is null) 9677 RGB24 = XRenderFindStandardFormat(display, PictStandardRGB24); 9678 if(ARGB32 is null) 9679 ARGB32 = XRenderFindStandardFormat(display, PictStandardARGB32); 9680 } 9681 9682 protected this() {} 9683 9684 this(SimpleWindow win, int width, int height, bool enableAlpha = false) { 9685 this._width = width; 9686 this._height = height; 9687 this.enableAlpha = enableAlpha; 9688 9689 version(X11) { 9690 auto display = XDisplayConnection.get(); 9691 9692 if(enableAlpha) { 9693 requireXRender(); 9694 } 9695 9696 handle = XCreatePixmap(display, cast(Drawable) win.window, width, height, enableAlpha ? 32 : DefaultDepthOfDisplay(display)); 9697 9698 if(enableAlpha) { 9699 XRenderPictureAttributes attrs; 9700 xrenderPicture = XRenderCreatePicture(display, handle, ARGB32, 0, &attrs); 9701 } 9702 } else version(Windows) { 9703 version(CRuntime_DigitalMars) { 9704 //if(enableAlpha) 9705 //throw new Exception("Alpha support not available, try recompiling with -m32mscoff"); 9706 } 9707 9708 BITMAPINFO infoheader; 9709 infoheader.bmiHeader.biSize = infoheader.bmiHeader.sizeof; 9710 infoheader.bmiHeader.biWidth = width; 9711 infoheader.bmiHeader.biHeight = height; 9712 infoheader.bmiHeader.biPlanes = 1; 9713 infoheader.bmiHeader.biBitCount = enableAlpha ? 32 : 24; 9714 infoheader.bmiHeader.biCompression = BI_RGB; 9715 9716 // FIXME: this should prolly be a device dependent bitmap... 9717 handle = CreateDIBSection( 9718 null, 9719 &infoheader, 9720 DIB_RGB_COLORS, 9721 cast(void**) &rawData, 9722 null, 9723 0); 9724 9725 if(handle is null) 9726 throw new WindowsApiException("couldn't create pixmap", GetLastError()); 9727 } 9728 } 9729 9730 /// Makes a sprite based on the image with the initial contents from the Image 9731 this(SimpleWindow win, Image i) { 9732 this(win, i.width, i.height, i.enableAlpha); 9733 9734 version(X11) { 9735 auto display = XDisplayConnection.get(); 9736 auto gc = XCreateGC(display, this.handle, 0, null); 9737 scope(exit) XFreeGC(display, gc); 9738 if(i.usingXshm) 9739 XShmPutImage(display, cast(Drawable) handle, gc, i.handle, 0, 0, 0, 0, i.width, i.height, false); 9740 else 9741 XPutImage(display, cast(Drawable) handle, gc, i.handle, 0, 0, 0, 0, i.width, i.height); 9742 } else version(Windows) { 9743 auto itemsPerLine = enableAlpha ? (4 * width) : (((cast(int) width * 3 + 3) / 4) * 4); 9744 auto arrLength = itemsPerLine * height; 9745 rawData[0..arrLength] = i.rawData[0..arrLength]; 9746 } else version(OSXCocoa) { 9747 // FIXME: I have no idea if this is even any good 9748 auto colorSpace = CGColorSpaceCreateDeviceRGB(); 9749 handle = CGBitmapContextCreate(null, width, height, 8, 4*width, 9750 colorSpace, 9751 kCGImageAlphaPremultipliedLast 9752 |kCGBitmapByteOrder32Big); 9753 CGColorSpaceRelease(colorSpace); 9754 auto rawData = CGBitmapContextGetData(handle); 9755 9756 auto rdl = (width * height * 4); 9757 rawData[0 .. rdl] = i.rawData[0 .. rdl]; 9758 } else static assert(0); 9759 } 9760 9761 /++ 9762 Draws the image on the specified painter at the specified point. The point is the upper-left point where the image will be drawn. 9763 9764 Params: 9765 where = point on the window where the upper left corner of the image will be drawn 9766 imageUpperLeft = point on the image to start the slice to draw 9767 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. 9768 History: 9769 The `imageUpperLeft` and `sliceSize` parameters were added on March 11, 2021 (dub v9.3.0) 9770 +/ 9771 void drawAt(ScreenPainter painter, Point where, Point imageUpperLeft = Point(0, 0), Size sliceSize = Size(0, 0)) { 9772 painter.drawPixmap(this, where, imageUpperLeft, sliceSize); 9773 } 9774 9775 /// Call this when you're ready to get rid of it 9776 void dispose() { 9777 version(X11) { 9778 staticDispose(xrenderPicture, handle); 9779 xrenderPicture = None; 9780 handle = None; 9781 } else version(Windows) { 9782 staticDispose(handle); 9783 handle = null; 9784 } else version(OSXCocoa) { 9785 staticDispose(handle); 9786 handle = null; 9787 } else static assert(0); 9788 9789 } 9790 9791 version(X11) 9792 static void staticDispose(Picture xrenderPicture, Pixmap handle) { 9793 if(xrenderPicture) 9794 XRenderFreePicture(XDisplayConnection.get, xrenderPicture); 9795 if(handle) 9796 XFreePixmap(XDisplayConnection.get(), handle); 9797 } 9798 else version(Windows) 9799 static void staticDispose(HBITMAP handle) { 9800 if(handle) 9801 DeleteObject(handle); 9802 } 9803 else version(OSXCocoa) 9804 static void staticDispose(CGContextRef context) { 9805 if(context) 9806 CGContextRelease(context); 9807 } 9808 9809 ~this() { 9810 version(X11) { if(xrenderPicture || handle) 9811 cleanupQueue.queue!staticDispose(xrenderPicture, handle); 9812 } else version(Windows) { if(handle) 9813 cleanupQueue.queue!staticDispose(handle); 9814 } else version(OSXCocoa) { if(handle) 9815 cleanupQueue.queue!staticDispose(handle); 9816 } else static assert(0); 9817 } 9818 9819 /// 9820 final @property int width() { return _width; } 9821 9822 /// 9823 final @property int height() { return _height; } 9824 9825 /// 9826 static Sprite fromMemoryImage(SimpleWindow win, MemoryImage img, bool enableAlpha = false) { 9827 return new Sprite(win, Image.fromMemoryImage(img, enableAlpha)); 9828 } 9829 9830 auto nativeHandle() { 9831 return handle; 9832 } 9833 9834 private: 9835 9836 int _width; 9837 int _height; 9838 bool enableAlpha; 9839 version(X11) 9840 Pixmap handle; 9841 else version(Windows) 9842 HBITMAP handle; 9843 else version(OSXCocoa) 9844 CGContextRef handle; 9845 else static assert(0); 9846 } 9847 9848 /++ 9849 Represents a display-side gradient pseudo-image. Actually construct it with [LinearGradient], [RadialGradient], or [ConicalGradient]. 9850 9851 History: 9852 Added November 20, 2021 (dub v10.4) 9853 +/ 9854 version(OSXCocoa) {} else // NotYetImplementedException 9855 abstract class Gradient : Sprite { 9856 protected this(int w, int h) { 9857 version(X11) { 9858 Sprite.requireXRender(); 9859 9860 super(); 9861 enableAlpha = true; 9862 _width = w; 9863 _height = h; 9864 } else version(Windows) { 9865 super(null, w, h, true); // on Windows i'm just making a bitmap myself 9866 } 9867 } 9868 9869 version(Windows) 9870 final void forEachPixel(scope Color delegate(int x, int y) dg) { 9871 auto ptr = rawData; 9872 foreach(j; 0 .. _height) 9873 foreach(i; 0 .. _width) { 9874 auto color = dg(i, _height - j - 1); // cuz of upside down bitmap 9875 *rawData = (color.a * color.b) / 255; rawData++; 9876 *rawData = (color.a * color.g) / 255; rawData++; 9877 *rawData = (color.a * color.r) / 255; rawData++; 9878 *rawData = color.a; rawData++; 9879 } 9880 } 9881 9882 version(X11) 9883 protected void helper(scope Stop[] stops, scope Picture delegate(scope XFixed[] stopsPositions, scope XRenderColor[] colors) dg) { 9884 assert(stops.length > 0); 9885 assert(stops.length <= 16, "I got lazy with buffers"); 9886 9887 XFixed[16] stopsPositions = void; 9888 XRenderColor[16] colors = void; 9889 9890 foreach(idx, stop; stops) { 9891 stopsPositions[idx] = cast(int)(stop.percentage * ushort.max); 9892 auto c = stop.c; 9893 colors[idx] = XRenderColor( 9894 cast(ushort)(c.r * ushort.max / 255), 9895 cast(ushort)(c.g * ushort.max / 255), 9896 cast(ushort)(c.b * ushort.max / 255), 9897 cast(ushort)(c.a * ubyte.max) // max value here is fractional 9898 ); 9899 } 9900 9901 xrenderPicture = dg(stopsPositions, colors); 9902 } 9903 9904 /// 9905 static struct Stop { 9906 float percentage; /// between 0 and 1.0 9907 Color c; 9908 } 9909 } 9910 9911 /++ 9912 Creates a linear gradient between p1 and p2. 9913 9914 X ONLY RIGHT NOW 9915 9916 History: 9917 Added November 20, 2021 (dub v10.4) 9918 9919 Bugs: 9920 Not yet implemented on Windows. 9921 +/ 9922 version(OSXCocoa) {} else // NotYetImplementedException 9923 class LinearGradient : Gradient { 9924 /++ 9925 9926 +/ 9927 this(Point p1, Point p2, Stop[] stops...) { 9928 super(p2.x, p2.y); 9929 9930 version(X11) { 9931 XLinearGradient gradient; 9932 gradient.p1 = XPointFixed(p1.x * ushort.max, p1.y * ushort.max); 9933 gradient.p2 = XPointFixed(p2.x * ushort.max, p2.y * ushort.max); 9934 9935 helper(stops, (scope XFixed[] stopsPositions, scope XRenderColor[] colors) { 9936 return XRenderCreateLinearGradient( 9937 XDisplayConnection.get, 9938 &gradient, 9939 stopsPositions.ptr, 9940 colors.ptr, 9941 cast(int) stops.length); 9942 }); 9943 } else version(Windows) { 9944 // FIXME 9945 forEachPixel((int x, int y) { 9946 import core.stdc.math; 9947 9948 //sqrtf( 9949 9950 return Color.transparent; 9951 // return Color(x * 2, y * 2, 0, 128); // this result is so beautiful 9952 }); 9953 } 9954 } 9955 } 9956 9957 /++ 9958 A conical gradient goes from color to color around a circumference from a center point. 9959 9960 X ONLY RIGHT NOW 9961 9962 History: 9963 Added November 20, 2021 (dub v10.4) 9964 9965 Bugs: 9966 Not yet implemented on Windows. 9967 +/ 9968 version(OSXCocoa) {} else // NotYetImplementedException 9969 class ConicalGradient : Gradient { 9970 /++ 9971 9972 +/ 9973 this(Point center, float angleInDegrees, Stop[] stops...) { 9974 super(center.x * 2, center.y * 2); 9975 9976 version(X11) { 9977 XConicalGradient gradient; 9978 gradient.center = XPointFixed(center.x * ushort.max, center.y * ushort.max); 9979 gradient.angle = cast(int)(angleInDegrees * ushort.max); 9980 9981 helper(stops, (scope XFixed[] stopsPositions, scope XRenderColor[] colors) { 9982 return XRenderCreateConicalGradient( 9983 XDisplayConnection.get, 9984 &gradient, 9985 stopsPositions.ptr, 9986 colors.ptr, 9987 cast(int) stops.length); 9988 }); 9989 } else version(Windows) { 9990 // FIXME 9991 forEachPixel((int x, int y) { 9992 import core.stdc.math; 9993 9994 //sqrtf( 9995 9996 return Color.transparent; 9997 // return Color(x * 2, y * 2, 0, 128); // this result is so beautiful 9998 }); 9999 10000 } 10001 } 10002 } 10003 10004 /++ 10005 A radial gradient goes from color to color based on distance from the center. 10006 It is like rings of color. 10007 10008 X ONLY RIGHT NOW 10009 10010 10011 More specifically, you create two circles: an inner circle and an outer circle. 10012 The gradient is only drawn in the area outside the inner circle but inside the outer 10013 circle. The closest line between those two circles forms the line for the gradient 10014 and the stops are calculated the same as the [LinearGradient]. Then it just sweeps around. 10015 10016 History: 10017 Added November 20, 2021 (dub v10.4) 10018 10019 Bugs: 10020 Not yet implemented on Windows. 10021 +/ 10022 version(OSXCocoa) {} else // NotYetImplementedException 10023 class RadialGradient : Gradient { 10024 /++ 10025 10026 +/ 10027 this(Point innerCenter, float innerRadius, Point outerCenter, float outerRadius, Stop[] stops...) { 10028 super(cast(int)(outerCenter.x + outerRadius + 0.5), cast(int)(outerCenter.y + outerRadius + 0.5)); 10029 10030 version(X11) { 10031 XRadialGradient gradient; 10032 gradient.inner = XCircle(innerCenter.x * ushort.max, innerCenter.y * ushort.max, cast(int) (innerRadius * ushort.max)); 10033 gradient.outer = XCircle(outerCenter.x * ushort.max, outerCenter.y * ushort.max, cast(int) (outerRadius * ushort.max)); 10034 10035 helper(stops, (scope XFixed[] stopsPositions, scope XRenderColor[] colors) { 10036 return XRenderCreateRadialGradient( 10037 XDisplayConnection.get, 10038 &gradient, 10039 stopsPositions.ptr, 10040 colors.ptr, 10041 cast(int) stops.length); 10042 }); 10043 } else version(Windows) { 10044 // FIXME 10045 forEachPixel((int x, int y) { 10046 import core.stdc.math; 10047 10048 //sqrtf( 10049 10050 return Color.transparent; 10051 // return Color(x * 2, y * 2, 0, 128); // this result is so beautiful 10052 }); 10053 } 10054 } 10055 } 10056 10057 10058 10059 /+ 10060 NOT IMPLEMENTED 10061 10062 A display-stored image optimized for relatively quick drawing, like 10063 [Sprite], but this one supports alpha channel blending and does NOT 10064 support direct drawing upon it with a [ScreenPainter]. 10065 10066 You can think of it as an [arsd.game.OpenGlTexture] for usage with a 10067 plain [ScreenPainter]... sort of. 10068 10069 On X11, it requires the Xrender extension and library. This is available 10070 almost everywhere though. 10071 10072 History: 10073 Added November 14, 2020 but NOT ACTUALLY IMPLEMENTED 10074 +/ 10075 version(none) 10076 class AlphaSprite { 10077 /++ 10078 Copies the given image into it. 10079 +/ 10080 this(MemoryImage img) { 10081 10082 if(!XRenderLibrary.loadAttempted) { 10083 XRenderLibrary.loadDynamicLibrary(); 10084 10085 // FIXME: this needs to be reconstructed when the X server changes 10086 repopulateX(); 10087 } 10088 if(!XRenderLibrary.loadSuccessful) 10089 throw new Exception("XRender library load failure"); 10090 10091 // I probably need to put the alpha mask in a separate Picture 10092 // ugh 10093 // maybe the Sprite itself can have an alpha bitmask anyway 10094 10095 10096 auto display = XDisplayConnection.get(); 10097 pixmap = XCreatePixmap(display, cast(Drawable) win.window, width, height, DefaultDepthOfDisplay(display)); 10098 10099 10100 XRenderPictureAttributes attrs; 10101 10102 handle = XRenderCreatePicture( 10103 XDisplayConnection.get, 10104 pixmap, 10105 RGBA, 10106 0, 10107 &attrs 10108 ); 10109 10110 } 10111 10112 // maybe i'll use the create gradient functions too with static factories.. 10113 10114 void drawAt(ScreenPainter painter, Point where) { 10115 //painter.drawPixmap(this, where); 10116 10117 XRenderPictureAttributes attrs; 10118 10119 auto pic = XRenderCreatePicture( 10120 XDisplayConnection.get, 10121 painter.impl.d, 10122 RGB, 10123 0, 10124 &attrs 10125 ); 10126 10127 XRenderComposite( 10128 XDisplayConnection.get, 10129 3, // PictOpOver 10130 handle, 10131 None, 10132 pic, 10133 0, // src 10134 0, 10135 0, // mask 10136 0, 10137 10, // dest 10138 10, 10139 100, // width 10140 100 10141 ); 10142 10143 /+ 10144 XRenderFreePicture( 10145 XDisplayConnection.get, 10146 pic 10147 ); 10148 10149 XRenderFreePicture( 10150 XDisplayConnection.get, 10151 fill 10152 ); 10153 +/ 10154 // on Windows you can stretch but Xrender still can't :( 10155 } 10156 10157 static XRenderPictFormat* RGB; 10158 static XRenderPictFormat* RGBA; 10159 static void repopulateX() { 10160 auto display = XDisplayConnection.get; 10161 RGB = XRenderFindStandardFormat(display, PictStandardRGB24); 10162 RGBA = XRenderFindStandardFormat(display, PictStandardARGB32); 10163 } 10164 10165 XPixmap pixmap; 10166 Picture handle; 10167 } 10168 10169 /// 10170 interface CapableOfBeingDrawnUpon { 10171 /// 10172 ScreenPainter draw(); 10173 /// 10174 int width(); 10175 /// 10176 int height(); 10177 protected ScreenPainterImplementation* activeScreenPainter(); 10178 protected void activeScreenPainter(ScreenPainterImplementation*); 10179 bool closed(); 10180 10181 void delegate() paintingFinishedDg(); 10182 10183 /// Be warned: this can be a very slow operation 10184 TrueColorImage takeScreenshot(); 10185 } 10186 10187 /// 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]. 10188 void flushGui() { 10189 version(X11) { 10190 auto dpy = XDisplayConnection.get(); 10191 XLockDisplay(dpy); 10192 scope(exit) XUnlockDisplay(dpy); 10193 XFlush(dpy); 10194 } 10195 } 10196 10197 /++ 10198 Runs the given code in the GUI thread when its event loop 10199 is available, blocking until it completes. This allows you 10200 to create and manipulate windows from another thread without 10201 invoking undefined behavior. 10202 10203 If this is the gui thread, it runs the code immediately. 10204 10205 If no gui thread exists yet, the current thread is assumed 10206 to be it. Attempting to create windows or run the event loop 10207 in any other thread will cause an assertion failure. 10208 10209 10210 $(TIP 10211 Did you know you can use UFCS on delegate literals? 10212 10213 () { 10214 // code here 10215 }.runInGuiThread; 10216 ) 10217 10218 Returns: 10219 `true` if the function was called, `false` if it was not. 10220 The function may not be called because the gui thread had 10221 already terminated by the time you called this. 10222 10223 History: 10224 Added April 10, 2020 (v7.2.0) 10225 10226 Return value added and implementation tweaked to avoid locking 10227 at program termination on February 24, 2021 (v9.2.1). 10228 +/ 10229 bool runInGuiThread(scope void delegate() dg) @trusted { 10230 claimGuiThread(); 10231 10232 if(thisIsGuiThread) { 10233 dg(); 10234 return true; 10235 } 10236 10237 if(guiThreadTerminating) 10238 return false; 10239 10240 import core.sync.semaphore; 10241 static Semaphore sc; 10242 if(sc is null) 10243 sc = new Semaphore(); 10244 10245 static RunQueueMember* rqm; 10246 if(rqm is null) 10247 rqm = new RunQueueMember; 10248 rqm.dg = cast(typeof(rqm.dg)) dg; 10249 rqm.signal = sc; 10250 rqm.thrown = null; 10251 10252 synchronized(runInGuiThreadLock) { 10253 runInGuiThreadQueue ~= rqm; 10254 } 10255 10256 if(!SimpleWindow.eventWakeUp()) 10257 throw new Error("runInGuiThread impossible; eventWakeUp failed"); 10258 10259 rqm.signal.wait(); 10260 auto t = rqm.thrown; 10261 10262 if(t) 10263 throw t; 10264 10265 return true; 10266 } 10267 10268 // note it runs sync if this is the gui thread.... 10269 void runInGuiThreadAsync(void delegate() dg, void delegate(Exception) nothrow handleError = null) nothrow { 10270 claimGuiThread(); 10271 10272 try { 10273 10274 if(thisIsGuiThread) { 10275 dg(); 10276 return; 10277 } 10278 10279 if(guiThreadTerminating) 10280 return; 10281 10282 RunQueueMember* rqm = new RunQueueMember; 10283 rqm.dg = cast(typeof(rqm.dg)) dg; 10284 rqm.signal = null; 10285 rqm.thrown = null; 10286 10287 synchronized(runInGuiThreadLock) { 10288 runInGuiThreadQueue ~= rqm; 10289 } 10290 10291 if(!SimpleWindow.eventWakeUp()) 10292 throw new Error("runInGuiThread impossible; eventWakeUp failed"); 10293 } catch(Exception e) { 10294 if(handleError) 10295 handleError(e); 10296 } 10297 } 10298 10299 private void runPendingRunInGuiThreadDelegates() { 10300 more: 10301 RunQueueMember* next; 10302 synchronized(runInGuiThreadLock) { 10303 if(runInGuiThreadQueue.length) { 10304 next = runInGuiThreadQueue[0]; 10305 runInGuiThreadQueue = runInGuiThreadQueue[1 .. $]; 10306 } else { 10307 next = null; 10308 } 10309 } 10310 10311 if(next) { 10312 try { 10313 next.dg(); 10314 next.thrown = null; 10315 } catch(Throwable t) { 10316 next.thrown = t; 10317 } 10318 10319 if(next.signal) 10320 next.signal.notify(); 10321 10322 goto more; 10323 } 10324 } 10325 10326 private void claimGuiThread() nothrow { 10327 import core.atomic; 10328 if(cas(&guiThreadExists_, false, true)) 10329 thisIsGuiThread = true; 10330 } 10331 10332 private struct RunQueueMember { 10333 void delegate() dg; 10334 import core.sync.semaphore; 10335 Semaphore signal; 10336 Throwable thrown; 10337 } 10338 10339 private __gshared RunQueueMember*[] runInGuiThreadQueue; 10340 private __gshared Object runInGuiThreadLock = new Object; // intentional CTFE 10341 private bool thisIsGuiThread = false; 10342 private shared bool guiThreadExists_ = false; 10343 private shared bool guiThreadTerminating = false; 10344 10345 /++ 10346 Returns `true` if a gui thread exists, that is, a thread running the simpledisplay.d 10347 event loop. All windows must be exclusively created and managed by a single thread. 10348 10349 If no gui thread exists, simpledisplay.d will automatically adopt the current thread 10350 when you call one of its constructors. 10351 10352 If a gui thread exists, you should check [thisThreadRunningGui] to see if it is this 10353 one. If so, you can run gui functions on it. If not, don't. The helper functions 10354 [runInGuiThread] and [runInGuiThreadAsync] can be used to help you with this automatically. 10355 10356 The reason this function is available is in case you want to message pass between a gui 10357 thread and your current thread. If no gui thread exists or if this is the gui thread, 10358 you're liable to deadlock when trying to communicate since you'd end up talking to yourself. 10359 10360 History: 10361 Added December 3, 2021 (dub v10.5) 10362 +/ 10363 public bool guiThreadExists() { 10364 return guiThreadExists_; 10365 } 10366 10367 /++ 10368 Returns `true` if this thread is either running or set to be running the 10369 simpledisplay.d gui core event loop because it owns windows. 10370 10371 It is important to keep gui-related functionality in the right thread, so you will 10372 want to `runInGuiThread` when you call them (with some specific exceptions called 10373 out in those specific functions' documentation). Notably, all windows must be 10374 created and managed only from the gui thread. 10375 10376 Will return false if simpledisplay's other functions haven't been called 10377 yet; check [guiThreadExists] in addition to this. 10378 10379 History: 10380 Added December 3, 2021 (dub v10.5) 10381 +/ 10382 public bool thisThreadRunningGui() { 10383 return thisIsGuiThread; 10384 } 10385 10386 /++ 10387 Function to help temporarily print debugging info. It will bypass any stdout/err redirection 10388 and go to the controlling tty or console (attaching to the parent and/or allocating one as 10389 needed on Windows. Please note it may overwrite output from other programs in the parent and the 10390 allocated one will not survive if your program crashes. Use the `fileOverride` to print to a log 10391 file instead if you are in one of those situations). 10392 10393 It does not support outputting very many types; just strings and ints are likely to actually work. 10394 10395 It will perform very slowly and swallows any errors that may occur. Moreover, the specific output 10396 is unspecified meaning I can change it at any time. The only point of this function is to help 10397 in temporary use for printf-style debugging. It is NOT nogc, but you can use the `debug` keyword 10398 and the compiler will cheat for you. It is, however, formally nothrow and trusted to ease its use 10399 in those contexts. 10400 10401 $(WARNING 10402 I reserve the right to change this function at any time. You can use it if it helps you 10403 but do not rely on it for anything permanent. 10404 ) 10405 10406 History: 10407 Added December 3, 2021. Not formally supported under any stable tag. 10408 +/ 10409 void sdpyPrintDebugString(string fileOverride = null, T...)(T t) nothrow @trusted { 10410 try { 10411 version(Windows) { 10412 import core.sys.windows.wincon; 10413 if(!AttachConsole(ATTACH_PARENT_PROCESS)) 10414 AllocConsole(); 10415 const(char)* fn = "CONOUT$"; 10416 } else version(Posix) { 10417 const(char)* fn = "/dev/tty"; 10418 } else static assert(0, "Function not implemented for your system"); 10419 10420 if(fileOverride.length) 10421 fn = fileOverride.ptr; 10422 10423 import core.stdc.stdio; 10424 auto fp = fopen(fn, "wt"); 10425 if(fp is null) return; 10426 scope(exit) fclose(fp); 10427 10428 string str; 10429 foreach(item; t) { 10430 static if(is(typeof(item) : const(char)[])) 10431 str ~= item; 10432 else 10433 str ~= toInternal!string(item); 10434 str ~= " "; 10435 } 10436 str ~= "\n"; 10437 10438 fwrite(str.ptr, 1, str.length, fp); 10439 fflush(fp); 10440 } catch(Exception e) { 10441 // sorry no hope 10442 } 10443 } 10444 10445 private void guiThreadFinalize() { 10446 assert(thisIsGuiThread); 10447 10448 guiThreadTerminating = true; // don't add any more from this point on 10449 runPendingRunInGuiThreadDelegates(); 10450 } 10451 10452 /+ 10453 interface IPromise { 10454 void reportProgress(int current, int max, string message); 10455 10456 /+ // not formally in cuz of templates but still 10457 IPromise Then(); 10458 IPromise Catch(); 10459 IPromise Finally(); 10460 +/ 10461 } 10462 10463 /+ 10464 auto promise = async({ ... }); 10465 promise.Then(whatever). 10466 Then(whateverelse). 10467 Catch((exception) { }); 10468 10469 10470 A promise is run inside a fiber and it looks something like: 10471 10472 try { 10473 auto res = whatever(); 10474 auto res2 = whateverelse(res); 10475 } catch(Exception e) { 10476 { }(e); 10477 } 10478 10479 When a thing succeeds, it is passed as an arg to the next 10480 +/ 10481 class Promise(T) : IPromise { 10482 auto Then() { return null; } 10483 auto Catch() { return null; } 10484 auto Finally() { return null; } 10485 10486 // wait for it to resolve and return the value, or rethrow the error if that occurred. 10487 // cannot be called from the gui thread, but this is caught at runtime instead of compile time. 10488 T await(); 10489 } 10490 10491 interface Task { 10492 } 10493 10494 interface Resolvable(T) : Task { 10495 void run(); 10496 10497 void resolve(T); 10498 10499 Resolvable!T then(void delegate(T)); // returns a new promise 10500 Resolvable!T error(Throwable); // js catch 10501 Resolvable!T completed(); // js finally 10502 10503 } 10504 10505 /++ 10506 Runs `work` in a helper thread and sends its return value back to the main gui 10507 thread as the argument to `uponCompletion`. If `work` throws, the exception is 10508 sent to the `uponThrown` if given, or if null, rethrown from the event loop to 10509 kill the program. 10510 10511 You can call reportProgress(position, max, message) to update your parent window 10512 on your progress. 10513 10514 I should also use `shared` methods. FIXME 10515 10516 History: 10517 Added March 6, 2021 (dub version 9.3). 10518 +/ 10519 void runInWorkerThread(T)(T delegate(Task) work, void delegate(T) uponCompletion) { 10520 uponCompletion(work(null)); 10521 } 10522 10523 +/ 10524 10525 /// Used internal to dispatch events to various classes. 10526 interface CapableOfHandlingNativeEvent { 10527 NativeEventHandler getNativeEventHandler(); 10528 10529 /*private*//*protected*/ __gshared CapableOfHandlingNativeEvent[NativeWindowHandle] nativeHandleMapping; 10530 10531 version(X11) { 10532 // if this is impossible, you are allowed to just throw from it 10533 // Note: if you call it from another object, set a flag cuz the manger will call you again 10534 void recreateAfterDisconnect(); 10535 // discard any *connection specific* state, but keep enough that you 10536 // can be recreated if possible. discardConnectionState() is always called immediately 10537 // before recreateAfterDisconnect(), so you can set a flag there to decide if 10538 // you need initialization order 10539 void discardConnectionState(); 10540 } 10541 } 10542 10543 version(X11) 10544 /++ 10545 State of keys on mouse events, especially motion. 10546 10547 Do not trust the actual integer values in this, they are platform-specific. Always use the names. 10548 +/ 10549 enum ModifierState : uint { 10550 shift = 1, /// 10551 capsLock = 2, /// 10552 ctrl = 4, /// 10553 alt = 8, /// Not always available on Windows 10554 windows = 64, /// ditto 10555 numLock = 16, /// 10556 10557 leftButtonDown = 256, /// these aren't available on Windows for key events, so don't use them for that unless your app is X only. 10558 middleButtonDown = 512, /// ditto 10559 rightButtonDown = 1024, /// ditto 10560 } 10561 else version(Windows) 10562 /// ditto 10563 enum ModifierState : uint { 10564 shift = 4, /// 10565 ctrl = 8, /// 10566 10567 // i'm not sure if the next two are available 10568 alt = 256, /// not always available on Windows 10569 windows = 512, /// ditto 10570 10571 capsLock = 1024, /// 10572 numLock = 2048, /// 10573 10574 leftButtonDown = 1, /// not available on key events 10575 middleButtonDown = 16, /// ditto 10576 rightButtonDown = 2, /// ditto 10577 10578 backButtonDown = 0x20, /// not available on X 10579 forwardButtonDown = 0x40, /// ditto 10580 } 10581 else version(OSXCocoa) 10582 // FIXME FIXME NotYetImplementedException 10583 enum ModifierState : uint { 10584 shift = 1, /// 10585 capsLock = 2, /// 10586 ctrl = 4, /// 10587 alt = 8, /// Not always available on Windows 10588 windows = 64, /// ditto 10589 numLock = 16, /// 10590 10591 leftButtonDown = 256, /// these aren't available on Windows for key events, so don't use them for that unless your app is X only. 10592 middleButtonDown = 512, /// ditto 10593 rightButtonDown = 1024, /// ditto 10594 } 10595 10596 /// The names assume a right-handed mouse. These are bitwise combined on the events that use them. 10597 enum MouseButton : int { 10598 none = 0, 10599 left = 1, /// 10600 right = 2, /// 10601 middle = 4, /// 10602 wheelUp = 8, /// 10603 wheelDown = 16, /// 10604 backButton = 32, /// often found on the thumb and used for back in browsers 10605 forwardButton = 64, /// often found on the thumb and used for forward in browsers 10606 } 10607 10608 /// Corresponds to the values found in MouseEvent.buttonLinear, being equal to `core.bitop.bsf(button) + 1` 10609 enum MouseButtonLinear : ubyte { 10610 left = 1, /// 10611 right, /// 10612 middle, /// 10613 wheelUp, /// 10614 wheelDown, /// 10615 backButton, /// often found on the thumb and used for back in browsers 10616 forwardButton, /// often found on the thumb and used for forward in browsers 10617 } 10618 10619 version(X11) { 10620 // FIXME: match ASCII whenever we can. Most of it is already there, 10621 // but there's a few exceptions and mismatches with Windows 10622 10623 /// Do not trust the numeric values as they are platform-specific. Always use the symbolic name. 10624 enum Key { 10625 Escape = 0xff1b, /// 10626 F1 = 0xffbe, /// 10627 F2 = 0xffbf, /// 10628 F3 = 0xffc0, /// 10629 F4 = 0xffc1, /// 10630 F5 = 0xffc2, /// 10631 F6 = 0xffc3, /// 10632 F7 = 0xffc4, /// 10633 F8 = 0xffc5, /// 10634 F9 = 0xffc6, /// 10635 F10 = 0xffc7, /// 10636 F11 = 0xffc8, /// 10637 F12 = 0xffc9, /// 10638 PrintScreen = 0xff61, /// 10639 ScrollLock = 0xff14, /// 10640 Pause = 0xff13, /// 10641 Grave = 0x60, /// The $(BACKTICK) ~ key 10642 // number keys across the top of the keyboard 10643 N1 = 0x31, /// Number key atop the keyboard 10644 N2 = 0x32, /// 10645 N3 = 0x33, /// 10646 N4 = 0x34, /// 10647 N5 = 0x35, /// 10648 N6 = 0x36, /// 10649 N7 = 0x37, /// 10650 N8 = 0x38, /// 10651 N9 = 0x39, /// 10652 N0 = 0x30, /// 10653 Dash = 0x2d, /// 10654 Equals = 0x3d, /// 10655 Backslash = 0x5c, /// The \ | key 10656 Backspace = 0xff08, /// 10657 Insert = 0xff63, /// 10658 Home = 0xff50, /// 10659 PageUp = 0xff55, /// 10660 Delete = 0xffff, /// 10661 End = 0xff57, /// 10662 PageDown = 0xff56, /// 10663 Up = 0xff52, /// 10664 Down = 0xff54, /// 10665 Left = 0xff51, /// 10666 Right = 0xff53, /// 10667 10668 Tab = 0xff09, /// 10669 Q = 0x71, /// 10670 W = 0x77, /// 10671 E = 0x65, /// 10672 R = 0x72, /// 10673 T = 0x74, /// 10674 Y = 0x79, /// 10675 U = 0x75, /// 10676 I = 0x69, /// 10677 O = 0x6f, /// 10678 P = 0x70, /// 10679 LeftBracket = 0x5b, /// the [ { key 10680 RightBracket = 0x5d, /// the ] } key 10681 CapsLock = 0xffe5, /// 10682 A = 0x61, /// 10683 S = 0x73, /// 10684 D = 0x64, /// 10685 F = 0x66, /// 10686 G = 0x67, /// 10687 H = 0x68, /// 10688 J = 0x6a, /// 10689 K = 0x6b, /// 10690 L = 0x6c, /// 10691 Semicolon = 0x3b, /// 10692 Apostrophe = 0x27, /// 10693 Enter = 0xff0d, /// 10694 Shift = 0xffe1, /// 10695 Z = 0x7a, /// 10696 X = 0x78, /// 10697 C = 0x63, /// 10698 V = 0x76, /// 10699 B = 0x62, /// 10700 N = 0x6e, /// 10701 M = 0x6d, /// 10702 Comma = 0x2c, /// 10703 Period = 0x2e, /// 10704 Slash = 0x2f, /// the / ? key 10705 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 10706 Ctrl = 0xffe3, /// 10707 Windows = 0xffeb, /// 10708 Alt = 0xffe9, /// 10709 Space = 0x20, /// 10710 Alt_r = 0xffea, /// ditto of shift_r 10711 Windows_r = 0xffec, /// 10712 Menu = 0xff67, /// 10713 Ctrl_r = 0xffe4, /// 10714 10715 NumLock = 0xff7f, /// 10716 Divide = 0xffaf, /// The / key on the number pad 10717 Multiply = 0xffaa, /// The * key on the number pad 10718 Minus = 0xffad, /// The - key on the number pad 10719 Plus = 0xffab, /// The + key on the number pad 10720 PadEnter = 0xff8d, /// Numberpad enter key 10721 Pad1 = 0xff9c, /// Numberpad keys 10722 Pad2 = 0xff99, /// 10723 Pad3 = 0xff9b, /// 10724 Pad4 = 0xff96, /// 10725 Pad5 = 0xff9d, /// 10726 Pad6 = 0xff98, /// 10727 Pad7 = 0xff95, /// 10728 Pad8 = 0xff97, /// 10729 Pad9 = 0xff9a, /// 10730 Pad0 = 0xff9e, /// 10731 PadDot = 0xff9f, /// 10732 } 10733 } else version(Windows) { 10734 // the character here is for en-us layouts and for illustration only 10735 // if you actually want to get characters, wait for character events 10736 // (the argument to your event handler is simply a dchar) 10737 // those will be converted by the OS for the right locale. 10738 10739 enum Key { 10740 Escape = 0x1b, 10741 F1 = 0x70, 10742 F2 = 0x71, 10743 F3 = 0x72, 10744 F4 = 0x73, 10745 F5 = 0x74, 10746 F6 = 0x75, 10747 F7 = 0x76, 10748 F8 = 0x77, 10749 F9 = 0x78, 10750 F10 = 0x79, 10751 F11 = 0x7a, 10752 F12 = 0x7b, 10753 PrintScreen = 0x2c, 10754 ScrollLock = 0x91, 10755 Pause = 0x13, 10756 Grave = 0xc0, 10757 // number keys across the top of the keyboard 10758 N1 = 0x31, 10759 N2 = 0x32, 10760 N3 = 0x33, 10761 N4 = 0x34, 10762 N5 = 0x35, 10763 N6 = 0x36, 10764 N7 = 0x37, 10765 N8 = 0x38, 10766 N9 = 0x39, 10767 N0 = 0x30, 10768 Dash = 0xbd, 10769 Equals = 0xbb, 10770 Backslash = 0xdc, 10771 Backspace = 0x08, 10772 Insert = 0x2d, 10773 Home = 0x24, 10774 PageUp = 0x21, 10775 Delete = 0x2e, 10776 End = 0x23, 10777 PageDown = 0x22, 10778 Up = 0x26, 10779 Down = 0x28, 10780 Left = 0x25, 10781 Right = 0x27, 10782 10783 Tab = 0x09, 10784 Q = 0x51, 10785 W = 0x57, 10786 E = 0x45, 10787 R = 0x52, 10788 T = 0x54, 10789 Y = 0x59, 10790 U = 0x55, 10791 I = 0x49, 10792 O = 0x4f, 10793 P = 0x50, 10794 LeftBracket = 0xdb, 10795 RightBracket = 0xdd, 10796 CapsLock = 0x14, 10797 A = 0x41, 10798 S = 0x53, 10799 D = 0x44, 10800 F = 0x46, 10801 G = 0x47, 10802 H = 0x48, 10803 J = 0x4a, 10804 K = 0x4b, 10805 L = 0x4c, 10806 Semicolon = 0xba, 10807 Apostrophe = 0xde, 10808 Enter = 0x0d, 10809 Shift = 0x10, 10810 Z = 0x5a, 10811 X = 0x58, 10812 C = 0x43, 10813 V = 0x56, 10814 B = 0x42, 10815 N = 0x4e, 10816 M = 0x4d, 10817 Comma = 0xbc, 10818 Period = 0xbe, 10819 Slash = 0xbf, 10820 Shift_r = 0xa1, // FIXME Note: this isn't sent on all computers, sometimes it just sends Shift, so don't rely on it 10821 Ctrl = 0x11, 10822 Windows = 0x5b, 10823 Alt = -5, // FIXME 10824 Space = 0x20, 10825 Alt_r = 0xffea, // ditto of shift_r 10826 Windows_r = 0x5c, // ditto of shift_r 10827 Menu = 0x5d, 10828 Ctrl_r = 0xa3, // ditto of shift_r 10829 10830 NumLock = 0x90, 10831 Divide = 0x6f, 10832 Multiply = 0x6a, 10833 Minus = 0x6d, 10834 Plus = 0x6b, 10835 PadEnter = -8, // FIXME 10836 Pad1 = 0x61, 10837 Pad2 = 0x62, 10838 Pad3 = 0x63, 10839 Pad4 = 0x64, 10840 Pad5 = 0x65, 10841 Pad6 = 0x66, 10842 Pad7 = 0x67, 10843 Pad8 = 0x68, 10844 Pad9 = 0x69, 10845 Pad0 = 0x60, 10846 PadDot = 0x6e, 10847 } 10848 10849 // I'm keeping this around for reference purposes 10850 // ideally all these buttons will be listed for all platforms, 10851 // but now now I'm just focusing on my US keyboard 10852 version(none) 10853 enum Key { 10854 LBUTTON = 0x01, 10855 RBUTTON = 0x02, 10856 CANCEL = 0x03, 10857 MBUTTON = 0x04, 10858 //static if (_WIN32_WINNT > = 0x500) { 10859 XBUTTON1 = 0x05, 10860 XBUTTON2 = 0x06, 10861 //} 10862 BACK = 0x08, 10863 TAB = 0x09, 10864 CLEAR = 0x0C, 10865 RETURN = 0x0D, 10866 SHIFT = 0x10, 10867 CONTROL = 0x11, 10868 MENU = 0x12, 10869 PAUSE = 0x13, 10870 CAPITAL = 0x14, 10871 KANA = 0x15, 10872 HANGEUL = 0x15, 10873 HANGUL = 0x15, 10874 JUNJA = 0x17, 10875 FINAL = 0x18, 10876 HANJA = 0x19, 10877 KANJI = 0x19, 10878 ESCAPE = 0x1B, 10879 CONVERT = 0x1C, 10880 NONCONVERT = 0x1D, 10881 ACCEPT = 0x1E, 10882 MODECHANGE = 0x1F, 10883 SPACE = 0x20, 10884 PRIOR = 0x21, 10885 NEXT = 0x22, 10886 END = 0x23, 10887 HOME = 0x24, 10888 LEFT = 0x25, 10889 UP = 0x26, 10890 RIGHT = 0x27, 10891 DOWN = 0x28, 10892 SELECT = 0x29, 10893 PRINT = 0x2A, 10894 EXECUTE = 0x2B, 10895 SNAPSHOT = 0x2C, 10896 INSERT = 0x2D, 10897 DELETE = 0x2E, 10898 HELP = 0x2F, 10899 LWIN = 0x5B, 10900 RWIN = 0x5C, 10901 APPS = 0x5D, 10902 SLEEP = 0x5F, 10903 NUMPAD0 = 0x60, 10904 NUMPAD1 = 0x61, 10905 NUMPAD2 = 0x62, 10906 NUMPAD3 = 0x63, 10907 NUMPAD4 = 0x64, 10908 NUMPAD5 = 0x65, 10909 NUMPAD6 = 0x66, 10910 NUMPAD7 = 0x67, 10911 NUMPAD8 = 0x68, 10912 NUMPAD9 = 0x69, 10913 MULTIPLY = 0x6A, 10914 ADD = 0x6B, 10915 SEPARATOR = 0x6C, 10916 SUBTRACT = 0x6D, 10917 DECIMAL = 0x6E, 10918 DIVIDE = 0x6F, 10919 F1 = 0x70, 10920 F2 = 0x71, 10921 F3 = 0x72, 10922 F4 = 0x73, 10923 F5 = 0x74, 10924 F6 = 0x75, 10925 F7 = 0x76, 10926 F8 = 0x77, 10927 F9 = 0x78, 10928 F10 = 0x79, 10929 F11 = 0x7A, 10930 F12 = 0x7B, 10931 F13 = 0x7C, 10932 F14 = 0x7D, 10933 F15 = 0x7E, 10934 F16 = 0x7F, 10935 F17 = 0x80, 10936 F18 = 0x81, 10937 F19 = 0x82, 10938 F20 = 0x83, 10939 F21 = 0x84, 10940 F22 = 0x85, 10941 F23 = 0x86, 10942 F24 = 0x87, 10943 NUMLOCK = 0x90, 10944 SCROLL = 0x91, 10945 LSHIFT = 0xA0, 10946 RSHIFT = 0xA1, 10947 LCONTROL = 0xA2, 10948 RCONTROL = 0xA3, 10949 LMENU = 0xA4, 10950 RMENU = 0xA5, 10951 //static if (_WIN32_WINNT > = 0x500) { 10952 BROWSER_BACK = 0xA6, 10953 BROWSER_FORWARD = 0xA7, 10954 BROWSER_REFRESH = 0xA8, 10955 BROWSER_STOP = 0xA9, 10956 BROWSER_SEARCH = 0xAA, 10957 BROWSER_FAVORITES = 0xAB, 10958 BROWSER_HOME = 0xAC, 10959 VOLUME_MUTE = 0xAD, 10960 VOLUME_DOWN = 0xAE, 10961 VOLUME_UP = 0xAF, 10962 MEDIA_NEXT_TRACK = 0xB0, 10963 MEDIA_PREV_TRACK = 0xB1, 10964 MEDIA_STOP = 0xB2, 10965 MEDIA_PLAY_PAUSE = 0xB3, 10966 LAUNCH_MAIL = 0xB4, 10967 LAUNCH_MEDIA_SELECT = 0xB5, 10968 LAUNCH_APP1 = 0xB6, 10969 LAUNCH_APP2 = 0xB7, 10970 //} 10971 OEM_1 = 0xBA, 10972 //static if (_WIN32_WINNT > = 0x500) { 10973 OEM_PLUS = 0xBB, 10974 OEM_COMMA = 0xBC, 10975 OEM_MINUS = 0xBD, 10976 OEM_PERIOD = 0xBE, 10977 //} 10978 OEM_2 = 0xBF, 10979 OEM_3 = 0xC0, 10980 OEM_4 = 0xDB, 10981 OEM_5 = 0xDC, 10982 OEM_6 = 0xDD, 10983 OEM_7 = 0xDE, 10984 OEM_8 = 0xDF, 10985 //static if (_WIN32_WINNT > = 0x500) { 10986 OEM_102 = 0xE2, 10987 //} 10988 PROCESSKEY = 0xE5, 10989 //static if (_WIN32_WINNT > = 0x500) { 10990 PACKET = 0xE7, 10991 //} 10992 ATTN = 0xF6, 10993 CRSEL = 0xF7, 10994 EXSEL = 0xF8, 10995 EREOF = 0xF9, 10996 PLAY = 0xFA, 10997 ZOOM = 0xFB, 10998 NONAME = 0xFC, 10999 PA1 = 0xFD, 11000 OEM_CLEAR = 0xFE, 11001 } 11002 11003 } else version(OSXCocoa) { 11004 enum Key { 11005 Escape = 53, 11006 F1 = 122, 11007 F2 = 120, 11008 F3 = 99, 11009 F4 = 118, 11010 F5 = 96, 11011 F6 = 97, 11012 F7 = 98, 11013 F8 = 100, 11014 F9 = 101, 11015 F10 = 109, 11016 F11 = 103, 11017 F12 = 111, 11018 PrintScreen = 105, 11019 ScrollLock = 107, 11020 Pause = 113, 11021 Grave = 50, 11022 // number keys across the top of the keyboard 11023 N1 = 18, 11024 N2 = 19, 11025 N3 = 20, 11026 N4 = 21, 11027 N5 = 23, 11028 N6 = 22, 11029 N7 = 26, 11030 N8 = 28, 11031 N9 = 25, 11032 N0 = 29, 11033 Dash = 27, 11034 Equals = 24, 11035 Backslash = 42, 11036 Backspace = 51, 11037 Insert = 114, 11038 Home = 115, 11039 PageUp = 116, 11040 Delete = 117, 11041 End = 119, 11042 PageDown = 121, 11043 Up = 126, 11044 Down = 125, 11045 Left = 123, 11046 Right = 124, 11047 11048 Tab = 48, 11049 Q = 12, 11050 W = 13, 11051 E = 14, 11052 R = 15, 11053 T = 17, 11054 Y = 16, 11055 U = 32, 11056 I = 34, 11057 O = 31, 11058 P = 35, 11059 LeftBracket = 33, 11060 RightBracket = 30, 11061 CapsLock = 57, 11062 A = 0, 11063 S = 1, 11064 D = 2, 11065 F = 3, 11066 G = 5, 11067 H = 4, 11068 J = 38, 11069 K = 40, 11070 L = 37, 11071 Semicolon = 41, 11072 Apostrophe = 39, 11073 Enter = 36, 11074 Shift = 56, 11075 Z = 6, 11076 X = 7, 11077 C = 8, 11078 V = 9, 11079 B = 11, 11080 N = 45, 11081 M = 46, 11082 Comma = 43, 11083 Period = 47, 11084 Slash = 44, 11085 Shift_r = -4, // FIXME Note: this isn't sent on all computers, sometimes it just sends Shift, so don't rely on it 11086 Ctrl = 59, 11087 Windows = 55, 11088 Alt = 58, 11089 Space = 49, 11090 Alt_r = -3, // ditto of shift_r 11091 Windows_r = -2, 11092 Menu = 110, 11093 Ctrl_r = -1, 11094 11095 NumLock = 1, 11096 Divide = 75, 11097 Multiply = 67, 11098 Minus = 78, 11099 Plus = 69, 11100 PadEnter = 76, 11101 Pad1 = 83, 11102 Pad2 = 84, 11103 Pad3 = 85, 11104 Pad4 = 86, 11105 Pad5 = 87, 11106 Pad6 = 88, 11107 Pad7 = 89, 11108 Pad8 = 91, 11109 Pad9 = 92, 11110 Pad0 = 82, 11111 PadDot = 65, 11112 } 11113 11114 } 11115 11116 /* Additional utilities */ 11117 11118 11119 Color fromHsl(real h, real s, real l) { 11120 return arsd.color.fromHsl([h,s,l]); 11121 } 11122 11123 11124 11125 /* ********** What follows is the system-specific implementations *********/ 11126 version(Windows) { 11127 11128 11129 // helpers for making HICONs from MemoryImages 11130 class WindowsIcon { 11131 struct Win32Icon { 11132 align(1): 11133 uint biSize; 11134 int biWidth; 11135 int biHeight; 11136 ushort biPlanes; 11137 ushort biBitCount; 11138 uint biCompression; 11139 uint biSizeImage; 11140 int biXPelsPerMeter; 11141 int biYPelsPerMeter; 11142 uint biClrUsed; 11143 uint biClrImportant; 11144 // RGBQUAD[colorCount] biColors; 11145 /* Pixels: 11146 Uint8 pixels[] 11147 */ 11148 /* Mask: 11149 Uint8 mask[] 11150 */ 11151 } 11152 11153 ubyte[] fromMemoryImage(MemoryImage mi, out int icon_len, out int width, out int height) { 11154 11155 assert(mi.width <= 256, "image too wide"); 11156 assert(mi.height <= 256, "image too tall"); 11157 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 11158 assert(mi.height % 4 == 0, "image not multiple of 4 height"); 11159 11160 int icon_plen = mi.width * mi.height * 4; 11161 int icon_mlen = mi.width * mi.height / 8; 11162 11163 int colorCount = 0; 11164 icon_len = 40 + icon_plen + icon_mlen + cast(int) RGBQUAD.sizeof * colorCount; 11165 11166 ubyte[] memory = new ubyte[](Win32Icon.sizeof + icon_plen + icon_mlen); 11167 Win32Icon* icon_win32 = cast(Win32Icon*) memory.ptr; 11168 11169 auto data = memory[Win32Icon.sizeof .. $]; 11170 11171 width = mi.width; 11172 height = mi.height; 11173 11174 auto trueColorImage = mi.getAsTrueColorImage(); 11175 11176 icon_win32.biSize = 40; 11177 icon_win32.biWidth = mi.width; 11178 icon_win32.biHeight = mi.height*2; 11179 icon_win32.biPlanes = 1; 11180 icon_win32.biBitCount = 32; 11181 icon_win32.biSizeImage = icon_plen + icon_mlen; 11182 11183 int offset = 0; 11184 int andOff = icon_plen * 8; // the and offset is in bits 11185 11186 // leaving the and mask as the default 0 so the rgba alpha blend 11187 // does its thing instead 11188 for(int y = height - 1; y >= 0; y--) { 11189 int off2 = y * width * 4; 11190 foreach(x; 0 .. width) { 11191 data[offset + 2] = trueColorImage.imageData.bytes[off2 + 0]; 11192 data[offset + 1] = trueColorImage.imageData.bytes[off2 + 1]; 11193 data[offset + 0] = trueColorImage.imageData.bytes[off2 + 2]; 11194 data[offset + 3] = trueColorImage.imageData.bytes[off2 + 3]; 11195 11196 offset += 4; 11197 off2 += 4; 11198 } 11199 } 11200 11201 return memory; 11202 } 11203 11204 this(MemoryImage mi) { 11205 int icon_len, width, height; 11206 11207 auto icon_win32 = fromMemoryImage(mi, icon_len, width, height); 11208 11209 /* 11210 PNG* png = readPnpngData); 11211 PNGHeader pngh = getHeader(png); 11212 void* icon_win32; 11213 if(pngh.depth == 4) { 11214 auto i = new Win32Icon!(16); 11215 i.fromPNG(png, pngh, icon_len, width, height); 11216 icon_win32 = i; 11217 } 11218 else if(pngh.depth == 8) { 11219 auto i = new Win32Icon!(256); 11220 i.fromPNG(png, pngh, icon_len, width, height); 11221 icon_win32 = i; 11222 } else assert(0); 11223 */ 11224 11225 hIcon = CreateIconFromResourceEx(icon_win32.ptr, icon_len, true, 0x00030000, width, height, 0); 11226 11227 if(hIcon is null) throw new WindowsApiException("CreateIconFromResourceEx", GetLastError()); 11228 } 11229 11230 ~this() { 11231 if(!thisIsGuiThread) return; // FIXME: leaks if multithreaded gc 11232 DestroyIcon(hIcon); 11233 } 11234 11235 HICON hIcon; 11236 } 11237 11238 11239 11240 11241 11242 11243 alias int delegate(HWND, UINT, WPARAM, LPARAM, out int) NativeEventHandler; 11244 alias HWND NativeWindowHandle; 11245 11246 extern(Windows) 11247 LRESULT WndProc(HWND hWnd, UINT iMessage, WPARAM wParam, LPARAM lParam) nothrow { 11248 try { 11249 if(SimpleWindow.handleNativeGlobalEvent !is null) { 11250 // it returns zero if the message is handled, so we won't do anything more there 11251 // do I like that though? 11252 int mustReturn; 11253 auto ret = SimpleWindow.handleNativeGlobalEvent(hWnd, iMessage, wParam, lParam, mustReturn); 11254 if(mustReturn) 11255 return ret; 11256 } 11257 11258 if(auto window = hWnd in CapableOfHandlingNativeEvent.nativeHandleMapping) { 11259 if(window.getNativeEventHandler !is null) { 11260 int mustReturn; 11261 auto ret = window.getNativeEventHandler()(hWnd, iMessage, wParam, lParam, mustReturn); 11262 if(mustReturn) 11263 return ret; 11264 } 11265 if(auto w = cast(SimpleWindow) (*window)) 11266 return w.windowProcedure(hWnd, iMessage, wParam, lParam); 11267 else 11268 return DefWindowProc(hWnd, iMessage, wParam, lParam); 11269 } else { 11270 return DefWindowProc(hWnd, iMessage, wParam, lParam); 11271 } 11272 } catch (Exception e) { 11273 try { 11274 sdpy_abort(e); 11275 return 0; 11276 } catch(Exception e) { assert(0); } 11277 } 11278 } 11279 11280 void sdpy_abort(Throwable e) nothrow { 11281 try 11282 MessageBoxA(null, (e.toString() ~ "\0").ptr, "Exception caught in WndProc", 0); 11283 catch(Exception e) 11284 MessageBoxA(null, "Exception.toString threw too!", "Exception caught in WndProc", 0); 11285 ExitProcess(1); 11286 } 11287 11288 mixin template NativeScreenPainterImplementation() { 11289 HDC hdc; 11290 HWND hwnd; 11291 //HDC windowHdc; 11292 HBITMAP oldBmp; 11293 11294 void create(PaintingHandle window) { 11295 hwnd = window; 11296 11297 if(auto sw = cast(SimpleWindow) this.window) { 11298 // drawing on a window, double buffer 11299 auto windowHdc = GetDC(hwnd); 11300 11301 auto buffer = sw.impl.buffer; 11302 if(buffer is null) { 11303 hdc = windowHdc; 11304 windowDc = true; 11305 } else { 11306 hdc = CreateCompatibleDC(windowHdc); 11307 11308 ReleaseDC(hwnd, windowHdc); 11309 11310 oldBmp = SelectObject(hdc, buffer); 11311 } 11312 } else { 11313 // drawing on something else, draw directly 11314 hdc = CreateCompatibleDC(null); 11315 SelectObject(hdc, window); 11316 } 11317 11318 // X doesn't draw a text background, so neither should we 11319 SetBkMode(hdc, TRANSPARENT); 11320 11321 ensureDefaultFontLoaded(); 11322 11323 if(defaultGuiFont) { 11324 SelectObject(hdc, defaultGuiFont); 11325 // DeleteObject(defaultGuiFont); 11326 } 11327 } 11328 11329 static HFONT defaultGuiFont; 11330 static void ensureDefaultFontLoaded() { 11331 static bool triedDefaultGuiFont = false; 11332 if(!triedDefaultGuiFont) { 11333 NONCLIENTMETRICS params; 11334 params.cbSize = params.sizeof; 11335 if(SystemParametersInfo(SPI_GETNONCLIENTMETRICS, params.sizeof, ¶ms, 0)) { 11336 defaultGuiFont = CreateFontIndirect(¶ms.lfMessageFont); 11337 } 11338 triedDefaultGuiFont = true; 11339 } 11340 } 11341 11342 private OperatingSystemFont _activeFont; 11343 11344 void setFont(OperatingSystemFont font) { 11345 _activeFont = font; 11346 if(font && font.font) { 11347 if(SelectObject(hdc, font.font) == HGDI_ERROR) { 11348 // error... how to handle tho? 11349 } else { 11350 11351 } 11352 } 11353 else if(defaultGuiFont) 11354 SelectObject(hdc, defaultGuiFont); 11355 } 11356 11357 arsd.color.Rectangle _clipRectangle; 11358 11359 void setClipRectangle(int x, int y, int width, int height) { 11360 auto old = _clipRectangle; 11361 _clipRectangle = arsd.color.Rectangle(Point(x, y), Size(width, height)); 11362 if(old == _clipRectangle) 11363 return; 11364 11365 if(width == 0 || height == 0) { 11366 SelectClipRgn(hdc, null); 11367 } else { 11368 auto region = CreateRectRgn(x, y, x + width, y + height); 11369 SelectClipRgn(hdc, region); 11370 DeleteObject(region); 11371 } 11372 } 11373 11374 11375 // just because we can on Windows... 11376 //void create(Image image); 11377 11378 void invalidateRect(Rectangle invalidRect) { 11379 RECT rect; 11380 rect.left = invalidRect.left; 11381 rect.right = invalidRect.right; 11382 rect.top = invalidRect.top; 11383 rect.bottom = invalidRect.bottom; 11384 InvalidateRect(hwnd, &rect, false); 11385 } 11386 bool manualInvalidations; 11387 11388 void dispose() { 11389 // FIXME: this.window.width/height is probably wrong 11390 // BitBlt(windowHdc, 0, 0, this.window.width, this.window.height, hdc, 0, 0, SRCCOPY); 11391 // ReleaseDC(hwnd, windowHdc); 11392 11393 // FIXME: it shouldn't invalidate the whole thing in all cases... it would be ideal to do this right 11394 if(cast(SimpleWindow) this.window) { 11395 if(!manualInvalidations) 11396 InvalidateRect(hwnd, cast(RECT*)null, false); // no need to erase bg as the whole thing gets bitblt'd ove 11397 } 11398 11399 if(originalPen !is null) 11400 SelectObject(hdc, originalPen); 11401 if(currentPen !is null) 11402 DeleteObject(currentPen); 11403 if(originalBrush !is null) 11404 SelectObject(hdc, originalBrush); 11405 if(currentBrush !is null) 11406 DeleteObject(currentBrush); 11407 11408 SelectObject(hdc, oldBmp); 11409 11410 if(windowDc) 11411 ReleaseDC(hwnd, hdc); 11412 else 11413 DeleteDC(hdc); 11414 11415 if(window.paintingFinishedDg !is null) 11416 window.paintingFinishedDg()(); 11417 } 11418 11419 bool windowDc; 11420 HPEN originalPen; 11421 HPEN currentPen; 11422 11423 Pen _activePen; 11424 11425 Color _outlineColor; 11426 11427 @property void pen(Pen p) { 11428 _activePen = p; 11429 _outlineColor = p.color; 11430 11431 HPEN pen; 11432 if(p.color.a == 0) { 11433 pen = GetStockObject(NULL_PEN); 11434 } else { 11435 int style = PS_SOLID; 11436 final switch(p.style) { 11437 case Pen.Style.Solid: 11438 style = PS_SOLID; 11439 break; 11440 case Pen.Style.Dashed: 11441 style = PS_DASH; 11442 break; 11443 case Pen.Style.Dotted: 11444 style = PS_DOT; 11445 break; 11446 } 11447 pen = CreatePen(style, p.width, RGB(p.color.r, p.color.g, p.color.b)); 11448 } 11449 auto orig = SelectObject(hdc, pen); 11450 if(originalPen is null) 11451 originalPen = orig; 11452 11453 if(currentPen !is null) 11454 DeleteObject(currentPen); 11455 11456 currentPen = pen; 11457 11458 // the outline is like a foreground since it's done that way on X 11459 SetTextColor(hdc, RGB(p.color.r, p.color.g, p.color.b)); 11460 11461 } 11462 11463 @property void rasterOp(RasterOp op) { 11464 int mode; 11465 final switch(op) { 11466 case RasterOp.normal: 11467 mode = R2_COPYPEN; 11468 break; 11469 case RasterOp.xor: 11470 mode = R2_XORPEN; 11471 break; 11472 } 11473 SetROP2(hdc, mode); 11474 } 11475 11476 HBRUSH originalBrush; 11477 HBRUSH currentBrush; 11478 Color _fillColor = Color(1, 1, 1, 1); // what are the odds that they'd set this?? 11479 @property void fillColor(Color c) { 11480 if(c == _fillColor) 11481 return; 11482 _fillColor = c; 11483 HBRUSH brush; 11484 if(c.a == 0) { 11485 brush = GetStockObject(HOLLOW_BRUSH); 11486 } else { 11487 brush = CreateSolidBrush(RGB(c.r, c.g, c.b)); 11488 } 11489 auto orig = SelectObject(hdc, brush); 11490 if(originalBrush is null) 11491 originalBrush = orig; 11492 11493 if(currentBrush !is null) 11494 DeleteObject(currentBrush); 11495 11496 currentBrush = brush; 11497 11498 // background color is NOT set because X doesn't draw text backgrounds 11499 // SetBkColor(hdc, RGB(255, 255, 255)); 11500 } 11501 11502 void drawImage(int x, int y, Image i, int ix, int iy, int w, int h) { 11503 BITMAP bm; 11504 11505 HDC hdcMem = CreateCompatibleDC(hdc); 11506 HBITMAP hbmOld = SelectObject(hdcMem, i.handle); 11507 11508 GetObject(i.handle, bm.sizeof, &bm); 11509 11510 // or should I AlphaBlend!??!?! 11511 BitBlt(hdc, x, y, w /* bm.bmWidth */, /*bm.bmHeight*/ h, hdcMem, ix, iy, SRCCOPY); 11512 11513 SelectObject(hdcMem, hbmOld); 11514 DeleteDC(hdcMem); 11515 } 11516 11517 void drawPixmap(Sprite s, int x, int y, int ix, int iy, int w, int h) { 11518 BITMAP bm; 11519 11520 HDC hdcMem = CreateCompatibleDC(hdc); 11521 HBITMAP hbmOld = SelectObject(hdcMem, s.handle); 11522 11523 GetObject(s.handle, bm.sizeof, &bm); 11524 11525 version(CRuntime_DigitalMars) goto noalpha; 11526 11527 // or should I AlphaBlend!??!?! note it is supposed to be premultiplied http://www.fengyuan.com/article/alphablend.html 11528 if(s.enableAlpha) { 11529 auto dw = w ? w : bm.bmWidth; 11530 auto dh = h ? h : bm.bmHeight; 11531 BLENDFUNCTION bf; 11532 bf.BlendOp = AC_SRC_OVER; 11533 bf.SourceConstantAlpha = 255; 11534 bf.AlphaFormat = AC_SRC_ALPHA; 11535 AlphaBlend(hdc, x, y, dw, dh, hdcMem, ix, iy, dw, dh, bf); 11536 } else { 11537 noalpha: 11538 BitBlt(hdc, x, y, w ? w : bm.bmWidth, h ? h : bm.bmHeight, hdcMem, ix, iy, SRCCOPY); 11539 } 11540 11541 SelectObject(hdcMem, hbmOld); 11542 DeleteDC(hdcMem); 11543 } 11544 11545 Size textSize(scope const(char)[] text) { 11546 bool dummyX; 11547 if(text.length == 0) { 11548 text = " "; 11549 dummyX = true; 11550 } 11551 RECT rect; 11552 WCharzBuffer buffer = WCharzBuffer(text); 11553 DrawTextW(hdc, buffer.ptr, cast(int) buffer.length, &rect, DT_CALCRECT | DT_NOPREFIX); 11554 return Size(dummyX ? 0 : rect.right, rect.bottom); 11555 } 11556 11557 void drawText(int x, int y, int x2, int y2, scope const(char)[] text, uint alignment) { 11558 if(text.length && text[$-1] == '\n') 11559 text = text[0 .. $-1]; // tailing newlines are weird on windows... 11560 if(text.length && text[$-1] == '\r') 11561 text = text[0 .. $-1]; 11562 11563 WCharzBuffer buffer = WCharzBuffer(text, WindowsStringConversionFlags.convertNewLines); 11564 if(x2 == 0 && y2 == 0) { 11565 TextOutW(hdc, x, y, buffer.ptr, cast(int) buffer.length); 11566 } else { 11567 RECT rect; 11568 rect.left = x; 11569 rect.top = y; 11570 rect.right = x2; 11571 rect.bottom = y2; 11572 11573 uint mode = DT_LEFT; 11574 if(alignment & TextAlignment.Right) 11575 mode = DT_RIGHT; 11576 else if(alignment & TextAlignment.Center) 11577 mode = DT_CENTER; 11578 11579 // FIXME: vcenter on windows only works with single line, but I want it to work in all cases 11580 if(alignment & TextAlignment.VerticalCenter) 11581 mode |= DT_VCENTER | DT_SINGLELINE; 11582 11583 DrawTextW(hdc, buffer.ptr, cast(int) buffer.length, &rect, mode | DT_NOPREFIX); 11584 } 11585 11586 /* 11587 uint mode; 11588 11589 if(alignment & TextAlignment.Center) 11590 mode = TA_CENTER; 11591 11592 SetTextAlign(hdc, mode); 11593 */ 11594 } 11595 11596 int fontHeight() { 11597 TEXTMETRIC metric; 11598 if(GetTextMetricsW(hdc, &metric)) { 11599 return metric.tmHeight; 11600 } 11601 11602 return 16; // idk just guessing here, maybe we should throw 11603 } 11604 11605 void drawPixel(int x, int y) { 11606 SetPixel(hdc, x, y, RGB(_activePen.color.r, _activePen.color.g, _activePen.color.b)); 11607 } 11608 11609 // The basic shapes, outlined 11610 11611 void drawLine(int x1, int y1, int x2, int y2) { 11612 MoveToEx(hdc, x1, y1, null); 11613 LineTo(hdc, x2, y2); 11614 } 11615 11616 void drawRectangle(int x, int y, int width, int height) { 11617 // FIXME: with a wider pen this might not draw quite right. im not sure. 11618 gdi.Rectangle(hdc, x, y, x + width, y + height); 11619 } 11620 11621 /// Arguments are the points of the bounding rectangle 11622 void drawEllipse(int x1, int y1, int x2, int y2) { 11623 Ellipse(hdc, x1, y1, x2, y2); 11624 } 11625 11626 void drawArc(int x1, int y1, int width, int height, int start, int finish) { 11627 if((start % (360*64)) == (finish % (360*64))) 11628 drawEllipse(x1, y1, x1 + width, y1 + height); 11629 else { 11630 import core.stdc.math; 11631 float startAngle = cast(float) start / 64.0 / 180.0 * 3.14159265358979323; 11632 float endAngle = cast(float) finish / 64.0 / 180.0 * 3.14159265358979323; 11633 11634 auto c1 = cast(int) roundf(cos(startAngle) * width / 2 + x1 + width / 2); 11635 auto c2 = cast(int) roundf(-sin(startAngle) * height / 2 + y1 + height / 2); 11636 auto c3 = cast(int) roundf(cos(endAngle) * width / 2 + x1 + width / 2); 11637 auto c4 = cast(int) roundf(-sin(endAngle) * height / 2 + y1 + height / 2); 11638 11639 if(_activePen.color.a) 11640 Arc(hdc, x1, y1, x1 + width + 1, y1 + height + 1, c1, c2, c3, c4); 11641 if(_fillColor.a) 11642 Pie(hdc, x1, y1, x1 + width + 1, y1 + height + 1, c1, c2, c3, c4); 11643 } 11644 } 11645 11646 void drawPolygon(Point[] vertexes) { 11647 POINT[] points; 11648 points.length = vertexes.length; 11649 11650 foreach(i, p; vertexes) { 11651 points[i].x = p.x; 11652 points[i].y = p.y; 11653 } 11654 11655 Polygon(hdc, points.ptr, cast(int) points.length); 11656 } 11657 } 11658 11659 11660 // Mix this into the SimpleWindow class 11661 mixin template NativeSimpleWindowImplementation() { 11662 int curHidden = 0; // counter 11663 __gshared static bool[string] knownWinClasses; 11664 static bool altPressed = false; 11665 11666 HANDLE oldCursor; 11667 11668 void hideCursor () { 11669 if(curHidden == 0) 11670 oldCursor = SetCursor(null); 11671 ++curHidden; 11672 } 11673 11674 void showCursor () { 11675 --curHidden; 11676 if(curHidden == 0) { 11677 SetCursor(currentCursor is null ? oldCursor : currentCursor); // show it immediately without waiting for mouse movement 11678 } 11679 } 11680 11681 11682 int minWidth = 0, minHeight = 0, maxWidth = int.max, maxHeight = int.max; 11683 11684 void setMinSize (int minwidth, int minheight) { 11685 minWidth = minwidth; 11686 minHeight = minheight; 11687 } 11688 void setMaxSize (int maxwidth, int maxheight) { 11689 maxWidth = maxwidth; 11690 maxHeight = maxheight; 11691 } 11692 11693 // FIXME i'm not sure that Windows has this functionality 11694 // though it is nonessential anyway. 11695 void setResizeGranularity (int granx, int grany) {} 11696 11697 ScreenPainter getPainter(bool manualInvalidations) { 11698 return ScreenPainter(this, hwnd, manualInvalidations); 11699 } 11700 11701 HBITMAP buffer; 11702 11703 void setTitle(string title) { 11704 WCharzBuffer bfr = WCharzBuffer(title); 11705 SetWindowTextW(hwnd, bfr.ptr); 11706 } 11707 11708 string getTitle() { 11709 auto len = GetWindowTextLengthW(hwnd); 11710 if (!len) 11711 return null; 11712 wchar[256] tmpBuffer; 11713 wchar[] buffer = (len <= tmpBuffer.length) ? tmpBuffer[] : new wchar[len]; 11714 auto len2 = GetWindowTextW(hwnd, buffer.ptr, cast(int) buffer.length); 11715 auto str = buffer[0 .. len2]; 11716 return makeUtf8StringFromWindowsString(str); 11717 } 11718 11719 void move(int x, int y) { 11720 RECT rect; 11721 GetWindowRect(hwnd, &rect); 11722 // move it while maintaining the same size... 11723 MoveWindow(hwnd, x, y, rect.right - rect.left, rect.bottom - rect.top, true); 11724 } 11725 11726 void resize(int w, int h) { 11727 RECT rect; 11728 GetWindowRect(hwnd, &rect); 11729 11730 RECT client; 11731 GetClientRect(hwnd, &client); 11732 11733 rect.right = rect.right - client.right + w; 11734 rect.bottom = rect.bottom - client.bottom + h; 11735 11736 // same position, new size for the client rectangle 11737 MoveWindow(hwnd, rect.left, rect.top, rect.right, rect.bottom, true); 11738 11739 updateOpenglViewportIfNeeded(w, h); 11740 } 11741 11742 void moveResize (int x, int y, int w, int h) { 11743 // what's given is the client rectangle, we need to adjust 11744 11745 RECT rect; 11746 rect.left = x; 11747 rect.top = y; 11748 rect.right = w + x; 11749 rect.bottom = h + y; 11750 if(!AdjustWindowRect(&rect, GetWindowLong(hwnd, GWL_STYLE), GetMenu(hwnd) !is null)) 11751 throw new WindowsApiException("AdjustWindowRect", GetLastError()); 11752 11753 MoveWindow(hwnd, rect.left, rect.top, rect.right - rect.left, rect.bottom - rect.top, true); 11754 updateOpenglViewportIfNeeded(w, h); 11755 if (windowResized !is null) windowResized(w, h); 11756 } 11757 11758 version(without_opengl) {} else { 11759 HGLRC ghRC; 11760 HDC ghDC; 11761 } 11762 11763 void createWindow(int width, int height, string title, OpenGlOptions opengl, SimpleWindow parent) { 11764 string cnamec; 11765 if (sdpyWindowClassStr is null) loadBinNameToWindowClassName(); 11766 if (sdpyWindowClassStr is null || sdpyWindowClassStr[0] == 0) { 11767 cnamec = "DSimpleWindow"; 11768 } else { 11769 cnamec = sdpyWindowClass; 11770 } 11771 11772 WCharzBuffer cn = WCharzBuffer(cnamec); 11773 11774 HINSTANCE hInstance = cast(HINSTANCE) GetModuleHandle(null); 11775 11776 if(cnamec !in knownWinClasses) { 11777 WNDCLASSEX wc; 11778 11779 // FIXME: I might be able to use cbWndExtra to hold the pointer back 11780 // to the object. Maybe. 11781 wc.cbSize = wc.sizeof; 11782 wc.cbClsExtra = 0; 11783 wc.cbWndExtra = 0; 11784 wc.hbrBackground = cast(HBRUSH) (COLOR_WINDOW+1); // GetStockObject(WHITE_BRUSH); 11785 wc.hCursor = LoadCursorW(null, IDC_ARROW); 11786 wc.hIcon = LoadIcon(hInstance, null); 11787 wc.hInstance = hInstance; 11788 wc.lpfnWndProc = &WndProc; 11789 wc.lpszClassName = cn.ptr; 11790 wc.hIconSm = null; 11791 wc.style = CS_HREDRAW | CS_VREDRAW | CS_DBLCLKS; 11792 if(!RegisterClassExW(&wc)) 11793 throw new WindowsApiException("RegisterClassExW", GetLastError()); 11794 knownWinClasses[cnamec] = true; 11795 } 11796 11797 int style; 11798 uint flags = WS_EX_ACCEPTFILES; // accept drag-drop files 11799 11800 // FIXME: windowType and customizationFlags 11801 final switch(windowType) { 11802 case WindowTypes.normal: 11803 if(resizability == Resizability.fixedSize) { 11804 style = WS_SYSMENU | WS_OVERLAPPED | WS_CAPTION; 11805 } else { 11806 style = WS_OVERLAPPEDWINDOW; 11807 } 11808 break; 11809 case WindowTypes.undecorated: 11810 style = WS_POPUP | WS_SYSMENU; 11811 break; 11812 case WindowTypes.eventOnly: 11813 _hidden = true; 11814 break; 11815 case WindowTypes.dropdownMenu: 11816 case WindowTypes.popupMenu: 11817 case WindowTypes.notification: 11818 style = WS_POPUP; 11819 flags |= WS_EX_NOACTIVATE; 11820 break; 11821 case WindowTypes.nestedChild: 11822 style = WS_CHILD; 11823 break; 11824 case WindowTypes.minimallyWrapped: 11825 assert(0, "construct minimally wrapped through the other ctor overlad"); 11826 } 11827 11828 if ((customizationFlags & WindowFlags.extraComposite) != 0) 11829 flags |= WS_EX_LAYERED; // composite window for better performance and effects support 11830 11831 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 11832 CW_USEDEFAULT, CW_USEDEFAULT, width, height, 11833 parent is null ? null : parent.impl.hwnd, null, hInstance, null); 11834 11835 if ((customizationFlags & WindowFlags.extraComposite) != 0) 11836 setOpacity(255); 11837 11838 SimpleWindow.nativeMapping[hwnd] = this; 11839 CapableOfHandlingNativeEvent.nativeHandleMapping[hwnd] = this; 11840 11841 if(windowType == WindowTypes.eventOnly) 11842 return; 11843 11844 HDC hdc = GetDC(hwnd); 11845 11846 11847 version(without_opengl) {} 11848 else { 11849 if(opengl == OpenGlOptions.yes) { 11850 if(!openGlLibrariesSuccessfullyLoaded) throw new Exception("OpenGL libraries did not load"); 11851 static if (SdpyIsUsingIVGLBinds) {if (glbindGetProcAddress("glHint") is null) assert(0, "GL: error loading OpenGL"); } // loads all necessary functions 11852 static if (SdpyIsUsingIVGLBinds) import iv.glbinds; // override druntime windows imports 11853 ghDC = hdc; 11854 PIXELFORMATDESCRIPTOR pfd; 11855 11856 pfd.nSize = PIXELFORMATDESCRIPTOR.sizeof; 11857 pfd.nVersion = 1; 11858 pfd.dwFlags = PFD_DRAW_TO_WINDOW | PFD_SUPPORT_OPENGL | PFD_DOUBLEBUFFER; 11859 pfd.dwLayerMask = PFD_MAIN_PLANE; 11860 pfd.iPixelType = PFD_TYPE_RGBA; 11861 pfd.cColorBits = 24; 11862 pfd.cDepthBits = 24; 11863 pfd.cAccumBits = 0; 11864 pfd.cStencilBits = 8; // any reasonable OpenGL implementation should support this anyway 11865 11866 auto pixelformat = ChoosePixelFormat(hdc, &pfd); 11867 11868 if (pixelformat == 0) 11869 throw new WindowsApiException("ChoosePixelFormat", GetLastError()); 11870 11871 if (SetPixelFormat(hdc, pixelformat, &pfd) == 0) 11872 throw new WindowsApiException("SetPixelFormat", GetLastError()); 11873 11874 if (sdpyOpenGLContextVersion && wglCreateContextAttribsARB is null) { 11875 // windoze is idiotic: we have to have OpenGL context to get function addresses 11876 // so we will create fake context to get that stupid address 11877 auto tmpcc = wglCreateContext(ghDC); 11878 if (tmpcc !is null) { 11879 scope(exit) { wglMakeCurrent(ghDC, null); wglDeleteContext(tmpcc); } 11880 wglMakeCurrent(ghDC, tmpcc); 11881 wglInitOtherFunctions(); 11882 } 11883 } 11884 11885 if (wglCreateContextAttribsARB !is null && sdpyOpenGLContextVersion) { 11886 int[9] contextAttribs = [ 11887 WGL_CONTEXT_MAJOR_VERSION_ARB, (sdpyOpenGLContextVersion>>8), 11888 WGL_CONTEXT_MINOR_VERSION_ARB, (sdpyOpenGLContextVersion&0xff), 11889 WGL_CONTEXT_PROFILE_MASK_ARB, (sdpyOpenGLContextCompatible ? WGL_CONTEXT_COMPATIBILITY_PROFILE_BIT_ARB : WGL_CONTEXT_CORE_PROFILE_BIT_ARB), 11890 // for modern context, set "forward compatibility" flag too 11891 (sdpyOpenGLContextCompatible ? 0/*None*/ : WGL_CONTEXT_FLAGS_ARB), WGL_CONTEXT_FORWARD_COMPATIBLE_BIT_ARB, 11892 0/*None*/, 11893 ]; 11894 ghRC = wglCreateContextAttribsARB(ghDC, null, contextAttribs.ptr); 11895 if (ghRC is null && sdpyOpenGLContextAllowFallback) { 11896 // activate fallback mode 11897 // 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; 11898 ghRC = wglCreateContext(ghDC); 11899 } 11900 if (ghRC is null) 11901 throw new WindowsApiException("wglCreateContextAttribsARB", GetLastError()); 11902 } else { 11903 // try to do at least something 11904 if (sdpyOpenGLContextAllowFallback || sdpyOpenGLContextVersion == 0) { 11905 sdpyOpenGLContextVersion = 0; 11906 ghRC = wglCreateContext(ghDC); 11907 } 11908 if (ghRC is null) 11909 throw new WindowsApiException("wglCreateContext", GetLastError()); 11910 } 11911 } 11912 } 11913 11914 if(opengl == OpenGlOptions.no) { 11915 buffer = CreateCompatibleBitmap(hdc, width, height); 11916 11917 auto hdcBmp = CreateCompatibleDC(hdc); 11918 // make sure it's filled with a blank slate 11919 auto oldBmp = SelectObject(hdcBmp, buffer); 11920 auto oldBrush = SelectObject(hdcBmp, GetStockObject(WHITE_BRUSH)); 11921 auto oldPen = SelectObject(hdcBmp, GetStockObject(WHITE_PEN)); 11922 gdi.Rectangle(hdcBmp, 0, 0, width, height); 11923 SelectObject(hdcBmp, oldBmp); 11924 SelectObject(hdcBmp, oldBrush); 11925 SelectObject(hdcBmp, oldPen); 11926 DeleteDC(hdcBmp); 11927 11928 bmpWidth = width; 11929 bmpHeight = height; 11930 11931 ReleaseDC(hwnd, hdc); // we keep this in opengl mode since it is a class member now 11932 } 11933 11934 // We want the window's client area to match the image size 11935 RECT rcClient, rcWindow; 11936 POINT ptDiff; 11937 GetClientRect(hwnd, &rcClient); 11938 GetWindowRect(hwnd, &rcWindow); 11939 ptDiff.x = (rcWindow.right - rcWindow.left) - rcClient.right; 11940 ptDiff.y = (rcWindow.bottom - rcWindow.top) - rcClient.bottom; 11941 MoveWindow(hwnd,rcWindow.left, rcWindow.top, width + ptDiff.x, height + ptDiff.y, true); 11942 11943 if ((customizationFlags&WindowFlags.dontAutoShow) == 0) { 11944 ShowWindow(hwnd, SW_SHOWNORMAL); 11945 } else { 11946 _hidden = true; 11947 } 11948 this._visibleForTheFirstTimeCalled = false; // hack! 11949 } 11950 11951 11952 void dispose() { 11953 if(buffer) 11954 DeleteObject(buffer); 11955 } 11956 11957 void closeWindow() { 11958 if(ghRC) { 11959 wglDeleteContext(ghRC); 11960 ghRC = null; 11961 } 11962 DestroyWindow(hwnd); 11963 } 11964 11965 bool setOpacity(ubyte alpha) { 11966 return SetLayeredWindowAttributes(hwnd, 0, alpha, LWA_ALPHA) == TRUE; 11967 } 11968 11969 HANDLE currentCursor; 11970 11971 // returns zero if it recognized the event 11972 static int triggerEvents(HWND hwnd, uint msg, WPARAM wParam, LPARAM lParam, int offsetX, int offsetY, SimpleWindow wind) { 11973 MouseEvent mouse; 11974 11975 void mouseEvent(bool isScreen, ulong mods) { 11976 auto x = LOWORD(lParam); 11977 auto y = HIWORD(lParam); 11978 if(isScreen) { 11979 POINT p; 11980 p.x = x; 11981 p.y = y; 11982 ScreenToClient(hwnd, &p); 11983 x = cast(ushort) p.x; 11984 y = cast(ushort) p.y; 11985 } 11986 11987 if(wind.resizability == Resizability.automaticallyScaleIfPossible) { 11988 x = cast(ushort)( x * wind._virtualWidth / wind._width ); 11989 y = cast(ushort)( y * wind._virtualHeight / wind._height ); 11990 } 11991 11992 mouse.x = x + offsetX; 11993 mouse.y = y + offsetY; 11994 11995 wind.mdx(mouse); 11996 mouse.modifierState = cast(int) mods; 11997 mouse.window = wind; 11998 11999 if(wind.handleMouseEvent) 12000 wind.handleMouseEvent(mouse); 12001 } 12002 12003 switch(msg) { 12004 case WM_GETMINMAXINFO: 12005 MINMAXINFO* mmi = cast(MINMAXINFO*) lParam; 12006 12007 if(wind.minWidth > 0) { 12008 RECT rect; 12009 rect.left = 100; 12010 rect.top = 100; 12011 rect.right = wind.minWidth + 100; 12012 rect.bottom = wind.minHeight + 100; 12013 if(!AdjustWindowRect(&rect, GetWindowLong(wind.hwnd, GWL_STYLE), GetMenu(wind.hwnd) !is null)) 12014 throw new WindowsApiException("AdjustWindowRect", GetLastError()); 12015 12016 mmi.ptMinTrackSize.x = rect.right - rect.left; 12017 mmi.ptMinTrackSize.y = rect.bottom - rect.top; 12018 } 12019 12020 if(wind.maxWidth < int.max) { 12021 RECT rect; 12022 rect.left = 100; 12023 rect.top = 100; 12024 rect.right = wind.maxWidth + 100; 12025 rect.bottom = wind.maxHeight + 100; 12026 if(!AdjustWindowRect(&rect, GetWindowLong(wind.hwnd, GWL_STYLE), GetMenu(wind.hwnd) !is null)) 12027 throw new WindowsApiException("AdjustWindowRect", GetLastError()); 12028 12029 mmi.ptMaxTrackSize.x = rect.right - rect.left; 12030 mmi.ptMaxTrackSize.y = rect.bottom - rect.top; 12031 } 12032 break; 12033 case WM_CHAR: 12034 wchar c = cast(wchar) wParam; 12035 if(wind.handleCharEvent) 12036 wind.handleCharEvent(cast(dchar) c); 12037 break; 12038 case WM_SETFOCUS: 12039 case WM_KILLFOCUS: 12040 wind._focused = (msg == WM_SETFOCUS); 12041 if (msg == WM_SETFOCUS) altPressed = false; //k8: reset alt state on defocus (it is better than nothing...) 12042 if(wind.onFocusChange) 12043 wind.onFocusChange(msg == WM_SETFOCUS); 12044 break; 12045 12046 case WM_SYSKEYDOWN: 12047 goto case; 12048 case WM_SYSKEYUP: 12049 if(lParam & (1 << 29)) { 12050 goto case; 12051 } else { 12052 // no window has keyboard focus 12053 goto default; 12054 } 12055 case WM_KEYDOWN: 12056 case WM_KEYUP: 12057 KeyEvent ev; 12058 ev.key = cast(Key) wParam; 12059 ev.pressed = (msg == WM_KEYDOWN || msg == WM_SYSKEYDOWN); 12060 if (wParam == 0x12) ev.key = Key.Alt; // windows does it this way 12061 12062 ev.hardwareCode = (lParam & 0xff0000) >> 16; 12063 12064 if(GetKeyState(Key.Shift)&0x8000 || GetKeyState(Key.Shift_r)&0x8000) 12065 ev.modifierState |= ModifierState.shift; 12066 //k8: this doesn't work; thanks for nothing, windows 12067 /*if(GetKeyState(Key.Alt)&0x8000 || GetKeyState(Key.Alt_r)&0x8000) 12068 ev.modifierState |= ModifierState.alt;*/ 12069 // this never seems to actually be set 12070 // if (lParam & 0x2000 /* KF_ALTDOWN */) ev.modifierState |= ModifierState.alt; else ev.modifierState &= ~ModifierState.alt; 12071 12072 if (wParam == 0x12) { 12073 altPressed = (msg == WM_SYSKEYDOWN); 12074 } 12075 12076 if(msg == WM_KEYDOWN || msg == WM_KEYUP) { 12077 altPressed = false; 12078 } 12079 // sdpyPrintDebugString(altPressed ? "alt down" : " up "); 12080 12081 if (altPressed) ev.modifierState |= ModifierState.alt; else ev.modifierState &= ~ModifierState.alt; 12082 if(GetKeyState(Key.Ctrl)&0x8000 || GetKeyState(Key.Ctrl_r)&0x8000) 12083 ev.modifierState |= ModifierState.ctrl; 12084 if(GetKeyState(Key.Windows)&0x8000 || GetKeyState(Key.Windows_r)&0x8000) 12085 ev.modifierState |= ModifierState.windows; 12086 if(GetKeyState(Key.NumLock)) 12087 ev.modifierState |= ModifierState.numLock; 12088 if(GetKeyState(Key.CapsLock)) 12089 ev.modifierState |= ModifierState.capsLock; 12090 12091 /+ 12092 // we always want to send the character too, so let's convert it 12093 ubyte[256] state; 12094 wchar[16] buffer; 12095 GetKeyboardState(state.ptr); 12096 ToUnicodeEx(wParam, lParam, state.ptr, buffer.ptr, buffer.length, 0, null); 12097 12098 foreach(dchar d; buffer) { 12099 ev.character = d; 12100 break; 12101 } 12102 +/ 12103 12104 ev.window = wind; 12105 if(wind.handleKeyEvent) 12106 wind.handleKeyEvent(ev); 12107 break; 12108 case 0x020a /*WM_MOUSEWHEEL*/: 12109 // send click 12110 mouse.type = cast(MouseEventType) 1; 12111 mouse.button = ((GET_WHEEL_DELTA_WPARAM(wParam) > 0) ? MouseButton.wheelUp : MouseButton.wheelDown); 12112 mouseEvent(true, LOWORD(wParam)); 12113 12114 // also send release 12115 mouse.type = cast(MouseEventType) 2; 12116 mouse.button = ((GET_WHEEL_DELTA_WPARAM(wParam) > 0) ? MouseButton.wheelUp : MouseButton.wheelDown); 12117 mouseEvent(true, LOWORD(wParam)); 12118 break; 12119 case WM_MOUSEMOVE: 12120 mouse.type = cast(MouseEventType) 0; 12121 mouseEvent(false, wParam); 12122 break; 12123 case WM_LBUTTONDOWN: 12124 case WM_LBUTTONDBLCLK: 12125 mouse.type = cast(MouseEventType) 1; 12126 mouse.button = MouseButton.left; 12127 mouse.doubleClick = msg == WM_LBUTTONDBLCLK; 12128 mouseEvent(false, wParam); 12129 break; 12130 case WM_LBUTTONUP: 12131 mouse.type = cast(MouseEventType) 2; 12132 mouse.button = MouseButton.left; 12133 mouseEvent(false, wParam); 12134 break; 12135 case WM_RBUTTONDOWN: 12136 case WM_RBUTTONDBLCLK: 12137 mouse.type = cast(MouseEventType) 1; 12138 mouse.button = MouseButton.right; 12139 mouse.doubleClick = msg == WM_RBUTTONDBLCLK; 12140 mouseEvent(false, wParam); 12141 break; 12142 case WM_RBUTTONUP: 12143 mouse.type = cast(MouseEventType) 2; 12144 mouse.button = MouseButton.right; 12145 mouseEvent(false, wParam); 12146 break; 12147 case WM_MBUTTONDOWN: 12148 case WM_MBUTTONDBLCLK: 12149 mouse.type = cast(MouseEventType) 1; 12150 mouse.button = MouseButton.middle; 12151 mouse.doubleClick = msg == WM_MBUTTONDBLCLK; 12152 mouseEvent(false, wParam); 12153 break; 12154 case WM_MBUTTONUP: 12155 mouse.type = cast(MouseEventType) 2; 12156 mouse.button = MouseButton.middle; 12157 mouseEvent(false, wParam); 12158 break; 12159 case WM_XBUTTONDOWN: 12160 case WM_XBUTTONDBLCLK: 12161 mouse.type = cast(MouseEventType) 1; 12162 mouse.button = HIWORD(wParam) == 1 ? MouseButton.backButton : MouseButton.forwardButton; 12163 mouse.doubleClick = msg == WM_XBUTTONDBLCLK; 12164 mouseEvent(false, wParam); 12165 return 1; // MSDN says special treatment here, return TRUE to bypass simulation programs 12166 case WM_XBUTTONUP: 12167 mouse.type = cast(MouseEventType) 2; 12168 mouse.button = HIWORD(wParam) == 1 ? MouseButton.backButton : MouseButton.forwardButton; 12169 mouseEvent(false, wParam); 12170 return 1; // see: https://msdn.microsoft.com/en-us/library/windows/desktop/ms646246(v=vs.85).aspx 12171 12172 default: return 1; 12173 } 12174 return 0; 12175 } 12176 12177 HWND hwnd; 12178 private int oldWidth; 12179 private int oldHeight; 12180 private bool inSizeMove; 12181 12182 /++ 12183 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. 12184 12185 History: 12186 Added November 23, 2021 12187 12188 Not fully stable, may be moved out of the impl struct. 12189 12190 Default value changed to `true` on February 15, 2021 12191 +/ 12192 bool doLiveResizing = true; 12193 12194 package int bmpWidth; 12195 package int bmpHeight; 12196 12197 // the extern(Windows) wndproc should just forward to this 12198 LRESULT windowProcedure(HWND hwnd, uint msg, WPARAM wParam, LPARAM lParam) { 12199 try { 12200 assert(hwnd is this.hwnd); 12201 12202 if(triggerEvents(hwnd, msg, wParam, lParam, 0, 0, this)) 12203 switch(msg) { 12204 case WM_MENUCHAR: // menu active but key not associated with a thing. 12205 // you would ideally use this for like a search function but sdpy not that ideally designed. alas. 12206 // The main things we can do are select, execute, close, or ignore 12207 // the default is ignore, but it doesn't *just* ignore it - it also dings an audio alert to 12208 // the user. This can be a bit annoying for sdpy things so instead im overriding and setting it 12209 // to close, which can be really annoying when you hit the wrong button. but meh i think for sdpy 12210 // that's the lesser bad choice rn. Can always override by returning true in triggerEvents.... 12211 12212 // returns the value in the *high order word* of the return value 12213 // hence the << 16 12214 return 1 << 16; // MNC_CLOSE, close the menu without dinging at the user 12215 case WM_SETCURSOR: 12216 if(cast(HWND) wParam !is hwnd) 12217 return 0; // further processing elsewhere 12218 12219 if(LOWORD(lParam) == HTCLIENT && (this.curHidden > 0 || currentCursor !is null)) { 12220 SetCursor(this.curHidden > 0 ? null : currentCursor); 12221 return 1; 12222 } else { 12223 return DefWindowProc(hwnd, msg, wParam, lParam); 12224 } 12225 //break; 12226 12227 case WM_CLOSE: 12228 if (this.closeQuery !is null) this.closeQuery(); else this.close(); 12229 break; 12230 case WM_DESTROY: 12231 if (this.onDestroyed !is null) try { this.onDestroyed(); } catch (Exception e) {} // sorry 12232 SimpleWindow.nativeMapping.remove(hwnd); 12233 CapableOfHandlingNativeEvent.nativeHandleMapping.remove(hwnd); 12234 12235 bool anyImportant = false; 12236 foreach(SimpleWindow w; SimpleWindow.nativeMapping) 12237 if(w.beingOpenKeepsAppOpen) { 12238 anyImportant = true; 12239 break; 12240 } 12241 if(!anyImportant) { 12242 PostQuitMessage(0); 12243 } 12244 break; 12245 case 0x02E0 /*WM_DPICHANGED*/: 12246 this.actualDpi_ = LOWORD(wParam); // hiword is the y param but it is the same per docs 12247 12248 RECT* prcNewWindow = cast(RECT*)lParam; 12249 // docs say this is the recommended position and we should honor it 12250 SetWindowPos(hwnd, 12251 null, 12252 prcNewWindow.left, 12253 prcNewWindow.top, 12254 prcNewWindow.right - prcNewWindow.left, 12255 prcNewWindow.bottom - prcNewWindow.top, 12256 SWP_NOZORDER | SWP_NOACTIVATE); 12257 12258 // doing this because of https://github.com/microsoft/Windows-classic-samples/blob/main/Samples/DPIAwarenessPerWindow/client/DpiAwarenessContext.cpp 12259 // im not sure it is completely correct 12260 // but without it the tabs and such do look weird as things change. 12261 if(SystemParametersInfoForDpi) { 12262 LOGFONT lfText; 12263 SystemParametersInfoForDpi(SPI_GETICONTITLELOGFONT, lfText.sizeof, &lfText, FALSE, this.actualDpi_); 12264 HFONT hFontNew = CreateFontIndirect(&lfText); 12265 if (hFontNew) 12266 { 12267 //DeleteObject(hFontOld); 12268 static extern(Windows) BOOL helper(HWND hWnd, LPARAM lParam) { 12269 SendMessage(hWnd, WM_SETFONT, cast(WPARAM)lParam, MAKELPARAM(TRUE, 0)); 12270 return TRUE; 12271 } 12272 EnumChildWindows(hwnd, &helper, cast(LPARAM) hFontNew); 12273 } 12274 } 12275 12276 if(this.onDpiChanged) 12277 this.onDpiChanged(); 12278 break; 12279 case WM_ENTERIDLE: 12280 // when a menu is up, it stops normal event processing (modal message loop) 12281 // but this at least gives us a chance to SOMETIMES catch up 12282 // FIXME: I can use SetTimer while idle to keep working i think... but idk when i'd destroy it. 12283 SimpleWindow.processAllCustomEvents; 12284 SimpleWindow.processAllCustomEvents; 12285 SleepEx(0, true); 12286 break; 12287 case WM_SIZE: 12288 if(wParam == 1 /* SIZE_MINIMIZED */) 12289 break; 12290 _width = LOWORD(lParam); 12291 _height = HIWORD(lParam); 12292 12293 // I want to avoid tearing in the windows (my code is inefficient 12294 // so this is a hack around that) so while sizing, we don't trigger, 12295 // but we do want to trigger on events like mazimize. 12296 if(!inSizeMove || doLiveResizing) 12297 goto size_changed; 12298 break; 12299 /+ 12300 case WM_SIZING: 12301 writeln("size"); 12302 break; 12303 +/ 12304 // I don't like the tearing I get when redrawing on WM_SIZE 12305 // (I know there's other ways to fix that but I don't like that behavior anyway) 12306 // so instead it is going to redraw only at the end of a size. 12307 case 0x0231: /* WM_ENTERSIZEMOVE */ 12308 inSizeMove = true; 12309 break; 12310 case 0x0232: /* WM_EXITSIZEMOVE */ 12311 inSizeMove = false; 12312 12313 size_changed: 12314 12315 // nothing relevant changed, don't bother redrawing 12316 if(oldWidth == _width && oldHeight == _height) { 12317 if(msg == 0x0232) 12318 goto finalize_resize; 12319 break; 12320 } 12321 12322 // note: OpenGL windows don't use a backing bmp, so no need to change them 12323 // if resizability is anything other than allowResizing, it is meant to either stretch the one image or just do nothing 12324 if(openglMode == OpenGlOptions.no) { // && resizability == Resizability.allowResizing) { 12325 // gotta get the double buffer bmp to match the window 12326 // FIXME: could this be more efficient? it never relinquishes a large bitmap 12327 12328 // if it is auto-scaled, we keep the backing bitmap the same size all the time 12329 if(resizability != Resizability.automaticallyScaleIfPossible) 12330 if(_width > bmpWidth || _height > bmpHeight) { 12331 auto hdc = GetDC(hwnd); 12332 auto oldBuffer = buffer; 12333 buffer = CreateCompatibleBitmap(hdc, _width, _height); 12334 12335 auto hdcBmp = CreateCompatibleDC(hdc); 12336 auto oldBmp = SelectObject(hdcBmp, buffer); 12337 12338 auto hdcOldBmp = CreateCompatibleDC(hdc); 12339 auto oldOldBmp = SelectObject(hdcOldBmp, oldBuffer); 12340 12341 /+ 12342 RECT r; 12343 r.left = 0; 12344 r.top = 0; 12345 r.right = width; 12346 r.bottom = height; 12347 auto c = Color.green; 12348 auto brush = CreateSolidBrush(RGB(c.r, c.g, c.b)); 12349 FillRect(hdcBmp, &r, brush); 12350 DeleteObject(brush); 12351 +/ 12352 12353 BitBlt(hdcBmp, 0, 0, bmpWidth, bmpHeight, hdcOldBmp, 0, 0, SRCCOPY); 12354 12355 bmpWidth = _width; 12356 bmpHeight = _height; 12357 12358 SelectObject(hdcOldBmp, oldOldBmp); 12359 DeleteDC(hdcOldBmp); 12360 12361 SelectObject(hdcBmp, oldBmp); 12362 DeleteDC(hdcBmp); 12363 12364 ReleaseDC(hwnd, hdc); 12365 12366 DeleteObject(oldBuffer); 12367 } 12368 } 12369 12370 updateOpenglViewportIfNeeded(_width, _height); 12371 12372 if(resizability != Resizability.automaticallyScaleIfPossible) 12373 if(windowResized !is null) 12374 windowResized(_width, _height); 12375 12376 /+ 12377 if(inSizeMove) { 12378 // SimpleWindow.processAllCustomEvents(); 12379 // SimpleWindow.processAllCustomEvents(); 12380 12381 //RedrawWindow(hwnd, null, null, RDW_ERASE | RDW_INVALIDATE | RDW_ALLCHILDREN); 12382 //sdpyPrintDebugString("redraw b"); 12383 } else { 12384 +/ { 12385 finalize_resize: 12386 // when it is all done, make sure everything is freshly drawn or there might be 12387 // weird bugs left. 12388 SimpleWindow.processAllCustomEvents(); 12389 SimpleWindow.processAllCustomEvents(); 12390 12391 RedrawWindow(hwnd, null, null, RDW_ERASE | RDW_INVALIDATE | RDW_ALLCHILDREN); 12392 // sdpyPrintDebugString("redraw"); 12393 } 12394 12395 oldWidth = this._width; 12396 oldHeight = this._height; 12397 break; 12398 case WM_ERASEBKGND: 12399 // call `visibleForTheFirstTime` here, so we can do initialization as early as possible 12400 if (!this._visibleForTheFirstTimeCalled) { 12401 this._visibleForTheFirstTimeCalled = true; 12402 if (this.visibleForTheFirstTime !is null) { 12403 this.visibleForTheFirstTime(); 12404 } 12405 } 12406 // block it in OpenGL mode, 'cause no sane person will (or should) draw windows controls over OpenGL scene 12407 version(without_opengl) {} else { 12408 if (openglMode == OpenGlOptions.yes) return 1; 12409 } 12410 // call windows default handler, so it can paint standard controls 12411 goto default; 12412 case WM_CTLCOLORBTN: 12413 case WM_CTLCOLORSTATIC: 12414 SetBkMode(cast(HDC) wParam, TRANSPARENT); 12415 return cast(typeof(return)) //GetStockObject(NULL_BRUSH); 12416 GetSysColorBrush(COLOR_3DFACE); 12417 //break; 12418 case WM_SHOWWINDOW: 12419 this._visible = (wParam != 0); 12420 if (!this._visibleForTheFirstTimeCalled && this._visible) { 12421 this._visibleForTheFirstTimeCalled = true; 12422 if (this.visibleForTheFirstTime !is null) { 12423 this.visibleForTheFirstTime(); 12424 } 12425 } 12426 if (this.visibilityChanged !is null) this.visibilityChanged(this._visible); 12427 break; 12428 case WM_PAINT: { 12429 if (!this._visibleForTheFirstTimeCalled) { 12430 this._visibleForTheFirstTimeCalled = true; 12431 if (this.visibleForTheFirstTime !is null) { 12432 this.visibleForTheFirstTime(); 12433 } 12434 } 12435 12436 BITMAP bm; 12437 PAINTSTRUCT ps; 12438 12439 HDC hdc = BeginPaint(hwnd, &ps); 12440 12441 if(openglMode == OpenGlOptions.no) { 12442 12443 HDC hdcMem = CreateCompatibleDC(hdc); 12444 HBITMAP hbmOld = SelectObject(hdcMem, buffer); 12445 12446 GetObject(buffer, bm.sizeof, &bm); 12447 12448 // FIXME: only BitBlt the invalidated rectangle, not the whole thing 12449 if(resizability == Resizability.automaticallyScaleIfPossible) 12450 StretchBlt(hdc, 0, 0, this._width, this._height, hdcMem, 0, 0, bm.bmWidth, bm.bmHeight, SRCCOPY); 12451 else 12452 BitBlt(hdc, 0, 0, bm.bmWidth, bm.bmHeight, hdcMem, 0, 0, SRCCOPY); 12453 //BitBlt(hdc, ps.rcPaint.left, ps.rcPaint.top, ps.rcPaint.right - ps.rcPaint.left, ps.rcPaint.top - ps.rcPaint.bottom, hdcMem, 0, 0, SRCCOPY); 12454 12455 SelectObject(hdcMem, hbmOld); 12456 DeleteDC(hdcMem); 12457 EndPaint(hwnd, &ps); 12458 } else { 12459 EndPaint(hwnd, &ps); 12460 version(without_opengl) {} else 12461 redrawOpenGlSceneSoon(); 12462 } 12463 } break; 12464 default: 12465 return DefWindowProc(hwnd, msg, wParam, lParam); 12466 } 12467 return 0; 12468 12469 } 12470 catch(Throwable t) { 12471 sdpyPrintDebugString(t.toString); 12472 return 0; 12473 } 12474 } 12475 } 12476 12477 mixin template NativeImageImplementation() { 12478 HBITMAP handle; 12479 ubyte* rawData; 12480 12481 final: 12482 12483 Color getPixel(int x, int y) { 12484 auto itemsPerLine = ((cast(int) width * 3 + 3) / 4) * 4; 12485 // remember, bmps are upside down 12486 auto offset = itemsPerLine * (height - y - 1) + x * 3; 12487 12488 Color c; 12489 if(enableAlpha) 12490 c.a = rawData[offset + 3]; 12491 else 12492 c.a = 255; 12493 c.b = rawData[offset + 0]; 12494 c.g = rawData[offset + 1]; 12495 c.r = rawData[offset + 2]; 12496 c.unPremultiply(); 12497 return c; 12498 } 12499 12500 void setPixel(int x, int y, Color c) { 12501 auto itemsPerLine = ((cast(int) width * 3 + 3) / 4) * 4; 12502 // remember, bmps are upside down 12503 auto offset = itemsPerLine * (height - y - 1) + x * 3; 12504 12505 if(enableAlpha) 12506 c.premultiply(); 12507 12508 rawData[offset + 0] = c.b; 12509 rawData[offset + 1] = c.g; 12510 rawData[offset + 2] = c.r; 12511 if(enableAlpha) 12512 rawData[offset + 3] = c.a; 12513 } 12514 12515 void convertToRgbaBytes(ubyte[] where) { 12516 assert(where.length == this.width * this.height * 4); 12517 12518 auto itemsPerLine = ((cast(int) width * 3 + 3) / 4) * 4; 12519 int idx = 0; 12520 int offset = itemsPerLine * (height - 1); 12521 // remember, bmps are upside down 12522 for(int y = height - 1; y >= 0; y--) { 12523 auto offsetStart = offset; 12524 for(int x = 0; x < width; x++) { 12525 where[idx + 0] = rawData[offset + 2]; // r 12526 where[idx + 1] = rawData[offset + 1]; // g 12527 where[idx + 2] = rawData[offset + 0]; // b 12528 if(enableAlpha) { 12529 where[idx + 3] = rawData[offset + 3]; // a 12530 unPremultiplyRgba(where[idx .. idx + 4]); 12531 offset++; 12532 } else 12533 where[idx + 3] = 255; // a 12534 idx += 4; 12535 offset += 3; 12536 } 12537 12538 offset = offsetStart - itemsPerLine; 12539 } 12540 } 12541 12542 void setFromRgbaBytes(in ubyte[] what) { 12543 assert(what.length == this.width * this.height * 4); 12544 12545 auto itemsPerLine = enableAlpha ? (width * 4) : (((cast(int) width * 3 + 3) / 4) * 4); 12546 int idx = 0; 12547 int offset = itemsPerLine * (height - 1); 12548 // remember, bmps are upside down 12549 for(int y = height - 1; y >= 0; y--) { 12550 auto offsetStart = offset; 12551 for(int x = 0; x < width; x++) { 12552 if(enableAlpha) { 12553 auto a = what[idx + 3]; 12554 12555 rawData[offset + 2] = (a * what[idx + 0]) / 255; // r 12556 rawData[offset + 1] = (a * what[idx + 1]) / 255; // g 12557 rawData[offset + 0] = (a * what[idx + 2]) / 255; // b 12558 rawData[offset + 3] = a; // a 12559 //premultiplyBgra(rawData[offset .. offset + 4]); 12560 offset++; 12561 } else { 12562 rawData[offset + 2] = what[idx + 0]; // r 12563 rawData[offset + 1] = what[idx + 1]; // g 12564 rawData[offset + 0] = what[idx + 2]; // b 12565 } 12566 idx += 4; 12567 offset += 3; 12568 } 12569 12570 offset = offsetStart - itemsPerLine; 12571 } 12572 } 12573 12574 12575 void createImage(int width, int height, bool forcexshm=false, bool enableAlpha = false) { 12576 BITMAPINFO infoheader; 12577 infoheader.bmiHeader.biSize = infoheader.bmiHeader.sizeof; 12578 infoheader.bmiHeader.biWidth = width; 12579 infoheader.bmiHeader.biHeight = height; 12580 infoheader.bmiHeader.biPlanes = 1; 12581 infoheader.bmiHeader.biBitCount = enableAlpha ? 32: 24; 12582 infoheader.bmiHeader.biCompression = BI_RGB; 12583 12584 handle = CreateDIBSection( 12585 null, 12586 &infoheader, 12587 DIB_RGB_COLORS, 12588 cast(void**) &rawData, 12589 null, 12590 0); 12591 if(handle is null) 12592 throw new WindowsApiException("create image failed", GetLastError()); 12593 12594 } 12595 12596 void dispose() { 12597 DeleteObject(handle); 12598 } 12599 } 12600 12601 enum KEY_ESCAPE = 27; 12602 } 12603 version(X11) { 12604 /// This is the default font used. You might change this before doing anything else with 12605 /// the library if you want to try something else. Surround that in `static if(UsingSimpledisplayX11)` 12606 /// for cross-platform compatibility. 12607 //__gshared string xfontstr = "-*-dejavu sans-medium-r-*-*-12-*-*-*-*-*-*-*"; 12608 //__gshared string xfontstr = "-*-dejavu sans-medium-r-*-*-12-*-*-*-*-*-*-*"; 12609 __gshared string xfontstr = "-*-lucida-medium-r-normal-sans-12-*-*-*-*-*-*-*"; 12610 //__gshared string xfontstr = "-*-fixed-medium-r-*-*-14-*-*-*-*-*-*-*"; 12611 12612 alias int delegate(XEvent) NativeEventHandler; 12613 alias Window NativeWindowHandle; 12614 12615 enum KEY_ESCAPE = 9; 12616 12617 mixin template NativeScreenPainterImplementation() { 12618 Display* display; 12619 Drawable d; 12620 Drawable destiny; 12621 12622 // FIXME: should the gc be static too so it isn't recreated every time draw is called? 12623 GC gc; 12624 12625 __gshared bool fontAttempted; 12626 12627 __gshared XFontStruct* defaultfont; 12628 __gshared XFontSet defaultfontset; 12629 12630 XFontStruct* font; 12631 XFontSet fontset; 12632 12633 void create(PaintingHandle window) { 12634 this.display = XDisplayConnection.get(); 12635 12636 Drawable buffer = None; 12637 if(auto sw = cast(SimpleWindow) this.window) { 12638 buffer = sw.impl.buffer; 12639 this.destiny = cast(Drawable) window; 12640 } else { 12641 buffer = cast(Drawable) window; 12642 this.destiny = None; 12643 } 12644 12645 this.d = cast(Drawable) buffer; 12646 12647 auto dgc = DefaultGC(display, DefaultScreen(display)); 12648 12649 this.gc = XCreateGC(display, d, 0, null); 12650 12651 XCopyGC(display, dgc, 0xffffffff, this.gc); 12652 12653 ensureDefaultFontLoaded(); 12654 12655 font = defaultfont; 12656 fontset = defaultfontset; 12657 12658 if(font) { 12659 XSetFont(display, gc, font.fid); 12660 } 12661 } 12662 12663 static void ensureDefaultFontLoaded() { 12664 if(!fontAttempted) { 12665 auto display = XDisplayConnection.get; 12666 auto font = XLoadQueryFont(display, xfontstr.ptr); 12667 // if the user font choice fails, fixed is pretty reliable (required by X to start!) and not bad either 12668 if(font is null) { 12669 xfontstr = "-*-fixed-medium-r-*-*-13-*-*-*-*-*-*-*"; 12670 font = XLoadQueryFont(display, xfontstr.ptr); 12671 } 12672 12673 char** lol; 12674 int lol2; 12675 char* lol3; 12676 auto fontset = XCreateFontSet(display, xfontstr.ptr, &lol, &lol2, &lol3); 12677 12678 fontAttempted = true; 12679 12680 defaultfont = font; 12681 defaultfontset = fontset; 12682 } 12683 } 12684 12685 arsd.color.Rectangle _clipRectangle; 12686 void setClipRectangle(int x, int y, int width, int height) { 12687 auto old = _clipRectangle; 12688 _clipRectangle = arsd.color.Rectangle(Point(x, y), Size(width, height)); 12689 if(old == _clipRectangle) 12690 return; 12691 12692 if(width == 0 || height == 0) { 12693 XSetClipMask(display, gc, None); 12694 12695 if(xrenderPicturePainter) { 12696 12697 XRectangle[1] rects; 12698 rects[0] = XRectangle(short.min, short.min, short.max, short.max); 12699 XRenderSetPictureClipRectangles(display, xrenderPicturePainter, 0, 0, rects.ptr, cast(int) rects.length); 12700 } 12701 12702 version(with_xft) { 12703 if(xftFont is null || xftDraw is null) 12704 return; 12705 XftDrawSetClip(xftDraw, null); 12706 } 12707 } else { 12708 XRectangle[1] rects; 12709 rects[0] = XRectangle(cast(short)(x), cast(short)(y), cast(short) width, cast(short) height); 12710 XSetClipRectangles(XDisplayConnection.get, gc, 0, 0, rects.ptr, 1, 0); 12711 12712 if(xrenderPicturePainter) 12713 XRenderSetPictureClipRectangles(display, xrenderPicturePainter, 0, 0, rects.ptr, cast(int) rects.length); 12714 12715 version(with_xft) { 12716 if(xftFont is null || xftDraw is null) 12717 return; 12718 XftDrawSetClipRectangles(xftDraw, 0, 0, rects.ptr, 1); 12719 } 12720 } 12721 } 12722 12723 version(with_xft) { 12724 XftFont* xftFont; 12725 XftDraw* xftDraw; 12726 12727 XftColor xftColor; 12728 12729 void updateXftColor() { 12730 if(xftFont is null) 12731 return; 12732 12733 // not bothering with XftColorFree since p sure i don't need it on 24 bit displays.... 12734 XRenderColor colorIn = XRenderColor(_outlineColor.r * 255, _outlineColor.g * 255, _outlineColor.b * 255, _outlineColor.a * 255); 12735 12736 XftColorAllocValue( 12737 display, 12738 DefaultVisual(display, DefaultScreen(display)), 12739 DefaultColormap(display, 0), 12740 &colorIn, 12741 &xftColor 12742 ); 12743 } 12744 } 12745 12746 private OperatingSystemFont _activeFont; 12747 void setFont(OperatingSystemFont font) { 12748 _activeFont = font; 12749 version(with_xft) { 12750 if(font && font.isXft && font.xftFont) 12751 this.xftFont = font.xftFont; 12752 else 12753 this.xftFont = null; 12754 12755 if(this.xftFont) { 12756 if(xftDraw is null) { 12757 xftDraw = XftDrawCreate( 12758 display, 12759 d, 12760 DefaultVisual(display, DefaultScreen(display)), 12761 DefaultColormap(display, 0) 12762 ); 12763 12764 updateXftColor(); 12765 } 12766 12767 return; 12768 } 12769 } 12770 12771 if(font && font.font) { 12772 this.font = font.font; 12773 this.fontset = font.fontset; 12774 XSetFont(display, gc, font.font.fid); 12775 } else { 12776 this.font = defaultfont; 12777 this.fontset = defaultfontset; 12778 } 12779 12780 } 12781 12782 private Picture xrenderPicturePainter; 12783 12784 bool manualInvalidations; 12785 void invalidateRect(Rectangle invalidRect) { 12786 // FIXME if manualInvalidations 12787 } 12788 12789 void dispose() { 12790 this.rasterOp = RasterOp.normal; 12791 12792 if(xrenderPicturePainter) { 12793 XRenderFreePicture(display, xrenderPicturePainter); 12794 xrenderPicturePainter = None; 12795 } 12796 12797 // FIXME: this.window.width/height is probably wrong 12798 12799 // src x,y then dest x, y 12800 if(destiny != None) { 12801 // FIXME: if manual invalidations we can actually only copy some of the area. 12802 // if(manualInvalidations) 12803 XSetClipMask(display, gc, None); 12804 XCopyArea(display, d, destiny, gc, 0, 0, this.window.width, this.window.height, 0, 0); 12805 } 12806 12807 XFreeGC(display, gc); 12808 12809 version(with_xft) 12810 if(xftDraw) { 12811 XftDrawDestroy(xftDraw); 12812 xftDraw = null; 12813 } 12814 12815 /+ 12816 // this should prolly legit never be used since if it destroys the font handle from a OperatingSystemFont, it also ruins a reusable resource. 12817 if(font && font !is defaultfont) { 12818 XFreeFont(display, font); 12819 font = null; 12820 } 12821 if(fontset && fontset !is defaultfontset) { 12822 XFreeFontSet(display, fontset); 12823 fontset = null; 12824 } 12825 +/ 12826 XFlush(display); 12827 12828 if(window.paintingFinishedDg !is null) 12829 window.paintingFinishedDg()(); 12830 } 12831 12832 bool backgroundIsNotTransparent = true; 12833 bool foregroundIsNotTransparent = true; 12834 12835 bool _penInitialized = false; 12836 Pen _activePen; 12837 12838 Color _outlineColor; 12839 Color _fillColor; 12840 12841 @property void pen(Pen p) { 12842 if(_penInitialized && p == _activePen) { 12843 return; 12844 } 12845 _penInitialized = true; 12846 _activePen = p; 12847 _outlineColor = p.color; 12848 12849 int style; 12850 12851 byte dashLength; 12852 12853 final switch(p.style) { 12854 case Pen.Style.Solid: 12855 style = 0 /*LineSolid*/; 12856 break; 12857 case Pen.Style.Dashed: 12858 style = 1 /*LineOnOffDash*/; 12859 dashLength = 4; 12860 break; 12861 case Pen.Style.Dotted: 12862 style = 1 /*LineOnOffDash*/; 12863 dashLength = 1; 12864 break; 12865 } 12866 12867 XSetLineAttributes(display, gc, p.width, style, style == 0 ? 3 : 0, 0); 12868 if(dashLength) 12869 XSetDashes(display, gc, 0, &dashLength, 1); 12870 12871 if(p.color.a == 0) { 12872 foregroundIsNotTransparent = false; 12873 return; 12874 } 12875 12876 foregroundIsNotTransparent = true; 12877 12878 XSetForeground(display, gc, colorToX(p.color, display)); 12879 12880 version(with_xft) 12881 updateXftColor(); 12882 } 12883 12884 RasterOp _currentRasterOp; 12885 bool _currentRasterOpInitialized = false; 12886 @property void rasterOp(RasterOp op) { 12887 if(_currentRasterOpInitialized && _currentRasterOp == op) 12888 return; 12889 _currentRasterOp = op; 12890 _currentRasterOpInitialized = true; 12891 int mode; 12892 final switch(op) { 12893 case RasterOp.normal: 12894 mode = GXcopy; 12895 break; 12896 case RasterOp.xor: 12897 mode = GXxor; 12898 break; 12899 } 12900 XSetFunction(display, gc, mode); 12901 } 12902 12903 12904 bool _fillColorInitialized = false; 12905 12906 @property void fillColor(Color c) { 12907 if(_fillColorInitialized && _fillColor == c) 12908 return; // already good, no need to waste time calling it 12909 _fillColor = c; 12910 _fillColorInitialized = true; 12911 if(c.a == 0) { 12912 backgroundIsNotTransparent = false; 12913 return; 12914 } 12915 12916 backgroundIsNotTransparent = true; 12917 12918 XSetBackground(display, gc, colorToX(c, display)); 12919 12920 } 12921 12922 void swapColors() { 12923 auto tmp = _fillColor; 12924 fillColor = _outlineColor; 12925 auto newPen = _activePen; 12926 newPen.color = tmp; 12927 pen(newPen); 12928 } 12929 12930 uint colorToX(Color c, Display* display) { 12931 auto visual = DefaultVisual(display, DefaultScreen(display)); 12932 import core.bitop; 12933 uint color = 0; 12934 { 12935 auto startBit = bsf(visual.red_mask); 12936 auto lastBit = bsr(visual.red_mask); 12937 auto r = cast(uint) c.r; 12938 r >>= 7 - (lastBit - startBit); 12939 r <<= startBit; 12940 color |= r; 12941 } 12942 { 12943 auto startBit = bsf(visual.green_mask); 12944 auto lastBit = bsr(visual.green_mask); 12945 auto g = cast(uint) c.g; 12946 g >>= 7 - (lastBit - startBit); 12947 g <<= startBit; 12948 color |= g; 12949 } 12950 { 12951 auto startBit = bsf(visual.blue_mask); 12952 auto lastBit = bsr(visual.blue_mask); 12953 auto b = cast(uint) c.b; 12954 b >>= 7 - (lastBit - startBit); 12955 b <<= startBit; 12956 color |= b; 12957 } 12958 12959 12960 12961 return color; 12962 } 12963 12964 void drawImage(int x, int y, Image i, int ix, int iy, int w, int h) { 12965 // source x, source y 12966 if(ix >= i.width) return; 12967 if(iy >= i.height) return; 12968 if(ix + w > i.width) w = i.width - ix; 12969 if(iy + h > i.height) h = i.height - iy; 12970 if(i.usingXshm) 12971 XShmPutImage(display, d, gc, i.handle, ix, iy, x, y, w, h, false); 12972 else 12973 XPutImage(display, d, gc, i.handle, ix, iy, x, y, w, h); 12974 } 12975 12976 void drawPixmap(Sprite s, int x, int y, int ix, int iy, int w, int h) { 12977 if(s.enableAlpha) { 12978 // the Sprite must be created first, meaning if we're here, XRender is already loaded 12979 if(this.xrenderPicturePainter == None) { 12980 XRenderPictureAttributes attrs; 12981 // FIXME: I can prolly reuse this as long as the pixmap itself is valid. 12982 xrenderPicturePainter = XRenderCreatePicture(display, d, Sprite.RGB24, 0, &attrs); 12983 12984 // need to initialize the clip 12985 XRectangle[1] rects; 12986 rects[0] = XRectangle(cast(short)(_clipRectangle.left), cast(short)(_clipRectangle.top), cast(short) _clipRectangle.width, cast(short) _clipRectangle.height); 12987 12988 if(_clipRectangle != Rectangle.init) 12989 XRenderSetPictureClipRectangles(display, xrenderPicturePainter, 0, 0, rects.ptr, cast(int) rects.length); 12990 } 12991 12992 XRenderComposite( 12993 display, 12994 3, // PicOpOver 12995 s.xrenderPicture, 12996 None, 12997 this.xrenderPicturePainter, 12998 ix, 12999 iy, 13000 0, 13001 0, 13002 x, 13003 y, 13004 w ? w : s.width, 13005 h ? h : s.height 13006 ); 13007 } else { 13008 XCopyArea(display, s.handle, d, gc, ix, iy, w ? w : s.width, h ? h : s.height, x, y); 13009 } 13010 } 13011 13012 int fontHeight() { 13013 version(with_xft) 13014 if(xftFont !is null) 13015 return xftFont.height; 13016 if(font) 13017 return font.max_bounds.ascent + font.max_bounds.descent; 13018 return 12; // pretty common default... 13019 } 13020 13021 int textWidth(in char[] line) { 13022 version(with_xft) 13023 if(xftFont) { 13024 if(line.length == 0) 13025 return 0; 13026 XGlyphInfo extents; 13027 XftTextExtentsUtf8(display, xftFont, line.ptr, cast(int) line.length, &extents); 13028 return extents.width; 13029 } 13030 13031 if(fontset) { 13032 if(line.length == 0) 13033 return 0; 13034 XRectangle rect; 13035 Xutf8TextExtents(fontset, line.ptr, cast(int) line.length, null, &rect); 13036 13037 return rect.width; 13038 } 13039 13040 if(font) 13041 // FIXME: unicode 13042 return XTextWidth( font, line.ptr, cast(int) line.length); 13043 else 13044 return fontHeight / 2 * cast(int) line.length; // if no font is loaded, it is prolly Fixed, which is a 2:1 ratio 13045 } 13046 13047 Size textSize(in char[] text) { 13048 auto maxWidth = 0; 13049 auto lineHeight = fontHeight; 13050 int h = text.length ? 0 : lineHeight + 4; // if text is empty, it still gives the line height 13051 foreach(line; text.split('\n')) { 13052 int textWidth = this.textWidth(line); 13053 if(textWidth > maxWidth) 13054 maxWidth = textWidth; 13055 h += lineHeight + 4; 13056 } 13057 return Size(maxWidth, h); 13058 } 13059 13060 void drawText(in int x, in int y, in int x2, in int y2, in char[] originalText, in uint alignment) { 13061 const(char)[] text; 13062 version(with_xft) 13063 if(xftFont) { 13064 text = originalText; 13065 goto loaded; 13066 } 13067 13068 if(fontset) 13069 text = originalText; 13070 else { 13071 text.reserve(originalText.length); 13072 // the first 256 unicode codepoints are the same as ascii and latin-1, which is what X expects, so we can keep all those 13073 // then strip the rest so there isn't garbage 13074 foreach(dchar ch; originalText) 13075 if(ch < 256) 13076 text ~= cast(ubyte) ch; 13077 else 13078 text ~= 191; // FIXME: using a random character (upside down question mark) to fill the space 13079 } 13080 loaded: 13081 if(text.length == 0) 13082 return; 13083 13084 // FIXME: should we clip it to the bounding box? 13085 int textHeight = fontHeight; 13086 13087 auto lines = text.split('\n'); 13088 13089 const lineHeight = textHeight; 13090 textHeight *= lines.length; 13091 13092 int cy = y; 13093 13094 if(alignment & TextAlignment.VerticalBottom) { 13095 if(y2 <= 0) 13096 return; 13097 auto h = y2 - y; 13098 if(h > textHeight) { 13099 cy += h - textHeight; 13100 cy -= lineHeight / 2; 13101 } 13102 } else if(alignment & TextAlignment.VerticalCenter) { 13103 if(y2 <= 0) 13104 return; 13105 auto h = y2 - y; 13106 if(textHeight < h) { 13107 cy += (h - textHeight) / 2; 13108 //cy -= lineHeight / 4; 13109 } 13110 } 13111 13112 foreach(line; text.split('\n')) { 13113 int textWidth = this.textWidth(line); 13114 13115 int px = x, py = cy; 13116 13117 if(alignment & TextAlignment.Center) { 13118 if(x2 <= 0) 13119 return; 13120 auto w = x2 - x; 13121 if(w > textWidth) 13122 px += (w - textWidth) / 2; 13123 } else if(alignment & TextAlignment.Right) { 13124 if(x2 <= 0) 13125 return; 13126 auto pos = x2 - textWidth; 13127 if(pos > x) 13128 px = pos; 13129 } 13130 13131 version(with_xft) 13132 if(xftFont) { 13133 XftDrawStringUtf8(xftDraw, &xftColor, xftFont, px, py + xftFont.ascent, line.ptr, cast(int) line.length); 13134 13135 goto carry_on; 13136 } 13137 13138 if(fontset) 13139 Xutf8DrawString(display, d, fontset, gc, px, py + (font ? font.max_bounds.ascent : lineHeight), line.ptr, cast(int) line.length); 13140 else 13141 XDrawString(display, d, gc, px, py + (font ? font.max_bounds.ascent : lineHeight), line.ptr, cast(int) line.length); 13142 carry_on: 13143 cy += lineHeight + 4; 13144 } 13145 } 13146 13147 void drawPixel(int x, int y) { 13148 XDrawPoint(display, d, gc, x, y); 13149 } 13150 13151 // The basic shapes, outlined 13152 13153 void drawLine(int x1, int y1, int x2, int y2) { 13154 if(foregroundIsNotTransparent) 13155 XDrawLine(display, d, gc, x1, y1, x2, y2); 13156 } 13157 13158 void drawRectangle(int x, int y, int width, int height) { 13159 if(backgroundIsNotTransparent) { 13160 swapColors(); 13161 XFillRectangle(display, d, gc, x+1, y+1, width-2, height-2); // Need to ensure pixels are only drawn once... 13162 swapColors(); 13163 } 13164 // 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 13165 if(foregroundIsNotTransparent) 13166 XDrawRectangle(display, d, gc, x + _activePen.width / 2, y + _activePen.width / 2, width - 1 - _activePen.width / 2, height - 1 - _activePen.width / 2); 13167 } 13168 13169 /// Arguments are the points of the bounding rectangle 13170 void drawEllipse(int x1, int y1, int x2, int y2) { 13171 drawArc(x1, y1, x2 - x1, y2 - y1, 0, 360 * 64); 13172 } 13173 13174 // NOTE: start and finish are in units of degrees * 64 13175 void drawArc(int x1, int y1, int width, int height, int start, int finish) { 13176 if(backgroundIsNotTransparent) { 13177 swapColors(); 13178 XFillArc(display, d, gc, x1, y1, width, height, start, finish); 13179 swapColors(); 13180 } 13181 if(foregroundIsNotTransparent) { 13182 XDrawArc(display, d, gc, x1, y1, width, height, start, finish); 13183 13184 // Windows draws the straight lines on the edges too so FIXME sort of 13185 } 13186 } 13187 13188 void drawPolygon(Point[] vertexes) { 13189 XPoint[16] pointsBuffer; 13190 XPoint[] points; 13191 if(vertexes.length <= pointsBuffer.length) 13192 points = pointsBuffer[0 .. vertexes.length]; 13193 else 13194 points.length = vertexes.length; 13195 13196 foreach(i, p; vertexes) { 13197 points[i].x = cast(short) p.x; 13198 points[i].y = cast(short) p.y; 13199 } 13200 13201 if(backgroundIsNotTransparent) { 13202 swapColors(); 13203 XFillPolygon(display, d, gc, points.ptr, cast(int) points.length, PolygonShape.Complex, CoordMode.CoordModeOrigin); 13204 swapColors(); 13205 } 13206 if(foregroundIsNotTransparent) { 13207 XDrawLines(display, d, gc, points.ptr, cast(int) points.length, CoordMode.CoordModeOrigin); 13208 } 13209 } 13210 } 13211 13212 /* XRender { */ 13213 13214 struct XRenderColor { 13215 ushort red; 13216 ushort green; 13217 ushort blue; 13218 ushort alpha; 13219 } 13220 13221 alias Picture = XID; 13222 alias PictFormat = XID; 13223 13224 struct XGlyphInfo { 13225 ushort width; 13226 ushort height; 13227 short x; 13228 short y; 13229 short xOff; 13230 short yOff; 13231 } 13232 13233 struct XRenderDirectFormat { 13234 short red; 13235 short redMask; 13236 short green; 13237 short greenMask; 13238 short blue; 13239 short blueMask; 13240 short alpha; 13241 short alphaMask; 13242 } 13243 13244 struct XRenderPictFormat { 13245 PictFormat id; 13246 int type; 13247 int depth; 13248 XRenderDirectFormat direct; 13249 Colormap colormap; 13250 } 13251 13252 enum PictFormatID = (1 << 0); 13253 enum PictFormatType = (1 << 1); 13254 enum PictFormatDepth = (1 << 2); 13255 enum PictFormatRed = (1 << 3); 13256 enum PictFormatRedMask =(1 << 4); 13257 enum PictFormatGreen = (1 << 5); 13258 enum PictFormatGreenMask=(1 << 6); 13259 enum PictFormatBlue = (1 << 7); 13260 enum PictFormatBlueMask =(1 << 8); 13261 enum PictFormatAlpha = (1 << 9); 13262 enum PictFormatAlphaMask=(1 << 10); 13263 enum PictFormatColormap =(1 << 11); 13264 13265 struct XRenderPictureAttributes { 13266 int repeat; 13267 Picture alpha_map; 13268 int alpha_x_origin; 13269 int alpha_y_origin; 13270 int clip_x_origin; 13271 int clip_y_origin; 13272 Pixmap clip_mask; 13273 Bool graphics_exposures; 13274 int subwindow_mode; 13275 int poly_edge; 13276 int poly_mode; 13277 Atom dither; 13278 Bool component_alpha; 13279 } 13280 13281 alias int XFixed; 13282 13283 struct XPointFixed { 13284 XFixed x, y; 13285 } 13286 13287 struct XCircle { 13288 XFixed x; 13289 XFixed y; 13290 XFixed radius; 13291 } 13292 13293 struct XTransform { 13294 XFixed[3][3] matrix; 13295 } 13296 13297 struct XFilters { 13298 int nfilter; 13299 char **filter; 13300 int nalias; 13301 short *alias_; 13302 } 13303 13304 struct XIndexValue { 13305 c_ulong pixel; 13306 ushort red, green, blue, alpha; 13307 } 13308 13309 struct XAnimCursor { 13310 Cursor cursor; 13311 c_ulong delay; 13312 } 13313 13314 struct XLinearGradient { 13315 XPointFixed p1; 13316 XPointFixed p2; 13317 } 13318 13319 struct XRadialGradient { 13320 XCircle inner; 13321 XCircle outer; 13322 } 13323 13324 struct XConicalGradient { 13325 XPointFixed center; 13326 XFixed angle; /* in degrees */ 13327 } 13328 13329 enum PictStandardARGB32 = 0; 13330 enum PictStandardRGB24 = 1; 13331 enum PictStandardA8 = 2; 13332 enum PictStandardA4 = 3; 13333 enum PictStandardA1 = 4; 13334 enum PictStandardNUM = 5; 13335 13336 interface XRender { 13337 extern(C) @nogc: 13338 13339 Bool XRenderQueryExtension (Display *dpy, int *event_basep, int *error_basep); 13340 13341 Status XRenderQueryVersion (Display *dpy, 13342 int *major_versionp, 13343 int *minor_versionp); 13344 13345 Status XRenderQueryFormats (Display *dpy); 13346 13347 int XRenderQuerySubpixelOrder (Display *dpy, int screen); 13348 13349 Bool XRenderSetSubpixelOrder (Display *dpy, int screen, int subpixel); 13350 13351 XRenderPictFormat * 13352 XRenderFindVisualFormat (Display *dpy, const Visual *visual); 13353 13354 XRenderPictFormat * 13355 XRenderFindFormat (Display *dpy, 13356 c_ulong mask, 13357 const XRenderPictFormat *templ, 13358 int count); 13359 XRenderPictFormat * 13360 XRenderFindStandardFormat (Display *dpy, 13361 int format); 13362 13363 XIndexValue * 13364 XRenderQueryPictIndexValues(Display *dpy, 13365 const XRenderPictFormat *format, 13366 int *num); 13367 13368 Picture XRenderCreatePicture( 13369 Display *dpy, 13370 Drawable drawable, 13371 const XRenderPictFormat *format, 13372 c_ulong valuemask, 13373 const XRenderPictureAttributes *attributes); 13374 13375 void XRenderChangePicture (Display *dpy, 13376 Picture picture, 13377 c_ulong valuemask, 13378 const XRenderPictureAttributes *attributes); 13379 13380 void 13381 XRenderSetPictureClipRectangles (Display *dpy, 13382 Picture picture, 13383 int xOrigin, 13384 int yOrigin, 13385 const XRectangle *rects, 13386 int n); 13387 13388 void 13389 XRenderSetPictureClipRegion (Display *dpy, 13390 Picture picture, 13391 Region r); 13392 13393 void 13394 XRenderSetPictureTransform (Display *dpy, 13395 Picture picture, 13396 XTransform *transform); 13397 13398 void 13399 XRenderFreePicture (Display *dpy, 13400 Picture picture); 13401 13402 void 13403 XRenderComposite (Display *dpy, 13404 int op, 13405 Picture src, 13406 Picture mask, 13407 Picture dst, 13408 int src_x, 13409 int src_y, 13410 int mask_x, 13411 int mask_y, 13412 int dst_x, 13413 int dst_y, 13414 uint width, 13415 uint height); 13416 13417 13418 Picture XRenderCreateSolidFill (Display *dpy, 13419 const XRenderColor *color); 13420 13421 Picture XRenderCreateLinearGradient (Display *dpy, 13422 const XLinearGradient *gradient, 13423 const XFixed *stops, 13424 const XRenderColor *colors, 13425 int nstops); 13426 13427 Picture XRenderCreateRadialGradient (Display *dpy, 13428 const XRadialGradient *gradient, 13429 const XFixed *stops, 13430 const XRenderColor *colors, 13431 int nstops); 13432 13433 Picture XRenderCreateConicalGradient (Display *dpy, 13434 const XConicalGradient *gradient, 13435 const XFixed *stops, 13436 const XRenderColor *colors, 13437 int nstops); 13438 13439 13440 13441 Cursor 13442 XRenderCreateCursor (Display *dpy, 13443 Picture source, 13444 uint x, 13445 uint y); 13446 13447 XFilters * 13448 XRenderQueryFilters (Display *dpy, Drawable drawable); 13449 13450 void 13451 XRenderSetPictureFilter (Display *dpy, 13452 Picture picture, 13453 const char *filter, 13454 XFixed *params, 13455 int nparams); 13456 13457 Cursor 13458 XRenderCreateAnimCursor (Display *dpy, 13459 int ncursor, 13460 XAnimCursor *cursors); 13461 } 13462 13463 __gshared bool XRenderLibrarySuccessfullyLoaded = true; 13464 mixin DynamicLoad!(XRender, "Xrender", 1, XRenderLibrarySuccessfullyLoaded) XRenderLibrary; 13465 13466 /* XRender } */ 13467 13468 /* Xrandr { */ 13469 13470 struct XRRMonitorInfo { 13471 Atom name; 13472 Bool primary; 13473 Bool automatic; 13474 int noutput; 13475 int x; 13476 int y; 13477 int width; 13478 int height; 13479 int mwidth; 13480 int mheight; 13481 /*RROutput*/ void *outputs; 13482 } 13483 13484 struct XRRScreenChangeNotifyEvent { 13485 int type; /* event base */ 13486 c_ulong serial; /* # of last request processed by server */ 13487 Bool send_event; /* true if this came from a SendEvent request */ 13488 Display *display; /* Display the event was read from */ 13489 Window window; /* window which selected for this event */ 13490 Window root; /* Root window for changed screen */ 13491 Time timestamp; /* when the screen change occurred */ 13492 Time config_timestamp; /* when the last configuration change */ 13493 ushort/*SizeID*/ size_index; 13494 ushort/*SubpixelOrder*/ subpixel_order; 13495 ushort/*Rotation*/ rotation; 13496 int width; 13497 int height; 13498 int mwidth; 13499 int mheight; 13500 } 13501 13502 enum RRScreenChangeNotify = 0; 13503 13504 enum RRScreenChangeNotifyMask = 1; 13505 13506 __gshared int xrrEventBase = -1; 13507 13508 13509 interface XRandr { 13510 extern(C) @nogc: 13511 Bool XRRQueryExtension (Display *dpy, int *event_base_return, int *error_base_return); 13512 Status XRRQueryVersion (Display *dpy, int *major_version_return, int *minor_version_return); 13513 13514 XRRMonitorInfo * XRRGetMonitors(Display *dpy, Window window, Bool get_active, int *nmonitors); 13515 void XRRFreeMonitors(XRRMonitorInfo *monitors); 13516 13517 void XRRSelectInput(Display *dpy, Window window, int mask); 13518 } 13519 13520 __gshared bool XRandrLibrarySuccessfullyLoaded = true; 13521 mixin DynamicLoad!(XRandr, "Xrandr", 2, XRandrLibrarySuccessfullyLoaded) XRandrLibrary; 13522 /* Xrandr } */ 13523 13524 /* Xft { */ 13525 13526 // actually freetype 13527 alias void FT_Face; 13528 13529 // actually fontconfig 13530 private alias FcBool = int; 13531 alias void FcCharSet; 13532 alias void FcPattern; 13533 alias void FcResult; 13534 enum FcEndian { FcEndianBig, FcEndianLittle } 13535 struct FcFontSet { 13536 int nfont; 13537 int sfont; 13538 FcPattern** fonts; 13539 } 13540 13541 // actually XRegion 13542 struct BOX { 13543 short x1, x2, y1, y2; 13544 } 13545 struct _XRegion { 13546 c_long size; 13547 c_long numRects; 13548 BOX* rects; 13549 BOX extents; 13550 } 13551 13552 alias Region = _XRegion*; 13553 13554 // ok actually Xft 13555 13556 struct XftFontInfo; 13557 13558 struct XftFont { 13559 int ascent; 13560 int descent; 13561 int height; 13562 int max_advance_width; 13563 FcCharSet* charset; 13564 FcPattern* pattern; 13565 } 13566 13567 struct XftDraw; 13568 13569 struct XftColor { 13570 c_ulong pixel; 13571 XRenderColor color; 13572 } 13573 13574 struct XftCharSpec { 13575 dchar ucs4; 13576 short x; 13577 short y; 13578 } 13579 13580 struct XftCharFontSpec { 13581 XftFont *font; 13582 dchar ucs4; 13583 short x; 13584 short y; 13585 } 13586 13587 struct XftGlyphSpec { 13588 uint glyph; 13589 short x; 13590 short y; 13591 } 13592 13593 struct XftGlyphFontSpec { 13594 XftFont *font; 13595 uint glyph; 13596 short x; 13597 short y; 13598 } 13599 13600 interface Xft { 13601 extern(C) @nogc pure: 13602 13603 Bool XftColorAllocName (Display *dpy, 13604 const Visual *visual, 13605 Colormap cmap, 13606 const char *name, 13607 XftColor *result); 13608 13609 Bool XftColorAllocValue (Display *dpy, 13610 Visual *visual, 13611 Colormap cmap, 13612 const XRenderColor *color, 13613 XftColor *result); 13614 13615 void XftColorFree (Display *dpy, 13616 Visual *visual, 13617 Colormap cmap, 13618 XftColor *color); 13619 13620 Bool XftDefaultHasRender (Display *dpy); 13621 13622 Bool XftDefaultSet (Display *dpy, FcPattern *defaults); 13623 13624 void XftDefaultSubstitute (Display *dpy, int screen, FcPattern *pattern); 13625 13626 XftDraw * XftDrawCreate (Display *dpy, 13627 Drawable drawable, 13628 Visual *visual, 13629 Colormap colormap); 13630 13631 XftDraw * XftDrawCreateBitmap (Display *dpy, 13632 Pixmap bitmap); 13633 13634 XftDraw * XftDrawCreateAlpha (Display *dpy, 13635 Pixmap pixmap, 13636 int depth); 13637 13638 void XftDrawChange (XftDraw *draw, 13639 Drawable drawable); 13640 13641 Display * XftDrawDisplay (XftDraw *draw); 13642 13643 Drawable XftDrawDrawable (XftDraw *draw); 13644 13645 Colormap XftDrawColormap (XftDraw *draw); 13646 13647 Visual * XftDrawVisual (XftDraw *draw); 13648 13649 void XftDrawDestroy (XftDraw *draw); 13650 13651 Picture XftDrawPicture (XftDraw *draw); 13652 13653 Picture XftDrawSrcPicture (XftDraw *draw, const XftColor *color); 13654 13655 void XftDrawGlyphs (XftDraw *draw, 13656 const XftColor *color, 13657 XftFont *pub, 13658 int x, 13659 int y, 13660 const uint *glyphs, 13661 int nglyphs); 13662 13663 void XftDrawString8 (XftDraw *draw, 13664 const XftColor *color, 13665 XftFont *pub, 13666 int x, 13667 int y, 13668 const char *string, 13669 int len); 13670 13671 void XftDrawString16 (XftDraw *draw, 13672 const XftColor *color, 13673 XftFont *pub, 13674 int x, 13675 int y, 13676 const wchar *string, 13677 int len); 13678 13679 void XftDrawString32 (XftDraw *draw, 13680 const XftColor *color, 13681 XftFont *pub, 13682 int x, 13683 int y, 13684 const dchar *string, 13685 int len); 13686 13687 void XftDrawStringUtf8 (XftDraw *draw, 13688 const XftColor *color, 13689 XftFont *pub, 13690 int x, 13691 int y, 13692 const char *string, 13693 int len); 13694 void XftDrawStringUtf16 (XftDraw *draw, 13695 const XftColor *color, 13696 XftFont *pub, 13697 int x, 13698 int y, 13699 const char *string, 13700 FcEndian endian, 13701 int len); 13702 13703 void XftDrawCharSpec (XftDraw *draw, 13704 const XftColor *color, 13705 XftFont *pub, 13706 const XftCharSpec *chars, 13707 int len); 13708 13709 void XftDrawCharFontSpec (XftDraw *draw, 13710 const XftColor *color, 13711 const XftCharFontSpec *chars, 13712 int len); 13713 13714 void XftDrawGlyphSpec (XftDraw *draw, 13715 const XftColor *color, 13716 XftFont *pub, 13717 const XftGlyphSpec *glyphs, 13718 int len); 13719 13720 void XftDrawGlyphFontSpec (XftDraw *draw, 13721 const XftColor *color, 13722 const XftGlyphFontSpec *glyphs, 13723 int len); 13724 13725 void XftDrawRect (XftDraw *draw, 13726 const XftColor *color, 13727 int x, 13728 int y, 13729 uint width, 13730 uint height); 13731 13732 Bool XftDrawSetClip (XftDraw *draw, 13733 Region r); 13734 13735 13736 Bool XftDrawSetClipRectangles (XftDraw *draw, 13737 int xOrigin, 13738 int yOrigin, 13739 const XRectangle *rects, 13740 int n); 13741 13742 void XftDrawSetSubwindowMode (XftDraw *draw, 13743 int mode); 13744 13745 void XftGlyphExtents (Display *dpy, 13746 XftFont *pub, 13747 const uint *glyphs, 13748 int nglyphs, 13749 XGlyphInfo *extents); 13750 13751 void XftTextExtents8 (Display *dpy, 13752 XftFont *pub, 13753 const char *string, 13754 int len, 13755 XGlyphInfo *extents); 13756 13757 void XftTextExtents16 (Display *dpy, 13758 XftFont *pub, 13759 const wchar *string, 13760 int len, 13761 XGlyphInfo *extents); 13762 13763 void XftTextExtents32 (Display *dpy, 13764 XftFont *pub, 13765 const dchar *string, 13766 int len, 13767 XGlyphInfo *extents); 13768 13769 void XftTextExtentsUtf8 (Display *dpy, 13770 XftFont *pub, 13771 const char *string, 13772 int len, 13773 XGlyphInfo *extents); 13774 13775 void XftTextExtentsUtf16 (Display *dpy, 13776 XftFont *pub, 13777 const char *string, 13778 FcEndian endian, 13779 int len, 13780 XGlyphInfo *extents); 13781 13782 FcPattern * XftFontMatch (Display *dpy, 13783 int screen, 13784 const FcPattern *pattern, 13785 FcResult *result); 13786 13787 XftFont * XftFontOpen (Display *dpy, int screen, ...); 13788 13789 XftFont * XftFontOpenName (Display *dpy, int screen, const char *name); 13790 13791 XftFont * XftFontOpenXlfd (Display *dpy, int screen, const char *xlfd); 13792 13793 FT_Face XftLockFace (XftFont *pub); 13794 13795 void XftUnlockFace (XftFont *pub); 13796 13797 XftFontInfo * XftFontInfoCreate (Display *dpy, const FcPattern *pattern); 13798 13799 void XftFontInfoDestroy (Display *dpy, XftFontInfo *fi); 13800 13801 dchar XftFontInfoHash (const XftFontInfo *fi); 13802 13803 FcBool XftFontInfoEqual (const XftFontInfo *a, const XftFontInfo *b); 13804 13805 XftFont * XftFontOpenInfo (Display *dpy, 13806 FcPattern *pattern, 13807 XftFontInfo *fi); 13808 13809 XftFont * XftFontOpenPattern (Display *dpy, FcPattern *pattern); 13810 13811 XftFont * XftFontCopy (Display *dpy, XftFont *pub); 13812 13813 void XftFontClose (Display *dpy, XftFont *pub); 13814 13815 FcBool XftInitFtLibrary(); 13816 void XftFontLoadGlyphs (Display *dpy, 13817 XftFont *pub, 13818 FcBool need_bitmaps, 13819 const uint *glyphs, 13820 int nglyph); 13821 13822 void XftFontUnloadGlyphs (Display *dpy, 13823 XftFont *pub, 13824 const uint *glyphs, 13825 int nglyph); 13826 13827 FcBool XftFontCheckGlyph (Display *dpy, 13828 XftFont *pub, 13829 FcBool need_bitmaps, 13830 uint glyph, 13831 uint *missing, 13832 int *nmissing); 13833 13834 FcBool XftCharExists (Display *dpy, 13835 XftFont *pub, 13836 dchar ucs4); 13837 13838 uint XftCharIndex (Display *dpy, 13839 XftFont *pub, 13840 dchar ucs4); 13841 FcBool XftInit (const char *config); 13842 13843 int XftGetVersion (); 13844 13845 FcFontSet * XftListFonts (Display *dpy, 13846 int screen, 13847 ...); 13848 13849 FcPattern *XftNameParse (const char *name); 13850 13851 void XftGlyphRender (Display *dpy, 13852 int op, 13853 Picture src, 13854 XftFont *pub, 13855 Picture dst, 13856 int srcx, 13857 int srcy, 13858 int x, 13859 int y, 13860 const uint *glyphs, 13861 int nglyphs); 13862 13863 void XftGlyphSpecRender (Display *dpy, 13864 int op, 13865 Picture src, 13866 XftFont *pub, 13867 Picture dst, 13868 int srcx, 13869 int srcy, 13870 const XftGlyphSpec *glyphs, 13871 int nglyphs); 13872 13873 void XftCharSpecRender (Display *dpy, 13874 int op, 13875 Picture src, 13876 XftFont *pub, 13877 Picture dst, 13878 int srcx, 13879 int srcy, 13880 const XftCharSpec *chars, 13881 int len); 13882 void XftGlyphFontSpecRender (Display *dpy, 13883 int op, 13884 Picture src, 13885 Picture dst, 13886 int srcx, 13887 int srcy, 13888 const XftGlyphFontSpec *glyphs, 13889 int nglyphs); 13890 13891 void XftCharFontSpecRender (Display *dpy, 13892 int op, 13893 Picture src, 13894 Picture dst, 13895 int srcx, 13896 int srcy, 13897 const XftCharFontSpec *chars, 13898 int len); 13899 13900 void XftTextRender8 (Display *dpy, 13901 int op, 13902 Picture src, 13903 XftFont *pub, 13904 Picture dst, 13905 int srcx, 13906 int srcy, 13907 int x, 13908 int y, 13909 const char *string, 13910 int len); 13911 void XftTextRender16 (Display *dpy, 13912 int op, 13913 Picture src, 13914 XftFont *pub, 13915 Picture dst, 13916 int srcx, 13917 int srcy, 13918 int x, 13919 int y, 13920 const wchar *string, 13921 int len); 13922 13923 void XftTextRender16BE (Display *dpy, 13924 int op, 13925 Picture src, 13926 XftFont *pub, 13927 Picture dst, 13928 int srcx, 13929 int srcy, 13930 int x, 13931 int y, 13932 const char *string, 13933 int len); 13934 13935 void XftTextRender16LE (Display *dpy, 13936 int op, 13937 Picture src, 13938 XftFont *pub, 13939 Picture dst, 13940 int srcx, 13941 int srcy, 13942 int x, 13943 int y, 13944 const char *string, 13945 int len); 13946 13947 void XftTextRender32 (Display *dpy, 13948 int op, 13949 Picture src, 13950 XftFont *pub, 13951 Picture dst, 13952 int srcx, 13953 int srcy, 13954 int x, 13955 int y, 13956 const dchar *string, 13957 int len); 13958 13959 void XftTextRender32BE (Display *dpy, 13960 int op, 13961 Picture src, 13962 XftFont *pub, 13963 Picture dst, 13964 int srcx, 13965 int srcy, 13966 int x, 13967 int y, 13968 const char *string, 13969 int len); 13970 13971 void XftTextRender32LE (Display *dpy, 13972 int op, 13973 Picture src, 13974 XftFont *pub, 13975 Picture dst, 13976 int srcx, 13977 int srcy, 13978 int x, 13979 int y, 13980 const char *string, 13981 int len); 13982 13983 void XftTextRenderUtf8 (Display *dpy, 13984 int op, 13985 Picture src, 13986 XftFont *pub, 13987 Picture dst, 13988 int srcx, 13989 int srcy, 13990 int x, 13991 int y, 13992 const char *string, 13993 int len); 13994 13995 void XftTextRenderUtf16 (Display *dpy, 13996 int op, 13997 Picture src, 13998 XftFont *pub, 13999 Picture dst, 14000 int srcx, 14001 int srcy, 14002 int x, 14003 int y, 14004 const char *string, 14005 FcEndian endian, 14006 int len); 14007 FcPattern * XftXlfdParse (const char *xlfd_orig, Bool ignore_scalable, Bool complete); 14008 14009 } 14010 14011 interface FontConfig { 14012 extern(C) @nogc pure: 14013 int FcPatternGetString(const FcPattern *p, const char *object, int n, char ** s); 14014 void FcFontSetDestroy(FcFontSet*); 14015 char* FcNameUnparse(const FcPattern *); 14016 } 14017 14018 mixin DynamicLoad!(Xft, "Xft", 2, librariesSuccessfullyLoaded) XftLibrary; 14019 mixin DynamicLoad!(FontConfig, "fontconfig", 1, librariesSuccessfullyLoaded) FontConfigLibrary; 14020 14021 14022 /* Xft } */ 14023 14024 class XDisconnectException : Exception { 14025 bool userRequested; 14026 this(bool userRequested = true) { 14027 this.userRequested = userRequested; 14028 super("X disconnected"); 14029 } 14030 } 14031 14032 /++ 14033 Platform-specific for X11. Traps errors for the duration of `dg`. Avoid calling this from inside a call to this. 14034 14035 Please note that it returns 14036 +/ 14037 XErrorEvent[] trapXErrors(scope void delegate() dg) { 14038 14039 static XErrorEvent[] errorBuffer; 14040 14041 static extern(C) int handler (Display* dpy, XErrorEvent* evt) nothrow { 14042 errorBuffer ~= *evt; 14043 return 0; 14044 } 14045 14046 auto savedErrorHandler = XSetErrorHandler(&handler); 14047 14048 try { 14049 dg(); 14050 } finally { 14051 XSync(XDisplayConnection.get, 0/*False*/); 14052 XSetErrorHandler(savedErrorHandler); 14053 } 14054 14055 auto bfr = errorBuffer; 14056 errorBuffer = null; 14057 14058 return bfr; 14059 } 14060 14061 /// Platform-specific for X11. A singleton class (well, all its methods are actually static... so more like a namespace) wrapping a `Display*`. 14062 class XDisplayConnection { 14063 private __gshared Display* display; 14064 private __gshared XIM xim; 14065 private __gshared char* displayName; 14066 14067 private __gshared int connectionSequence_; 14068 private __gshared bool isLocal_; 14069 14070 /// use this for lazy caching when reconnection 14071 static int connectionSequenceNumber() { return connectionSequence_; } 14072 14073 /++ 14074 Guesses if the connection appears to be local. 14075 14076 History: 14077 Added June 3, 2021 14078 +/ 14079 static @property bool isLocal() nothrow @trusted @nogc { 14080 return isLocal_; 14081 } 14082 14083 /// Attempts recreation of state, may require application assistance 14084 /// You MUST call this OUTSIDE the event loop. Let the exception kill the loop, 14085 /// then call this, and if successful, reenter the loop. 14086 static void discardAndRecreate(string newDisplayString = null) { 14087 if(insideXEventLoop) 14088 throw new Error("You MUST call discardAndRecreate from OUTSIDE the event loop"); 14089 14090 // 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 14091 auto chnenhm = CapableOfHandlingNativeEvent.nativeHandleMapping.dup; 14092 14093 foreach(handle; chnenhm) { 14094 handle.discardConnectionState(); 14095 } 14096 14097 discardState(); 14098 14099 if(newDisplayString !is null) 14100 setDisplayName(newDisplayString); 14101 14102 auto display = get(); 14103 14104 foreach(handle; chnenhm) { 14105 handle.recreateAfterDisconnect(); 14106 } 14107 } 14108 14109 private __gshared EventMask rootEventMask; 14110 14111 /++ 14112 Requests the specified input from the root window on the connection, in addition to any other request. 14113 14114 14115 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. 14116 14117 $(WARNING it calls XSelectInput itself, which will override any other root window input you have!) 14118 +/ 14119 static void addRootInput(EventMask mask) { 14120 auto old = rootEventMask; 14121 rootEventMask |= mask; 14122 get(); // to ensure display connected 14123 if(display !is null && rootEventMask != old) 14124 XSelectInput(display, RootWindow(display, DefaultScreen(display)), rootEventMask); 14125 } 14126 14127 static void discardState() { 14128 freeImages(); 14129 14130 foreach(atomPtr; interredAtoms) 14131 *atomPtr = 0; 14132 interredAtoms = null; 14133 interredAtoms.assumeSafeAppend(); 14134 14135 ScreenPainterImplementation.fontAttempted = false; 14136 ScreenPainterImplementation.defaultfont = null; 14137 ScreenPainterImplementation.defaultfontset = null; 14138 14139 Image.impl.xshmQueryCompleted = false; 14140 Image.impl._xshmAvailable = false; 14141 14142 SimpleWindow.nativeMapping = null; 14143 CapableOfHandlingNativeEvent.nativeHandleMapping = null; 14144 // GlobalHotkeyManager 14145 14146 display = null; 14147 xim = null; 14148 } 14149 14150 // Do you want to know why do we need all this horrible-looking code? See comment at the bottom. 14151 private static void createXIM () { 14152 import core.stdc.locale : setlocale, LC_ALL; 14153 import core.stdc.stdio : stderr, fprintf; 14154 import core.stdc.stdlib : free; 14155 import core.stdc.string : strdup; 14156 14157 static immutable string[3] mtry = [ "", "@im=local", "@im=" ]; 14158 14159 auto olocale = strdup(setlocale(LC_ALL, null)); 14160 setlocale(LC_ALL, (sdx_isUTF8Locale ? "" : "en_US.UTF-8")); 14161 scope(exit) { setlocale(LC_ALL, olocale); free(olocale); } 14162 14163 //fprintf(stderr, "opening IM...\n"); 14164 foreach (string s; mtry) { 14165 XSetLocaleModifiers(s.ptr); // it's safe, as `s` is string literal 14166 if ((xim = XOpenIM(display, null, null, null)) !is null) return; 14167 } 14168 fprintf(stderr, "createXIM: XOpenIM failed!\n"); 14169 } 14170 14171 // for X11 we will keep all XShm-allocated images in this list, so we can free 'em on connection closing. 14172 // we'll use glibc malloc()/free(), 'cause `unregisterImage()` can be called from object dtor. 14173 static struct ImgList { 14174 size_t img; // class; hide it from GC 14175 ImgList* next; 14176 } 14177 14178 static __gshared ImgList* imglist = null; 14179 static __gshared bool imglistLocked = false; // true: don't register and unregister images 14180 14181 static void registerImage (Image img) { 14182 if (!imglistLocked && img !is null) { 14183 import core.stdc.stdlib : malloc; 14184 auto it = cast(ImgList*)malloc(ImgList.sizeof); 14185 assert(it !is null); // do proper checks 14186 it.img = cast(size_t)cast(void*)img; 14187 it.next = imglist; 14188 imglist = it; 14189 version(sdpy_debug_xshm) { import core.stdc.stdio : printf; printf("registering image %p\n", cast(void*)img); } 14190 } 14191 } 14192 14193 static void unregisterImage (Image img) { 14194 if (!imglistLocked && img !is null) { 14195 import core.stdc.stdlib : free; 14196 ImgList* prev = null; 14197 ImgList* cur = imglist; 14198 while (cur !is null) { 14199 if (cur.img == cast(size_t)cast(void*)img) break; // i found her! 14200 prev = cur; 14201 cur = cur.next; 14202 } 14203 if (cur !is null) { 14204 if (prev is null) imglist = cur.next; else prev.next = cur.next; 14205 free(cur); 14206 version(sdpy_debug_xshm) { import core.stdc.stdio : printf; printf("unregistering image %p\n", cast(void*)img); } 14207 } else { 14208 version(sdpy_debug_xshm) { import core.stdc.stdio : printf; printf("trying to unregister unknown image %p\n", cast(void*)img); } 14209 } 14210 } 14211 } 14212 14213 static void freeImages () { // needed for discardAndRecreate 14214 imglistLocked = true; 14215 scope(exit) imglistLocked = false; 14216 ImgList* cur = imglist; 14217 ImgList* next = null; 14218 while (cur !is null) { 14219 import core.stdc.stdlib : free; 14220 next = cur.next; 14221 version(sdpy_debug_xshm) { import core.stdc.stdio : printf; printf("disposing image %p\n", cast(void*)cur.img); } 14222 (cast(Image)cast(void*)cur.img).dispose(); 14223 free(cur); 14224 cur = next; 14225 } 14226 imglist = null; 14227 } 14228 14229 /// can be used to override normal handling of display name 14230 /// from environment and/or command line 14231 static setDisplayName(string newDisplayName) { 14232 displayName = cast(char*) (newDisplayName ~ '\0'); 14233 } 14234 14235 /// resets to the default display string 14236 static resetDisplayName() { 14237 displayName = null; 14238 } 14239 14240 /// 14241 static Display* get() { 14242 if(display is null) { 14243 if(!librariesSuccessfullyLoaded) 14244 throw new Exception("Unable to load X11 client libraries"); 14245 display = XOpenDisplay(displayName); 14246 14247 isLocal_ = false; 14248 14249 connectionSequence_++; 14250 if(display is null) 14251 throw new Exception("Unable to open X display"); 14252 14253 auto str = display.display_name; 14254 // this is a bit of a hack but like if it looks like a unix socket we assume it is local 14255 // and otherwise it probably isn't 14256 if(str is null || (str[0] != ':' && str[0] != '/')) 14257 isLocal_ = false; 14258 else 14259 isLocal_ = true; 14260 14261 debug(sdpy_x_errors) { 14262 XSetErrorHandler(&adrlogger); 14263 XSynchronize(display, true); 14264 14265 extern(C) int wtf() { 14266 if(errorHappened) { 14267 asm { int 3; } 14268 errorHappened = false; 14269 } 14270 return 0; 14271 } 14272 XSetAfterFunction(display, &wtf); 14273 } 14274 14275 14276 XSetIOErrorHandler(&x11ioerrCB); 14277 Bool sup; 14278 XkbSetDetectableAutoRepeat(display, 1, &sup); // so we will not receive KeyRelease until key is really released 14279 createXIM(); 14280 version(with_eventloop) { 14281 import arsd.eventloop; 14282 addFileEventListeners(display.fd, &eventListener, null, null); 14283 } 14284 } 14285 14286 return display; 14287 } 14288 14289 extern(C) 14290 static int x11ioerrCB(Display* dpy) { 14291 throw new XDisconnectException(false); 14292 } 14293 14294 version(with_eventloop) { 14295 import arsd.eventloop; 14296 static void eventListener(OsFileHandle fd) { 14297 //this.mtLock(); 14298 //scope(exit) this.mtUnlock(); 14299 while(XPending(display)) 14300 doXNextEvent(display); 14301 } 14302 } 14303 14304 // close connection on program exit -- we need this to properly free all images 14305 static ~this () { 14306 // the gui thread must clean up after itself or else Xlib might deadlock 14307 // using this flag on any thread destruction is the easiest way i know of 14308 // (shared static this is run by the LAST thread to exit, which may not be 14309 // the gui thread, and normal static this run by ALL threads, so we gotta check.) 14310 if(thisIsGuiThread) 14311 close(); 14312 } 14313 14314 /// 14315 static void close() { 14316 if(display is null) 14317 return; 14318 14319 version(with_eventloop) { 14320 import arsd.eventloop; 14321 removeFileEventListeners(display.fd); 14322 } 14323 14324 // now remove all registered images to prevent shared memory leaks 14325 freeImages(); 14326 14327 // tbh I don't know why it is doing this but like if this happens to run 14328 // from the other thread there's frequent hanging inside here. 14329 if(thisIsGuiThread) 14330 XCloseDisplay(display); 14331 display = null; 14332 } 14333 } 14334 14335 mixin template NativeImageImplementation() { 14336 XImage* handle; 14337 ubyte* rawData; 14338 14339 XShmSegmentInfo shminfo; 14340 bool premultiply = true; 14341 14342 __gshared bool xshmQueryCompleted; 14343 __gshared bool _xshmAvailable; 14344 public static @property bool xshmAvailable() { 14345 if(!xshmQueryCompleted) { 14346 int i1, i2, i3; 14347 xshmQueryCompleted = true; 14348 14349 if(!XDisplayConnection.isLocal) 14350 _xshmAvailable = false; 14351 else 14352 _xshmAvailable = XQueryExtension(XDisplayConnection.get(), "MIT-SHM", &i1, &i2, &i3) != 0; 14353 } 14354 return _xshmAvailable; 14355 } 14356 14357 bool usingXshm; 14358 final: 14359 14360 private __gshared bool xshmfailed; 14361 14362 void createImage(int width, int height, bool forcexshm=false, bool enableAlpha = false) { 14363 auto display = XDisplayConnection.get(); 14364 assert(display !is null); 14365 auto screen = DefaultScreen(display); 14366 14367 // it will only use shared memory for somewhat largish images, 14368 // since otherwise we risk wasting shared memory handles on a lot of little ones 14369 if (xshmAvailable && (forcexshm || (width > 100 && height > 100))) { 14370 14371 14372 // it is possible for the query extension to return true, the DISPLAY check to pass, yet 14373 // the actual use still fails. For example, if the program is in a container and permission denied 14374 // on shared memory, or if it is a local thing forwarded to a remote server, etc. 14375 // 14376 // If it does fail, we need to detect it now, abort the xshm and fall back to core protocol. 14377 14378 14379 // synchronize so preexisting buffers are clear 14380 XSync(display, false); 14381 xshmfailed = false; 14382 14383 auto oldErrorHandler = XSetErrorHandler(&XShmErrorHandler); 14384 14385 14386 usingXshm = true; 14387 handle = XShmCreateImage( 14388 display, 14389 DefaultVisual(display, screen), 14390 enableAlpha ? 32: 24, 14391 ImageFormat.ZPixmap, 14392 null, 14393 &shminfo, 14394 width, height); 14395 if(handle is null) 14396 goto abortXshm1; 14397 14398 if(handle.bytes_per_line != 4 * width) 14399 goto abortXshm2; 14400 14401 shminfo.shmid = shmget(IPC_PRIVATE, handle.bytes_per_line * height, IPC_CREAT | 511 /* 0777 */); 14402 if(shminfo.shmid < 0) 14403 goto abortXshm3; 14404 handle.data = shminfo.shmaddr = rawData = cast(ubyte*) shmat(shminfo.shmid, null, 0); 14405 if(rawData == cast(ubyte*) -1) 14406 goto abortXshm4; 14407 shminfo.readOnly = 0; 14408 XShmAttach(display, &shminfo); 14409 14410 // and now to the final error check to ensure it actually worked. 14411 XSync(display, false); 14412 if(xshmfailed) 14413 goto abortXshm5; 14414 14415 XSetErrorHandler(oldErrorHandler); 14416 14417 XDisplayConnection.registerImage(this); 14418 // if I don't flush here there's a chance the dtor will run before the 14419 // ctor and lead to a bad value X error. While this hurts the efficiency 14420 // it is local anyway so prolly better to keep it simple 14421 XFlush(display); 14422 14423 return; 14424 14425 abortXshm5: 14426 shmdt(shminfo.shmaddr); 14427 rawData = null; 14428 14429 abortXshm4: 14430 shmctl(shminfo.shmid, IPC_RMID, null); 14431 14432 abortXshm3: 14433 // nothing needed, the shmget failed so there's nothing to free 14434 14435 abortXshm2: 14436 XDestroyImage(handle); 14437 handle = null; 14438 14439 abortXshm1: 14440 XSetErrorHandler(oldErrorHandler); 14441 usingXshm = false; 14442 handle = null; 14443 14444 shminfo = typeof(shminfo).init; 14445 14446 _xshmAvailable = false; // don't try again in the future 14447 14448 // writeln("fallingback"); 14449 14450 goto fallback; 14451 14452 } else { 14453 fallback: 14454 14455 if (forcexshm) throw new Exception("can't create XShm Image"); 14456 // This actually needs to be malloc to avoid a double free error when XDestroyImage is called 14457 import core.stdc.stdlib : malloc; 14458 rawData = cast(ubyte*) malloc(width * height * 4); 14459 14460 handle = XCreateImage( 14461 display, 14462 DefaultVisual(display, screen), 14463 enableAlpha ? 32 : 24, // bpp 14464 ImageFormat.ZPixmap, 14465 0, // offset 14466 rawData, 14467 width, height, 14468 enableAlpha ? 32 : 8 /* FIXME */, 4 * width); // padding, bytes per line 14469 } 14470 } 14471 14472 void dispose() { 14473 // note: this calls free(rawData) for us 14474 if(handle) { 14475 if (usingXshm) { 14476 XDisplayConnection.unregisterImage(this); 14477 if (XDisplayConnection.get()) XShmDetach(XDisplayConnection.get(), &shminfo); 14478 } 14479 XDestroyImage(handle); 14480 if(usingXshm) { 14481 shmdt(shminfo.shmaddr); 14482 shmctl(shminfo.shmid, IPC_RMID, null); 14483 } 14484 handle = null; 14485 } 14486 } 14487 14488 Color getPixel(int x, int y) { 14489 auto offset = (y * width + x) * 4; 14490 Color c; 14491 c.a = enableAlpha ? rawData[offset + 3] : 255; 14492 c.b = rawData[offset + 0]; 14493 c.g = rawData[offset + 1]; 14494 c.r = rawData[offset + 2]; 14495 if(enableAlpha && premultiply) 14496 c.unPremultiply; 14497 return c; 14498 } 14499 14500 void setPixel(int x, int y, Color c) { 14501 if(enableAlpha && premultiply) 14502 c.premultiply(); 14503 auto offset = (y * width + x) * 4; 14504 rawData[offset + 0] = c.b; 14505 rawData[offset + 1] = c.g; 14506 rawData[offset + 2] = c.r; 14507 if(enableAlpha) 14508 rawData[offset + 3] = c.a; 14509 } 14510 14511 void convertToRgbaBytes(ubyte[] where) { 14512 assert(where.length == this.width * this.height * 4); 14513 14514 // if rawData had a length.... 14515 //assert(rawData.length == where.length); 14516 for(int idx = 0; idx < where.length; idx += 4) { 14517 where[idx + 0] = rawData[idx + 2]; // r 14518 where[idx + 1] = rawData[idx + 1]; // g 14519 where[idx + 2] = rawData[idx + 0]; // b 14520 where[idx + 3] = enableAlpha ? rawData[idx + 3] : 255; // a 14521 14522 if(enableAlpha && premultiply) 14523 unPremultiplyRgba(where[idx .. idx + 4]); 14524 } 14525 } 14526 14527 void setFromRgbaBytes(in ubyte[] where) { 14528 assert(where.length == this.width * this.height * 4); 14529 14530 // if rawData had a length.... 14531 //assert(rawData.length == where.length); 14532 for(int idx = 0; idx < where.length; idx += 4) { 14533 rawData[idx + 2] = where[idx + 0]; // r 14534 rawData[idx + 1] = where[idx + 1]; // g 14535 rawData[idx + 0] = where[idx + 2]; // b 14536 if(enableAlpha) { 14537 rawData[idx + 3] = where[idx + 3]; // a 14538 if(premultiply) 14539 premultiplyBgra(rawData[idx .. idx + 4]); 14540 } 14541 } 14542 } 14543 14544 } 14545 14546 mixin template NativeSimpleWindowImplementation() { 14547 GC gc; 14548 Window window; 14549 Display* display; 14550 14551 Pixmap buffer; 14552 int bufferw, bufferh; // size of the buffer; can be bigger than window 14553 XIC xic; // input context 14554 int curHidden = 0; // counter 14555 Cursor blankCurPtr = 0; 14556 int cursorSequenceNumber = 0; 14557 int warpEventCount = 0; // number of mouse movement events to eat 14558 14559 __gshared X11SetSelectionHandler[Atom] setSelectionHandlers; 14560 X11GetSelectionHandler[Atom] getSelectionHandlers; 14561 14562 version(without_opengl) {} else 14563 GLXContext glc; 14564 14565 private void fixFixedSize(bool forced=false) (int width, int height) { 14566 if (forced || this.resizability == Resizability.fixedSize) { 14567 //{ import core.stdc.stdio; printf("fixing size to: %dx%d\n", width, height); } 14568 XSizeHints sh; 14569 static if (!forced) { 14570 c_long spr; 14571 XGetWMNormalHints(display, window, &sh, &spr); 14572 sh.flags |= PMaxSize | PMinSize; 14573 } else { 14574 sh.flags = PMaxSize | PMinSize; 14575 } 14576 sh.min_width = width; 14577 sh.min_height = height; 14578 sh.max_width = width; 14579 sh.max_height = height; 14580 XSetWMNormalHints(display, window, &sh); 14581 //XFlush(display); 14582 } 14583 } 14584 14585 ScreenPainter getPainter(bool manualInvalidations) { 14586 return ScreenPainter(this, window, manualInvalidations); 14587 } 14588 14589 void move(int x, int y) { 14590 XMoveWindow(display, window, x, y); 14591 } 14592 14593 void resize(int w, int h) { 14594 if (w < 1) w = 1; 14595 if (h < 1) h = 1; 14596 XResizeWindow(display, window, w, h); 14597 14598 // calling this now to avoid waiting for the server to 14599 // acknowledge the resize; draws without returning to the 14600 // event loop will thus actually work. the server's event 14601 // btw might overrule this and resize it again 14602 recordX11Resize(display, this, w, h); 14603 14604 updateOpenglViewportIfNeeded(w, h); 14605 } 14606 14607 void moveResize (int x, int y, int w, int h) { 14608 if (w < 1) w = 1; 14609 if (h < 1) h = 1; 14610 XMoveResizeWindow(display, window, x, y, w, h); 14611 updateOpenglViewportIfNeeded(w, h); 14612 } 14613 14614 void hideCursor () { 14615 if (curHidden++ == 0) { 14616 if (!blankCurPtr || cursorSequenceNumber != XDisplayConnection.connectionSequenceNumber) { 14617 static const(char)[1] cmbmp = 0; 14618 XColor blackcolor = { 0, 0, 0, 0, 0, 0 }; 14619 Pixmap pm = XCreateBitmapFromData(display, window, cmbmp.ptr, 1, 1); 14620 blankCurPtr = XCreatePixmapCursor(display, pm, pm, &blackcolor, &blackcolor, 0, 0); 14621 cursorSequenceNumber = XDisplayConnection.connectionSequenceNumber; 14622 XFreePixmap(display, pm); 14623 } 14624 XDefineCursor(display, window, blankCurPtr); 14625 } 14626 } 14627 14628 void showCursor () { 14629 if (--curHidden == 0) XUndefineCursor(display, window); 14630 } 14631 14632 void warpMouse (int x, int y) { 14633 // here i will send dummy "ignore next mouse motion" event, 14634 // 'cause `XWarpPointer()` sends synthesised mouse motion, 14635 // and we don't need to report it to the user (as warping is 14636 // used when the user needs movement deltas). 14637 //XClientMessageEvent xclient; 14638 XEvent e; 14639 e.xclient.type = EventType.ClientMessage; 14640 e.xclient.window = window; 14641 e.xclient.message_type = GetAtom!("_X11SDPY_INSMME_FLAG_EVENT_", true)(display); // let's hope nobody else will use such stupid name ;-) 14642 e.xclient.format = 32; 14643 e.xclient.data.l[0] = 0; 14644 debug(x11sdpy_warp_debug) { import core.stdc.stdio : printf; printf("X11: sending \"INSMME\"...\n"); } 14645 //{ 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]); } 14646 XSendEvent(display, window, false, EventMask.NoEventMask, /*cast(XEvent*)&xclient*/&e); 14647 // now warp pointer... 14648 debug(x11sdpy_warp_debug) { import core.stdc.stdio : printf; printf("X11: sending \"warp\"...\n"); } 14649 XWarpPointer(display, None, window, 0, 0, 0, 0, x, y); 14650 // ...and flush 14651 debug(x11sdpy_warp_debug) { import core.stdc.stdio : printf; printf("X11: flushing...\n"); } 14652 XFlush(display); 14653 } 14654 14655 void sendDummyEvent () { 14656 // here i will send dummy event to ping event queue 14657 XEvent e; 14658 e.xclient.type = EventType.ClientMessage; 14659 e.xclient.window = window; 14660 e.xclient.message_type = GetAtom!("_X11SDPY_DUMMY_EVENT_", true)(display); // let's hope nobody else will use such stupid name ;-) 14661 e.xclient.format = 32; 14662 e.xclient.data.l[0] = 0; 14663 XSendEvent(display, window, false, EventMask.NoEventMask, /*cast(XEvent*)&xclient*/&e); 14664 XFlush(display); 14665 } 14666 14667 void setTitle(string title) { 14668 if (title.ptr is null) title = ""; 14669 auto XA_UTF8 = XInternAtom(display, "UTF8_STRING".ptr, false); 14670 auto XA_NETWM_NAME = XInternAtom(display, "_NET_WM_NAME".ptr, false); 14671 XTextProperty windowName; 14672 windowName.value = title.ptr; 14673 windowName.encoding = XA_UTF8; //XA_STRING; 14674 windowName.format = 8; 14675 windowName.nitems = cast(uint)title.length; 14676 XSetWMName(display, window, &windowName); 14677 char[1024] namebuf = 0; 14678 auto maxlen = namebuf.length-1; 14679 if (maxlen > title.length) maxlen = title.length; 14680 namebuf[0..maxlen] = title[0..maxlen]; 14681 XStoreName(display, window, namebuf.ptr); 14682 XChangeProperty(display, window, XA_NETWM_NAME, XA_UTF8, 8, PropModeReplace, title.ptr, cast(uint)title.length); 14683 flushGui(); // without this OpenGL windows has a LONG delay before changing title 14684 } 14685 14686 string[] getTitles() { 14687 auto XA_UTF8 = XInternAtom(display, "UTF8_STRING".ptr, false); 14688 auto XA_NETWM_NAME = XInternAtom(display, "_NET_WM_NAME".ptr, false); 14689 XTextProperty textProp; 14690 if (XGetTextProperty(display, window, &textProp, XA_NETWM_NAME) != 0 || XGetWMName(display, window, &textProp) != 0) { 14691 if ((textProp.encoding == XA_UTF8 || textProp.encoding == XA_STRING) && textProp.format == 8) { 14692 return textProp.value[0 .. textProp.nitems].idup.split('\0'); 14693 } else 14694 return []; 14695 } else 14696 return null; 14697 } 14698 14699 string getTitle() { 14700 auto titles = getTitles(); 14701 return titles.length ? titles[0] : null; 14702 } 14703 14704 void setMinSize (int minwidth, int minheight) { 14705 import core.stdc.config : c_long; 14706 if (minwidth < 1) minwidth = 1; 14707 if (minheight < 1) minheight = 1; 14708 XSizeHints sh; 14709 c_long spr; 14710 XGetWMNormalHints(display, window, &sh, &spr); 14711 sh.min_width = minwidth; 14712 sh.min_height = minheight; 14713 sh.flags |= PMinSize; 14714 XSetWMNormalHints(display, window, &sh); 14715 flushGui(); 14716 } 14717 14718 void setMaxSize (int maxwidth, int maxheight) { 14719 import core.stdc.config : c_long; 14720 if (maxwidth < 1) maxwidth = 1; 14721 if (maxheight < 1) maxheight = 1; 14722 XSizeHints sh; 14723 c_long spr; 14724 XGetWMNormalHints(display, window, &sh, &spr); 14725 sh.max_width = maxwidth; 14726 sh.max_height = maxheight; 14727 sh.flags |= PMaxSize; 14728 XSetWMNormalHints(display, window, &sh); 14729 flushGui(); 14730 } 14731 14732 void setResizeGranularity (int granx, int grany) { 14733 import core.stdc.config : c_long; 14734 if (granx < 1) granx = 1; 14735 if (grany < 1) grany = 1; 14736 XSizeHints sh; 14737 c_long spr; 14738 XGetWMNormalHints(display, window, &sh, &spr); 14739 sh.width_inc = granx; 14740 sh.height_inc = grany; 14741 sh.flags |= PResizeInc; 14742 XSetWMNormalHints(display, window, &sh); 14743 flushGui(); 14744 } 14745 14746 void setOpacity (uint opacity) { 14747 arch_ulong o = opacity; 14748 if (opacity == uint.max) 14749 XDeleteProperty(display, window, XInternAtom(display, "_NET_WM_WINDOW_OPACITY".ptr, false)); 14750 else 14751 XChangeProperty(display, window, XInternAtom(display, "_NET_WM_WINDOW_OPACITY".ptr, false), 14752 XA_CARDINAL, 32, PropModeReplace, &o, 1); 14753 } 14754 14755 void createWindow(int width, int height, string title, in OpenGlOptions opengl, SimpleWindow parent) { 14756 version(without_opengl) {} else if(opengl == OpenGlOptions.yes && !openGlLibrariesSuccessfullyLoaded) throw new Exception("OpenGL libraries did not load"); 14757 display = XDisplayConnection.get(); 14758 auto screen = DefaultScreen(display); 14759 14760 bool overrideRedirect = false; 14761 if(windowType == WindowTypes.dropdownMenu || windowType == WindowTypes.popupMenu || windowType == WindowTypes.notification)// || windowType == WindowTypes.nestedChild) 14762 overrideRedirect = true; 14763 14764 version(without_opengl) {} 14765 else { 14766 if(opengl == OpenGlOptions.yes) { 14767 GLXFBConfig fbconf = null; 14768 XVisualInfo* vi = null; 14769 bool useLegacy = false; 14770 static if (SdpyIsUsingIVGLBinds) {if (glbindGetProcAddress("glHint") is null) assert(0, "GL: error loading OpenGL"); } // loads all necessary functions 14771 if (sdpyOpenGLContextVersion != 0 && glXCreateContextAttribsARB_present()) { 14772 int[23] visualAttribs = [ 14773 GLX_X_RENDERABLE , 1/*True*/, 14774 GLX_DRAWABLE_TYPE, GLX_WINDOW_BIT, 14775 GLX_RENDER_TYPE , GLX_RGBA_BIT, 14776 GLX_X_VISUAL_TYPE, GLX_TRUE_COLOR, 14777 GLX_RED_SIZE , 8, 14778 GLX_GREEN_SIZE , 8, 14779 GLX_BLUE_SIZE , 8, 14780 GLX_ALPHA_SIZE , 8, 14781 GLX_DEPTH_SIZE , 24, 14782 GLX_STENCIL_SIZE , 8, 14783 GLX_DOUBLEBUFFER , 1/*True*/, 14784 0/*None*/, 14785 ]; 14786 int fbcount; 14787 GLXFBConfig* fbc = glXChooseFBConfig(display, screen, visualAttribs.ptr, &fbcount); 14788 if (fbcount == 0) { 14789 useLegacy = true; // try to do at least something 14790 } else { 14791 // pick the FB config/visual with the most samples per pixel 14792 int bestidx = -1, bestns = -1; 14793 foreach (int fbi; 0..fbcount) { 14794 int sb, samples; 14795 glXGetFBConfigAttrib(display, fbc[fbi], GLX_SAMPLE_BUFFERS, &sb); 14796 glXGetFBConfigAttrib(display, fbc[fbi], GLX_SAMPLES, &samples); 14797 if (bestidx < 0 || sb && samples > bestns) { bestidx = fbi; bestns = samples; } 14798 } 14799 //{ import core.stdc.stdio; printf("found gl visual with %d samples\n", bestns); } 14800 fbconf = fbc[bestidx]; 14801 // Be sure to free the FBConfig list allocated by glXChooseFBConfig() 14802 XFree(fbc); 14803 vi = cast(XVisualInfo*)glXGetVisualFromFBConfig(display, fbconf); 14804 } 14805 } 14806 if (vi is null || useLegacy) { 14807 static immutable GLint[5] attrs = [ GLX_RGBA, GLX_DEPTH_SIZE, 24, GLX_DOUBLEBUFFER, None ]; 14808 vi = cast(XVisualInfo*)glXChooseVisual(display, 0, attrs.ptr); 14809 useLegacy = true; 14810 } 14811 if (vi is null) throw new Exception("no open gl visual found"); 14812 14813 XSetWindowAttributes swa; 14814 auto root = RootWindow(display, screen); 14815 swa.colormap = XCreateColormap(display, root, vi.visual, AllocNone); 14816 14817 swa.override_redirect = overrideRedirect; 14818 14819 window = XCreateWindow(display, (windowType != WindowTypes.nestedChild || parent is null) ? root : parent.impl.window, 14820 0, 0, width, height, 14821 0, vi.depth, 1 /* InputOutput */, vi.visual, CWColormap | CWOverrideRedirect, &swa); 14822 14823 // now try to use `glXCreateContextAttribsARB()` if it's here 14824 if (!useLegacy) { 14825 // request fairly advanced context, even with stencil buffer! 14826 int[9] contextAttribs = [ 14827 GLX_CONTEXT_MAJOR_VERSION_ARB, (sdpyOpenGLContextVersion>>8), 14828 GLX_CONTEXT_MINOR_VERSION_ARB, (sdpyOpenGLContextVersion&0xff), 14829 /*GLX_CONTEXT_PROFILE_MASK_ARB*/0x9126, (sdpyOpenGLContextCompatible ? /*GLX_CONTEXT_COMPATIBILITY_PROFILE_BIT_ARB*/0x02 : /*GLX_CONTEXT_CORE_PROFILE_BIT_ARB*/ 0x01), 14830 // for modern context, set "forward compatibility" flag too 14831 (sdpyOpenGLContextCompatible ? None : /*GLX_CONTEXT_FLAGS_ARB*/ 0x2094), /*GLX_CONTEXT_FORWARD_COMPATIBLE_BIT_ARB*/ 0x02, 14832 0/*None*/, 14833 ]; 14834 glc = glXCreateContextAttribsARB(display, fbconf, null, 1/*True*/, contextAttribs.ptr); 14835 if (glc is null && sdpyOpenGLContextAllowFallback) { 14836 sdpyOpenGLContextVersion = 0; 14837 glc = glXCreateContext(display, vi, null, /*GL_TRUE*/1); 14838 } 14839 //{ import core.stdc.stdio; printf("using modern ogl v%d.%d\n", contextAttribs[1], contextAttribs[3]); } 14840 } else { 14841 // fallback to old GLX call 14842 if (sdpyOpenGLContextAllowFallback || sdpyOpenGLContextVersion == 0) { 14843 sdpyOpenGLContextVersion = 0; 14844 glc = glXCreateContext(display, vi, null, /*GL_TRUE*/1); 14845 } 14846 } 14847 // sync to ensure any errors generated are processed 14848 XSync(display, 0/*False*/); 14849 //{ import core.stdc.stdio; printf("ogl is here\n"); } 14850 if(glc is null) 14851 throw new Exception("glc"); 14852 } 14853 } 14854 14855 if(opengl == OpenGlOptions.no) { 14856 14857 XSetWindowAttributes swa; 14858 swa.background_pixel = WhitePixel(display, screen); 14859 swa.border_pixel = BlackPixel(display, screen); 14860 swa.override_redirect = overrideRedirect; 14861 auto root = RootWindow(display, screen); 14862 swa.colormap = XCreateColormap(display, root, DefaultVisual(display, screen), AllocNone); 14863 14864 window = XCreateWindow(display, (windowType != WindowTypes.nestedChild || parent is null) ? root : parent.impl.window, 14865 0, 0, width, height, 14866 // I'm commenting that CWBackPixel thing just because it actually causes flicker for no apparent benefit. 14867 0, CopyFromParent, 1 /* InputOutput */, cast(Visual*) CopyFromParent, CWColormap /*| CWBackPixel*/ | CWBorderPixel | CWOverrideRedirect, &swa); 14868 14869 14870 14871 /* 14872 window = XCreateSimpleWindow( 14873 display, 14874 parent is null ? RootWindow(display, screen) : parent.impl.window, 14875 0, 0, // x, y 14876 width, height, 14877 1, // border width 14878 BlackPixel(display, screen), // border 14879 WhitePixel(display, screen)); // background 14880 */ 14881 14882 buffer = XCreatePixmap(display, cast(Drawable) window, width, height, DefaultDepthOfDisplay(display)); 14883 bufferw = width; 14884 bufferh = height; 14885 14886 gc = DefaultGC(display, screen); 14887 14888 // clear out the buffer to get us started... 14889 XSetForeground(display, gc, WhitePixel(display, screen)); 14890 XFillRectangle(display, cast(Drawable) buffer, gc, 0, 0, width, height); 14891 XSetForeground(display, gc, BlackPixel(display, screen)); 14892 } 14893 14894 // input context 14895 //TODO: create this only for top-level windows, and reuse that? 14896 populateXic(); 14897 14898 if (sdpyWindowClassStr is null) loadBinNameToWindowClassName(); 14899 if (sdpyWindowClassStr is null) sdpyWindowClass = "DSimpleWindow"; 14900 // window class 14901 if (sdpyWindowClassStr !is null && sdpyWindowClassStr[0]) { 14902 //{ import core.stdc.stdio; printf("winclass: [%s]\n", sdpyWindowClassStr); } 14903 XClassHint klass; 14904 XWMHints wh; 14905 if(this.customizationFlags & WindowFlags.managesChildWindowFocus) { 14906 wh.input = true; 14907 wh.flags |= InputHint; 14908 } 14909 XSizeHints size; 14910 klass.res_name = sdpyWindowClassStr; 14911 klass.res_class = sdpyWindowClassStr; 14912 XSetWMProperties(display, window, null, null, null, 0, &size, &wh, &klass); 14913 } 14914 14915 setTitle(title); 14916 SimpleWindow.nativeMapping[window] = this; 14917 CapableOfHandlingNativeEvent.nativeHandleMapping[window] = this; 14918 14919 // This gives our window a close button 14920 if (windowType != WindowTypes.eventOnly) { 14921 Atom[2] atoms = [GetAtom!"WM_DELETE_WINDOW"(display), GetAtom!"WM_TAKE_FOCUS"(display)]; 14922 int useAtoms; 14923 if(this.customizationFlags & WindowFlags.managesChildWindowFocus) { 14924 useAtoms = 2; 14925 } else { 14926 useAtoms = 1; 14927 } 14928 assert(useAtoms <= atoms.length); 14929 XSetWMProtocols(display, window, atoms.ptr, useAtoms); 14930 } 14931 14932 // FIXME: windowType and customizationFlags 14933 Atom[8] wsatoms; // here, due to goto 14934 int wmsacount = 0; // here, due to goto 14935 14936 try 14937 final switch(windowType) { 14938 case WindowTypes.normal: 14939 setNetWMWindowType(GetAtom!"_NET_WM_WINDOW_TYPE_NORMAL"(display)); 14940 break; 14941 case WindowTypes.undecorated: 14942 motifHideDecorations(); 14943 setNetWMWindowType(GetAtom!"_NET_WM_WINDOW_TYPE_NORMAL"(display)); 14944 break; 14945 case WindowTypes.eventOnly: 14946 _hidden = true; 14947 XSelectInput(display, window, EventMask.StructureNotifyMask); // without this, we won't get destroy notification 14948 goto hiddenWindow; 14949 //break; 14950 case WindowTypes.nestedChild: 14951 // handled in XCreateWindow calls 14952 break; 14953 14954 case WindowTypes.dropdownMenu: 14955 motifHideDecorations(); 14956 setNetWMWindowType(GetAtom!"_NET_WM_WINDOW_TYPE_DROPDOWN_MENU"(display)); 14957 customizationFlags |= WindowFlags.skipTaskbar | WindowFlags.alwaysOnTop; 14958 break; 14959 case WindowTypes.popupMenu: 14960 motifHideDecorations(); 14961 setNetWMWindowType(GetAtom!"_NET_WM_WINDOW_TYPE_POPUP_MENU"(display)); 14962 customizationFlags |= WindowFlags.skipTaskbar | WindowFlags.alwaysOnTop; 14963 break; 14964 case WindowTypes.notification: 14965 motifHideDecorations(); 14966 setNetWMWindowType(GetAtom!"_NET_WM_WINDOW_TYPE_NOTIFICATION"(display)); 14967 customizationFlags |= WindowFlags.skipTaskbar | WindowFlags.alwaysOnTop; 14968 break; 14969 case WindowTypes.minimallyWrapped: 14970 assert(0, "don't create a minimallyWrapped thing explicitly!"); 14971 /+ 14972 case WindowTypes.menu: 14973 atoms[0] = GetAtom!"_NET_WM_WINDOW_TYPE_MENU"(display); 14974 motifHideDecorations(); 14975 break; 14976 case WindowTypes.desktop: 14977 atoms[0] = GetAtom!"_NET_WM_WINDOW_TYPE_DESKTOP"(display); 14978 break; 14979 case WindowTypes.dock: 14980 atoms[0] = GetAtom!"_NET_WM_WINDOW_TYPE_DOCK"(display); 14981 break; 14982 case WindowTypes.toolbar: 14983 atoms[0] = GetAtom!"_NET_WM_WINDOW_TYPE_TOOLBAR"(display); 14984 break; 14985 case WindowTypes.menu: 14986 atoms[0] = GetAtom!"_NET_WM_WINDOW_TYPE_MENU"(display); 14987 break; 14988 case WindowTypes.utility: 14989 atoms[0] = GetAtom!"_NET_WM_WINDOW_TYPE_UTILITY"(display); 14990 break; 14991 case WindowTypes.splash: 14992 atoms[0] = GetAtom!"_NET_WM_WINDOW_TYPE_SPLASH"(display); 14993 break; 14994 case WindowTypes.dialog: 14995 atoms[0] = GetAtom!"_NET_WM_WINDOW_TYPE_DIALOG"(display); 14996 break; 14997 case WindowTypes.tooltip: 14998 atoms[0] = GetAtom!"_NET_WM_WINDOW_TYPE_TOOLTIP"(display); 14999 break; 15000 case WindowTypes.notification: 15001 atoms[0] = GetAtom!"_NET_WM_WINDOW_TYPE_NOTIFICATION"(display); 15002 break; 15003 case WindowTypes.combo: 15004 atoms[0] = GetAtom!"_NET_WM_WINDOW_TYPE_COMBO"(display); 15005 break; 15006 case WindowTypes.dnd: 15007 atoms[0] = GetAtom!"_NET_WM_WINDOW_TYPE_DND"(display); 15008 break; 15009 +/ 15010 } 15011 catch(Exception e) { 15012 // XInternAtom failed, prolly a WM 15013 // that doesn't support these things 15014 } 15015 15016 if (customizationFlags&WindowFlags.skipTaskbar) wsatoms[wmsacount++] = GetAtom!("_NET_WM_STATE_SKIP_TASKBAR", true)(display); 15017 // the two following flags may be ignored by WM 15018 if (customizationFlags&WindowFlags.alwaysOnTop) wsatoms[wmsacount++] = GetAtom!("_NET_WM_STATE_ABOVE", true)(display); 15019 if (customizationFlags&WindowFlags.alwaysOnBottom) wsatoms[wmsacount++] = GetAtom!("_NET_WM_STATE_BELOW", true)(display); 15020 15021 if (wmsacount != 0) XChangeProperty(display, window, GetAtom!("_NET_WM_STATE", true)(display), XA_ATOM, 32 /* bits */,0 /*PropModeReplace*/, wsatoms.ptr, wmsacount); 15022 15023 if (this.resizability == Resizability.fixedSize || (opengl == OpenGlOptions.no && this.resizability != Resizability.allowResizing)) fixFixedSize!true(width, height); 15024 15025 // What would be ideal here is if they only were 15026 // selected if there was actually an event handler 15027 // for them... 15028 15029 selectDefaultInput((customizationFlags & WindowFlags.alwaysRequestMouseMotionEvents)?true:false); 15030 15031 hiddenWindow: 15032 15033 // set the pid property for lookup later by window managers 15034 // a standard convenience 15035 import core.sys.posix.unistd; 15036 arch_ulong pid = getpid(); 15037 15038 XChangeProperty( 15039 display, 15040 impl.window, 15041 GetAtom!("_NET_WM_PID", true)(display), 15042 XA_CARDINAL, 15043 32 /* bits */, 15044 0 /*PropModeReplace*/, 15045 &pid, 15046 1); 15047 15048 if(isTransient && parent) { // customizationFlags & WindowFlags.transient) { 15049 if(parent is null) assert(0); 15050 XChangeProperty( 15051 display, 15052 impl.window, 15053 GetAtom!("WM_TRANSIENT_FOR", true)(display), 15054 XA_WINDOW, 15055 32 /* bits */, 15056 0 /*PropModeReplace*/, 15057 &parent.impl.window, 15058 1); 15059 15060 } 15061 15062 if(windowType != WindowTypes.eventOnly && (customizationFlags&WindowFlags.dontAutoShow) == 0) { 15063 XMapWindow(display, window); 15064 } else { 15065 _hidden = true; 15066 } 15067 } 15068 15069 void populateXic() { 15070 if (XDisplayConnection.xim !is null) { 15071 xic = XCreateIC(XDisplayConnection.xim, 15072 /*XNInputStyle*/"inputStyle".ptr, XIMPreeditNothing|XIMStatusNothing, 15073 /*XNClientWindow*/"clientWindow".ptr, window, 15074 /*XNFocusWindow*/"focusWindow".ptr, window, 15075 null); 15076 if (xic is null) { 15077 import core.stdc.stdio : stderr, fprintf; 15078 fprintf(stderr, "XCreateIC failed for window %u\n", cast(uint)window); 15079 } 15080 } 15081 } 15082 15083 void selectDefaultInput(bool forceIncludeMouseMotion) { 15084 auto mask = EventMask.ExposureMask | 15085 EventMask.KeyPressMask | 15086 EventMask.KeyReleaseMask | 15087 EventMask.PropertyChangeMask | 15088 EventMask.FocusChangeMask | 15089 EventMask.StructureNotifyMask | 15090 EventMask.SubstructureNotifyMask | 15091 EventMask.VisibilityChangeMask 15092 | EventMask.ButtonPressMask 15093 | EventMask.ButtonReleaseMask 15094 ; 15095 15096 // xshm is our shortcut for local connections 15097 if(XDisplayConnection.isLocal || forceIncludeMouseMotion) 15098 mask |= EventMask.PointerMotionMask; 15099 else 15100 mask |= EventMask.ButtonMotionMask; 15101 15102 XSelectInput(display, window, mask); 15103 } 15104 15105 15106 void setNetWMWindowType(Atom type) { 15107 Atom[2] atoms; 15108 15109 atoms[0] = type; 15110 // generic fallback 15111 atoms[1] = GetAtom!"_NET_WM_WINDOW_TYPE_NORMAL"(display); 15112 15113 XChangeProperty( 15114 display, 15115 impl.window, 15116 GetAtom!"_NET_WM_WINDOW_TYPE"(display), 15117 XA_ATOM, 15118 32 /* bits */, 15119 0 /*PropModeReplace*/, 15120 atoms.ptr, 15121 cast(int) atoms.length); 15122 } 15123 15124 void motifHideDecorations(bool hide = true) { 15125 MwmHints hints; 15126 hints.flags = MWM_HINTS_DECORATIONS; 15127 hints.decorations = hide ? 0 : 1; 15128 15129 XChangeProperty( 15130 display, 15131 impl.window, 15132 GetAtom!"_MOTIF_WM_HINTS"(display), 15133 GetAtom!"_MOTIF_WM_HINTS"(display), 15134 32 /* bits */, 15135 0 /*PropModeReplace*/, 15136 &hints, 15137 hints.sizeof / 4); 15138 } 15139 15140 /*k8: unused 15141 void createOpenGlContext() { 15142 15143 } 15144 */ 15145 15146 void closeWindow() { 15147 // I can't close this or a child window closing will 15148 // break events for everyone. So I'm just leaking it right 15149 // now and that is probably perfectly fine... 15150 version(none) 15151 if (customEventFDRead != -1) { 15152 import core.sys.posix.unistd : close; 15153 auto same = customEventFDRead == customEventFDWrite; 15154 15155 close(customEventFDRead); 15156 if(!same) 15157 close(customEventFDWrite); 15158 customEventFDRead = -1; 15159 customEventFDWrite = -1; 15160 } 15161 15162 version(without_opengl) {} else 15163 if(glc !is null) { 15164 glXDestroyContext(display, glc); 15165 glc = null; 15166 } 15167 15168 if(buffer) 15169 XFreePixmap(display, buffer); 15170 bufferw = bufferh = 0; 15171 if (blankCurPtr && cursorSequenceNumber == XDisplayConnection.connectionSequenceNumber) XFreeCursor(display, blankCurPtr); 15172 XDestroyWindow(display, window); 15173 XFlush(display); 15174 } 15175 15176 void dispose() { 15177 } 15178 15179 bool destroyed = false; 15180 } 15181 15182 bool insideXEventLoop; 15183 } 15184 15185 version(X11) { 15186 15187 int mouseDoubleClickTimeout = 350; /// Double click timeout. X only, you probably shouldn't change this. 15188 15189 private class ResizeEvent { 15190 int width, height; 15191 } 15192 15193 void recordX11ResizeAsync(Display* display, SimpleWindow win, int width, int height) { 15194 if(win.windowType == WindowTypes.minimallyWrapped) 15195 return; 15196 15197 if(win.pendingResizeEvent is null) { 15198 win.pendingResizeEvent = new ResizeEvent(); 15199 win.addEventListener((ResizeEvent re) { 15200 recordX11Resize(XDisplayConnection.get, win, re.width, re.height); 15201 }); 15202 } 15203 win.pendingResizeEvent.width = width; 15204 win.pendingResizeEvent.height = height; 15205 if(!win.eventQueued!ResizeEvent) { 15206 win.postEvent(win.pendingResizeEvent); 15207 } 15208 } 15209 15210 void recordX11Resize(Display* display, SimpleWindow win, int width, int height) { 15211 if(win.windowType == WindowTypes.minimallyWrapped) 15212 return; 15213 if(win.closed) 15214 return; 15215 15216 if(width != win.width || height != win.height) { 15217 15218 // writeln("RESIZE: ", width, "x", height, " was ", win._width, "x", win._height, " window: ", win.windowType, "-", win.title, " ", win.window); 15219 win._width = width; 15220 win._height = height; 15221 15222 if(win.openglMode == OpenGlOptions.no) { 15223 // FIXME: could this be more efficient? 15224 15225 if (win.bufferw < width || win.bufferh < height) { 15226 //{ 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); } 15227 // grow the internal buffer to match the window... 15228 auto newPixmap = XCreatePixmap(display, cast(Drawable) win.window, width, height, DefaultDepthOfDisplay(display)); 15229 { 15230 GC xgc = XCreateGC(win.display, cast(Drawable)win.window, 0, null); 15231 XCopyGC(win.display, win.gc, 0xffffffff, xgc); 15232 scope(exit) XFreeGC(win.display, xgc); 15233 XSetClipMask(win.display, xgc, None); 15234 XSetForeground(win.display, xgc, 0); 15235 XFillRectangle(display, cast(Drawable)newPixmap, xgc, 0, 0, width, height); 15236 } 15237 XCopyArea(display, 15238 cast(Drawable) win.buffer, 15239 cast(Drawable) newPixmap, 15240 win.gc, 0, 0, 15241 win.bufferw < width ? win.bufferw : win.width, 15242 win.bufferh < height ? win.bufferh : win.height, 15243 0, 0); 15244 15245 XFreePixmap(display, win.buffer); 15246 win.buffer = newPixmap; 15247 win.bufferw = width; 15248 win.bufferh = height; 15249 } 15250 15251 // clear unused parts of the buffer 15252 if (win.bufferw > width || win.bufferh > height) { 15253 GC xgc = XCreateGC(win.display, cast(Drawable)win.window, 0, null); 15254 XCopyGC(win.display, win.gc, 0xffffffff, xgc); 15255 scope(exit) XFreeGC(win.display, xgc); 15256 XSetClipMask(win.display, xgc, None); 15257 XSetForeground(win.display, xgc, 0); 15258 immutable int maxw = (win.bufferw > width ? win.bufferw : width); 15259 immutable int maxh = (win.bufferh > height ? win.bufferh : height); 15260 XFillRectangle(win.display, cast(Drawable)win.buffer, xgc, width, 0, maxw, maxh); // let X11 do clipping 15261 XFillRectangle(win.display, cast(Drawable)win.buffer, xgc, 0, height, maxw, maxh); // let X11 do clipping 15262 } 15263 15264 } 15265 15266 win.updateOpenglViewportIfNeeded(width, height); 15267 15268 win.fixFixedSize(width, height); //k8: this does nothing on my FluxBox; wtf?! 15269 15270 if(win.resizability != Resizability.automaticallyScaleIfPossible) 15271 if(win.windowResized !is null) { 15272 XUnlockDisplay(display); 15273 scope(exit) XLockDisplay(display); 15274 win.windowResized(width, height); 15275 } 15276 } 15277 } 15278 15279 15280 /// Platform-specific, you might use it when doing a custom event loop. 15281 bool doXNextEvent(Display* display) { 15282 bool done; 15283 XEvent e; 15284 XNextEvent(display, &e); 15285 version(sddddd) { 15286 if(auto win = e.xany.window in CapableOfHandlingNativeEvent.nativeHandleMapping) { 15287 if(typeid(cast(Object) *win) == NotificationAreaIcon.classinfo) 15288 writeln("event for: ", e.xany.window, "; type is ", to!string(cast(EventType)e.type)); 15289 } 15290 } 15291 15292 // filter out compose events 15293 if (XFilterEvent(&e, None)) { 15294 //{ import core.stdc.stdio : printf; printf("XFilterEvent filtered!\n"); } 15295 //NOTE: we should ungrab keyboard here, but simpledisplay doesn't use keyboard grabbing (yet) 15296 return false; 15297 } 15298 // process keyboard mapping changes 15299 if (e.type == EventType.KeymapNotify) { 15300 //{ import core.stdc.stdio : printf; printf("KeymapNotify processed!\n"); } 15301 XRefreshKeyboardMapping(&e.xmapping); 15302 return false; 15303 } 15304 15305 version(with_eventloop) 15306 import arsd.eventloop; 15307 15308 if(SimpleWindow.handleNativeGlobalEvent !is null) { 15309 // see windows impl's comments 15310 XUnlockDisplay(display); 15311 scope(exit) XLockDisplay(display); 15312 auto ret = SimpleWindow.handleNativeGlobalEvent(e); 15313 if(ret == 0) 15314 return done; 15315 } 15316 15317 15318 if(auto win = e.xany.window in CapableOfHandlingNativeEvent.nativeHandleMapping) { 15319 if(win.getNativeEventHandler !is null) { 15320 XUnlockDisplay(display); 15321 scope(exit) XLockDisplay(display); 15322 auto ret = win.getNativeEventHandler()(e); 15323 if(ret == 0) 15324 return done; 15325 } 15326 } 15327 15328 if(xrrEventBase != -1 && e.type == xrrEventBase + RRScreenChangeNotify) { 15329 if(auto win = e.xany.window in SimpleWindow.nativeMapping) { 15330 // we get this because of the RRScreenChangeNotifyMask 15331 15332 // this isn't actually an ideal way to do it since it wastes time 15333 // but meh it is simple and it works. 15334 win.actualDpiLoadAttempted = false; 15335 SimpleWindow.xRandrInfoLoadAttemped = false; 15336 win.updateActualDpi(); // trigger a reload 15337 } 15338 } 15339 15340 switch(e.type) { 15341 case EventType.SelectionClear: 15342 if(auto win = e.xselectionclear.window in SimpleWindow.nativeMapping) { 15343 // FIXME so it is supposed to finish any in progress transfers... but idk... 15344 // writeln("SelectionClear"); 15345 SimpleWindow.impl.setSelectionHandlers.remove(e.xselectionclear.selection); 15346 } 15347 break; 15348 case EventType.SelectionRequest: 15349 if(auto win = e.xselectionrequest.owner in SimpleWindow.nativeMapping) 15350 if(auto ssh = e.xselectionrequest.selection in SimpleWindow.impl.setSelectionHandlers) { 15351 // printf("SelectionRequest %s\n", XGetAtomName(e.xselectionrequest.display, e.xselectionrequest.target)); 15352 XUnlockDisplay(display); 15353 scope(exit) XLockDisplay(display); 15354 (*ssh).handleRequest(e); 15355 } 15356 break; 15357 case EventType.PropertyNotify: 15358 // printf("PropertyNotify %s %d\n", XGetAtomName(e.xproperty.display, e.xproperty.atom), e.xproperty.state); 15359 15360 foreach(ssh; SimpleWindow.impl.setSelectionHandlers) { 15361 if(ssh.matchesIncr(e.xproperty.window, e.xproperty.atom) && e.xproperty.state == PropertyNotification.PropertyDelete) 15362 ssh.sendMoreIncr(&e.xproperty); 15363 } 15364 15365 15366 if(auto win = e.xproperty.window in SimpleWindow.nativeMapping) 15367 if(auto handler = e.xproperty.atom in win.getSelectionHandlers) { 15368 if(handler.matchesIncr(e.xproperty.window, e.xproperty.atom) && e.xproperty.state == PropertyNotification.PropertyNewValue) { 15369 Atom target; 15370 int format; 15371 arch_ulong bytesafter, length; 15372 void* value; 15373 15374 ubyte[] s; 15375 Atom targetToKeep; 15376 15377 XGetWindowProperty( 15378 e.xproperty.display, 15379 e.xproperty.window, 15380 e.xproperty.atom, 15381 0, 15382 100000 /* length */, 15383 true, /* erase it to signal we got it and want more */ 15384 0 /*AnyPropertyType*/, 15385 &target, &format, &length, &bytesafter, &value); 15386 15387 if(!targetToKeep) 15388 targetToKeep = target; 15389 15390 auto id = (cast(ubyte*) value)[0 .. length]; 15391 15392 handler.handleIncrData(targetToKeep, id); 15393 15394 XFree(value); 15395 } 15396 } 15397 break; 15398 case EventType.SelectionNotify: 15399 if(auto win = e.xselection.requestor in SimpleWindow.nativeMapping) 15400 if(auto handler = e.xproperty.atom in win.getSelectionHandlers) { 15401 if(e.xselection.property == None) { // || e.xselection.property == GetAtom!("NULL", true)(e.xselection.display)) { 15402 XUnlockDisplay(display); 15403 scope(exit) XLockDisplay(display); 15404 handler.handleData(None, null); 15405 } else { 15406 Atom target; 15407 int format; 15408 arch_ulong bytesafter, length; 15409 void* value; 15410 XGetWindowProperty( 15411 e.xselection.display, 15412 e.xselection.requestor, 15413 e.xselection.property, 15414 0, 15415 100000 /* length */, 15416 //false, /* don't erase it */ 15417 true, /* do erase it lol */ 15418 0 /*AnyPropertyType*/, 15419 &target, &format, &length, &bytesafter, &value); 15420 15421 // FIXME: I don't have to copy it now since it is in char[] instead of string 15422 15423 { 15424 XUnlockDisplay(display); 15425 scope(exit) XLockDisplay(display); 15426 15427 if(target == XA_ATOM) { 15428 // initial request, see what they are able to work with and request the best one 15429 // we can handle, if available 15430 15431 Atom[] answer = (cast(Atom*) value)[0 .. length]; 15432 Atom best = handler.findBestFormat(answer); 15433 15434 /+ 15435 writeln("got ", answer); 15436 foreach(a; answer) 15437 printf("%s\n", XGetAtomName(display, a)); 15438 writeln("best ", best); 15439 +/ 15440 15441 if(best != None) { 15442 // actually request the best format 15443 XConvertSelection(e.xselection.display, e.xselection.selection, best, GetAtom!("SDD_DATA", true)(display), e.xselection.requestor, 0 /*CurrentTime*/); 15444 } 15445 } else if(target == GetAtom!"INCR"(display)) { 15446 // incremental 15447 15448 handler.prepareIncremental(e.xselection.requestor, e.xselection.property); 15449 15450 // signal the sending program that we see 15451 // the incr and are ready to receive more. 15452 XDeleteProperty( 15453 e.xselection.display, 15454 e.xselection.requestor, 15455 e.xselection.property); 15456 } else { 15457 // unsupported type... maybe, forward 15458 handler.handleData(target, cast(ubyte[]) value[0 .. length]); 15459 } 15460 } 15461 XFree(value); 15462 /* 15463 XDeleteProperty( 15464 e.xselection.display, 15465 e.xselection.requestor, 15466 e.xselection.property); 15467 */ 15468 } 15469 } 15470 break; 15471 case EventType.ConfigureNotify: 15472 auto event = e.xconfigure; 15473 if(auto win = event.window in SimpleWindow.nativeMapping) { 15474 if(win.windowType == WindowTypes.minimallyWrapped) 15475 break; 15476 //version(sdddd) { writeln(" w=", event.width, "; h=", event.height); } 15477 15478 /+ 15479 The ICCCM says window managers must send a synthetic event when the window 15480 is moved but NOT when it is resized. In the resize case, an event is sent 15481 with position (0, 0) which can be wrong and break the dpi calculations. 15482 15483 So we only consider the synthetic events from the WM and otherwise 15484 need to wait for some other event to get the position which... sucks. 15485 15486 I'd rather not have windows changing their layout on mouse motion after 15487 switching monitors... might be forced to but for now just ignoring it. 15488 15489 Easiest way to switch monitors without sending a size position is by 15490 maximize or fullscreen in a setup like mine, but on most setups those 15491 work on the monitor it is already living on, so it should be ok most the 15492 time. 15493 +/ 15494 if(event.send_event) { 15495 win.screenPositionKnown = true; 15496 win.screenPositionX = event.x; 15497 win.screenPositionY = event.y; 15498 win.updateActualDpi(); 15499 } 15500 15501 win.updateIMEPopupLocation(); 15502 recordX11ResizeAsync(display, *win, event.width, event.height); 15503 } 15504 break; 15505 case EventType.Expose: 15506 if(auto win = e.xexpose.window in SimpleWindow.nativeMapping) { 15507 if(win.windowType == WindowTypes.minimallyWrapped) 15508 break; 15509 // if it is closing from a popup menu, it can get 15510 // an Expose event right by the end and trigger a 15511 // BadDrawable error ... we'll just check 15512 // closed to handle that. 15513 if((*win).closed) break; 15514 if((*win).openglMode == OpenGlOptions.no) { 15515 bool doCopy = true;// e.xexpose.count == 0; // the count is better if we copy all area but meh 15516 if (win.handleExpose !is null) doCopy = !win.handleExpose(e.xexpose.x, e.xexpose.y, e.xexpose.width, e.xexpose.height, e.xexpose.count); 15517 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); 15518 } else { 15519 // need to redraw the scene somehow 15520 if(e.xexpose.count == 0) { // only do the last one since redrawOpenGlSceneNow always does it all 15521 XUnlockDisplay(display); 15522 scope(exit) XLockDisplay(display); 15523 version(without_opengl) {} else 15524 win.redrawOpenGlSceneSoon(); 15525 } 15526 } 15527 } 15528 break; 15529 case EventType.FocusIn: 15530 case EventType.FocusOut: 15531 15532 if(auto win = e.xfocus.window in SimpleWindow.nativeMapping) { 15533 /+ 15534 15535 void info(string detail) { 15536 string s; 15537 // import std.conv; 15538 // import std.datetime; 15539 s ~= to!string(Clock.currTime); 15540 s ~= " "; 15541 s ~= e.type == EventType.FocusIn ? "in " : "out"; 15542 s ~= " "; 15543 s ~= win.windowType == WindowTypes.nestedChild ? "child " : "main "; 15544 s ~= e.xfocus.mode == NotifyModes.NotifyNormal ? " normal ": " grabbed "; 15545 s ~= detail; 15546 s ~= " "; 15547 15548 sdpyPrintDebugString(s); 15549 15550 } 15551 15552 switch(e.xfocus.detail) { 15553 case NotifyDetail.NotifyAncestor: info("Ancestor"); break; 15554 case NotifyDetail.NotifyVirtual: info("Virtual"); break; 15555 case NotifyDetail.NotifyInferior: info("Inferior"); break; 15556 case NotifyDetail.NotifyNonlinear: info("Nonlinear"); break; 15557 case NotifyDetail.NotifyNonlinearVirtual: info("nlinearvirtual"); break; 15558 case NotifyDetail.NotifyPointer: info("pointer"); break; 15559 case NotifyDetail.NotifyPointerRoot: info("pointerroot"); break; 15560 case NotifyDetail.NotifyDetailNone: info("none"); break; 15561 default: 15562 15563 } 15564 +/ 15565 15566 15567 if(e.xfocus.detail == NotifyDetail.NotifyPointer) 15568 break; // just ignore these they seem irrelevant 15569 15570 auto old = win._focused; 15571 win._focused = e.type == EventType.FocusIn; 15572 15573 // yes, we are losing the focus, but to our own child. that's actually kinda keeping it. 15574 if(e.type == EventType.FocusOut && e.xfocus.detail == NotifyDetail.NotifyInferior) 15575 win._focused = true; 15576 15577 if(win.demandingAttention) 15578 demandAttention(*win, false); 15579 15580 win.updateIMEFocused(); 15581 15582 if(old != win._focused && win.onFocusChange) { 15583 XUnlockDisplay(display); 15584 scope(exit) XLockDisplay(display); 15585 win.onFocusChange(win._focused); 15586 } 15587 } 15588 break; 15589 case EventType.VisibilityNotify: 15590 if(auto win = e.xfocus.window in SimpleWindow.nativeMapping) { 15591 if (e.xvisibility.state == VisibilityNotify.VisibilityFullyObscured) { 15592 if (win.visibilityChanged !is null) { 15593 XUnlockDisplay(display); 15594 scope(exit) XLockDisplay(display); 15595 win.visibilityChanged(false); 15596 } 15597 } else { 15598 if (win.visibilityChanged !is null) { 15599 XUnlockDisplay(display); 15600 scope(exit) XLockDisplay(display); 15601 win.visibilityChanged(true); 15602 } 15603 } 15604 } 15605 break; 15606 case EventType.ClientMessage: 15607 if (e.xclient.message_type == GetAtom!("_X11SDPY_INSMME_FLAG_EVENT_", true)(e.xany.display)) { 15608 // "ignore next mouse motion" event, increment ignore counter for teh window 15609 if (auto win = e.xclient.window in SimpleWindow.nativeMapping) { 15610 ++(*win).warpEventCount; 15611 debug(x11sdpy_warp_debug) { import core.stdc.stdio : printf; printf("X11: got \"INSMME\" message, new count=%d\n", (*win).warpEventCount); } 15612 } else { 15613 debug(x11sdpy_warp_debug) { import core.stdc.stdio : printf; printf("X11: got \"INSMME\" WTF?!!\n"); } 15614 } 15615 } else if(e.xclient.data.l[0] == GetAtom!"WM_DELETE_WINDOW"(e.xany.display)) { 15616 // user clicked the close button on the window manager 15617 if(auto win = e.xclient.window in SimpleWindow.nativeMapping) { 15618 XUnlockDisplay(display); 15619 scope(exit) XLockDisplay(display); 15620 if ((*win).closeQuery !is null) (*win).closeQuery(); else (*win).close(); 15621 } 15622 15623 } else if(e.xclient.data.l[0] == GetAtom!"WM_TAKE_FOCUS"(e.xany.display)) { 15624 // writeln("HAPPENED"); 15625 // user clicked the close button on the window manager 15626 if(auto win = e.xclient.window in SimpleWindow.nativeMapping) { 15627 XUnlockDisplay(display); 15628 scope(exit) XLockDisplay(display); 15629 15630 auto setTo = *win; 15631 15632 if(win.setRequestedInputFocus !is null) { 15633 auto s = win.setRequestedInputFocus(); 15634 if(s !is null) { 15635 setTo = s; 15636 } 15637 } 15638 15639 assert(setTo !is null); 15640 15641 // FIXME: so this is actually supposed to focus to a relevant child window if appropriate 15642 15643 XSetInputFocus(display, setTo.impl.window, RevertToParent, e.xclient.data.l[1]); 15644 } 15645 } else if(e.xclient.message_type == GetAtom!"MANAGER"(e.xany.display)) { 15646 foreach(nai; NotificationAreaIcon.activeIcons) 15647 nai.newManager(); 15648 } else if(auto win = e.xclient.window in SimpleWindow.nativeMapping) { 15649 15650 bool xDragWindow = true; 15651 if(xDragWindow && e.xclient.message_type == GetAtom!"XdndStatus"(e.xany.display)) { 15652 //XDefineCursor(display, xDragWindow.impl.window, 15653 //writeln("XdndStatus ", e.xclient.data.l); 15654 } 15655 if(auto dh = win.dropHandler) { 15656 15657 static Atom[3] xFormatsBuffer; 15658 static Atom[] xFormats; 15659 15660 void resetXFormats() { 15661 xFormatsBuffer[] = 0; 15662 xFormats = xFormatsBuffer[]; 15663 } 15664 15665 if(e.xclient.message_type == GetAtom!"XdndEnter"(e.xany.display)) { 15666 // on Windows it is supposed to return the effect you actually do FIXME 15667 15668 auto sourceWindow = e.xclient.data.l[0]; 15669 15670 xFormatsBuffer[0] = e.xclient.data.l[2]; 15671 xFormatsBuffer[1] = e.xclient.data.l[3]; 15672 xFormatsBuffer[2] = e.xclient.data.l[4]; 15673 15674 if(e.xclient.data.l[1] & 1) { 15675 // can just grab it all but like we don't necessarily need them... 15676 xFormats = cast(Atom[]) getX11PropertyData(sourceWindow, GetAtom!"XdndTypeList"(display), XA_ATOM); 15677 } else { 15678 int len; 15679 foreach(fmt; xFormatsBuffer) 15680 if(fmt) len++; 15681 xFormats = xFormatsBuffer[0 .. len]; 15682 } 15683 15684 auto pkg = DropPackage(*win, e.xclient.data.l[0], 0, xFormats); 15685 15686 dh.dragEnter(&pkg); 15687 } else if(e.xclient.message_type == GetAtom!"XdndPosition"(e.xany.display)) { 15688 15689 auto pack = e.xclient.data.l[2]; 15690 15691 auto result = dh.dragOver(Point((pack & 0xffff0000) >> 16, pack & 0xffff)); // FIXME: translate screen coordinates back to window coords 15692 15693 15694 XClientMessageEvent xclient; 15695 15696 xclient.type = EventType.ClientMessage; 15697 xclient.window = e.xclient.data.l[0]; 15698 xclient.message_type = GetAtom!"XdndStatus"(display); 15699 xclient.format = 32; 15700 xclient.data.l[0] = win.impl.window; 15701 xclient.data.l[1] = (result.action != DragAndDropAction.none) ? 1 : 0; // will accept 15702 auto r = result.consistentWithin; 15703 xclient.data.l[2] = ((cast(short) r.left) << 16) | (cast(short) r.top); 15704 xclient.data.l[3] = ((cast(short) r.width) << 16) | (cast(short) r.height); 15705 xclient.data.l[4] = dndActionAtom(e.xany.display, result.action); 15706 15707 XSendEvent( 15708 display, 15709 e.xclient.data.l[0], 15710 false, 15711 EventMask.NoEventMask, 15712 cast(XEvent*) &xclient 15713 ); 15714 15715 15716 } else if(e.xclient.message_type == GetAtom!"XdndLeave"(e.xany.display)) { 15717 //writeln("XdndLeave"); 15718 // drop cancelled. 15719 // data.l[0] is the source window 15720 dh.dragLeave(); 15721 15722 resetXFormats(); 15723 } else if(e.xclient.message_type == GetAtom!"XdndDrop"(e.xany.display)) { 15724 // drop happening, should fetch data, then send finished 15725 // writeln("XdndDrop"); 15726 15727 auto pkg = DropPackage(*win, e.xclient.data.l[0], e.xclient.data.l[2], xFormats); 15728 15729 dh.drop(&pkg); 15730 15731 resetXFormats(); 15732 } else if(e.xclient.message_type == GetAtom!"XdndFinished"(e.xany.display)) { 15733 // writeln("XdndFinished"); 15734 15735 dh.finish(); 15736 } 15737 15738 } 15739 } 15740 break; 15741 case EventType.MapNotify: 15742 if(auto win = e.xmap.window in SimpleWindow.nativeMapping) { 15743 (*win)._visible = true; 15744 if (!(*win)._visibleForTheFirstTimeCalled) { 15745 (*win)._visibleForTheFirstTimeCalled = true; 15746 if ((*win).visibleForTheFirstTime !is null) { 15747 XUnlockDisplay(display); 15748 scope(exit) XLockDisplay(display); 15749 (*win).visibleForTheFirstTime(); 15750 } 15751 } 15752 if ((*win).visibilityChanged !is null) { 15753 XUnlockDisplay(display); 15754 scope(exit) XLockDisplay(display); 15755 (*win).visibilityChanged(true); 15756 } 15757 } 15758 break; 15759 case EventType.UnmapNotify: 15760 if(auto win = e.xunmap.window in SimpleWindow.nativeMapping) { 15761 win._visible = false; 15762 if (win.visibilityChanged !is null) { 15763 XUnlockDisplay(display); 15764 scope(exit) XLockDisplay(display); 15765 win.visibilityChanged(false); 15766 } 15767 } 15768 break; 15769 case EventType.DestroyNotify: 15770 if(auto win = e.xdestroywindow.window in SimpleWindow.nativeMapping) { 15771 if(win.destroyed) 15772 break; // might get a notification both for itself and from its parent 15773 if (win.onDestroyed !is null) try { win.onDestroyed(); } catch (Exception e) {} // sorry 15774 win._closed = true; // just in case 15775 win.destroyed = true; 15776 if (win.xic !is null) { 15777 XDestroyIC(win.xic); 15778 win.xic = null; // just in case 15779 } 15780 SimpleWindow.nativeMapping.remove(e.xdestroywindow.window); 15781 bool anyImportant = false; 15782 foreach(SimpleWindow w; SimpleWindow.nativeMapping) 15783 if(w.beingOpenKeepsAppOpen) { 15784 anyImportant = true; 15785 break; 15786 } 15787 if(!anyImportant) { 15788 EventLoop.quitApplication(); 15789 done = true; 15790 } 15791 } 15792 auto window = e.xdestroywindow.window; 15793 if(window in CapableOfHandlingNativeEvent.nativeHandleMapping) 15794 CapableOfHandlingNativeEvent.nativeHandleMapping.remove(window); 15795 15796 version(with_eventloop) { 15797 if(done) exit(); 15798 } 15799 break; 15800 15801 case EventType.MotionNotify: 15802 MouseEvent mouse; 15803 auto event = e.xmotion; 15804 15805 mouse.type = MouseEventType.motion; 15806 mouse.x = event.x; 15807 mouse.y = event.y; 15808 mouse.modifierState = event.state; 15809 15810 mouse.timestamp = event.time; 15811 15812 if(auto win = e.xmotion.window in SimpleWindow.nativeMapping) { 15813 mouse.window = *win; 15814 if (win.warpEventCount > 0) { 15815 debug(x11sdpy_warp_debug) { import core.stdc.stdio : printf; printf("X11: got \"warp motion\" message, current count=%d\n", (*win).warpEventCount); } 15816 --(*win).warpEventCount; 15817 (*win).mdx(mouse); // so deltas will be correctly updated 15818 } else { 15819 win.warpEventCount = 0; // just in case 15820 (*win).mdx(mouse); 15821 if((*win).handleMouseEvent) { 15822 XUnlockDisplay(display); 15823 scope(exit) XLockDisplay(display); 15824 (*win).handleMouseEvent(mouse); 15825 } 15826 } 15827 } 15828 15829 version(with_eventloop) 15830 send(mouse); 15831 break; 15832 case EventType.ButtonPress: 15833 case EventType.ButtonRelease: 15834 MouseEvent mouse; 15835 auto event = e.xbutton; 15836 15837 mouse.timestamp = event.time; 15838 15839 mouse.type = cast(MouseEventType) (e.type == EventType.ButtonPress ? 1 : 2); 15840 mouse.x = event.x; 15841 mouse.y = event.y; 15842 15843 static Time lastMouseDownTime = 0; 15844 static int lastMouseDownButton = -1; 15845 15846 mouse.doubleClick = e.type == EventType.ButtonPress && event.button == lastMouseDownButton && (event.time - lastMouseDownTime) < mouseDoubleClickTimeout; 15847 if(e.type == EventType.ButtonPress) { 15848 lastMouseDownTime = event.time; 15849 lastMouseDownButton = event.button; 15850 } 15851 15852 switch(event.button) { 15853 case 1: mouse.button = MouseButton.left; break; // left 15854 case 2: mouse.button = MouseButton.middle; break; // middle 15855 case 3: mouse.button = MouseButton.right; break; // right 15856 case 4: mouse.button = MouseButton.wheelUp; break; // scroll up 15857 case 5: mouse.button = MouseButton.wheelDown; break; // scroll down 15858 case 6: break; // idk 15859 case 7: break; // idk 15860 case 8: mouse.button = MouseButton.backButton; break; 15861 case 9: mouse.button = MouseButton.forwardButton; break; 15862 default: 15863 } 15864 15865 // FIXME: double check this 15866 mouse.modifierState = event.state; 15867 15868 //mouse.modifierState = event.detail; 15869 15870 if(auto win = e.xbutton.window in SimpleWindow.nativeMapping) { 15871 mouse.window = *win; 15872 (*win).mdx(mouse); 15873 if((*win).handleMouseEvent) { 15874 XUnlockDisplay(display); 15875 scope(exit) XLockDisplay(display); 15876 (*win).handleMouseEvent(mouse); 15877 } 15878 } 15879 version(with_eventloop) 15880 send(mouse); 15881 break; 15882 15883 case EventType.KeyPress: 15884 case EventType.KeyRelease: 15885 //if (e.type == EventType.KeyPress) { import core.stdc.stdio : stderr, fprintf; fprintf(stderr, "X11 keyboard event!\n"); } 15886 KeyEvent ke; 15887 ke.pressed = e.type == EventType.KeyPress; 15888 ke.hardwareCode = cast(ubyte) e.xkey.keycode; 15889 15890 auto sym = XKeycodeToKeysym( 15891 XDisplayConnection.get(), 15892 e.xkey.keycode, 15893 0); 15894 15895 ke.key = cast(Key) sym;//e.xkey.keycode; 15896 15897 ke.modifierState = e.xkey.state; 15898 15899 // writefln("%x", sym); 15900 wchar_t[128] charbuf = void; // buffer for XwcLookupString; composed value can consist of many chars! 15901 int charbuflen = 0; // return value of XwcLookupString 15902 if (ke.pressed) { 15903 auto win = e.xkey.window in SimpleWindow.nativeMapping; 15904 if (win !is null && win.xic !is null) { 15905 //{ import core.stdc.stdio : printf; printf("using xic!\n"); } 15906 Status status; 15907 charbuflen = XwcLookupString(win.xic, &e.xkey, charbuf.ptr, cast(int)charbuf.length, &sym, &status); 15908 //{ import core.stdc.stdio : printf; printf("charbuflen=%d\n", charbuflen); } 15909 } else { 15910 //{ import core.stdc.stdio : printf; printf("NOT using xic!\n"); } 15911 // If XIM initialization failed, don't process intl chars. Sorry, boys and girls. 15912 char[16] buffer; 15913 auto res = XLookupString(&e.xkey, buffer.ptr, buffer.length, null, null); 15914 if (res && buffer[0] < 128) charbuf[charbuflen++] = cast(wchar_t)buffer[0]; 15915 } 15916 } 15917 15918 // if there's no char, subst one 15919 if (charbuflen == 0) { 15920 switch (sym) { 15921 case 0xff09: charbuf[charbuflen++] = '\t'; break; 15922 case 0xff8d: // keypad enter 15923 case 0xff0d: charbuf[charbuflen++] = '\n'; break; 15924 default : // ignore 15925 } 15926 } 15927 15928 if (auto win = e.xkey.window in SimpleWindow.nativeMapping) { 15929 ke.window = *win; 15930 15931 15932 if(win.inputProxy) 15933 win = &win.inputProxy; 15934 15935 // char events are separate since they are on Windows too 15936 // also, xcompose can generate long char sequences 15937 // don't send char events if Meta and/or Hyper is pressed 15938 // TODO: ctrl+char should only send control chars; not yet 15939 if ((e.xkey.state&ModifierState.ctrl) != 0) { 15940 if (charbuflen > 1 || charbuf[0] >= ' ') charbuflen = 0; 15941 } 15942 15943 dchar[32] charsComingBuffer; 15944 int charsComingPosition; 15945 dchar[] charsComing = charsComingBuffer[]; 15946 15947 if (ke.pressed && charbuflen > 0) { 15948 // FIXME: I think Windows sends these on releases... we should try to match that, but idk about repeats. 15949 foreach (immutable dchar ch; charbuf[0..charbuflen]) { 15950 if(charsComingPosition >= charsComing.length) 15951 charsComing.length = charsComingPosition + 8; 15952 15953 charsComing[charsComingPosition++] = ch; 15954 } 15955 15956 charsComing = charsComing[0 .. charsComingPosition]; 15957 } else { 15958 charsComing = null; 15959 } 15960 15961 ke.charsPossible = charsComing; 15962 15963 if (win.handleKeyEvent) { 15964 XUnlockDisplay(display); 15965 scope(exit) XLockDisplay(display); 15966 win.handleKeyEvent(ke); 15967 } 15968 15969 // Super and alt modifier keys never actually send the chars, they are assumed to be special. 15970 if ((e.xkey.state&(ModifierState.alt|ModifierState.windows)) == 0 && win.handleCharEvent) { 15971 XUnlockDisplay(display); 15972 scope(exit) XLockDisplay(display); 15973 foreach(ch; charsComing) 15974 win.handleCharEvent(ch); 15975 } 15976 } 15977 15978 version(with_eventloop) 15979 send(ke); 15980 break; 15981 default: 15982 } 15983 15984 return done; 15985 } 15986 } 15987 15988 /* *************************************** */ 15989 /* Done with simpledisplay stuff */ 15990 /* *************************************** */ 15991 15992 // Necessary C library bindings follow 15993 version(Windows) {} else 15994 version(X11) { 15995 15996 extern(C) int eventfd (uint initval, int flags) nothrow @trusted @nogc; 15997 15998 // X11 bindings needed here 15999 /* 16000 A little of this is from the bindings project on 16001 D Source and some of it is copy/paste from the C 16002 header. 16003 16004 The DSource listing consistently used D's long 16005 where C used long. That's wrong - C long is 32 bit, so 16006 it should be int in D. I changed that here. 16007 16008 Note: 16009 This isn't complete, just took what I needed for myself. 16010 */ 16011 16012 import core.stdc.stddef : wchar_t; 16013 16014 interface XLib { 16015 extern(C) nothrow @nogc { 16016 char* XResourceManagerString(Display*); 16017 void XrmInitialize(); 16018 XrmDatabase XrmGetStringDatabase(char* data); 16019 bool XrmGetResource(XrmDatabase, const char*, const char*, char**, XrmValue*); 16020 16021 Cursor XCreateFontCursor(Display*, uint shape); 16022 int XDefineCursor(Display* display, Window w, Cursor cursor); 16023 int XUndefineCursor(Display* display, Window w); 16024 16025 Pixmap XCreateBitmapFromData(Display* display, Drawable d, const(char)* data, uint width, uint height); 16026 Cursor XCreatePixmapCursor(Display* display, Pixmap source, Pixmap mask, XColor* foreground_color, XColor* background_color, uint x, uint y); 16027 int XFreeCursor(Display* display, Cursor cursor); 16028 16029 int XLookupString(XKeyEvent *event_struct, char *buffer_return, int bytes_buffer, KeySym *keysym_return, void *status_in_out); 16030 16031 int XwcLookupString(XIC ic, XKeyPressedEvent* event, wchar_t* buffer_return, int wchars_buffer, KeySym* keysym_return, Status* status_return); 16032 16033 XVaNestedList XVaCreateNestedList(int unused, ...); 16034 16035 char *XKeysymToString(KeySym keysym); 16036 KeySym XKeycodeToKeysym( 16037 Display* /* display */, 16038 KeyCode /* keycode */, 16039 int /* index */ 16040 ); 16041 16042 int XConvertSelection(Display *display, Atom selection, Atom target, Atom property, Window requestor, Time time); 16043 16044 int XFree(void*); 16045 int XDeleteProperty(Display *display, Window w, Atom property); 16046 16047 int XChangeProperty(Display *display, Window w, Atom property, Atom type, int format, int mode, scope const void *data, int nelements); 16048 16049 int XGetWindowProperty(Display *display, Window w, Atom property, arch_long 16050 long_offset, arch_long long_length, Bool del, Atom req_type, Atom 16051 *actual_type_return, int *actual_format_return, arch_ulong 16052 *nitems_return, arch_ulong *bytes_after_return, void** prop_return); 16053 Atom* XListProperties(Display *display, Window w, int *num_prop_return); 16054 Status XGetTextProperty(Display *display, Window w, XTextProperty *text_prop_return, Atom property); 16055 Status XQueryTree(Display *display, Window w, Window *root_return, Window *parent_return, Window **children_return, uint *nchildren_return); 16056 16057 int XSetSelectionOwner(Display *display, Atom selection, Window owner, Time time); 16058 16059 Window XGetSelectionOwner(Display *display, Atom selection); 16060 16061 XVisualInfo* XGetVisualInfo(Display*, c_long, XVisualInfo*, int*); 16062 16063 char** XListFonts(Display*, const char*, int, int*); 16064 void XFreeFontNames(char**); 16065 16066 Display* XOpenDisplay(const char*); 16067 int XCloseDisplay(Display*); 16068 16069 int function() XSynchronize(Display*, bool); 16070 int function() XSetAfterFunction(Display*, int function() proc); 16071 16072 Bool XQueryExtension(Display*, const char*, int*, int*, int*); 16073 16074 Bool XSupportsLocale(); 16075 char* XSetLocaleModifiers(const(char)* modifier_list); 16076 XOM XOpenOM(Display* display, _XrmHashBucketRec* rdb, const(char)* res_name, const(char)* res_class); 16077 Status XCloseOM(XOM om); 16078 16079 XIM XOpenIM(Display* dpy, _XrmHashBucketRec* rdb, const(char)* res_name, const(char)* res_class); 16080 Status XCloseIM(XIM im); 16081 16082 char* XGetIMValues(XIM im, ...) /*_X_SENTINEL(0)*/; 16083 char* XSetIMValues(XIM im, ...) /*_X_SENTINEL(0)*/; 16084 Display* XDisplayOfIM(XIM im); 16085 char* XLocaleOfIM(XIM im); 16086 XIC XCreateIC(XIM im, ...) /*_X_SENTINEL(0)*/; 16087 void XDestroyIC(XIC ic); 16088 void XSetICFocus(XIC ic); 16089 void XUnsetICFocus(XIC ic); 16090 //wchar_t* XwcResetIC(XIC ic); 16091 char* XmbResetIC(XIC ic); 16092 char* Xutf8ResetIC(XIC ic); 16093 char* XSetICValues(XIC ic, ...) /*_X_SENTINEL(0)*/; 16094 char* XGetICValues(XIC ic, ...) /*_X_SENTINEL(0)*/; 16095 XIM XIMOfIC(XIC ic); 16096 16097 uint XSendEvent(Display* display, Window w, Bool propagate, arch_long event_mask, XEvent* event_send); 16098 16099 16100 XFontStruct *XLoadQueryFont(Display *display, scope const char *name); 16101 int XFreeFont(Display *display, XFontStruct *font_struct); 16102 int XSetFont(Display* display, GC gc, Font font); 16103 int XTextWidth(XFontStruct*, scope const char*, int); 16104 16105 int XSetLineAttributes(Display *display, GC gc, uint line_width, int line_style, int cap_style, int join_style); 16106 int XSetDashes(Display *display, GC gc, int dash_offset, scope const byte* dash_list, int n); 16107 16108 Window XCreateSimpleWindow( 16109 Display* /* display */, 16110 Window /* parent */, 16111 int /* x */, 16112 int /* y */, 16113 uint /* width */, 16114 uint /* height */, 16115 uint /* border_width */, 16116 uint /* border */, 16117 uint /* background */ 16118 ); 16119 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); 16120 16121 int XReparentWindow(Display*, Window, Window, int, int); 16122 int XClearWindow(Display*, Window); 16123 int XMoveResizeWindow(Display*, Window, int, int, uint, uint); 16124 int XMoveWindow(Display*, Window, int, int); 16125 int XResizeWindow(Display *display, Window w, uint width, uint height); 16126 16127 Colormap XCreateColormap(Display *display, Window w, Visual *visual, int alloc); 16128 16129 Status XMatchVisualInfo(Display *display, int screen, int depth, int class_, XVisualInfo *vinfo_return); 16130 16131 Status XGetWindowAttributes(Display*, Window, XWindowAttributes*); 16132 16133 XImage *XCreateImage( 16134 Display* /* display */, 16135 Visual* /* visual */, 16136 uint /* depth */, 16137 int /* format */, 16138 int /* offset */, 16139 ubyte* /* data */, 16140 uint /* width */, 16141 uint /* height */, 16142 int /* bitmap_pad */, 16143 int /* bytes_per_line */ 16144 ); 16145 16146 Status XInitImage (XImage* image); 16147 16148 Atom XInternAtom( 16149 Display* /* display */, 16150 const char* /* atom_name */, 16151 Bool /* only_if_exists */ 16152 ); 16153 16154 Status XInternAtoms(Display*, const char**, int, Bool, Atom*); 16155 char* XGetAtomName(Display*, Atom); 16156 Status XGetAtomNames(Display*, Atom*, int count, char**); 16157 16158 int XPutImage( 16159 Display* /* display */, 16160 Drawable /* d */, 16161 GC /* gc */, 16162 XImage* /* image */, 16163 int /* src_x */, 16164 int /* src_y */, 16165 int /* dest_x */, 16166 int /* dest_y */, 16167 uint /* width */, 16168 uint /* height */ 16169 ); 16170 16171 XImage *XGetImage(Display *display, Drawable d, int x, int y, uint width, uint height, c_ulong plane_mask, int format); 16172 16173 16174 int XDestroyWindow( 16175 Display* /* display */, 16176 Window /* w */ 16177 ); 16178 16179 int XDestroyImage(XImage*); 16180 16181 int XSelectInput( 16182 Display* /* display */, 16183 Window /* w */, 16184 EventMask /* event_mask */ 16185 ); 16186 16187 int XMapWindow( 16188 Display* /* display */, 16189 Window /* w */ 16190 ); 16191 16192 Status XIconifyWindow(Display*, Window, int); 16193 int XMapRaised(Display*, Window); 16194 int XMapSubwindows(Display*, Window); 16195 16196 int XNextEvent( 16197 Display* /* display */, 16198 XEvent* /* event_return */ 16199 ); 16200 16201 int XMaskEvent(Display*, arch_long, XEvent*); 16202 16203 Bool XFilterEvent(XEvent *event, Window window); 16204 int XRefreshKeyboardMapping(XMappingEvent *event_map); 16205 16206 Status XSetWMProtocols( 16207 Display* /* display */, 16208 Window /* w */, 16209 Atom* /* protocols */, 16210 int /* count */ 16211 ); 16212 16213 void XSetWMNormalHints(Display *display, Window w, XSizeHints *hints); 16214 Status XGetWMNormalHints(Display *display, Window w, XSizeHints *hints, c_long* supplied_return); 16215 16216 16217 Status XInitThreads(); 16218 void XLockDisplay (Display* display); 16219 void XUnlockDisplay (Display* display); 16220 16221 void XSetWMProperties(Display*, Window, XTextProperty*, XTextProperty*, char**, int, XSizeHints*, XWMHints*, XClassHint*); 16222 16223 int XSetWindowBackground (Display* display, Window w, c_ulong background_pixel); 16224 int XSetWindowBackgroundPixmap (Display* display, Window w, Pixmap background_pixmap); 16225 //int XSetWindowBorder (Display* display, Window w, c_ulong border_pixel); 16226 //int XSetWindowBorderPixmap (Display* display, Window w, Pixmap border_pixmap); 16227 //int XSetWindowBorderWidth (Display* display, Window w, uint width); 16228 16229 16230 // check out Xft too: http://www.keithp.com/~keithp/render/Xft.tutorial 16231 int XDrawString(Display*, Drawable, GC, int, int, scope const char*, int); 16232 int XDrawLine(Display*, Drawable, GC, int, int, int, int); 16233 int XDrawRectangle(Display*, Drawable, GC, int, int, uint, uint); 16234 int XDrawArc(Display*, Drawable, GC, int, int, uint, uint, int, int); 16235 int XFillRectangle(Display*, Drawable, GC, int, int, uint, uint); 16236 int XFillArc(Display*, Drawable, GC, int, int, uint, uint, int, int); 16237 int XDrawPoint(Display*, Drawable, GC, int, int); 16238 int XSetForeground(Display*, GC, uint); 16239 int XSetBackground(Display*, GC, uint); 16240 16241 XFontSet XCreateFontSet(Display*, const char*, char***, int*, char**); 16242 void XFreeFontSet(Display*, XFontSet); 16243 void Xutf8DrawString(Display*, Drawable, XFontSet, GC, int, int, scope const char*, int); 16244 void Xutf8DrawText(Display*, Drawable, GC, int, int, XmbTextItem*, int); 16245 16246 int Xutf8TextExtents(XFontSet font_set, const char *, int num_bytes, XRectangle *overall_ink_return, XRectangle *overall_logical_return); 16247 16248 16249 //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); 16250 16251 void XDrawText(Display*, Drawable, GC, int, int, XTextItem*, int); 16252 int XSetFunction(Display*, GC, int); 16253 16254 GC XCreateGC(Display*, Drawable, uint, void*); 16255 int XCopyGC(Display*, GC, uint, GC); 16256 int XFreeGC(Display*, GC); 16257 16258 bool XCheckWindowEvent(Display*, Window, int, XEvent*); 16259 bool XCheckMaskEvent(Display*, int, XEvent*); 16260 16261 int XPending(Display*); 16262 int XEventsQueued(Display* display, int mode); 16263 16264 Pixmap XCreatePixmap(Display*, Drawable, uint, uint, uint); 16265 int XFreePixmap(Display*, Pixmap); 16266 int XCopyArea(Display*, Drawable, Drawable, GC, int, int, uint, uint, int, int); 16267 int XFlush(Display*); 16268 int XBell(Display*, int); 16269 int XSync(Display*, bool); 16270 16271 int XGrabKey (Display* display, int keycode, uint modifiers, Window grab_window, Bool owner_events, int pointer_mode, int keyboard_mode); 16272 int XUngrabKey (Display* display, int keycode, uint modifiers, Window grab_window); 16273 16274 int XGrabKeyboard(Display*, Window, Bool, int, int, Time); 16275 int XUngrabKeyboard(Display*, Time); 16276 16277 KeyCode XKeysymToKeycode (Display* display, KeySym keysym); 16278 16279 KeySym XStringToKeysym(const char *string); 16280 16281 Bool XCheckTypedEvent(Display *display, int event_type, XEvent *event_return); 16282 16283 Window XDefaultRootWindow(Display*); 16284 16285 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); 16286 16287 int XUngrabButton(Display *display, uint button, uint modifiers, Window grab_window); 16288 16289 int XDrawLines(Display*, Drawable, GC, XPoint*, int, CoordMode); 16290 int XFillPolygon(Display*, Drawable, GC, XPoint*, int, PolygonShape, CoordMode); 16291 16292 Status XAllocColor(Display*, Colormap, XColor*); 16293 16294 int XWithdrawWindow(Display*, Window, int); 16295 int XUnmapWindow(Display*, Window); 16296 int XLowerWindow(Display*, Window); 16297 int XRaiseWindow(Display*, Window); 16298 16299 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); 16300 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); 16301 16302 int XGetInputFocus(Display*, Window*, int*); 16303 int XSetInputFocus(Display*, Window, int, Time); 16304 16305 XErrorHandler XSetErrorHandler(XErrorHandler); 16306 16307 int XGetErrorText(Display*, int, char*, int); 16308 16309 Bool XkbSetDetectableAutoRepeat(Display* dpy, Bool detectable, Bool* supported); 16310 16311 16312 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); 16313 int XUngrabPointer(Display *display, Time time); 16314 int XChangeActivePointerGrab(Display *display, uint event_mask, Cursor cursor, Time time); 16315 16316 int XCopyPlane(Display*, Drawable, Drawable, GC, int, int, uint, uint, int, int, arch_ulong); 16317 16318 Status XGetGeometry(Display*, Drawable, Window*, int*, int*, uint*, uint*, uint*, uint*); 16319 int XSetClipMask(Display*, GC, Pixmap); 16320 int XSetClipOrigin(Display*, GC, int, int); 16321 16322 void XSetClipRectangles(Display*, GC, int, int, XRectangle*, int, int); 16323 16324 void XSetWMName(Display*, Window, XTextProperty*); 16325 Status XGetWMName(Display*, Window, XTextProperty*); 16326 int XStoreName(Display* display, Window w, const(char)* window_name); 16327 16328 XIOErrorHandler XSetIOErrorHandler (XIOErrorHandler handler); 16329 16330 } 16331 } 16332 16333 interface Xext { 16334 extern(C) nothrow @nogc { 16335 Status XShmAttach(Display*, XShmSegmentInfo*); 16336 Status XShmDetach(Display*, XShmSegmentInfo*); 16337 Status XShmPutImage( 16338 Display* /* dpy */, 16339 Drawable /* d */, 16340 GC /* gc */, 16341 XImage* /* image */, 16342 int /* src_x */, 16343 int /* src_y */, 16344 int /* dst_x */, 16345 int /* dst_y */, 16346 uint /* src_width */, 16347 uint /* src_height */, 16348 Bool /* send_event */ 16349 ); 16350 16351 Status XShmQueryExtension(Display*); 16352 16353 XImage *XShmCreateImage( 16354 Display* /* dpy */, 16355 Visual* /* visual */, 16356 uint /* depth */, 16357 int /* format */, 16358 char* /* data */, 16359 XShmSegmentInfo* /* shminfo */, 16360 uint /* width */, 16361 uint /* height */ 16362 ); 16363 16364 Pixmap XShmCreatePixmap( 16365 Display* /* dpy */, 16366 Drawable /* d */, 16367 char* /* data */, 16368 XShmSegmentInfo* /* shminfo */, 16369 uint /* width */, 16370 uint /* height */, 16371 uint /* depth */ 16372 ); 16373 16374 } 16375 } 16376 16377 // this requires -lXpm 16378 //int XpmCreatePixmapFromData(Display*, Drawable, scope const char**, Pixmap*, Pixmap*, void*); // FIXME: void* should be XpmAttributes 16379 16380 16381 mixin DynamicLoad!(XLib, "X11", 6, librariesSuccessfullyLoaded) xlib; 16382 mixin DynamicLoad!(Xext, "Xext", 6, librariesSuccessfullyLoaded) xext; 16383 shared static this() { 16384 xlib.loadDynamicLibrary(); 16385 xext.loadDynamicLibrary(); 16386 } 16387 16388 16389 extern(C) nothrow @nogc { 16390 16391 alias XrmDatabase = void*; 16392 struct XrmValue { 16393 uint size; 16394 void* addr; 16395 } 16396 16397 struct XVisualInfo { 16398 Visual* visual; 16399 VisualID visualid; 16400 int screen; 16401 uint depth; 16402 int c_class; 16403 c_ulong red_mask; 16404 c_ulong green_mask; 16405 c_ulong blue_mask; 16406 int colormap_size; 16407 int bits_per_rgb; 16408 } 16409 16410 enum VisualNoMask= 0x0; 16411 enum VisualIDMask= 0x1; 16412 enum VisualScreenMask=0x2; 16413 enum VisualDepthMask= 0x4; 16414 enum VisualClassMask= 0x8; 16415 enum VisualRedMaskMask=0x10; 16416 enum VisualGreenMaskMask=0x20; 16417 enum VisualBlueMaskMask=0x40; 16418 enum VisualColormapSizeMask=0x80; 16419 enum VisualBitsPerRGBMask=0x100; 16420 enum VisualAllMask= 0x1FF; 16421 16422 enum AnyKey = 0; 16423 enum AnyModifier = 1 << 15; 16424 16425 // XIM and other crap 16426 struct _XOM {} 16427 struct _XIM {} 16428 struct _XIC {} 16429 alias XOM = _XOM*; 16430 alias XIM = _XIM*; 16431 alias XIC = _XIC*; 16432 16433 alias XVaNestedList = void*; 16434 16435 alias XIMStyle = arch_ulong; 16436 enum : arch_ulong { 16437 XIMPreeditArea = 0x0001, 16438 XIMPreeditCallbacks = 0x0002, 16439 XIMPreeditPosition = 0x0004, 16440 XIMPreeditNothing = 0x0008, 16441 XIMPreeditNone = 0x0010, 16442 XIMStatusArea = 0x0100, 16443 XIMStatusCallbacks = 0x0200, 16444 XIMStatusNothing = 0x0400, 16445 XIMStatusNone = 0x0800, 16446 } 16447 16448 16449 /* X Shared Memory Extension functions */ 16450 //pragma(lib, "Xshm"); 16451 alias arch_ulong ShmSeg; 16452 struct XShmSegmentInfo { 16453 ShmSeg shmseg; 16454 int shmid; 16455 ubyte* shmaddr; 16456 Bool readOnly; 16457 } 16458 16459 // and the necessary OS functions 16460 int shmget(int, size_t, int); 16461 void* shmat(int, scope const void*, int); 16462 int shmdt(scope const void*); 16463 int shmctl (int shmid, int cmd, void* ptr /*struct shmid_ds *buf*/); 16464 16465 enum IPC_PRIVATE = 0; 16466 enum IPC_CREAT = 512; 16467 enum IPC_RMID = 0; 16468 16469 /* MIT-SHM end */ 16470 16471 16472 enum MappingType:int { 16473 MappingModifier =0, 16474 MappingKeyboard =1, 16475 MappingPointer =2 16476 } 16477 16478 /* ImageFormat -- PutImage, GetImage */ 16479 enum ImageFormat:int { 16480 XYBitmap =0, /* depth 1, XYFormat */ 16481 XYPixmap =1, /* depth == drawable depth */ 16482 ZPixmap =2 /* depth == drawable depth */ 16483 } 16484 16485 enum ModifierName:int { 16486 ShiftMapIndex =0, 16487 LockMapIndex =1, 16488 ControlMapIndex =2, 16489 Mod1MapIndex =3, 16490 Mod2MapIndex =4, 16491 Mod3MapIndex =5, 16492 Mod4MapIndex =6, 16493 Mod5MapIndex =7 16494 } 16495 16496 enum ButtonMask:int { 16497 Button1Mask =1<<8, 16498 Button2Mask =1<<9, 16499 Button3Mask =1<<10, 16500 Button4Mask =1<<11, 16501 Button5Mask =1<<12, 16502 AnyModifier =1<<15/* used in GrabButton, GrabKey */ 16503 } 16504 16505 enum KeyOrButtonMask:uint { 16506 ShiftMask =1<<0, 16507 LockMask =1<<1, 16508 ControlMask =1<<2, 16509 Mod1Mask =1<<3, 16510 Mod2Mask =1<<4, 16511 Mod3Mask =1<<5, 16512 Mod4Mask =1<<6, 16513 Mod5Mask =1<<7, 16514 Button1Mask =1<<8, 16515 Button2Mask =1<<9, 16516 Button3Mask =1<<10, 16517 Button4Mask =1<<11, 16518 Button5Mask =1<<12, 16519 AnyModifier =1<<15/* used in GrabButton, GrabKey */ 16520 } 16521 16522 enum ButtonName:int { 16523 Button1 =1, 16524 Button2 =2, 16525 Button3 =3, 16526 Button4 =4, 16527 Button5 =5 16528 } 16529 16530 /* Notify modes */ 16531 enum NotifyModes:int 16532 { 16533 NotifyNormal =0, 16534 NotifyGrab =1, 16535 NotifyUngrab =2, 16536 NotifyWhileGrabbed =3 16537 } 16538 enum NotifyHint = 1; /* for MotionNotify events */ 16539 16540 /* Notify detail */ 16541 enum NotifyDetail:int 16542 { 16543 NotifyAncestor =0, 16544 NotifyVirtual =1, 16545 NotifyInferior =2, 16546 NotifyNonlinear =3, 16547 NotifyNonlinearVirtual =4, 16548 NotifyPointer =5, 16549 NotifyPointerRoot =6, 16550 NotifyDetailNone =7 16551 } 16552 16553 /* Visibility notify */ 16554 16555 enum VisibilityNotify:int 16556 { 16557 VisibilityUnobscured =0, 16558 VisibilityPartiallyObscured =1, 16559 VisibilityFullyObscured =2 16560 } 16561 16562 16563 enum WindowStackingMethod:int 16564 { 16565 Above =0, 16566 Below =1, 16567 TopIf =2, 16568 BottomIf =3, 16569 Opposite =4 16570 } 16571 16572 /* Circulation request */ 16573 enum CirculationRequest:int 16574 { 16575 PlaceOnTop =0, 16576 PlaceOnBottom =1 16577 } 16578 16579 enum PropertyNotification:int 16580 { 16581 PropertyNewValue =0, 16582 PropertyDelete =1 16583 } 16584 16585 enum ColorMapNotification:int 16586 { 16587 ColormapUninstalled =0, 16588 ColormapInstalled =1 16589 } 16590 16591 16592 struct _XPrivate {} 16593 struct _XrmHashBucketRec {} 16594 16595 alias void* XPointer; 16596 alias void* XExtData; 16597 16598 version( X86_64 ) { 16599 alias ulong XID; 16600 alias ulong arch_ulong; 16601 alias long arch_long; 16602 } else version (AArch64) { 16603 alias ulong XID; 16604 alias ulong arch_ulong; 16605 alias long arch_long; 16606 } else { 16607 alias uint XID; 16608 alias uint arch_ulong; 16609 alias int arch_long; 16610 } 16611 16612 alias XID Window; 16613 alias XID Drawable; 16614 alias XID Pixmap; 16615 16616 alias arch_ulong Atom; 16617 alias int Bool; 16618 alias Display XDisplay; 16619 16620 alias int ByteOrder; 16621 alias arch_ulong Time; 16622 alias void ScreenFormat; 16623 16624 struct XImage { 16625 int width, height; /* size of image */ 16626 int xoffset; /* number of pixels offset in X direction */ 16627 ImageFormat format; /* XYBitmap, XYPixmap, ZPixmap */ 16628 void *data; /* pointer to image data */ 16629 ByteOrder byte_order; /* data byte order, LSBFirst, MSBFirst */ 16630 int bitmap_unit; /* quant. of scanline 8, 16, 32 */ 16631 int bitmap_bit_order; /* LSBFirst, MSBFirst */ 16632 int bitmap_pad; /* 8, 16, 32 either XY or ZPixmap */ 16633 int depth; /* depth of image */ 16634 int bytes_per_line; /* accelarator to next line */ 16635 int bits_per_pixel; /* bits per pixel (ZPixmap) */ 16636 arch_ulong red_mask; /* bits in z arrangment */ 16637 arch_ulong green_mask; 16638 arch_ulong blue_mask; 16639 XPointer obdata; /* hook for the object routines to hang on */ 16640 static struct F { /* image manipulation routines */ 16641 XImage* function( 16642 XDisplay* /* display */, 16643 Visual* /* visual */, 16644 uint /* depth */, 16645 int /* format */, 16646 int /* offset */, 16647 ubyte* /* data */, 16648 uint /* width */, 16649 uint /* height */, 16650 int /* bitmap_pad */, 16651 int /* bytes_per_line */) create_image; 16652 int function(XImage *) destroy_image; 16653 arch_ulong function(XImage *, int, int) get_pixel; 16654 int function(XImage *, int, int, arch_ulong) put_pixel; 16655 XImage* function(XImage *, int, int, uint, uint) sub_image; 16656 int function(XImage *, arch_long) add_pixel; 16657 } 16658 F f; 16659 } 16660 version(X86_64) static assert(XImage.sizeof == 136); 16661 else version(X86) static assert(XImage.sizeof == 88); 16662 16663 struct XCharStruct { 16664 short lbearing; /* origin to left edge of raster */ 16665 short rbearing; /* origin to right edge of raster */ 16666 short width; /* advance to next char's origin */ 16667 short ascent; /* baseline to top edge of raster */ 16668 short descent; /* baseline to bottom edge of raster */ 16669 ushort attributes; /* per char flags (not predefined) */ 16670 } 16671 16672 /* 16673 * To allow arbitrary information with fonts, there are additional properties 16674 * returned. 16675 */ 16676 struct XFontProp { 16677 Atom name; 16678 arch_ulong card32; 16679 } 16680 16681 alias Atom Font; 16682 16683 struct XFontStruct { 16684 XExtData *ext_data; /* Hook for extension to hang data */ 16685 Font fid; /* Font ID for this font */ 16686 uint direction; /* Direction the font is painted */ 16687 uint min_char_or_byte2; /* First character */ 16688 uint max_char_or_byte2; /* Last character */ 16689 uint min_byte1; /* First row that exists (for two-byte fonts) */ 16690 uint max_byte1; /* Last row that exists (for two-byte fonts) */ 16691 Bool all_chars_exist; /* Flag if all characters have nonzero size */ 16692 uint default_char; /* Char to print for undefined character */ 16693 int n_properties; /* How many properties there are */ 16694 XFontProp *properties; /* Pointer to array of additional properties*/ 16695 XCharStruct min_bounds; /* Minimum bounds over all existing char*/ 16696 XCharStruct max_bounds; /* Maximum bounds over all existing char*/ 16697 XCharStruct *per_char; /* first_char to last_char information */ 16698 int ascent; /* Max extent above baseline for spacing */ 16699 int descent; /* Max descent below baseline for spacing */ 16700 } 16701 16702 16703 /* 16704 * Definitions of specific events. 16705 */ 16706 struct XKeyEvent 16707 { 16708 int type; /* of event */ 16709 arch_ulong serial; /* # of last request processed by server */ 16710 Bool send_event; /* true if this came from a SendEvent request */ 16711 Display *display; /* Display the event was read from */ 16712 Window window; /* "event" window it is reported relative to */ 16713 Window root; /* root window that the event occurred on */ 16714 Window subwindow; /* child window */ 16715 Time time; /* milliseconds */ 16716 int x, y; /* pointer x, y coordinates in event window */ 16717 int x_root, y_root; /* coordinates relative to root */ 16718 KeyOrButtonMask state; /* key or button mask */ 16719 uint keycode; /* detail */ 16720 Bool same_screen; /* same screen flag */ 16721 } 16722 version(X86_64) static assert(XKeyEvent.sizeof == 96); 16723 alias XKeyEvent XKeyPressedEvent; 16724 alias XKeyEvent XKeyReleasedEvent; 16725 16726 struct XButtonEvent 16727 { 16728 int type; /* of event */ 16729 arch_ulong serial; /* # of last request processed by server */ 16730 Bool send_event; /* true if this came from a SendEvent request */ 16731 Display *display; /* Display the event was read from */ 16732 Window window; /* "event" window it is reported relative to */ 16733 Window root; /* root window that the event occurred on */ 16734 Window subwindow; /* child window */ 16735 Time time; /* milliseconds */ 16736 int x, y; /* pointer x, y coordinates in event window */ 16737 int x_root, y_root; /* coordinates relative to root */ 16738 KeyOrButtonMask state; /* key or button mask */ 16739 uint button; /* detail */ 16740 Bool same_screen; /* same screen flag */ 16741 } 16742 alias XButtonEvent XButtonPressedEvent; 16743 alias XButtonEvent XButtonReleasedEvent; 16744 16745 struct XMotionEvent{ 16746 int type; /* of event */ 16747 arch_ulong serial; /* # of last request processed by server */ 16748 Bool send_event; /* true if this came from a SendEvent request */ 16749 Display *display; /* Display the event was read from */ 16750 Window window; /* "event" window reported relative to */ 16751 Window root; /* root window that the event occurred on */ 16752 Window subwindow; /* child window */ 16753 Time time; /* milliseconds */ 16754 int x, y; /* pointer x, y coordinates in event window */ 16755 int x_root, y_root; /* coordinates relative to root */ 16756 KeyOrButtonMask state; /* key or button mask */ 16757 byte is_hint; /* detail */ 16758 Bool same_screen; /* same screen flag */ 16759 } 16760 alias XMotionEvent XPointerMovedEvent; 16761 16762 struct XCrossingEvent{ 16763 int type; /* of event */ 16764 arch_ulong serial; /* # of last request processed by server */ 16765 Bool send_event; /* true if this came from a SendEvent request */ 16766 Display *display; /* Display the event was read from */ 16767 Window window; /* "event" window reported relative to */ 16768 Window root; /* root window that the event occurred on */ 16769 Window subwindow; /* child window */ 16770 Time time; /* milliseconds */ 16771 int x, y; /* pointer x, y coordinates in event window */ 16772 int x_root, y_root; /* coordinates relative to root */ 16773 NotifyModes mode; /* NotifyNormal, NotifyGrab, NotifyUngrab */ 16774 NotifyDetail detail; 16775 /* 16776 * NotifyAncestor, NotifyVirtual, NotifyInferior, 16777 * NotifyNonlinear,NotifyNonlinearVirtual 16778 */ 16779 Bool same_screen; /* same screen flag */ 16780 Bool focus; /* Boolean focus */ 16781 KeyOrButtonMask state; /* key or button mask */ 16782 } 16783 alias XCrossingEvent XEnterWindowEvent; 16784 alias XCrossingEvent XLeaveWindowEvent; 16785 16786 struct XFocusChangeEvent{ 16787 int type; /* FocusIn or FocusOut */ 16788 arch_ulong serial; /* # of last request processed by server */ 16789 Bool send_event; /* true if this came from a SendEvent request */ 16790 Display *display; /* Display the event was read from */ 16791 Window window; /* window of event */ 16792 NotifyModes mode; /* NotifyNormal, NotifyWhileGrabbed, 16793 NotifyGrab, NotifyUngrab */ 16794 NotifyDetail detail; 16795 /* 16796 * NotifyAncestor, NotifyVirtual, NotifyInferior, 16797 * NotifyNonlinear,NotifyNonlinearVirtual, NotifyPointer, 16798 * NotifyPointerRoot, NotifyDetailNone 16799 */ 16800 } 16801 alias XFocusChangeEvent XFocusInEvent; 16802 alias XFocusChangeEvent XFocusOutEvent; 16803 16804 enum CWBackPixmap = (1L<<0); 16805 enum CWBackPixel = (1L<<1); 16806 enum CWBorderPixmap = (1L<<2); 16807 enum CWBorderPixel = (1L<<3); 16808 enum CWBitGravity = (1L<<4); 16809 enum CWWinGravity = (1L<<5); 16810 enum CWBackingStore = (1L<<6); 16811 enum CWBackingPlanes = (1L<<7); 16812 enum CWBackingPixel = (1L<<8); 16813 enum CWOverrideRedirect = (1L<<9); 16814 enum CWSaveUnder = (1L<<10); 16815 enum CWEventMask = (1L<<11); 16816 enum CWDontPropagate = (1L<<12); 16817 enum CWColormap = (1L<<13); 16818 enum CWCursor = (1L<<14); 16819 16820 struct XWindowAttributes { 16821 int x, y; /* location of window */ 16822 int width, height; /* width and height of window */ 16823 int border_width; /* border width of window */ 16824 int depth; /* depth of window */ 16825 Visual *visual; /* the associated visual structure */ 16826 Window root; /* root of screen containing window */ 16827 int class_; /* InputOutput, InputOnly*/ 16828 int bit_gravity; /* one of the bit gravity values */ 16829 int win_gravity; /* one of the window gravity values */ 16830 int backing_store; /* NotUseful, WhenMapped, Always */ 16831 arch_ulong backing_planes; /* planes to be preserved if possible */ 16832 arch_ulong backing_pixel; /* value to be used when restoring planes */ 16833 Bool save_under; /* boolean, should bits under be saved? */ 16834 Colormap colormap; /* color map to be associated with window */ 16835 Bool map_installed; /* boolean, is color map currently installed*/ 16836 int map_state; /* IsUnmapped, IsUnviewable, IsViewable */ 16837 arch_long all_event_masks; /* set of events all people have interest in*/ 16838 arch_long your_event_mask; /* my event mask */ 16839 arch_long do_not_propagate_mask; /* set of events that should not propagate */ 16840 Bool override_redirect; /* boolean value for override-redirect */ 16841 Screen *screen; /* back pointer to correct screen */ 16842 } 16843 16844 enum IsUnmapped = 0; 16845 enum IsUnviewable = 1; 16846 enum IsViewable = 2; 16847 16848 struct XSetWindowAttributes { 16849 Pixmap background_pixmap;/* background, None, or ParentRelative */ 16850 arch_ulong background_pixel;/* background pixel */ 16851 Pixmap border_pixmap; /* border of the window or CopyFromParent */ 16852 arch_ulong border_pixel;/* border pixel value */ 16853 int bit_gravity; /* one of bit gravity values */ 16854 int win_gravity; /* one of the window gravity values */ 16855 int backing_store; /* NotUseful, WhenMapped, Always */ 16856 arch_ulong backing_planes;/* planes to be preserved if possible */ 16857 arch_ulong backing_pixel;/* value to use in restoring planes */ 16858 Bool save_under; /* should bits under be saved? (popups) */ 16859 arch_long event_mask; /* set of events that should be saved */ 16860 arch_long do_not_propagate_mask;/* set of events that should not propagate */ 16861 Bool override_redirect; /* boolean value for override_redirect */ 16862 Colormap colormap; /* color map to be associated with window */ 16863 Cursor cursor; /* cursor to be displayed (or None) */ 16864 } 16865 16866 16867 alias int Status; 16868 16869 16870 enum EventMask:int 16871 { 16872 NoEventMask =0, 16873 KeyPressMask =1<<0, 16874 KeyReleaseMask =1<<1, 16875 ButtonPressMask =1<<2, 16876 ButtonReleaseMask =1<<3, 16877 EnterWindowMask =1<<4, 16878 LeaveWindowMask =1<<5, 16879 PointerMotionMask =1<<6, 16880 PointerMotionHintMask =1<<7, 16881 Button1MotionMask =1<<8, 16882 Button2MotionMask =1<<9, 16883 Button3MotionMask =1<<10, 16884 Button4MotionMask =1<<11, 16885 Button5MotionMask =1<<12, 16886 ButtonMotionMask =1<<13, 16887 KeymapStateMask =1<<14, 16888 ExposureMask =1<<15, 16889 VisibilityChangeMask =1<<16, 16890 StructureNotifyMask =1<<17, 16891 ResizeRedirectMask =1<<18, 16892 SubstructureNotifyMask =1<<19, 16893 SubstructureRedirectMask=1<<20, 16894 FocusChangeMask =1<<21, 16895 PropertyChangeMask =1<<22, 16896 ColormapChangeMask =1<<23, 16897 OwnerGrabButtonMask =1<<24 16898 } 16899 16900 struct MwmHints { 16901 c_ulong flags; 16902 c_ulong functions; 16903 c_ulong decorations; 16904 c_long input_mode; 16905 c_ulong status; 16906 } 16907 16908 enum { 16909 MWM_HINTS_FUNCTIONS = (1L << 0), 16910 MWM_HINTS_DECORATIONS = (1L << 1), 16911 16912 MWM_FUNC_ALL = (1L << 0), 16913 MWM_FUNC_RESIZE = (1L << 1), 16914 MWM_FUNC_MOVE = (1L << 2), 16915 MWM_FUNC_MINIMIZE = (1L << 3), 16916 MWM_FUNC_MAXIMIZE = (1L << 4), 16917 MWM_FUNC_CLOSE = (1L << 5), 16918 16919 MWM_DECOR_ALL = (1L << 0), 16920 MWM_DECOR_BORDER = (1L << 1), 16921 MWM_DECOR_RESIZEH = (1L << 2), 16922 MWM_DECOR_TITLE = (1L << 3), 16923 MWM_DECOR_MENU = (1L << 4), 16924 MWM_DECOR_MINIMIZE = (1L << 5), 16925 MWM_DECOR_MAXIMIZE = (1L << 6), 16926 } 16927 16928 import core.stdc.config : c_long, c_ulong; 16929 16930 /* Size hints mask bits */ 16931 16932 enum USPosition = (1L << 0) /* user specified x, y */; 16933 enum USSize = (1L << 1) /* user specified width, height */; 16934 enum PPosition = (1L << 2) /* program specified position */; 16935 enum PSize = (1L << 3) /* program specified size */; 16936 enum PMinSize = (1L << 4) /* program specified minimum size */; 16937 enum PMaxSize = (1L << 5) /* program specified maximum size */; 16938 enum PResizeInc = (1L << 6) /* program specified resize increments */; 16939 enum PAspect = (1L << 7) /* program specified min and max aspect ratios */; 16940 enum PBaseSize = (1L << 8); 16941 enum PWinGravity = (1L << 9); 16942 enum PAllHints = (PPosition|PSize| PMinSize|PMaxSize| PResizeInc|PAspect); 16943 struct XSizeHints { 16944 arch_long flags; /* marks which fields in this structure are defined */ 16945 int x, y; /* Obsolete */ 16946 int width, height; /* Obsolete */ 16947 int min_width, min_height; 16948 int max_width, max_height; 16949 int width_inc, height_inc; 16950 struct Aspect { 16951 int x; /* numerator */ 16952 int y; /* denominator */ 16953 } 16954 16955 Aspect min_aspect; 16956 Aspect max_aspect; 16957 int base_width, base_height; 16958 int win_gravity; 16959 /* this structure may be extended in the future */ 16960 } 16961 16962 16963 16964 enum EventType:int 16965 { 16966 KeyPress =2, 16967 KeyRelease =3, 16968 ButtonPress =4, 16969 ButtonRelease =5, 16970 MotionNotify =6, 16971 EnterNotify =7, 16972 LeaveNotify =8, 16973 FocusIn =9, 16974 FocusOut =10, 16975 KeymapNotify =11, 16976 Expose =12, 16977 GraphicsExpose =13, 16978 NoExpose =14, 16979 VisibilityNotify =15, 16980 CreateNotify =16, 16981 DestroyNotify =17, 16982 UnmapNotify =18, 16983 MapNotify =19, 16984 MapRequest =20, 16985 ReparentNotify =21, 16986 ConfigureNotify =22, 16987 ConfigureRequest =23, 16988 GravityNotify =24, 16989 ResizeRequest =25, 16990 CirculateNotify =26, 16991 CirculateRequest =27, 16992 PropertyNotify =28, 16993 SelectionClear =29, 16994 SelectionRequest =30, 16995 SelectionNotify =31, 16996 ColormapNotify =32, 16997 ClientMessage =33, 16998 MappingNotify =34, 16999 LASTEvent =35 /* must be bigger than any event # */ 17000 } 17001 /* generated on EnterWindow and FocusIn when KeyMapState selected */ 17002 struct XKeymapEvent 17003 { 17004 int type; 17005 arch_ulong serial; /* # of last request processed by server */ 17006 Bool send_event; /* true if this came from a SendEvent request */ 17007 Display *display; /* Display the event was read from */ 17008 Window window; 17009 byte[32] key_vector; 17010 } 17011 17012 struct XExposeEvent 17013 { 17014 int type; 17015 arch_ulong serial; /* # of last request processed by server */ 17016 Bool send_event; /* true if this came from a SendEvent request */ 17017 Display *display; /* Display the event was read from */ 17018 Window window; 17019 int x, y; 17020 int width, height; 17021 int count; /* if non-zero, at least this many more */ 17022 } 17023 17024 struct XGraphicsExposeEvent{ 17025 int type; 17026 arch_ulong serial; /* # of last request processed by server */ 17027 Bool send_event; /* true if this came from a SendEvent request */ 17028 Display *display; /* Display the event was read from */ 17029 Drawable drawable; 17030 int x, y; 17031 int width, height; 17032 int count; /* if non-zero, at least this many more */ 17033 int major_code; /* core is CopyArea or CopyPlane */ 17034 int minor_code; /* not defined in the core */ 17035 } 17036 17037 struct XNoExposeEvent{ 17038 int type; 17039 arch_ulong serial; /* # of last request processed by server */ 17040 Bool send_event; /* true if this came from a SendEvent request */ 17041 Display *display; /* Display the event was read from */ 17042 Drawable drawable; 17043 int major_code; /* core is CopyArea or CopyPlane */ 17044 int minor_code; /* not defined in the core */ 17045 } 17046 17047 struct XVisibilityEvent{ 17048 int type; 17049 arch_ulong serial; /* # of last request processed by server */ 17050 Bool send_event; /* true if this came from a SendEvent request */ 17051 Display *display; /* Display the event was read from */ 17052 Window window; 17053 VisibilityNotify state; /* Visibility state */ 17054 } 17055 17056 struct XCreateWindowEvent{ 17057 int type; 17058 arch_ulong serial; /* # of last request processed by server */ 17059 Bool send_event; /* true if this came from a SendEvent request */ 17060 Display *display; /* Display the event was read from */ 17061 Window parent; /* parent of the window */ 17062 Window window; /* window id of window created */ 17063 int x, y; /* window location */ 17064 int width, height; /* size of window */ 17065 int border_width; /* border width */ 17066 Bool override_redirect; /* creation should be overridden */ 17067 } 17068 17069 struct XDestroyWindowEvent 17070 { 17071 int type; 17072 arch_ulong serial; /* # of last request processed by server */ 17073 Bool send_event; /* true if this came from a SendEvent request */ 17074 Display *display; /* Display the event was read from */ 17075 Window event; 17076 Window window; 17077 } 17078 17079 struct XUnmapEvent 17080 { 17081 int type; 17082 arch_ulong serial; /* # of last request processed by server */ 17083 Bool send_event; /* true if this came from a SendEvent request */ 17084 Display *display; /* Display the event was read from */ 17085 Window event; 17086 Window window; 17087 Bool from_configure; 17088 } 17089 17090 struct XMapEvent 17091 { 17092 int type; 17093 arch_ulong serial; /* # of last request processed by server */ 17094 Bool send_event; /* true if this came from a SendEvent request */ 17095 Display *display; /* Display the event was read from */ 17096 Window event; 17097 Window window; 17098 Bool override_redirect; /* Boolean, is override set... */ 17099 } 17100 17101 struct XMapRequestEvent 17102 { 17103 int type; 17104 arch_ulong serial; /* # of last request processed by server */ 17105 Bool send_event; /* true if this came from a SendEvent request */ 17106 Display *display; /* Display the event was read from */ 17107 Window parent; 17108 Window window; 17109 } 17110 17111 struct XReparentEvent 17112 { 17113 int type; 17114 arch_ulong serial; /* # of last request processed by server */ 17115 Bool send_event; /* true if this came from a SendEvent request */ 17116 Display *display; /* Display the event was read from */ 17117 Window event; 17118 Window window; 17119 Window parent; 17120 int x, y; 17121 Bool override_redirect; 17122 } 17123 17124 struct XConfigureEvent 17125 { 17126 int type; 17127 arch_ulong serial; /* # of last request processed by server */ 17128 Bool send_event; /* true if this came from a SendEvent request */ 17129 Display *display; /* Display the event was read from */ 17130 Window event; 17131 Window window; 17132 int x, y; 17133 int width, height; 17134 int border_width; 17135 Window above; 17136 Bool override_redirect; 17137 } 17138 17139 struct XGravityEvent 17140 { 17141 int type; 17142 arch_ulong serial; /* # of last request processed by server */ 17143 Bool send_event; /* true if this came from a SendEvent request */ 17144 Display *display; /* Display the event was read from */ 17145 Window event; 17146 Window window; 17147 int x, y; 17148 } 17149 17150 struct XResizeRequestEvent 17151 { 17152 int type; 17153 arch_ulong serial; /* # of last request processed by server */ 17154 Bool send_event; /* true if this came from a SendEvent request */ 17155 Display *display; /* Display the event was read from */ 17156 Window window; 17157 int width, height; 17158 } 17159 17160 struct XConfigureRequestEvent 17161 { 17162 int type; 17163 arch_ulong serial; /* # of last request processed by server */ 17164 Bool send_event; /* true if this came from a SendEvent request */ 17165 Display *display; /* Display the event was read from */ 17166 Window parent; 17167 Window window; 17168 int x, y; 17169 int width, height; 17170 int border_width; 17171 Window above; 17172 WindowStackingMethod detail; /* Above, Below, TopIf, BottomIf, Opposite */ 17173 arch_ulong value_mask; 17174 } 17175 17176 struct XCirculateEvent 17177 { 17178 int type; 17179 arch_ulong serial; /* # of last request processed by server */ 17180 Bool send_event; /* true if this came from a SendEvent request */ 17181 Display *display; /* Display the event was read from */ 17182 Window event; 17183 Window window; 17184 CirculationRequest place; /* PlaceOnTop, PlaceOnBottom */ 17185 } 17186 17187 struct XCirculateRequestEvent 17188 { 17189 int type; 17190 arch_ulong serial; /* # of last request processed by server */ 17191 Bool send_event; /* true if this came from a SendEvent request */ 17192 Display *display; /* Display the event was read from */ 17193 Window parent; 17194 Window window; 17195 CirculationRequest place; /* PlaceOnTop, PlaceOnBottom */ 17196 } 17197 17198 struct XPropertyEvent 17199 { 17200 int type; 17201 arch_ulong serial; /* # of last request processed by server */ 17202 Bool send_event; /* true if this came from a SendEvent request */ 17203 Display *display; /* Display the event was read from */ 17204 Window window; 17205 Atom atom; 17206 Time time; 17207 PropertyNotification state; /* NewValue, Deleted */ 17208 } 17209 17210 struct XSelectionClearEvent 17211 { 17212 int type; 17213 arch_ulong serial; /* # of last request processed by server */ 17214 Bool send_event; /* true if this came from a SendEvent request */ 17215 Display *display; /* Display the event was read from */ 17216 Window window; 17217 Atom selection; 17218 Time time; 17219 } 17220 17221 struct XSelectionRequestEvent 17222 { 17223 int type; 17224 arch_ulong serial; /* # of last request processed by server */ 17225 Bool send_event; /* true if this came from a SendEvent request */ 17226 Display *display; /* Display the event was read from */ 17227 Window owner; 17228 Window requestor; 17229 Atom selection; 17230 Atom target; 17231 Atom property; 17232 Time time; 17233 } 17234 17235 struct XSelectionEvent 17236 { 17237 int type; 17238 arch_ulong serial; /* # of last request processed by server */ 17239 Bool send_event; /* true if this came from a SendEvent request */ 17240 Display *display; /* Display the event was read from */ 17241 Window requestor; 17242 Atom selection; 17243 Atom target; 17244 Atom property; /* ATOM or None */ 17245 Time time; 17246 } 17247 version(X86_64) static assert(XSelectionClearEvent.sizeof == 56); 17248 17249 struct XColormapEvent 17250 { 17251 int type; 17252 arch_ulong serial; /* # of last request processed by server */ 17253 Bool send_event; /* true if this came from a SendEvent request */ 17254 Display *display; /* Display the event was read from */ 17255 Window window; 17256 Colormap colormap; /* COLORMAP or None */ 17257 Bool new_; /* C++ */ 17258 ColorMapNotification state; /* ColormapInstalled, ColormapUninstalled */ 17259 } 17260 version(X86_64) static assert(XColormapEvent.sizeof == 56); 17261 17262 struct XClientMessageEvent 17263 { 17264 int type; 17265 arch_ulong serial; /* # of last request processed by server */ 17266 Bool send_event; /* true if this came from a SendEvent request */ 17267 Display *display; /* Display the event was read from */ 17268 Window window; 17269 Atom message_type; 17270 int format; 17271 union Data{ 17272 byte[20] b; 17273 short[10] s; 17274 arch_ulong[5] l; 17275 } 17276 Data data; 17277 17278 } 17279 version(X86_64) static assert(XClientMessageEvent.sizeof == 96); 17280 17281 struct XMappingEvent 17282 { 17283 int type; 17284 arch_ulong serial; /* # of last request processed by server */ 17285 Bool send_event; /* true if this came from a SendEvent request */ 17286 Display *display; /* Display the event was read from */ 17287 Window window; /* unused */ 17288 MappingType request; /* one of MappingModifier, MappingKeyboard, 17289 MappingPointer */ 17290 int first_keycode; /* first keycode */ 17291 int count; /* defines range of change w. first_keycode*/ 17292 } 17293 17294 struct XErrorEvent 17295 { 17296 int type; 17297 Display *display; /* Display the event was read from */ 17298 XID resourceid; /* resource id */ 17299 arch_ulong serial; /* serial number of failed request */ 17300 ubyte error_code; /* error code of failed request */ 17301 ubyte request_code; /* Major op-code of failed request */ 17302 ubyte minor_code; /* Minor op-code of failed request */ 17303 } 17304 17305 struct XAnyEvent 17306 { 17307 int type; 17308 arch_ulong serial; /* # of last request processed by server */ 17309 Bool send_event; /* true if this came from a SendEvent request */ 17310 Display *display;/* Display the event was read from */ 17311 Window window; /* window on which event was requested in event mask */ 17312 } 17313 17314 union XEvent{ 17315 int type; /* must not be changed; first element */ 17316 XAnyEvent xany; 17317 XKeyEvent xkey; 17318 XButtonEvent xbutton; 17319 XMotionEvent xmotion; 17320 XCrossingEvent xcrossing; 17321 XFocusChangeEvent xfocus; 17322 XExposeEvent xexpose; 17323 XGraphicsExposeEvent xgraphicsexpose; 17324 XNoExposeEvent xnoexpose; 17325 XVisibilityEvent xvisibility; 17326 XCreateWindowEvent xcreatewindow; 17327 XDestroyWindowEvent xdestroywindow; 17328 XUnmapEvent xunmap; 17329 XMapEvent xmap; 17330 XMapRequestEvent xmaprequest; 17331 XReparentEvent xreparent; 17332 XConfigureEvent xconfigure; 17333 XGravityEvent xgravity; 17334 XResizeRequestEvent xresizerequest; 17335 XConfigureRequestEvent xconfigurerequest; 17336 XCirculateEvent xcirculate; 17337 XCirculateRequestEvent xcirculaterequest; 17338 XPropertyEvent xproperty; 17339 XSelectionClearEvent xselectionclear; 17340 XSelectionRequestEvent xselectionrequest; 17341 XSelectionEvent xselection; 17342 XColormapEvent xcolormap; 17343 XClientMessageEvent xclient; 17344 XMappingEvent xmapping; 17345 XErrorEvent xerror; 17346 XKeymapEvent xkeymap; 17347 arch_ulong[24] pad; 17348 } 17349 17350 17351 struct Display { 17352 XExtData *ext_data; /* hook for extension to hang data */ 17353 _XPrivate *private1; 17354 int fd; /* Network socket. */ 17355 int private2; 17356 int proto_major_version;/* major version of server's X protocol */ 17357 int proto_minor_version;/* minor version of servers X protocol */ 17358 char *vendor; /* vendor of the server hardware */ 17359 XID private3; 17360 XID private4; 17361 XID private5; 17362 int private6; 17363 XID function(Display*)resource_alloc;/* allocator function */ 17364 ByteOrder byte_order; /* screen byte order, LSBFirst, MSBFirst */ 17365 int bitmap_unit; /* padding and data requirements */ 17366 int bitmap_pad; /* padding requirements on bitmaps */ 17367 ByteOrder bitmap_bit_order; /* LeastSignificant or MostSignificant */ 17368 int nformats; /* number of pixmap formats in list */ 17369 ScreenFormat *pixmap_format; /* pixmap format list */ 17370 int private8; 17371 int release; /* release of the server */ 17372 _XPrivate *private9; 17373 _XPrivate *private10; 17374 int qlen; /* Length of input event queue */ 17375 arch_ulong last_request_read; /* seq number of last event read */ 17376 arch_ulong request; /* sequence number of last request. */ 17377 XPointer private11; 17378 XPointer private12; 17379 XPointer private13; 17380 XPointer private14; 17381 uint max_request_size; /* maximum number 32 bit words in request*/ 17382 _XrmHashBucketRec *db; 17383 int function (Display*)private15; 17384 char *display_name; /* "host:display" string used on this connect*/ 17385 int default_screen; /* default screen for operations */ 17386 int nscreens; /* number of screens on this server*/ 17387 Screen *screens; /* pointer to list of screens */ 17388 arch_ulong motion_buffer; /* size of motion buffer */ 17389 arch_ulong private16; 17390 int min_keycode; /* minimum defined keycode */ 17391 int max_keycode; /* maximum defined keycode */ 17392 XPointer private17; 17393 XPointer private18; 17394 int private19; 17395 byte *xdefaults; /* contents of defaults from server */ 17396 /* there is more to this structure, but it is private to Xlib */ 17397 } 17398 17399 // I got these numbers from a C program as a sanity test 17400 version(X86_64) { 17401 static assert(Display.sizeof == 296); 17402 static assert(XPointer.sizeof == 8); 17403 static assert(XErrorEvent.sizeof == 40); 17404 static assert(XAnyEvent.sizeof == 40); 17405 static assert(XMappingEvent.sizeof == 56); 17406 static assert(XEvent.sizeof == 192); 17407 } else version (AArch64) { 17408 // omit check for aarch64 17409 } else { 17410 static assert(Display.sizeof == 176); 17411 static assert(XPointer.sizeof == 4); 17412 static assert(XEvent.sizeof == 96); 17413 } 17414 17415 struct Depth 17416 { 17417 int depth; /* this depth (Z) of the depth */ 17418 int nvisuals; /* number of Visual types at this depth */ 17419 Visual *visuals; /* list of visuals possible at this depth */ 17420 } 17421 17422 alias void* GC; 17423 alias c_ulong VisualID; 17424 alias XID Colormap; 17425 alias XID Cursor; 17426 alias XID KeySym; 17427 alias uint KeyCode; 17428 enum None = 0; 17429 } 17430 17431 version(without_opengl) {} 17432 else { 17433 extern(C) nothrow @nogc { 17434 17435 17436 static if(!SdpyIsUsingIVGLBinds) { 17437 enum GLX_USE_GL= 1; /* support GLX rendering */ 17438 enum GLX_BUFFER_SIZE= 2; /* depth of the color buffer */ 17439 enum GLX_LEVEL= 3; /* level in plane stacking */ 17440 enum GLX_RGBA= 4; /* true if RGBA mode */ 17441 enum GLX_DOUBLEBUFFER= 5; /* double buffering supported */ 17442 enum GLX_STEREO= 6; /* stereo buffering supported */ 17443 enum GLX_AUX_BUFFERS= 7; /* number of aux buffers */ 17444 enum GLX_RED_SIZE= 8; /* number of red component bits */ 17445 enum GLX_GREEN_SIZE= 9; /* number of green component bits */ 17446 enum GLX_BLUE_SIZE= 10; /* number of blue component bits */ 17447 enum GLX_ALPHA_SIZE= 11; /* number of alpha component bits */ 17448 enum GLX_DEPTH_SIZE= 12; /* number of depth bits */ 17449 enum GLX_STENCIL_SIZE= 13; /* number of stencil bits */ 17450 enum GLX_ACCUM_RED_SIZE= 14; /* number of red accum bits */ 17451 enum GLX_ACCUM_GREEN_SIZE= 15; /* number of green accum bits */ 17452 enum GLX_ACCUM_BLUE_SIZE= 16; /* number of blue accum bits */ 17453 enum GLX_ACCUM_ALPHA_SIZE= 17; /* number of alpha accum bits */ 17454 17455 17456 //XVisualInfo* glXChooseVisual(Display *dpy, int screen, in int *attrib_list); 17457 17458 17459 17460 enum GL_TRUE = 1; 17461 enum GL_FALSE = 0; 17462 } 17463 17464 alias XID GLXContextID; 17465 alias XID GLXPixmap; 17466 alias XID GLXDrawable; 17467 alias XID GLXPbuffer; 17468 alias XID GLXWindow; 17469 alias XID GLXFBConfigID; 17470 alias void* GLXContext; 17471 17472 } 17473 } 17474 17475 enum AllocNone = 0; 17476 17477 extern(C) { 17478 /* WARNING, this type not in Xlib spec */ 17479 extern(C) alias XIOErrorHandler = int function (Display* display); 17480 } 17481 17482 extern(C) nothrow 17483 alias XErrorHandler = int function(Display*, XErrorEvent*); 17484 17485 extern(C) nothrow @nogc { 17486 struct Screen{ 17487 XExtData *ext_data; /* hook for extension to hang data */ 17488 Display *display; /* back pointer to display structure */ 17489 Window root; /* Root window id. */ 17490 int width, height; /* width and height of screen */ 17491 int mwidth, mheight; /* width and height of in millimeters */ 17492 int ndepths; /* number of depths possible */ 17493 Depth *depths; /* list of allowable depths on the screen */ 17494 int root_depth; /* bits per pixel */ 17495 Visual *root_visual; /* root visual */ 17496 GC default_gc; /* GC for the root root visual */ 17497 Colormap cmap; /* default color map */ 17498 uint white_pixel; 17499 uint black_pixel; /* White and Black pixel values */ 17500 int max_maps, min_maps; /* max and min color maps */ 17501 int backing_store; /* Never, WhenMapped, Always */ 17502 bool save_unders; 17503 int root_input_mask; /* initial root input mask */ 17504 } 17505 17506 struct Visual 17507 { 17508 XExtData *ext_data; /* hook for extension to hang data */ 17509 VisualID visualid; /* visual id of this visual */ 17510 int class_; /* class of screen (monochrome, etc.) */ 17511 c_ulong red_mask, green_mask, blue_mask; /* mask values */ 17512 int bits_per_rgb; /* log base 2 of distinct color values */ 17513 int map_entries; /* color map entries */ 17514 } 17515 17516 alias Display* _XPrivDisplay; 17517 17518 extern(D) Screen* ScreenOfDisplay(Display* dpy, int scr) { 17519 assert(dpy !is null); 17520 return &dpy.screens[scr]; 17521 } 17522 17523 extern(D) Window RootWindow(Display *dpy,int scr) { 17524 return ScreenOfDisplay(dpy,scr).root; 17525 } 17526 17527 struct XWMHints { 17528 arch_long flags; 17529 Bool input; 17530 int initial_state; 17531 Pixmap icon_pixmap; 17532 Window icon_window; 17533 int icon_x, icon_y; 17534 Pixmap icon_mask; 17535 XID window_group; 17536 } 17537 17538 struct XClassHint { 17539 char* res_name; 17540 char* res_class; 17541 } 17542 17543 extern(D) int DefaultScreen(Display *dpy) { 17544 return dpy.default_screen; 17545 } 17546 17547 extern(D) int DefaultDepth(Display* dpy, int scr) { return ScreenOfDisplay(dpy, scr).root_depth; } 17548 extern(D) int DisplayWidth(Display* dpy, int scr) { return ScreenOfDisplay(dpy, scr).width; } 17549 extern(D) int DisplayHeight(Display* dpy, int scr) { return ScreenOfDisplay(dpy, scr).height; } 17550 extern(D) int DisplayWidthMM(Display* dpy, int scr) { return ScreenOfDisplay(dpy, scr).mwidth; } 17551 extern(D) int DisplayHeightMM(Display* dpy, int scr) { return ScreenOfDisplay(dpy, scr).mheight; } 17552 extern(D) auto DefaultColormap(Display* dpy, int scr) { return ScreenOfDisplay(dpy, scr).cmap; } 17553 17554 extern(D) int ConnectionNumber(Display* dpy) { return dpy.fd; } 17555 17556 enum int AnyPropertyType = 0; 17557 enum int Success = 0; 17558 17559 enum int RevertToNone = None; 17560 enum int PointerRoot = 1; 17561 enum Time CurrentTime = 0; 17562 enum int RevertToPointerRoot = PointerRoot; 17563 enum int RevertToParent = 2; 17564 17565 extern(D) int DefaultDepthOfDisplay(Display* dpy) { 17566 return ScreenOfDisplay(dpy, DefaultScreen(dpy)).root_depth; 17567 } 17568 17569 extern(D) Visual* DefaultVisual(Display *dpy,int scr) { 17570 return ScreenOfDisplay(dpy,scr).root_visual; 17571 } 17572 17573 extern(D) GC DefaultGC(Display *dpy,int scr) { 17574 return ScreenOfDisplay(dpy,scr).default_gc; 17575 } 17576 17577 extern(D) uint BlackPixel(Display *dpy,int scr) { 17578 return ScreenOfDisplay(dpy,scr).black_pixel; 17579 } 17580 17581 extern(D) uint WhitePixel(Display *dpy,int scr) { 17582 return ScreenOfDisplay(dpy,scr).white_pixel; 17583 } 17584 17585 alias void* XFontSet; // i think 17586 struct XmbTextItem { 17587 char* chars; 17588 int nchars; 17589 int delta; 17590 XFontSet font_set; 17591 } 17592 17593 struct XTextItem { 17594 char* chars; 17595 int nchars; 17596 int delta; 17597 Font font; 17598 } 17599 17600 enum { 17601 GXclear = 0x0, /* 0 */ 17602 GXand = 0x1, /* src AND dst */ 17603 GXandReverse = 0x2, /* src AND NOT dst */ 17604 GXcopy = 0x3, /* src */ 17605 GXandInverted = 0x4, /* NOT src AND dst */ 17606 GXnoop = 0x5, /* dst */ 17607 GXxor = 0x6, /* src XOR dst */ 17608 GXor = 0x7, /* src OR dst */ 17609 GXnor = 0x8, /* NOT src AND NOT dst */ 17610 GXequiv = 0x9, /* NOT src XOR dst */ 17611 GXinvert = 0xa, /* NOT dst */ 17612 GXorReverse = 0xb, /* src OR NOT dst */ 17613 GXcopyInverted = 0xc, /* NOT src */ 17614 GXorInverted = 0xd, /* NOT src OR dst */ 17615 GXnand = 0xe, /* NOT src OR NOT dst */ 17616 GXset = 0xf, /* 1 */ 17617 } 17618 enum QueueMode : int { 17619 QueuedAlready, 17620 QueuedAfterReading, 17621 QueuedAfterFlush 17622 } 17623 17624 enum GrabMode { GrabModeSync = 0, GrabModeAsync = 1 } 17625 17626 struct XPoint { 17627 short x; 17628 short y; 17629 } 17630 17631 enum CoordMode:int { 17632 CoordModeOrigin = 0, 17633 CoordModePrevious = 1 17634 } 17635 17636 enum PolygonShape:int { 17637 Complex = 0, 17638 Nonconvex = 1, 17639 Convex = 2 17640 } 17641 17642 struct XTextProperty { 17643 const(char)* value; /* same as Property routines */ 17644 Atom encoding; /* prop type */ 17645 int format; /* prop data format: 8, 16, or 32 */ 17646 arch_ulong nitems; /* number of data items in value */ 17647 } 17648 17649 version( X86_64 ) { 17650 static assert(XTextProperty.sizeof == 32); 17651 } 17652 17653 17654 struct XGCValues { 17655 int function_; /* logical operation */ 17656 arch_ulong plane_mask;/* plane mask */ 17657 arch_ulong foreground;/* foreground pixel */ 17658 arch_ulong background;/* background pixel */ 17659 int line_width; /* line width */ 17660 int line_style; /* LineSolid, LineOnOffDash, LineDoubleDash */ 17661 int cap_style; /* CapNotLast, CapButt, 17662 CapRound, CapProjecting */ 17663 int join_style; /* JoinMiter, JoinRound, JoinBevel */ 17664 int fill_style; /* FillSolid, FillTiled, 17665 FillStippled, FillOpaeueStippled */ 17666 int fill_rule; /* EvenOddRule, WindingRule */ 17667 int arc_mode; /* ArcChord, ArcPieSlice */ 17668 Pixmap tile; /* tile pixmap for tiling operations */ 17669 Pixmap stipple; /* stipple 1 plane pixmap for stipping */ 17670 int ts_x_origin; /* offset for tile or stipple operations */ 17671 int ts_y_origin; 17672 Font font; /* default text font for text operations */ 17673 int subwindow_mode; /* ClipByChildren, IncludeInferiors */ 17674 Bool graphics_exposures;/* boolean, should exposures be generated */ 17675 int clip_x_origin; /* origin for clipping */ 17676 int clip_y_origin; 17677 Pixmap clip_mask; /* bitmap clipping; other calls for rects */ 17678 int dash_offset; /* patterned/dashed line information */ 17679 char dashes; 17680 } 17681 17682 struct XColor { 17683 arch_ulong pixel; 17684 ushort red, green, blue; 17685 byte flags; 17686 byte pad; 17687 } 17688 17689 struct XRectangle { 17690 short x; 17691 short y; 17692 ushort width; 17693 ushort height; 17694 } 17695 17696 enum ClipByChildren = 0; 17697 enum IncludeInferiors = 1; 17698 17699 enum Atom XA_PRIMARY = 1; 17700 enum Atom XA_SECONDARY = 2; 17701 enum Atom XA_STRING = 31; 17702 enum Atom XA_CARDINAL = 6; 17703 enum Atom XA_WM_NAME = 39; 17704 enum Atom XA_ATOM = 4; 17705 enum Atom XA_WINDOW = 33; 17706 enum Atom XA_WM_HINTS = 35; 17707 enum int PropModeAppend = 2; 17708 enum int PropModeReplace = 0; 17709 enum int PropModePrepend = 1; 17710 17711 enum int CopyFromParent = 0; 17712 enum int InputOutput = 1; 17713 17714 // XWMHints 17715 enum InputHint = 1 << 0; 17716 enum StateHint = 1 << 1; 17717 enum IconPixmapHint = (1L << 2); 17718 enum IconWindowHint = (1L << 3); 17719 enum IconPositionHint = (1L << 4); 17720 enum IconMaskHint = (1L << 5); 17721 enum WindowGroupHint = (1L << 6); 17722 enum AllHints = (InputHint|StateHint|IconPixmapHint|IconWindowHint|IconPositionHint|IconMaskHint|WindowGroupHint); 17723 enum XUrgencyHint = (1L << 8); 17724 17725 // GC Components 17726 enum GCFunction = (1L<<0); 17727 enum GCPlaneMask = (1L<<1); 17728 enum GCForeground = (1L<<2); 17729 enum GCBackground = (1L<<3); 17730 enum GCLineWidth = (1L<<4); 17731 enum GCLineStyle = (1L<<5); 17732 enum GCCapStyle = (1L<<6); 17733 enum GCJoinStyle = (1L<<7); 17734 enum GCFillStyle = (1L<<8); 17735 enum GCFillRule = (1L<<9); 17736 enum GCTile = (1L<<10); 17737 enum GCStipple = (1L<<11); 17738 enum GCTileStipXOrigin = (1L<<12); 17739 enum GCTileStipYOrigin = (1L<<13); 17740 enum GCFont = (1L<<14); 17741 enum GCSubwindowMode = (1L<<15); 17742 enum GCGraphicsExposures= (1L<<16); 17743 enum GCClipXOrigin = (1L<<17); 17744 enum GCClipYOrigin = (1L<<18); 17745 enum GCClipMask = (1L<<19); 17746 enum GCDashOffset = (1L<<20); 17747 enum GCDashList = (1L<<21); 17748 enum GCArcMode = (1L<<22); 17749 enum GCLastBit = 22; 17750 17751 17752 enum int WithdrawnState = 0; 17753 enum int NormalState = 1; 17754 enum int IconicState = 3; 17755 17756 } 17757 } else version (OSXCocoa) { 17758 17759 /+ 17760 DON'T FORGET TO MARK THE CLASSES `extern`!! can cause "unrecognized selector sent to class" errors if you do. 17761 +/ 17762 17763 private __gshared AppDelegate globalAppDelegate; 17764 17765 extern(Objective-C) 17766 class AppDelegate : NSObject, NSApplicationDelegate { 17767 override static AppDelegate alloc() @selector("alloc"); 17768 17769 17770 void sdpyCustomEventWakeup(NSid arg) @selector("sdpyCustomEventWakeup:") { 17771 SimpleWindow.processAllCustomEvents(); 17772 } 17773 17774 override void applicationWillFinishLaunching(NSNotification notification) @selector("applicationWillFinishLaunching:") { 17775 immutable style = NSWindowStyleMask.resizable | 17776 NSWindowStyleMask.closable | 17777 NSWindowStyleMask.miniaturizable | 17778 NSWindowStyleMask.titled; 17779 17780 NSMenu mainMenu = NSMenu.alloc.init(MacString("Main").borrow); 17781 17782 { 17783 auto item = mainMenu.addItem(MacString("Test").borrow, null, MacString("").borrow); 17784 auto menu = NSMenu.alloc.init(MacString("Test2").borrow); 17785 mainMenu.setSubmenu(menu, item); 17786 17787 auto newItem = menu.addItem(MacString("Quit").borrow, sel_registerName("terminate:"), MacString("q").borrow); 17788 newItem.target = NSApp; 17789 auto newItem2 = menu.addItem(MacString("Disabled").borrow, sel_registerName("doesnotexist:"), MacString("x").borrow); 17790 newItem2.target = NSApp; 17791 } 17792 17793 { 17794 auto item = mainMenu.addItem(MacString("Test3").borrow, null, MacString("").borrow); 17795 auto menu = NSMenu.alloc.init(MacString("Test4").borrow); // this is the title actually used 17796 mainMenu.setSubmenu(menu, item); 17797 17798 auto newItem = menu.addItem(MacString("Quit2").borrow, sel_registerName("stop:"), MacString("s").borrow); 17799 menu.addItem(MacString("Pulse").borrow, sel_registerName("simpledisplay_pulse:"), MacString("p").borrow); 17800 } 17801 17802 17803 NSApp.menu = mainMenu; 17804 17805 17806 // auto controller = ViewController.alloc.init; 17807 17808 // auto timer = NSTimer.schedule(1.0, cast(NSid) view, sel_registerName("simpledisplay_pulse:"), null, true); 17809 17810 /+ 17811 this.window = window; 17812 this.controller = controller; 17813 +/ 17814 } 17815 17816 override void applicationDidFinishLaunching(NSNotification notification) @selector("applicationDidFinishLaunching:") { 17817 NSApplication.shared_.activateIgnoringOtherApps(true); 17818 } 17819 override bool applicationShouldTerminateAfterLastWindowClosed(NSNotification notification) @selector("applicationShouldTerminateAfterLastWindowClosed:") { 17820 return true; 17821 } 17822 } 17823 17824 extern(Objective-C) 17825 class SDWindowDelegate : NSObject, NSWindowDelegate { 17826 override static SDWindowDelegate alloc() @selector("alloc"); 17827 override SDWindowDelegate init() @selector("init"); 17828 17829 SimpleWindow simpleWindow; 17830 17831 override void windowWillClose(NSNotification notification) @selector("windowWillClose:") { 17832 auto window = cast(void*) notification.object; 17833 17834 // FIXME: do i need to release it? 17835 SimpleWindow.nativeMapping.remove(window); 17836 } 17837 17838 override NSSize windowWillResize(NSWindow sender, NSSize frameSize) @selector("windowWillResize:toSize:") { 17839 if(simpleWindow.windowResized) { 17840 // FIXME: automaticallyScaleIfPossible behaviors 17841 17842 simpleWindow._width = cast(int) frameSize.width; 17843 simpleWindow._height = cast(int) frameSize.height; 17844 17845 simpleWindow.view.setFrameSize(frameSize); 17846 17847 /+ 17848 auto size = simpleWindow.view.frame.size; 17849 writeln(cast(int) size.width, "x", cast(int) size.height); 17850 +/ 17851 17852 simpleWindow.createNewDrawingContext(simpleWindow._width, simpleWindow._height); 17853 17854 simpleWindow.windowResized(simpleWindow._width, simpleWindow._height); 17855 17856 // simpleWindow.view.setNeedsDisplay(true); 17857 } 17858 17859 return frameSize; 17860 } 17861 17862 /+ 17863 override void windowDidResize(NSNotification notification) @selector("windowDidResize:") { 17864 if(simpleWindow.windowResized) { 17865 auto window = simpleWindow.window; 17866 auto rect = window.contentRectForFrameRect(window.frame); 17867 import std.stdio; writeln(window.frame.size); 17868 simpleWindow.windowResized(cast(int) rect.size.width, cast(int) rect.size.height); 17869 } 17870 } 17871 +/ 17872 } 17873 17874 extern(Objective-C) 17875 class SDGraphicsView : NSView { 17876 SimpleWindow simpleWindow; 17877 17878 override static SDGraphicsView alloc() @selector("alloc"); 17879 override SDGraphicsView init() @selector("init") { 17880 super.init(); 17881 return this; 17882 } 17883 17884 override void drawRect(NSRect rect) @selector("drawRect:") { 17885 auto curCtx = NSGraphicsContext.currentContext.graphicsPort; 17886 auto cgImage = CGBitmapContextCreateImage(simpleWindow.drawingContext); 17887 auto size = CGSize(CGBitmapContextGetWidth(simpleWindow.drawingContext), CGBitmapContextGetHeight(simpleWindow.drawingContext)); 17888 CGContextDrawImage(curCtx, CGRect(CGPoint(0, 0), size), cgImage); 17889 CGImageRelease(cgImage); 17890 } 17891 17892 private void mouseHelper(NSEvent event, MouseEventType type, MouseButton button) { 17893 MouseEvent me; 17894 me.type = type; 17895 17896 auto pos = event.locationInWindow; 17897 17898 me.x = cast(int) pos.x; 17899 me.y = cast(int) (simpleWindow.height - pos.y); 17900 17901 me.dx = 0; // FIXME 17902 me.dy = 0; // FIXME 17903 17904 me.button = button; 17905 me.modifierState = cast(uint) event.modifierFlags; 17906 me.window = simpleWindow; 17907 17908 me.doubleClick = false; 17909 17910 if(simpleWindow && simpleWindow.handleMouseEvent) 17911 simpleWindow.handleMouseEvent(me); 17912 } 17913 17914 override void mouseDown(NSEvent event) @selector("mouseDown:") { 17915 // writeln(event.pressedMouseButtons); 17916 17917 mouseHelper(event, MouseEventType.buttonPressed, MouseButton.left); 17918 } 17919 override void mouseDragged(NSEvent event) @selector("mouseDragged:") { 17920 mouseHelper(event, MouseEventType.motion, MouseButton.left); 17921 } 17922 override void mouseUp(NSEvent event) @selector("mouseUp:") { 17923 mouseHelper(event, MouseEventType.buttonReleased, MouseButton.left); 17924 } 17925 override void mouseMoved(NSEvent event) @selector("mouseMoved:") { 17926 mouseHelper(event, MouseEventType.motion, MouseButton.left); // button wrong prolly 17927 } 17928 /+ 17929 // FIXME 17930 override void mouseEntered(NSEvent event) @selector("mouseEntered:") { 17931 mouseHelper(event, MouseEventType.buttonPressed, MouseButton.left); 17932 } 17933 override void mouseExited(NSEvent event) @selector("mouseExited:") { 17934 mouseHelper(event, MouseEventType.buttonPressed, MouseButton.left); 17935 } 17936 +/ 17937 17938 override void rightMouseDown(NSEvent event) @selector("rightMouseDown:") { 17939 mouseHelper(event, MouseEventType.buttonPressed, MouseButton.right); 17940 } 17941 override void rightMouseDragged(NSEvent event) @selector("rightMouseDragged:") { 17942 mouseHelper(event, MouseEventType.motion, MouseButton.right); 17943 } 17944 override void rightMouseUp(NSEvent event) @selector("rightMouseUp:") { 17945 mouseHelper(event, MouseEventType.buttonReleased, MouseButton.right); 17946 } 17947 17948 override void otherMouseDown(NSEvent event) @selector("otherMouseDown:") { 17949 mouseHelper(event, MouseEventType.buttonPressed, MouseButton.middle); 17950 } 17951 override void otherMouseDragged(NSEvent event) @selector("otherMouseDragged:") { 17952 mouseHelper(event, MouseEventType.motion, MouseButton.middle); 17953 } 17954 override void otherMouseUp(NSEvent event) @selector("otherMouseUp:") { 17955 mouseHelper(event, MouseEventType.buttonReleased, MouseButton.middle); 17956 } 17957 17958 override void scrollWheel(NSEvent event) @selector("scrollWheel:") { 17959 import std.stdio; 17960 writeln(event.deltaY); 17961 } 17962 17963 override void keyDown(NSEvent event) @selector("keyDown:") { 17964 // the event may have multiple characters, and we send them all at once. 17965 if (simpleWindow.handleCharEvent) { 17966 auto chars = DeifiedNSString(event.characters); 17967 foreach (dchar dc; chars.str) 17968 simpleWindow.handleCharEvent(dc); 17969 } 17970 17971 keyHelper(event, true); 17972 } 17973 17974 override void keyUp(NSEvent event) @selector("keyUp:") { 17975 keyHelper(event, false); 17976 } 17977 17978 private void keyHelper(NSEvent event, bool pressed) { 17979 if(simpleWindow.handleKeyEvent) { 17980 KeyEvent ev; 17981 ev.key = cast(Key) event.keyCode;// (event.specialKey ? event.specialKey : event.keyCode); 17982 ev.pressed = pressed; 17983 ev.hardwareCode = cast(ubyte) event.keyCode; 17984 ev.modifierState = cast(uint) event.modifierFlags; 17985 ev.window = simpleWindow; 17986 17987 simpleWindow.handleKeyEvent(ev); 17988 } 17989 } 17990 17991 override bool isFlipped() @selector("isFlipped") { 17992 return true; 17993 } 17994 override bool acceptsFirstResponder() @selector("acceptsFirstResponder") { 17995 return true; 17996 } 17997 17998 void simpledisplay_pulse(NSTimer timer) @selector("simpledisplay_pulse:") { 17999 if(simpleWindow && simpleWindow.handlePulse) 18000 simpleWindow.handlePulse(); 18001 /+ 18002 setNeedsDisplay = true; 18003 +/ 18004 } 18005 } 18006 18007 private: 18008 alias const(void)* CFStringRef; 18009 alias const(void)* CFAllocatorRef; 18010 alias const(void)* CFTypeRef; 18011 alias const(void)* CGColorSpaceRef; 18012 alias const(void)* CGImageRef; 18013 alias ulong CGBitmapInfo; 18014 alias NSGraphicsContext CGContextRef; 18015 18016 alias NSPoint CGPoint; 18017 alias NSSize CGSize; 18018 alias NSRect CGRect; 18019 18020 struct CGAffineTransform { 18021 double a, b, c, d, tx, ty; 18022 } 18023 18024 enum NSApplicationActivationPolicyRegular = 0; 18025 enum NSBackingStoreBuffered = 2; 18026 enum kCFStringEncodingUTF8 = 0x08000100; 18027 18028 enum : size_t { 18029 NSBorderlessWindowMask = 0, 18030 NSTitledWindowMask = 1 << 0, 18031 NSClosableWindowMask = 1 << 1, 18032 NSMiniaturizableWindowMask = 1 << 2, 18033 NSResizableWindowMask = 1 << 3, 18034 NSTexturedBackgroundWindowMask = 1 << 8 18035 } 18036 18037 enum : ulong { 18038 kCGImageAlphaNone, 18039 kCGImageAlphaPremultipliedLast, 18040 kCGImageAlphaPremultipliedFirst, 18041 kCGImageAlphaLast, 18042 kCGImageAlphaFirst, 18043 kCGImageAlphaNoneSkipLast, 18044 kCGImageAlphaNoneSkipFirst 18045 } 18046 enum : ulong { 18047 kCGBitmapAlphaInfoMask = 0x1F, 18048 kCGBitmapFloatComponents = (1 << 8), 18049 kCGBitmapByteOrderMask = 0x7000, 18050 kCGBitmapByteOrderDefault = (0 << 12), 18051 kCGBitmapByteOrder16Little = (1 << 12), 18052 kCGBitmapByteOrder32Little = (2 << 12), 18053 kCGBitmapByteOrder16Big = (3 << 12), 18054 kCGBitmapByteOrder32Big = (4 << 12) 18055 } 18056 enum CGPathDrawingMode { 18057 kCGPathFill, 18058 kCGPathEOFill, 18059 kCGPathStroke, 18060 kCGPathFillStroke, 18061 kCGPathEOFillStroke 18062 } 18063 enum objc_AssociationPolicy : size_t { 18064 OBJC_ASSOCIATION_ASSIGN = 0, 18065 OBJC_ASSOCIATION_RETAIN_NONATOMIC = 1, 18066 OBJC_ASSOCIATION_COPY_NONATOMIC = 3, 18067 OBJC_ASSOCIATION_RETAIN = 0x301, //01401, 18068 OBJC_ASSOCIATION_COPY = 0x303 //01403 18069 } 18070 18071 extern(C) { 18072 CGContextRef CGBitmapContextCreate(void* data, size_t width, size_t height, size_t bitsPerComponent, size_t bytesPerRow, CGColorSpaceRef colorspace, CGBitmapInfo bitmapInfo); 18073 void CGContextRelease(CGContextRef c); 18074 ubyte* CGBitmapContextGetData(CGContextRef c); 18075 CGImageRef CGBitmapContextCreateImage(CGContextRef c); 18076 size_t CGBitmapContextGetWidth(CGContextRef c); 18077 size_t CGBitmapContextGetHeight(CGContextRef c); 18078 18079 CGColorSpaceRef CGColorSpaceCreateDeviceRGB(); 18080 void CGColorSpaceRelease(CGColorSpaceRef cs); 18081 18082 void CGContextSetRGBStrokeColor(CGContextRef c, double red, double green, double blue, double alpha); 18083 void CGContextSetRGBFillColor(CGContextRef c, double red, double green, double blue, double alpha); 18084 void CGContextDrawImage(CGContextRef c, CGRect rect, CGImageRef image); 18085 void CGContextShowTextAtPoint(CGContextRef c, double x, double y, const(char)* str, size_t length); 18086 void CGContextStrokeLineSegments(CGContextRef c, const(CGPoint)* points, size_t count); 18087 void CGContextSetLineDash(CGContextRef c, CGFloat phase, const CGFloat *lengths, size_t count); 18088 18089 void CGContextBeginPath(CGContextRef c); 18090 void CGContextDrawPath(CGContextRef c, CGPathDrawingMode mode); 18091 void CGContextAddEllipseInRect(CGContextRef c, CGRect rect); 18092 void CGContextAddArc(CGContextRef c, double x, double y, double radius, double startAngle, double endAngle, long clockwise); 18093 void CGContextAddRect(CGContextRef c, CGRect rect); 18094 void CGContextAddLines(CGContextRef c, const(CGPoint)* points, size_t count); 18095 void CGContextSaveGState(CGContextRef c); 18096 void CGContextRestoreGState(CGContextRef c); 18097 void CGContextSelectFont(CGContextRef c, const(char)* name, double size, ulong textEncoding); 18098 CGAffineTransform CGContextGetTextMatrix(CGContextRef c); 18099 void CGContextSetTextMatrix(CGContextRef c, CGAffineTransform t); 18100 18101 void CGImageRelease(CGImageRef image); 18102 } 18103 } else static assert(0, "Unsupported operating system"); 18104 18105 18106 version(OSXCocoa) { 18107 // I don't know anything about the Mac, but a couple years ago, KennyTM on the newsgroup wrote this for me 18108 // 18109 // http://forum.dlang.org/thread/innr0v$1deh$1@digitalmars.com?page=4#post-int88l:24uaf:241:40digitalmars.com 18110 // https://github.com/kennytm/simpledisplay.d/blob/osx/simpledisplay.d 18111 // 18112 // and it is about time I merged it in here. It is available with -version=OSXCocoa until someone tests it for me! 18113 // Probably won't even fully compile right now 18114 18115 private enum double PI = 3.14159265358979323; 18116 18117 alias NSWindow NativeWindowHandle; 18118 alias void delegate(NSid) NativeEventHandler; 18119 18120 enum KEY_ESCAPE = 27; 18121 18122 mixin template NativeImageImplementation() { 18123 CGContextRef context; 18124 ubyte* rawData; 18125 18126 final: 18127 18128 void convertToRgbaBytes(ubyte[] where) { 18129 assert(where.length == this.width * this.height * 4); 18130 18131 // if rawData had a length.... 18132 //assert(rawData.length == where.length); 18133 for(long idx = 0; idx < where.length; idx += 4) { 18134 auto alpha = rawData[idx + 3]; 18135 if(alpha == 255) { 18136 where[idx + 0] = rawData[idx + 0]; // r 18137 where[idx + 1] = rawData[idx + 1]; // g 18138 where[idx + 2] = rawData[idx + 2]; // b 18139 where[idx + 3] = rawData[idx + 3]; // a 18140 } else { 18141 where[idx + 0] = cast(ubyte)(rawData[idx + 0] * 255 / alpha); // r 18142 where[idx + 1] = cast(ubyte)(rawData[idx + 1] * 255 / alpha); // g 18143 where[idx + 2] = cast(ubyte)(rawData[idx + 2] * 255 / alpha); // b 18144 where[idx + 3] = rawData[idx + 3]; // a 18145 18146 } 18147 } 18148 } 18149 18150 void setFromRgbaBytes(in ubyte[] where) { 18151 // FIXME: this is probably wrong 18152 assert(where.length == this.width * this.height * 4); 18153 18154 // if rawData had a length.... 18155 //assert(rawData.length == where.length); 18156 for(long idx = 0; idx < where.length; idx += 4) { 18157 auto alpha = where[idx + 3]; 18158 if(alpha == 255) { 18159 rawData[idx + 0] = where[idx + 0]; // r 18160 rawData[idx + 1] = where[idx + 1]; // g 18161 rawData[idx + 2] = where[idx + 2]; // b 18162 rawData[idx + 3] = where[idx + 3]; // a 18163 } else if(alpha == 0) { 18164 rawData[idx + 0] = 0; 18165 rawData[idx + 1] = 0; 18166 rawData[idx + 2] = 0; 18167 rawData[idx + 3] = 0; 18168 } else { 18169 rawData[idx + 0] = cast(ubyte)(where[idx + 0] * 255 / alpha); // r 18170 rawData[idx + 1] = cast(ubyte)(where[idx + 1] * 255 / alpha); // g 18171 rawData[idx + 2] = cast(ubyte)(where[idx + 2] * 255 / alpha); // b 18172 rawData[idx + 3] = where[idx + 3]; // a 18173 } 18174 } 18175 } 18176 18177 18178 void createImage(int width, int height, bool forcexshm=false, bool ignored = false) { 18179 auto colorSpace = CGColorSpaceCreateDeviceRGB(); 18180 context = CGBitmapContextCreate(null, width, height, 8, 4*width, colorSpace, kCGImageAlphaPremultipliedLast |kCGBitmapByteOrder32Big); 18181 CGColorSpaceRelease(colorSpace); 18182 rawData = CGBitmapContextGetData(context); 18183 } 18184 void dispose() { 18185 CGContextRelease(context); 18186 } 18187 18188 void setPixel(int x, int y, Color c) { 18189 auto offset = (y * width + x) * 4; 18190 if (c.a == 255) { 18191 rawData[offset + 0] = c.r; 18192 rawData[offset + 1] = c.g; 18193 rawData[offset + 2] = c.b; 18194 rawData[offset + 3] = c.a; 18195 } else { 18196 rawData[offset + 0] = cast(ubyte)(c.r*c.a/255); 18197 rawData[offset + 1] = cast(ubyte)(c.g*c.a/255); 18198 rawData[offset + 2] = cast(ubyte)(c.b*c.a/255); 18199 rawData[offset + 3] = c.a; 18200 } 18201 } 18202 } 18203 18204 mixin template NativeScreenPainterImplementation() { 18205 CGContextRef context; 18206 ubyte[4] _outlineComponents; 18207 NSView view; 18208 18209 Pen _activePen; 18210 Color _fillColor; 18211 Rectangle _clipRectangle; 18212 OperatingSystemFont _font; 18213 18214 OperatingSystemFont getFont() { 18215 if(_font is null) { 18216 static OperatingSystemFont _defaultFont; 18217 if(_defaultFont is null) { 18218 _defaultFont = new OperatingSystemFont(); 18219 _defaultFont.loadDefault(); 18220 } 18221 _font = _defaultFont; 18222 } 18223 18224 return _font; 18225 } 18226 18227 void create(PaintingHandle window) { 18228 // this.destiny = window; 18229 if(auto sw = cast(SimpleWindow) this.window) { 18230 context = sw.drawingContext; 18231 view = sw.view; 18232 } else { 18233 throw new NotYetImplementedException(); 18234 } 18235 } 18236 18237 void dispose() { 18238 view.setNeedsDisplay(true); 18239 } 18240 18241 bool manualInvalidations; 18242 void invalidateRect(Rectangle invalidRect) { } 18243 18244 // NotYetImplementedException 18245 void rasterOp(RasterOp op) { 18246 } 18247 void setClipRectangle(int, int, int, int) { 18248 } 18249 Size textSize(in char[] txt) { 18250 auto font = getFont(); 18251 return Size(font.stringWidth(txt), font.height()); 18252 } 18253 18254 void setFont(OperatingSystemFont font) { 18255 _font = font; 18256 //font.font.setInContext(context); 18257 } 18258 int fontHeight() { 18259 auto font = getFont(); 18260 return font.height; 18261 } 18262 18263 // end 18264 18265 void pen(Pen pen) { 18266 _activePen = pen; 18267 auto color = pen.color; // FIXME 18268 double alphaComponent = color.a/255.0f; 18269 CGContextSetRGBStrokeColor(context, color.r/255.0f, color.g/255.0f, color.b/255.0f, alphaComponent); 18270 18271 double[2] patternBuffer; 18272 double[] pattern; 18273 final switch(pen.style) { 18274 case Pen.Style.Solid: 18275 pattern = null; 18276 break; 18277 case Pen.Style.Dashed: 18278 patternBuffer[0] = 4; 18279 patternBuffer[1] = 1; 18280 pattern = patternBuffer[]; 18281 break; 18282 case Pen.Style.Dotted: 18283 patternBuffer[0] = 1; 18284 patternBuffer[1] = 1; 18285 pattern = patternBuffer[]; 18286 break; 18287 } 18288 18289 CGContextSetLineDash(context, 0, pattern.ptr, pattern.length); 18290 18291 if (color.a != 255) { 18292 _outlineComponents[0] = cast(ubyte)(color.r*color.a/255); 18293 _outlineComponents[1] = cast(ubyte)(color.g*color.a/255); 18294 _outlineComponents[2] = cast(ubyte)(color.b*color.a/255); 18295 _outlineComponents[3] = color.a; 18296 } else { 18297 _outlineComponents[0] = color.r; 18298 _outlineComponents[1] = color.g; 18299 _outlineComponents[2] = color.b; 18300 _outlineComponents[3] = color.a; 18301 } 18302 } 18303 18304 @property void fillColor(Color color) { 18305 CGContextSetRGBFillColor(context, color.r/255.0f, color.g/255.0f, color.b/255.0f, color.a/255.0f); 18306 } 18307 18308 void drawImage(int x, int y, Image image, int ulx, int upy, int width, int height) { 18309 // NotYetImplementedException for upper left/width/height 18310 auto cgImage = CGBitmapContextCreateImage(image.context); 18311 auto size = CGSize(CGBitmapContextGetWidth(image.context), CGBitmapContextGetHeight(image.context)); 18312 CGContextDrawImage(context, CGRect(CGPoint(x, y), size), cgImage); 18313 CGImageRelease(cgImage); 18314 } 18315 18316 void drawPixmap(Sprite s, int x, int y, int ix, int iy, int w, int h) { 18317 // FIXME: is this efficient? 18318 auto cgImage = CGBitmapContextCreateImage(s.handle); 18319 auto size = CGSize(CGBitmapContextGetWidth(s.handle), CGBitmapContextGetHeight(s.handle)); 18320 CGContextDrawImage(context, CGRect(CGPoint(x, y), size), cgImage); 18321 CGImageRelease(cgImage); 18322 } 18323 18324 18325 void drawText(int x, int y, int x2, int y2, in char[] text, uint alignment) { 18326 // FIXME: alignment 18327 if (_outlineComponents[3] != 0) { 18328 CGContextSaveGState(context); 18329 auto invAlpha = 1.0f/_outlineComponents[3]; 18330 CGContextSetRGBFillColor(context, _outlineComponents[0]*invAlpha, 18331 _outlineComponents[1]*invAlpha, 18332 _outlineComponents[2]*invAlpha, 18333 _outlineComponents[3]/255.0f); 18334 18335 18336 18337 // FIXME: should we clip it to the bounding box? 18338 int textHeight = fontHeight; 18339 18340 auto lines = text.split('\n'); 18341 18342 const lineHeight = textHeight; 18343 textHeight *= lines.length; 18344 18345 int cy = y; 18346 18347 if(alignment & TextAlignment.VerticalBottom) { 18348 if(y2 <= 0) 18349 return; 18350 auto h = y2 - y; 18351 if(h > textHeight) { 18352 cy += h - textHeight; 18353 cy -= lineHeight / 2; 18354 } 18355 } else if(alignment & TextAlignment.VerticalCenter) { 18356 if(y2 <= 0) 18357 return; 18358 auto h = y2 - y; 18359 if(textHeight < h) { 18360 cy += (h - textHeight) / 2; 18361 //cy -= lineHeight / 4; 18362 } 18363 } 18364 18365 foreach(line; text.split('\n')) { 18366 int textWidth = this.textSize(line).width; 18367 18368 int px = x, py = cy; 18369 18370 if(alignment & TextAlignment.Center) { 18371 if(x2 <= 0) 18372 return; 18373 auto w = x2 - x; 18374 if(w > textWidth) 18375 px += (w - textWidth) / 2; 18376 } else if(alignment & TextAlignment.Right) { 18377 if(x2 <= 0) 18378 return; 18379 auto pos = x2 - textWidth; 18380 if(pos > x) 18381 px = pos; 18382 } 18383 18384 CGContextShowTextAtPoint(context, px, py + getFont.ascent /* this is cuz this picks baseline but i want bounding box */, line.ptr, line.length); 18385 18386 carry_on: 18387 cy += lineHeight + 4; 18388 } 18389 18390 // auto cfstr = cast(NSid)createCFString(text); 18391 // objc_msgSend(cfstr, sel_registerName("drawAtPoint:withAttributes:"), 18392 // NSPoint(x, y), null); 18393 // CFRelease(cfstr); 18394 CGContextRestoreGState(context); 18395 } 18396 } 18397 18398 void drawPixel(int x, int y) { 18399 auto rawData = CGBitmapContextGetData(context); 18400 auto width = CGBitmapContextGetWidth(context); 18401 auto height = CGBitmapContextGetHeight(context); 18402 auto offset = ((height - y - 1) * width + x) * 4; 18403 rawData[offset .. offset+4] = _outlineComponents; 18404 } 18405 18406 void drawLine(int x1, int y1, int x2, int y2) { 18407 CGPoint[2] linePoints; 18408 linePoints[0] = CGPoint(x1, y1); 18409 linePoints[1] = CGPoint(x2, y2); 18410 CGContextStrokeLineSegments(context, linePoints.ptr, linePoints.length); 18411 } 18412 18413 void drawRectangle(int x, int y, int width, int height) { 18414 CGContextBeginPath(context); 18415 auto rect = CGRect(CGPoint(x, y), CGSize(width, height)); 18416 CGContextAddRect(context, rect); 18417 CGContextDrawPath(context, CGPathDrawingMode.kCGPathFillStroke); 18418 } 18419 18420 void drawEllipse(int x1, int y1, int x2, int y2) { 18421 CGContextBeginPath(context); 18422 auto rect = CGRect(CGPoint(x1, y1), CGSize(x2-x1, y2-y1)); 18423 CGContextAddEllipseInRect(context, rect); 18424 CGContextDrawPath(context, CGPathDrawingMode.kCGPathFillStroke); 18425 } 18426 18427 void drawArc(int x1, int y1, int width, int height, int start, int finish) { 18428 // @@@BUG@@@ Does not support elliptic arc (width != height). 18429 CGContextBeginPath(context); 18430 CGContextAddArc(context, x1+width*0.5f, y1+height*0.5f, width, 18431 start*PI/(180*64), finish*PI/(180*64), 0); 18432 CGContextDrawPath(context, CGPathDrawingMode.kCGPathFillStroke); 18433 } 18434 18435 void drawPolygon(Point[] intPoints) { 18436 CGContextBeginPath(context); 18437 CGPoint[16] pointsBuffer; 18438 CGPoint[] points; 18439 if(intPoints.length <= pointsBuffer.length) 18440 points = pointsBuffer[0 .. intPoints.length]; 18441 else 18442 points = new CGPoint[](intPoints.length); 18443 18444 foreach(idx, pt; intPoints) 18445 points[idx] = CGPoint(pt.x, pt.y); 18446 18447 CGContextAddLines(context, points.ptr, points.length); 18448 CGContextDrawPath(context, CGPathDrawingMode.kCGPathFillStroke); 18449 } 18450 } 18451 18452 private bool appInitialized = false; 18453 void initializeApp() { 18454 if(appInitialized) 18455 return; 18456 synchronized { 18457 if(appInitialized) 18458 return; 18459 18460 auto app = NSApp(); // ensure the is initialized 18461 18462 auto dg = AppDelegate.alloc; 18463 globalAppDelegate = dg; 18464 NSApp.delegate_ = dg; 18465 18466 NSApp.setActivationPolicy(NSApplicationActivationPolicy.regular); 18467 18468 appInitialized = true; 18469 } 18470 } 18471 18472 mixin template NativeSimpleWindowImplementation() { 18473 void createWindow(int width, int height, string title, OpenGlOptions opengl, SimpleWindow parent) { 18474 initializeApp(); 18475 18476 auto contentRect = NSRect(NSPoint(0, 0), NSSize(width, height)); 18477 18478 auto window = NSWindow.alloc.initWithContentRect( 18479 contentRect, 18480 NSWindowStyleMask.resizable | NSWindowStyleMask.closable | NSWindowStyleMask.miniaturizable | NSWindowStyleMask.titled, 18481 NSBackingStoreType.buffered, 18482 true 18483 ); 18484 18485 SimpleWindow.nativeMapping[cast(void*) window] = this; 18486 18487 window.title = MacString(title).borrow; 18488 18489 auto dg = SDWindowDelegate.alloc.init; 18490 dg.simpleWindow = this; 18491 window.delegate_ = dg; 18492 18493 auto view = SDGraphicsView.alloc.init; 18494 assert(view !is null); 18495 window.contentView = view; 18496 this.view = view; 18497 view.simpleWindow = this; 18498 18499 window.center(); 18500 18501 window.makeKeyAndOrderFront(null); 18502 18503 // no need to make a bitmap on mac since everything is double buffered already 18504 18505 // create area to draw on. 18506 createNewDrawingContext(width, height); 18507 18508 window.setBackgroundColor(NSColor.whiteColor); 18509 } 18510 18511 void createNewDrawingContext(int width, int height) { 18512 // FIXME need to preserve info from the old context too i think... maybe. or at least setNeedsDisplay 18513 if(this.drawingContext) 18514 CGContextRelease(this.drawingContext); 18515 auto colorSpace = CGColorSpaceCreateDeviceRGB(); 18516 this.drawingContext = CGBitmapContextCreate(null, width, height, 8, 4*width, colorSpace, kCGImageAlphaPremultipliedLast |kCGBitmapByteOrder32Big); 18517 CGColorSpaceRelease(colorSpace); 18518 CGContextSelectFont(drawingContext, "Lucida Grande", 12.0f, 1); 18519 auto matrix = CGContextGetTextMatrix(drawingContext); 18520 matrix.c = -matrix.c; 18521 matrix.d = -matrix.d; 18522 CGContextSetTextMatrix(drawingContext, matrix); 18523 18524 } 18525 18526 void dispose() { 18527 closeWindow(); 18528 // window.release(); // closing the window does this automatically i think 18529 } 18530 void closeWindow() { 18531 if(timer) 18532 timer.invalidate(); 18533 window.close(); 18534 } 18535 18536 ScreenPainter getPainter(bool manualInvalidations) { 18537 return ScreenPainter(this, this.window, manualInvalidations); 18538 } 18539 18540 NSWindow window; 18541 NSTimer timer; 18542 NSView view; 18543 CGContextRef drawingContext; 18544 } 18545 } 18546 18547 version(without_opengl) {} else 18548 extern(System) nothrow @nogc { 18549 //enum uint GL_VERSION = 0x1F02; 18550 //const(char)* glGetString (/*GLenum*/uint); 18551 version(X11) { 18552 static if (!SdpyIsUsingIVGLBinds) { 18553 18554 enum GLX_X_RENDERABLE = 0x8012; 18555 enum GLX_DRAWABLE_TYPE = 0x8010; 18556 enum GLX_RENDER_TYPE = 0x8011; 18557 enum GLX_X_VISUAL_TYPE = 0x22; 18558 enum GLX_TRUE_COLOR = 0x8002; 18559 enum GLX_WINDOW_BIT = 0x00000001; 18560 enum GLX_RGBA_BIT = 0x00000001; 18561 enum GLX_COLOR_INDEX_BIT = 0x00000002; 18562 enum GLX_SAMPLE_BUFFERS = 0x186a0; 18563 enum GLX_SAMPLES = 0x186a1; 18564 enum GLX_CONTEXT_MAJOR_VERSION_ARB = 0x2091; 18565 enum GLX_CONTEXT_MINOR_VERSION_ARB = 0x2092; 18566 } 18567 18568 // GLX_EXT_swap_control 18569 alias glXSwapIntervalEXT = void function (Display* dpy, /*GLXDrawable*/Drawable drawable, int interval); 18570 private __gshared glXSwapIntervalEXT _glx_swapInterval_fn = null; 18571 18572 //k8: ugly code to prevent warnings when sdpy is compiled into .a 18573 extern(System) { 18574 alias glXCreateContextAttribsARB_fna = GLXContext function (Display *dpy, GLXFBConfig config, GLXContext share_context, /*Bool*/int direct, const(int)* attrib_list); 18575 } 18576 private __gshared /*glXCreateContextAttribsARB_fna*/void* glXCreateContextAttribsARBFn = cast(void*)1; //HACK! 18577 18578 // this made public so we don't have to get it again and again 18579 public bool glXCreateContextAttribsARB_present () { 18580 if (glXCreateContextAttribsARBFn is cast(void*)1) { 18581 // get it 18582 glXCreateContextAttribsARBFn = cast(void*)glbindGetProcAddress("glXCreateContextAttribsARB"); 18583 //{ import core.stdc.stdio; printf("checking glXCreateContextAttribsARB: %shere\n", (glXCreateContextAttribsARBFn !is null ? "".ptr : "not ".ptr)); } 18584 } 18585 return (glXCreateContextAttribsARBFn !is null); 18586 } 18587 18588 // this made public so we don't have to get it again and again 18589 public GLXContext glXCreateContextAttribsARB (Display *dpy, GLXFBConfig config, GLXContext share_context, /*Bool*/int direct, const(int)* attrib_list) { 18590 if (!glXCreateContextAttribsARB_present()) assert(0, "glXCreateContextAttribsARB is not present"); 18591 return (cast(glXCreateContextAttribsARB_fna)glXCreateContextAttribsARBFn)(dpy, config, share_context, direct, attrib_list); 18592 } 18593 18594 // extern(C) private __gshared int function(int) glXSwapIntervalSGI; // seems totally redundant to the tohers 18595 extern(C) private __gshared int function(int) glXSwapIntervalMESA; 18596 18597 void glxSetVSync (Display* dpy, /*GLXDrawable*/Drawable drawable, bool wait) { 18598 if (cast(void*)_glx_swapInterval_fn is cast(void*)1) return; 18599 if (_glx_swapInterval_fn is null) { 18600 _glx_swapInterval_fn = cast(glXSwapIntervalEXT)glXGetProcAddress("glXSwapIntervalEXT"); 18601 if (_glx_swapInterval_fn is null) { 18602 _glx_swapInterval_fn = cast(glXSwapIntervalEXT)1; 18603 return; 18604 } 18605 version(sdddd) { debug writeln("glXSwapIntervalEXT found!"); } 18606 } 18607 18608 if(glXSwapIntervalMESA is null) { 18609 // it seems to require both to actually take effect on many computers 18610 // idk why 18611 glXSwapIntervalMESA = cast(typeof(glXSwapIntervalMESA)) glXGetProcAddress("glXSwapIntervalMESA"); 18612 if(glXSwapIntervalMESA is null) 18613 glXSwapIntervalMESA = cast(typeof(glXSwapIntervalMESA)) 1; 18614 } 18615 18616 if(cast(void*) glXSwapIntervalMESA > cast(void*) 1) 18617 glXSwapIntervalMESA(wait ? 1 : 0); 18618 18619 _glx_swapInterval_fn(dpy, drawable, (wait ? 1 : 0)); 18620 } 18621 } else version(Windows) { 18622 static if (!SdpyIsUsingIVGLBinds) { 18623 enum GL_TRUE = 1; 18624 enum GL_FALSE = 0; 18625 18626 public void* glbindGetProcAddress (const(char)* name) { 18627 void* res = wglGetProcAddress(name); 18628 if (res is null) { 18629 /+ 18630 //{ import core.stdc.stdio; printf("GL: '%s' not found (0)\n", name); } 18631 import core.sys.windows.windef, core.sys.windows.winbase; 18632 __gshared HINSTANCE dll = null; 18633 if (dll is null) { 18634 dll = LoadLibraryA("opengl32.dll"); 18635 if (dll is null) return null; // <32, but idc 18636 } 18637 res = GetProcAddress(dll, name); 18638 +/ 18639 res = GetProcAddress(gl.libHandle, name); 18640 } 18641 //{ import core.stdc.stdio; printf(" GL: '%s' is 0x%08x\n", name, cast(uint)res); } 18642 return res; 18643 } 18644 } 18645 18646 18647 private __gshared extern(System) BOOL function(int) wglSwapIntervalEXT; 18648 void wglSetVSync(bool wait) { 18649 if(wglSwapIntervalEXT is null) { 18650 wglSwapIntervalEXT = cast(typeof(wglSwapIntervalEXT)) wglGetProcAddress("wglSwapIntervalEXT"); 18651 if(wglSwapIntervalEXT is null) 18652 wglSwapIntervalEXT = cast(typeof(wglSwapIntervalEXT)) 1; 18653 } 18654 if(cast(void*) wglSwapIntervalEXT is cast(void*) 1) 18655 return; 18656 18657 wglSwapIntervalEXT(wait ? 1 : 0); 18658 } 18659 18660 enum WGL_CONTEXT_MAJOR_VERSION_ARB = 0x2091; 18661 enum WGL_CONTEXT_MINOR_VERSION_ARB = 0x2092; 18662 enum WGL_CONTEXT_LAYER_PLANE_ARB = 0x2093; 18663 enum WGL_CONTEXT_FLAGS_ARB = 0x2094; 18664 enum WGL_CONTEXT_PROFILE_MASK_ARB = 0x9126; 18665 18666 enum WGL_CONTEXT_DEBUG_BIT_ARB = 0x0001; 18667 enum WGL_CONTEXT_FORWARD_COMPATIBLE_BIT_ARB = 0x0002; 18668 18669 enum WGL_CONTEXT_CORE_PROFILE_BIT_ARB = 0x00000001; 18670 enum WGL_CONTEXT_COMPATIBILITY_PROFILE_BIT_ARB = 0x00000002; 18671 18672 alias wglCreateContextAttribsARB_fna = HGLRC function (HDC hDC, HGLRC hShareContext, const(int)* attribList); 18673 __gshared wglCreateContextAttribsARB_fna wglCreateContextAttribsARB = null; 18674 18675 void wglInitOtherFunctions () { 18676 if (wglCreateContextAttribsARB is null) { 18677 wglCreateContextAttribsARB = cast(wglCreateContextAttribsARB_fna)glbindGetProcAddress("wglCreateContextAttribsARB"); 18678 } 18679 } 18680 } 18681 18682 static if (!SdpyIsUsingIVGLBinds) { 18683 18684 interface GL { 18685 extern(System) @nogc nothrow: 18686 18687 void glGetIntegerv(int, void*); 18688 void glMatrixMode(int); 18689 void glPushMatrix(); 18690 void glLoadIdentity(); 18691 void glOrtho(double, double, double, double, double, double); 18692 void glFrustum(double, double, double, double, double, double); 18693 18694 void glPopMatrix(); 18695 void glEnable(int); 18696 void glDisable(int); 18697 void glClear(int); 18698 void glBegin(int); 18699 void glVertex2f(float, float); 18700 void glVertex3f(float, float, float); 18701 void glEnd(); 18702 void glColor3b(byte, byte, byte); 18703 void glColor3ub(ubyte, ubyte, ubyte); 18704 void glColor4b(byte, byte, byte, byte); 18705 void glColor4ub(ubyte, ubyte, ubyte, ubyte); 18706 void glColor3i(int, int, int); 18707 void glColor3ui(uint, uint, uint); 18708 void glColor4i(int, int, int, int); 18709 void glColor4ui(uint, uint, uint, uint); 18710 void glColor3f(float, float, float); 18711 void glColor4f(float, float, float, float); 18712 void glTranslatef(float, float, float); 18713 void glScalef(float, float, float); 18714 version(X11) { 18715 void glSecondaryColor3b(byte, byte, byte); 18716 void glSecondaryColor3ub(ubyte, ubyte, ubyte); 18717 void glSecondaryColor3i(int, int, int); 18718 void glSecondaryColor3ui(uint, uint, uint); 18719 void glSecondaryColor3f(float, float, float); 18720 } 18721 18722 void glDrawElements(int, int, int, void*); 18723 18724 void glRotatef(float, float, float, float); 18725 18726 uint glGetError(); 18727 18728 void glDeleteTextures(int, uint*); 18729 18730 18731 void glRasterPos2i(int, int); 18732 void glDrawPixels(int, int, uint, uint, void*); 18733 void glClearColor(float, float, float, float); 18734 18735 18736 void glPixelStorei(uint, int); 18737 18738 void glGenTextures(uint, uint*); 18739 void glBindTexture(int, int); 18740 void glTexParameteri(uint, uint, int); 18741 void glTexParameterf(uint/*GLenum*/ target, uint/*GLenum*/ pname, float param); 18742 void glTexImage2D(int, int, int, int, int, int, int, int, scope const void*); 18743 void glTexSubImage2D(uint/*GLenum*/ target, int level, int xoffset, int yoffset, 18744 /*GLsizei*/int width, /*GLsizei*/int height, 18745 uint/*GLenum*/ format, uint/*GLenum*/ type, scope const void* pixels); 18746 void glTexEnvf(uint/*GLenum*/ target, uint/*GLenum*/ pname, float param); 18747 18748 void glLineWidth(int); 18749 18750 18751 void glTexCoord2f(float, float); 18752 void glVertex2i(int, int); 18753 void glBlendFunc (int, int); 18754 void glDepthFunc (int); 18755 void glViewport(int, int, int, int); 18756 18757 void glClearDepth(double); 18758 18759 void glReadBuffer(uint); 18760 void glReadPixels(int, int, int, int, int, int, void*); 18761 18762 void glScissor(GLint x, GLint y, GLsizei width, GLsizei height); 18763 18764 void glFlush(); 18765 void glFinish(); 18766 18767 version(Windows) { 18768 BOOL wglCopyContext(HGLRC, HGLRC, UINT); 18769 HGLRC wglCreateContext(HDC); 18770 HGLRC wglCreateLayerContext(HDC, int); 18771 BOOL wglDeleteContext(HGLRC); 18772 BOOL wglDescribeLayerPlane(HDC, int, int, UINT, LPLAYERPLANEDESCRIPTOR); 18773 HGLRC wglGetCurrentContext(); 18774 HDC wglGetCurrentDC(); 18775 int wglGetLayerPaletteEntries(HDC, int, int, int, COLORREF*); 18776 PROC wglGetProcAddress(LPCSTR); 18777 BOOL wglMakeCurrent(HDC, HGLRC); 18778 BOOL wglRealizeLayerPalette(HDC, int, BOOL); 18779 int wglSetLayerPaletteEntries(HDC, int, int, int, const(COLORREF)*); 18780 BOOL wglShareLists(HGLRC, HGLRC); 18781 BOOL wglSwapLayerBuffers(HDC, UINT); 18782 BOOL wglUseFontBitmapsA(HDC, DWORD, DWORD, DWORD); 18783 BOOL wglUseFontBitmapsW(HDC, DWORD, DWORD, DWORD); 18784 BOOL wglUseFontOutlinesA(HDC, DWORD, DWORD, DWORD, FLOAT, FLOAT, int, LPGLYPHMETRICSFLOAT); 18785 BOOL wglUseFontOutlinesW(HDC, DWORD, DWORD, DWORD, FLOAT, FLOAT, int, LPGLYPHMETRICSFLOAT); 18786 } 18787 18788 } 18789 18790 interface GL3 { 18791 extern(System) @nogc nothrow: 18792 18793 void glGenVertexArrays(GLsizei, GLuint*); 18794 void glBindVertexArray(GLuint); 18795 void glDeleteVertexArrays(GLsizei, const(GLuint)*); 18796 void glGenerateMipmap(GLenum); 18797 void glBufferSubData(GLenum, GLintptr, GLsizeiptr, const(GLvoid)*); 18798 void glStencilMask(GLuint); 18799 void glStencilFunc(GLenum, GLint, GLuint); 18800 void glGetShaderInfoLog(GLuint, GLsizei, GLsizei*, GLchar*); 18801 void glGetProgramInfoLog(GLuint, GLsizei, GLsizei*, GLchar*); 18802 GLuint glCreateProgram(); 18803 GLuint glCreateShader(GLenum); 18804 void glShaderSource(GLuint, GLsizei, const(GLchar*)*, const(GLint)*); 18805 void glCompileShader(GLuint); 18806 void glGetShaderiv(GLuint, GLenum, GLint*); 18807 void glAttachShader(GLuint, GLuint); 18808 void glBindAttribLocation(GLuint, GLuint, const(GLchar)*); 18809 void glLinkProgram(GLuint); 18810 void glGetProgramiv(GLuint, GLenum, GLint*); 18811 void glDeleteProgram(GLuint); 18812 void glDeleteShader(GLuint); 18813 GLint glGetUniformLocation(GLuint, const(GLchar)*); 18814 void glGenBuffers(GLsizei, GLuint*); 18815 18816 void glUniform1f(GLint location, GLfloat v0); 18817 void glUniform2f(GLint location, GLfloat v0, GLfloat v1); 18818 void glUniform3f(GLint location, GLfloat v0, GLfloat v1, GLfloat v2); 18819 void glUniform4f(GLint location, GLfloat v0, GLfloat v1, GLfloat v2, GLfloat v3); 18820 void glUniform1i(GLint location, GLint v0); 18821 void glUniform2i(GLint location, GLint v0, GLint v1); 18822 void glUniform3i(GLint location, GLint v0, GLint v1, GLint v2); 18823 void glUniform4i(GLint location, GLint v0, GLint v1, GLint v2, GLint v3); 18824 void glUniform1ui(GLint location, GLuint v0); 18825 void glUniform2ui(GLint location, GLuint v0, GLuint v1); 18826 void glUniform3ui(GLint location, GLuint v0, GLuint v1, GLuint v2); 18827 void glUniform4ui(GLint location, GLuint v0, GLuint v1, GLuint v2, GLuint v3); 18828 void glUniform1fv(GLint location, GLsizei count, const GLfloat *value); 18829 void glUniform2fv(GLint location, GLsizei count, const GLfloat *value); 18830 void glUniform3fv(GLint location, GLsizei count, const GLfloat *value); 18831 void glUniform4fv(GLint location, GLsizei count, const GLfloat *value); 18832 void glUniform1iv(GLint location, GLsizei count, const GLint *value); 18833 void glUniform2iv(GLint location, GLsizei count, const GLint *value); 18834 void glUniform3iv(GLint location, GLsizei count, const GLint *value); 18835 void glUniform4iv(GLint location, GLsizei count, const GLint *value); 18836 void glUniform1uiv(GLint location, GLsizei count, const GLuint *value); 18837 void glUniform2uiv(GLint location, GLsizei count, const GLuint *value); 18838 void glUniform3uiv(GLint location, GLsizei count, const GLuint *value); 18839 void glUniform4uiv(GLint location, GLsizei count, const GLuint *value); 18840 void glUniformMatrix2fv(GLint location, GLsizei count, GLboolean transpose, const GLfloat *value); 18841 void glUniformMatrix3fv(GLint location, GLsizei count, GLboolean transpose, const GLfloat *value); 18842 void glUniformMatrix4fv(GLint location, GLsizei count, GLboolean transpose, const GLfloat *value); 18843 void glUniformMatrix2x3fv(GLint location, GLsizei count, GLboolean transpose, const GLfloat *value); 18844 void glUniformMatrix3x2fv(GLint location, GLsizei count, GLboolean transpose, const GLfloat *value); 18845 void glUniformMatrix2x4fv(GLint location, GLsizei count, GLboolean transpose, const GLfloat *value); 18846 void glUniformMatrix4x2fv(GLint location, GLsizei count, GLboolean transpose, const GLfloat *value); 18847 void glUniformMatrix3x4fv(GLint location, GLsizei count, GLboolean transpose, const GLfloat *value); 18848 void glUniformMatrix4x3fv(GLint location, GLsizei count, GLboolean transpose, const GLfloat *value); 18849 18850 void glColorMask(GLboolean, GLboolean, GLboolean, GLboolean); 18851 void glStencilOpSeparate(GLenum, GLenum, GLenum, GLenum); 18852 void glDrawArrays(GLenum, GLint, GLsizei); 18853 void glStencilOp(GLenum, GLenum, GLenum); 18854 void glUseProgram(GLuint); 18855 void glCullFace(GLenum); 18856 void glFrontFace(GLenum); 18857 void glActiveTexture(GLenum); 18858 void glBindBuffer(GLenum, GLuint); 18859 void glBufferData(GLenum, GLsizeiptr, const(void)*, GLenum); 18860 void glEnableVertexAttribArray(GLuint); 18861 void glVertexAttribPointer(GLuint, GLint, GLenum, GLboolean, GLsizei, const(void)*); 18862 void glUniform1i(GLint, GLint); 18863 void glUniform2fv(GLint, GLsizei, const(GLfloat)*); 18864 void glDisableVertexAttribArray(GLuint); 18865 void glDeleteBuffers(GLsizei, const(GLuint)*); 18866 void glBlendFuncSeparate(GLenum, GLenum, GLenum, GLenum); 18867 void glLogicOp (GLenum opcode); 18868 void glFramebufferTexture2D (GLenum target, GLenum attachment, GLenum textarget, GLuint texture, GLint level); 18869 void glDeleteFramebuffers (GLsizei n, const(GLuint)* framebuffers); 18870 void glGenFramebuffers (GLsizei n, GLuint* framebuffers); 18871 GLenum glCheckFramebufferStatus (GLenum target); 18872 void glBindFramebuffer (GLenum target, GLuint framebuffer); 18873 } 18874 18875 interface GL4 { 18876 extern(System) @nogc nothrow: 18877 18878 void glTextureSubImage2D(uint texture, int level, int xoffset, int yoffset, 18879 /*GLsizei*/int width, /*GLsizei*/int height, 18880 uint/*GLenum*/ format, uint/*GLenum*/ type, scope const void* pixels); 18881 } 18882 18883 interface GLU { 18884 extern(System) @nogc nothrow: 18885 18886 void gluLookAt(double, double, double, double, double, double, double, double, double); 18887 void gluPerspective(double, double, double, double); 18888 18889 char* gluErrorString(uint); 18890 } 18891 18892 18893 enum GL_RED = 0x1903; 18894 enum GL_ALPHA = 0x1906; 18895 18896 enum uint GL_FRONT = 0x0404; 18897 18898 enum uint GL_BLEND = 0x0be2; 18899 enum uint GL_LEQUAL = 0x0203; 18900 18901 18902 enum uint GL_RGB = 0x1907; 18903 enum uint GL_BGRA = 0x80e1; 18904 enum uint GL_RGBA = 0x1908; 18905 enum uint GL_RGBA8 = 0x8058; 18906 enum uint GL_TEXTURE_2D = 0x0DE1; 18907 enum uint GL_TEXTURE_MIN_FILTER = 0x2801; 18908 enum uint GL_NEAREST = 0x2600; 18909 enum uint GL_LINEAR = 0x2601; 18910 enum uint GL_TEXTURE_MAG_FILTER = 0x2800; 18911 enum uint GL_TEXTURE_WRAP_S = 0x2802; 18912 enum uint GL_TEXTURE_WRAP_T = 0x2803; 18913 enum uint GL_REPEAT = 0x2901; 18914 enum uint GL_CLAMP = 0x2900; 18915 enum uint GL_CLAMP_TO_EDGE = 0x812F; 18916 enum uint GL_CLAMP_TO_BORDER = 0x812D; 18917 enum uint GL_DECAL = 0x2101; 18918 enum uint GL_MODULATE = 0x2100; 18919 enum uint GL_TEXTURE_ENV = 0x2300; 18920 enum uint GL_TEXTURE_ENV_MODE = 0x2200; 18921 enum uint GL_REPLACE = 0x1E01; 18922 enum uint GL_LIGHTING = 0x0B50; 18923 enum uint GL_DITHER = 0x0BD0; 18924 18925 enum uint GL_NO_ERROR = 0; 18926 18927 18928 18929 enum int GL_VIEWPORT = 0x0BA2; 18930 enum int GL_MODELVIEW = 0x1700; 18931 enum int GL_TEXTURE = 0x1702; 18932 enum int GL_PROJECTION = 0x1701; 18933 enum int GL_DEPTH_TEST = 0x0B71; 18934 18935 enum int GL_COLOR_BUFFER_BIT = 0x00004000; 18936 enum int GL_ACCUM_BUFFER_BIT = 0x00000200; 18937 enum int GL_DEPTH_BUFFER_BIT = 0x00000100; 18938 enum uint GL_STENCIL_BUFFER_BIT = 0x00000400; 18939 18940 enum int GL_POINTS = 0x0000; 18941 enum int GL_LINES = 0x0001; 18942 enum int GL_LINE_LOOP = 0x0002; 18943 enum int GL_LINE_STRIP = 0x0003; 18944 enum int GL_TRIANGLES = 0x0004; 18945 enum int GL_TRIANGLE_STRIP = 5; 18946 enum int GL_TRIANGLE_FAN = 6; 18947 enum int GL_QUADS = 7; 18948 enum int GL_QUAD_STRIP = 8; 18949 enum int GL_POLYGON = 9; 18950 18951 alias GLvoid = void; 18952 alias GLboolean = ubyte; 18953 alias GLint = int; 18954 alias GLuint = uint; 18955 alias GLenum = uint; 18956 alias GLchar = char; 18957 alias GLsizei = int; 18958 alias GLfloat = float; 18959 alias GLintptr = size_t; 18960 alias GLsizeiptr = ptrdiff_t; 18961 18962 18963 enum uint GL_INVALID_ENUM = 0x0500; 18964 18965 enum uint GL_ZERO = 0; 18966 enum uint GL_ONE = 1; 18967 18968 enum uint GL_BYTE = 0x1400; 18969 enum uint GL_UNSIGNED_BYTE = 0x1401; 18970 enum uint GL_SHORT = 0x1402; 18971 enum uint GL_UNSIGNED_SHORT = 0x1403; 18972 enum uint GL_INT = 0x1404; 18973 enum uint GL_UNSIGNED_INT = 0x1405; 18974 enum uint GL_FLOAT = 0x1406; 18975 enum uint GL_2_BYTES = 0x1407; 18976 enum uint GL_3_BYTES = 0x1408; 18977 enum uint GL_4_BYTES = 0x1409; 18978 enum uint GL_DOUBLE = 0x140A; 18979 18980 enum uint GL_STREAM_DRAW = 0x88E0; 18981 18982 enum uint GL_CCW = 0x0901; 18983 18984 enum uint GL_STENCIL_TEST = 0x0B90; 18985 enum uint GL_SCISSOR_TEST = 0x0C11; 18986 18987 enum uint GL_EQUAL = 0x0202; 18988 enum uint GL_NOTEQUAL = 0x0205; 18989 18990 enum uint GL_ALWAYS = 0x0207; 18991 enum uint GL_KEEP = 0x1E00; 18992 18993 enum uint GL_INCR = 0x1E02; 18994 18995 enum uint GL_INCR_WRAP = 0x8507; 18996 enum uint GL_DECR_WRAP = 0x8508; 18997 18998 enum uint GL_CULL_FACE = 0x0B44; 18999 enum uint GL_BACK = 0x0405; 19000 19001 enum uint GL_FRAGMENT_SHADER = 0x8B30; 19002 enum uint GL_VERTEX_SHADER = 0x8B31; 19003 19004 enum uint GL_COMPILE_STATUS = 0x8B81; 19005 enum uint GL_LINK_STATUS = 0x8B82; 19006 19007 enum uint GL_ELEMENT_ARRAY_BUFFER = 0x8893; 19008 19009 enum uint GL_STATIC_DRAW = 0x88E4; 19010 19011 enum uint GL_UNPACK_ALIGNMENT = 0x0CF5; 19012 enum uint GL_UNPACK_ROW_LENGTH = 0x0CF2; 19013 enum uint GL_UNPACK_SKIP_PIXELS = 0x0CF4; 19014 enum uint GL_UNPACK_SKIP_ROWS = 0x0CF3; 19015 19016 enum uint GL_GENERATE_MIPMAP = 0x8191; 19017 enum uint GL_LINEAR_MIPMAP_LINEAR = 0x2703; 19018 19019 enum uint GL_TEXTURE0 = 0x84C0U; 19020 enum uint GL_TEXTURE1 = 0x84C1U; 19021 19022 enum uint GL_ARRAY_BUFFER = 0x8892; 19023 19024 enum uint GL_SRC_COLOR = 0x0300; 19025 enum uint GL_ONE_MINUS_SRC_COLOR = 0x0301; 19026 enum uint GL_SRC_ALPHA = 0x0302; 19027 enum uint GL_ONE_MINUS_SRC_ALPHA = 0x0303; 19028 enum uint GL_DST_ALPHA = 0x0304; 19029 enum uint GL_ONE_MINUS_DST_ALPHA = 0x0305; 19030 enum uint GL_DST_COLOR = 0x0306; 19031 enum uint GL_ONE_MINUS_DST_COLOR = 0x0307; 19032 enum uint GL_SRC_ALPHA_SATURATE = 0x0308; 19033 19034 enum uint GL_INVERT = 0x150AU; 19035 19036 enum uint GL_DEPTH_STENCIL = 0x84F9U; 19037 enum uint GL_UNSIGNED_INT_24_8 = 0x84FAU; 19038 19039 enum uint GL_FRAMEBUFFER = 0x8D40U; 19040 enum uint GL_COLOR_ATTACHMENT0 = 0x8CE0U; 19041 enum uint GL_DEPTH_STENCIL_ATTACHMENT = 0x821AU; 19042 19043 enum uint GL_FRAMEBUFFER_COMPLETE = 0x8CD5U; 19044 enum uint GL_FRAMEBUFFER_INCOMPLETE_ATTACHMENT = 0x8CD6U; 19045 enum uint GL_FRAMEBUFFER_INCOMPLETE_MISSING_ATTACHMENT = 0x8CD7U; 19046 enum uint GL_FRAMEBUFFER_INCOMPLETE_DIMENSIONS = 0x8CD9U; 19047 enum uint GL_FRAMEBUFFER_UNSUPPORTED = 0x8CDDU; 19048 19049 enum uint GL_COLOR_LOGIC_OP = 0x0BF2U; 19050 enum uint GL_CLEAR = 0x1500U; 19051 enum uint GL_COPY = 0x1503U; 19052 enum uint GL_XOR = 0x1506U; 19053 19054 enum uint GL_FRAMEBUFFER_BINDING = 0x8CA6U; 19055 19056 enum uint GL_TEXTURE_LOD_BIAS = 0x8501; 19057 19058 } 19059 } 19060 19061 /++ 19062 History: 19063 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. 19064 +/ 19065 __gshared bool gluSuccessfullyLoaded = true; 19066 19067 version(without_opengl) {} else { 19068 static if(!SdpyIsUsingIVGLBinds) { 19069 version(Windows) { 19070 mixin DynamicLoad!(GL, "opengl32", 1, openGlLibrariesSuccessfullyLoaded) gl; 19071 mixin DynamicLoad!(GLU, "glu32", 1, gluSuccessfullyLoaded) glu; 19072 } else { 19073 mixin DynamicLoad!(GL, "GL", 1, openGlLibrariesSuccessfullyLoaded) gl; 19074 mixin DynamicLoad!(GLU, "GLU", 3, gluSuccessfullyLoaded) glu; 19075 } 19076 mixin DynamicLoadSupplementalOpenGL!(GL3) gl3; 19077 19078 19079 shared static this() { 19080 gl.loadDynamicLibrary(); 19081 19082 // FIXME: this is NOT actually required and should NOT fail if it is not loaded 19083 // unless those functions are actually used 19084 // go to mark b openGlLibrariesSuccessfullyLoaded = false; 19085 glu.loadDynamicLibrary(); 19086 } 19087 } 19088 } 19089 19090 /++ 19091 Convenience method for converting D arrays to opengl buffer data 19092 19093 I would LOVE to overload it with the original glBufferData, but D won't 19094 let me since glBufferData is a function pointer :( 19095 19096 Added: August 25, 2020 (version 8.5) 19097 +/ 19098 version(without_opengl) {} else 19099 void glBufferDataSlice(GLenum target, const(void[]) data, GLenum usage) { 19100 glBufferData(target, data.length, data.ptr, usage); 19101 } 19102 19103 /+ 19104 /++ 19105 A matrix for simple uses that easily integrates with [OpenGlShader]. 19106 19107 Might not be useful to you since it only as some simple functions and 19108 probably isn't that fast. 19109 19110 Note it uses an inline static array for its storage, so copying it 19111 may be expensive. 19112 +/ 19113 struct BasicMatrix(int columns, int rows, T = float) { 19114 import core.stdc.math; 19115 19116 T[columns * rows] data = 0.0; 19117 19118 /++ 19119 Basic operations that operate *in place*. 19120 +/ 19121 void translate() { 19122 19123 } 19124 19125 /// ditto 19126 void scale() { 19127 19128 } 19129 19130 /// ditto 19131 void rotate() { 19132 19133 } 19134 19135 /++ 19136 19137 +/ 19138 static if(columns == rows) 19139 static BasicMatrix identity() { 19140 BasicMatrix m; 19141 foreach(i; 0 .. columns) 19142 data[0 + i + i * columns] = 1.0; 19143 return m; 19144 } 19145 19146 static BasicMatrix ortho() { 19147 return BasicMatrix.init; 19148 } 19149 } 19150 +/ 19151 19152 /++ 19153 Convenience class for using opengl shaders. 19154 19155 Ensure that you've loaded opengl 3+ and set your active 19156 context before trying to use this. 19157 19158 Added: August 25, 2020 (version 8.5) 19159 +/ 19160 version(without_opengl) {} else 19161 final class OpenGlShader { 19162 private int shaderProgram_; 19163 private @property void shaderProgram(int a) { 19164 shaderProgram_ = a; 19165 } 19166 /// Get the program ID for use in OpenGL functions. 19167 public @property int shaderProgram() { 19168 return shaderProgram_; 19169 } 19170 19171 /++ 19172 19173 +/ 19174 static struct Source { 19175 uint type; /// GL_FRAGMENT_SHADER, GL_VERTEX_SHADER, etc. 19176 string code; /// 19177 } 19178 19179 /++ 19180 Helper method to just compile some shader code and check for errors 19181 while you do glCreateShader, etc. on the outside yourself. 19182 19183 This just does `glShaderSource` and `glCompileShader` for the given code. 19184 19185 If you the OpenGlShader class constructor, you never need to call this yourself. 19186 +/ 19187 static void compile(int sid, Source code) { 19188 const(char)*[1] buffer; 19189 int[1] lengthBuffer; 19190 19191 buffer[0] = code.code.ptr; 19192 lengthBuffer[0] = cast(int) code.code.length; 19193 19194 glShaderSource(sid, cast(int) buffer.length, buffer.ptr, lengthBuffer.ptr); 19195 glCompileShader(sid); 19196 19197 int success; 19198 glGetShaderiv(sid, GL_COMPILE_STATUS, &success); 19199 if(!success) { 19200 char[512] info; 19201 int len; 19202 glGetShaderInfoLog(sid, info.length, &len, info.ptr); 19203 19204 throw new Exception("Shader compile failure: " ~ cast(immutable) info[0 .. len]); 19205 } 19206 } 19207 19208 /++ 19209 Calls `glLinkProgram` and throws if error a occurs. 19210 19211 If you the OpenGlShader class constructor, you never need to call this yourself. 19212 +/ 19213 static void link(int shaderProgram) { 19214 glLinkProgram(shaderProgram); 19215 int success; 19216 glGetProgramiv(shaderProgram, GL_LINK_STATUS, &success); 19217 if(!success) { 19218 char[512] info; 19219 int len; 19220 glGetProgramInfoLog(shaderProgram, info.length, &len, info.ptr); 19221 19222 throw new Exception("Shader link failure: " ~ cast(immutable) info[0 .. len]); 19223 } 19224 } 19225 19226 /++ 19227 Constructs the shader object by calling `glCreateProgram`, then 19228 compiling each given [Source], and finally, linking them together. 19229 19230 Throws: on compile or link failure. 19231 +/ 19232 this(Source[] codes...) { 19233 shaderProgram = glCreateProgram(); 19234 19235 int[16] shadersBufferStack; 19236 19237 int[] shadersBuffer = codes.length <= shadersBufferStack.length ? 19238 shadersBufferStack[0 .. codes.length] : 19239 new int[](codes.length); 19240 19241 foreach(idx, code; codes) { 19242 shadersBuffer[idx] = glCreateShader(code.type); 19243 19244 compile(shadersBuffer[idx], code); 19245 19246 glAttachShader(shaderProgram, shadersBuffer[idx]); 19247 } 19248 19249 link(shaderProgram); 19250 19251 foreach(s; shadersBuffer) 19252 glDeleteShader(s); 19253 } 19254 19255 /// Calls `glUseProgram(this.shaderProgram)` 19256 void use() { 19257 glUseProgram(this.shaderProgram); 19258 } 19259 19260 /// Deletes the program. 19261 void delete_() { 19262 glDeleteProgram(shaderProgram); 19263 shaderProgram = 0; 19264 } 19265 19266 /++ 19267 [OpenGlShader.uniforms].name gives you one of these. 19268 19269 You can get the id out of it or just assign 19270 +/ 19271 static struct Uniform { 19272 /// the id passed to glUniform* 19273 int id; 19274 19275 /// Assigns the 4 floats. You will probably have to call this via the .opAssign name 19276 void opAssign(float x, float y, float z, float w) { 19277 if(id != -1) 19278 glUniform4f(id, x, y, z, w); 19279 } 19280 19281 void opAssign(float x) { 19282 if(id != -1) 19283 glUniform1f(id, x); 19284 } 19285 19286 void opAssign(float x, float y) { 19287 if(id != -1) 19288 glUniform2f(id, x, y); 19289 } 19290 19291 void opAssign(T)(T t) { 19292 t.glUniform(id); 19293 } 19294 } 19295 19296 static struct UniformsHelper { 19297 OpenGlShader _shader; 19298 19299 @property Uniform opDispatch(string name)() { 19300 auto i = glGetUniformLocation(_shader.shaderProgram, name.ptr); 19301 // FIXME: decide what to do here; the exception is liable to be swallowed by the event syste 19302 //if(i == -1) 19303 //throw new Exception("Could not find uniform " ~ name); 19304 return Uniform(i); 19305 } 19306 19307 @property void opDispatch(string name, T)(T t) { 19308 Uniform f = this.opDispatch!name; 19309 t.glUniform(f); 19310 } 19311 } 19312 19313 /++ 19314 Gives access to the uniforms through dot access. 19315 `OpenGlShader.Uniform = shader.uniforms.foo; // calls glGetUniformLocation(this, "foo"); 19316 +/ 19317 @property UniformsHelper uniforms() { return UniformsHelper(this); } 19318 } 19319 19320 version(without_opengl) {} else { 19321 /++ 19322 A static container of experimental types and value constructors for opengl 3+ shaders. 19323 19324 19325 You can declare variables like: 19326 19327 ``` 19328 OGL.vec3f something; 19329 ``` 19330 19331 But generally it would be used with [OpenGlShader]'s uniform helpers like 19332 19333 ``` 19334 shader.uniforms.mouse = OGL.vec(mouseX, mouseY); // or OGL.vec2f if you want to be more specific 19335 ``` 19336 19337 This is still extremely experimental, not very useful at this point, and thus subject to change at random. 19338 19339 19340 History: 19341 Added December 7, 2021. Not yet stable. 19342 +/ 19343 final class OGL { 19344 static: 19345 19346 private template typeFromSpecifier(string specifier) { 19347 static if(specifier == "f") 19348 alias typeFromSpecifier = GLfloat; 19349 else static if(specifier == "i") 19350 alias typeFromSpecifier = GLint; 19351 else static if(specifier == "ui") 19352 alias typeFromSpecifier = GLuint; 19353 else static assert(0, "I don't know this ogl type suffix " ~ specifier); 19354 } 19355 19356 private template CommonType(T...) { 19357 static if(T.length == 1) 19358 alias CommonType = T[0]; 19359 else static if(is(typeof(true ? T[0].init : T[1].init) C)) 19360 alias CommonType = CommonType!(C, T[2 .. $]); 19361 } 19362 19363 private template typesToSpecifier(T...) { 19364 static if(is(CommonType!T == float)) 19365 enum typesToSpecifier = "f"; 19366 else static if(is(CommonType!T == int)) 19367 enum typesToSpecifier = "i"; 19368 else static if(is(CommonType!T == uint)) 19369 enum typesToSpecifier = "ui"; 19370 else static assert(0, "I can't find a gl type suffix for common type " ~ CommonType!T.stringof); 19371 } 19372 19373 private template genNames(size_t dim, size_t dim2 = 0) { 19374 string helper() { 19375 string s; 19376 if(dim2) { 19377 s ~= "type["~(dim + '0')~"]["~(dim2 + '0')~"] matrix;"; 19378 } else { 19379 if(dim > 0) s ~= "type x = 0;"; 19380 if(dim > 1) s ~= "type y = 0;"; 19381 if(dim > 2) s ~= "type z = 0;"; 19382 if(dim > 3) s ~= "type w = 0;"; 19383 } 19384 return s; 19385 } 19386 19387 enum genNames = helper(); 19388 } 19389 19390 // there's vec, arrays of vec, mat, and arrays of mat 19391 template opDispatch(string name) 19392 if(name.length > 4 && (name[0 .. 3] == "vec" || name[0 .. 3] == "mat")) 19393 { 19394 static if(name[4] == 'x') { 19395 enum dimX = cast(int) (name[3] - '0'); 19396 static assert(dimX > 0 && dimX <= 4, "Bad dimension for OGL X type " ~ name[3]); 19397 19398 enum dimY = cast(int) (name[5] - '0'); 19399 static assert(dimY > 0 && dimY <= 4, "Bad dimension for OGL Y type " ~ name[5]); 19400 19401 enum isArray = name[$ - 1] == 'v'; 19402 enum typeSpecifier = isArray ? name[6 .. $ - 1] : name[6 .. $]; 19403 alias type = typeFromSpecifier!typeSpecifier; 19404 } else { 19405 enum dim = cast(int) (name[3] - '0'); 19406 static assert(dim > 0 && dim <= 4, "Bad dimension for OGL type " ~ name[3]); 19407 enum isArray = name[$ - 1] == 'v'; 19408 enum typeSpecifier = isArray ? name[4 .. $ - 1] : name[4 .. $]; 19409 alias type = typeFromSpecifier!typeSpecifier; 19410 } 19411 19412 align(1) 19413 struct opDispatch { 19414 align(1): 19415 static if(name[4] == 'x') 19416 mixin(genNames!(dimX, dimY)); 19417 else 19418 mixin(genNames!dim); 19419 19420 private void glUniform(OpenGlShader.Uniform assignTo) { 19421 glUniform(assignTo.id); 19422 } 19423 private void glUniform(int assignTo) { 19424 static if(name[4] == 'x') { 19425 // FIXME 19426 pragma(msg, "This matrix uniform helper has never been tested!!!!"); 19427 mixin("glUniformMatrix" ~ name[3 .. $] ~ "v")(assignTo, dimX * dimY, false, this.matrix.ptr); 19428 } else 19429 mixin("glUniform" ~ name[3 .. $])(assignTo, this.tupleof); 19430 } 19431 } 19432 } 19433 19434 auto vec(T...)(T members) { 19435 return typeof(this).opDispatch!("vec" ~ toInternal!string(cast(int) T.length)~ typesToSpecifier!T)(members); 19436 } 19437 } 19438 } 19439 19440 version(linux) { 19441 version(with_eventloop) {} else { 19442 private int epollFd = -1; 19443 void prepareEventLoop() { 19444 if(epollFd != -1) 19445 return; // already initialized, no need to do it again 19446 import ep = core.sys.linux.epoll; 19447 19448 epollFd = ep.epoll_create1(ep.EPOLL_CLOEXEC); 19449 if(epollFd == -1) 19450 throw new Exception("epoll create failure"); 19451 } 19452 } 19453 } else version(Posix) { 19454 void prepareEventLoop() {} 19455 } 19456 19457 version(X11) { 19458 import core.stdc.locale : LC_ALL; // rdmd fix 19459 __gshared bool sdx_isUTF8Locale; 19460 19461 // This whole crap is used to initialize X11 locale, so that you can use XIM methods later. 19462 // Yes, there are people with non-utf locale (it's me, Ketmar!), but XIM (composing) will 19463 // not work right if app/X11 locale is not utf. This sux. That's why all that "utf detection" 19464 // anal magic is here. I (Ketmar) hope you like it. 19465 // We will use `sdx_isUTF8Locale` on XIM creation to enforce UTF-8 locale, so XCompose will 19466 // always return correct unicode symbols. The detection is here 'cause user can change locale 19467 // later. 19468 19469 // NOTE: IT IS VERY IMPORTANT THAT THIS BE THE LAST STATIC CTOR OF THE FILE since it tests librariesSuccessfullyLoaded 19470 shared static this () { 19471 if(!librariesSuccessfullyLoaded) 19472 return; 19473 19474 import core.stdc.locale : setlocale, LC_ALL, LC_CTYPE; 19475 19476 // this doesn't hurt; it may add some locking, but the speed is still 19477 // allows doing 60 FPS videogames; also, ignore the result, as most 19478 // users will probably won't do mulththreaded X11 anyway (and I (ketmar) 19479 // never seen this failing). 19480 if (XInitThreads() == 0) { import core.stdc.stdio; fprintf(stderr, "XInitThreads() failed!\n"); } 19481 19482 setlocale(LC_ALL, ""); 19483 // check if out locale is UTF-8 19484 auto lct = setlocale(LC_CTYPE, null); 19485 if (lct is null) { 19486 sdx_isUTF8Locale = false; 19487 } else { 19488 for (size_t idx = 0; lct[idx] && lct[idx+1] && lct[idx+2]; ++idx) { 19489 if ((lct[idx+0] == 'u' || lct[idx+0] == 'U') && 19490 (lct[idx+1] == 't' || lct[idx+1] == 'T') && 19491 (lct[idx+2] == 'f' || lct[idx+2] == 'F')) 19492 { 19493 sdx_isUTF8Locale = true; 19494 break; 19495 } 19496 } 19497 } 19498 //{ import core.stdc.stdio : stderr, fprintf; fprintf(stderr, "UTF8: %s\n", sdx_isUTF8Locale ? "tan".ptr : "ona".ptr); } 19499 } 19500 } 19501 19502 class ExperimentalTextComponent2 { 19503 /+ 19504 Stage 1: get it working monospace 19505 Stage 2: use proportional font 19506 Stage 3: allow changes in inline style 19507 Stage 4: allow new fonts and sizes in the middle 19508 Stage 5: optimize gap buffer 19509 Stage 6: optimize layout 19510 Stage 7: word wrap 19511 Stage 8: justification 19512 Stage 9: editing, selection, etc. 19513 19514 Operations: 19515 insert text 19516 overstrike text 19517 select 19518 cut 19519 modify 19520 +/ 19521 19522 /++ 19523 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. 19524 +/ 19525 this(SimpleWindow window) { 19526 this.window = window; 19527 } 19528 19529 private SimpleWindow window; 19530 19531 19532 /++ 19533 When you render a [ComponentInFlow], it returns an arbitrary number of these interfaces 19534 representing the internal parts. The first pass is focused on the x parameter, then the 19535 renderer is responsible for going back to the parts in the current line and calling 19536 adjustDownForAscent to change the y params. 19537 +/ 19538 static interface ComponentRenderHelper { 19539 19540 /+ 19541 When you do an edit, possibly stuff on the same line previously need to move (to adjust 19542 the baseline), stuff subsequent needs to move (adjust x) and possibly stuff below needs 19543 to move (adjust y to make room for new line) until you get back to the same position, 19544 then you can stop - if one thing is unchanged, nothing after it is changed too. 19545 19546 Word wrap might change this as if can rewrap tons of stuff, but the same idea applies, 19547 once you reach something that is unchanged, you can stop. 19548 +/ 19549 19550 void adjustDownForAscent(int amount); // at the end of the line it needs to do these 19551 19552 int ascent() const; 19553 int descent() const; 19554 19555 int advance() const; 19556 19557 bool endsWithExplititLineBreak() const; 19558 } 19559 19560 static interface RenderResult { 19561 /++ 19562 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. 19563 +/ 19564 void popFront(); 19565 @property bool empty() const; 19566 @property ComponentRenderHelper front() const; 19567 19568 void repositionForNextLine(Point baseline, int availableWidth); 19569 } 19570 19571 static interface ComponentInFlow { 19572 void draw(ScreenPainter painter); 19573 //RenderResult render(Point baseline, int availableWidth); // FIXME: it needs to be able to say "my cache is good, nothing different" 19574 19575 bool startsWithExplicitLineBreak() const; 19576 } 19577 19578 static class TextFlowComponent : ComponentInFlow { 19579 bool startsWithExplicitLineBreak() const { return false; } // FIXME: if it is block this can return true 19580 19581 Color foreground; 19582 Color background; 19583 19584 OperatingSystemFont font; // should NEVER be null 19585 19586 ubyte attributes; // underline, strike through, display on new block 19587 19588 version(Windows) 19589 const(wchar)[] content; 19590 else 19591 const(char)[] content; // this should NEVER have a newline, except at the end 19592 19593 RenderedComponent[] rendered; // entirely controlled by [rerender] 19594 19595 // could prolly put some spacing around it too like margin / padding 19596 19597 this(Color f, Color b, OperatingSystemFont font, ubyte attr, const(char)[] c) 19598 in { assert(font !is null); 19599 assert(!font.isNull); } 19600 do 19601 { 19602 this.foreground = f; 19603 this.background = b; 19604 this.font = font; 19605 19606 this.attributes = attr; 19607 version(Windows) { 19608 auto conversionFlags = 0;//WindowsStringConversionFlags.convertNewLines; 19609 auto sz = sizeOfConvertedWstring(c, conversionFlags); 19610 auto buffer = new wchar[](sz); 19611 this.content = makeWindowsString(c, buffer, conversionFlags); 19612 } else { 19613 this.content = c.dup; 19614 } 19615 } 19616 19617 void draw(ScreenPainter painter) { 19618 painter.setFont(this.font); 19619 painter.outlineColor = this.foreground; 19620 painter.fillColor = Color.transparent; 19621 foreach(rendered; this.rendered) { 19622 // the component works in term of baseline, 19623 // but the painter works in term of upper left bounding box 19624 // so need to translate that 19625 19626 if(this.background.a) { 19627 painter.fillColor = this.background; 19628 painter.outlineColor = this.background; 19629 19630 painter.drawRectangle(Point(rendered.startX, rendered.startY - this.font.ascent), Size(rendered.width, this.font.height)); 19631 19632 painter.outlineColor = this.foreground; 19633 painter.fillColor = Color.transparent; 19634 } 19635 19636 painter.drawText(Point(rendered.startX, rendered.startY - this.font.ascent), rendered.slice); 19637 19638 // FIXME: strike through, underline, highlight selection, etc. 19639 } 19640 } 19641 } 19642 19643 // I could split the parts into words on render 19644 // for easier word-wrap, each one being an unbreakable "inline-block" 19645 private TextFlowComponent[] parts; 19646 private int needsRerenderFrom; 19647 19648 void addPart(Color f, Color b, OperatingSystemFont font, ubyte attr, const(char)[] c) { 19649 // FIXME: needsRerenderFrom. Basically if the bounding box and baseline is the same as the previous thing, it can prolly just stop. 19650 parts ~= new TextFlowComponent(f, b, font, attr, c); 19651 } 19652 19653 static struct RenderedComponent { 19654 int startX; 19655 int startY; 19656 short width; 19657 // 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! 19658 // for individual chars in here you've gotta process on demand 19659 version(Windows) 19660 const(wchar)[] slice; 19661 else 19662 const(char)[] slice; 19663 } 19664 19665 19666 void rerender(Rectangle boundingBox) { 19667 Point baseline = boundingBox.upperLeft; 19668 19669 this.boundingBox.left = boundingBox.left; 19670 this.boundingBox.top = boundingBox.top; 19671 19672 auto remainingParts = parts; 19673 19674 int largestX; 19675 19676 19677 foreach(part; parts) 19678 part.font.prepareContext(window); 19679 scope(exit) 19680 foreach(part; parts) 19681 part.font.releaseContext(); 19682 19683 calculateNextLine: 19684 19685 int nextLineHeight = 0; 19686 int nextBiggestDescent = 0; 19687 19688 foreach(part; remainingParts) { 19689 auto height = part.font.ascent; 19690 if(height > nextLineHeight) 19691 nextLineHeight = height; 19692 if(part.font.descent > nextBiggestDescent) 19693 nextBiggestDescent = part.font.descent; 19694 if(part.content.length && part.content[$-1] == '\n') 19695 break; 19696 } 19697 19698 baseline.y += nextLineHeight; 19699 auto lineStart = baseline; 19700 19701 while(remainingParts.length) { 19702 remainingParts[0].rendered = null; 19703 19704 bool eol; 19705 if(remainingParts[0].content.length && remainingParts[0].content[$-1] == '\n') 19706 eol = true; 19707 19708 // FIXME: word wrap 19709 auto font = remainingParts[0].font; 19710 auto slice = remainingParts[0].content[0 .. $ - (eol ? 1 : 0)]; 19711 auto width = font.stringWidth(slice, window); 19712 remainingParts[0].rendered ~= RenderedComponent(baseline.x, baseline.y, cast(short) width, slice); 19713 19714 remainingParts = remainingParts[1 .. $]; 19715 baseline.x += width; 19716 19717 if(eol) { 19718 baseline.y += nextBiggestDescent; 19719 if(baseline.x > largestX) 19720 largestX = baseline.x; 19721 baseline.x = lineStart.x; 19722 goto calculateNextLine; 19723 } 19724 } 19725 19726 if(baseline.x > largestX) 19727 largestX = baseline.x; 19728 19729 this.boundingBox.right = largestX; 19730 this.boundingBox.bottom = baseline.y; 19731 } 19732 19733 // you must call rerender first! 19734 void draw(ScreenPainter painter) { 19735 foreach(part; parts) { 19736 part.draw(painter); 19737 } 19738 } 19739 19740 struct IdentifyResult { 19741 TextFlowComponent part; 19742 int charIndexInPart; 19743 int totalCharIndex = -1; // if this is -1, it just means the end 19744 19745 Rectangle boundingBox; 19746 } 19747 19748 IdentifyResult identify(Point pt, bool exact = false) { 19749 if(parts.length == 0) 19750 return IdentifyResult(null, 0); 19751 19752 if(pt.y < boundingBox.top) { 19753 if(exact) 19754 return IdentifyResult(null, 1); 19755 return IdentifyResult(parts[0], 0); 19756 } 19757 if(pt.y > boundingBox.bottom) { 19758 if(exact) 19759 return IdentifyResult(null, 2); 19760 return IdentifyResult(parts[$-1], cast(int) parts[$-1].content.length); 19761 } 19762 19763 int tci = 0; 19764 19765 // I should probably like binary search this or something... 19766 foreach(ref part; parts) { 19767 foreach(rendered; part.rendered) { 19768 auto rect = Rectangle(rendered.startX, rendered.startY - part.font.ascent, rendered.startX + rendered.width, rendered.startY + part.font.descent); 19769 if(rect.contains(pt)) { 19770 auto x = pt.x - rendered.startX; 19771 auto estimatedIdx = x / part.font.averageWidth; 19772 19773 if(estimatedIdx < 0) 19774 estimatedIdx = 0; 19775 19776 if(estimatedIdx > rendered.slice.length) 19777 estimatedIdx = cast(int) rendered.slice.length; 19778 19779 int idx; 19780 int x1, x2; 19781 if(part.font.isMonospace) { 19782 auto w = part.font.averageWidth; 19783 if(!exact && x > (estimatedIdx + 1) * w) 19784 return IdentifyResult(null, 4); 19785 idx = estimatedIdx; 19786 x1 = idx * w; 19787 x2 = (idx + 1) * w; 19788 } else { 19789 idx = estimatedIdx; 19790 19791 part.font.prepareContext(window); 19792 scope(exit) part.font.releaseContext(); 19793 19794 // int iterations; 19795 19796 while(true) { 19797 // iterations++; 19798 x1 = idx ? part.font.stringWidth(rendered.slice[0 .. idx - 1]) : 0; 19799 x2 = part.font.stringWidth(rendered.slice[0 .. idx]); // should be the maximum since `averageWidth` kinda lies. 19800 19801 x1 += rendered.startX; 19802 x2 += rendered.startX; 19803 19804 if(pt.x < x1) { 19805 if(idx == 0) { 19806 if(exact) 19807 return IdentifyResult(null, 6); 19808 else 19809 break; 19810 } 19811 idx--; 19812 } else if(pt.x > x2) { 19813 idx++; 19814 if(idx > rendered.slice.length) { 19815 if(exact) 19816 return IdentifyResult(null, 5); 19817 else 19818 break; 19819 } 19820 } else if(pt.x >= x1 && pt.x <= x2) { 19821 if(idx) 19822 idx--; // point it at the original index 19823 break; // we fit 19824 } 19825 } 19826 19827 // writeln(iterations) 19828 } 19829 19830 19831 return IdentifyResult(part, idx, tci + idx, Rectangle(x1, rect.top, x2, rect.bottom)); // FIXME: utf-8? 19832 } 19833 } 19834 tci += cast(int) part.content.length; // FIXME: utf-8? 19835 } 19836 return IdentifyResult(null, 3); 19837 } 19838 19839 Rectangle boundingBox; // only set after [rerender] 19840 19841 // text will be positioned around the exclusion zone 19842 static struct ExclusionZone { 19843 19844 } 19845 19846 ExclusionZone[] exclusionZones; 19847 } 19848 19849 19850 // Don't use this yet. When I'm happy with it, I will move it to the 19851 // regular module namespace. 19852 mixin template ExperimentalTextComponent() { 19853 19854 static: 19855 19856 alias Rectangle = arsd.color.Rectangle; 19857 19858 struct ForegroundColor { 19859 Color color; 19860 alias color this; 19861 19862 this(Color c) { 19863 color = c; 19864 } 19865 19866 this(int r, int g, int b, int a = 255) { 19867 color = Color(r, g, b, a); 19868 } 19869 19870 static ForegroundColor opDispatch(string s)() if(__traits(compiles, ForegroundColor(mixin("Color." ~ s)))) { 19871 return ForegroundColor(mixin("Color." ~ s)); 19872 } 19873 } 19874 19875 struct BackgroundColor { 19876 Color color; 19877 alias color this; 19878 19879 this(Color c) { 19880 color = c; 19881 } 19882 19883 this(int r, int g, int b, int a = 255) { 19884 color = Color(r, g, b, a); 19885 } 19886 19887 static BackgroundColor opDispatch(string s)() if(__traits(compiles, BackgroundColor(mixin("Color." ~ s)))) { 19888 return BackgroundColor(mixin("Color." ~ s)); 19889 } 19890 } 19891 19892 static class InlineElement { 19893 string text; 19894 19895 BlockElement containingBlock; 19896 19897 Color color = Color.black; 19898 Color backgroundColor = Color.transparent; 19899 ushort styles; 19900 19901 string font; 19902 int fontSize; 19903 19904 int lineHeight; 19905 19906 void* identifier; 19907 19908 Rectangle boundingBox; 19909 int[] letterXs; // FIXME: maybe i should do bounding boxes for every character 19910 19911 bool isMergeCompatible(InlineElement other) { 19912 return 19913 containingBlock is other.containingBlock && 19914 color == other.color && 19915 backgroundColor == other.backgroundColor && 19916 styles == other.styles && 19917 font == other.font && 19918 fontSize == other.fontSize && 19919 lineHeight == other.lineHeight && 19920 true; 19921 } 19922 19923 int xOfIndex(size_t index) { 19924 if(index < letterXs.length) 19925 return letterXs[index]; 19926 else 19927 return boundingBox.right; 19928 } 19929 19930 InlineElement clone() { 19931 auto ie = new InlineElement(); 19932 ie.tupleof = this.tupleof; 19933 return ie; 19934 } 19935 19936 InlineElement getPreviousInlineElement() { 19937 InlineElement prev = null; 19938 foreach(ie; this.containingBlock.parts) { 19939 if(ie is this) 19940 break; 19941 prev = ie; 19942 } 19943 if(prev is null) { 19944 BlockElement pb; 19945 BlockElement cb = this.containingBlock; 19946 moar: 19947 foreach(ie; this.containingBlock.containingLayout.blocks) { 19948 if(ie is cb) 19949 break; 19950 pb = ie; 19951 } 19952 if(pb is null) 19953 return null; 19954 if(pb.parts.length == 0) { 19955 cb = pb; 19956 goto moar; 19957 } 19958 19959 prev = pb.parts[$-1]; 19960 19961 } 19962 return prev; 19963 } 19964 19965 InlineElement getNextInlineElement() { 19966 InlineElement next = null; 19967 foreach(idx, ie; this.containingBlock.parts) { 19968 if(ie is this) { 19969 if(idx + 1 < this.containingBlock.parts.length) 19970 next = this.containingBlock.parts[idx + 1]; 19971 break; 19972 } 19973 } 19974 if(next is null) { 19975 BlockElement n; 19976 foreach(idx, ie; this.containingBlock.containingLayout.blocks) { 19977 if(ie is this.containingBlock) { 19978 if(idx + 1 < this.containingBlock.containingLayout.blocks.length) 19979 n = this.containingBlock.containingLayout.blocks[idx + 1]; 19980 break; 19981 } 19982 } 19983 if(n is null) 19984 return null; 19985 19986 if(n.parts.length) 19987 next = n.parts[0]; 19988 else {} // FIXME 19989 19990 } 19991 return next; 19992 } 19993 19994 } 19995 19996 // Block elements are used entirely for positioning inline elements, 19997 // which are the things that are actually drawn. 19998 class BlockElement { 19999 InlineElement[] parts; 20000 uint alignment; 20001 20002 int whiteSpace; // pre, pre-wrap, wrap 20003 20004 TextLayout containingLayout; 20005 20006 // inputs 20007 Point where; 20008 Size minimumSize; 20009 Size maximumSize; 20010 Rectangle[] excludedBoxes; // like if you want it to write around a floated image or something. Coordinates are relative to the bounding box. 20011 void* identifier; 20012 20013 Rectangle margin; 20014 Rectangle padding; 20015 20016 // outputs 20017 Rectangle[] boundingBoxes; 20018 } 20019 20020 struct TextIdentifyResult { 20021 InlineElement element; 20022 int offset; 20023 20024 private TextIdentifyResult fixupNewline() { 20025 if(element !is null && offset < element.text.length && element.text[offset] == '\n') { 20026 offset--; 20027 } else if(element !is null && offset == element.text.length && element.text.length > 1 && element.text[$-1] == '\n') { 20028 offset--; 20029 } 20030 return this; 20031 } 20032 } 20033 20034 class TextLayout { 20035 BlockElement[] blocks; 20036 Rectangle boundingBox_; 20037 Rectangle boundingBox() { return boundingBox_; } 20038 void boundingBox(Rectangle r) { 20039 if(r != boundingBox_) { 20040 boundingBox_ = r; 20041 layoutInvalidated = true; 20042 } 20043 } 20044 20045 Rectangle contentBoundingBox() { 20046 Rectangle r; 20047 foreach(block; blocks) 20048 foreach(ie; block.parts) { 20049 if(ie.boundingBox.right > r.right) 20050 r.right = ie.boundingBox.right; 20051 if(ie.boundingBox.bottom > r.bottom) 20052 r.bottom = ie.boundingBox.bottom; 20053 } 20054 return r; 20055 } 20056 20057 BlockElement[] getBlocks() { 20058 return blocks; 20059 } 20060 20061 InlineElement[] getTexts() { 20062 InlineElement[] elements; 20063 foreach(block; blocks) 20064 elements ~= block.parts; 20065 return elements; 20066 } 20067 20068 string getPlainText() { 20069 string text; 20070 foreach(block; blocks) 20071 foreach(part; block.parts) 20072 text ~= part.text; 20073 return text; 20074 } 20075 20076 string getHtml() { 20077 return null; // FIXME 20078 } 20079 20080 this(Rectangle boundingBox) { 20081 this.boundingBox = boundingBox; 20082 } 20083 20084 BlockElement addBlock(InlineElement after = null, Rectangle margin = Rectangle(0, 0, 0, 0), Rectangle padding = Rectangle(0, 0, 0, 0)) { 20085 auto be = new BlockElement(); 20086 be.containingLayout = this; 20087 if(after is null) 20088 blocks ~= be; 20089 else { 20090 foreach(idx, b; blocks) { 20091 if(b is after.containingBlock) { 20092 blocks = blocks[0 .. idx + 1] ~ be ~ blocks[idx + 1 .. $]; 20093 break; 20094 } 20095 } 20096 } 20097 return be; 20098 } 20099 20100 void clear() { 20101 blocks = null; 20102 selectionStart = selectionEnd = caret = Caret.init; 20103 } 20104 20105 void addText(Args...)(Args args) { 20106 if(blocks.length == 0) 20107 addBlock(); 20108 20109 InlineElement ie = new InlineElement(); 20110 foreach(idx, arg; args) { 20111 static if(is(typeof(arg) == ForegroundColor)) 20112 ie.color = arg; 20113 else static if(is(typeof(arg) == TextFormat)) { 20114 if(arg & 0x8000) // ~TextFormat.something turns it off 20115 ie.styles &= arg; 20116 else 20117 ie.styles |= arg; 20118 } else static if(is(typeof(arg) == string)) { 20119 static if(idx == 0 && args.length > 1) 20120 static assert(0, "Put styles before the string."); 20121 size_t lastLineIndex; 20122 foreach(cidx, char a; arg) { 20123 if(a == '\n') { 20124 ie.text = arg[lastLineIndex .. cidx + 1]; 20125 lastLineIndex = cidx + 1; 20126 ie.containingBlock = blocks[$-1]; 20127 blocks[$-1].parts ~= ie.clone; 20128 ie.text = null; 20129 } else { 20130 20131 } 20132 } 20133 20134 ie.text = arg[lastLineIndex .. $]; 20135 ie.containingBlock = blocks[$-1]; 20136 blocks[$-1].parts ~= ie.clone; 20137 caret = Caret(this, blocks[$-1].parts[$-1], cast(int) blocks[$-1].parts[$-1].text.length); 20138 } 20139 } 20140 20141 invalidateLayout(); 20142 } 20143 20144 void tryMerge(InlineElement into, InlineElement what) { 20145 if(!into.isMergeCompatible(what)) { 20146 return; // cannot merge, different configs 20147 } 20148 20149 // cool, can merge, bring text together... 20150 into.text ~= what.text; 20151 20152 // and remove what 20153 for(size_t a = 0; a < what.containingBlock.parts.length; a++) { 20154 if(what.containingBlock.parts[a] is what) { 20155 for(size_t i = a; i < what.containingBlock.parts.length - 1; i++) 20156 what.containingBlock.parts[i] = what.containingBlock.parts[i + 1]; 20157 what.containingBlock.parts = what.containingBlock.parts[0 .. $-1]; 20158 20159 } 20160 } 20161 20162 // FIXME: ensure no other carets have a reference to it 20163 } 20164 20165 /// exact = true means return null if no match. otherwise, get the closest one that makes sense for a mouse click. 20166 TextIdentifyResult identify(int x, int y, bool exact = false) { 20167 TextIdentifyResult inexactMatch; 20168 foreach(block; blocks) { 20169 foreach(part; block.parts) { 20170 if(x >= part.boundingBox.left && x < part.boundingBox.right && y >= part.boundingBox.top && y < part.boundingBox.bottom) { 20171 20172 // FIXME binary search 20173 int tidx; 20174 int lastX; 20175 foreach_reverse(idxo, lx; part.letterXs) { 20176 int idx = cast(int) idxo; 20177 if(lx <= x) { 20178 if(lastX && lastX - x < x - lx) 20179 tidx = idx + 1; 20180 else 20181 tidx = idx; 20182 break; 20183 } 20184 lastX = lx; 20185 } 20186 20187 return TextIdentifyResult(part, tidx).fixupNewline; 20188 } else if(!exact) { 20189 // we're not in the box, but are we on the same line? 20190 if(y >= part.boundingBox.top && y < part.boundingBox.bottom) 20191 inexactMatch = TextIdentifyResult(part, x == 0 ? 0 : cast(int) part.text.length); 20192 } 20193 } 20194 } 20195 20196 if(!exact && inexactMatch is TextIdentifyResult.init && blocks.length && blocks[$-1].parts.length) 20197 return TextIdentifyResult(blocks[$-1].parts[$-1], cast(int) blocks[$-1].parts[$-1].text.length).fixupNewline; 20198 20199 return exact ? TextIdentifyResult.init : inexactMatch.fixupNewline; 20200 } 20201 20202 void moveCaretToPixelCoordinates(int x, int y) { 20203 auto result = identify(x, y); 20204 caret.inlineElement = result.element; 20205 caret.offset = result.offset; 20206 } 20207 20208 void selectToPixelCoordinates(int x, int y) { 20209 auto result = identify(x, y); 20210 20211 if(y < caretLastDrawnY1) { 20212 // on a previous line, carat is selectionEnd 20213 selectionEnd = caret; 20214 20215 selectionStart = Caret(this, result.element, result.offset); 20216 } else if(y > caretLastDrawnY2) { 20217 // on a later line 20218 selectionStart = caret; 20219 20220 selectionEnd = Caret(this, result.element, result.offset); 20221 } else { 20222 // on the same line... 20223 if(x <= caretLastDrawnX) { 20224 selectionEnd = caret; 20225 selectionStart = Caret(this, result.element, result.offset); 20226 } else { 20227 selectionStart = caret; 20228 selectionEnd = Caret(this, result.element, result.offset); 20229 } 20230 20231 } 20232 } 20233 20234 20235 /// Call this if the inputs change. It will reflow everything 20236 void redoLayout(ScreenPainter painter) { 20237 //painter.setClipRectangle(boundingBox); 20238 auto pos = Point(boundingBox.left, boundingBox.top); 20239 20240 int lastHeight; 20241 void nl() { 20242 pos.x = boundingBox.left; 20243 pos.y += lastHeight; 20244 } 20245 foreach(block; blocks) { 20246 nl(); 20247 foreach(part; block.parts) { 20248 part.letterXs = null; 20249 20250 auto size = painter.textSize(part.text); 20251 version(Windows) 20252 if(part.text.length && part.text[$-1] == '\n') 20253 size.height /= 2; // windows counts the new line at the end, but we don't want that 20254 20255 part.boundingBox = Rectangle(pos.x, pos.y, pos.x + size.width, pos.y + size.height); 20256 20257 foreach(idx, char c; part.text) { 20258 // FIXME: unicode 20259 part.letterXs ~= painter.textSize(part.text[0 .. idx]).width + pos.x; 20260 } 20261 20262 pos.x += size.width; 20263 if(pos.x >= boundingBox.right) { 20264 pos.y += size.height; 20265 pos.x = boundingBox.left; 20266 lastHeight = 0; 20267 } else { 20268 lastHeight = size.height; 20269 } 20270 20271 if(part.text.length && part.text[$-1] == '\n') 20272 nl(); 20273 } 20274 } 20275 20276 layoutInvalidated = false; 20277 } 20278 20279 bool layoutInvalidated = true; 20280 void invalidateLayout() { 20281 layoutInvalidated = true; 20282 } 20283 20284 // FIXME: caret can remain sometimes when inserting 20285 // FIXME: inserting at the beginning once you already have something can eff it up. 20286 void drawInto(ScreenPainter painter, bool focused = false) { 20287 if(layoutInvalidated) 20288 redoLayout(painter); 20289 foreach(block; blocks) { 20290 foreach(part; block.parts) { 20291 painter.outlineColor = part.color; 20292 painter.fillColor = part.backgroundColor; 20293 20294 auto pos = part.boundingBox.upperLeft; 20295 auto size = part.boundingBox.size; 20296 20297 painter.drawText(pos, part.text); 20298 if(part.styles & TextFormat.underline) 20299 painter.drawLine(Point(pos.x, pos.y + size.height - 4), Point(pos.x + size.width, pos.y + size.height - 4)); 20300 if(part.styles & TextFormat.strikethrough) 20301 painter.drawLine(Point(pos.x, pos.y + size.height/2), Point(pos.x + size.width, pos.y + size.height/2)); 20302 } 20303 } 20304 20305 // on every redraw, I will force the caret to be 20306 // redrawn too, in order to eliminate perceived lag 20307 // when moving around with the mouse. 20308 eraseCaret(painter); 20309 20310 if(focused) { 20311 highlightSelection(painter); 20312 drawCaret(painter); 20313 } 20314 } 20315 20316 Color selectionXorColor = Color(255, 255, 127); 20317 20318 void highlightSelection(ScreenPainter painter) { 20319 if(selectionStart is selectionEnd) 20320 return; // no selection 20321 20322 if(selectionStart.inlineElement is null) return; 20323 if(selectionEnd.inlineElement is null) return; 20324 20325 assert(selectionStart.inlineElement !is null); 20326 assert(selectionEnd.inlineElement !is null); 20327 20328 painter.rasterOp = RasterOp.xor; 20329 painter.outlineColor = Color.transparent; 20330 painter.fillColor = selectionXorColor; 20331 20332 auto at = selectionStart.inlineElement; 20333 auto atOffset = selectionStart.offset; 20334 bool done; 20335 while(at) { 20336 auto box = at.boundingBox; 20337 if(atOffset < at.letterXs.length) 20338 box.left = at.letterXs[atOffset]; 20339 20340 if(at is selectionEnd.inlineElement) { 20341 if(selectionEnd.offset < at.letterXs.length) 20342 box.right = at.letterXs[selectionEnd.offset]; 20343 done = true; 20344 } 20345 20346 painter.drawRectangle(box.upperLeft, box.width, box.height); 20347 20348 if(done) 20349 break; 20350 20351 at = at.getNextInlineElement(); 20352 atOffset = 0; 20353 } 20354 } 20355 20356 int caretLastDrawnX, caretLastDrawnY1, caretLastDrawnY2; 20357 bool caretShowingOnScreen = false; 20358 void drawCaret(ScreenPainter painter) { 20359 //painter.setClipRectangle(boundingBox); 20360 int x, y1, y2; 20361 if(caret.inlineElement is null) { 20362 x = boundingBox.left; 20363 y1 = boundingBox.top + 2; 20364 y2 = boundingBox.top + painter.fontHeight; 20365 } else { 20366 x = caret.inlineElement.xOfIndex(caret.offset); 20367 y1 = caret.inlineElement.boundingBox.top + 2; 20368 y2 = caret.inlineElement.boundingBox.bottom - 2; 20369 } 20370 20371 if(caretShowingOnScreen && (x != caretLastDrawnX || y1 != caretLastDrawnY1 || y2 != caretLastDrawnY2)) 20372 eraseCaret(painter); 20373 20374 painter.pen = Pen(Color.white, 1); 20375 painter.rasterOp = RasterOp.xor; 20376 painter.drawLine( 20377 Point(x, y1), 20378 Point(x, y2) 20379 ); 20380 painter.rasterOp = RasterOp.normal; 20381 caretShowingOnScreen = !caretShowingOnScreen; 20382 20383 if(caretShowingOnScreen) { 20384 caretLastDrawnX = x; 20385 caretLastDrawnY1 = y1; 20386 caretLastDrawnY2 = y2; 20387 } 20388 } 20389 20390 Rectangle caretBoundingBox() { 20391 int x, y1, y2; 20392 if(caret.inlineElement is null) { 20393 x = boundingBox.left; 20394 y1 = boundingBox.top + 2; 20395 y2 = boundingBox.top + 16; 20396 } else { 20397 x = caret.inlineElement.xOfIndex(caret.offset); 20398 y1 = caret.inlineElement.boundingBox.top + 2; 20399 y2 = caret.inlineElement.boundingBox.bottom - 2; 20400 } 20401 20402 return Rectangle(x, y1, x + 1, y2); 20403 } 20404 20405 void eraseCaret(ScreenPainter painter) { 20406 //painter.setClipRectangle(boundingBox); 20407 if(!caretShowingOnScreen) return; 20408 painter.pen = Pen(Color.white, 1); 20409 painter.rasterOp = RasterOp.xor; 20410 painter.drawLine( 20411 Point(caretLastDrawnX, caretLastDrawnY1), 20412 Point(caretLastDrawnX, caretLastDrawnY2) 20413 ); 20414 20415 caretShowingOnScreen = false; 20416 painter.rasterOp = RasterOp.normal; 20417 } 20418 20419 /// Caret movement api 20420 /// These should give the user a logical result based on what they see on screen... 20421 /// thus they locate predominately by *pixels* not char index. (These will generally coincide with monospace fonts tho!) 20422 void moveUp() { 20423 if(caret.inlineElement is null) return; 20424 auto x = caret.inlineElement.xOfIndex(caret.offset); 20425 auto y = caret.inlineElement.boundingBox.top + 2; 20426 20427 y -= caret.inlineElement.boundingBox.bottom - caret.inlineElement.boundingBox.top; 20428 if(y < 0) 20429 return; 20430 20431 auto i = identify(x, y); 20432 20433 if(i.element) { 20434 caret.inlineElement = i.element; 20435 caret.offset = i.offset; 20436 } 20437 } 20438 void moveDown() { 20439 if(caret.inlineElement is null) return; 20440 auto x = caret.inlineElement.xOfIndex(caret.offset); 20441 auto y = caret.inlineElement.boundingBox.bottom - 2; 20442 20443 y += caret.inlineElement.boundingBox.bottom - caret.inlineElement.boundingBox.top; 20444 20445 auto i = identify(x, y); 20446 if(i.element) { 20447 caret.inlineElement = i.element; 20448 caret.offset = i.offset; 20449 } 20450 } 20451 void moveLeft() { 20452 if(caret.inlineElement is null) return; 20453 if(caret.offset) 20454 caret.offset--; 20455 else { 20456 auto p = caret.inlineElement.getPreviousInlineElement(); 20457 if(p) { 20458 caret.inlineElement = p; 20459 if(p.text.length && p.text[$-1] == '\n') 20460 caret.offset = cast(int) p.text.length - 1; 20461 else 20462 caret.offset = cast(int) p.text.length; 20463 } 20464 } 20465 } 20466 void moveRight() { 20467 if(caret.inlineElement is null) return; 20468 if(caret.offset < caret.inlineElement.text.length && caret.inlineElement.text[caret.offset] != '\n') { 20469 caret.offset++; 20470 } else { 20471 auto p = caret.inlineElement.getNextInlineElement(); 20472 if(p) { 20473 caret.inlineElement = p; 20474 caret.offset = 0; 20475 } 20476 } 20477 } 20478 void moveHome() { 20479 if(caret.inlineElement is null) return; 20480 auto x = 0; 20481 auto y = caret.inlineElement.boundingBox.top + 2; 20482 20483 auto i = identify(x, y); 20484 20485 if(i.element) { 20486 caret.inlineElement = i.element; 20487 caret.offset = i.offset; 20488 } 20489 } 20490 void moveEnd() { 20491 if(caret.inlineElement is null) return; 20492 auto x = int.max; 20493 auto y = caret.inlineElement.boundingBox.top + 2; 20494 20495 auto i = identify(x, y); 20496 20497 if(i.element) { 20498 caret.inlineElement = i.element; 20499 caret.offset = i.offset; 20500 } 20501 20502 } 20503 void movePageUp(ref Caret caret) {} 20504 void movePageDown(ref Caret caret) {} 20505 20506 void moveDocumentStart(ref Caret caret) { 20507 if(blocks.length && blocks[0].parts.length) 20508 caret = Caret(this, blocks[0].parts[0], 0); 20509 else 20510 caret = Caret.init; 20511 } 20512 20513 void moveDocumentEnd(ref Caret caret) { 20514 if(blocks.length) { 20515 auto parts = blocks[$-1].parts; 20516 if(parts.length) { 20517 caret = Caret(this, parts[$-1], cast(int) parts[$-1].text.length); 20518 } else { 20519 caret = Caret.init; 20520 } 20521 } else 20522 caret = Caret.init; 20523 } 20524 20525 void deleteSelection() { 20526 if(selectionStart is selectionEnd) 20527 return; 20528 20529 if(selectionStart.inlineElement is null) return; 20530 if(selectionEnd.inlineElement is null) return; 20531 20532 assert(selectionStart.inlineElement !is null); 20533 assert(selectionEnd.inlineElement !is null); 20534 20535 auto at = selectionStart.inlineElement; 20536 20537 if(selectionEnd.inlineElement is at) { 20538 // same element, need to chop out 20539 at.text = at.text[0 .. selectionStart.offset] ~ at.text[selectionEnd.offset .. $]; 20540 at.letterXs = at.letterXs[0 .. selectionStart.offset] ~ at.letterXs[selectionEnd.offset .. $]; 20541 selectionEnd.offset -= selectionEnd.offset - selectionStart.offset; 20542 } else { 20543 // different elements, we can do it with slicing 20544 at.text = at.text[0 .. selectionStart.offset]; 20545 if(selectionStart.offset < at.letterXs.length) 20546 at.letterXs = at.letterXs[0 .. selectionStart.offset]; 20547 20548 at = at.getNextInlineElement(); 20549 20550 while(at) { 20551 if(at is selectionEnd.inlineElement) { 20552 at.text = at.text[selectionEnd.offset .. $]; 20553 if(selectionEnd.offset < at.letterXs.length) 20554 at.letterXs = at.letterXs[selectionEnd.offset .. $]; 20555 selectionEnd.offset = 0; 20556 break; 20557 } else { 20558 auto cfd = at; 20559 cfd.text = null; // delete the whole thing 20560 20561 at = at.getNextInlineElement(); 20562 20563 if(cfd.text.length == 0) { 20564 // and remove cfd 20565 for(size_t a = 0; a < cfd.containingBlock.parts.length; a++) { 20566 if(cfd.containingBlock.parts[a] is cfd) { 20567 for(size_t i = a; i < cfd.containingBlock.parts.length - 1; i++) 20568 cfd.containingBlock.parts[i] = cfd.containingBlock.parts[i + 1]; 20569 cfd.containingBlock.parts = cfd.containingBlock.parts[0 .. $-1]; 20570 20571 } 20572 } 20573 } 20574 } 20575 } 20576 } 20577 20578 caret = selectionEnd; 20579 selectNone(); 20580 20581 invalidateLayout(); 20582 20583 } 20584 20585 /// Plain text editing api. These work at the current caret inside the selected inline element. 20586 void insert(in char[] text) { 20587 foreach(dchar ch; text) 20588 insert(ch); 20589 } 20590 /// ditto 20591 void insert(dchar ch) { 20592 20593 bool selectionDeleted = false; 20594 if(selectionStart !is selectionEnd) { 20595 deleteSelection(); 20596 selectionDeleted = true; 20597 } 20598 20599 if(ch == 127) { 20600 delete_(); 20601 return; 20602 } 20603 if(ch == 8) { 20604 if(!selectionDeleted) 20605 backspace(); 20606 return; 20607 } 20608 20609 invalidateLayout(); 20610 20611 if(ch == 13) ch = 10; 20612 auto e = caret.inlineElement; 20613 if(e is null) { 20614 addText("" ~ cast(char) ch) ; // FIXME 20615 return; 20616 } 20617 20618 if(caret.offset == e.text.length) { 20619 e.text ~= cast(char) ch; // FIXME 20620 caret.offset++; 20621 if(ch == 10) { 20622 auto c = caret.inlineElement.clone; 20623 c.text = null; 20624 c.letterXs = null; 20625 insertPartAfter(c,e); 20626 caret = Caret(this, c, 0); 20627 } 20628 } else { 20629 // FIXME cast char sucks 20630 if(ch == 10) { 20631 auto c = caret.inlineElement.clone; 20632 c.text = e.text[caret.offset .. $]; 20633 if(caret.offset < c.letterXs.length) 20634 c.letterXs = e.letterXs[caret.offset .. $]; // FIXME boundingBox 20635 e.text = e.text[0 .. caret.offset] ~ cast(char) ch; 20636 if(caret.offset <= e.letterXs.length) { 20637 e.letterXs = e.letterXs[0 .. caret.offset] ~ 0; // FIXME bounding box 20638 } 20639 insertPartAfter(c,e); 20640 caret = Caret(this, c, 0); 20641 } else { 20642 e.text = e.text[0 .. caret.offset] ~ cast(char) ch ~ e.text[caret.offset .. $]; 20643 caret.offset++; 20644 } 20645 } 20646 } 20647 20648 void insertPartAfter(InlineElement what, InlineElement where) { 20649 foreach(idx, p; where.containingBlock.parts) { 20650 if(p is where) { 20651 if(idx + 1 == where.containingBlock.parts.length) 20652 where.containingBlock.parts ~= what; 20653 else 20654 where.containingBlock.parts = where.containingBlock.parts[0 .. idx + 1] ~ what ~ where.containingBlock.parts[idx + 1 .. $]; 20655 return; 20656 } 20657 } 20658 } 20659 20660 void cleanupStructures() { 20661 for(size_t i = 0; i < blocks.length; i++) { 20662 auto block = blocks[i]; 20663 for(size_t a = 0; a < block.parts.length; a++) { 20664 auto part = block.parts[a]; 20665 if(part.text.length == 0) { 20666 for(size_t b = a; b < block.parts.length - 1; b++) 20667 block.parts[b] = block.parts[b+1]; 20668 block.parts = block.parts[0 .. $-1]; 20669 } 20670 } 20671 if(block.parts.length == 0) { 20672 for(size_t a = i; a < blocks.length - 1; a++) 20673 blocks[a] = blocks[a+1]; 20674 blocks = blocks[0 .. $-1]; 20675 } 20676 } 20677 } 20678 20679 void backspace() { 20680 try_again: 20681 auto e = caret.inlineElement; 20682 if(e is null) 20683 return; 20684 if(caret.offset == 0) { 20685 auto prev = e.getPreviousInlineElement(); 20686 if(prev is null) 20687 return; 20688 auto newOffset = cast(int) prev.text.length; 20689 tryMerge(prev, e); 20690 caret.inlineElement = prev; 20691 caret.offset = prev is null ? 0 : newOffset; 20692 20693 goto try_again; 20694 } else if(caret.offset == e.text.length) { 20695 e.text = e.text[0 .. $-1]; 20696 caret.offset--; 20697 } else { 20698 e.text = e.text[0 .. caret.offset - 1] ~ e.text[caret.offset .. $]; 20699 caret.offset--; 20700 } 20701 //cleanupStructures(); 20702 20703 invalidateLayout(); 20704 } 20705 void delete_() { 20706 if(selectionStart !is selectionEnd) 20707 deleteSelection(); 20708 else { 20709 auto before = caret; 20710 moveRight(); 20711 if(caret != before) { 20712 backspace(); 20713 } 20714 } 20715 20716 invalidateLayout(); 20717 } 20718 void overstrike() {} 20719 20720 /// Selection API. See also: caret movement. 20721 void selectAll() { 20722 moveDocumentStart(selectionStart); 20723 moveDocumentEnd(selectionEnd); 20724 } 20725 bool selectNone() { 20726 if(selectionStart != selectionEnd) { 20727 selectionStart = selectionEnd = Caret.init; 20728 return true; 20729 } 20730 return false; 20731 } 20732 20733 /// Rich text editing api. These allow you to manipulate the meta data of the current element and add new elements. 20734 /// They will modify the current selection if there is one and will splice one in if needed. 20735 void changeAttributes() {} 20736 20737 20738 /// Text search api. They manipulate the selection and/or caret. 20739 void findText(string text) {} 20740 void findIndex(size_t textIndex) {} 20741 20742 // sample event handlers 20743 20744 void handleEvent(KeyEvent event) { 20745 //if(event.type == KeyEvent.Type.KeyPressed) { 20746 20747 //} 20748 } 20749 20750 void handleEvent(dchar ch) { 20751 20752 } 20753 20754 void handleEvent(MouseEvent event) { 20755 20756 } 20757 20758 bool contentEditable; // can it be edited? 20759 bool contentCaretable; // is there a caret/cursor that moves around in there? 20760 bool contentSelectable; // selectable? 20761 20762 Caret caret; 20763 Caret selectionStart; 20764 Caret selectionEnd; 20765 20766 bool insertMode; 20767 } 20768 20769 struct Caret { 20770 TextLayout layout; 20771 InlineElement inlineElement; 20772 int offset; 20773 } 20774 20775 enum TextFormat : ushort { 20776 // decorations 20777 underline = 1, 20778 strikethrough = 2, 20779 20780 // font selectors 20781 20782 bold = 0x4000 | 1, // weight 700 20783 light = 0x4000 | 2, // weight 300 20784 veryBoldOrLight = 0x4000 | 4, // weight 100 with light, weight 900 with bold 20785 // bold | light is really invalid but should give weight 500 20786 // veryBoldOrLight without one of the others should just give the default for the font; it should be ignored. 20787 20788 italic = 0x4000 | 8, 20789 smallcaps = 0x4000 | 16, 20790 } 20791 20792 void* findFont(string family, int weight, TextFormat formats) { 20793 return null; 20794 } 20795 20796 } 20797 20798 /++ 20799 $(PITFALL This is not yet stable and may break in future versions without notice.) 20800 20801 History: 20802 Added February 19, 2021 20803 +/ 20804 /// Group: drag_and_drop 20805 interface DropHandler { 20806 /++ 20807 Called when the drag enters the handler's area. 20808 +/ 20809 DragAndDropAction dragEnter(DropPackage*); 20810 /++ 20811 Called when the drag leaves the handler's area or is 20812 cancelled. You should free your resources when this is called. 20813 +/ 20814 void dragLeave(); 20815 /++ 20816 Called continually as the drag moves over the handler's area. 20817 20818 Returns: feedback to the dragger 20819 +/ 20820 DropParameters dragOver(Point pt); 20821 /++ 20822 The user dropped the data and you should process it now. You can 20823 access the data through the given [DropPackage]. 20824 +/ 20825 void drop(scope DropPackage*); 20826 /++ 20827 Called when the drop is complete. You should free whatever temporary 20828 resources you were using. It is often reasonable to simply forward 20829 this call to [dragLeave]. 20830 +/ 20831 void finish(); 20832 20833 /++ 20834 Parameters returned by [DropHandler.drop]. 20835 +/ 20836 static struct DropParameters { 20837 /++ 20838 Acceptable action over this area. 20839 +/ 20840 DragAndDropAction action; 20841 /++ 20842 Rectangle, in client coordinates, where the dragger can expect the same result during this drag session and thus need not ask again. 20843 20844 If you leave this as Rectangle.init, the dragger will continue to ask and this can waste resources. 20845 +/ 20846 Rectangle consistentWithin; 20847 } 20848 } 20849 20850 /++ 20851 History: 20852 Added February 19, 2021 20853 +/ 20854 /// Group: drag_and_drop 20855 enum DragAndDropAction { 20856 none = 0, 20857 copy, 20858 move, 20859 link, 20860 ask, 20861 custom 20862 } 20863 20864 /++ 20865 An opaque structure representing dropped data. It contains 20866 private, platform-specific data that your `drop` function 20867 should simply forward. 20868 20869 $(PITFALL This is not yet stable and may break in future versions without notice.) 20870 20871 History: 20872 Added February 19, 2021 20873 +/ 20874 /// Group: drag_and_drop 20875 struct DropPackage { 20876 /++ 20877 Lists the available formats as magic numbers. You should compare these 20878 against looked-up formats (see [DraggableData.getFormatId]) you know you support and can 20879 understand the passed data. 20880 +/ 20881 DraggableData.FormatId[] availableFormats() { 20882 version(X11) { 20883 return xFormats; 20884 } else version(Windows) { 20885 if(pDataObj is null) 20886 return null; 20887 20888 typeof(return) ret; 20889 20890 IEnumFORMATETC ef; 20891 if(pDataObj.EnumFormatEtc(DATADIR.DATADIR_GET, &ef) == S_OK) { 20892 FORMATETC fmt; 20893 ULONG fetched; 20894 while(ef.Next(1, &fmt, &fetched) == S_OK) { 20895 if(fetched == 0) 20896 break; 20897 20898 if(fmt.lindex != -1) 20899 continue; 20900 if(fmt.dwAspect != DVASPECT.DVASPECT_CONTENT) 20901 continue; 20902 if(!(fmt.tymed & TYMED.TYMED_HGLOBAL)) 20903 continue; 20904 20905 ret ~= fmt.cfFormat; 20906 } 20907 } 20908 20909 return ret; 20910 } else throw new NotYetImplementedException(); 20911 } 20912 20913 /++ 20914 Gets data from the drop and optionally accepts it. 20915 20916 Returns: 20917 void because the data is fed asynchronously through the `dg` parameter. 20918 20919 Params: 20920 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. 20921 20922 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. 20923 20924 Calling `getData` again after accepting a drop is not permitted. 20925 20926 format = the format you want, from [availableFormats]. Use [DraggableData.getFormatId] to convert from a MIME string or well-known standard format. 20927 20928 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. 20929 20930 Throws: 20931 if `format` was not compatible with the [availableFormats] or if the drop has already been accepted. 20932 20933 History: 20934 Included in first release of [DropPackage]. 20935 +/ 20936 void getData(DragAndDropAction acceptedAction, DraggableData.FormatId format, void delegate(scope ubyte[] data) dg) { 20937 version(X11) { 20938 20939 auto display = XDisplayConnection.get(); 20940 auto selectionAtom = GetAtom!"XdndSelection"(display); 20941 auto best = format; 20942 20943 static class X11GetSelectionHandler_Drop : X11GetSelectionHandler { 20944 20945 XDisplay* display; 20946 Atom selectionAtom; 20947 DraggableData.FormatId best; 20948 DraggableData.FormatId format; 20949 void delegate(scope ubyte[] data) dg; 20950 DragAndDropAction acceptedAction; 20951 Window sourceWindow; 20952 SimpleWindow win; 20953 this(XDisplay* display, SimpleWindow win, Window sourceWindow, DraggableData.FormatId format, Atom selectionAtom, DraggableData.FormatId best, void delegate(scope ubyte[] data) dg, DragAndDropAction acceptedAction) { 20954 this.display = display; 20955 this.win = win; 20956 this.sourceWindow = sourceWindow; 20957 this.format = format; 20958 this.selectionAtom = selectionAtom; 20959 this.best = best; 20960 this.dg = dg; 20961 this.acceptedAction = acceptedAction; 20962 } 20963 20964 20965 mixin X11GetSelectionHandler_Basics; 20966 20967 void handleData(Atom target, in ubyte[] data) { 20968 //if(target == GetAtom!"UTF8_STRING"(XDisplayConnection.get) || target == XA_STRING || target == GetAtom!"text/plain"(XDisplayConnection.get)) 20969 20970 dg(cast(ubyte[]) data); 20971 20972 if(acceptedAction != DragAndDropAction.none) { 20973 auto display = XDisplayConnection.get; 20974 20975 XClientMessageEvent xclient; 20976 20977 xclient.type = EventType.ClientMessage; 20978 xclient.window = sourceWindow; 20979 xclient.message_type = GetAtom!"XdndFinished"(display); 20980 xclient.format = 32; 20981 xclient.data.l[0] = win.impl.window; 20982 xclient.data.l[1] = 1; // drop successful 20983 xclient.data.l[2] = dndActionAtom(display, acceptedAction); 20984 20985 XSendEvent( 20986 display, 20987 sourceWindow, 20988 false, 20989 EventMask.NoEventMask, 20990 cast(XEvent*) &xclient 20991 ); 20992 20993 XFlush(display); 20994 } 20995 } 20996 20997 Atom findBestFormat(Atom[] answer) { 20998 Atom best = None; 20999 foreach(option; answer) { 21000 if(option == format) { 21001 best = option; 21002 break; 21003 } 21004 /* 21005 if(option == GetAtom!"UTF8_STRING"(XDisplayConnection.get)) { 21006 best = option; 21007 break; 21008 } else if(option == XA_STRING) { 21009 best = option; 21010 } else if(option == GetAtom!"text/plain"(XDisplayConnection.get)) { 21011 best = option; 21012 } 21013 */ 21014 } 21015 return best; 21016 } 21017 } 21018 21019 win.impl.getSelectionHandlers[selectionAtom] = new X11GetSelectionHandler_Drop(display, win, sourceWindow, format, selectionAtom, best, dg, acceptedAction); 21020 21021 XConvertSelection(display, selectionAtom, best, GetAtom!("SDD_DATA", true)(display), win.impl.window, dataTimestamp); 21022 21023 } else version(Windows) { 21024 21025 // clean up like DragLeave 21026 // pass effect back up 21027 21028 FORMATETC t; 21029 assert(format >= 0 && format <= ushort.max); 21030 t.cfFormat = cast(ushort) format; 21031 t.lindex = -1; 21032 t.dwAspect = DVASPECT.DVASPECT_CONTENT; 21033 t.tymed = TYMED.TYMED_HGLOBAL; 21034 21035 STGMEDIUM m; 21036 21037 if(pDataObj.GetData(&t, &m) != S_OK) { 21038 // fail 21039 } else { 21040 // succeed, take the data and clean up 21041 21042 // FIXME: ensure it is legit HGLOBAL 21043 auto handle = m.hGlobal; 21044 21045 if(handle) { 21046 auto sz = GlobalSize(handle); 21047 if(auto ptr = cast(ubyte*) GlobalLock(handle)) { 21048 scope(exit) GlobalUnlock(handle); 21049 scope(exit) GlobalFree(handle); 21050 21051 auto data = ptr[0 .. sz]; 21052 21053 dg(data); 21054 } 21055 } 21056 } 21057 } 21058 } 21059 21060 private: 21061 21062 version(X11) { 21063 SimpleWindow win; 21064 Window sourceWindow; 21065 Time dataTimestamp; 21066 21067 Atom[] xFormats; 21068 } 21069 version(Windows) { 21070 IDataObject pDataObj; 21071 } 21072 } 21073 21074 /++ 21075 A generic helper base class for making a drop handler with a preference list of custom types. 21076 This is the base for [TextDropHandler] and [FilesDropHandler] and you can use it for your own 21077 droppers too. 21078 21079 It assumes the whole window it used, but you can subclass to change that. 21080 21081 $(PITFALL This is not yet stable and may break in future versions without notice.) 21082 21083 History: 21084 Added February 19, 2021 21085 +/ 21086 /// Group: drag_and_drop 21087 class GenericDropHandlerBase : DropHandler { 21088 // no fancy state here so no need to do anything here 21089 void finish() { } 21090 void dragLeave() { } 21091 21092 private DragAndDropAction acceptedAction; 21093 private DraggableData.FormatId acceptedFormat; 21094 private void delegate(scope ubyte[]) acceptedHandler; 21095 21096 struct FormatHandler { 21097 DraggableData.FormatId format; 21098 void delegate(scope ubyte[]) handler; 21099 } 21100 21101 protected abstract FormatHandler[] formatHandlers(); 21102 21103 DragAndDropAction dragEnter(DropPackage* pkg) { 21104 debug(sdpy_dnd) { foreach(fmt; pkg.availableFormats()) writeln(fmt, " ", DraggableData.getFormatName(fmt)); } 21105 foreach(fmt; formatHandlers()) 21106 foreach(f; pkg.availableFormats()) 21107 if(f == fmt.format) { 21108 acceptedFormat = f; 21109 acceptedHandler = fmt.handler; 21110 return acceptedAction = DragAndDropAction.copy; 21111 } 21112 return acceptedAction = DragAndDropAction.none; 21113 } 21114 DropParameters dragOver(Point pt) { 21115 return DropParameters(acceptedAction); 21116 } 21117 21118 void drop(scope DropPackage* dropPackage) { 21119 if(!acceptedFormat || acceptedHandler is null) { 21120 debug(sdpy_dnd) { writeln("drop called w/ handler ", acceptedHandler, " and format ", acceptedFormat); } 21121 return; // prolly shouldn't happen anyway... 21122 } 21123 21124 dropPackage.getData(acceptedAction, acceptedFormat, acceptedHandler); 21125 } 21126 } 21127 21128 /++ 21129 A simple handler for making your window accept drops of plain text. 21130 21131 $(PITFALL This is not yet stable and may break in future versions without notice.) 21132 21133 History: 21134 Added February 22, 2021 21135 +/ 21136 /// Group: drag_and_drop 21137 class TextDropHandler : GenericDropHandlerBase { 21138 private void delegate(in char[] text) dg; 21139 21140 /++ 21141 21142 +/ 21143 this(void delegate(in char[] text) dg) { 21144 this.dg = dg; 21145 } 21146 21147 protected override FormatHandler[] formatHandlers() { 21148 version(X11) 21149 return [ 21150 FormatHandler(GetAtom!"UTF8_STRING"(XDisplayConnection.get), &translator), 21151 FormatHandler(GetAtom!"text/plain;charset=utf-8"(XDisplayConnection.get), &translator), 21152 ]; 21153 else version(Windows) 21154 return [ 21155 FormatHandler(CF_UNICODETEXT, &translator), 21156 ]; 21157 else throw new NotYetImplementedException(); 21158 } 21159 21160 private void translator(scope ubyte[] data) { 21161 version(X11) 21162 dg(cast(char[]) data); 21163 else version(Windows) 21164 dg(makeUtf8StringFromWindowsString(cast(wchar[]) data)); 21165 } 21166 } 21167 21168 /++ 21169 A simple handler for making your window accept drops of files, issued to you as file names. 21170 21171 $(PITFALL This is not yet stable and may break in future versions without notice.) 21172 21173 History: 21174 Added February 22, 2021 21175 +/ 21176 /// Group: drag_and_drop 21177 21178 class FilesDropHandler : GenericDropHandlerBase { 21179 private void delegate(in char[][]) dg; 21180 21181 /++ 21182 21183 +/ 21184 this(void delegate(in char[][] fileNames) dg) { 21185 this.dg = dg; 21186 } 21187 21188 protected override FormatHandler[] formatHandlers() { 21189 version(X11) 21190 return [ 21191 FormatHandler(GetAtom!"text/uri-list"(XDisplayConnection.get), &translator), 21192 ]; 21193 else version(Windows) 21194 return [ 21195 FormatHandler(CF_HDROP, &translator), 21196 ]; 21197 else throw new NotYetImplementedException(); 21198 } 21199 21200 private void translator(scope ubyte[] data) { 21201 version(X11) { 21202 char[] listString = cast(char[]) data; 21203 char[][16] buffer; 21204 int count; 21205 char[][] result = buffer[]; 21206 21207 void commit(char[] s) { 21208 if(count == result.length) 21209 result.length += 16; 21210 if(s.length > 7 && s[0 ..7] == "file://") 21211 s = s[7 .. $]; // FIXME: also may need to trim out the host and do some entity decoding 21212 result[count++] = s; 21213 } 21214 21215 size_t last; 21216 foreach(idx, char c; listString) { 21217 if(c == '\n') { 21218 commit(listString[last .. idx - 1]); // a \r 21219 last = idx + 1; // a \n 21220 } 21221 } 21222 21223 if(last < listString.length) { 21224 commit(listString[last .. $]); 21225 } 21226 21227 // FIXME: they are uris now, should I translate it to local file names? 21228 // of course the host name is supposed to be there cuz of X rokking... 21229 21230 dg(result[0 .. count]); 21231 } else version(Windows) { 21232 21233 static struct DROPFILES { 21234 DWORD pFiles; 21235 POINT pt; 21236 BOOL fNC; 21237 BOOL fWide; 21238 } 21239 21240 21241 const(char)[][16] buffer; 21242 int count; 21243 const(char)[][] result = buffer[]; 21244 size_t last; 21245 21246 void commitA(in char[] stuff) { 21247 if(count == result.length) 21248 result.length += 16; 21249 result[count++] = stuff; 21250 } 21251 21252 void commitW(in wchar[] stuff) { 21253 commitA(makeUtf8StringFromWindowsString(stuff)); 21254 } 21255 21256 void magic(T)(T chars) { 21257 size_t idx; 21258 while(chars[idx]) { 21259 last = idx; 21260 while(chars[idx]) { 21261 idx++; 21262 } 21263 static if(is(T == char*)) 21264 commitA(chars[last .. idx]); 21265 else 21266 commitW(chars[last .. idx]); 21267 idx++; 21268 } 21269 } 21270 21271 auto df = cast(DROPFILES*) data.ptr; 21272 if(df.fWide) { 21273 wchar* chars = cast(wchar*) (data.ptr + df.pFiles); 21274 magic(chars); 21275 } else { 21276 char* chars = cast(char*) (data.ptr + df.pFiles); 21277 magic(chars); 21278 } 21279 dg(result[0 .. count]); 21280 } 21281 else throw new NotYetImplementedException(); 21282 } 21283 } 21284 21285 /++ 21286 Interface to describe data being dragged. See also [draggable] helper function. 21287 21288 $(PITFALL This is not yet stable and may break in future versions without notice.) 21289 21290 History: 21291 Added February 19, 2021 21292 +/ 21293 interface DraggableData { 21294 version(X11) 21295 alias FormatId = Atom; 21296 else 21297 alias FormatId = uint; 21298 /++ 21299 Gets the platform-specific FormatId associated with the given named format. 21300 21301 This may be a MIME type, but may also be other various strings defined by the 21302 programs you want to interoperate with. 21303 21304 FIXME: sdpy needs to offer data adapter things that look for compatible formats 21305 and convert it to some particular type for you. 21306 +/ 21307 static FormatId getFormatId(string name)() { 21308 version(X11) 21309 return GetAtom!name(XDisplayConnection.get); 21310 else version(Windows) { 21311 static UINT cache; 21312 if(!cache) 21313 cache = RegisterClipboardFormatA(name); 21314 return cache; 21315 } else 21316 throw new NotYetImplementedException(); 21317 } 21318 21319 /++ 21320 Looks up a string to represent the name for the given format, if there is one. 21321 21322 You should avoid using this function because it is slow. It is provided more for 21323 debugging than for primary use. 21324 +/ 21325 static string getFormatName(FormatId format) { 21326 version(X11) { 21327 if(format == 0) 21328 return "None"; 21329 else 21330 return getAtomName(format, XDisplayConnection.get); 21331 } else version(Windows) { 21332 switch(format) { 21333 case CF_UNICODETEXT: return "CF_UNICODETEXT"; 21334 case CF_DIBV5: return "CF_DIBV5"; 21335 case CF_RIFF: return "CF_RIFF"; 21336 case CF_WAVE: return "CF_WAVE"; 21337 case CF_HDROP: return "CF_HDROP"; 21338 default: 21339 char[1024] name; 21340 auto count = GetClipboardFormatNameA(format, name.ptr, name.length); 21341 return name[0 .. count].idup; 21342 } 21343 } else throw new NotYetImplementedException(); 21344 } 21345 21346 FormatId[] availableFormats(); 21347 // Return the slice of data you filled, empty slice if done. 21348 // this is to support the incremental thing 21349 ubyte[] getData(FormatId format, return scope ubyte[] data); 21350 21351 size_t dataLength(FormatId format); 21352 } 21353 21354 /++ 21355 $(PITFALL This is not yet stable and may break in future versions without notice.) 21356 21357 History: 21358 Added February 19, 2021 21359 +/ 21360 DraggableData draggable(string s) { 21361 version(X11) 21362 return new class X11SetSelectionHandler_Text, DraggableData { 21363 this() { 21364 super(s); 21365 } 21366 21367 override FormatId[] availableFormats() { 21368 return X11SetSelectionHandler_Text.availableFormats(); 21369 } 21370 21371 override ubyte[] getData(FormatId format, return scope ubyte[] data) { 21372 return X11SetSelectionHandler_Text.getData(format, data); 21373 } 21374 21375 size_t dataLength(FormatId format) { 21376 return s.length; 21377 } 21378 }; 21379 else version(Windows) 21380 return new class DraggableData { 21381 FormatId[] availableFormats() { 21382 return [CF_UNICODETEXT]; 21383 } 21384 21385 ubyte[] getData(FormatId format, return scope ubyte[] data) { 21386 return cast(ubyte[]) makeWindowsString(s, cast(wchar[]) data, WindowsStringConversionFlags.convertNewLines | WindowsStringConversionFlags.zeroTerminate); 21387 } 21388 21389 size_t dataLength(FormatId format) { 21390 return sizeOfConvertedWstring(s, WindowsStringConversionFlags.convertNewLines | WindowsStringConversionFlags.zeroTerminate) * wchar.sizeof; 21391 } 21392 }; 21393 else 21394 throw new NotYetImplementedException(); 21395 } 21396 21397 /++ 21398 $(PITFALL This is not yet stable and may break in future versions without notice.) 21399 21400 History: 21401 Added February 19, 2021 21402 +/ 21403 /// Group: drag_and_drop 21404 int doDragDrop(SimpleWindow window, DraggableData handler, DragAndDropAction action = DragAndDropAction.copy) 21405 in { 21406 assert(window !is null); 21407 assert(handler !is null); 21408 } 21409 do 21410 { 21411 version(X11) { 21412 auto sh = cast(X11SetSelectionHandler) handler; 21413 if(sh is null) { 21414 // gotta make my own adapter. 21415 sh = new class X11SetSelectionHandler { 21416 mixin X11SetSelectionHandler_Basics; 21417 21418 Atom[] availableFormats() { return handler.availableFormats(); } 21419 ubyte[] getData(Atom format, return scope ubyte[] data) { 21420 return handler.getData(format, data); 21421 } 21422 21423 // since the drop selection is only ever used once it isn't important 21424 // to reset it. 21425 void done() {} 21426 }; 21427 } 21428 return doDragDropX11(window, sh, action); 21429 } else version(Windows) { 21430 return doDragDropWindows(window, handler, action); 21431 } else throw new NotYetImplementedException(); 21432 } 21433 21434 version(Windows) 21435 private int doDragDropWindows(SimpleWindow window, DraggableData handler, DragAndDropAction action = DragAndDropAction.copy) { 21436 IDataObject obj = new class IDataObject { 21437 ULONG refCount; 21438 ULONG AddRef() { 21439 return ++refCount; 21440 } 21441 ULONG Release() { 21442 return --refCount; 21443 } 21444 HRESULT QueryInterface(const (IID)*riid, LPVOID *ppv) { 21445 if (IID_IUnknown == *riid) { 21446 *ppv = cast(void*) cast(IUnknown) this; 21447 } 21448 else if (IID_IDataObject == *riid) { 21449 *ppv = cast(void*) cast(IDataObject) this; 21450 } 21451 else { 21452 *ppv = null; 21453 return E_NOINTERFACE; 21454 } 21455 21456 AddRef(); 21457 return NOERROR; 21458 } 21459 21460 HRESULT DAdvise(FORMATETC* pformatetc, DWORD advf, IAdviseSink pAdvSink, DWORD* pdwConnection) { 21461 // writeln("Advise"); 21462 return E_NOTIMPL; 21463 } 21464 HRESULT DUnadvise(DWORD dwConnection) { 21465 return E_NOTIMPL; 21466 } 21467 HRESULT EnumDAdvise(IEnumSTATDATA* ppenumAdvise) { 21468 // writeln("EnumDAdvise"); 21469 return OLE_E_ADVISENOTSUPPORTED; 21470 } 21471 // tell what formats it supports 21472 21473 FORMATETC[] types; 21474 this() { 21475 FORMATETC t; 21476 foreach(ty; handler.availableFormats()) { 21477 assert(ty <= ushort.max && ty >= 0); 21478 t.cfFormat = cast(ushort) ty; 21479 t.lindex = -1; 21480 t.dwAspect = DVASPECT.DVASPECT_CONTENT; 21481 t.tymed = TYMED.TYMED_HGLOBAL; 21482 } 21483 types ~= t; 21484 } 21485 HRESULT EnumFormatEtc(DWORD dwDirection, IEnumFORMATETC* ppenumFormatEtc) { 21486 if(dwDirection == DATADIR.DATADIR_GET) { 21487 *ppenumFormatEtc = new class IEnumFORMATETC { 21488 ULONG refCount; 21489 ULONG AddRef() { 21490 return ++refCount; 21491 } 21492 ULONG Release() { 21493 return --refCount; 21494 } 21495 HRESULT QueryInterface(const (IID)*riid, LPVOID *ppv) { 21496 if (IID_IUnknown == *riid) { 21497 *ppv = cast(void*) cast(IUnknown) this; 21498 } 21499 else if (IID_IEnumFORMATETC == *riid) { 21500 *ppv = cast(void*) cast(IEnumFORMATETC) this; 21501 } 21502 else { 21503 *ppv = null; 21504 return E_NOINTERFACE; 21505 } 21506 21507 AddRef(); 21508 return NOERROR; 21509 } 21510 21511 21512 int pos; 21513 this() { 21514 pos = 0; 21515 } 21516 21517 HRESULT Clone(IEnumFORMATETC* ppenum) { 21518 // writeln("clone"); 21519 return E_NOTIMPL; // FIXME 21520 } 21521 21522 // Caller is responsible for freeing memory 21523 HRESULT Next(ULONG celt, FORMATETC* rgelt, ULONG* pceltFetched) { 21524 // fetched may be null if celt is one 21525 if(celt != 1) 21526 return E_NOTIMPL; // FIXME 21527 21528 if(celt + pos > types.length) 21529 return S_FALSE; 21530 21531 *rgelt = types[pos++]; 21532 21533 if(pceltFetched !is null) 21534 *pceltFetched = 1; 21535 21536 // writeln("ok celt ", celt); 21537 return S_OK; 21538 } 21539 21540 HRESULT Reset() { 21541 pos = 0; 21542 return S_OK; 21543 } 21544 21545 HRESULT Skip(ULONG celt) { 21546 if(celt + pos <= types.length) { 21547 pos += celt; 21548 return S_OK; 21549 } 21550 return S_FALSE; 21551 } 21552 }; 21553 21554 return S_OK; 21555 } else 21556 return E_NOTIMPL; 21557 } 21558 // given a format, return the format you'd prefer to use cuz it is identical 21559 HRESULT GetCanonicalFormatEtc(FORMATETC* pformatectIn, FORMATETC* pformatetcOut) { 21560 // FIXME: prolly could be better but meh 21561 // writeln("gcf: ", *pformatectIn); 21562 *pformatetcOut = *pformatectIn; 21563 return S_OK; 21564 } 21565 HRESULT GetData(FORMATETC* pformatetcIn, STGMEDIUM* pmedium) { 21566 foreach(ty; types) { 21567 if(ty == *pformatetcIn) { 21568 auto format = ty.cfFormat; 21569 // writeln("A: ", *pformatetcIn, "\nB: ", ty); 21570 STGMEDIUM medium; 21571 medium.tymed = TYMED.TYMED_HGLOBAL; 21572 21573 auto sz = handler.dataLength(format); 21574 auto handle = GlobalAlloc(GMEM_MOVEABLE, sz); 21575 if(handle is null) throw new WindowsApiException("GlobalAlloc", GetLastError()); 21576 if(auto data = cast(wchar*) GlobalLock(handle)) { 21577 auto slice = data[0 .. sz]; 21578 scope(exit) 21579 GlobalUnlock(handle); 21580 21581 handler.getData(format, cast(ubyte[]) slice[]); 21582 } 21583 21584 21585 medium.hGlobal = handle; // FIXME 21586 *pmedium = medium; 21587 return S_OK; 21588 } 21589 } 21590 return DV_E_FORMATETC; 21591 } 21592 HRESULT GetDataHere(FORMATETC* pformatetcIn, STGMEDIUM* pmedium) { 21593 // writeln("GDH: ", *pformatetcIn); 21594 return E_NOTIMPL; // FIXME 21595 } 21596 HRESULT QueryGetData(FORMATETC* pformatetc) { 21597 auto search = *pformatetc; 21598 search.tymed &= TYMED.TYMED_HGLOBAL; 21599 foreach(ty; types) 21600 if(ty == search) { 21601 // writeln("QueryGetData ", search, " ", types[0]); 21602 return S_OK; 21603 } 21604 if(pformatetc.cfFormat==CF_UNICODETEXT) { 21605 //writeln("QueryGetData FALSE ", search, " ", types[0]); 21606 } 21607 return S_FALSE; 21608 } 21609 HRESULT SetData(FORMATETC* pformatetc, STGMEDIUM* pmedium, BOOL fRelease) { 21610 // writeln("SetData: "); 21611 return E_NOTIMPL; 21612 } 21613 }; 21614 21615 21616 IDropSource src = new class IDropSource { 21617 ULONG refCount; 21618 ULONG AddRef() { 21619 return ++refCount; 21620 } 21621 ULONG Release() { 21622 return --refCount; 21623 } 21624 HRESULT QueryInterface(const (IID)*riid, LPVOID *ppv) { 21625 if (IID_IUnknown == *riid) { 21626 *ppv = cast(void*) cast(IUnknown) this; 21627 } 21628 else if (IID_IDropSource == *riid) { 21629 *ppv = cast(void*) cast(IDropSource) this; 21630 } 21631 else { 21632 *ppv = null; 21633 return E_NOINTERFACE; 21634 } 21635 21636 AddRef(); 21637 return NOERROR; 21638 } 21639 21640 int QueryContinueDrag(int fEscapePressed, uint grfKeyState) { 21641 if(fEscapePressed) 21642 return DRAGDROP_S_CANCEL; 21643 if(!(grfKeyState & MK_LBUTTON)) 21644 return DRAGDROP_S_DROP; 21645 return S_OK; 21646 } 21647 21648 int GiveFeedback(uint dwEffect) { 21649 return DRAGDROP_S_USEDEFAULTCURSORS; 21650 } 21651 }; 21652 21653 DWORD effect; 21654 21655 if(action == DragAndDropAction.none) assert(0, "Don't drag something with a none effect."); 21656 21657 DROPEFFECT de = win32DragAndDropAction(action); 21658 21659 // I'm not as concerned about the GC here since DoDragDrop blocks so the stack frame still sane the whole time 21660 // but still prolly a FIXME 21661 21662 auto ret = DoDragDrop(obj, src, de, &effect); 21663 /+ 21664 if(ret == DRAGDROP_S_DROP) 21665 writeln("drop ", effect); 21666 else if(ret == DRAGDROP_S_CANCEL) 21667 writeln("cancel"); 21668 else if(ret == S_OK) 21669 writeln("ok"); 21670 else writeln(ret); 21671 +/ 21672 21673 return ret; 21674 } 21675 21676 version(Windows) 21677 DROPEFFECT win32DragAndDropAction(DragAndDropAction action) { 21678 DROPEFFECT de; 21679 21680 with(DragAndDropAction) 21681 with(DROPEFFECT) 21682 final switch(action) { 21683 case none: de = DROPEFFECT_NONE; break; 21684 case copy: de = DROPEFFECT_COPY; break; 21685 case move: de = DROPEFFECT_MOVE; break; 21686 case link: de = DROPEFFECT_LINK; break; 21687 case ask: throw new Exception("ask not implemented yet"); 21688 case custom: throw new Exception("custom not implemented yet"); 21689 } 21690 21691 return de; 21692 } 21693 21694 21695 /++ 21696 History: 21697 Added February 19, 2021 21698 +/ 21699 /// Group: drag_and_drop 21700 void enableDragAndDrop(SimpleWindow window, DropHandler handler) { 21701 version(X11) { 21702 auto display = XDisplayConnection.get; 21703 21704 Atom atom = 5; // right??? 21705 21706 XChangeProperty( 21707 display, 21708 window.impl.window, 21709 GetAtom!"XdndAware"(display), 21710 XA_ATOM, 21711 32 /* bits */, 21712 PropModeReplace, 21713 &atom, 21714 1); 21715 21716 window.dropHandler = handler; 21717 } else version(Windows) { 21718 21719 initDnd(); 21720 21721 auto dropTarget = new class (handler) IDropTarget { 21722 DropHandler handler; 21723 this(DropHandler handler) { 21724 this.handler = handler; 21725 } 21726 ULONG refCount; 21727 ULONG AddRef() { 21728 return ++refCount; 21729 } 21730 ULONG Release() { 21731 return --refCount; 21732 } 21733 HRESULT QueryInterface(const (IID)*riid, LPVOID *ppv) { 21734 if (IID_IUnknown == *riid) { 21735 *ppv = cast(void*) cast(IUnknown) this; 21736 } 21737 else if (IID_IDropTarget == *riid) { 21738 *ppv = cast(void*) cast(IDropTarget) this; 21739 } 21740 else { 21741 *ppv = null; 21742 return E_NOINTERFACE; 21743 } 21744 21745 AddRef(); 21746 return NOERROR; 21747 } 21748 21749 21750 // /////////////////// 21751 21752 HRESULT DragEnter(IDataObject pDataObj, DWORD grfKeyState, POINTL pt, DWORD* pdwEffect) { 21753 DropPackage dropPackage = DropPackage(pDataObj); 21754 *pdwEffect = win32DragAndDropAction(handler.dragEnter(&dropPackage)); 21755 return S_OK; // https://docs.microsoft.com/en-us/windows/win32/api/oleidl/nf-oleidl-idroptarget-dragenter 21756 } 21757 21758 HRESULT DragLeave() { 21759 handler.dragLeave(); 21760 // release the IDataObject if needed 21761 return S_OK; 21762 } 21763 21764 HRESULT DragOver(DWORD grfKeyState, POINTL pt, DWORD* pdwEffect) { 21765 auto res = handler.dragOver(Point(pt.x, pt.y)); // FIXME: translate screen coordinates back to window coordinates 21766 21767 *pdwEffect = win32DragAndDropAction(res.action); 21768 // same as DragEnter basically 21769 return S_OK; 21770 } 21771 21772 HRESULT Drop(IDataObject pDataObj, DWORD grfKeyState, POINTL pt, DWORD* pdwEffect) { 21773 DropPackage pkg = DropPackage(pDataObj); 21774 handler.drop(&pkg); 21775 21776 return S_OK; 21777 } 21778 }; 21779 // Windows can hold on to the handler and try to call it 21780 // during which time the GC can't see it. so important to 21781 // manually manage this. At some point i'll FIXME and make 21782 // all my com instances manually managed since they supposed 21783 // to respect the refcount. 21784 import core.memory; 21785 GC.addRoot(cast(void*) dropTarget); 21786 21787 if(RegisterDragDrop(window.impl.hwnd, dropTarget) != S_OK) 21788 throw new WindowsApiException("RegisterDragDrop", GetLastError()); 21789 21790 window.dropHandler = handler; 21791 } else throw new NotYetImplementedException(); 21792 } 21793 21794 21795 21796 static if(UsingSimpledisplayX11) { 21797 21798 enum _NET_WM_STATE_ADD = 1; 21799 enum _NET_WM_STATE_REMOVE = 0; 21800 enum _NET_WM_STATE_TOGGLE = 2; 21801 21802 /// X-specific. Use [SimpleWindow.requestAttention] instead for most cases. 21803 void demandAttention(SimpleWindow window, bool needs = true) { 21804 demandAttention(window.impl.window, needs); 21805 } 21806 21807 /// ditto 21808 void demandAttention(Window window, bool needs = true) { 21809 setNetWmStateAtom(window, GetAtom!("_NET_WM_STATE_DEMANDS_ATTENTION", false)(XDisplayConnection.get), needs); 21810 } 21811 21812 void setNetWmStateAtom(Window window, Atom atom, bool set = true, Atom atom2 = None) { 21813 auto display = XDisplayConnection.get(); 21814 if(atom == None) 21815 return; // non-failure error 21816 //auto atom2 = GetAtom!"_NET_WM_STATE_SHADED"(display); 21817 21818 XClientMessageEvent xclient; 21819 21820 xclient.type = EventType.ClientMessage; 21821 xclient.window = window; 21822 xclient.message_type = GetAtom!"_NET_WM_STATE"(display); 21823 xclient.format = 32; 21824 xclient.data.l[0] = set ? _NET_WM_STATE_ADD : _NET_WM_STATE_REMOVE; 21825 xclient.data.l[1] = atom; 21826 xclient.data.l[2] = atom2; 21827 xclient.data.l[3] = 1; 21828 // [3] == source. 0 == unknown, 1 == app, 2 == else 21829 21830 XSendEvent( 21831 display, 21832 RootWindow(display, DefaultScreen(display)), 21833 false, 21834 EventMask.SubstructureRedirectMask | EventMask.SubstructureNotifyMask, 21835 cast(XEvent*) &xclient 21836 ); 21837 21838 /+ 21839 XChangeProperty( 21840 display, 21841 window.impl.window, 21842 GetAtom!"_NET_WM_STATE"(display), 21843 XA_ATOM, 21844 32 /* bits */, 21845 PropModeAppend, 21846 &atom, 21847 1); 21848 +/ 21849 } 21850 21851 private Atom dndActionAtom(Display* display, DragAndDropAction action) { 21852 Atom actionAtom; 21853 with(DragAndDropAction) 21854 final switch(action) { 21855 case none: actionAtom = None; break; 21856 case copy: actionAtom = GetAtom!"XdndActionCopy"(display); break; 21857 case move: actionAtom = GetAtom!"XdndActionMove"(display); break; 21858 case link: actionAtom = GetAtom!"XdndActionLink"(display); break; 21859 case ask: actionAtom = GetAtom!"XdndActionAsk"(display); break; 21860 case custom: actionAtom = GetAtom!"XdndActionCustom"(display); break; 21861 } 21862 21863 return actionAtom; 21864 } 21865 21866 private int doDragDropX11(SimpleWindow window, X11SetSelectionHandler handler, DragAndDropAction action) { 21867 // FIXME: I need to show user feedback somehow. 21868 auto display = XDisplayConnection.get; 21869 21870 auto actionAtom = dndActionAtom(display, action); 21871 assert(actionAtom, "Don't use action none to accept a drop"); 21872 21873 setX11Selection!"XdndSelection"(window, handler, null); 21874 21875 auto oldKeyHandler = window.handleKeyEvent; 21876 scope(exit) window.handleKeyEvent = oldKeyHandler; 21877 21878 auto oldCharHandler = window.handleCharEvent; 21879 scope(exit) window.handleCharEvent = oldCharHandler; 21880 21881 auto oldMouseHandler = window.handleMouseEvent; 21882 scope(exit) window.handleMouseEvent = oldMouseHandler; 21883 21884 Window[Window] eligibility; // 0 == not eligible, otherwise it is the window id of an eligible child 21885 21886 import core.sys.posix.sys.time; 21887 timeval tv; 21888 gettimeofday(&tv, null); 21889 21890 Time dataTimestamp = tv.tv_sec * 1000 + tv.tv_usec / 1000; 21891 21892 Time lastMouseTimestamp; 21893 21894 bool dnding = true; 21895 Window lastIn = None; 21896 21897 void leave() { 21898 if(lastIn == None) 21899 return; 21900 21901 XEvent ev; 21902 ev.xclient.type = EventType.ClientMessage; 21903 ev.xclient.window = lastIn; 21904 ev.xclient.message_type = GetAtom!("XdndLeave", true)(display); 21905 ev.xclient.format = 32; 21906 ev.xclient.data.l[0] = window.impl.window; 21907 21908 XSendEvent(XDisplayConnection.get, lastIn, false, EventMask.NoEventMask, &ev); 21909 XFlush(display); 21910 21911 lastIn = None; 21912 } 21913 21914 void enter(Window w) { 21915 assert(lastIn == None); 21916 21917 lastIn = w; 21918 21919 XEvent ev; 21920 ev.xclient.type = EventType.ClientMessage; 21921 ev.xclient.window = lastIn; 21922 ev.xclient.message_type = GetAtom!("XdndEnter", true)(display); 21923 ev.xclient.format = 32; 21924 ev.xclient.data.l[0] = window.impl.window; 21925 ev.xclient.data.l[1] = (5 << 24) | 0; // version 5, no more sources. FIXME source types 21926 21927 auto types = handler.availableFormats(); 21928 assert(types.length > 0); 21929 21930 ev.xclient.data.l[2] = types[0]; 21931 if(types.length > 1) 21932 ev.xclient.data.l[3] = types[1]; 21933 if(types.length > 2) 21934 ev.xclient.data.l[4] = types[2]; 21935 21936 // FIXME: other types?!?!? and make sure we skip TARGETS 21937 21938 XSendEvent(XDisplayConnection.get, lastIn, false, EventMask.NoEventMask, &ev); 21939 XFlush(display); 21940 } 21941 21942 void position(int rootX, int rootY) { 21943 assert(lastIn != None); 21944 21945 XEvent ev; 21946 ev.xclient.type = EventType.ClientMessage; 21947 ev.xclient.window = lastIn; 21948 ev.xclient.message_type = GetAtom!("XdndPosition", true)(display); 21949 ev.xclient.format = 32; 21950 ev.xclient.data.l[0] = window.impl.window; 21951 ev.xclient.data.l[1] = 0; // reserved 21952 ev.xclient.data.l[2] = (rootX << 16) | rootY; 21953 ev.xclient.data.l[3] = dataTimestamp; 21954 ev.xclient.data.l[4] = actionAtom; 21955 21956 XSendEvent(XDisplayConnection.get, lastIn, false, EventMask.NoEventMask, &ev); 21957 XFlush(display); 21958 21959 } 21960 21961 void drop() { 21962 XEvent ev; 21963 ev.xclient.type = EventType.ClientMessage; 21964 ev.xclient.window = lastIn; 21965 ev.xclient.message_type = GetAtom!("XdndDrop", true)(display); 21966 ev.xclient.format = 32; 21967 ev.xclient.data.l[0] = window.impl.window; 21968 ev.xclient.data.l[1] = 0; // reserved 21969 ev.xclient.data.l[2] = dataTimestamp; 21970 21971 XSendEvent(XDisplayConnection.get, lastIn, false, EventMask.NoEventMask, &ev); 21972 XFlush(display); 21973 21974 lastIn = None; 21975 dnding = false; 21976 } 21977 21978 // fyi nativeEventHandler can return 0 if it handles it, or otherwise it goes back to the normal handler 21979 // but idk if i should... 21980 21981 window.setEventHandlers( 21982 delegate(KeyEvent ev) { 21983 if(ev.pressed == true && ev.key == Key.Escape) { 21984 // cancel 21985 dnding = false; 21986 } 21987 }, 21988 delegate(MouseEvent ev) { 21989 if(ev.timestamp < lastMouseTimestamp) 21990 return; 21991 21992 lastMouseTimestamp = ev.timestamp; 21993 21994 if(ev.type == MouseEventType.motion) { 21995 auto display = XDisplayConnection.get; 21996 auto root = RootWindow(display, DefaultScreen(display)); 21997 21998 Window topWindow; 21999 int rootX, rootY; 22000 22001 XTranslateCoordinates(display, window.impl.window, root, ev.x, ev.y, &rootX, &rootY, &topWindow); 22002 22003 if(topWindow == None) 22004 return; 22005 22006 top: 22007 if(auto result = topWindow in eligibility) { 22008 auto dropWindow = *result; 22009 if(dropWindow == None) { 22010 leave(); 22011 return; 22012 } 22013 22014 if(dropWindow != lastIn) { 22015 leave(); 22016 enter(dropWindow); 22017 position(rootX, rootY); 22018 } else { 22019 position(rootX, rootY); 22020 } 22021 } else { 22022 // determine eligibility 22023 auto data = cast(Atom[]) getX11PropertyData(topWindow, GetAtom!"XdndAware"(display), XA_ATOM); 22024 if(data.length == 1) { 22025 // in case there is no WM or it isn't reparenting 22026 eligibility[topWindow] = (data[0] == 5) ? topWindow : None; // FIXME I'm supposed to handle older versions too but meh 22027 } else { 22028 22029 Window tryScanChildren(Window search, int maxRecurse) { 22030 // could be reparenting window manager, so gotta check the next few children too 22031 Window child; 22032 int x; 22033 int y; 22034 XTranslateCoordinates(display, window.impl.window, search, ev.x, ev.y, &x, &y, &child); 22035 22036 if(child == None) 22037 return None; 22038 auto data = cast(Atom[]) getX11PropertyData(child, GetAtom!"XdndAware"(display), XA_ATOM); 22039 if(data.length == 1) { 22040 return (data[0] == 5) ? child : None; // FIXME I'm supposed to handle older versions too but meh 22041 } else { 22042 if(maxRecurse) 22043 return tryScanChildren(child, maxRecurse - 1); 22044 else 22045 return None; 22046 } 22047 22048 } 22049 22050 // if a WM puts more than 3 layers on it, like wtf is it doing, screw that. 22051 auto topResult = tryScanChildren(topWindow, 3); 22052 // it is easy to have a false negative due to the mouse going over a WM 22053 // child window like the close button if separate from the frame... so I 22054 // can't really cache negatives, :( 22055 if(topResult != None) { 22056 eligibility[topWindow] = topResult; 22057 goto top; // reload to do the positioning iff eligibility changed lest we endless loop 22058 } 22059 } 22060 22061 } 22062 22063 } else if(ev.type == MouseEventType.buttonReleased) { 22064 drop(); 22065 dnding = false; 22066 } 22067 } 22068 ); 22069 22070 window.grabInput(); 22071 scope(exit) 22072 window.releaseInputGrab(); 22073 22074 22075 EventLoop.get.run(() => dnding); 22076 22077 return 0; 22078 } 22079 22080 /// X-specific 22081 TrueColorImage getWindowNetWmIcon(Window window) { 22082 try { 22083 auto display = XDisplayConnection.get; 22084 22085 auto data = getX11PropertyData (window, GetAtom!"_NET_WM_ICON"(display), XA_CARDINAL); 22086 22087 if (data.length > arch_ulong.sizeof * 2) { 22088 auto meta = cast(arch_ulong[]) (data[0 .. arch_ulong.sizeof * 2]); 22089 // these are an array of rgba images that we have to convert into pixmaps ourself 22090 22091 int width = cast(int) meta[0]; 22092 int height = cast(int) meta[1]; 22093 22094 auto bytes = cast(ubyte[]) (data[arch_ulong.sizeof * 2 .. $]); 22095 22096 static if(arch_ulong.sizeof == 4) { 22097 bytes = bytes[0 .. width * height * 4]; 22098 alias imageData = bytes; 22099 } else static if(arch_ulong.sizeof == 8) { 22100 bytes = bytes[0 .. width * height * 8]; 22101 auto imageData = new ubyte[](4 * width * height); 22102 } else static assert(0); 22103 22104 22105 22106 // this returns ARGB. Remember it is little-endian so 22107 // we have BGRA 22108 // our thing uses RGBA, which in little endian, is ABGR 22109 for(int idx = 0, idx2 = 0; idx < bytes.length; idx += arch_ulong.sizeof, idx2 += 4) { 22110 auto r = bytes[idx + 2]; 22111 auto g = bytes[idx + 1]; 22112 auto b = bytes[idx + 0]; 22113 auto a = bytes[idx + 3]; 22114 22115 imageData[idx2 + 0] = r; 22116 imageData[idx2 + 1] = g; 22117 imageData[idx2 + 2] = b; 22118 imageData[idx2 + 3] = a; 22119 } 22120 22121 return new TrueColorImage(width, height, imageData); 22122 } 22123 22124 return null; 22125 } catch(Exception e) { 22126 return null; 22127 } 22128 } 22129 22130 } /* UsingSimpledisplayX11 */ 22131 22132 22133 void loadBinNameToWindowClassName () { 22134 import core.stdc.stdlib : realloc; 22135 version(linux) { 22136 // args[0] MAY be empty, so we'll just use this 22137 import core.sys.posix.unistd : readlink; 22138 char[1024] ebuf = void; // 1KB should be enough for everyone! 22139 auto len = readlink("/proc/self/exe", ebuf.ptr, ebuf.length); 22140 if (len < 1) return; 22141 } else /*version(Windows)*/ { 22142 import core.runtime : Runtime; 22143 if (Runtime.args.length == 0 || Runtime.args[0].length == 0) return; 22144 auto ebuf = Runtime.args[0]; 22145 auto len = ebuf.length; 22146 } 22147 auto pos = len; 22148 while (pos > 0 && ebuf[pos-1] != '/') --pos; 22149 sdpyWindowClassStr = cast(char*)realloc(sdpyWindowClassStr, len-pos+1); 22150 if (sdpyWindowClassStr is null) return; // oops 22151 sdpyWindowClassStr[0..len-pos+1] = 0; // just in case 22152 sdpyWindowClassStr[0..len-pos] = ebuf[pos..len]; 22153 } 22154 22155 /++ 22156 An interface representing a font that is drawn with custom facilities. 22157 22158 You might want [OperatingSystemFont] instead, which represents 22159 a font loaded and drawn by functions native to the operating system. 22160 22161 WARNING: I might still change this. 22162 +/ 22163 interface DrawableFont : MeasurableFont { 22164 /++ 22165 Please note the point is upperLeft, NOT baseline! This is the point of a bounding box of the string. 22166 22167 Implementations must use the painter's fillColor to draw a rectangle behind the string, 22168 then use the outlineColor to draw the string. It might alpha composite if there's a transparent 22169 fill color, but that's up to the implementation. 22170 +/ 22171 void drawString(ScreenPainter painter, Point upperLeft, in char[] text); 22172 22173 /++ 22174 Requests that the given string is added to the image cache. You should only do this rarely, but 22175 if you have a string that you know will be used over and over again, adding it to a cache can 22176 improve things (assuming the implementation actually has a cache; it is also valid for an implementation 22177 to implement this as a do-nothing method). 22178 +/ 22179 void cacheString(SimpleWindow window, Color foreground, Color background, string text); 22180 } 22181 22182 /++ 22183 Loads a true type font using [arsd.ttf] that can be drawn as images on windows 22184 through a [ScreenPainter]. That module must be compiled in if you choose to use this function. 22185 22186 You should also consider [OperatingSystemFont], which loads and draws a font with 22187 facilities native to the user's operating system. You might also consider 22188 [arsd.ttf.OpenGlLimitedFont] or using [arsd.nanovega] if you are making some kind 22189 of game, as they have their own ways to draw text too. 22190 22191 Be warned: this can be slow, especially on remote connections to the X server, since 22192 it needs to create and transfer bitmaps instead of just text. The [DrawableFont] interface 22193 offers [DrawableFont.cacheString] which can help with this, sometimes. You might want to 22194 experiment in your specific case. 22195 22196 Please note that the return type of [DrawableFont] also includes an implementation of 22197 [MeasurableFont]. 22198 +/ 22199 DrawableFont arsdTtfFont()(in ubyte[] data, int size) { 22200 import arsd.ttf; 22201 static class ArsdTtfFont : DrawableFont { 22202 TtfFont font; 22203 int size; 22204 this(in ubyte[] data, int size) { 22205 font = TtfFont(data); 22206 this.size = size; 22207 22208 22209 auto scale = stbtt_ScaleForPixelHeight(&font.font, size); 22210 int ascent_, descent_, line_gap; 22211 stbtt_GetFontVMetrics(&font.font, &ascent_, &descent_, &line_gap); 22212 22213 int advance, lsb; 22214 stbtt_GetCodepointHMetrics(&font.font, 'x', &advance, &lsb); 22215 xWidth = cast(int) (advance * scale); 22216 stbtt_GetCodepointHMetrics(&font.font, 'M', &advance, &lsb); 22217 MWidth = cast(int) (advance * scale); 22218 } 22219 22220 private int ascent_; 22221 private int descent_; 22222 private int xWidth; 22223 private int MWidth; 22224 22225 bool isMonospace() { 22226 return xWidth == MWidth; 22227 } 22228 int averageWidth() { 22229 return xWidth; 22230 } 22231 int height() { 22232 return size; 22233 } 22234 int ascent() { 22235 return ascent_; 22236 } 22237 int descent() { 22238 return descent_; 22239 } 22240 22241 int stringWidth(scope const(char)[] s, SimpleWindow window = null) { 22242 int width, height; 22243 font.getStringSize(s, size, width, height); 22244 return width; 22245 } 22246 22247 22248 22249 Sprite[string] cache; 22250 22251 void cacheString(SimpleWindow window, Color foreground, Color background, string text) { 22252 auto sprite = new Sprite(window, stringToImage(foreground, background, text)); 22253 cache[text] = sprite; 22254 } 22255 22256 Image stringToImage(Color fg, Color bg, in char[] text) { 22257 int width, height; 22258 auto data = font.renderString(text, size, width, height); 22259 auto image = new TrueColorImage(width, height); 22260 int pos = 0; 22261 foreach(y; 0 .. height) 22262 foreach(x; 0 .. width) { 22263 fg.a = data[0]; 22264 bg.a = 255; 22265 auto color = alphaBlend(fg, bg); 22266 image.imageData.bytes[pos++] = color.r; 22267 image.imageData.bytes[pos++] = color.g; 22268 image.imageData.bytes[pos++] = color.b; 22269 image.imageData.bytes[pos++] = data[0]; 22270 data = data[1 .. $]; 22271 } 22272 assert(data.length == 0); 22273 22274 return Image.fromMemoryImage(image); 22275 } 22276 22277 void drawString(ScreenPainter painter, Point upperLeft, in char[] text) { 22278 Sprite sprite = (text in cache) ? *(text in cache) : null; 22279 22280 auto fg = painter.impl._outlineColor; 22281 auto bg = painter.impl._fillColor; 22282 22283 if(sprite !is null) { 22284 auto w = cast(SimpleWindow) painter.window; 22285 assert(w !is null); 22286 22287 sprite.drawAt(painter, upperLeft); 22288 } else { 22289 painter.drawImage(upperLeft, stringToImage(fg, bg, text)); 22290 } 22291 } 22292 } 22293 22294 return new ArsdTtfFont(data, size); 22295 } 22296 22297 class NotYetImplementedException : Exception { 22298 this(string file = __FILE__, size_t line = __LINE__) { 22299 super("Not yet implemented", file, line); 22300 } 22301 } 22302 22303 /// 22304 __gshared bool librariesSuccessfullyLoaded = true; 22305 /// 22306 __gshared bool openGlLibrariesSuccessfullyLoaded = true; 22307 22308 private mixin template DynamicLoadSupplementalOpenGL(Iface) { 22309 mixin(staticForeachReplacement!Iface); 22310 22311 void loadDynamicLibrary() @nogc { 22312 (cast(void function() @nogc) &loadDynamicLibraryForReal)(); 22313 } 22314 22315 void loadDynamicLibraryForReal() { 22316 foreach(name; __traits(derivedMembers, Iface)) { 22317 mixin("alias tmp = " ~ name ~ ";"); 22318 tmp = cast(typeof(tmp)) glbindGetProcAddress(name); 22319 if(tmp is null) throw new Exception("load failure of function " ~ name ~ " from supplemental OpenGL"); 22320 } 22321 } 22322 } 22323 22324 private const(char)[] staticForeachReplacement(Iface)() pure { 22325 /* 22326 // just this for gdc 9.... 22327 // when i drop support for it and switch to gdc10, we can put this original back for a slight compile time ram decrease 22328 22329 static foreach(name; __traits(derivedMembers, Iface)) 22330 mixin("__gshared typeof(&__traits(getMember, Iface, name)) " ~ name ~ ";"); 22331 */ 22332 22333 char[] code = new char[](__traits(derivedMembers, Iface).length * 64); 22334 size_t pos; 22335 22336 void append(in char[] what) { 22337 if(pos + what.length > code.length) 22338 code.length = (code.length * 3) / 2; 22339 code[pos .. pos + what.length] = what[]; 22340 pos += what.length; 22341 } 22342 22343 foreach(name; __traits(derivedMembers, Iface)) { 22344 append(`__gshared typeof(&__traits(getMember, Iface, "`); 22345 append(name); 22346 append(`")) `); 22347 append(name); 22348 append(";"); 22349 } 22350 22351 return code[0 .. pos]; 22352 } 22353 22354 private mixin template DynamicLoad(Iface, string library, int majorVersion, alias success) { 22355 mixin(staticForeachReplacement!Iface); 22356 22357 private __gshared void* libHandle; 22358 private __gshared bool attempted; 22359 22360 void loadDynamicLibrary() @nogc { 22361 (cast(void function() @nogc) &loadDynamicLibraryForReal)(); 22362 } 22363 22364 bool loadAttempted() { 22365 return attempted; 22366 } 22367 bool loadSuccessful() { 22368 return libHandle !is null; 22369 } 22370 22371 void loadDynamicLibraryForReal() { 22372 attempted = true; 22373 version(Posix) { 22374 import core.sys.posix.dlfcn; 22375 version(OSX) { 22376 version(X11) 22377 libHandle = dlopen("/usr/X11/lib/lib" ~ library ~ ".dylib", RTLD_NOW); 22378 else 22379 libHandle = dlopen(library ~ ".dylib", RTLD_NOW); 22380 } else { 22381 libHandle = dlopen("lib" ~ library ~ ".so", RTLD_NOW); 22382 if(libHandle is null) 22383 libHandle = dlopen(("lib" ~ library ~ ".so." ~ toInternal!string(majorVersion) ~ "\0").ptr, RTLD_NOW); 22384 } 22385 22386 static void* loadsym(void* l, const char* name) { 22387 import core.stdc.stdlib; 22388 if(l is null) 22389 return &abort; 22390 return dlsym(l, name); 22391 } 22392 } else version(Windows) { 22393 import core.sys.windows.winbase; 22394 libHandle = LoadLibrary(library ~ ".dll"); 22395 static void* loadsym(void* l, const char* name) { 22396 import core.stdc.stdlib; 22397 if(l is null) 22398 return &abort; 22399 return GetProcAddress(l, name); 22400 } 22401 } 22402 if(libHandle is null) { 22403 success = false; 22404 //throw new Exception("load failure of library " ~ library); 22405 } 22406 foreach(name; __traits(derivedMembers, Iface)) { 22407 mixin("alias tmp = " ~ name ~ ";"); 22408 tmp = cast(typeof(tmp)) loadsym(libHandle, name); 22409 if(tmp is null) throw new Exception("load failure of function " ~ name ~ " from " ~ library); 22410 } 22411 } 22412 22413 void unloadDynamicLibrary() { 22414 version(Posix) { 22415 import core.sys.posix.dlfcn; 22416 dlclose(libHandle); 22417 } else version(Windows) { 22418 import core.sys.windows.winbase; 22419 FreeLibrary(libHandle); 22420 } 22421 foreach(name; __traits(derivedMembers, Iface)) 22422 mixin(name ~ " = null;"); 22423 } 22424 } 22425 22426 /+ 22427 The GC can be called from any thread, and a lot of cleanup must be done 22428 on the gui thread. Since the GC can interrupt any locks - including being 22429 triggered inside a critical section - it is vital to avoid deadlocks to get 22430 these functions called from the right place. 22431 22432 If the buffer overflows, things are going to get leaked. I'm kinda ok with that 22433 right now. 22434 22435 The cleanup function is run when the event loop gets around to it, which is just 22436 whenever there's something there after it has been woken up for other work. It does 22437 NOT wake up the loop itself - can't risk doing that from inside the GC in another thread. 22438 (Well actually it might be ok but i don't wanna mess with it right now.) 22439 +/ 22440 private struct CleanupQueue { 22441 import core.stdc.stdlib; 22442 22443 void queue(alias func, T...)(T args) { 22444 static struct Args { 22445 T args; 22446 } 22447 static struct RealJob { 22448 Job j; 22449 Args a; 22450 } 22451 static void call(Job* data) { 22452 auto rj = cast(RealJob*) data; 22453 func(rj.a.args); 22454 } 22455 22456 RealJob* thing = cast(RealJob*) malloc(RealJob.sizeof); 22457 thing.j.call = &call; 22458 thing.a.args = args; 22459 22460 buffer[tail++] = cast(Job*) thing; 22461 22462 // FIXME: set overflowed 22463 } 22464 22465 void process() { 22466 const tail = this.tail; 22467 22468 while(tail != head) { 22469 Job* job = cast(Job*) buffer[head++]; 22470 job.call(job); 22471 free(job); 22472 } 22473 22474 if(overflowed) 22475 throw new Exception("cleanup overflowed"); 22476 } 22477 22478 private: 22479 22480 ubyte tail; // must ONLY be written by queue 22481 ubyte head; // must ONLY be written by process 22482 bool overflowed; 22483 22484 static struct Job { 22485 void function(Job*) call; 22486 } 22487 22488 void*[256] buffer; 22489 } 22490 private __gshared CleanupQueue cleanupQueue; 22491 22492 // version(X11) 22493 /++ 22494 Returns the custom scaling factor read out of environment["ARSD_SCALING_FACTOR"]. 22495 22496 $(WARNING 22497 This function is exempted from stability guarantees. 22498 ) 22499 +/ 22500 float customScalingFactorForMonitor(int monitorNumber) { 22501 import core.stdc.stdlib; 22502 auto val = getenv("ARSD_SCALING_FACTOR"); 22503 22504 // FIXME: maybe we should assume a default nbased on the dpi thing if this isn't given 22505 if(val is null) 22506 return 1.0; 22507 22508 char[16] buffer = 0; 22509 int pos; 22510 22511 const(char)* at = val; 22512 22513 foreach(item; 0 .. monitorNumber + 1) { 22514 if(*at == 0) 22515 break; // reuse the last number when we at the end of the string 22516 pos = 0; 22517 while(pos + 1 < buffer.length && *at && *at != ';') { 22518 buffer[pos++] = *at; 22519 at++; 22520 } 22521 if(*at) 22522 at++; // skip the semicolon 22523 buffer[pos] = 0; 22524 } 22525 22526 //sdpyPrintDebugString(buffer[0 .. pos]); 22527 22528 import core.stdc.math; 22529 auto f = atof(buffer.ptr); 22530 22531 if(f <= 0.0 || isnan(f) || isinf(f)) 22532 return 1.0; 22533 22534 return f; 22535 } 22536 22537 void guiAbortProcess(string msg) { 22538 import core.stdc.stdlib; 22539 version(Windows) { 22540 WCharzBuffer t = WCharzBuffer(msg); 22541 MessageBoxW(null, t.ptr, "Program Termination"w.ptr, 0); 22542 } else { 22543 import core.stdc.stdio; 22544 fwrite(msg.ptr, 1, msg.length, stderr); 22545 msg = "\n"; 22546 fwrite(msg.ptr, 1, msg.length, stderr); 22547 fflush(stderr); 22548 } 22549 22550 abort(); 22551 } 22552 22553 private int minInternal(int a, int b) { 22554 return (a < b) ? a : b; 22555 } 22556 22557 private alias scriptable = arsd_jsvar_compatible;