1 // https://dpaste.dzfl.pl/7a77355acaec 2 3 /+ 4 To share some stuff between two opengl threads: 5 windows 6 https://www.khronos.org/opengl/wiki/OpenGL_and_multithreading 7 linux 8 https://stackoverflow.com/questions/18879520/sharing-opengl-objects-between-contexts-on-linux 9 +/ 10 11 12 // Search for: FIXME: leaks if multithreaded gc 13 14 // https://freedesktop.org/wiki/Specifications/XDND/ 15 16 // https://docs.microsoft.com/en-us/windows/win32/dataxchg/html-clipboard-format 17 18 // https://www.x.org/releases/X11R7.7/doc/libXext/dbelib.html 19 // https://www.x.org/releases/X11R7.6/doc/libXext/synclib.html 20 21 22 // on Mac with X11: -L-L/usr/X11/lib 23 24 /+ 25 26 * I might need to set modal hints too _NET_WM_STATE_MODAL and make sure that TRANSIENT_FOR legit works 27 28 Progress bar in taskbar 29 - i can probably just set a property on the window... 30 it sets that prop to an integer 0 .. 100. Taskbar 31 deletes it or window deletes it when it is handled. 32 - prolly display it as a nice little line at the bottom. 33 34 35 from gtk: 36 37 #define PROGRESS_HINT "_NET_WM_XAPP_PROGRESS" 38 #define PROGRESS_PULSE_HINT "_NET_WM_XAPP_PROGRESS_PULSE" 39 40 >+ if (cardinal > 0) 41 >+ { 42 >+ XChangeProperty (GDK_DISPLAY_XDISPLAY (display), 43 >+ xid, 44 >+ gdk_x11_get_xatom_by_name_for_display (display, atom_name), 45 >+ XA_CARDINAL, 32, 46 >+ PropModeReplace, 47 >+ (guchar *) &cardinal, 1); 48 >+ } 49 >+ else 50 >+ { 51 >+ XDeleteProperty (GDK_DISPLAY_XDISPLAY (display), 52 >+ xid, 53 >+ gdk_x11_get_xatom_by_name_for_display (display, atom_name)); 54 >+ } 55 56 from Windows: 57 58 see: https://docs.microsoft.com/en-us/windows/win32/api/shobjidl_core/nn-shobjidl_core-itaskbarlist3 59 60 interface 61 CoCreateInstance( CLSID_TaskbarList, nullptr, CLSCTX_ALL, __uuidof(ITaskbarList3), (LPVOID*)&m_pTL3 ); 62 auto msg = RegisterWindowMessage(TEXT(“TaskbarButtonCreated”)); 63 listen for msg, return TRUE 64 interface->SetProgressState(hwnd, TBPF_NORMAL); 65 interface->SetProgressValue(hwnd, 40, 100); 66 67 68 My new notification system. 69 - use a unix socket? or a x property? or a udp port? 70 - could of course also get on the dbus train but ugh. 71 - it could also reply with the info as a string for easy remote examination. 72 73 +/ 74 75 /* 76 Event Loop would be nices: 77 78 * add on idle - runs when nothing else happens 79 * which can specify how long to yield for 80 * send messages without a recipient window 81 * setTimeout 82 * setInterval 83 */ 84 85 /* 86 Classic games I want to add: 87 * my tetris clone 88 * pac man 89 */ 90 91 /* 92 Text layout needs a lot of work. Plain drawText is useful but too 93 limited. It will need some kind of text context thing which it will 94 update and you can pass it on and get more details out of it. 95 96 It will need a bounding box, a current cursor location that is updated 97 as drawing continues, and various changable facts (which can also be 98 changed on the painter i guess) like font, color, size, background, 99 etc. 100 101 We can also fetch the caret location from it somehow. 102 103 Should prolly be an overload of drawText 104 105 blink taskbar / demand attention cross platform. FlashWindow and demandAttention 106 107 WS_EX_NOACTIVATE 108 WS_CHILD - owner and owned vs parent and child. Does X have something similar? 109 full screen windows. Can just set the atom on X. Windows will be harder. 110 111 moving windows. resizing windows. 112 113 hide cursor, capture cursor, change cursor. 114 115 REMEMBER: simpledisplay does NOT have to do everything! It just needs to make 116 sure the pieces are there to do its job easily and make other jobs possible. 117 */ 118 119 /++ 120 simpledisplay.d (often abbreviated to "sdpy") provides basic cross-platform GUI-related functionality, 121 including creating windows, drawing on them, working with the clipboard, 122 timers, OpenGL, and more. However, it does NOT provide high level GUI 123 widgets. See my minigui.d, an extension to this module, for that 124 functionality. 125 126 simpledisplay provides cross-platform wrapping for Windows and Linux 127 (and perhaps other OSes that use X11), but also does not prevent you 128 from using the underlying facilities if you need them. It has a goal 129 of working efficiently over a remote X link (at least as far as Xlib 130 reasonably allows.) 131 132 simpledisplay depends on [arsd.color|color.d], which should be available from the 133 same place where you got this file. Other than that, however, it has 134 very few dependencies and ones that don't come with the OS and/or the 135 compiler are all opt-in. 136 137 simpledisplay.d's home base is on my arsd repo on Github. The file is: 138 https://github.com/adamdruppe/arsd/blob/master/simpledisplay.d 139 140 simpledisplay is basically stable. I plan to refactor the internals, 141 and may add new features and fix bugs, but It do not expect to 142 significantly change the API. It has been stable a few years already now. 143 144 Installation_instructions: 145 146 `simpledisplay.d` does not have any dependencies outside the 147 operating system and `color.d`, so it should just work most the 148 time, but there are a few caveats on some systems: 149 150 On Win32, you can pass `-L/subsystem:windows` if you don't want a 151 console to be automatically allocated. 152 153 Please note when compiling on Win64, you need to explicitly list 154 `-Lgdi32.lib -Luser32.lib` on the build command. If you want the Windows 155 subsystem too, use `-L/subsystem:windows -L/entry:mainCRTStartup`. 156 157 If using ldc instead of dmd, use `-L/entry:wmainCRTStartup` instead of `mainCRTStartup`; 158 note the "w". 159 160 I provided a `mixin EnableWindowsSubsystem;` helper to do those linker flags for you, 161 but you still need to use dmd -m32mscoff or -m64 (which dub does by default too fyi). 162 See [EnableWindowsSubsystem] for more information. 163 164 $(PITFALL 165 With the Windows subsystem, there is no console, so standard writeln will throw! 166 You can use [sdpyPrintDebugString] instead of stdio writeln instead which will 167 create a console as needed. 168 ) 169 170 On Mac, when compiling with X11, you need XQuartz and -L-L/usr/X11R6/lib passed to dmd. If using the Cocoa implementation on Mac, you need to pass `-L-framework -LCocoa` to dmd. For OpenGL, add `-L-framework -LOpenGL` to the build command. 171 172 On Ubuntu, you might need to install X11 development libraries to 173 successfully link. 174 175 $(CONSOLE 176 $ sudo apt-get install libglc-dev 177 $ sudo apt-get install libx11-dev 178 ) 179 180 181 Jump_list: 182 183 Don't worry, you don't have to read this whole documentation file! 184 185 Check out the [#event-example] and [#Pong-example] to get started quickly. 186 187 The main classes you may want to create are [SimpleWindow], [Timer], 188 [Image], and [Sprite]. 189 190 The main functions you'll want are [setClipboardText] and [getClipboardText]. 191 192 There are also platform-specific functions available such as [XDisplayConnection] 193 and [GetAtom] for X11, among others. 194 195 See the examples and topics list below to learn more. 196 197 $(WARNING 198 There should only be one GUI thread per application, 199 and all windows should be created in it and your 200 event loop should run there. 201 202 To do otherwise is undefined behavior and has no 203 cross platform guarantees. 204 ) 205 206 $(H2 About this documentation) 207 208 The goal here is to give some complete programs as overview examples first, then a look at each major feature with working examples first, then, finally, the inline class and method list will follow. 209 210 Scan for headers for a topic - $(B they will visually stand out) - you're interested in to get started quickly and feel free to copy and paste any example as a starting point for your program. I encourage you to learn the library by experimenting with the examples! 211 212 All examples are provided with no copyright restrictions whatsoever. You do not need to credit me or carry any kind of notice with the source if you copy and paste from them. 213 214 To get started, download `simpledisplay.d` and `color.d` to a working directory. Copy an example info a file called `example.d` and compile using the command given at the top of each example. 215 216 If you need help, email me: destructionator@gmail.com or IRC us, #d on Freenode (I am destructionator or adam_d_ruppe there). If you learn something that isn't documented, I appreciate pull requests on github to this file. 217 218 At points, I will talk about implementation details in the documentation. These are sometimes 219 subject to change, but nevertheless useful to understand what is really going on. You can learn 220 more about some of the referenced things by searching the web for info about using them from C. 221 You can always look at the source of simpledisplay.d too for the most authoritative source on 222 its specific implementation. If you disagree with how I did something, please contact me so we 223 can discuss it! 224 225 $(H2 Using with fibers) 226 227 simpledisplay can be used with [core.thread.Fiber], but be warned many of the functions can use a significant amount of stack space. I recommend at least 64 KB stack for each fiber (just set through the second argument to Fiber's constructor). 228 229 $(H2 Topics) 230 231 $(H3 $(ID topic-windows) Windows) 232 The [SimpleWindow] class is simpledisplay's flagship feature. It represents a single 233 window on the user's screen. 234 235 You may create multiple windows, if the underlying platform supports it. You may check 236 `static if(multipleWindowsSupported)` at compile time, or catch exceptions thrown by 237 SimpleWindow's constructor at runtime to handle those cases. 238 239 A single running event loop will handle as many windows as needed. 240 241 $(H3 $(ID topic-event-loops) Event loops) 242 The simpledisplay event loop is designed to handle common cases easily while being extensible for more advanced cases, or replaceable by other libraries. 243 244 The most common scenario is creating a window, then calling [SimpleWindow.eventLoop|window.eventLoop] when setup is complete. You can pass several handlers to the `eventLoop` method right there: 245 246 --- 247 // dmd example.d simpledisplay.d color.d 248 import arsd.simpledisplay; 249 void main() { 250 auto window = new SimpleWindow(200, 200); 251 window.eventLoop(0, 252 delegate (dchar) { /* got a character key press */ } 253 ); 254 } 255 --- 256 257 $(TIP If you get a compile error saying "I can't use this event handler", the most common thing in my experience is passing a function instead of a delegate. The simple solution is to use the `delegate` keyword, like I did in the example above.) 258 259 On Linux, the event loop is implemented with the `epoll` system call for efficiency an extensibility to other files. On Windows, it runs a traditional `GetMessage` + `DispatchMessage` loop, with a call to `SleepEx` in each iteration to allow the thread to enter an alertable wait state regularly, primarily so Overlapped I/O callbacks will get a chance to run. 260 261 On Linux, simpledisplay also supports my (deprecated) [arsd.eventloop] module. Compile your program, including the eventloop.d file, with the `-version=with_eventloop` switch. 262 263 It should be possible to integrate simpledisplay with vibe.d as well, though I haven't tried. 264 265 You can also run the event loop independently of a window, with [EventLoop.run|EventLoop.get.run], though since it will automatically terminate when there are no open windows, you will want to have one anyway. 266 267 $(H3 $(ID topic-notification-areas) Notification area (aka systray) icons) 268 Notification area icons are currently implemented on X11 and Windows. On X11, it defaults to using `libnotify` to show bubbles, if available, and will do a custom bubble window if not. You can `version=without_libnotify` to avoid this run-time dependency, if you like. 269 270 See the [NotificationAreaIcon] class. 271 272 $(H3 $(ID topic-input-handling) Input handling) 273 There are event handlers for low-level keyboard and mouse events, and higher level handlers for character events. 274 275 See [SimpleWindow.handleCharEvent], [SimpleWindow.handleKeyEvent], [SimpleWindow.handleMouseEvent]. 276 277 $(H3 $(ID topic-2d-drawing) 2d Drawing) 278 To draw on your window, use the [SimpleWindow.draw] method. It returns a [ScreenPainter] structure with drawing methods. 279 280 Important: `ScreenPainter` double-buffers and will not actually update the window until its destructor is run. Always ensure the painter instance goes out-of-scope before proceeding. You can do this by calling it inside an event handler, a timer callback, or an small scope inside main. For example: 281 282 --- 283 // dmd example.d simpledisplay.d color.d 284 import arsd.simpledisplay; 285 void main() { 286 auto window = new SimpleWindow(200, 200); 287 { // introduce sub-scope 288 auto painter = window.draw(); // begin drawing 289 /* draw here */ 290 painter.outlineColor = Color.red; 291 painter.fillColor = Color.black; 292 painter.drawRectangle(Point(0, 0), 200, 200); 293 } // end scope, calling `painter`'s destructor, drawing to the screen. 294 window.eventLoop(0); // handle events 295 } 296 --- 297 298 Painting is done based on two color properties, a pen and a brush. 299 300 At this time, the 2d drawing does not support alpha blending, except for the [Sprite] class. If you need that, use a 2d OpenGL context instead. 301 302 FIXME Add example of 2d opengl drawing here. 303 $(H3 $(ID topic-3d-drawing) 3d Drawing (or 2d with OpenGL)) 304 simpledisplay can create OpenGL contexts on your window. It works quite differently than 2d drawing. 305 306 Note that it is still possible to draw 2d on top of an OpenGL window, using the `draw` method, though I don't recommend it. 307 308 To start, you create a [SimpleWindow] with OpenGL enabled by passing the argument [OpenGlOptions.yes] to the constructor. 309 310 Next, you set [SimpleWindow.redrawOpenGlScene|window.redrawOpenGlScene] to a delegate which draws your frame. 311 312 To force a redraw of the scene, call [SimpleWindow.redrawOpenGlSceneNow|window.redrawOpenGlSceneNow()] or to queue a redraw after processing the next batch of pending events, use [SimpleWindow.redrawOpenGlSceneSoon|window.redrawOpenGlSceneSoon]. 313 314 simpledisplay supports both old-style `glBegin` and newer-style shader-based code all through its built-in bindings. See the next section of the docs to see a shader-based program. 315 316 This example program will draw a rectangle on your window using old-style OpenGL with a pulsating color: 317 318 --- 319 import arsd.simpledisplay; 320 321 void main() { 322 auto window = new SimpleWindow(800, 600, "opengl 1", OpenGlOptions.yes, Resizability.allowResizing); 323 324 float otherColor = 0.0; 325 float colorDelta = 0.05; 326 327 window.redrawOpenGlScene = delegate() { 328 glLoadIdentity(); 329 glBegin(GL_QUADS); 330 331 glColor3f(1.0, otherColor, 0); 332 glVertex3f(-0.8, -0.8, 0); 333 334 glColor3f(1.0, otherColor, 1.0); 335 glVertex3f(0.8, -0.8, 0); 336 337 glColor3f(0, 1.0, otherColor); 338 glVertex3f(0.8, 0.8, 0); 339 340 glColor3f(otherColor, 0, 1.0); 341 glVertex3f(-0.8, 0.8, 0); 342 343 glEnd(); 344 }; 345 346 window.eventLoop(50, () { 347 otherColor += colorDelta; 348 if(otherColor > 1.0) { 349 otherColor = 1.0; 350 colorDelta = -0.05; 351 } 352 if(otherColor < 0) { 353 otherColor = 0; 354 colorDelta = 0.05; 355 } 356 // at the end of the timer, we have to request a redraw 357 // or we won't see the changes. 358 window.redrawOpenGlSceneSoon(); 359 }); 360 } 361 --- 362 363 My [arsd.game] module has some helpers for using old-style opengl to make 2D windows too. See: [arsd.game.create2dWindow]. 364 $(H3 $(ID topic-modern-opengl) Modern OpenGL) 365 simpledisplay's opengl support, by default, is for "legacy" opengl. To use "modern" functions, you must opt-into them with a little more setup. But the library provides helpers for this too. 366 367 This example program shows how you can set up a shader to draw a rectangle: 368 369 --- 370 import arsd.simpledisplay; 371 372 // based on https://learnopengl.com/Getting-started/Hello-Triangle 373 374 void main() { 375 // First thing we do, before creating the window, is declare what version we want. 376 setOpenGLContextVersion(3, 3); 377 // turning off legacy compat is required to use version 3.3 and newer 378 openGLContextCompatible = false; 379 380 uint VAO; 381 OpenGlShader shader; 382 383 // then we can create the window. 384 auto window = new SimpleWindow(800, 600, "opengl 3", OpenGlOptions.yes, Resizability.allowResizing); 385 386 // additional setup needs to be done when it is visible, simpledisplay offers a property 387 // for exactly that: 388 window.visibleForTheFirstTime = delegate() { 389 // now with the window loaded, we can start loading the modern opengl functions. 390 391 // you MUST set the context first. 392 window.setAsCurrentOpenGlContext; 393 // then load the remainder of the library 394 gl3.loadDynamicLibrary(); 395 396 // now you can create the shaders, etc. 397 shader = new OpenGlShader( 398 OpenGlShader.Source(GL_VERTEX_SHADER, ` 399 #version 330 core 400 layout (location = 0) in vec3 aPos; 401 void main() { 402 gl_Position = vec4(aPos.x, aPos.y, aPos.z, 1.0); 403 } 404 `), 405 OpenGlShader.Source(GL_FRAGMENT_SHADER, ` 406 #version 330 core 407 out vec4 FragColor; 408 uniform vec4 mycolor; 409 void main() { 410 FragColor = mycolor; 411 } 412 `), 413 ); 414 415 // and do whatever other setup you want. 416 417 float[] vertices = [ 418 0.5f, 0.5f, 0.0f, // top right 419 0.5f, -0.5f, 0.0f, // bottom right 420 -0.5f, -0.5f, 0.0f, // bottom left 421 -0.5f, 0.5f, 0.0f // top left 422 ]; 423 uint[] indices = [ // note that we start from 0! 424 0, 1, 3, // first Triangle 425 1, 2, 3 // second Triangle 426 ]; 427 uint VBO, EBO; 428 glGenVertexArrays(1, &VAO); 429 // bind the Vertex Array Object first, then bind and set vertex buffer(s), and then configure vertex attributes(s). 430 glBindVertexArray(VAO); 431 432 glGenBuffers(1, &VBO); 433 glGenBuffers(1, &EBO); 434 435 glBindBuffer(GL_ARRAY_BUFFER, VBO); 436 glBufferDataSlice(GL_ARRAY_BUFFER, vertices, GL_STATIC_DRAW); 437 438 glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, EBO); 439 glBufferDataSlice(GL_ELEMENT_ARRAY_BUFFER, indices, GL_STATIC_DRAW); 440 441 glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, 3 * float.sizeof, null); 442 glEnableVertexAttribArray(0); 443 444 // the library will set the initial viewport and trigger our first draw, 445 // so these next two lines are NOT needed. they are just here as comments 446 // to show what would happen next. 447 448 // glViewport(0, 0, window.width, window.height); 449 // window.redrawOpenGlSceneNow(); 450 }; 451 452 // this delegate is called any time the window needs to be redrawn or if you call `window.redrawOpenGlSceneNow;` 453 // it is our render method. 454 window.redrawOpenGlScene = delegate() { 455 glClearColor(0.2f, 0.3f, 0.3f, 1.0f); 456 glClear(GL_COLOR_BUFFER_BIT); 457 458 glUseProgram(shader.shaderProgram); 459 460 // the shader helper class has methods to set uniforms too 461 shader.uniforms.mycolor.opAssign(1.0, 1.0, 0, 1.0); 462 463 glBindVertexArray(VAO); 464 glDrawElements(GL_TRIANGLES, 6, GL_UNSIGNED_INT, null); 465 }; 466 467 window.eventLoop(0); 468 } 469 --- 470 471 This program only draws the image once because that's all that is necessary, since it is static. If you want to do animation, you might set a pulse timer (which would be a fixed max fps, not necessarily consistent) or use a render loop in a separate thread. 472 473 $(H3 $(ID vulkan) Vulkan) 474 475 See a couple examples ported from GLFW to simpledisplay using the erupted vulkan bindings: 476 477 https://github.com/adamdruppe/VulkanizeDSdpy 478 479 https://github.com/adamdruppe/VulkanSdpyDemo/tree/demo 480 481 $(H3 $(ID topic-images) Displaying images) 482 You can also load PNG images using [arsd.png]. 483 484 --- 485 // dmd example.d simpledisplay.d color.d png.d 486 import arsd.simpledisplay; 487 import arsd.png; 488 489 void main() { 490 auto image = Image.fromMemoryImage(readPng("image.png")); 491 displayImage(image); 492 } 493 --- 494 495 Compile with `dmd example.d simpledisplay.d png.d`. 496 497 If you find an image file which is a valid png that [arsd.png] fails to load, please let me know. In the mean time of fixing the bug, you can probably convert the file into an easier-to-load format. Be sure to turn OFF png interlacing, as that isn't supported. Other things to try would be making the image smaller, or trying 24 bit truecolor mode with an alpha channel. 498 499 $(H3 $(ID topic-sprites) Sprites) 500 The [Sprite] class is used to make images on the display server for fast blitting to screen. This is especially important to use to support fast drawing of repeated images on a remote X11 link. 501 502 [Sprite] is also the only facility that currently supports alpha blending without using OpenGL . 503 504 $(H3 $(ID topic-clipboard) Clipboard) 505 The free functions [getClipboardText] and [setClipboardText] consist of simpledisplay's cross-platform clipboard support at this time. 506 507 It also has helpers for handling X-specific events. 508 509 $(H3 $(ID topic-dnd) Drag and Drop) 510 See [enableDragAndDrop] and [draggable]. 511 512 $(H3 $(ID topic-timers) Timers) 513 There are two timers in simpledisplay: one is the pulse timeout you can set on the call to `window.eventLoop`, and the other is a customizable class, [Timer]. 514 515 The pulse timeout is used by setting a non-zero interval as the first argument to `eventLoop` function and adding a zero-argument delegate to handle the pulse. 516 517 --- 518 import arsd.simpledisplay; 519 520 void main() { 521 auto window = new SimpleWindow(400, 400); 522 // every 100 ms, it will draw a random line 523 // on the window. 524 window.eventLoop(100, { 525 auto painter = window.draw(); 526 527 import std.random; 528 // random color 529 painter.outlineColor = Color(uniform(0, 256), uniform(0, 256), uniform(0, 256)); 530 // random line 531 painter.drawLine( 532 Point(uniform(0, window.width), uniform(0, window.height)), 533 Point(uniform(0, window.width), uniform(0, window.height))); 534 535 }); 536 } 537 --- 538 539 The `Timer` class works similarly, but is created separately from the event loop. (It still fires through the event loop, though.) You may make as many instances of `Timer` as you wish. 540 541 The pulse timer and instances of the [Timer] class may be combined at will. 542 543 --- 544 import arsd.simpledisplay; 545 546 void main() { 547 auto window = new SimpleWindow(400, 400); 548 auto timer = new Timer(1000, delegate { 549 auto painter = window.draw(); 550 painter.clear(); 551 }); 552 553 window.eventLoop(0); 554 } 555 --- 556 557 Timers are currently only implemented on Windows, using `SetTimer` and Linux, using `timerfd_create`. These deliver timeout messages through your application event loop. 558 559 $(H3 $(ID topic-os-helpers) OS-specific helpers) 560 simpledisplay carries a lot of code to help implement itself without extra dependencies, and much of this code is available for you too, so you may extend the functionality yourself. 561 562 See also: `xwindows.d` from my github. 563 564 $(H3 $(ID topic-os-extension) Extending with OS-specific functionality) 565 `handleNativeEvent` and `handleNativeGlobalEvent`. 566 567 $(H3 $(ID topic-integration) Integration with other libraries) 568 Integration with a third-party event loop is possible. 569 570 On Linux, you might want to support both terminal input and GUI input. You can do this by using simpledisplay together with eventloop.d and terminal.d. 571 572 $(H3 $(ID topic-guis) GUI widgets) 573 simpledisplay does not provide GUI widgets such as text areas, buttons, checkboxes, etc. It only gives basic windows, the ability to draw on it, receive input from it, and access native information for extension. You may write your own gui widgets with these, but you don't have to because I already did for you! 574 575 Download `minigui.d` from my github repository and add it to your project. minigui builds these things on top of simpledisplay and offers its own Window class (and subclasses) to use that wrap SimpleWindow, adding a new event and drawing model that is hookable by subwidgets, represented by their own classes. 576 577 Migrating to minigui from simpledisplay is often easy though, because they both use the same ScreenPainter API, and the same simpledisplay events are available, if you want them. (Though you may like using the minigui model, especially if you are familiar with writing web apps in the browser with Javascript.) 578 579 minigui still needs a lot of work to be finished at this time, but it already offers a number of useful classes. 580 581 $(H2 Platform-specific tips and tricks) 582 583 X_tips: 584 585 On X11, if you set an environment variable, `ARSD_SCALING_FACTOR`, you can control the per-monitor DPI scaling returned to the application. The format is `ARSD_SCALING_FACTOR=2;1`, for example, to set 2x scaling on your first monitor and 1x scaling on your second monitor. Support for this was added on March 22, 2022, the dub 10.7 release. 586 587 $(H4 apitrace) 588 589 Out of the box, simpledisplay might not work as expected in combination with 590 [apitrace](https://apitrace.github.io). 591 However it can be instructed to specifically load the GL/GLX wrapper libraries provided by apitrace instead of 592 the system libraries. This should restore the lost functionality. 593 594 $(NUMBERED_LIST 595 * Compile with `-version=apitrace`. 596 * When launching such a simpledisplay app, it must be able to locate the apitrace wrapper libraries. 597 * Running the app will generate an apitrace trace file. 598 It should print a log message similar to "apitrace: loaded into /directory" during startup. 599 ) 600 601 There are multiple ways to enable a simpledisplay app to locate the wrapper libraries. 602 603 One way to achieved this is by pointing the `LD_LIBRARY_PATH` environment variable to the directory containing 604 those wrappers. 605 606 ```sh 607 LD_LIBRARY_PATH=/path/to/apitrace/wrappers:$LD_LIBRARY_PATH ./myapp 608 609 # e.g. 610 LD_LIBRARY_PATH=/usr/lib/x86_64-linux-gnu/apitrace/wrappers:$LD_LIBRARY_PATH ./myapp 611 ``` 612 613 Alternatively, the simpledisplay app can also be launched via $(I apitrace). 614 615 ```sh 616 apitrace trace -a gl ./myapp 617 ``` 618 619 Another way that seems to work is to preload `glxtrace.so` through `LD_PRELOAD`. 620 621 ```sh 622 LD_PRELOAD=/path/to/apitrace/wrappers/glxtrace.so ./myapp 623 ``` 624 625 Windows_tips: 626 627 You can add icons or manifest files to your exe using a resource file. 628 629 To create a Windows .ico file, use the gimp or something. I'll write a helper 630 program later. 631 632 Create `yourapp.rc`: 633 634 ```rc 635 1 ICON filename.ico 636 CREATEPROCESS_MANIFEST_RESOURCE_ID RT_MANIFEST "YourApp.exe.manifest" 637 ``` 638 639 And `yourapp.exe.manifest`: 640 641 ```xml 642 <?xml version="1.0" encoding="UTF-8" standalone="yes"?> 643 <assembly xmlns="urn:schemas-microsoft-com:asm.v1" manifestVersion="1.0"> 644 <assemblyIdentity 645 version="1.0.0.0" 646 processorArchitecture="*" 647 name="CompanyName.ProductName.YourApplication" 648 type="win32" 649 /> 650 <description>Your application description here.</description> 651 <dependency> 652 <dependentAssembly> 653 <assemblyIdentity 654 type="win32" 655 name="Microsoft.Windows.Common-Controls" 656 version="6.0.0.0" 657 processorArchitecture="*" 658 publicKeyToken="6595b64144ccf1df" 659 language="*" 660 /> 661 </dependentAssembly> 662 </dependency> 663 <application xmlns="urn:schemas-microsoft-com:asm.v3"> 664 <windowsSettings> 665 <dpiAware xmlns="http://schemas.microsoft.com/SMI/2005/WindowsSettings">true/pm</dpiAware> <!-- old style --> 666 <dpiAwareness xmlns="http://schemas.microsoft.com/SMI/2016/WindowsSettings">PerMonitorV2</dpiAwareness> <!-- new style --> 667 <!-- Un-comment the line below to enable GDI-scaling in this project. This will enable text --> 668 <!-- to render crisply in DPI-unaware contexts --> 669 <!--<gdiScaling xmlns="http://schemas.microsoft.com/SMI/2017/WindowsSettings">true</gdiScaling>--> 670 </windowsSettings> 671 </application> 672 </assembly> 673 ``` 674 675 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`. 676 677 Doing this lets you opt into various new things since Windows XP. 678 679 See: https://docs.microsoft.com/en-us/windows/win32/SbsCs/application-manifests 680 681 $(H2 Tips) 682 683 $(H3 Name conflicts) 684 685 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: 686 687 --- 688 static import sdpy = arsd.simpledisplay; 689 import arsd.simpledisplay : SimpleWindow; 690 691 void main() { 692 auto window = new SimpleWindow(); 693 sdpy.EventLoop.get.run(); 694 } 695 --- 696 697 $(H2 $(ID developer-notes) Developer notes) 698 699 I don't have a Mac, so that code isn't maintained. I would like to have a Cocoa 700 implementation though. 701 702 The NativeSimpleWindowImplementation and NativeScreenPainterImplementation both 703 suck. If I was rewriting it, I wouldn't do it that way again. 704 705 This file must not have any more required dependencies. If you need bindings, add 706 them right to this file. Once it gets into druntime and is there for a while, remove 707 bindings from here to avoid conflicts (or put them in an appropriate version block 708 so it continues to just work on old dmd), but wait a couple releases before making the 709 transition so this module remains usable with older versions of dmd. 710 711 You may have optional dependencies if needed by putting them in version blocks or 712 template functions. You may also extend the module with other modules with UFCS without 713 actually editing this - that is nice to do if you can. 714 715 Try to make functions work the same way across operating systems. I typically make 716 it thinly wrap Windows, then emulate that on Linux. 717 718 A goal of this is to keep a gui hello world to less than 250 KB. This means avoiding 719 Phobos! So try to avoid it. 720 721 See more comments throughout the source. 722 723 I realize this file is fairly large, but over half that is just bindings at the bottom 724 or documentation at the top. Some of the classes are a bit big too, but hopefully easy 725 to understand. I suggest you jump around the source by looking for a particular 726 declaration you're interested in, like `class SimpleWindow` using your editor's search 727 function, then look at one piece at a time. 728 729 Authors: Adam D. Ruppe with the help of others. If you need help, please email me with 730 destructionator@gmail.com or find me on IRC. Our channel is #d on Freenode and you can 731 ping me, adam_d_ruppe, and I'll usually see it if I'm around. 732 733 I live in the eastern United States, so I will most likely not be around at night in 734 that US east timezone. 735 736 License: Copyright Adam D. Ruppe, 2011-2021. Released under the Boost Software License. 737 738 Building documentation: use my adrdox generator, `dub run adrdox`. 739 740 Examples: 741 742 $(DIV $(ID Event-example)) 743 $(H3 $(ID event-example) Event example) 744 This program creates a window and draws events inside them as they 745 happen, scrolling the text in the window as needed. Run this program 746 and experiment to get a feel for where basic input events take place 747 in the library. 748 749 --- 750 // dmd example.d simpledisplay.d color.d 751 import arsd.simpledisplay; 752 import std.conv; 753 754 void main() { 755 auto window = new SimpleWindow(Size(500, 500), "Event example - simpledisplay.d"); 756 757 int y = 0; 758 759 void addLine(string text) { 760 auto painter = window.draw(); 761 762 if(y + painter.fontHeight >= window.height) { 763 painter.scrollArea(Point(0, 0), window.width, window.height, 0, painter.fontHeight); 764 y -= painter.fontHeight; 765 } 766 767 painter.outlineColor = Color.red; 768 painter.fillColor = Color.black; 769 painter.drawRectangle(Point(0, y), window.width, painter.fontHeight); 770 771 painter.outlineColor = Color.white; 772 773 painter.drawText(Point(10, y), text); 774 775 y += painter.fontHeight; 776 } 777 778 window.eventLoop(1000, 779 () { 780 addLine("Timer went off!"); 781 }, 782 (KeyEvent event) { 783 addLine(to!string(event)); 784 }, 785 (MouseEvent event) { 786 addLine(to!string(event)); 787 }, 788 (dchar ch) { 789 addLine(to!string(ch)); 790 } 791 ); 792 } 793 --- 794 795 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. 796 797 $(COMMENT 798 This program displays a pie chart. Clicking on a color will increase its share of the pie. 799 800 --- 801 802 --- 803 ) 804 805 History: 806 Initial release in April 2011. 807 808 simpledisplay was stand alone until about 2015. It then added a dependency on [arsd.color] and changed its name to `arsd.simpledisplay`. 809 810 On March 4, 2023 (dub v11.0), it started importing [arsd.core] as well, making that a build-time requirement. 811 812 On October 5, 2024, apitrace support was added for Linux targets. 813 +/ 814 module arsd.simpledisplay; 815 816 // FIXME: tetris demo 817 // FIXME: space invaders demo 818 // FIXME: asteroids demo 819 820 /++ $(ID Pong-example) 821 $(H3 Pong) 822 823 This program creates a little Pong-like game. Player one is controlled 824 with the keyboard. Player two is controlled with the mouse. It demos 825 the pulse timer, event handling, and some basic drawing. 826 +/ 827 version(demos) 828 unittest { 829 // dmd example.d simpledisplay.d color.d 830 import arsd.simpledisplay; 831 832 enum paddleMovementSpeed = 8; 833 enum paddleHeight = 48; 834 835 void main() { 836 auto window = new SimpleWindow(600, 400, "Pong game!"); 837 838 int playerOnePosition, playerTwoPosition; 839 int playerOneMovement, playerTwoMovement; 840 int playerOneScore, playerTwoScore; 841 842 int ballX, ballY; 843 int ballDx, ballDy; 844 845 void serve() { 846 import std.random; 847 848 ballX = window.width / 2; 849 ballY = window.height / 2; 850 ballDx = uniform(-4, 4) * 3; 851 ballDy = uniform(-4, 4) * 3; 852 if(ballDx == 0) 853 ballDx = uniform(0, 2) == 0 ? 3 : -3; 854 } 855 856 serve(); 857 858 window.eventLoop(50, // set a 50 ms timer pulls 859 // This runs once per timer pulse 860 delegate () { 861 auto painter = window.draw(); 862 863 painter.clear(); 864 865 // Update everyone's motion 866 playerOnePosition += playerOneMovement; 867 playerTwoPosition += playerTwoMovement; 868 869 ballX += ballDx; 870 ballY += ballDy; 871 872 // Bounce off the top and bottom edges of the window 873 if(ballY + 7 >= window.height) 874 ballDy = -ballDy; 875 if(ballY - 8 <= 0) 876 ballDy = -ballDy; 877 878 // Bounce off the paddle, if it is in position 879 if(ballX - 8 <= 16) { 880 if(ballY + 7 > playerOnePosition && ballY - 8 < playerOnePosition + paddleHeight) { 881 ballDx = -ballDx + 1; // add some speed to keep it interesting 882 ballDy += playerOneMovement; // and y movement based on your controls too 883 ballX = 24; // move it past the paddle so it doesn't wiggle inside 884 } else { 885 // Missed it 886 playerTwoScore ++; 887 serve(); 888 } 889 } 890 891 if(ballX + 7 >= window.width - 16) { // do the same thing but for player 1 892 if(ballY + 7 > playerTwoPosition && ballY - 8 < playerTwoPosition + paddleHeight) { 893 ballDx = -ballDx - 1; 894 ballDy += playerTwoMovement; 895 ballX = window.width - 24; 896 } else { 897 // Missed it 898 playerOneScore ++; 899 serve(); 900 } 901 } 902 903 // Draw the paddles 904 painter.outlineColor = Color.black; 905 painter.drawLine(Point(16, playerOnePosition), Point(16, playerOnePosition + paddleHeight)); 906 painter.drawLine(Point(window.width - 16, playerTwoPosition), Point(window.width - 16, playerTwoPosition + paddleHeight)); 907 908 // Draw the ball 909 painter.fillColor = Color.red; 910 painter.outlineColor = Color.yellow; 911 painter.drawEllipse(Point(ballX - 8, ballY - 8), Point(ballX + 7, ballY + 7)); 912 913 // Draw the score 914 painter.outlineColor = Color.blue; 915 import std.conv; 916 painter.drawText(Point(64, 4), to!string(playerOneScore)); 917 painter.drawText(Point(window.width - 64, 4), to!string(playerTwoScore)); 918 919 }, 920 delegate (KeyEvent event) { 921 // Player 1's controls are the arrow keys on the keyboard 922 if(event.key == Key.Down) 923 playerOneMovement = event.pressed ? paddleMovementSpeed : 0; 924 if(event.key == Key.Up) 925 playerOneMovement = event.pressed ? -paddleMovementSpeed : 0; 926 927 }, 928 delegate (MouseEvent event) { 929 // Player 2's controls are mouse movement while the left button is held down 930 if(event.type == MouseEventType.motion && (event.modifierState & ModifierState.leftButtonDown)) { 931 if(event.dy > 0) 932 playerTwoMovement = paddleMovementSpeed; 933 else if(event.dy < 0) 934 playerTwoMovement = -paddleMovementSpeed; 935 } else { 936 playerTwoMovement = 0; 937 } 938 } 939 ); 940 } 941 } 942 943 /++ $(H3 $(ID example-minesweeper) Minesweeper) 944 945 This minesweeper demo shows how we can implement another classic 946 game with simpledisplay and shows some mouse input and basic output 947 code. 948 +/ 949 version(demos) 950 unittest { 951 import arsd.simpledisplay; 952 953 enum GameSquare { 954 mine = 0, 955 clear, 956 m1, m2, m3, m4, m5, m6, m7, m8 957 } 958 959 enum UserSquare { 960 unknown, 961 revealed, 962 flagged, 963 questioned 964 } 965 966 enum GameState { 967 inProgress, 968 lose, 969 win 970 } 971 972 GameSquare[] board; 973 UserSquare[] userState; 974 GameState gameState; 975 int boardWidth; 976 int boardHeight; 977 978 bool isMine(int x, int y) { 979 if(x < 0 || y < 0 || x >= boardWidth || y >= boardHeight) 980 return false; 981 return board[y * boardWidth + x] == GameSquare.mine; 982 } 983 984 GameState reveal(int x, int y) { 985 if(board[y * boardWidth + x] == GameSquare.clear) { 986 floodFill(userState, boardWidth, boardHeight, 987 UserSquare.unknown, UserSquare.revealed, 988 x, y, 989 (x, y) { 990 if(board[y * boardWidth + x] == GameSquare.clear) 991 return true; 992 else { 993 userState[y * boardWidth + x] = UserSquare.revealed; 994 return false; 995 } 996 }); 997 } else { 998 userState[y * boardWidth + x] = UserSquare.revealed; 999 if(isMine(x, y)) 1000 return GameState.lose; 1001 } 1002 1003 foreach(state; userState) { 1004 if(state == UserSquare.unknown || state == UserSquare.questioned) 1005 return GameState.inProgress; 1006 } 1007 1008 return GameState.win; 1009 } 1010 1011 void initializeBoard(int width, int height, int numberOfMines) { 1012 boardWidth = width; 1013 boardHeight = height; 1014 board.length = width * height; 1015 1016 userState.length = width * height; 1017 userState[] = UserSquare.unknown; 1018 1019 import std.algorithm, std.random, std.range; 1020 1021 board[] = GameSquare.clear; 1022 1023 foreach(minePosition; randomSample(iota(0, board.length), numberOfMines)) 1024 board[minePosition] = GameSquare.mine; 1025 1026 int x; 1027 int y; 1028 foreach(idx, ref square; board) { 1029 if(square == GameSquare.clear) { 1030 int danger = 0; 1031 danger += isMine(x-1, y-1)?1:0; 1032 danger += isMine(x-1, y)?1:0; 1033 danger += isMine(x-1, y+1)?1:0; 1034 danger += isMine(x, y-1)?1:0; 1035 danger += isMine(x, y+1)?1:0; 1036 danger += isMine(x+1, y-1)?1:0; 1037 danger += isMine(x+1, y)?1:0; 1038 danger += isMine(x+1, y+1)?1:0; 1039 1040 square = cast(GameSquare) (danger + 1); 1041 } 1042 1043 x++; 1044 if(x == width) { 1045 x = 0; 1046 y++; 1047 } 1048 } 1049 } 1050 1051 void redraw(SimpleWindow window) { 1052 import std.conv; 1053 1054 auto painter = window.draw(); 1055 1056 painter.clear(); 1057 1058 final switch(gameState) with(GameState) { 1059 case inProgress: 1060 break; 1061 case win: 1062 painter.fillColor = Color.green; 1063 painter.drawRectangle(Point(0, 0), window.width, window.height); 1064 return; 1065 case lose: 1066 painter.fillColor = Color.red; 1067 painter.drawRectangle(Point(0, 0), window.width, window.height); 1068 return; 1069 } 1070 1071 int x = 0; 1072 int y = 0; 1073 1074 foreach(idx, square; board) { 1075 auto state = userState[idx]; 1076 1077 final switch(state) with(UserSquare) { 1078 case unknown: 1079 painter.outlineColor = Color.black; 1080 painter.fillColor = Color(128,128,128); 1081 1082 painter.drawRectangle( 1083 Point(x * 20, y * 20), 1084 20, 20 1085 ); 1086 break; 1087 case revealed: 1088 if(square == GameSquare.clear) { 1089 painter.outlineColor = Color.white; 1090 painter.fillColor = Color.white; 1091 1092 painter.drawRectangle( 1093 Point(x * 20, y * 20), 1094 20, 20 1095 ); 1096 } else { 1097 painter.outlineColor = Color.black; 1098 painter.fillColor = Color.white; 1099 1100 painter.drawText( 1101 Point(x * 20, y * 20), 1102 to!string(square)[1..2], 1103 Point(x * 20 + 20, y * 20 + 20), 1104 TextAlignment.Center | TextAlignment.VerticalCenter); 1105 } 1106 break; 1107 case flagged: 1108 painter.outlineColor = Color.black; 1109 painter.fillColor = Color.red; 1110 painter.drawRectangle( 1111 Point(x * 20, y * 20), 1112 20, 20 1113 ); 1114 break; 1115 case questioned: 1116 painter.outlineColor = Color.black; 1117 painter.fillColor = Color.yellow; 1118 painter.drawRectangle( 1119 Point(x * 20, y * 20), 1120 20, 20 1121 ); 1122 break; 1123 } 1124 1125 x++; 1126 if(x == boardWidth) { 1127 x = 0; 1128 y++; 1129 } 1130 } 1131 1132 } 1133 1134 void main() { 1135 auto window = new SimpleWindow(200, 200); 1136 1137 initializeBoard(10, 10, 10); 1138 1139 redraw(window); 1140 window.eventLoop(0, 1141 delegate (MouseEvent me) { 1142 if(me.type != MouseEventType.buttonPressed) 1143 return; 1144 auto x = me.x / 20; 1145 auto y = me.y / 20; 1146 if(x >= 0 && x < boardWidth && y >= 0 && y < boardHeight) { 1147 if(me.button == MouseButton.left) { 1148 gameState = reveal(x, y); 1149 } else { 1150 userState[y*boardWidth+x] = UserSquare.flagged; 1151 } 1152 redraw(window); 1153 } 1154 } 1155 ); 1156 } 1157 } 1158 1159 // FIXME: tetris demo 1160 // FIXME: space invaders demo 1161 // FIXME: asteroids demo 1162 1163 import arsd.core; 1164 1165 version(D_OpenD) { 1166 version(OSX) 1167 version=OSXCocoa; 1168 version(iOS) 1169 version=OSXCocoa; 1170 } else { 1171 version(OSX) version(DigitalMars) version=OSXCocoa; 1172 } 1173 1174 1175 1176 version(Emscripten) { 1177 version=allow_unimplemented_features; 1178 version=without_opengl; 1179 } 1180 1181 1182 version(OSXCocoa) { 1183 version=without_opengl; 1184 version=allow_unimplemented_features; 1185 // version=OSXCocoa; 1186 // pragma(linkerDirective, "-framework Cocoa"); 1187 } 1188 1189 version(without_opengl) { 1190 enum SdpyIsUsingIVGLBinds = false; 1191 } else /*version(Posix)*/ { 1192 static if (__traits(compiles, (){import iv.glbinds;})) { 1193 enum SdpyIsUsingIVGLBinds = true; 1194 public import iv.glbinds; 1195 //pragma(msg, "SDPY: using iv.glbinds"); 1196 } else { 1197 enum SdpyIsUsingIVGLBinds = false; 1198 } 1199 //} else { 1200 // enum SdpyIsUsingIVGLBinds = false; 1201 } 1202 1203 version(Windows) { 1204 //import core.sys.windows.windows; 1205 import core.sys.windows.winnls; 1206 import core.sys.windows.windef; 1207 import core.sys.windows.basetyps; 1208 import core.sys.windows.winbase; 1209 import core.sys.windows.winuser; 1210 import core.sys.windows.shellapi; 1211 import core.sys.windows.wingdi; 1212 static import gdi = core.sys.windows.wingdi; // so i 1213 1214 pragma(lib, "gdi32"); 1215 pragma(lib, "user32"); 1216 1217 // for AlphaBlend... a breaking change.... 1218 version(CRuntime_DigitalMars) { } else 1219 pragma(lib, "msimg32"); 1220 1221 // core.sys.windows.dwmapi 1222 private { 1223 /++ 1224 See_also: 1225 https://learn.microsoft.com/en-us/windows/win32/api/dwmapi/nf-dwmapi-dwmgetwindowattribute 1226 +/ 1227 extern extern(Windows) HRESULT DwmGetWindowAttribute( 1228 HWND hwnd, 1229 DWORD dwAttribute, 1230 PVOID pvAttribute, 1231 DWORD cbAttribute 1232 ) nothrow @nogc; 1233 1234 /++ 1235 See_also: 1236 https://learn.microsoft.com/en-us/windows/win32/api/dwmapi/nf-dwmapi-dwmsetwindowattribute 1237 +/ 1238 extern extern(Windows) HRESULT DwmSetWindowAttribute( 1239 HWND hwnd, 1240 DWORD dwAttribute, 1241 LPCVOID pvAttribute, 1242 DWORD cbAttribute 1243 ) nothrow @nogc; 1244 1245 /++ 1246 See_also: 1247 https://learn.microsoft.com/en-us/windows/win32/api/dwmapi/ne-dwmapi-dwmwindowattribute 1248 +/ 1249 enum DWMWINDOWATTRIBUTE { 1250 // incomplete, only declare what we need 1251 1252 /++ 1253 Usage: 1254 pvAttribute → `DWM_WINDOW_CORNER_PREFERENCE*` 1255 1256 $(NOTE 1257 Requires Windows 11 or later. 1258 ) 1259 +/ 1260 DWMWA_WINDOW_CORNER_PREFERENCE = 33, 1261 } 1262 1263 /++ 1264 See_also: 1265 https://learn.microsoft.com/en-us/windows/win32/api/dwmapi/ne-dwmapi-dwm_window_corner_preference 1266 +/ 1267 enum DWM_WINDOW_CORNER_PREFERENCE { 1268 /// System decision 1269 DWMWCP_DEFAULT = 0, 1270 1271 /// Never 1272 DWMWCP_DONOTROUND = 1, 1273 1274 // If "appropriate" 1275 DWMWCP_ROUND = 2, 1276 1277 // If "appropriate", but smaller radius 1278 DWMWCP_ROUNDSMALL = 3 1279 } 1280 1281 bool fromDWM( 1282 DWM_WINDOW_CORNER_PREFERENCE value, 1283 out CornerStyle result, 1284 ) @safe pure nothrow @nogc { 1285 switch (value) with (DWM_WINDOW_CORNER_PREFERENCE) { 1286 case DWMWCP_DEFAULT: 1287 result = CornerStyle.automatic; 1288 return true; 1289 case DWMWCP_DONOTROUND: 1290 result = CornerStyle.rectangular; 1291 return true; 1292 case DWMWCP_ROUND: 1293 result = CornerStyle.rounded; 1294 return true; 1295 case DWMWCP_ROUNDSMALL: 1296 result = CornerStyle.roundedSlightly; 1297 return true; 1298 default: 1299 return false; 1300 } 1301 } 1302 1303 bool toDWM( 1304 CornerStyle value, 1305 out DWM_WINDOW_CORNER_PREFERENCE result, 1306 ) @safe pure nothrow @nogc { 1307 final switch (value) with (DWM_WINDOW_CORNER_PREFERENCE) { 1308 case CornerStyle.automatic: 1309 result = DWMWCP_DEFAULT; 1310 return true; 1311 case CornerStyle.rectangular: 1312 result = DWMWCP_DONOTROUND; 1313 return true; 1314 case CornerStyle.rounded: 1315 result = DWMWCP_ROUND; 1316 return true; 1317 case CornerStyle.roundedSlightly: 1318 result = DWMWCP_ROUNDSMALL; 1319 return true; 1320 } 1321 } 1322 1323 pragma(lib, "dwmapi"); 1324 } 1325 } else version(Emscripten) { 1326 } else version (linux) { 1327 //k8: this is hack for rdmd. sorry. 1328 static import core.sys.linux.epoll; 1329 static import core.sys.linux.timerfd; 1330 } 1331 1332 1333 // FIXME: icons on Windows don't look quite right, I think the transparency mask is off. 1334 1335 // http://wiki.dlang.org/Simpledisplay.d 1336 1337 // see : http://www.sbin.org/doc/Xlib/chapt_09.html section on Keyboard Preferences re: scroll lock led 1338 1339 // Cool stuff: I want right alt and scroll lock to do different stuff for personal use. maybe even right ctrl 1340 // but can i control the scroll lock led 1341 1342 1343 // Note: if you are using Image on X, you might want to do: 1344 /* 1345 static if(UsingSimpledisplayX11) { 1346 if(!Image.impl.xshmAvailable) { 1347 // the images will use the slower XPutImage, you might 1348 // want to consider an alternative method to get better speed 1349 } 1350 } 1351 1352 If the shared memory extension is available though, simpledisplay uses it 1353 for a significant speed boost whenever you draw large Images. 1354 */ 1355 1356 // 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. 1357 1358 // WARNING: if you are using with_eventloop, don't forget to call XFlush(XDisplayConnection.get()); before calling loop()! 1359 1360 /* 1361 Biggest FIXME: 1362 make sure the key event numbers match between X and Windows OR provide symbolic constants on each system 1363 1364 clean up opengl contexts when their windows close 1365 1366 fix resizing the bitmaps/pixmaps 1367 */ 1368 1369 // BTW on Windows: 1370 // -L/SUBSYSTEM:WINDOWS:5.0 1371 // to dmd will make a nice windows binary w/o a console if you want that. 1372 1373 /* 1374 Stuff to add: 1375 1376 use multibyte functions everywhere we can 1377 1378 OpenGL windows 1379 more event stuff 1380 extremely basic windows w/ no decoration for tooltips, splash screens, etc. 1381 1382 1383 resizeEvent 1384 and make the windows non-resizable by default, 1385 or perhaps stretched (if I can find something in X like StretchBlt) 1386 1387 take a screenshot function! 1388 1389 Pens and brushes? 1390 Maybe a global event loop? 1391 1392 Mouse deltas 1393 Key items 1394 */ 1395 1396 /* 1397 From MSDN: 1398 1399 You can also use the GET_X_LPARAM or GET_Y_LPARAM macro to extract the x- or y-coordinate. 1400 1401 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. 1402 1403 */ 1404 1405 version(Emscripten) { 1406 1407 } else version(linux) { 1408 version = X11; 1409 version(without_libnotify) { 1410 // we cool 1411 } 1412 else 1413 version = libnotify; 1414 } 1415 1416 version(libnotify) { 1417 pragma(lib, "dl"); 1418 import core.sys.posix.dlfcn; 1419 1420 void delegate()[int] libnotify_action_delegates; 1421 int libnotify_action_delegates_count; 1422 extern(C) static void libnotify_action_callback_sdpy(void* notification, char* action, void* user_data) { 1423 auto idx = cast(int) user_data; 1424 if(auto dgptr = idx in libnotify_action_delegates) { 1425 (*dgptr)(); 1426 libnotify_action_delegates.remove(idx); 1427 } 1428 } 1429 1430 struct C_DynamicLibrary { 1431 void* handle; 1432 this(string name) { 1433 handle = dlopen((name ~ "\0").ptr, RTLD_NOW); 1434 if(handle is null) 1435 throw new Exception("dlopen"); 1436 } 1437 1438 void close() { 1439 dlclose(handle); 1440 } 1441 1442 ~this() { 1443 // close 1444 } 1445 1446 // FIXME: this looks up by name every time.... 1447 template call(string func, Ret, Args...) { 1448 extern(C) Ret function(Args) fptr; 1449 typeof(fptr) call() { 1450 fptr = cast(typeof(fptr)) dlsym(handle, func); 1451 return fptr; 1452 } 1453 } 1454 } 1455 1456 C_DynamicLibrary* libnotify; 1457 } 1458 1459 version(OSX) { 1460 version(OSXCocoa) {} 1461 else { version = X11; } 1462 } 1463 //version = OSXCocoa; // this was written by KennyTM 1464 version(FreeBSD) 1465 version = X11; 1466 version(Solaris) 1467 version = X11; 1468 1469 version(X11) { 1470 version(without_xft) {} 1471 else version=with_xft; 1472 } 1473 1474 void featureNotImplemented()() { 1475 version(allow_unimplemented_features) 1476 throw new NotYetImplementedException(); 1477 else 1478 static assert(0); 1479 } 1480 1481 // these are so the static asserts don't trigger unless you want to 1482 // add support to it for an OS 1483 version(Windows) 1484 version = with_timer; 1485 version(linux) 1486 version = with_timer; 1487 version(OSXCocoa) 1488 version = with_timer; 1489 1490 version(with_timer) 1491 enum bool SimpledisplayTimerAvailable = true; 1492 else 1493 enum bool SimpledisplayTimerAvailable = false; 1494 1495 /// 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. 1496 version(Windows) 1497 enum bool UsingSimpledisplayWindows = true; 1498 else 1499 enum bool UsingSimpledisplayWindows = false; 1500 1501 /// 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. 1502 version(X11) 1503 enum bool UsingSimpledisplayX11 = true; 1504 else 1505 enum bool UsingSimpledisplayX11 = false; 1506 1507 /// 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. 1508 version(OSXCocoa) 1509 enum bool UsingSimpledisplayCocoa = true; 1510 else 1511 enum bool UsingSimpledisplayCocoa = false; 1512 1513 /// Does this platform support multiple windows? If not, trying to create another will cause it to throw an exception. 1514 version(Windows) 1515 enum multipleWindowsSupported = true; 1516 else version(Emscripten) 1517 enum multipleWindowsSupported = false; 1518 else version(X11) 1519 enum multipleWindowsSupported = true; 1520 else version(OSXCocoa) 1521 enum multipleWindowsSupported = true; 1522 else 1523 static assert(0); 1524 1525 version(without_opengl) 1526 enum bool OpenGlEnabled = false; 1527 else 1528 enum bool OpenGlEnabled = true; 1529 1530 /++ 1531 Adds the necessary pragmas to your application to use the Windows gui subsystem. 1532 If you mix this in above your `main` function, you no longer need to use the linker 1533 flags explicitly. It does the necessary version blocks for various compilers and runtimes. 1534 1535 It does nothing if not compiling for Windows, so you need not version it out yourself. 1536 1537 Please note that Windows gui subsystem applications must NOT use std.stdio's stdout and 1538 stderr writeln. It will fail and throw an exception. 1539 1540 This will NOT work with plain `dmd` on Windows; you must use `dmd -m32mscoff` or `dmd -m64`. 1541 1542 History: 1543 Added November 24, 2021 (dub v10.4) 1544 +/ 1545 mixin template EnableWindowsSubsystem() { 1546 version(Windows) 1547 version(CRuntime_Microsoft) { 1548 pragma(linkerDirective, "/subsystem:windows"); 1549 version(LDC) 1550 pragma(linkerDirective, "/entry:wmainCRTStartup"); 1551 else 1552 pragma(linkerDirective, "/entry:mainCRTStartup"); 1553 } 1554 } 1555 1556 1557 /++ 1558 After selecting a type from [WindowTypes], you may further customize 1559 its behavior by setting one or more of these flags. 1560 1561 1562 The different window types have different meanings of `normal`. If the 1563 window type already is a good match for what you want to do, you should 1564 just use [WindowFlags.normal], the default, which will do the right thing 1565 for your users. 1566 1567 The window flags will not always be honored by the operating system 1568 and window managers; they are hints, not commands. 1569 +/ 1570 enum WindowFlags : int { 1571 normal = 0, /// 1572 skipTaskbar = 1, /// 1573 alwaysOnTop = 2, /// 1574 alwaysOnBottom = 4, /// 1575 cannotBeActivated = 8, /// 1576 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. 1577 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. 1578 /++ 1579 Sets the window as a short-lived child of its parent, but unlike an ordinary child, 1580 it is still a top-level window. This should NOT be set separately for most window types. 1581 1582 A transient window will not keep the application open if its main window closes. 1583 1584 $(PITFALL This may not be correctly implemented and its behavior is subject to change.) 1585 1586 1587 From the ICCM: 1588 1589 $(BLOCKQUOTE 1590 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. 1591 1592 $(CITE https://tronche.com/gui/x/icccm/sec-4.html) 1593 ) 1594 1595 So if you are using a window type that already describes this like [WindowTypes.dropdownMenu] etc., you should not use this flag. 1596 1597 History: 1598 Added February 23, 2021 but not yet stabilized. 1599 +/ 1600 transient = 64, 1601 /++ 1602 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. 1603 1604 This is currently only used for the WM_TAKE_FOCUS protocol on X11 at this time. 1605 1606 History: 1607 Added April 1, 2022 1608 +/ 1609 managesChildWindowFocus = 128, 1610 1611 /++ 1612 +/ 1613 overrideRedirect = 256, 1614 1615 dontAutoShow = 0x1000_0000, /// Don't automatically show window after creation; you will have to call `show()` manually. 1616 } 1617 1618 /++ 1619 When creating a window, you can pass a type to SimpleWindow's constructor, 1620 then further customize the window by changing `WindowFlags`. 1621 1622 1623 You should mostly only need [normal], [undecorated], and [eventOnly] for normal 1624 use. The others are there to build a foundation for a higher level GUI toolkit, 1625 but are themselves not as high level as you might think from their names. 1626 1627 This list is based on the EMWH spec for X11. 1628 http://standards.freedesktop.org/wm-spec/1.4/ar01s05.html#idm139704063786896 1629 +/ 1630 enum WindowTypes : int { 1631 /// An ordinary application window. 1632 normal, 1633 /// 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. 1634 undecorated, 1635 /// 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. 1636 eventOnly, 1637 /// A drop down menu, such as from a menu bar 1638 dropdownMenu, 1639 /// A popup menu, such as from a right click 1640 popupMenu, 1641 /// A popup bubble notification 1642 notification, 1643 /* 1644 menu, /// a tearable menu bar (not override-redirect - contrast to popups) 1645 toolbar, /// a tearable menu bar (not override-redirect) 1646 splashScreen, /// a loading splash screen for your application 1647 desktop, /// 1648 dockOrPanel, /// think taskbar 1649 utility, /// a palette or something 1650 */ 1651 /// A tiny window showing temporary help text or something. 1652 tooltip, 1653 /// only supported on X; will assert fail elsewhere 1654 dnd, 1655 /// can also be used for other auto-complete presentations 1656 comboBoxDropdown, 1657 /// a dialog box of some sort 1658 dialog, 1659 /// a child nested inside the parent. You must pass a parent window to the ctor 1660 nestedChild, 1661 1662 /++ 1663 The type you get when you pass in an existing browser handle, which means most 1664 of simpledisplay's fancy things will not be done since they were never set up. 1665 1666 Using this to the main SimpleWindow constructor explicitly will trigger an assertion 1667 failure; you should use the existing handle constructor. 1668 1669 History: 1670 Added November 17, 2022 (previously it would have type `normal`) 1671 +/ 1672 minimallyWrapped 1673 } 1674 1675 1676 private __gshared ushort sdpyOpenGLContextVersion = 0; // default: use legacy call 1677 private __gshared bool sdpyOpenGLContextCompatible = true; // default: allow "deprecated" features 1678 private __gshared char* sdpyWindowClassStr = null; 1679 private __gshared bool sdpyOpenGLContextAllowFallback = false; 1680 1681 /** 1682 Set OpenGL context version to use. This has no effect on non-OpenGL windows. 1683 You may want to change context version if you want to use advanced shaders or 1684 other modern OpenGL techinques. This setting doesn't affect already created 1685 windows. You may use version 2.1 as your default, which should be supported 1686 by any box since 2006, so seems to be a reasonable choice. 1687 1688 Note that by default version is set to `0`, which forces SimpleDisplay to use 1689 old context creation code without any version specified. This is the safest 1690 way to init OpenGL, but it may not give you access to advanced features. 1691 1692 See available OpenGL versions here: https://en.wikipedia.org/wiki/OpenGL 1693 */ 1694 void setOpenGLContextVersion() (ubyte hi, ubyte lo) { sdpyOpenGLContextVersion = cast(ushort)(hi<<8|lo); } 1695 1696 /** 1697 Set OpenGL context mode. Modern (3.0+) OpenGL versions deprecated old fixed 1698 pipeline functions, and without "compatible" mode you won't be able to use 1699 your old non-shader-based code with such contexts. By default SimpleDisplay 1700 creates compatible context, so you can gradually upgrade your OpenGL code if 1701 you want to (or leave it as is, as it should "just work"). 1702 */ 1703 @property void openGLContextCompatible() (bool v) { sdpyOpenGLContextCompatible = v; } 1704 1705 /** 1706 Set to `true` to allow creating OpenGL context with lower version than requested 1707 instead of throwing. If fallback was activated (or legacy OpenGL was requested), 1708 `openGLContextFallbackActivated()` will return `true`. 1709 */ 1710 @property void openGLContextAllowFallback() (bool v) { sdpyOpenGLContextAllowFallback = v; } 1711 1712 /** 1713 After creating OpenGL window, you can check this to see if you got only "legacy" OpenGL context. 1714 */ 1715 @property bool openGLContextFallbackActivated() () { return (sdpyOpenGLContextVersion == 0); } 1716 1717 /++ 1718 History: 1719 Added April 24, 2023 (dub v11.0) 1720 +/ 1721 version(without_opengl) {} else 1722 auto openGLCurrentContext() { 1723 version(Windows) 1724 return wglGetCurrentContext(); 1725 else 1726 return glXGetCurrentContext(); 1727 } 1728 1729 1730 /** 1731 Set window class name for all following `new SimpleWindow()` calls. 1732 1733 WARNING! For Windows, you should set your class name before creating any 1734 window, and NEVER change it after that! 1735 */ 1736 void sdpyWindowClass (const(char)[] v) { 1737 import core.stdc.stdlib : realloc; 1738 if (v.length == 0) v = "SimpleDisplayWindow"; 1739 sdpyWindowClassStr = cast(char*)realloc(sdpyWindowClassStr, v.length+1); 1740 if (sdpyWindowClassStr is null) return; // oops 1741 sdpyWindowClassStr[0..v.length+1] = 0; 1742 sdpyWindowClassStr[0..v.length] = v[]; 1743 } 1744 1745 /** 1746 Get current window class name. 1747 */ 1748 string sdpyWindowClass () @trusted { 1749 if (sdpyWindowClassStr is null) return null; 1750 foreach (immutable idx; 0..size_t.max-1) { 1751 if (sdpyWindowClassStr[idx] == 0) return sdpyWindowClassStr[0..idx].idup; 1752 } 1753 return null; 1754 } 1755 1756 /++ 1757 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. 1758 1759 If you want per-monitor dpi values, check [SimpleWindow.actualDpi], but you can fall back to this if it returns 0. 1760 +/ 1761 float[2] getDpi() { 1762 float[2] dpi; 1763 version(Windows) { 1764 HDC screen = GetDC(null); 1765 dpi[0] = GetDeviceCaps(screen, LOGPIXELSX); 1766 dpi[1] = GetDeviceCaps(screen, LOGPIXELSY); 1767 } else version(X11) { 1768 auto display = XDisplayConnection.get; 1769 auto screen = DefaultScreen(display); 1770 1771 void fallback() { 1772 /+ 1773 // 25.4 millimeters in an inch... 1774 dpi[0] = cast(float) DisplayWidth(display, screen) / DisplayWidthMM(display, screen) * 25.4; 1775 dpi[1] = cast(float) DisplayHeight(display, screen) / DisplayHeightMM(display, screen) * 25.4; 1776 +/ 1777 1778 // the physical size isn't actually as important as the logical size since this is 1779 // all about scaling really 1780 dpi[0] = 96; 1781 dpi[1] = 96; 1782 } 1783 1784 auto xft = getXftDpi(); 1785 if(xft is float.init) 1786 fallback(); 1787 else { 1788 dpi[0] = xft; 1789 dpi[1] = xft; 1790 } 1791 } 1792 1793 return dpi; 1794 } 1795 1796 version(X11) 1797 float getXftDpi() { 1798 auto display = XDisplayConnection.get; 1799 1800 char* resourceString = XResourceManagerString(display); 1801 XrmInitialize(); 1802 1803 if (resourceString) { 1804 auto db = XrmGetStringDatabase(resourceString); 1805 XrmValue value; 1806 char* type; 1807 if (XrmGetResource(db, "Xft.dpi", "String", &type, &value) == true) { 1808 if (value.addr) { 1809 import core.stdc.stdlib; 1810 return atof(cast(char*) value.addr); 1811 } 1812 } 1813 } 1814 1815 return float.init; 1816 } 1817 1818 /++ 1819 Implementation used by [SimpleWindow.takeScreenshot]. 1820 1821 Params: 1822 handle = the native window handle. If `NativeWindowHandle.init`, it will attempt to get the whole screen. 1823 width = the width of the image you wish to capture. If 0, it will attempt to capture the full width of the target. 1824 height = the height of the image you wish to capture. If 0, it will attempt to capture the full height of the target. 1825 x = the x-offset of the image to capture, from the left. 1826 y = the y-offset of the image to capture, from the top. 1827 1828 History: 1829 Added on March 14, 2021 1830 1831 Documented public on September 23, 2021 with full support for null params (dub 10.3) 1832 1833 +/ 1834 TrueColorImage trueColorImageFromNativeHandle(PaintingHandle handle, int width = 0, int height = 0, int x = 0, int y = 0) { 1835 TrueColorImage got; 1836 version(X11) { 1837 auto display = XDisplayConnection.get; 1838 if(handle == 0) 1839 handle = RootWindow(display, DefaultScreen(display)); 1840 1841 if(width == 0 || height == 0) { 1842 Window root; 1843 int xpos, ypos; 1844 uint widthret, heightret, borderret, depthret; 1845 XGetGeometry(display, handle, &root, &xpos, &ypos, &widthret, &heightret, &borderret, &depthret); 1846 1847 if(width == 0) 1848 width = widthret; 1849 if(height == 0) 1850 height = heightret; 1851 } 1852 1853 auto image = XGetImage(display, handle, x, y, width, height, (cast(c_ulong) ~0) /*AllPlanes*/, ImageFormat.ZPixmap); 1854 1855 // https://github.com/adamdruppe/arsd/issues/98 1856 1857 auto i = new Image(image); 1858 got = i.toTrueColorImage(); 1859 1860 XDestroyImage(image); 1861 } else version(Windows) { 1862 auto hdc = GetDC(handle); 1863 scope(exit) ReleaseDC(handle, hdc); 1864 1865 if(width == 0 || height == 0) { 1866 BITMAP bmHeader; 1867 auto bm = GetCurrentObject(hdc, OBJ_BITMAP); 1868 GetObject(bm, BITMAP.sizeof, &bmHeader); 1869 if(width == 0) 1870 width = bmHeader.bmWidth; 1871 if(height == 0) 1872 height = bmHeader.bmHeight; 1873 } 1874 1875 auto i = new Image(width, height); 1876 HDC hdcMem = CreateCompatibleDC(hdc); 1877 HBITMAP hbmOld = SelectObject(hdcMem, i.handle); 1878 BitBlt(hdcMem, x, y, width, height, hdc, 0, 0, SRCCOPY); 1879 SelectObject(hdcMem, hbmOld); 1880 DeleteDC(hdcMem); 1881 1882 got = i.toTrueColorImage(); 1883 } else featureNotImplemented(); 1884 1885 return got; 1886 } 1887 1888 version(Windows) extern(Windows) private alias SetProcessDpiAwarenessContext_t = BOOL function(HANDLE); 1889 version(Windows) extern(Windows) private __gshared UINT function(HWND) GetDpiForWindow; 1890 version(Windows) extern(Windows) private __gshared BOOL function(UINT, UINT, PVOID, UINT, UINT) SystemParametersInfoForDpi; 1891 1892 version(Windows) 1893 shared static this() { 1894 auto lib = LoadLibrary("User32.dll"); 1895 if(lib is null) 1896 return; 1897 //scope(exit) 1898 //FreeLibrary(lib); 1899 1900 SetProcessDpiAwarenessContext_t SetProcessDpiAwarenessContext = cast(SetProcessDpiAwarenessContext_t) GetProcAddress(lib, "SetProcessDpiAwarenessContext"); 1901 1902 if(SetProcessDpiAwarenessContext is null) 1903 return; 1904 1905 enum DPI_AWARENESS_CONTEXT_PER_MONITOR_AWARE_V2 = cast(HANDLE) -4; 1906 if(!SetProcessDpiAwarenessContext(DPI_AWARENESS_CONTEXT_PER_MONITOR_AWARE_V2)) { 1907 //writeln(GetLastError()); 1908 } 1909 1910 GetDpiForWindow = cast(typeof(GetDpiForWindow)) GetProcAddress(lib, "GetDpiForWindow"); 1911 SystemParametersInfoForDpi = cast(typeof(SystemParametersInfoForDpi)) GetProcAddress(lib, "SystemParametersInfoForDpi"); 1912 } 1913 1914 /++ 1915 Blocking mode for event loop calls associated with a window instance. 1916 1917 History: 1918 Added December 8, 2021 (dub v10.5). Prior to that, all calls to 1919 `window.eventLoop` were the same as calls to `EventLoop.get.run`; that 1920 is, all would block until the application quit. 1921 1922 That behavior can still be achieved here with `untilApplicationQuits`, 1923 or explicitly calling the top-level `EventLoop.get.run` function. 1924 +/ 1925 enum BlockingMode { 1926 /++ 1927 The event loop call will block until the whole application is ready 1928 to quit if it is the only one running, but if it is nested inside 1929 another one, it will only block until the window you're calling it on 1930 closes. 1931 +/ 1932 automatic = 0x00, 1933 /++ 1934 The event loop call will only return when the whole application 1935 is ready to quit. This usually means all windows have been closed. 1936 1937 This is appropriate for your main application event loop. 1938 +/ 1939 untilApplicationQuits = 0x01, 1940 /++ 1941 The event loop will return when the window you're calling it on 1942 closes. If there are other windows still open, they may be destroyed 1943 unless you have another event loop running later. 1944 1945 This might be appropriate for a modal dialog box loop. Remember that 1946 other windows are still processing input though, so you can end up 1947 with a lengthy call stack if this happens in a loop, similar to a 1948 recursive function (well, it literally is a recursive function, just 1949 not an obvious looking one). 1950 +/ 1951 untilWindowCloses = 0x02, 1952 /++ 1953 If an event loop is already running, this call will immediately 1954 return, allowing the existing loop to handle it. If not, this call 1955 will block until the condition you bitwise-or into the flag. 1956 1957 The default is to block until the application quits, same as with 1958 the `automatic` setting (since if it were nested, which triggers until 1959 window closes in automatic, this flag would instead not block at all), 1960 but if you used `BlockingMode.onlyIfNotNested | BlockingMode.untilWindowCloses`, 1961 it will only nest until the window closes. You might want that if you are 1962 going to open two windows simultaneously and want closing just one of them 1963 to trigger the event loop return. 1964 +/ 1965 onlyIfNotNested = 0x10, 1966 } 1967 1968 /++ 1969 Window corner visuals preference 1970 +/ 1971 enum CornerStyle { 1972 /++ 1973 Use the default style automatically applied by the system or its window manager/compositor. 1974 +/ 1975 automatic, 1976 1977 /++ 1978 Prefer rectangular window corners 1979 +/ 1980 rectangular, 1981 1982 /++ 1983 Prefer rounded window corners 1984 +/ 1985 rounded, 1986 1987 /++ 1988 Prefer slightly-rounded window corners 1989 +/ 1990 roundedSlightly, 1991 } 1992 1993 /++ 1994 The flagship window class. 1995 1996 1997 SimpleWindow tries to make ordinary windows very easy to create and use without locking you 1998 out of more advanced or complex features of the underlying windowing system. 1999 2000 For many applications, you can simply call `new SimpleWindow(some_width, some_height, "some title")` 2001 and get a suitable window to work with. 2002 2003 From there, you can opt into additional features, like custom resizability and OpenGL support 2004 with the next two constructor arguments. Or, if you need even more, you can set a window type 2005 and customization flags with the final two constructor arguments. 2006 2007 If none of that works for you, you can also create a window using native function calls, then 2008 wrap the window in a SimpleWindow instance by calling `new SimpleWindow(native_handle)`. Remember, 2009 though, if you do this, managing the window is still your own responsibility! Notably, you 2010 will need to destroy it yourself. 2011 +/ 2012 class SimpleWindow : CapableOfHandlingNativeEvent, CapableOfBeingDrawnUpon { 2013 2014 /++ 2015 Copies the window's current state into a [TrueColorImage]. 2016 2017 Be warned: this can be a very slow operation 2018 2019 History: 2020 Actually implemented on March 14, 2021 2021 +/ 2022 TrueColorImage takeScreenshot() { 2023 version(Windows) 2024 return trueColorImageFromNativeHandle(impl.hwnd, _width, _height); 2025 else version(OSXCocoa) 2026 throw new NotYetImplementedException(); 2027 else 2028 return trueColorImageFromNativeHandle(impl.window, _width, _height); 2029 } 2030 2031 /++ 2032 Returns the actual logical DPI for the window on its current display monitor. If the window 2033 straddles monitors, it will return the value of one or the other in a platform-defined manner. 2034 2035 Please note this function may return zero if it doesn't know the answer! 2036 2037 2038 On Windows, it returns the dpi per monitor if the operating system supports it (Windows 10), 2039 or a system dpi value if not, which will live-update if the OS supports it (Windows 8 and up). 2040 2041 On X, it reads the xrandr extension to determine monitor positions and sizes. On some systems, 2042 this is not provided, meaning it will return 0. Otherwise, it will determine which monitor the 2043 window primarily resides on by checking the center point of the window against the monitor map. 2044 2045 Returns: 2046 0 if unknown. Otherwise, a rounded value of dots per inch reported by the monitor. It 2047 assumes the X and Y dpi are the same. 2048 2049 History: 2050 Added November 26, 2021 (dub v10.4) 2051 2052 It said "physical dpi" in the description prior to July 29, 2022, but the behavior was 2053 always a logical value on Windows and usually one on Linux too, so now the docs reflect 2054 that. 2055 2056 Bugs: 2057 Probably plenty. I haven't done a lot of tests on this. I know it doesn't automatically 2058 just work on linux; you need to set ARSD_SCALING_FACTOR as an environment variable to 2059 set it. Set ARSD_SCALING_FACTOR=1;1.5 for example to set it to 1x on the primary monitor 2060 and 1.5 on the secondary monitor. 2061 2062 The local dpi is not necessarily related to the physical dpi of the monitor. The name 2063 is a historical misnomer - the real thing of interest is the scale factor and due to 2064 compatibility concerns the scale would modify dpi values to trick applications. But since 2065 that's the terminology common out there, I used it too. 2066 2067 See_Also: 2068 [getDpi] gives the value provided for the default monitor. Not necessarily the same 2069 as this since the window many be on a different monitor, but it is a reasonable fallback 2070 to use if `actualDpi` returns 0. 2071 2072 [onDpiChanged] is changed when `actualDpi` has changed. 2073 +/ 2074 int actualDpi() { 2075 version(X11) bool useFallbackDpi = false; 2076 if(!actualDpiLoadAttempted) { 2077 // FIXME: do the actual monitor we are on 2078 // and on X this is a good chance to load the monitor map. 2079 version(Windows) { 2080 if(GetDpiForWindow) 2081 actualDpi_ = GetDpiForWindow(impl.hwnd); 2082 } else version(X11) { 2083 if(!xRandrInfoLoadAttemped) { 2084 xRandrInfoLoadAttemped = true; 2085 if(!XRandrLibrary.attempted) { 2086 XRandrLibrary.loadDynamicLibrary(); 2087 } 2088 2089 if(XRandrLibrary.loadSuccessful) { 2090 auto display = XDisplayConnection.get; 2091 int scratch; 2092 int major, minor; 2093 if(!XRRQueryExtension(display, &xrrEventBase, &scratch)) 2094 goto fallback; 2095 2096 XRRQueryVersion(display, &major, &minor); 2097 if(major <= 1 && minor < 5) 2098 goto fallback; 2099 2100 int count; 2101 XRRMonitorInfo *monitors = XRRGetMonitors(display, RootWindow(display, DefaultScreen(display)), true, &count); 2102 if(monitors is null) 2103 goto fallback; 2104 scope(exit) XRRFreeMonitors(monitors); 2105 2106 MonitorInfo.info = MonitorInfo.info[0 .. 0]; 2107 MonitorInfo.info.assumeSafeAppend(); 2108 foreach(idx, monitor; monitors[0 .. count]) { 2109 MonitorInfo.info ~= MonitorInfo( 2110 Rectangle(Point(monitor.x, monitor.y), Size(monitor.width, monitor.height)), 2111 Size(monitor.mwidth, monitor.mheight), 2112 cast(int) (customScalingFactorForMonitor(cast(int) idx) * getDpi()[0]) 2113 ); 2114 2115 /+ 2116 if(monitor.mwidth == 0 || monitor.mheight == 0) 2117 // unknown physical size, just guess 96 to avoid divide by zero 2118 MonitorInfo.info ~= MonitorInfo( 2119 Rectangle(Point(monitor.x, monitor.y), Size(monitor.width, monitor.height)), 2120 Size(monitor.mwidth, monitor.mheight), 2121 96 2122 ); 2123 else 2124 // and actual thing 2125 MonitorInfo.info ~= MonitorInfo( 2126 Rectangle(Point(monitor.x, monitor.y), Size(monitor.width, monitor.height)), 2127 Size(monitor.mwidth, monitor.mheight), 2128 minInternal( 2129 // millimeter to int then rounding up. 2130 cast(int)(monitor.width * 25.4 / monitor.mwidth + 0.5), 2131 cast(int)(monitor.height * 25.4 / monitor.mheight + 0.5) 2132 ) 2133 ); 2134 +/ 2135 } 2136 // writeln("Here", MonitorInfo.info); 2137 } 2138 } 2139 2140 if(XRandrLibrary.loadSuccessful) { 2141 updateActualDpi(true); 2142 // writeln("updated"); 2143 2144 if(!requestedInput) { 2145 // this is what requests live updates should the configuration change 2146 // each time you select input, it sends an initial event, so very important 2147 // to not get into a loop of selecting input, getting event, updating data, 2148 // and reselecting input... 2149 requestedInput = true; 2150 XRRSelectInput(display, impl.window, RRScreenChangeNotifyMask); 2151 // writeln("requested input"); 2152 } 2153 } else { 2154 fallback: 2155 // make sure we disable events that aren't coming 2156 xrrEventBase = -1; 2157 // best guess... respect the custom scaling user command to some extent at least though 2158 useFallbackDpi = true; 2159 } 2160 } else version(OSXCocoa) { 2161 actualDpi_ = cast(int)(96 * customScalingFactorForMonitor(0)); // FIXME 2162 } 2163 actualDpiLoadAttempted = true; 2164 } else version(X11) if(MonitorInfo.info.length == 0) { 2165 useFallbackDpi = true; 2166 } 2167 2168 version(X11) 2169 if(useFallbackDpi || actualDpi_ == 0) // FIXME: the actualDpi_ will be populated eventually when we get the first synthetic configure event from the window manager, but that might be a little while so actualDpi_ can be 0 until then... 2170 actualDpi_ = cast(int) (getDpi()[0] * customScalingFactorForMonitor(0)); 2171 return actualDpi_; 2172 } 2173 2174 private int actualDpi_; 2175 private bool actualDpiLoadAttempted; 2176 2177 version(X11) private { 2178 bool requestedInput; 2179 static bool xRandrInfoLoadAttemped; 2180 struct MonitorInfo { 2181 Rectangle position; 2182 Size size; 2183 int dpi; 2184 2185 static MonitorInfo[] info; 2186 } 2187 bool screenPositionKnown; 2188 int screenPositionX; 2189 int screenPositionY; 2190 void updateActualDpi(bool loadingNow = false) { 2191 if(!loadingNow && !actualDpiLoadAttempted) 2192 actualDpi(); // just to make it do the load 2193 foreach(idx, m; MonitorInfo.info) { 2194 if(m.position.contains(Point(screenPositionX + this.width / 2, screenPositionY + this.height / 2))) { 2195 bool changed = actualDpi_ && actualDpi_ != m.dpi; 2196 actualDpi_ = m.dpi; 2197 // writeln("monitor ", idx); 2198 if(changed && onDpiChanged) 2199 onDpiChanged(); 2200 break; 2201 } 2202 } 2203 } 2204 } 2205 2206 /++ 2207 Sent when the window is moved to a new DPI context, for example, when it is dragged between monitors 2208 or if the window is moved to a new remote connection or a monitor is hot-swapped. 2209 2210 History: 2211 Added November 26, 2021 (dub v10.4) 2212 2213 See_Also: 2214 [actualDpi] 2215 +/ 2216 void delegate() onDpiChanged; 2217 2218 version(X11) { 2219 void recreateAfterDisconnect() { 2220 if(!stateDiscarded) return; 2221 2222 if(_parent !is null && _parent.stateDiscarded) 2223 _parent.recreateAfterDisconnect(); 2224 2225 bool wasHidden = hidden; 2226 2227 activeScreenPainter = null; // should already be done but just to confirm 2228 2229 actualDpi_ = 0; 2230 actualDpiLoadAttempted = false; 2231 xRandrInfoLoadAttemped = false; 2232 2233 impl.createWindow(_width, _height, _title, openglMode, _parent); 2234 2235 if(auto dh = dropHandler) { 2236 dropHandler = null; 2237 enableDragAndDrop(this, dh); 2238 } 2239 2240 if(recreateAdditionalConnectionState) 2241 recreateAdditionalConnectionState(); 2242 2243 hidden = wasHidden; 2244 stateDiscarded = false; 2245 } 2246 2247 bool stateDiscarded; 2248 void discardConnectionState() { 2249 if(XDisplayConnection.display) 2250 impl.dispose(); // if display is already null, it is hopeless to try to destroy stuff on it anyway 2251 if(discardAdditionalConnectionState) 2252 discardAdditionalConnectionState(); 2253 stateDiscarded = true; 2254 } 2255 2256 void delegate() discardAdditionalConnectionState; 2257 void delegate() recreateAdditionalConnectionState; 2258 2259 } 2260 2261 private DropHandler dropHandler; 2262 2263 SimpleWindow _parent; 2264 bool beingOpenKeepsAppOpen = true; 2265 /++ 2266 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. 2267 2268 The constructor tries to have sane default arguments, so for many cases, you only need to provide a few of them. 2269 2270 Params: 2271 2272 width = the width of the window's client area, in pixels 2273 height = the height of the window's client area, in pixels 2274 title = the title of the window (seen in the title bar, taskbar, etc.). You can change it after construction with the [SimpleWindow.title] property. 2275 opengl = [OpenGlOptions] are yes and no. If yes, it creates an OpenGL context on the window. 2276 resizable = [Resizability] has three options: 2277 $(P `allowResizing`, which allows the window to be resized by the user. The `windowResized` delegate will be called when the size is changed.) 2278 $(P `fixedSize` will not allow the user to resize the window.) 2279 $(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.) 2280 windowType = The type of window you want to make. 2281 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. 2282 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". 2283 +/ 2284 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) { 2285 claimGuiThread(); 2286 version(sdpy_thread_checks) assert(thisIsGuiThread); 2287 this._width = this._virtualWidth = width; 2288 this._height = this._virtualHeight = height; 2289 this.openglMode = opengl; 2290 version(X11) { 2291 // auto scale not implemented except with opengl and even there it is kinda weird 2292 if(resizable == Resizability.automaticallyScaleIfPossible && opengl == OpenGlOptions.no) 2293 resizable = Resizability.fixedSize; 2294 } 2295 this.resizability = resizable; 2296 this.windowType = windowType; 2297 this.customizationFlags = customizationFlags; 2298 this._title = (title is null ? "D Application" : title); 2299 this._parent = parent; 2300 impl.createWindow(width, height, this._title, opengl, parent); 2301 2302 if(windowType == WindowTypes.dropdownMenu || windowType == WindowTypes.popupMenu || windowType == WindowTypes.nestedChild || (customizationFlags & WindowFlags.transient)) 2303 beingOpenKeepsAppOpen = false; 2304 } 2305 2306 /// ditto 2307 this(int width, int height, string title, Resizability resizable, OpenGlOptions opengl = OpenGlOptions.no, WindowTypes windowType = WindowTypes.normal, int customizationFlags = WindowFlags.normal, SimpleWindow parent = null) { 2308 this(width, height, title, opengl, resizable, windowType, customizationFlags, parent); 2309 } 2310 2311 /// Same as above, except using the `Size` struct instead of separate width and height. 2312 this(Size size, string title = null, OpenGlOptions opengl = OpenGlOptions.no, Resizability resizable = Resizability.automaticallyScaleIfPossible) { 2313 this(size.width, size.height, title, opengl, resizable); 2314 } 2315 2316 /// ditto 2317 this(Size size, string title, Resizability resizable, OpenGlOptions opengl = OpenGlOptions.no) { 2318 this(size, title, opengl, resizable); 2319 } 2320 2321 2322 /++ 2323 Creates a window based on the given [Image]. It's client area 2324 width and height is equal to the image. (A window's client area 2325 is the drawable space inside; it excludes the title bar, etc.) 2326 2327 Windows based on images will not be resizable and do not use OpenGL. 2328 2329 It will draw the image in upon creation, but this will be overwritten 2330 upon any draws, including the initial window visible event. 2331 2332 You probably do not want to use this and it may be removed from 2333 the library eventually, or I might change it to be a "permanent" 2334 background image; one that is automatically drawn on it before any 2335 other drawing event. idk. 2336 +/ 2337 this(Image image, string title = null) { 2338 this(image.width, image.height, title); 2339 this.image = image; 2340 } 2341 2342 /++ 2343 Wraps a native window handle with very little additional processing - notably no destruction 2344 this is incomplete so don't use it for much right now. The purpose of this is to make native 2345 windows created through the low level API (so you can use platform-specific options and 2346 other details SimpleWindow does not expose) available to the event loop wrappers. 2347 +/ 2348 this(NativeWindowHandle nativeWindow) { 2349 windowType = WindowTypes.minimallyWrapped; 2350 version(Windows) 2351 impl.hwnd = nativeWindow; 2352 else version(X11) { 2353 impl.window = nativeWindow; 2354 if(nativeWindow) 2355 display = XDisplayConnection.get(); // get initial display to not segfault 2356 } else version(Emscripten) { 2357 // FIXME 2358 } else version(OSXCocoa) { 2359 if(nativeWindow !is NullWindow) throw new NotYetImplementedException(); 2360 } else featureNotImplemented(); 2361 // FIXME: set the size correctly 2362 _width = 1; 2363 _height = 1; 2364 if(nativeWindow) 2365 nativeMapping[cast(void*) nativeWindow] = this; 2366 2367 beingOpenKeepsAppOpen = false; 2368 2369 if(nativeWindow) 2370 CapableOfHandlingNativeEvent.nativeHandleMapping[nativeWindow] = this; 2371 _suppressDestruction = true; // so it doesn't try to close 2372 } 2373 2374 /++ 2375 Used iff [WindowFlags.managesChildWindowFocus] is set when the window is created. 2376 The delegate will be called when the window manager asks you to take focus. 2377 2378 This is currently only used for the WM_TAKE_FOCUS protocol on X11 at this time. 2379 2380 History: 2381 Added April 1, 2022 (dub v10.8) 2382 +/ 2383 SimpleWindow delegate() setRequestedInputFocus; 2384 2385 /// Experimental, do not use yet 2386 /++ 2387 Grabs exclusive input from the user until you release it with 2388 [releaseInputGrab]. 2389 2390 2391 Note: it is extremely rude to do this without good reason. 2392 Reasons may include doing some kind of mouse drag operation 2393 or popping up a temporary menu that should get events and will 2394 be dismissed at ease by the user clicking away. 2395 2396 Params: 2397 keyboard = do you want to grab keyboard input? 2398 mouse = grab mouse input? 2399 confine = confine the mouse cursor to inside this window? 2400 2401 History: 2402 Prior to March 11, 2021, grabbing the keyboard would always also 2403 set the X input focus. Now, it only focuses if it is a non-transient 2404 window and otherwise manages the input direction internally. 2405 2406 This means spurious focus/blur events will no longer be sent and the 2407 application will not steal focus from other applications (which the 2408 window manager may have rejected anyway). 2409 +/ 2410 void grabInput(bool keyboard = true, bool mouse = true, bool confine = false) { 2411 static if(UsingSimpledisplayX11) { 2412 XSync(XDisplayConnection.get, 0); 2413 if(keyboard) { 2414 if(isTransient && _parent) { 2415 /* 2416 FIXME: 2417 setting the keyboard focus is not actually that helpful, what I more likely want 2418 is the events from the parent window to be sent over here if we're transient. 2419 */ 2420 2421 _parent.inputProxy = this; 2422 } else { 2423 2424 SimpleWindow setTo; 2425 if(setRequestedInputFocus !is null) 2426 setTo = setRequestedInputFocus(); 2427 if(setTo is null) 2428 setTo = this; 2429 2430 // sdpyPrintDebugString("grabInput() ", setTo.impl.window; 2431 XSetInputFocus(XDisplayConnection.get, setTo.impl.window, RevertToParent, CurrentTime); 2432 } 2433 } 2434 if(mouse) { 2435 if(auto res = XGrabPointer(XDisplayConnection.get, this.impl.window, false /* owner_events */, 2436 EventMask.PointerMotionMask // FIXME: not efficient 2437 | EventMask.ButtonPressMask 2438 | EventMask.ButtonReleaseMask 2439 /* event mask */, GrabMode.GrabModeAsync, GrabMode.GrabModeAsync, confine ? this.impl.window : None, None, CurrentTime) 2440 ) 2441 { 2442 XSync(XDisplayConnection.get, 0); 2443 import core.stdc.stdio; 2444 printf("Grab input failed %d\n", res); 2445 //throw new Exception("Grab input failed"); 2446 } else { 2447 // cool 2448 } 2449 } 2450 2451 } else version(Windows) { 2452 // FIXME: keyboard? 2453 SetCapture(impl.hwnd); 2454 if(confine) { 2455 RECT rcClip; 2456 //RECT rcOldClip; 2457 //GetClipCursor(&rcOldClip); 2458 GetWindowRect(hwnd, &rcClip); 2459 ClipCursor(&rcClip); 2460 } 2461 } else version(Emscripten) { 2462 // nothing necessary 2463 } else version(OSXCocoa) { 2464 // throw new NotYetImplementedException(); 2465 } else static assert(0); 2466 } 2467 2468 private Point imePopupLocation = Point(0, 0); 2469 2470 /++ 2471 Sets the location for the IME (input method editor) to pop up when the user activates it. 2472 2473 Bugs: 2474 Not implemented outside X11. 2475 +/ 2476 void setIMEPopupLocation(Point location) { 2477 static if(UsingSimpledisplayX11) { 2478 imePopupLocation = location; 2479 updateIMEPopupLocation(); 2480 } else { 2481 // this is non-fatal at this point... but still wanna find it when i search for NotYetImplementedException at least 2482 // throw new NotYetImplementedException(); 2483 } 2484 } 2485 2486 /// ditto 2487 void setIMEPopupLocation(int x, int y) { 2488 return setIMEPopupLocation(Point(x, y)); 2489 } 2490 2491 // we need to remind XIM of where we wanted to place the IME whenever the window moves 2492 // so this function gets called in setIMEPopupLocation as well as whenever the window 2493 // receives a ConfigureNotify event 2494 private void updateIMEPopupLocation() { 2495 static if(UsingSimpledisplayX11) { 2496 if (xic is null) { 2497 return; 2498 } 2499 2500 XPoint nspot; 2501 nspot.x = cast(short) imePopupLocation.x; 2502 nspot.y = cast(short) imePopupLocation.y; 2503 XVaNestedList preeditAttr = XVaCreateNestedList(0, /*XNSpotLocation*/"spotLocation".ptr, &nspot, null); 2504 XSetICValues(xic, /*XNPreeditAttributes*/"preeditAttributes".ptr, preeditAttr, null); 2505 XFree(preeditAttr); 2506 } 2507 } 2508 2509 private bool imeFocused = true; 2510 2511 /++ 2512 Tells the IME whether or not an input field is currently focused in the window. 2513 2514 Bugs: 2515 Not implemented outside X11. 2516 +/ 2517 void setIMEFocused(bool value) { 2518 imeFocused = value; 2519 updateIMEFocused(); 2520 } 2521 2522 // used to focus/unfocus the IC if necessary when the window gains/loses focus 2523 private void updateIMEFocused() { 2524 static if(UsingSimpledisplayX11) { 2525 if (xic is null) { 2526 return; 2527 } 2528 2529 if (focused && imeFocused) { 2530 XSetICFocus(xic); 2531 } else { 2532 XUnsetICFocus(xic); 2533 } 2534 } 2535 } 2536 2537 /++ 2538 Returns the native window. 2539 2540 History: 2541 Added November 5, 2021 (dub v10.4). Prior to that, you'd have 2542 to access it through the `impl` member (which is semi-supported 2543 but platform specific and here it is simple enough to offer an accessor). 2544 2545 Bugs: 2546 Not implemented outside Windows or X11. 2547 +/ 2548 NativeWindowHandle nativeWindowHandle() { 2549 version(X11) 2550 return impl.window; 2551 else version(Windows) 2552 return impl.hwnd; 2553 else 2554 throw new NotYetImplementedException(); 2555 } 2556 2557 private bool isTransient() { 2558 with(WindowTypes) 2559 final switch(windowType) { 2560 case normal, undecorated, eventOnly: 2561 case nestedChild, minimallyWrapped: 2562 return (customizationFlags & WindowFlags.transient) ? true : false; 2563 case dropdownMenu, popupMenu, notification, dialog, tooltip, dnd, comboBoxDropdown: 2564 return true; 2565 } 2566 } 2567 2568 private SimpleWindow inputProxy; 2569 2570 /++ 2571 Releases the grab acquired by [grabInput]. 2572 +/ 2573 void releaseInputGrab() { 2574 static if(UsingSimpledisplayX11) { 2575 XUngrabPointer(XDisplayConnection.get, CurrentTime); 2576 if(_parent) 2577 _parent.inputProxy = null; 2578 } else version(Windows) { 2579 ReleaseCapture(); 2580 ClipCursor(null); 2581 } else version(OSXCocoa) { 2582 // throw new NotYetImplementedException(); 2583 } else version(Emscripten) { 2584 // nothing needed 2585 } else static assert(0); 2586 } 2587 2588 /++ 2589 Sets the input focus to this window. 2590 2591 You shouldn't call this very often - please let the user control the input focus. 2592 +/ 2593 void focus() { 2594 static if(UsingSimpledisplayX11) { 2595 SimpleWindow setTo; 2596 if(setRequestedInputFocus !is null) 2597 setTo = setRequestedInputFocus(); 2598 if(setTo is null) 2599 setTo = this; 2600 // sdpyPrintDebugString("sdpy.focus() ", setTo.impl.window); 2601 XSetInputFocus(XDisplayConnection.get, setTo.impl.window, RevertToParent, CurrentTime); 2602 } else version(Windows) { 2603 SetFocus(this.impl.hwnd); 2604 } else version(Emscripten) { 2605 throw new NotYetImplementedException(); 2606 } else version(OSXCocoa) { 2607 throw new NotYetImplementedException(); 2608 } else static assert(0); 2609 } 2610 2611 /++ 2612 Requests attention from the user for this window. 2613 2614 2615 The typical result of this function is to change the color 2616 of the taskbar icon, though it may be tweaked on specific 2617 platforms. 2618 2619 It is meant to unobtrusively tell the user that something 2620 relevant to them happened in the background and they should 2621 check the window when they get a chance. Upon receiving the 2622 keyboard focus, the window will automatically return to its 2623 natural state. 2624 2625 If the window already has the keyboard focus, this function 2626 may do nothing, because the user is presumed to already be 2627 giving the window attention. 2628 2629 Implementation_note: 2630 2631 `requestAttention` uses the _NET_WM_STATE_DEMANDS_ATTENTION 2632 atom on X11 and the FlashWindow function on Windows. 2633 +/ 2634 void requestAttention() { 2635 if(_focused) 2636 return; 2637 2638 version(Windows) { 2639 FLASHWINFO info; 2640 info.cbSize = info.sizeof; 2641 info.hwnd = impl.hwnd; 2642 info.dwFlags = FLASHW_TRAY; 2643 info.uCount = 1; 2644 2645 FlashWindowEx(&info); 2646 2647 } else version(X11) { 2648 demandingAttention = true; 2649 demandAttention(this, true); 2650 } else version(Emscripten) { 2651 throw new NotYetImplementedException(); 2652 } else version(OSXCocoa) { 2653 throw new NotYetImplementedException(); 2654 } else static assert(0); 2655 } 2656 2657 private bool _focused; 2658 2659 version(X11) private bool demandingAttention; 2660 2661 /// This will be called when WM wants to close your window (i.e. user clicked "close" icon, for example). 2662 /// You'll have to call `close()` manually if you set this delegate. 2663 void delegate () closeQuery; 2664 2665 /// This will be called when window visibility was changed. 2666 void delegate (bool becomesVisible) visibilityChanged; 2667 2668 /// This will be called when window becomes visible for the first time. 2669 /// You can do OpenGL initialization here. Note that in X11 you can't call 2670 /// [setAsCurrentOpenGlContext] right after window creation, or X11 may 2671 /// fail to send reparent and map events (hit that with proprietary NVidia drivers). 2672 /// So you need to wait until this is called and call setAsCurrentOpenGlContext in there, then do the OpenGL initialization. 2673 private bool _visibleForTheFirstTimeCalled; 2674 void delegate () visibleForTheFirstTime; 2675 2676 /// Returns true if the window has been closed. 2677 final @property bool closed() { return _closed; } 2678 2679 private final @property bool notClosed() { return !_closed; } 2680 2681 /// Returns true if the window is focused. 2682 final @property bool focused() { return _focused; } 2683 2684 private bool _visible; 2685 /// Returns true if the window is visible (mapped). 2686 final @property bool visible() { return _visible; } 2687 2688 /// Closes the window. If there are no more open windows, the event loop will terminate. 2689 void close() { 2690 if (!_closed) { 2691 runInGuiThread( { 2692 if(_closed) return; // another thread got to it first. this isn't a big deal, it just means our message was queued 2693 if (onClosing !is null) onClosing(); 2694 impl.closeWindow(); 2695 _closed = true; 2696 } ); 2697 } 2698 } 2699 2700 /++ 2701 `close` is one of the few methods that can be called from other threads. This `shared` overload reflects that. 2702 2703 History: 2704 Overload added on March 7, 2021. 2705 +/ 2706 void close() shared { 2707 (cast() this).close(); 2708 } 2709 2710 /++ 2711 2712 +/ 2713 void maximize() { 2714 version(Windows) 2715 ShowWindow(impl.hwnd, SW_MAXIMIZE); 2716 else version(X11) { 2717 setNetWmStateAtom(this.impl.window, GetAtom!("_NET_WM_STATE_MAXIMIZED_VERT", false)(XDisplayConnection.get), true, GetAtom!("_NET_WM_STATE_MAXIMIZED_HORZ", false)(XDisplayConnection.get)); 2718 2719 // also note _NET_WM_STATE_FULLSCREEN 2720 } 2721 2722 } 2723 2724 private bool _fullscreen; 2725 version(Windows) 2726 private WINDOWPLACEMENT g_wpPrev; 2727 2728 /// not fully implemented but planned for a future release 2729 void fullscreen(bool yes) { 2730 version(Windows) { 2731 g_wpPrev.length = WINDOWPLACEMENT.sizeof; 2732 DWORD dwStyle = GetWindowLong(hwnd, GWL_STYLE); 2733 if (dwStyle & WS_OVERLAPPEDWINDOW) { 2734 MONITORINFO mi; 2735 mi.cbSize = MONITORINFO.sizeof; 2736 if (GetWindowPlacement(hwnd, &g_wpPrev) && 2737 GetMonitorInfo(MonitorFromWindow(hwnd, 2738 MONITOR_DEFAULTTOPRIMARY), &mi)) { 2739 SetWindowLong(hwnd, GWL_STYLE, 2740 dwStyle & ~WS_OVERLAPPEDWINDOW); 2741 SetWindowPos(hwnd, HWND_TOP, 2742 mi.rcMonitor.left, mi.rcMonitor.top, 2743 mi.rcMonitor.right - mi.rcMonitor.left, 2744 mi.rcMonitor.bottom - mi.rcMonitor.top, 2745 SWP_NOOWNERZORDER | SWP_FRAMECHANGED); 2746 } 2747 } else { 2748 SetWindowLong(hwnd, GWL_STYLE, 2749 dwStyle | WS_OVERLAPPEDWINDOW); 2750 SetWindowPlacement(hwnd, &g_wpPrev); 2751 SetWindowPos(hwnd, null, 0, 0, 0, 0, 2752 SWP_NOMOVE | SWP_NOSIZE | SWP_NOZORDER | 2753 SWP_NOOWNERZORDER | SWP_FRAMECHANGED); 2754 } 2755 2756 } else version(X11) { 2757 setNetWmStateAtom(this.impl.window, GetAtom!("_NET_WM_STATE_FULLSCREEN", false)(XDisplayConnection.get), yes); 2758 } 2759 2760 _fullscreen = yes; 2761 2762 } 2763 2764 bool fullscreen() { 2765 return _fullscreen; 2766 } 2767 2768 /++ 2769 Note: only implemented on Windows. No-op on other platforms. You may want to use [hide] instead. 2770 2771 +/ 2772 void minimize() { 2773 version(Windows) 2774 ShowWindow(impl.hwnd, SW_MINIMIZE); 2775 //else version(X11) 2776 //setNetWmStateAtom(this, GetAtom!("_NET_WM_STATE_MINIMIZED", false)(XDisplayConnection.get), true); 2777 } 2778 2779 /// Alias for `hidden = false` 2780 void show() { 2781 hidden = false; 2782 } 2783 2784 /// Alias for `hidden = true` 2785 void hide() { 2786 hidden = true; 2787 } 2788 2789 /// Hide cursor when it enters the window. 2790 void hideCursor() { 2791 version(OSXCocoa) throw new NotYetImplementedException(); else 2792 if (!_closed) impl.hideCursor(); 2793 } 2794 2795 /// Don't hide cursor when it enters the window. 2796 void showCursor() { 2797 version(OSXCocoa) throw new NotYetImplementedException(); else 2798 if (!_closed) impl.showCursor(); 2799 } 2800 2801 /** "Warp" mouse pointer to coordinates relative to window top-left corner. Return "success" flag. 2802 * 2803 * Please remember that the cursor is a shared resource that should usually be left to the user's 2804 * control. Try to think for other approaches before using this function. 2805 * 2806 * Note: "warping" pointer will not send any synthesised mouse events, so you probably doesn't want 2807 * to use it to move mouse pointer to some active GUI area, for example, as your window won't 2808 * receive "mouse moved here" event. 2809 */ 2810 bool warpMouse (int x, int y) { 2811 version(X11) { 2812 if (!_closed) { impl.warpMouse(x, y); return true; } 2813 } else version(Windows) { 2814 if (!_closed) { 2815 POINT point; 2816 point.x = x; 2817 point.y = y; 2818 if(ClientToScreen(impl.hwnd, &point)) { 2819 SetCursorPos(point.x, point.y); 2820 return true; 2821 } 2822 } 2823 } 2824 return false; 2825 } 2826 2827 /// Send dummy window event to ping event loop. Required to process NotificationIcon on X11, for example. 2828 void sendDummyEvent () { 2829 version(X11) { 2830 if (!_closed) { impl.sendDummyEvent(); } 2831 } 2832 } 2833 2834 /// Set window minimal size. 2835 void setMinSize (int minwidth, int minheight) { 2836 version(OSXCocoa) throw new NotYetImplementedException(); else 2837 if (!_closed) impl.setMinSize(minwidth, minheight); 2838 } 2839 2840 /// Set window maximal size. 2841 void setMaxSize (int maxwidth, int maxheight) { 2842 version(OSXCocoa) throw new NotYetImplementedException(); else 2843 if (!_closed) impl.setMaxSize(maxwidth, maxheight); 2844 } 2845 2846 /// Set window resize step (window size will be changed with the given granularity on supported platforms). 2847 /// Currently only supported on X11. 2848 void setResizeGranularity (int granx, int grany) { 2849 version(OSXCocoa) throw new NotYetImplementedException(); else 2850 if (!_closed) impl.setResizeGranularity(granx, grany); 2851 } 2852 2853 /// Move window. 2854 void move(int x, int y) { 2855 version(OSXCocoa) throw new NotYetImplementedException(); else 2856 if (!_closed) impl.move(x, y); 2857 } 2858 2859 /// ditto 2860 void move(Point p) { 2861 version(OSXCocoa) throw new NotYetImplementedException(); else 2862 if (!_closed) impl.move(p.x, p.y); 2863 } 2864 2865 /++ 2866 Resize window. 2867 2868 Note that the width and height of the window are NOT instantly 2869 updated - it waits for the window manager to approve the resize 2870 request, which means you must return to the event loop before the 2871 width and height are actually changed. 2872 +/ 2873 void resize(int w, int h) { 2874 if(!_closed && _fullscreen) fullscreen = false; 2875 version(OSXCocoa) throw new NotYetImplementedException(); else 2876 if (!_closed) impl.resize(w, h); 2877 } 2878 2879 /// Move and resize window (this can be faster and more visually pleasant than doing it separately). 2880 void moveResize (int x, int y, int w, int h) { 2881 if(!_closed && _fullscreen) fullscreen = false; 2882 version(OSXCocoa) throw new NotYetImplementedException(); else 2883 if (!_closed) impl.moveResize(x, y, w, h); 2884 } 2885 2886 private bool _hidden; 2887 2888 /// Returns true if the window is hidden. 2889 final @property bool hidden() { 2890 return _hidden; 2891 } 2892 2893 /// Shows or hides the window based on the bool argument. 2894 final @property void hidden(bool b) { 2895 _hidden = b; 2896 version(Windows) { 2897 ShowWindow(impl.hwnd, b ? SW_HIDE : SW_SHOW); 2898 } else version(X11) { 2899 if(b) 2900 //XUnmapWindow(impl.display, impl.window); 2901 XWithdrawWindow(impl.display, impl.window, DefaultScreen(impl.display)); 2902 else 2903 XMapWindow(impl.display, impl.window); 2904 } else version(OSXCocoa) { 2905 // throw new NotYetImplementedException(); 2906 } else version(Emscripten) { 2907 } else static assert(0); 2908 } 2909 2910 /// Sets the window opacity. On X11 this requires a compositor to be running. On windows the WindowFlags.extraComposite must be set at window creation. 2911 void opacity(double opacity) @property 2912 in { 2913 assert(opacity >= 0 && opacity <= 1); 2914 } do { 2915 version (Windows) { 2916 impl.setOpacity(cast(ubyte)(255 * opacity)); 2917 } else version (X11) { 2918 impl.setOpacity(cast(uint)(uint.max * opacity)); 2919 } else throw new NotYetImplementedException(); 2920 } 2921 2922 /++ 2923 Sets your event handlers, without entering the event loop. Useful if you 2924 have multiple windows - set the handlers on each window, then only do 2925 [eventLoop] on your main window or call `EventLoop.get.run();`. 2926 2927 This assigns the given handlers to [handleKeyEvent], [handleCharEvent], 2928 [handlePulse], and [handleMouseEvent] automatically based on the provide 2929 delegate signatures. 2930 +/ 2931 void setEventHandlers(T...)(T eventHandlers) { 2932 // FIXME: add more events 2933 foreach(handler; eventHandlers) { 2934 static if(__traits(compiles, handleKeyEvent = handler)) { 2935 handleKeyEvent = handler; 2936 } else static if(__traits(compiles, handleCharEvent = handler)) { 2937 handleCharEvent = handler; 2938 } else static if(__traits(compiles, handlePulse = handler)) { 2939 handlePulse = handler; 2940 } else static if(__traits(compiles, handleMouseEvent = handler)) { 2941 handleMouseEvent = handler; 2942 } else static assert(0, "I can't use this event handler " ~ typeof(handler).stringof ~ "\nHave you tried using the delegate keyword?"); 2943 } 2944 } 2945 2946 /++ 2947 The event loop automatically returns when the window is closed 2948 pulseTimeout is given in milliseconds. If pulseTimeout == 0, no 2949 pulse timer is created. The event loop will block until an event 2950 arrives or the pulse timer goes off. 2951 2952 The given `eventHandlers` are passed to [setEventHandlers], which in turn 2953 assigns them to [handleKeyEvent], [handleCharEvent], [handlePulse], and 2954 [handleMouseEvent], based on the signature of delegates you provide. 2955 2956 Give one with no parameters to set a timer pulse handler. Give one that 2957 takes [KeyEvent] for a key handler, [MouseEvent], for a mouse handler, 2958 and one that takes `dchar` for a char event handler. You can use as many 2959 or as few handlers as you need for your application. 2960 2961 Bugs: 2962 2963 $(PITFALL 2964 You should always have one event loop live for your application. 2965 If you make two windows in sequence, the second call to eventLoop 2966 might fail: 2967 2968 --- 2969 // don't do this! 2970 auto window = new SimpleWindow(); 2971 window.eventLoop(0); 2972 2973 auto window2 = new SimpleWindow(); 2974 window2.eventLoop(0); // problematic! might crash 2975 --- 2976 2977 simpledisplay's current implementation assumes that final cleanup is 2978 done when the event loop refcount reaches zero. So after the first 2979 eventLoop returns, when there isn't already another one active, it assumes 2980 the program will exit soon and cleans up. 2981 2982 This is arguably a bug that it doesn't reinitialize, and I'll probably change 2983 it eventually, but in the mean time, there's an easy solution: 2984 2985 --- 2986 // do this 2987 EventLoop mainEventLoop = EventLoop.get; // just add this line 2988 2989 auto window = new SimpleWindow(); 2990 window.eventLoop(0); 2991 2992 auto window2 = new SimpleWindow(); 2993 window2.eventLoop(0); // perfectly fine since mainEventLoop still alive 2994 --- 2995 2996 By adding a top-level reference to the event loop, it ensures the final cleanup 2997 is not performed until it goes out of scope too, letting the individual window loops 2998 work without trouble despite the bug. 2999 ) 3000 3001 History: 3002 The overload without `pulseTimeout` was added on December 8, 2021. 3003 3004 On December 9, 2021, the default blocking mode (which is now configurable 3005 because [eventLoopWithBlockingMode] was added) switched from 3006 [BlockingMode.untilApplicationQuits] over to [BlockingMode.automatic]. This 3007 should almost never be noticeable to you since the typical simpledisplay 3008 paradigm has been (and I still recommend) to have one `eventLoop` call. 3009 3010 See_Also: 3011 [eventLoopWithBlockingMode] 3012 +/ 3013 final int eventLoop(T...)( 3014 long pulseTimeout, /// set to zero if you don't want a pulse. 3015 T eventHandlers) /// delegate list like std.concurrency.receive 3016 { 3017 return eventLoopWithBlockingMode(BlockingMode.automatic, pulseTimeout, eventHandlers); 3018 } 3019 3020 /// ditto 3021 final int eventLoop(T...)(T eventHandlers) if(T.length == 0 || is(T[0] == delegate)) 3022 { 3023 return eventLoopWithBlockingMode(BlockingMode.automatic, 0, eventHandlers); 3024 } 3025 3026 /++ 3027 This is the function [eventLoop] forwards to. It, in turn, forwards to `EventLoop.get.run`. 3028 3029 History: 3030 Added December 8, 2021 (dub v10.5) 3031 3032 Previously, this implementation was right inside [eventLoop], but when I wanted 3033 to add the new [BlockingMode] parameter, the compiler got in a trouble loop so I 3034 just renamed it instead of adding as an overload. Besides, the new name makes it 3035 easier to remember the order and avoids ambiguity between two int-like params anyway. 3036 3037 See_Also: 3038 [SimpleWindow.eventLoop], [EventLoop] 3039 3040 Bugs: 3041 The blocking mode is not implemented on OSX Cocoa nor on the (deprecated) arsd.eventloop. 3042 +/ 3043 final int eventLoopWithBlockingMode(T...)( 3044 BlockingMode blockingMode, /// when you want this function to block until 3045 long pulseTimeout, /// set to zero if you don't want a pulse. 3046 T eventHandlers) /// delegate list like std.concurrency.receive 3047 { 3048 setEventHandlers(eventHandlers); 3049 3050 version(with_eventloop) { 3051 // delegates event loop to my other module 3052 version(X11) 3053 XFlush(display); 3054 3055 import arsd.eventloop; 3056 auto handle = setInterval(handlePulse, cast(int) pulseTimeout); 3057 scope(exit) clearInterval(handle); 3058 3059 loop(); 3060 return 0; 3061 } else version(OSXCocoa) { 3062 // FIXME 3063 if (handlePulse !is null && pulseTimeout != 0) { 3064 timer = NSTimer.schedule(pulseTimeout*1e-3, 3065 cast(NSid) view, sel_registerName("simpledisplay_pulse:"), 3066 null, true); 3067 } 3068 3069 view.setNeedsDisplay(true); 3070 3071 NSApp.run(); 3072 return 0; 3073 } else { 3074 EventLoop el = EventLoop(pulseTimeout, handlePulse); 3075 3076 if((blockingMode & BlockingMode.onlyIfNotNested) && el.impl.refcount > 1) 3077 return 0; 3078 3079 return el.run( 3080 ((blockingMode & 0x0f) == BlockingMode.untilApplicationQuits) ? 3081 null : 3082 &this.notClosed 3083 ); 3084 } 3085 } 3086 3087 /++ 3088 This lets you draw on the window (or its backing buffer) using basic 3089 2D primitives. 3090 3091 Be sure to call this in a limited scope because your changes will not 3092 actually appear on the window until ScreenPainter's destructor runs. 3093 3094 Returns: an instance of [ScreenPainter], which has the drawing methods 3095 on it to draw on this window. 3096 3097 Params: 3098 manualInvalidations = if you set this to true, you will need to 3099 set the invalid rectangle on the painter yourself. If false, it 3100 assumes the whole window has been redrawn each time you draw. 3101 3102 Only invalidated rectangles are blitted back to the window when 3103 the destructor runs. Doing this yourself can reduce flickering 3104 of child windows. 3105 3106 History: 3107 The `manualInvalidations` parameter overload was added on 3108 December 30, 2021 (dub v10.5) 3109 +/ 3110 ScreenPainter draw() { 3111 return draw(false); 3112 } 3113 /// ditto 3114 ScreenPainter draw(bool manualInvalidations) { 3115 return impl.getPainter(manualInvalidations); 3116 } 3117 3118 // This is here to implement the interface we use for various native handlers. 3119 NativeEventHandler getNativeEventHandler() { return handleNativeEvent; } 3120 3121 // maps native window handles to SimpleWindow instances, if there are any 3122 // you shouldn't need this, but it is public in case you do in a native event handler or something 3123 // mac uses void* cuz NSObject opHash won't pick up in typeinfo 3124 version(OSXCocoa) 3125 public __gshared SimpleWindow[void*] nativeMapping; 3126 else 3127 public __gshared SimpleWindow[NativeWindowHandle] nativeMapping; 3128 3129 // the size the user requested in the constructor, in automatic scale modes it always pretends to be this size 3130 private int _virtualWidth; 3131 private int _virtualHeight; 3132 3133 /// Width of the window's drawable client area, in pixels. 3134 @scriptable 3135 final @property int width() const pure nothrow @safe @nogc { 3136 if(resizability == Resizability.automaticallyScaleIfPossible) 3137 return _virtualWidth; 3138 else 3139 return _width; 3140 } 3141 3142 /// Height of the window's drawable client area, in pixels. 3143 @scriptable 3144 final @property int height() const pure nothrow @safe @nogc { 3145 if(resizability == Resizability.automaticallyScaleIfPossible) 3146 return _virtualHeight; 3147 else 3148 return _height; 3149 } 3150 3151 /++ 3152 Returns the actual size of the window, bypassing the logical 3153 illusions of [Resizability.automaticallyScaleIfPossible]. 3154 3155 History: 3156 Added November 11, 2022 (dub v10.10) 3157 +/ 3158 final @property Size actualWindowSize() const pure nothrow @safe @nogc { 3159 return Size(_width, _height); 3160 } 3161 3162 3163 private int _width; 3164 private int _height; 3165 3166 // HACK: making the best of some copy constructor woes with refcounting 3167 private ScreenPainterImplementation* activeScreenPainter_; 3168 3169 protected ScreenPainterImplementation* activeScreenPainter() { return activeScreenPainter_; } 3170 protected void activeScreenPainter(ScreenPainterImplementation* i) { activeScreenPainter_ = i; } 3171 3172 private OpenGlOptions openglMode; 3173 private Resizability resizability; 3174 private WindowTypes windowType; 3175 private int customizationFlags; 3176 3177 /// `true` if OpenGL was initialized for this window. 3178 @property bool isOpenGL () const pure nothrow @safe @nogc { 3179 version(without_opengl) 3180 return false; 3181 else 3182 return (openglMode == OpenGlOptions.yes); 3183 } 3184 @property Resizability resizingMode () const pure nothrow @safe @nogc { return resizability; } /// Original resizability. 3185 @property WindowTypes type () const pure nothrow @safe @nogc { return windowType; } /// Original window type. 3186 @property int customFlags () const pure nothrow @safe @nogc { return customizationFlags; } /// Original customization flags. 3187 3188 /// "Lock" this window handle, to do multithreaded synchronization. You probably won't need 3189 /// to call this, as it's not recommended to share window between threads. 3190 void mtLock () { 3191 version(X11) { 3192 XLockDisplay(this.display); 3193 } 3194 } 3195 3196 /// "Unlock" this window handle, to do multithreaded synchronization. You probably won't need 3197 /// to call this, as it's not recommended to share window between threads. 3198 void mtUnlock () { 3199 version(X11) { 3200 XUnlockDisplay(this.display); 3201 } 3202 } 3203 3204 /// Emit a beep to get user's attention. 3205 void beep () { 3206 version(X11) { 3207 XBell(this.display, 100); 3208 } else version(Windows) { 3209 MessageBeep(0xFFFFFFFF); 3210 } 3211 } 3212 3213 3214 3215 version(without_opengl) {} else { 3216 3217 /// 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`. 3218 void delegate() redrawOpenGlScene; 3219 3220 /// This will allow you to change OpenGL vsync state. 3221 final @property void vsync (bool wait) { 3222 if (this._closed) return; // window may be closed, but timer is still firing; avoid GLXBadDrawable error 3223 version(X11) { 3224 setAsCurrentOpenGlContext(); 3225 glxSetVSync(display, impl.window, wait); 3226 } else version(Windows) { 3227 setAsCurrentOpenGlContext(); 3228 wglSetVSync(wait); 3229 } 3230 } 3231 3232 /// Set this to `false` if you don't need to do `glFinish()` after `swapOpenGlBuffers()`. 3233 /// Note that at least NVidia proprietary driver may segfault if you will modify texture fast 3234 /// enough without waiting 'em to finish their frame business. 3235 bool useGLFinish = true; 3236 3237 // FIXME: it should schedule it for the end of the current iteration of the event loop... 3238 /// 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. 3239 void redrawOpenGlSceneNow() { 3240 version(X11) if (!this._visible) return; // no need to do this if window is invisible 3241 if (this._closed) return; // window may be closed, but timer is still firing; avoid GLXBadDrawable error 3242 if(redrawOpenGlScene is null) 3243 return; 3244 3245 this.mtLock(); 3246 scope(exit) this.mtUnlock(); 3247 3248 this.setAsCurrentOpenGlContext(); 3249 3250 redrawOpenGlScene(); 3251 3252 this.swapOpenGlBuffers(); 3253 // 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. 3254 if (useGLFinish) glFinish(); 3255 } 3256 3257 private bool redrawOpenGlSceneSoonSet = false; 3258 private static class RedrawOpenGlSceneEvent { 3259 SimpleWindow w; 3260 this(SimpleWindow w) { this.w = w; } 3261 } 3262 private RedrawOpenGlSceneEvent redrawOpenGlSceneEvent; 3263 /++ 3264 Queues an opengl redraw as soon as the other pending events are cleared. 3265 +/ 3266 void redrawOpenGlSceneSoon() { 3267 if(redrawOpenGlScene is null) 3268 return; 3269 3270 if(!redrawOpenGlSceneSoonSet) { 3271 redrawOpenGlSceneEvent = new RedrawOpenGlSceneEvent(this); 3272 this.addEventListener((RedrawOpenGlSceneEvent e) { e.w.redrawOpenGlSceneNow(); }); 3273 redrawOpenGlSceneSoonSet = true; 3274 } 3275 this.postEvent(redrawOpenGlSceneEvent, true); 3276 } 3277 3278 3279 /// Makes all gl* functions target this window until changed. This is only valid if you passed `OpenGlOptions.yes` to the constructor. 3280 void setAsCurrentOpenGlContext() { 3281 assert(openglMode == OpenGlOptions.yes); 3282 version(X11) { 3283 if(glXMakeCurrent(display, impl.window, impl.glc) == 0) 3284 throw new Exception("glXMakeCurrent"); 3285 } else version(Windows) { 3286 static if (SdpyIsUsingIVGLBinds) import iv.glbinds; // override druntime windows imports 3287 if (!wglMakeCurrent(ghDC, ghRC)) 3288 throw new Exception("wglMakeCurrent " ~ toInternal!int(GetLastError())); // let windows users suffer too 3289 } 3290 } 3291 3292 /// Makes all gl* functions target this window until changed. This is only valid if you passed `OpenGlOptions.yes` to the constructor. 3293 /// This doesn't throw, returning success flag instead. 3294 bool setAsCurrentOpenGlContextNT() nothrow { 3295 assert(openglMode == OpenGlOptions.yes); 3296 version(X11) { 3297 return (glXMakeCurrent(display, impl.window, impl.glc) != 0); 3298 } else version(Windows) { 3299 static if (SdpyIsUsingIVGLBinds) import iv.glbinds; // override druntime windows imports 3300 return wglMakeCurrent(ghDC, ghRC) ? true : false; 3301 } 3302 } 3303 3304 /// 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. 3305 /// This doesn't throw, returning success flag instead. 3306 bool releaseCurrentOpenGlContext() nothrow { 3307 assert(openglMode == OpenGlOptions.yes); 3308 version(X11) { 3309 return (glXMakeCurrent(display, 0, null) != 0); 3310 } else version(Windows) { 3311 static if (SdpyIsUsingIVGLBinds) import iv.glbinds; // override druntime windows imports 3312 return wglMakeCurrent(ghDC, null) ? true : false; 3313 } 3314 } 3315 3316 /++ 3317 simpledisplay always uses double buffering, usually automatically. This 3318 manually swaps the OpenGL buffers. You should only use this if you are NOT 3319 using the [redrawOpenGlScene] delegate. 3320 3321 3322 You must not this yourself if you use [redrawOpenGlScene] because simpledisplay will do it 3323 for you after calling your `redrawOpenGlScene`. Please note that once you swap 3324 buffers, the contents become undefined - the implementation, in the OpenGL driver 3325 or the desktop compositor, may not actually just swap two buffers. The back buffer's 3326 contents are $(B undefined) after calling this function. 3327 3328 See: https://learn.microsoft.com/en-us/windows/win32/api/wingdi/nf-wingdi-swapbuffers 3329 and https://linux.die.net/man/3/glxswapbuffers 3330 3331 Remember that this may throw an exception, which you can catch in a multithreaded 3332 application to keep your thread from dying from an unhandled exception. 3333 +/ 3334 void swapOpenGlBuffers() { 3335 assert(openglMode == OpenGlOptions.yes); 3336 version(X11) { 3337 if (!this._visible) return; // no need to do this if window is invisible 3338 if (this._closed) return; // window may be closed, but timer is still firing; avoid GLXBadDrawable error 3339 glXSwapBuffers(display, impl.window); 3340 } else version(Windows) { 3341 SwapBuffers(ghDC); 3342 } 3343 } 3344 } 3345 3346 /++ 3347 Set the window title, which is visible on the window manager title bar, operating system taskbar, etc. 3348 3349 3350 --- 3351 auto window = new SimpleWindow(100, 100, "First title"); 3352 window.title = "A new title"; 3353 --- 3354 3355 You may call this function at any time. 3356 +/ 3357 @property void title(string title) { 3358 _title = title; 3359 version(OSXCocoa) throw new NotYetImplementedException(); else 3360 impl.setTitle(title); 3361 } 3362 3363 private string _title; 3364 3365 /// Gets the title 3366 @property string title() { 3367 if(_title is null) 3368 _title = getRealTitle(); 3369 return _title; 3370 } 3371 3372 /++ 3373 Get the title as set by the window manager. 3374 May not match what you attempted to set. 3375 +/ 3376 string getRealTitle() { 3377 static if(is(typeof(impl.getTitle()))) 3378 return impl.getTitle(); 3379 else 3380 return null; 3381 } 3382 3383 // don't use this generally it is not yet really released 3384 version(X11) 3385 @property Image secret_icon() { 3386 return secret_icon_inner; 3387 } 3388 private Image secret_icon_inner; 3389 3390 3391 /// Set the icon that is seen in the title bar or taskbar, etc., for the user. If passed `null`, does nothing. 3392 @property void icon(MemoryImage icon) { 3393 if(icon is null) 3394 return; 3395 auto tci = icon.getAsTrueColorImage(); 3396 version(Windows) { 3397 winIcon = new WindowsIcon(icon); 3398 SendMessageA(impl.hwnd, 0x0080 /*WM_SETICON*/, 0 /*ICON_SMALL*/, cast(LPARAM) winIcon.hIcon); // there is also 1 == ICON_BIG 3399 } else version(X11) { 3400 secret_icon_inner = Image.fromMemoryImage(icon); 3401 // FIXME: ensure this is correct 3402 auto display = XDisplayConnection.get; 3403 arch_ulong[] buffer; 3404 buffer ~= icon.width; 3405 buffer ~= icon.height; 3406 foreach(c; tci.imageData.colors) { 3407 arch_ulong b; 3408 b |= c.a << 24; 3409 b |= c.r << 16; 3410 b |= c.g << 8; 3411 b |= c.b; 3412 buffer ~= b; 3413 } 3414 3415 XChangeProperty( 3416 display, 3417 impl.window, 3418 GetAtom!("_NET_WM_ICON", true)(display), 3419 GetAtom!"CARDINAL"(display), 3420 32 /* bits */, 3421 0 /*PropModeReplace*/, 3422 buffer.ptr, 3423 cast(int) buffer.length); 3424 } else version(OSXCocoa) { 3425 throw new NotYetImplementedException(); 3426 } else version(Emscripten) { 3427 throw new NotYetImplementedException(); 3428 } else static assert(0); 3429 } 3430 3431 version(Windows) 3432 private WindowsIcon winIcon; 3433 3434 bool _suppressDestruction; 3435 3436 ~this() { 3437 if(!thisIsGuiThread) return; // FIXME: leaks if multithreaded gc 3438 if(_suppressDestruction) 3439 return; 3440 impl.dispose(); 3441 } 3442 3443 private bool _closed; 3444 3445 // the idea here is to draw something temporary on top of the main picture e.g. a blinking cursor 3446 /* 3447 ScreenPainter drawTransiently() { 3448 return impl.getPainter(); 3449 } 3450 */ 3451 3452 /// Draws an image on the window. This is meant to provide quick look 3453 /// of a static image generated elsewhere. 3454 @property void image(Image i) { 3455 /+ 3456 version(Windows) { 3457 BITMAP bm; 3458 HDC hdc = GetDC(hwnd); 3459 HDC hdcMem = CreateCompatibleDC(hdc); 3460 HBITMAP hbmOld = SelectObject(hdcMem, i.handle); 3461 3462 GetObject(i.handle, bm.sizeof, &bm); 3463 3464 BitBlt(hdc, 0, 0, bm.bmWidth, bm.bmHeight, hdcMem, 0, 0, SRCCOPY); 3465 3466 SelectObject(hdcMem, hbmOld); 3467 DeleteDC(hdcMem); 3468 ReleaseDC(hwnd, hdc); 3469 3470 /* 3471 RECT r; 3472 r.right = i.width; 3473 r.bottom = i.height; 3474 InvalidateRect(hwnd, &r, false); 3475 */ 3476 } else 3477 version(X11) { 3478 if(!destroyed) { 3479 if(i.usingXshm) 3480 XShmPutImage(display, cast(Drawable) window, gc, i.handle, 0, 0, 0, 0, i.width, i.height, false); 3481 else 3482 XPutImage(display, cast(Drawable) window, gc, i.handle, 0, 0, 0, 0, i.width, i.height); 3483 } 3484 } else 3485 version(OSXCocoa) { 3486 draw().drawImage(Point(0, 0), i); 3487 setNeedsDisplay(view, true); 3488 } else static assert(0); 3489 +/ 3490 auto painter = this.draw; 3491 painter.drawImage(Point(0, 0), i); 3492 } 3493 3494 /++ 3495 Changes the cursor for the window. If the cursor is hidden via [hideCursor], this has no effect. 3496 3497 --- 3498 window.cursor = GenericCursor.Help; 3499 // now the window mouse cursor is set to a generic help 3500 --- 3501 3502 +/ 3503 @property void cursor(MouseCursor cursor) { 3504 version(OSXCocoa) 3505 {} // featureNotImplemented(); 3506 else 3507 if(this.impl.curHidden <= 0) { 3508 static if(UsingSimpledisplayX11) { 3509 auto ch = cursor.cursorHandle; 3510 XDefineCursor(XDisplayConnection.get(), this.impl.window, ch); 3511 } else version(Windows) { 3512 auto ch = cursor.cursorHandle; 3513 impl.currentCursor = ch; 3514 SetCursor(ch); // redraw without waiting for mouse movement to update 3515 } else featureNotImplemented(); 3516 } 3517 3518 } 3519 3520 /// What follows are the event handlers. These are set automatically 3521 /// by the eventLoop function, but are still public so you can change 3522 /// them later. wasPressed == true means key down. false == key up. 3523 3524 /// Handles a low-level keyboard event. Settable through setEventHandlers. 3525 void delegate(KeyEvent ke) handleKeyEvent; 3526 3527 /// Handles a higher level keyboard event - c is the character just pressed. Settable through setEventHandlers. 3528 void delegate(dchar c) handleCharEvent; 3529 3530 /// Handles a timer pulse. Settable through setEventHandlers. 3531 void delegate() handlePulse; 3532 3533 /// Called when the focus changes, param is if we have it (true) or are losing it (false). 3534 void delegate(bool) onFocusChange; 3535 3536 /** Called inside `close()` method. Our window is still alive, and we can free various resources. 3537 * Sometimes it is easier to setup the delegate instead of subclassing. */ 3538 void delegate() onClosing; 3539 3540 /** Called when we received destroy notification. At this stage we cannot do much with our window 3541 * (as it is already dead, and it's native handle cannot be used), but we still can do some 3542 * last minute cleanup. */ 3543 void delegate() onDestroyed; 3544 3545 static if (UsingSimpledisplayX11) 3546 /** Called when Expose event comes. See Xlib manual to understand the arguments. 3547 * Return `false` if you want Simpledisplay to copy backbuffer, or `true` if you did it yourself. 3548 * You will probably never need to setup this handler, it is for very low-level stuff. 3549 * 3550 * WARNING! Xlib is multithread-locked when this handles is called! */ 3551 bool delegate(int x, int y, int width, int height, int eventsLeft) handleExpose; 3552 3553 //version(Windows) 3554 //bool delegate(WPARAM wParam, LPARAM lParam) handleWM_PAINT; 3555 3556 private { 3557 int lastMouseX = int.min; 3558 int lastMouseY = int.min; 3559 void mdx(ref MouseEvent ev) { 3560 if(lastMouseX == int.min || lastMouseY == int.min) { 3561 ev.dx = 0; 3562 ev.dy = 0; 3563 } else { 3564 ev.dx = ev.x - lastMouseX; 3565 ev.dy = ev.y - lastMouseY; 3566 } 3567 3568 lastMouseX = ev.x; 3569 lastMouseY = ev.y; 3570 } 3571 } 3572 3573 /// Mouse event handler. Settable through setEventHandlers. 3574 void delegate(MouseEvent) handleMouseEvent; 3575 3576 /// use to redraw child widgets if you use system apis to add stuff 3577 void delegate() paintingFinished; 3578 3579 void delegate() paintingFinishedDg() { 3580 return paintingFinished; 3581 } 3582 3583 /// handle a resize, after it happens. You must construct the window with Resizability.allowResizing 3584 /// for this to ever happen. 3585 void delegate(int width, int height) windowResized; 3586 3587 /++ 3588 Platform specific - handle any native message this window gets. 3589 3590 Note: this is called *in addition to* other event handlers, unless you either: 3591 3592 1) On X11, return 0 indicating that you handled it. Any other return value is simply discarded. 3593 3594 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. 3595 3596 On Windows, your delegate takes the form of `int delegate(HWND hwnd, UINT message, WPARAM wParam, LPARAM lParam, out int mustReturn)`. 3597 3598 On X, it takes the form of `int delegate(XEvent)`. 3599 3600 History: 3601 In ancient versions, this was `static`. If you want a global hook, use [handleNativeGlobalEvent] instead. 3602 3603 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. 3604 +/ 3605 NativeEventHandler handleNativeEvent_; 3606 3607 @property NativeEventHandler handleNativeEvent() nothrow pure @nogc const @safe { 3608 return handleNativeEvent_; 3609 } 3610 @property void handleNativeEvent(NativeEventHandler neh) nothrow pure @nogc @safe { 3611 handleNativeEvent_ = neh; 3612 } 3613 3614 version(Windows) 3615 // compatibility shim with the old deprecated way 3616 // in this one, if you return 0, it means you must return. otherwise the ret value is ignored. 3617 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) { 3618 handleNativeEvent_ = delegate int(HWND h, UINT m, WPARAM w, LPARAM l, out int r) { 3619 auto ret = dg(h, m, w, l); 3620 if(ret == 0) 3621 r = 1; 3622 return ret; 3623 }; 3624 } 3625 3626 /// This is the same as handleNativeEvent, but static so it can hook ALL events in the loop. 3627 /// If you used to use handleNativeEvent depending on it being static, just change it to use 3628 /// this instead and it will work the same way. 3629 __gshared NativeEventHandler handleNativeGlobalEvent; 3630 3631 // private: 3632 /// The native implementation is available, but you shouldn't use it unless you are 3633 /// familiar with the underlying operating system, don't mind depending on it, and 3634 /// know simpledisplay.d's internals too. It is virtually private; you can hopefully 3635 /// do what you need to do with handleNativeEvent instead. 3636 /// 3637 /// This is likely to eventually change to be just a struct holding platform-specific 3638 /// handles instead of a template mixin at some point because I'm not happy with the 3639 /// code duplication here (ironically). 3640 mixin NativeSimpleWindowImplementation!() impl; 3641 3642 /** 3643 This is in-process one-way (from anything to window) event sending mechanics. 3644 It is thread-safe, so it can be used in multi-threaded applications to send, 3645 for example, "wake up and repaint" events when thread completed some operation. 3646 This will allow to avoid using timer pulse to check events with synchronization, 3647 'cause event handler will be called in UI thread. You can stop guessing which 3648 pulse frequency will be enough for your app. 3649 Note that events handlers may be called in arbitrary order, i.e. last registered 3650 handler can be called first, and vice versa. 3651 */ 3652 public: 3653 /** Is our custom event queue empty? Can be used in simple cases to prevent 3654 * "spamming" window with events it can't cope with. 3655 * It is safe to call this from non-UI threads. 3656 */ 3657 @property bool eventQueueEmpty() () { 3658 synchronized(this) { 3659 foreach (const ref o; eventQueue[0..eventQueueUsed]) if (!o.doProcess) return false; 3660 } 3661 return true; 3662 } 3663 3664 /** Does our custom event queue contains at least one with the given type? 3665 * Can be used in simple cases to prevent "spamming" window with events 3666 * it can't cope with. 3667 * It is safe to call this from non-UI threads. 3668 */ 3669 @property bool eventQueued(ET:Object) () { 3670 synchronized(this) { 3671 foreach (const ref o; eventQueue[0..eventQueueUsed]) { 3672 if (!o.doProcess) { 3673 if (cast(ET)(o.evt)) return true; 3674 } 3675 } 3676 } 3677 return false; 3678 } 3679 3680 /++ 3681 Event listeners added with [addEventListener] have their exceptions swallowed by the event loop. This delegate can handle them again before it proceeds. 3682 3683 History: 3684 Added May 12, 2021 3685 +/ 3686 void delegate(Exception e) nothrow eventUncaughtException; 3687 3688 /** Add listener for custom event. Can be used like this: 3689 * 3690 * --------------------- 3691 * auto eid = win.addEventListener((MyStruct evt) { ... }); 3692 * ... 3693 * win.removeEventListener(eid); 3694 * --------------------- 3695 * 3696 * Returns: 0 on failure (should never happen, so ignore it) 3697 * 3698 * $(WARNING Don't use this method in object destructors!) 3699 * 3700 * $(WARNING It is better to register all event handlers and don't remove 'em, 3701 * 'cause if event handler id counter will overflow, you won't be able 3702 * to register any more events.) 3703 */ 3704 uint addEventListener(ET:Object) (void delegate (ET) dg) { 3705 if (dg is null) return 0; // ignore empty handlers 3706 synchronized(this) { 3707 //FIXME: abort on overflow? 3708 if (++lastUsedHandlerId == 0) { --lastUsedHandlerId; return 0; } // alas, can't register more events. at all. 3709 EventHandlerEntry e; 3710 e.dg = delegate (Object o) { 3711 if (auto co = cast(ET)o) { 3712 try { 3713 dg(co); 3714 } catch (Exception e) { 3715 // sorry! 3716 if(eventUncaughtException) 3717 eventUncaughtException(e); 3718 } 3719 return true; 3720 } 3721 return false; 3722 }; 3723 e.id = lastUsedHandlerId; 3724 auto optr = eventHandlers.ptr; 3725 eventHandlers ~= e; 3726 if (eventHandlers.ptr !is optr) { 3727 import core.memory : GC; 3728 if (eventHandlers.ptr is GC.addrOf(eventHandlers.ptr)) GC.setAttr(eventHandlers.ptr, GC.BlkAttr.NO_INTERIOR); 3729 } 3730 return lastUsedHandlerId; 3731 } 3732 } 3733 3734 /// Remove event listener. It is safe to pass invalid event id here. 3735 /// $(WARNING Don't use this method in object destructors!) 3736 void removeEventListener() (uint id) { 3737 if (id == 0 || id > lastUsedHandlerId) return; 3738 synchronized(this) { 3739 foreach (immutable idx; 0..eventHandlers.length) { 3740 if (eventHandlers[idx].id == id) { 3741 foreach (immutable c; idx+1..eventHandlers.length) eventHandlers[c-1] = eventHandlers[c]; 3742 eventHandlers[$-1].dg = null; 3743 eventHandlers.length -= 1; 3744 eventHandlers.assumeSafeAppend; 3745 return; 3746 } 3747 } 3748 } 3749 } 3750 3751 /// Post event to queue. It is safe to call this from non-UI threads. 3752 /// If `timeoutmsecs` is greater than zero, the event will be delayed for at least `timeoutmsecs` milliseconds. 3753 /// if `replace` is `true`, replace all existing events typed `ET` with the new one (if `evt` is empty, remove 'em all) 3754 /// Returns `true` if event was queued. Always returns `false` if `evt` is null. 3755 bool postTimeout(ET:Object) (ET evt, uint timeoutmsecs, bool replace=false) { 3756 if (this.closed) return false; // closed windows can't handle events 3757 3758 // remove all events of type `ET` 3759 void removeAllET () { 3760 uint eidx = 0, ec = eventQueueUsed; 3761 auto eptr = eventQueue.ptr; 3762 while (eidx < ec) { 3763 if (eptr.doProcess) { ++eidx; ++eptr; continue; } 3764 if (cast(ET)eptr.evt !is null) { 3765 // i found her! 3766 if (inCustomEventProcessor) { 3767 // if we're in custom event processing loop, processor will clear it for us 3768 eptr.evt = null; 3769 ++eidx; 3770 ++eptr; 3771 } else { 3772 foreach (immutable c; eidx+1..ec) eventQueue.ptr[c-1] = eventQueue.ptr[c]; 3773 ec = --eventQueueUsed; 3774 // clear last event (it is already copied) 3775 eventQueue.ptr[ec].evt = null; 3776 } 3777 } else { 3778 ++eidx; 3779 ++eptr; 3780 } 3781 } 3782 } 3783 3784 if (evt is null) { 3785 if (replace) { synchronized(this) removeAllET(); } 3786 // ignore empty events, they can't be handled anyway 3787 return false; 3788 } 3789 3790 // add events even if no event FD/event object created yet 3791 synchronized(this) { 3792 if (replace) removeAllET(); 3793 if (eventQueueUsed == uint.max) return false; // just in case 3794 if (eventQueueUsed < eventQueue.length) { 3795 eventQueue[eventQueueUsed++] = QueuedEvent(evt, timeoutmsecs); 3796 } else { 3797 if (eventQueue.capacity == eventQueue.length) { 3798 // need to reallocate; do a trick to ensure that old array is cleared 3799 auto oarr = eventQueue; 3800 eventQueue ~= QueuedEvent(evt, timeoutmsecs); 3801 // just in case, do yet another check 3802 if (oarr.length != 0 && oarr.ptr !is eventQueue.ptr) foreach (ref e; oarr[0..eventQueueUsed]) e.evt = null; 3803 import core.memory : GC; 3804 if (eventQueue.ptr is GC.addrOf(eventQueue.ptr)) GC.setAttr(eventQueue.ptr, GC.BlkAttr.NO_INTERIOR); 3805 } else { 3806 auto optr = eventQueue.ptr; 3807 eventQueue ~= QueuedEvent(evt, timeoutmsecs); 3808 assert(eventQueue.ptr is optr); 3809 } 3810 ++eventQueueUsed; 3811 assert(eventQueueUsed == eventQueue.length); 3812 } 3813 if (!eventWakeUp()) { 3814 // can't wake up event processor, so there is no reason to keep the event 3815 assert(eventQueueUsed > 0); 3816 eventQueue[--eventQueueUsed].evt = null; 3817 return false; 3818 } 3819 return true; 3820 } 3821 } 3822 3823 /// Post event to queue. It is safe to call this from non-UI threads. 3824 /// if `replace` is `true`, replace all existing events typed `ET` with the new one (if `evt` is empty, remove 'em all) 3825 /// Returns `true` if event was queued. Always returns `false` if `evt` is null. 3826 bool postEvent(ET:Object) (ET evt, bool replace=false) { 3827 return postTimeout!ET(evt, 0, replace); 3828 } 3829 3830 private: 3831 private import core.time : MonoTime; 3832 3833 version(Posix) { 3834 __gshared int customEventFDRead = -1; 3835 __gshared int customEventFDWrite = -1; 3836 __gshared int customSignalFD = -1; 3837 } else version(Windows) { 3838 __gshared HANDLE customEventH = null; 3839 } 3840 3841 // wake up event processor 3842 static bool eventWakeUp () { 3843 version(X11) { 3844 import core.sys.posix.unistd : write; 3845 ulong n = 1; 3846 if (customEventFDWrite >= 0) write(customEventFDWrite, &n, n.sizeof); 3847 return true; 3848 } else version(Windows) { 3849 if (customEventH !is null) SetEvent(customEventH); 3850 return true; 3851 } else version(OSXCocoa) { 3852 if(globalAppDelegate) 3853 globalAppDelegate.performSelectorOnMainThread(sel_registerName("sdpyCustomEventWakeup:"), null, false); 3854 return true; 3855 } else { 3856 // not implemented for other OSes 3857 return false; 3858 } 3859 } 3860 3861 static struct QueuedEvent { 3862 Object evt; 3863 bool timed = false; 3864 MonoTime hittime = MonoTime.zero; 3865 bool doProcess = false; // process event at the current iteration (internal flag) 3866 3867 this (Object aevt, uint toutmsecs) { 3868 evt = aevt; 3869 if (toutmsecs > 0) { 3870 import core.time : msecs; 3871 timed = true; 3872 hittime = MonoTime.currTime+toutmsecs.msecs; 3873 } 3874 } 3875 } 3876 3877 alias CustomEventHandler = bool delegate (Object o) nothrow; 3878 static struct EventHandlerEntry { 3879 CustomEventHandler dg; 3880 uint id; 3881 } 3882 3883 uint lastUsedHandlerId; 3884 EventHandlerEntry[] eventHandlers; 3885 QueuedEvent[] eventQueue = null; 3886 uint eventQueueUsed = 0; // to avoid `.assumeSafeAppend` and length changes 3887 bool inCustomEventProcessor = false; // required to properly remove events 3888 3889 // process queued events and call custom event handlers 3890 // this will not process events posted from called handlers (such events are postponed for the next iteration) 3891 void processCustomEvents () @system { 3892 bool hasSomethingToDo = false; 3893 uint ecount; 3894 bool ocep; 3895 synchronized(this) { 3896 ocep = inCustomEventProcessor; 3897 inCustomEventProcessor = true; 3898 ecount = eventQueueUsed; // user may want to post new events from an event handler; process 'em on next iteration 3899 auto ctt = MonoTime.currTime; 3900 bool hasEmpty = false; 3901 // mark events to process (this is required for `eventQueued()`) 3902 foreach (ref qe; eventQueue[0..ecount]) { 3903 if (qe.evt is null) { hasEmpty = true; continue; } 3904 if (qe.timed) { 3905 qe.doProcess = (qe.hittime <= ctt); 3906 } else { 3907 qe.doProcess = true; 3908 } 3909 hasSomethingToDo = (hasSomethingToDo || qe.doProcess); 3910 } 3911 if (!hasSomethingToDo) { 3912 // remove empty events 3913 if (hasEmpty) { 3914 uint eidx = 0, ec = eventQueueUsed; 3915 auto eptr = eventQueue.ptr; 3916 while (eidx < ec) { 3917 if (eptr.evt is null) { 3918 foreach (immutable c; eidx+1..ec) eventQueue.ptr[c-1] = eventQueue.ptr[c]; 3919 ec = --eventQueueUsed; 3920 eventQueue.ptr[ec].evt = null; // make GC life easier 3921 } else { 3922 ++eidx; 3923 ++eptr; 3924 } 3925 } 3926 } 3927 inCustomEventProcessor = ocep; 3928 return; 3929 } 3930 } 3931 // process marked events 3932 uint efree = 0; // non-processed events will be put at this index 3933 EventHandlerEntry[] eh; 3934 Object evt; 3935 foreach (immutable eidx; 0..ecount) { 3936 synchronized(this) { 3937 if (!eventQueue[eidx].doProcess) { 3938 // skip this event 3939 assert(efree <= eidx); 3940 if (efree != eidx) { 3941 // copy this event to queue start 3942 eventQueue[efree] = eventQueue[eidx]; 3943 eventQueue[eidx].evt = null; // just in case 3944 } 3945 ++efree; 3946 continue; 3947 } 3948 evt = eventQueue[eidx].evt; 3949 eventQueue[eidx].evt = null; // in case event handler will hit GC 3950 if (evt is null) continue; // just in case 3951 // try all handlers; this can be slow, but meh... 3952 eh = eventHandlers; 3953 } 3954 foreach (ref evhan; eh) if (evhan.dg !is null) evhan.dg(evt); 3955 evt = null; 3956 eh = null; 3957 } 3958 synchronized(this) { 3959 // move all unprocessed events to queue top; efree holds first "free index" 3960 foreach (immutable eidx; ecount..eventQueueUsed) { 3961 assert(efree <= eidx); 3962 if (efree != eidx) eventQueue[efree] = eventQueue[eidx]; 3963 ++efree; 3964 } 3965 eventQueueUsed = efree; 3966 // wake up event processor on next event loop iteration if we have more queued events 3967 // also, remove empty events 3968 bool awaken = false; 3969 uint eidx = 0, ec = eventQueueUsed; 3970 auto eptr = eventQueue.ptr; 3971 while (eidx < ec) { 3972 if (eptr.evt is null) { 3973 foreach (immutable c; eidx+1..ec) eventQueue.ptr[c-1] = eventQueue.ptr[c]; 3974 ec = --eventQueueUsed; 3975 eventQueue.ptr[ec].evt = null; // make GC life easier 3976 } else { 3977 if (!awaken && !eptr.timed) { eventWakeUp(); awaken = true; } 3978 ++eidx; 3979 ++eptr; 3980 } 3981 } 3982 inCustomEventProcessor = ocep; 3983 } 3984 } 3985 3986 // for all windows in nativeMapping 3987 package static void processAllCustomEvents () @system { 3988 3989 cleanupQueue.process(); 3990 3991 justCommunication.processCustomEvents(); 3992 3993 foreach (SimpleWindow sw; SimpleWindow.nativeMapping.byValue) { 3994 if (sw is null || sw.closed) continue; 3995 sw.processCustomEvents(); 3996 } 3997 3998 runPendingRunInGuiThreadDelegates(); 3999 } 4000 4001 // 0: infinite (i.e. no scheduled events in queue) 4002 uint eventQueueTimeoutMSecs () { 4003 synchronized(this) { 4004 if (eventQueueUsed == 0) return 0; 4005 if (inCustomEventProcessor) assert(0, "WUTAFUUUUUUU..."); // the thing that should not be. ABSOLUTELY! (c) 4006 uint res = int.max; 4007 auto ctt = MonoTime.currTime; 4008 foreach (const ref qe; eventQueue[0..eventQueueUsed]) { 4009 if (qe.evt is null) assert(0, "WUTAFUUUUUUU..."); // the thing that should not be. ABSOLUTELY! (c) 4010 if (qe.doProcess) continue; // just in case 4011 if (!qe.timed) return 1; // minimal 4012 if (qe.hittime <= ctt) return 1; // minimal 4013 auto tms = (qe.hittime-ctt).total!"msecs"; 4014 if (tms < 1) tms = 1; // safety net 4015 if (tms >= int.max) tms = int.max-1; // and another safety net 4016 if (res > tms) res = cast(uint)tms; 4017 } 4018 return (res >= int.max ? 0 : res); 4019 } 4020 } 4021 4022 // for all windows in nativeMapping 4023 static uint eventAllQueueTimeoutMSecs () { 4024 uint res = uint.max; 4025 foreach (SimpleWindow sw; SimpleWindow.nativeMapping.byValue) { 4026 if (sw is null || sw.closed) continue; 4027 uint to = sw.eventQueueTimeoutMSecs(); 4028 if (to && to < res) { 4029 res = to; 4030 if (to == 1) break; // can't have less than this 4031 } 4032 } 4033 return (res >= int.max ? 0 : res); 4034 } 4035 4036 version(X11) { 4037 ResizeEvent pendingResizeEvent; 4038 } 4039 4040 /++ 4041 When in opengl mode and automatically resizing, it will set the opengl viewport to stretch. 4042 4043 If you work with multiple opengl contexts and/or threads, this might be more trouble than it is 4044 worth so you can disable it by setting this to `true`. 4045 4046 History: 4047 Added November 13, 2022. 4048 +/ 4049 public bool suppressAutoOpenglViewport = false; 4050 private void updateOpenglViewportIfNeeded(int width, int height) { 4051 if(suppressAutoOpenglViewport) return; 4052 4053 version(without_opengl) {} else 4054 if(openglMode == OpenGlOptions.yes && resizability == Resizability.automaticallyScaleIfPossible) { 4055 // writeln(width, " ", height); 4056 setAsCurrentOpenGlContextNT(); 4057 glViewport(0, 0, width, height); 4058 } 4059 } 4060 4061 // TODO: Implement on non-Windows platforms (where available). 4062 private CornerStyle _fauxCornerStyle = CornerStyle.automatic; 4063 4064 /++ 4065 Style of the window's corners 4066 4067 $(WARNING 4068 Currently only implemented on Windows targets. 4069 Has no visual effect elsewhere. 4070 4071 Windows: Requires Windows 11 or later. 4072 ) 4073 4074 History: 4075 Added September 09, 2024. 4076 +/ 4077 public CornerStyle cornerStyle() @trusted { 4078 version(Windows) { 4079 DWM_WINDOW_CORNER_PREFERENCE dwmCorner; 4080 const apiResult = DwmGetWindowAttribute( 4081 this.hwnd, 4082 DWMWINDOWATTRIBUTE.DWMWA_WINDOW_CORNER_PREFERENCE, 4083 &dwmCorner, 4084 typeof(dwmCorner).sizeof 4085 ); 4086 4087 if (apiResult != S_OK) { 4088 // Unsupported? 4089 if (apiResult == E_INVALIDARG) { 4090 // Feature unsupported; Windows version probably too old. 4091 // Requires Windows 11 (build 22000) or later. 4092 return _fauxCornerStyle; 4093 } 4094 4095 throw new WindowsApiException("DwmGetWindowAttribute", apiResult); 4096 } 4097 4098 CornerStyle corner; 4099 if (!dwmCorner.fromDWM(corner)) { 4100 throw ArsdException!"DwmGetWindowAttribute unfamiliar corner preference"(dwmCorner); 4101 } 4102 return corner; 4103 } else { 4104 return _fauxCornerStyle; 4105 } 4106 } 4107 4108 /// ditto 4109 public void cornerStyle(const CornerStyle corner) @trusted { 4110 version(Windows) { 4111 DWM_WINDOW_CORNER_PREFERENCE dwmCorner; 4112 if (!corner.toDWM(dwmCorner)) { 4113 assert(false, "This should have been impossible because of a final switch."); 4114 } 4115 4116 const apiResult = DwmSetWindowAttribute( 4117 this.hwnd, 4118 DWMWINDOWATTRIBUTE.DWMWA_WINDOW_CORNER_PREFERENCE, 4119 &dwmCorner, 4120 typeof(dwmCorner).sizeof 4121 ); 4122 4123 if (apiResult != S_OK) { 4124 // Unsupported? 4125 if (apiResult == E_INVALIDARG) { 4126 // Feature unsupported; Windows version probably too old. 4127 // Requires Windows 11 (build 22000) or later. 4128 _fauxCornerStyle = corner; 4129 return; 4130 } 4131 4132 throw new WindowsApiException("DwmSetWindowAttribute", apiResult); 4133 } 4134 } else { 4135 _fauxCornerStyle = corner; 4136 } 4137 } 4138 } 4139 4140 version(OSXCocoa) 4141 enum NSWindow NullWindow = null; 4142 else 4143 enum NullWindow = NativeWindowHandle.init; 4144 4145 /++ 4146 Magic pseudo-window for just posting events to a global queue. 4147 4148 Not entirely supported, I might delete it at any time. 4149 4150 Added Nov 5, 2021. 4151 +/ 4152 __gshared SimpleWindow justCommunication = new SimpleWindow(NullWindow); 4153 4154 /* Drag and drop support { */ 4155 version(X11) { 4156 4157 } else version(Windows) { 4158 import core.sys.windows.uuid; 4159 import core.sys.windows.ole2; 4160 import core.sys.windows.oleidl; 4161 import core.sys.windows.objidl; 4162 import core.sys.windows.wtypes; 4163 4164 pragma(lib, "ole32"); 4165 void initDnd() { 4166 auto err = OleInitialize(null); 4167 if(err != S_OK && err != S_FALSE) 4168 throw new Exception("init");//err); 4169 } 4170 } 4171 /* } End drag and drop support */ 4172 4173 4174 /// Represents a mouse cursor (aka the mouse pointer, the image seen on screen that indicates where the mouse is pointing). 4175 /// See [GenericCursor]. 4176 class MouseCursor { 4177 int osId; 4178 bool isStockCursor; 4179 private this(int osId) { 4180 this.osId = osId; 4181 this.isStockCursor = true; 4182 } 4183 4184 // https://msdn.microsoft.com/en-us/library/windows/desktop/ms648385(v=vs.85).aspx 4185 this(int xHotSpot, int yHotSpot, ubyte[] andMask, ubyte[] xorMask) {} 4186 4187 version(Windows) { 4188 HCURSOR cursor_; 4189 HCURSOR cursorHandle() { 4190 if(cursor_ is null) 4191 cursor_ = LoadCursor(null, MAKEINTRESOURCE(osId)); 4192 return cursor_; 4193 } 4194 4195 } else static if(UsingSimpledisplayX11) { 4196 Cursor cursor_ = None; 4197 int xDisplaySequence; 4198 4199 Cursor cursorHandle() { 4200 if(this.osId == None) 4201 return None; 4202 4203 // we need to reload if we on a new X connection 4204 if(cursor_ == None || XDisplayConnection.connectionSequenceNumber != xDisplaySequence) { 4205 cursor_ = XCreateFontCursor(XDisplayConnection.get(), this.osId); 4206 xDisplaySequence = XDisplayConnection.connectionSequenceNumber; 4207 } 4208 return cursor_; 4209 } 4210 } 4211 } 4212 4213 // https://developer.mozilla.org/en-US/docs/Web/CSS/cursor 4214 // https://tronche.com/gui/x/xlib/appendix/b/ 4215 // https://msdn.microsoft.com/en-us/library/windows/desktop/ms648391(v=vs.85).aspx 4216 /// 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. 4217 enum GenericCursorType { 4218 Default, /// The default arrow pointer. 4219 Wait, /// A cursor indicating something is loading and the user must wait. 4220 Hand, /// A pointing finger, like the one used hovering over hyperlinks in a web browser. 4221 Help, /// A cursor indicating the user can get help about the pointer location. 4222 Cross, /// A crosshair. 4223 Text, /// An i-beam shape, typically used to indicate text selection is possible. 4224 Move, /// Pointer indicating movement is possible. May also be used as SizeAll. 4225 UpArrow, /// An arrow pointing straight up. 4226 Progress, /// The hourglass and arrow, indicating the computer is working but the user can still work. Not great results on X11. 4227 NotAllowed, /// Indicates the current operation is not allowed. Not great results on X11. 4228 SizeNesw, /// Arrow pointing northeast and southwest (lower-left corner resize indicator). 4229 SizeNs, /// Arrow pointing north and south (upper/lower edge resize indicator). 4230 SizeNwse, /// Arrow pointing northwest and southeast (upper-left corner resize indicator). 4231 SizeWe, /// Arrow pointing west and east (left/right edge resize indicator). 4232 4233 } 4234 4235 /* 4236 X_plus == css cell == Windows ? 4237 */ 4238 4239 /// You get one by `GenericCursor.SomeTime`. See [GenericCursorType] for a list of types. 4240 static struct GenericCursor { 4241 static: 4242 /// 4243 MouseCursor opDispatch(string str)() if(__traits(hasMember, GenericCursorType, str)) { 4244 static MouseCursor mc; 4245 4246 auto type = __traits(getMember, GenericCursorType, str); 4247 4248 if(mc is null) { 4249 4250 version(Windows) { 4251 int osId; 4252 final switch(type) { 4253 case GenericCursorType.Default: osId = IDC_ARROW; break; 4254 case GenericCursorType.Wait: osId = IDC_WAIT; break; 4255 case GenericCursorType.Hand: osId = IDC_HAND; break; 4256 case GenericCursorType.Help: osId = IDC_HELP; break; 4257 case GenericCursorType.Cross: osId = IDC_CROSS; break; 4258 case GenericCursorType.Text: osId = IDC_IBEAM; break; 4259 case GenericCursorType.Move: osId = IDC_SIZEALL; break; 4260 case GenericCursorType.UpArrow: osId = IDC_UPARROW; break; 4261 case GenericCursorType.Progress: osId = IDC_APPSTARTING; break; 4262 case GenericCursorType.NotAllowed: osId = IDC_NO; break; 4263 case GenericCursorType.SizeNesw: osId = IDC_SIZENESW; break; 4264 case GenericCursorType.SizeNs: osId = IDC_SIZENS; break; 4265 case GenericCursorType.SizeNwse: osId = IDC_SIZENWSE; break; 4266 case GenericCursorType.SizeWe: osId = IDC_SIZEWE; break; 4267 } 4268 } else static if(UsingSimpledisplayX11) { 4269 int osId; 4270 final switch(type) { 4271 case GenericCursorType.Default: osId = None; break; 4272 case GenericCursorType.Wait: osId = 150 /* XC_watch */; break; 4273 case GenericCursorType.Hand: osId = 60 /* XC_hand2 */; break; 4274 case GenericCursorType.Help: osId = 92 /* XC_question_arrow */; break; 4275 case GenericCursorType.Cross: osId = 34 /* XC_crosshair */; break; 4276 case GenericCursorType.Text: osId = 152 /* XC_xterm */; break; 4277 case GenericCursorType.Move: osId = 52 /* XC_fleur */; break; 4278 case GenericCursorType.UpArrow: osId = 22 /* XC_center_ptr */; break; 4279 case GenericCursorType.Progress: osId = 150 /* XC_watch, best i can do i think */; break; 4280 4281 case GenericCursorType.NotAllowed: osId = 24 /* XC_circle. not great */; break; 4282 case GenericCursorType.SizeNesw: osId = 12 /* XC_bottom_left_corner */ ; break; 4283 case GenericCursorType.SizeNs: osId = 116 /* XC_sb_v_double_arrow */; break; 4284 case GenericCursorType.SizeNwse: osId = 14 /* XC_bottom_right_corner */; break; 4285 case GenericCursorType.SizeWe: osId = 108 /* XC_sb_h_double_arrow */; break; 4286 } 4287 4288 } else { 4289 int osId; 4290 // featureNotImplemented(); 4291 } 4292 4293 mc = new MouseCursor(osId); 4294 } 4295 return mc; 4296 } 4297 } 4298 4299 4300 /++ 4301 If you want to get more control over the event loop, you can use this. 4302 4303 Typically though, you can just call [SimpleWindow.eventLoop] which forwards 4304 to `EventLoop.get.run`. 4305 +/ 4306 struct EventLoop { 4307 @disable this(); 4308 4309 /// Gets a reference to an existing event loop 4310 static EventLoop get() { 4311 return EventLoop(0, null); 4312 } 4313 4314 static void quitApplication() { 4315 version(use_arsd_core) { 4316 import arsd.core; 4317 ICoreEventLoop.exitApplication(); 4318 } 4319 EventLoop.get().exit(); 4320 } 4321 4322 private __gshared static Object monitor = new Object(); // deliberate CTFE usage here fyi 4323 4324 /// Construct an application-global event loop for yourself 4325 /// See_Also: [SimpleWindow.setEventHandlers] 4326 this(long pulseTimeout, void delegate() handlePulse) { 4327 synchronized(monitor) { 4328 if(impl is null) { 4329 claimGuiThread(); 4330 version(sdpy_thread_checks) assert(thisIsGuiThread); 4331 impl = new EventLoopImpl(pulseTimeout, handlePulse); 4332 } else { 4333 if(pulseTimeout) { 4334 impl.pulseTimeout = pulseTimeout; 4335 impl.handlePulse = handlePulse; 4336 } 4337 } 4338 impl.refcount++; 4339 } 4340 } 4341 4342 ~this() { 4343 if(impl is null) 4344 return; 4345 impl.refcount--; 4346 if(impl.refcount == 0) { 4347 impl.dispose(); 4348 if(thisIsGuiThread) 4349 guiThreadFinalize(); 4350 } 4351 4352 } 4353 4354 this(this) { 4355 if(impl is null) 4356 return; 4357 impl.refcount++; 4358 } 4359 4360 /// Runs the event loop until the whileCondition, if present, returns false 4361 int run(bool delegate() whileCondition = null) { 4362 assert(impl !is null); 4363 impl.notExited = true; 4364 return impl.run(whileCondition); 4365 } 4366 4367 /// Exits the event loop, but allows you to reenter it again later (in contrast with quitApplication, which tries to terminate the program) 4368 void exit() { 4369 assert(impl !is null); 4370 impl.notExited = false; 4371 4372 version(use_arsd_core) { 4373 import arsd.core; 4374 ICoreEventLoop.exitApplication(); 4375 } 4376 } 4377 4378 version(linux) 4379 ref void delegate(int) signalHandler() { 4380 assert(impl !is null); 4381 return impl.signalHandler; 4382 } 4383 4384 __gshared static EventLoopImpl* impl; 4385 } 4386 4387 version(linux) 4388 void delegate(int, int) globalHupHandler; 4389 4390 version(Posix) 4391 void makeNonBlocking(int fd) { 4392 import fcntl = core.sys.posix.fcntl; 4393 auto flags = fcntl.fcntl(fd, fcntl.F_GETFL, 0); 4394 if(flags == -1) 4395 throw new Exception("fcntl get"); 4396 flags |= fcntl.O_NONBLOCK; 4397 auto s = fcntl.fcntl(fd, fcntl.F_SETFL, flags); 4398 if(s == -1) 4399 throw new Exception("fcntl set"); 4400 } 4401 4402 struct EventLoopImpl { 4403 int refcount; 4404 4405 bool notExited = true; 4406 4407 version(Emscripten) { 4408 void delegate(int) signalHandler; 4409 static import unix = core.sys.posix.unistd; 4410 static import err = core.stdc.errno; 4411 } else 4412 version(linux) { 4413 static import ep = core.sys.linux.epoll; 4414 static import unix = core.sys.posix.unistd; 4415 static import err = core.stdc.errno; 4416 import core.sys.linux.timerfd; 4417 4418 void delegate(int) signalHandler; 4419 } 4420 4421 version(X11) { 4422 int pulseFd = -1; 4423 version(Emscripten) {} else 4424 version(linux) ep.epoll_event[16] events = void; 4425 } else version(Windows) { 4426 Timer pulser; 4427 HANDLE[] handles; 4428 } 4429 4430 4431 /// "Lock" this window handle, to do multithreaded synchronization. You probably won't need 4432 /// to call this, as it's not recommended to share window between threads. 4433 void mtLock () { 4434 version(X11) { 4435 XLockDisplay(this.display); 4436 } 4437 } 4438 4439 version(X11) 4440 auto display() { return XDisplayConnection.get; } 4441 4442 /// "Unlock" this window handle, to do multithreaded synchronization. You probably won't need 4443 /// to call this, as it's not recommended to share window between threads. 4444 void mtUnlock () { 4445 version(X11) { 4446 XUnlockDisplay(this.display); 4447 } 4448 } 4449 4450 version(with_eventloop) 4451 void initialize(long pulseTimeout) {} 4452 else 4453 void initialize(long pulseTimeout) @system { 4454 version(Windows) { 4455 if(pulseTimeout && handlePulse !is null) 4456 pulser = new Timer(cast(int) pulseTimeout, handlePulse); 4457 4458 if (customEventH is null) { 4459 customEventH = CreateEvent(null, FALSE/*autoreset*/, FALSE/*initial state*/, null); 4460 if (customEventH !is null) { 4461 handles ~= customEventH; 4462 } else { 4463 // this is something that should not be; better be safe than sorry 4464 throw new Exception("can't create eventfd for custom event processing"); 4465 } 4466 } 4467 4468 SimpleWindow.processAllCustomEvents(); // process events added before event object creation 4469 } 4470 4471 version(Emscripten) { 4472 4473 } else version(linux) { 4474 prepareEventLoop(); 4475 { 4476 auto display = XDisplayConnection.get; 4477 // adding Xlib file 4478 ep.epoll_event ev = void; 4479 { import core.stdc.string : memset; memset(&ev, 0, ev.sizeof); } // this makes valgrind happy 4480 ev.events = ep.EPOLLIN; 4481 ev.data.fd = display.fd; 4482 if(ep.epoll_ctl(epollFd, ep.EPOLL_CTL_ADD, display.fd, &ev) == -1) 4483 throw new Exception("add x fd");// ~ to!string(epollFd)); 4484 displayFd = display.fd; 4485 } 4486 4487 if(pulseTimeout && handlePulse !is null) { 4488 pulseFd = timerfd_create(CLOCK_MONOTONIC, 0); 4489 if(pulseFd == -1) 4490 throw new Exception("pulse timer create failed"); 4491 4492 itimerspec value; 4493 value.it_value.tv_sec = cast(int) (pulseTimeout / 1000); 4494 value.it_value.tv_nsec = (pulseTimeout % 1000) * 1000_000; 4495 4496 value.it_interval.tv_sec = cast(int) (pulseTimeout / 1000); 4497 value.it_interval.tv_nsec = (pulseTimeout % 1000) * 1000_000; 4498 4499 if(timerfd_settime(pulseFd, 0, &value, null) == -1) 4500 throw new Exception("couldn't make pulse timer"); 4501 4502 ep.epoll_event ev = void; 4503 { import core.stdc.string : memset; memset(&ev, 0, ev.sizeof); } // this makes valgrind happy 4504 ev.events = ep.EPOLLIN; 4505 ev.data.fd = pulseFd; 4506 ep.epoll_ctl(epollFd, ep.EPOLL_CTL_ADD, pulseFd, &ev); 4507 } 4508 4509 // eventfd for custom events 4510 if (customEventFDWrite == -1) { 4511 customEventFDWrite = eventfd(0, 0); 4512 customEventFDRead = customEventFDWrite; 4513 if (customEventFDRead >= 0) { 4514 ep.epoll_event ev = void; 4515 { import core.stdc.string : memset; memset(&ev, 0, ev.sizeof); } // this makes valgrind happy 4516 ev.events = ep.EPOLLIN; 4517 ev.data.fd = customEventFDRead; 4518 ep.epoll_ctl(epollFd, ep.EPOLL_CTL_ADD, customEventFDRead, &ev); 4519 } else { 4520 // this is something that should not be; better be safe than sorry 4521 throw new Exception("can't create eventfd for custom event processing"); 4522 } 4523 } 4524 4525 if (customSignalFD == -1) { 4526 import core.sys.linux.sys.signalfd; 4527 4528 sigset_t sigset; 4529 auto err = sigemptyset(&sigset); 4530 assert(!err); 4531 err = sigaddset(&sigset, SIGINT); 4532 assert(!err); 4533 err = sigaddset(&sigset, SIGHUP); 4534 assert(!err); 4535 err = sigprocmask(SIG_BLOCK, &sigset, null); 4536 assert(!err); 4537 4538 customSignalFD = signalfd(-1, &sigset, SFD_NONBLOCK); 4539 assert(customSignalFD != -1); 4540 4541 ep.epoll_event ev = void; 4542 { import core.stdc.string : memset; memset(&ev, 0, ev.sizeof); } // this makes valgrind happy 4543 ev.events = ep.EPOLLIN; 4544 ev.data.fd = customSignalFD; 4545 ep.epoll_ctl(epollFd, ep.EPOLL_CTL_ADD, customSignalFD, &ev); 4546 } 4547 } else version(Posix) { 4548 prepareEventLoop(); 4549 if (customEventFDRead == -1) { 4550 int[2] bfr; 4551 import core.sys.posix.unistd; 4552 auto ret = pipe(bfr); 4553 if(ret == -1) throw new Exception("pipe"); 4554 customEventFDRead = bfr[0]; 4555 customEventFDWrite = bfr[1]; 4556 } 4557 4558 } 4559 4560 SimpleWindow.processAllCustomEvents(); // process events added before event FD creation 4561 4562 version(linux) { 4563 this.mtLock(); 4564 scope(exit) this.mtUnlock(); 4565 version(X11) 4566 XPending(display); // no, really 4567 } 4568 4569 disposed = false; 4570 } 4571 4572 bool disposed = true; 4573 version(X11) 4574 int displayFd = -1; 4575 4576 version(with_eventloop) 4577 void dispose() {} 4578 else 4579 void dispose() @system { 4580 disposed = true; 4581 version(X11) { 4582 if(pulseFd != -1) { 4583 import unix = core.sys.posix.unistd; 4584 unix.close(pulseFd); 4585 pulseFd = -1; 4586 } 4587 4588 version(Emscripten) {} else 4589 version(linux) 4590 if(displayFd != -1) { 4591 // 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 4592 ep.epoll_event ev = void; 4593 { import core.stdc.string : memset; memset(&ev, 0, ev.sizeof); } // this makes valgrind happy 4594 ev.events = ep.EPOLLIN; 4595 ev.data.fd = displayFd; 4596 ep.epoll_ctl(epollFd, ep.EPOLL_CTL_DEL, displayFd, &ev); 4597 displayFd = -1; 4598 } 4599 4600 } else version(Windows) { 4601 if(pulser !is null) { 4602 pulser.destroy(); 4603 pulser = null; 4604 } 4605 if (customEventH !is null) { 4606 CloseHandle(customEventH); 4607 customEventH = null; 4608 } 4609 } 4610 } 4611 4612 this(long pulseTimeout, void delegate() handlePulse) { 4613 this.pulseTimeout = pulseTimeout; 4614 this.handlePulse = handlePulse; 4615 initialize(pulseTimeout); 4616 } 4617 4618 private long pulseTimeout; 4619 void delegate() handlePulse; 4620 4621 ~this() { 4622 dispose(); 4623 } 4624 4625 version(Posix) 4626 ref int customEventFDRead() { return SimpleWindow.customEventFDRead; } 4627 version(Posix) 4628 ref int customEventFDWrite() { return SimpleWindow.customEventFDWrite; } 4629 version(linux) 4630 ref int customSignalFD() { return SimpleWindow.customSignalFD; } 4631 version(Windows) 4632 ref auto customEventH() { return SimpleWindow.customEventH; } 4633 4634 version(X11) { 4635 bool doXPending() { 4636 bool done = false; 4637 4638 this.mtLock(); 4639 scope(exit) this.mtUnlock(); 4640 //{ import core.stdc.stdio; printf("*** queued: %d\n", XEventsQueued(this.display, QueueMode.QueuedAlready)); } 4641 while(!done && XPending(display)) { 4642 done = doXNextEvent(this.display); 4643 } 4644 4645 return done; 4646 } 4647 void doXNextEventVoid() { 4648 doXPending(); 4649 } 4650 } 4651 4652 version(with_eventloop) { 4653 int loopHelper(bool delegate() whileCondition) { 4654 // FIXME: whileCondition 4655 import arsd.eventloop; 4656 loop(); 4657 return 0; 4658 } 4659 } else 4660 int loopHelper(bool delegate() whileCondition) { 4661 version(X11) { 4662 bool done = false; 4663 4664 XFlush(display); 4665 insideXEventLoop = true; 4666 scope(exit) insideXEventLoop = false; 4667 4668 version(use_arsd_core) { 4669 import arsd.core; 4670 auto el = getThisThreadEventLoop(EventLoopType.Ui); 4671 4672 static bool loopInitialized = false; 4673 if(!loopInitialized) { 4674 el.addDelegateOnLoopIteration(&doXNextEventVoid, 0); 4675 el.addDelegateOnLoopIteration(&SimpleWindow.processAllCustomEvents, 0); 4676 4677 if(customSignalFD != -1) 4678 cast(void) el.addCallbackOnFdReadable(customSignalFD, new CallbackHelper(() { 4679 version(linux) { 4680 import core.sys.linux.sys.signalfd; 4681 import core.sys.posix.unistd : read; 4682 signalfd_siginfo info; 4683 read(customSignalFD, &info, info.sizeof); 4684 4685 auto sig = info.ssi_signo; 4686 4687 if(EventLoop.get.signalHandler !is null) { 4688 EventLoop.get.signalHandler()(sig); 4689 } else { 4690 EventLoop.get.exit(); 4691 } 4692 } 4693 })); 4694 4695 if(display.fd != -1) 4696 cast(void) el.addCallbackOnFdReadable(display.fd, new CallbackHelper(() { 4697 this.mtLock(); 4698 scope(exit) this.mtUnlock(); 4699 while(!done && XPending(display)) { 4700 done = doXNextEvent(this.display); 4701 } 4702 })); 4703 4704 if(pulseFd != -1) 4705 cast(void) el.addCallbackOnFdReadable(pulseFd, new CallbackHelper(() { 4706 long expirationCount; 4707 // 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... 4708 4709 handlePulse(); 4710 4711 // read just to clear the buffer so poll doesn't trigger again 4712 // BTW I read AFTER the pulse because if the pulse handler takes 4713 // a lot of time to execute, we don't want the app to get stuck 4714 // in a loop of timer hits without a chance to do anything else 4715 // 4716 // IOW handlePulse happens at most once per pulse interval. 4717 unix.read(pulseFd, &expirationCount, expirationCount.sizeof); 4718 })); 4719 4720 if(customEventFDRead != -1) 4721 cast(void) el.addCallbackOnFdReadable(customEventFDRead, new CallbackHelper(() { 4722 // we have some custom events; process 'em 4723 import core.sys.posix.unistd : read; 4724 ulong n; 4725 read(customEventFDRead, &n, n.sizeof); // reset counter value to zero again 4726 //{ import core.stdc.stdio; printf("custom event! count=%u\n", eventQueueUsed); } 4727 //SimpleWindow.processAllCustomEvents(); 4728 })); 4729 4730 // FIXME: posix fds 4731 // FIXME up? 4732 4733 4734 loopInitialized = true; 4735 } 4736 4737 el.run(() => !whileCondition()); 4738 } else version(linux) { 4739 while(!done && (whileCondition is null || whileCondition() == true) && notExited) { 4740 bool forceXPending = false; 4741 auto wto = SimpleWindow.eventAllQueueTimeoutMSecs(); 4742 // eh... some events may be queued for "squashing" (or "late delivery"), so we have to do the following magic 4743 { 4744 this.mtLock(); 4745 scope(exit) this.mtUnlock(); 4746 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 4747 } 4748 //{ import core.stdc.stdio; printf("*** wto=%d; force=%d\n", wto, (forceXPending ? 1 : 0)); } 4749 auto nfds = ep.epoll_wait(epollFd, events.ptr, events.length, (wto == 0 || wto >= int.max ? -1 : cast(int)wto)); 4750 if(nfds == -1) { 4751 if(err.errno == err.EINTR) { 4752 //if(forceXPending) goto xpending; 4753 continue; // interrupted by signal, just try again 4754 } 4755 throw new Exception("epoll wait failure"); 4756 } 4757 // writeln(nfds, " ", events[0].data.fd); 4758 4759 SimpleWindow.processAllCustomEvents(); // anyway 4760 //version(sdddd) { writeln("nfds=", nfds, "; [0]=", events[0].data.fd); } 4761 foreach(idx; 0 .. nfds) { 4762 if(done) break; 4763 auto fd = events[idx].data.fd; 4764 assert(fd != -1); // should never happen cuz the api doesn't do that but better to assert than assume. 4765 auto flags = events[idx].events; 4766 if(flags & ep.EPOLLIN) { 4767 if (fd == customSignalFD) { 4768 version(linux) { 4769 import core.sys.linux.sys.signalfd; 4770 import core.sys.posix.unistd : read; 4771 signalfd_siginfo info; 4772 read(customSignalFD, &info, info.sizeof); 4773 4774 auto sig = info.ssi_signo; 4775 4776 if(EventLoop.get.signalHandler !is null) { 4777 EventLoop.get.signalHandler()(sig); 4778 } else { 4779 EventLoop.get.exit(); 4780 } 4781 } 4782 } else if(fd == display.fd) { 4783 version(sdddd) { writeln("X EVENT PENDING!"); } 4784 this.mtLock(); 4785 scope(exit) this.mtUnlock(); 4786 while(!done && XPending(display)) { 4787 done = doXNextEvent(this.display); 4788 } 4789 forceXPending = false; 4790 } else if(fd == pulseFd) { 4791 long expirationCount; 4792 // 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... 4793 4794 handlePulse(); 4795 4796 // read just to clear the buffer so poll doesn't trigger again 4797 // BTW I read AFTER the pulse because if the pulse handler takes 4798 // a lot of time to execute, we don't want the app to get stuck 4799 // in a loop of timer hits without a chance to do anything else 4800 // 4801 // IOW handlePulse happens at most once per pulse interval. 4802 unix.read(pulseFd, &expirationCount, expirationCount.sizeof); 4803 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 4804 } else if (fd == customEventFDRead) { 4805 // we have some custom events; process 'em 4806 import core.sys.posix.unistd : read; 4807 ulong n; 4808 read(customEventFDRead, &n, n.sizeof); // reset counter value to zero again 4809 //{ import core.stdc.stdio; printf("custom event! count=%u\n", eventQueueUsed); } 4810 //SimpleWindow.processAllCustomEvents(); 4811 4812 forceXPending = true; 4813 } else { 4814 // some other timer 4815 version(sdddd) { writeln("unknown fd: ", fd); } 4816 4817 if(Timer* t = fd in Timer.mapping) 4818 (*t).trigger(); 4819 4820 if(PosixFdReader* pfr = fd in PosixFdReader.mapping) 4821 (*pfr).ready(flags); 4822 4823 // we don't know what the user did in this timer, so we need to assume that 4824 // there's X data to be flushed and potentially processed 4825 forceXPending = true; 4826 4827 // or i might add support for other FDs too 4828 // but for now it is just timer 4829 // (if you want other fds, use arsd.eventloop and compile with -version=with_eventloop), it offers a fuller api for arbitrary stuff. 4830 } 4831 } 4832 if(flags & ep.EPOLLHUP) { 4833 if(PosixFdReader* pfr = fd in PosixFdReader.mapping) 4834 (*pfr).hup(flags); 4835 if(globalHupHandler) 4836 globalHupHandler(fd, flags); 4837 } 4838 /+ 4839 } else { 4840 // not interested in OUT, we are just reading here. 4841 // 4842 // error or hup might also be reported 4843 // but it shouldn't here since we are only 4844 // using a few types of FD and Xlib will report 4845 // if it dies. 4846 // so instead of thoughtfully handling it, I'll 4847 // just throw. for now at least 4848 4849 throw new Exception("epoll did something else"); 4850 } 4851 +/ 4852 } 4853 // if we won't call `XPending()` here, libX may delay some internal event delivery. 4854 // i.e. we HAVE to repeatedly call `XPending()` even if libX fd wasn't signalled! 4855 xpending: 4856 if (!done && forceXPending) { 4857 done = doXPending(); 4858 } 4859 } 4860 } else { 4861 // Generic fallback: yes to simple pulse support, 4862 // but NO timer support! 4863 4864 // FIXME: we could probably support the POSIX timer_create 4865 // signal-based option, but I'm in no rush to write it since 4866 // I prefer the fd-based functions. 4867 while (!done && (whileCondition is null || whileCondition() == true) && notExited) { 4868 4869 import core.sys.posix.poll; 4870 4871 pollfd[] pfds; 4872 pollfd[32] pfdsBuffer; 4873 auto len = PosixFdReader.mapping.length + 2; 4874 // FIXME: i should just reuse the buffer 4875 if(len < pfdsBuffer.length) 4876 pfds = pfdsBuffer[0 .. len]; 4877 else 4878 pfds = new pollfd[](len); 4879 4880 pfds[0].fd = display.fd; 4881 pfds[0].events = POLLIN; 4882 pfds[0].revents = 0; 4883 4884 int slot = 1; 4885 4886 if(customEventFDRead != -1) { 4887 pfds[slot].fd = customEventFDRead; 4888 pfds[slot].events = POLLIN; 4889 pfds[slot].revents = 0; 4890 4891 slot++; 4892 } 4893 4894 foreach(fd, obj; PosixFdReader.mapping) { 4895 if(!obj.enabled) continue; 4896 pfds[slot].fd = fd; 4897 pfds[slot].events = POLLIN; 4898 pfds[slot].revents = 0; 4899 4900 slot++; 4901 } 4902 4903 auto ret = poll(pfds.ptr, slot, pulseTimeout > 0 ? cast(int) pulseTimeout : -1); 4904 if(ret == -1) throw new Exception("poll"); 4905 4906 if(ret == 0) { 4907 // FIXME it may not necessarily time out if events keep coming 4908 if(handlePulse !is null) 4909 handlePulse(); 4910 } else { 4911 foreach(s; 0 .. slot) { 4912 if(pfds[s].revents == 0) continue; 4913 4914 if(pfds[s].fd == display.fd) { 4915 while(!done && XPending(display)) { 4916 this.mtLock(); 4917 scope(exit) this.mtUnlock(); 4918 done = doXNextEvent(this.display); 4919 } 4920 } else if(customEventFDRead != -1 && pfds[s].fd == customEventFDRead) { 4921 4922 import core.sys.posix.unistd : read; 4923 ulong n; 4924 read(customEventFDRead, &n, n.sizeof); 4925 SimpleWindow.processAllCustomEvents(); 4926 } else { 4927 auto obj = PosixFdReader.mapping[pfds[s].fd]; 4928 if(pfds[s].revents & POLLNVAL) { 4929 obj.dispose(); 4930 } else { 4931 obj.ready(pfds[s].revents); 4932 } 4933 } 4934 4935 ret--; 4936 if(ret == 0) break; 4937 } 4938 } 4939 } 4940 } 4941 } 4942 4943 version(Windows) { 4944 4945 version(use_arsd_core) { 4946 import arsd.core; 4947 auto el = getThisThreadEventLoop(EventLoopType.Ui); 4948 static bool loopInitialized = false; 4949 if(!loopInitialized) { 4950 el.addDelegateOnLoopIteration(&SimpleWindow.processAllCustomEvents, 0); 4951 el.addDelegateOnLoopIteration(function() { eventLoopRound++; }, 0); 4952 loopInitialized = true; 4953 } 4954 el.run(() => !whileCondition()); 4955 } else { 4956 int ret = -1; 4957 MSG message; 4958 while(ret != 0 && (whileCondition is null || whileCondition() == true) && notExited) { 4959 eventLoopRound++; 4960 auto wto = SimpleWindow.eventAllQueueTimeoutMSecs(); 4961 auto waitResult = MsgWaitForMultipleObjectsEx( 4962 cast(int) handles.length, handles.ptr, 4963 (wto == 0 ? INFINITE : wto), /* timeout */ 4964 0x04FF, /* QS_ALLINPUT */ 4965 0x0002 /* MWMO_ALERTABLE */ | 0x0004 /* MWMO_INPUTAVAILABLE */); 4966 4967 SimpleWindow.processAllCustomEvents(); // anyway 4968 enum WAIT_OBJECT_0 = 0; 4969 if(waitResult >= WAIT_OBJECT_0 && waitResult < handles.length + WAIT_OBJECT_0) { 4970 auto h = handles[waitResult - WAIT_OBJECT_0]; 4971 if(auto e = h in WindowsHandleReader.mapping) { 4972 (*e).ready(); 4973 } 4974 } else if(waitResult == handles.length + WAIT_OBJECT_0) { 4975 // message ready 4976 int count; 4977 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 4978 ret = GetMessage(&message, null, 0, 0); 4979 if(ret == -1) 4980 throw new WindowsApiException("GetMessage", GetLastError()); 4981 TranslateMessage(&message); 4982 DispatchMessage(&message); 4983 4984 count++; 4985 if(count > 10) 4986 break; // take the opportunity to catch up on other events 4987 4988 if(ret == 0) { // WM_QUIT 4989 EventLoop.quitApplication(); 4990 break; 4991 } 4992 } 4993 } else if(waitResult == 0x000000C0L /* WAIT_IO_COMPLETION */) { 4994 SleepEx(0, true); // I call this to give it a chance to do stuff like async io 4995 } else if(waitResult == 258L /* WAIT_TIMEOUT */) { 4996 // timeout, should never happen since we aren't using it 4997 } else if(waitResult == 0xFFFFFFFF) { 4998 // failed 4999 throw new WindowsApiException("MsgWaitForMultipleObjectsEx", GetLastError()); 5000 } else { 5001 // idk.... 5002 } 5003 } 5004 } 5005 5006 // return message.wParam; 5007 return 0; 5008 } else { 5009 return 0; 5010 } 5011 } 5012 5013 int run(bool delegate() whileCondition = null) { 5014 if(disposed) 5015 initialize(this.pulseTimeout); 5016 5017 version(X11) { 5018 try { 5019 return loopHelper(whileCondition); 5020 } catch(XDisconnectException e) { 5021 if(e.userRequested) { 5022 foreach(item; CapableOfHandlingNativeEvent.nativeHandleMapping) 5023 item.discardConnectionState(); 5024 XCloseDisplay(XDisplayConnection.display); 5025 } 5026 5027 XDisplayConnection.display = null; 5028 5029 this.dispose(); 5030 5031 throw e; 5032 } 5033 } else { 5034 return loopHelper(whileCondition); 5035 } 5036 } 5037 } 5038 5039 5040 /++ 5041 Provides an icon on the system notification area (also known as the system tray). 5042 5043 5044 If a notification area is not available with the NotificationIcon object is created, 5045 it will silently succeed and simply attempt to create one when an area becomes available. 5046 5047 5048 NotificationAreaIcon on Windows assumes you are on Windows Vista or later. Support for 5049 Windows XP was dropped on October 31, 2023. On the other hand, support for 32 bit transparency 5050 with true color was added at that time. I was just too lazy to write the fallback. 5051 5052 If this is an issue, let me know, it'd take about an hour to get it back in there, but I suggest 5053 you use arsd 10.x when targeting Windows XP. 5054 +/ 5055 version(Emscripten) {} else 5056 version(OSXCocoa) {} else // NotYetImplementedException 5057 class NotificationAreaIcon : CapableOfHandlingNativeEvent { 5058 5059 version(X11) { 5060 void recreateAfterDisconnect() { 5061 stateDiscarded = false; 5062 clippixmap = None; 5063 throw new Exception("NOT IMPLEMENTED"); 5064 } 5065 5066 bool stateDiscarded; 5067 void discardConnectionState() { 5068 stateDiscarded = true; 5069 } 5070 } 5071 5072 5073 version(X11) { 5074 Image img; 5075 5076 NativeEventHandler getNativeEventHandler() { 5077 return delegate int(XEvent e) { 5078 switch(e.type) { 5079 case EventType.Expose: 5080 //case EventType.VisibilityNotify: 5081 redraw(); 5082 break; 5083 case EventType.ClientMessage: 5084 version(sddddd) { 5085 writeln("\t", e.xclient.message_type == GetAtom!("_XEMBED")(XDisplayConnection.get)); 5086 writeln("\t", e.xclient.format); 5087 writeln("\t", e.xclient.data.l); 5088 } 5089 break; 5090 case EventType.ButtonPress: 5091 auto event = e.xbutton; 5092 if (onClick !is null || onClickEx !is null) { 5093 MouseButton mb = cast(MouseButton)0; 5094 switch (event.button) { 5095 case 1: mb = MouseButton.left; break; // left 5096 case 2: mb = MouseButton.middle; break; // middle 5097 case 3: mb = MouseButton.right; break; // right 5098 case 4: mb = MouseButton.wheelUp; break; // scroll up 5099 case 5: mb = MouseButton.wheelDown; break; // scroll down 5100 case 6: break; // scroll left... 5101 case 7: break; // scroll right... 5102 case 8: mb = MouseButton.backButton; break; 5103 case 9: mb = MouseButton.forwardButton; break; 5104 default: 5105 } 5106 if (mb) { 5107 try { onClick()(mb); } catch (Exception) {} 5108 if (onClickEx !is null) try { onClickEx(event.x_root, event.y_root, mb, cast(ModifierState)event.state); } catch (Exception) {} 5109 } 5110 } 5111 break; 5112 case EventType.EnterNotify: 5113 if (onEnter !is null) { 5114 onEnter(e.xcrossing.x_root, e.xcrossing.y_root, cast(ModifierState)e.xcrossing.state); 5115 } 5116 break; 5117 case EventType.LeaveNotify: 5118 if (onLeave !is null) try { onLeave(); } catch (Exception) {} 5119 break; 5120 case EventType.DestroyNotify: 5121 active = false; 5122 CapableOfHandlingNativeEvent.nativeHandleMapping.remove(nativeHandle); 5123 break; 5124 case EventType.ConfigureNotify: 5125 auto event = e.xconfigure; 5126 this.width = event.width; 5127 this.height = event.height; 5128 // writeln(width, " x " , height, " @ ", event.x, " ", event.y); 5129 redraw(); 5130 break; 5131 default: return 1; 5132 } 5133 return 1; 5134 }; 5135 } 5136 5137 /* private */ void hideBalloon() { 5138 balloon.close(); 5139 version(with_timer) 5140 timer.destroy(); 5141 balloon = null; 5142 version(with_timer) 5143 timer = null; 5144 } 5145 5146 void redraw() { 5147 if (!active) return; 5148 5149 auto display = XDisplayConnection.get; 5150 GC gc; 5151 5152 // from https://stackoverflow.com/questions/10492275/how-to-upload-32-bit-image-to-server-side-pixmap 5153 5154 int gc_depth(int depth, Display *dpy, Window root, GC *gc) { 5155 Visual *visual; 5156 XVisualInfo vis_info; 5157 XSetWindowAttributes win_attr; 5158 c_ulong win_mask; 5159 5160 if(!XMatchVisualInfo(dpy, 0, depth, 4 /*TrueColor*/, &vis_info)) { 5161 assert(0); 5162 // return 1; 5163 } 5164 5165 visual = vis_info.visual; 5166 5167 win_attr.colormap = XCreateColormap(dpy, root, visual, AllocNone); 5168 win_attr.background_pixel = 0; 5169 win_attr.border_pixel = 0; 5170 5171 win_mask = CWBackPixel | CWColormap | CWBorderPixel; 5172 5173 *gc = XCreateGC(dpy, nativeHandle, 0, null); 5174 5175 return 0; 5176 } 5177 5178 if(useAlpha) 5179 gc_depth(32, display, RootWindow(display, DefaultScreen(display)), &gc); 5180 else 5181 gc = DefaultGC(display, DefaultScreen(display)); 5182 5183 XClearWindow(display, nativeHandle); 5184 5185 if(!useAlpha && img !is null) 5186 XSetClipMask(display, gc, clippixmap); 5187 5188 /+ 5189 XSetForeground(display, gc, 5190 cast(uint) 0 << 16 | 5191 cast(uint) 0 << 8 | 5192 cast(uint) 0); 5193 XFillRectangle(display, nativeHandle, gc, 0, 0, width, height); 5194 +/ 5195 5196 if (img is null) { 5197 XSetForeground(display, gc, 5198 cast(uint) 0 << 16 | 5199 cast(uint) 127 << 8 | 5200 cast(uint) 0); 5201 XFillArc(display, nativeHandle, 5202 gc, width / 4, height / 4, width * 2 / 4, height * 2 / 4, 0 * 64, 360 * 64); 5203 } else { 5204 int dx = 0; 5205 int dy = 0; 5206 if(width > img.width) 5207 dx = (width - img.width) / 2; 5208 if(height > img.height) 5209 dy = (height - img.height) / 2; 5210 // writeln(img.width, " ", img.height, " vs ", width, " ", height); 5211 XSetClipOrigin(display, gc, dx, dy); 5212 5213 int max(int a, int b) { 5214 if(a > b) return a; else return b; 5215 } 5216 5217 if (img.usingXshm) 5218 XShmPutImage(display, cast(Drawable)nativeHandle, gc, img.handle, 0, 0, dx, dy, max(img.width, width), max(img.height, height), false); 5219 else 5220 XPutImage(display, cast(Drawable)nativeHandle, gc, img.handle, 0, 0, dx, dy, max(img.width, width), max(img.height, height)); 5221 } 5222 XSetClipMask(display, gc, None); 5223 flushGui(); 5224 } 5225 5226 static Window getTrayOwner() { 5227 auto display = XDisplayConnection.get; 5228 auto i = cast(int) DefaultScreen(display); 5229 if(i < 10 && i >= 0) { 5230 static Atom atom; 5231 if(atom == None) 5232 atom = XInternAtom(display, cast(char*) ("_NET_SYSTEM_TRAY_S"~(cast(char) (i + '0')) ~ '\0').ptr, false); 5233 return XGetSelectionOwner(display, atom); 5234 } 5235 return None; 5236 } 5237 5238 static void sendTrayMessage(arch_long message, arch_long d1, arch_long d2, arch_long d3) { 5239 auto to = getTrayOwner(); 5240 auto display = XDisplayConnection.get; 5241 XEvent ev; 5242 ev.xclient.type = EventType.ClientMessage; 5243 ev.xclient.window = to; 5244 ev.xclient.message_type = GetAtom!("_NET_SYSTEM_TRAY_OPCODE", true)(display); 5245 ev.xclient.format = 32; 5246 ev.xclient.data.l[0] = CurrentTime; 5247 ev.xclient.data.l[1] = message; 5248 ev.xclient.data.l[2] = d1; 5249 ev.xclient.data.l[3] = d2; 5250 ev.xclient.data.l[4] = d3; 5251 5252 XSendEvent(XDisplayConnection.get, to, false, EventMask.NoEventMask, &ev); 5253 } 5254 5255 private static NotificationAreaIcon[] activeIcons; 5256 5257 // FIXME: possible leak with this stuff, should be able to clear it and stuff. 5258 private void newManager() { 5259 close(); 5260 createXWin(); 5261 5262 if(this.clippixmap) 5263 XFreePixmap(XDisplayConnection.get, clippixmap); 5264 if(this.originalMemoryImage) 5265 this.icon = this.originalMemoryImage; 5266 else if(this.img) 5267 this.icon = this.img; 5268 } 5269 5270 private bool useAlpha = false; 5271 5272 private void createXWin () { 5273 // create window 5274 auto display = XDisplayConnection.get; 5275 5276 // to check for MANAGER on root window to catch new/changed tray owners 5277 XDisplayConnection.addRootInput(EventMask.StructureNotifyMask); 5278 // so if a thing does appear, we can handle it 5279 foreach(ai; activeIcons) 5280 if(ai is this) 5281 goto alreadythere; 5282 activeIcons ~= this; 5283 alreadythere: 5284 5285 // and check for an existing tray 5286 auto trayOwner = getTrayOwner(); 5287 if(trayOwner == None) 5288 return; 5289 //throw new Exception("No notification area found"); 5290 5291 Visual* v = cast(Visual*) CopyFromParent; 5292 5293 // GNOME's default is 22x22 and KDE assumes all icons are going to match that then bitmap scales 5294 // from there. It is ugly and stupid but this gives the fewest artifacts. Good environments will send 5295 // a resize event later. 5296 width = 22; 5297 height = 22; 5298 5299 // if they system gave us a 32 bit visual we need to switch to it too 5300 int depth = 24; 5301 5302 auto visualProp = getX11PropertyData(trayOwner, GetAtom!("_NET_SYSTEM_TRAY_VISUAL", true)(display)); 5303 if(visualProp !is null) { 5304 c_ulong[] info = cast(c_ulong[]) visualProp; 5305 if(info.length == 1) { 5306 auto vid = info[0]; 5307 int returned; 5308 XVisualInfo t; 5309 t.visualid = vid; 5310 auto got = XGetVisualInfo(display, VisualIDMask, &t, &returned); 5311 if(got !is null) { 5312 if(returned == 1) { 5313 v = got.visual; 5314 depth = got.depth; 5315 // writeln("using special visual ", got.depth); 5316 // writeln(depth); 5317 } 5318 XFree(got); 5319 } 5320 } 5321 } 5322 5323 int CWFlags = CWBackPixel | CWBorderPixel | CWOverrideRedirect; 5324 XSetWindowAttributes attr; 5325 attr.background_pixel = 0; 5326 attr.border_pixel = 0; 5327 attr.override_redirect = 0; 5328 if(v !is cast(Visual*) CopyFromParent) { 5329 attr.colormap = XCreateColormap(display, RootWindow(display, DefaultScreen(display)), v, AllocNone); 5330 CWFlags |= CWColormap; 5331 if(depth == 32) 5332 useAlpha = true; 5333 else 5334 goto plain; 5335 } else { 5336 plain: 5337 attr.background_pixmap = 1 /* ParentRelative */; 5338 CWFlags |= CWBackPixmap; 5339 } 5340 5341 auto nativeWindow = XCreateWindow(display, RootWindow(display, DefaultScreen(display)), 0, 0, width, height, 0, depth, InputOutput, v, CWFlags, &attr); 5342 5343 assert(nativeWindow); 5344 5345 if(!useAlpha) 5346 XSetWindowBackgroundPixmap(display, nativeWindow, 1 /* ParentRelative */); 5347 5348 nativeHandle = nativeWindow; 5349 5350 ///+ 5351 arch_ulong[2] info; 5352 info[0] = 0; 5353 info[1] = 1; 5354 5355 string title = this.name is null ? "simpledisplay.d program" : this.name; 5356 auto XA_UTF8 = XInternAtom(display, "UTF8_STRING".ptr, false); 5357 auto XA_NETWM_NAME = XInternAtom(display, "_NET_WM_NAME".ptr, false); 5358 XChangeProperty(display, nativeWindow, XA_NETWM_NAME, XA_UTF8, 8, PropModeReplace, title.ptr, cast(uint)title.length); 5359 5360 XChangeProperty( 5361 display, 5362 nativeWindow, 5363 GetAtom!("_XEMBED_INFO", true)(display), 5364 GetAtom!("_XEMBED_INFO", true)(display), 5365 32 /* bits */, 5366 0 /*PropModeReplace*/, 5367 info.ptr, 5368 2); 5369 5370 import core.sys.posix.unistd; 5371 arch_ulong pid = getpid(); 5372 5373 XChangeProperty( 5374 display, 5375 nativeWindow, 5376 GetAtom!("_NET_WM_PID", true)(display), 5377 XA_CARDINAL, 5378 32 /* bits */, 5379 0 /*PropModeReplace*/, 5380 &pid, 5381 1); 5382 5383 updateNetWmIcon(); 5384 5385 if (sdpyWindowClassStr !is null && sdpyWindowClassStr[0]) { 5386 //{ import core.stdc.stdio; printf("winclass: [%s]\n", sdpyWindowClassStr); } 5387 XClassHint klass; 5388 XWMHints wh; 5389 XSizeHints size; 5390 klass.res_name = sdpyWindowClassStr; 5391 klass.res_class = sdpyWindowClassStr; 5392 XSetWMProperties(display, nativeWindow, null, null, null, 0, &size, &wh, &klass); 5393 } 5394 5395 // believe it or not, THIS is what xfce needed for the 9999 issue 5396 XSizeHints sh; 5397 c_long spr; 5398 XGetWMNormalHints(display, nativeWindow, &sh, &spr); 5399 sh.flags |= PMaxSize | PMinSize; 5400 // FIXME maybe nicer resizing 5401 sh.min_width = 16; 5402 sh.min_height = 16; 5403 sh.max_width = 22; 5404 sh.max_height = 22; 5405 XSetWMNormalHints(display, nativeWindow, &sh); 5406 5407 5408 //+/ 5409 5410 5411 XSelectInput(display, nativeWindow, 5412 EventMask.ButtonPressMask | EventMask.ExposureMask | EventMask.StructureNotifyMask | EventMask.VisibilityChangeMask | 5413 EventMask.EnterWindowMask | EventMask.LeaveWindowMask); 5414 5415 sendTrayMessage(SYSTEM_TRAY_REQUEST_DOCK, nativeWindow, 0, 0); 5416 // XMapWindow(display, nativeWindow); // to demo it w/o a tray 5417 5418 CapableOfHandlingNativeEvent.nativeHandleMapping[nativeWindow] = this; 5419 active = true; 5420 } 5421 5422 void updateNetWmIcon() { 5423 if(img is null) return; 5424 auto display = XDisplayConnection.get; 5425 // FIXME: ensure this is correct 5426 arch_ulong[] buffer; 5427 auto imgMi = img.toTrueColorImage; 5428 buffer ~= imgMi.width; 5429 buffer ~= imgMi.height; 5430 foreach(c; imgMi.imageData.colors) { 5431 arch_ulong b; 5432 b |= c.a << 24; 5433 b |= c.r << 16; 5434 b |= c.g << 8; 5435 b |= c.b; 5436 buffer ~= b; 5437 } 5438 5439 XChangeProperty( 5440 display, 5441 nativeHandle, 5442 GetAtom!"_NET_WM_ICON"(display), 5443 GetAtom!"CARDINAL"(display), 5444 32 /* bits */, 5445 0 /*PropModeReplace*/, 5446 buffer.ptr, 5447 cast(int) buffer.length); 5448 } 5449 5450 5451 5452 private SimpleWindow balloon; 5453 version(with_timer) 5454 private Timer timer; 5455 5456 private Window nativeHandle; 5457 private Pixmap clippixmap = None; 5458 private int width = 16; 5459 private int height = 16; 5460 private bool active = false; 5461 5462 void delegate (int x, int y, MouseButton button, ModifierState mods) onClickEx; /// x and y are globals (relative to root window). X11 only. 5463 void delegate (int x, int y, ModifierState mods) onEnter; /// x and y are global window coordinates. X11 only. 5464 void delegate () onLeave; /// X11 only. 5465 5466 @property bool closed () const pure nothrow @safe @nogc { return !active; } /// 5467 5468 /// X11 only. Get global window coordinates and size. This can be used to show various notifications. 5469 void getWindowRect (out int x, out int y, out int width, out int height) { 5470 if (!active) { width = 1; height = 1; return; } // 1: just in case 5471 Window dummyw; 5472 auto dpy = XDisplayConnection.get; 5473 //XWindowAttributes xwa; 5474 //XGetWindowAttributes(dpy, nativeHandle, &xwa); 5475 //XTranslateCoordinates(dpy, nativeHandle, RootWindow(dpy, DefaultScreen(dpy)), xwa.x, xwa.y, &x, &y, &dummyw); 5476 XTranslateCoordinates(dpy, nativeHandle, RootWindow(dpy, DefaultScreen(dpy)), x, y, &x, &y, &dummyw); 5477 width = this.width; 5478 height = this.height; 5479 } 5480 } 5481 5482 /+ 5483 What I actually want from this: 5484 5485 * set / change: icon, tooltip 5486 * handle: mouse click, right click 5487 * show: notification bubble. 5488 +/ 5489 5490 version(Windows) { 5491 WindowsIcon win32Icon; 5492 HWND hwnd; 5493 5494 NOTIFYICONDATAW data; 5495 5496 NativeEventHandler getNativeEventHandler() { 5497 return delegate int(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam, out int mustReturn) { 5498 if(msg == WM_USER) { 5499 auto event = LOWORD(lParam); 5500 auto iconId = HIWORD(lParam); 5501 //auto x = GET_X_LPARAM(wParam); 5502 //auto y = GET_Y_LPARAM(wParam); 5503 switch(event) { 5504 case WM_LBUTTONDOWN: 5505 onClick()(MouseButton.left); 5506 break; 5507 case WM_RBUTTONDOWN: 5508 onClick()(MouseButton.right); 5509 break; 5510 case WM_MBUTTONDOWN: 5511 onClick()(MouseButton.middle); 5512 break; 5513 case WM_MOUSEMOVE: 5514 // sent, we could use it. 5515 break; 5516 case WM_MOUSEWHEEL: 5517 // NOT SENT 5518 break; 5519 //case NIN_KEYSELECT: 5520 //case NIN_SELECT: 5521 //break; 5522 default: {} 5523 } 5524 } 5525 return 0; 5526 }; 5527 } 5528 5529 enum NIF_SHOWTIP = 0x00000080; 5530 5531 private static struct NOTIFYICONDATAW { 5532 DWORD cbSize; 5533 HWND hWnd; 5534 UINT uID; 5535 UINT uFlags; 5536 UINT uCallbackMessage; 5537 HICON hIcon; 5538 WCHAR[128] szTip; 5539 DWORD dwState; 5540 DWORD dwStateMask; 5541 WCHAR[256] szInfo; 5542 union { 5543 UINT uTimeout; 5544 UINT uVersion; 5545 } 5546 WCHAR[64] szInfoTitle; 5547 DWORD dwInfoFlags; 5548 GUID guidItem; 5549 HICON hBalloonIcon; 5550 } 5551 5552 } 5553 5554 /++ 5555 Note that on Windows, only left, right, and middle buttons are sent. 5556 Mouse wheel buttons are NOT set, so don't rely on those events if your 5557 program is meant to be used on Windows too. 5558 +/ 5559 this(string name, MemoryImage icon, void delegate(MouseButton button) onClick) { 5560 // The canonical constructor for Windows needs the MemoryImage, so it is here, 5561 // but on X, we need an Image, so its canonical ctor is there. They should 5562 // forward to each other though. 5563 version(X11) { 5564 this.name = name; 5565 this.onClick = onClick; 5566 createXWin(); 5567 this.icon = icon; 5568 } else version(Windows) { 5569 this.onClick = onClick; 5570 this.win32Icon = new WindowsIcon(icon); 5571 5572 HINSTANCE hInstance = cast(HINSTANCE) GetModuleHandle(null); 5573 5574 static bool registered = false; 5575 if(!registered) { 5576 WNDCLASSEX wc; 5577 wc.cbSize = wc.sizeof; 5578 wc.hInstance = hInstance; 5579 wc.lpfnWndProc = &WndProc; 5580 wc.lpszClassName = "arsd_simpledisplay_notification_icon"w.ptr; 5581 if(!RegisterClassExW(&wc)) 5582 throw new WindowsApiException("RegisterClass", GetLastError()); 5583 registered = true; 5584 } 5585 5586 this.hwnd = CreateWindowW("arsd_simpledisplay_notification_icon"w.ptr, "test"w.ptr /* name */, 0 /* dwStyle */, 0, 0, 0, 0, HWND_MESSAGE, null, hInstance, null); 5587 if(hwnd is null) 5588 throw new WindowsApiException("CreateWindow", GetLastError()); 5589 5590 data.cbSize = data.sizeof; 5591 data.hWnd = hwnd; 5592 data.uID = cast(uint) cast(void*) this; 5593 data.uFlags = NIF_MESSAGE | NIF_ICON | NIF_TIP | NIF_STATE | NIF_SHOWTIP /* use default tooltip, for now. */; 5594 // NIF_INFO means show balloon 5595 data.uCallbackMessage = WM_USER; 5596 data.hIcon = this.win32Icon.hIcon; 5597 data.szTip = ""; // FIXME 5598 data.dwState = 0; // NIS_HIDDEN; // windows vista 5599 data.dwStateMask = NIS_HIDDEN; // windows vista 5600 5601 data.uVersion = 4; // NOTIFYICON_VERSION_4; // Windows Vista and up 5602 5603 5604 Shell_NotifyIcon(NIM_ADD, cast(NOTIFYICONDATA*) &data); 5605 5606 CapableOfHandlingNativeEvent.nativeHandleMapping[this.hwnd] = this; 5607 } else version(OSXCocoa) { 5608 throw new NotYetImplementedException(); 5609 } else static assert(0); 5610 } 5611 5612 /// ditto 5613 this(string name, Image icon, void delegate(MouseButton button) onClick) { 5614 version(X11) { 5615 this.onClick = onClick; 5616 this.name = name; 5617 createXWin(); 5618 this.icon = icon; 5619 } else version(Windows) { 5620 this(name, icon is null ? null : icon.toTrueColorImage(), onClick); 5621 } else version(OSXCocoa) { 5622 throw new NotYetImplementedException(); 5623 } else static assert(0); 5624 } 5625 5626 version(X11) { 5627 /++ 5628 X-specific extension (for now at least) 5629 +/ 5630 this(string name, MemoryImage icon, void delegate(int x, int y, MouseButton button, ModifierState mods) onClickEx) { 5631 this.onClickEx = onClickEx; 5632 createXWin(); 5633 if (icon !is null) this.icon = icon; 5634 } 5635 5636 /// ditto 5637 this(string name, Image icon, void delegate(int x, int y, MouseButton button, ModifierState mods) onClickEx) { 5638 this.onClickEx = onClickEx; 5639 createXWin(); 5640 this.icon = icon; 5641 } 5642 } 5643 5644 private void delegate (MouseButton button) onClick_; 5645 5646 /// 5647 @property final void delegate(MouseButton) onClick() { 5648 if(onClick_ is null) 5649 onClick_ = delegate void(MouseButton) {}; 5650 return onClick_; 5651 } 5652 5653 /// ditto 5654 @property final void onClick(void delegate(MouseButton) handler) { 5655 // I made this a property setter so we can wrap smaller arg 5656 // delegates and just forward all to onClickEx or something. 5657 onClick_ = handler; 5658 } 5659 5660 5661 string name_; 5662 @property void name(string n) { 5663 name_ = n; 5664 } 5665 5666 @property string name() { 5667 return name_; 5668 } 5669 5670 private MemoryImage originalMemoryImage; 5671 5672 /// 5673 @property void icon(MemoryImage i) { 5674 version(X11) { 5675 this.originalMemoryImage = i; 5676 if (!active) return; 5677 if (i !is null) { 5678 this.img = Image.fromMemoryImage(i, useAlpha, false); 5679 if(!useAlpha) 5680 this.clippixmap = transparencyMaskFromMemoryImage(i, nativeHandle); 5681 // writeln("using pixmap ", clippixmap); 5682 updateNetWmIcon(); 5683 redraw(); 5684 } else { 5685 if (this.img !is null) { 5686 this.img = null; 5687 redraw(); 5688 } 5689 } 5690 } else version(Windows) { 5691 this.win32Icon = new WindowsIcon(i); 5692 5693 data.uFlags = NIF_ICON; 5694 data.hIcon = this.win32Icon.hIcon; 5695 5696 Shell_NotifyIcon(NIM_MODIFY, cast(NOTIFYICONDATA*) &data); 5697 } else version(OSXCocoa) { 5698 throw new NotYetImplementedException(); 5699 } else static assert(0); 5700 } 5701 5702 /// ditto 5703 @property void icon (Image i) { 5704 version(X11) { 5705 if (!active) return; 5706 if (i !is img) { 5707 originalMemoryImage = null; 5708 img = i; 5709 redraw(); 5710 } 5711 } else version(Windows) { 5712 this.icon(i is null ? null : i.toTrueColorImage()); 5713 } else version(OSXCocoa) { 5714 throw new NotYetImplementedException(); 5715 } else static assert(0); 5716 } 5717 5718 /++ 5719 Shows a balloon notification. You can only show one balloon at a time, if you call 5720 it twice while one is already up, the first balloon will be replaced. 5721 5722 5723 The user is free to block notifications and they will automatically disappear after 5724 a timeout period. 5725 5726 Params: 5727 title = Title of the notification. Must be 40 chars or less or the OS may truncate it. 5728 message = The message to pop up. Must be 220 chars or less or the OS may truncate it. 5729 icon = the icon to display with the notification. If null, it uses your existing icon. 5730 onclick = delegate called if the user clicks the balloon. (not yet implemented) 5731 timeout = your suggested timeout period. The operating system is free to ignore your suggestion. 5732 +/ 5733 void showBalloon(string title, string message, MemoryImage icon = null, void delegate() onclick = null, int timeout = 2_500) { 5734 bool useCustom = true; 5735 version(libnotify) { 5736 if(onclick is null) // libnotify impl doesn't support callbacks yet because it doesn't do a dbus message loop 5737 try { 5738 if(!active) return; 5739 5740 if(libnotify is null) { 5741 libnotify = new C_DynamicLibrary("libnotify.so"); 5742 libnotify.call!("notify_init", int, const char*)()((ApplicationName ~ "\0").ptr); 5743 } 5744 5745 auto n = libnotify.call!("notify_notification_new", void*, const char*, const char*, const char*)()((title~"\0").ptr, (message~"\0").ptr, null /* icon */); 5746 5747 libnotify.call!("notify_notification_set_timeout", void, void*, int)()(n, timeout); 5748 5749 if(onclick) { 5750 libnotify_action_delegates[libnotify_action_delegates_count] = onclick; 5751 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); 5752 libnotify_action_delegates_count++; 5753 } 5754 5755 // FIXME icon 5756 5757 // set hint image-data 5758 // set default action for onclick 5759 5760 void* error; 5761 libnotify.call!("notify_notification_show", bool, void*, void**)()(n, &error); 5762 5763 useCustom = false; 5764 } catch(Exception e) { 5765 5766 } 5767 } 5768 5769 version(X11) { 5770 if(useCustom) { 5771 if(!active) return; 5772 if(balloon) { 5773 hideBalloon(); 5774 } 5775 // I know there are two specs for this, but one is never 5776 // implemented by any window manager I have ever seen, and 5777 // the other is a bloated mess and too complicated for simpledisplay... 5778 // so doing my own little window instead. 5779 balloon = new SimpleWindow(380, 120, null, OpenGlOptions.no, Resizability.fixedSize, WindowTypes.notification, WindowFlags.dontAutoShow/*, window*/); 5780 5781 int x, y, width, height; 5782 getWindowRect(x, y, width, height); 5783 5784 int bx = x - balloon.width; 5785 int by = y - balloon.height; 5786 if(bx < 0) 5787 bx = x + width + balloon.width; 5788 if(by < 0) 5789 by = y + height; 5790 5791 // just in case, make sure it is actually on scren 5792 if(bx < 0) 5793 bx = 0; 5794 if(by < 0) 5795 by = 0; 5796 5797 balloon.move(bx, by); 5798 auto painter = balloon.draw(); 5799 painter.fillColor = Color(220, 220, 220); 5800 painter.outlineColor = Color.black; 5801 painter.drawRectangle(Point(0, 0), balloon.width, balloon.height); 5802 auto iconWidth = icon is null ? 0 : icon.width; 5803 if(icon) 5804 painter.drawImage(Point(4, 4), Image.fromMemoryImage(icon)); 5805 iconWidth += 6; // margin around the icon 5806 5807 // draw a close button 5808 painter.outlineColor = Color(44, 44, 44); 5809 painter.fillColor = Color(255, 255, 255); 5810 painter.drawRectangle(Point(balloon.width - 15, 3), 13, 13); 5811 painter.pen = Pen(Color.black, 3); 5812 painter.drawLine(Point(balloon.width - 14, 4), Point(balloon.width - 4, 14)); 5813 painter.drawLine(Point(balloon.width - 4, 4), Point(balloon.width - 14, 13)); 5814 painter.pen = Pen(Color.black, 1); 5815 painter.fillColor = Color(220, 220, 220); 5816 5817 // Draw the title and message 5818 painter.drawText(Point(4 + iconWidth, 4), title); 5819 painter.drawLine( 5820 Point(4 + iconWidth, 4 + painter.fontHeight + 1), 5821 Point(balloon.width - 4, 4 + painter.fontHeight + 1), 5822 ); 5823 painter.drawText(Point(4 + iconWidth, 4 + painter.fontHeight + 4), message); 5824 5825 balloon.setEventHandlers( 5826 (MouseEvent ev) { 5827 if(ev.type == MouseEventType.buttonPressed) { 5828 if(ev.x > balloon.width - 16 && ev.y < 16) 5829 hideBalloon(); 5830 else if(onclick) 5831 onclick(); 5832 } 5833 } 5834 ); 5835 balloon.show(); 5836 5837 version(with_timer) 5838 timer = new Timer(timeout, &hideBalloon); 5839 else {} // FIXME 5840 } 5841 } else version(Windows) { 5842 enum NIF_INFO = 0x00000010; 5843 5844 data.uFlags = NIF_INFO; 5845 5846 // FIXME: go back to the last valid unicode code point 5847 if(title.length > 40) 5848 title = title[0 .. 40]; 5849 if(message.length > 220) 5850 message = message[0 .. 220]; 5851 5852 enum NIIF_RESPECT_QUIET_TIME = 0x00000080; 5853 enum NIIF_LARGE_ICON = 0x00000020; 5854 enum NIIF_NOSOUND = 0x00000010; 5855 enum NIIF_USER = 0x00000004; 5856 enum NIIF_ERROR = 0x00000003; 5857 enum NIIF_WARNING = 0x00000002; 5858 enum NIIF_INFO = 0x00000001; 5859 enum NIIF_NONE = 0; 5860 5861 WCharzBuffer t = WCharzBuffer(title); 5862 WCharzBuffer m = WCharzBuffer(message); 5863 5864 t.copyInto(data.szInfoTitle); 5865 m.copyInto(data.szInfo); 5866 data.dwInfoFlags = NIIF_RESPECT_QUIET_TIME; 5867 5868 if(icon !is null) { 5869 auto i = new WindowsIcon(icon); 5870 data.hBalloonIcon = i.hIcon; 5871 data.dwInfoFlags |= NIIF_USER; 5872 } 5873 5874 Shell_NotifyIcon(NIM_MODIFY, cast(NOTIFYICONDATA*) &data); 5875 } else version(OSXCocoa) { 5876 throw new NotYetImplementedException(); 5877 } else static assert(0); 5878 } 5879 5880 /// 5881 //version(Windows) 5882 void show() { 5883 version(X11) { 5884 if(!hidden) 5885 return; 5886 sendTrayMessage(SYSTEM_TRAY_REQUEST_DOCK, nativeHandle, 0, 0); 5887 hidden = false; 5888 } else version(Windows) { 5889 data.uFlags = NIF_STATE; 5890 data.dwState = 0; // NIS_HIDDEN; // windows vista 5891 data.dwStateMask = NIS_HIDDEN; // windows vista 5892 Shell_NotifyIcon(NIM_MODIFY, cast(NOTIFYICONDATA*) &data); 5893 } else version(OSXCocoa) { 5894 throw new NotYetImplementedException(); 5895 } else static assert(0); 5896 } 5897 5898 version(X11) 5899 bool hidden = false; 5900 5901 /// 5902 //version(Windows) 5903 void hide() { 5904 version(X11) { 5905 if(hidden) 5906 return; 5907 hidden = true; 5908 XUnmapWindow(XDisplayConnection.get, nativeHandle); 5909 } else version(Windows) { 5910 data.uFlags = NIF_STATE; 5911 data.dwState = NIS_HIDDEN; // windows vista 5912 data.dwStateMask = NIS_HIDDEN; // windows vista 5913 Shell_NotifyIcon(NIM_MODIFY, cast(NOTIFYICONDATA*) &data); 5914 } else version(OSXCocoa) { 5915 throw new NotYetImplementedException(); 5916 } else static assert(0); 5917 } 5918 5919 /// 5920 void close () { 5921 version(X11) { 5922 if (active) { 5923 active = false; // event handler will set this too, but meh 5924 XUnmapWindow(XDisplayConnection.get, nativeHandle); // 'cause why not; let's be polite 5925 XDestroyWindow(XDisplayConnection.get, nativeHandle); 5926 flushGui(); 5927 } 5928 } else version(Windows) { 5929 Shell_NotifyIcon(NIM_DELETE, cast(NOTIFYICONDATA*) &data); 5930 } else version(OSXCocoa) { 5931 throw new NotYetImplementedException(); 5932 } else static assert(0); 5933 } 5934 5935 ~this() { 5936 if(!thisIsGuiThread) return; // FIXME: leaks if multithreaded gc 5937 version(X11) 5938 if(clippixmap != None) 5939 XFreePixmap(XDisplayConnection.get, clippixmap); 5940 close(); 5941 } 5942 } 5943 5944 version(X11) 5945 /// Call `XFreePixmap` on the return value. 5946 Pixmap transparencyMaskFromMemoryImage(MemoryImage i, Window window) { 5947 char[] data = new char[](i.width * i.height / 8 + 2); 5948 data[] = 0; 5949 5950 int bitOffset = 0; 5951 foreach(c; i.getAsTrueColorImage().imageData.colors) { // FIXME inefficient unnecessary conversion in palette cases 5952 ubyte v = c.a > 128 ? 1 : 0; 5953 data[bitOffset / 8] |= v << (bitOffset%8); 5954 bitOffset++; 5955 } 5956 auto handle = XCreateBitmapFromData(XDisplayConnection.get, cast(Drawable) window, data.ptr, i.width, i.height); 5957 return handle; 5958 } 5959 5960 5961 // basic functions to make timers 5962 /** 5963 A timer that will trigger your function on a given interval. 5964 5965 5966 You create a timer with an interval and a callback. It will continue 5967 to fire on the interval until it is destroyed. 5968 5969 There are currently no one-off timers (instead, just create one and 5970 destroy it when it is triggered) nor are there pause/resume functions - 5971 the timer must again be destroyed and recreated if you want to pause it. 5972 5973 --- 5974 auto timer = new Timer(50, { it happened!; }); 5975 timer.destroy(); 5976 --- 5977 5978 Timers can only be expected to fire when the event loop is running and only 5979 once per iteration through the event loop. 5980 5981 History: 5982 Prior to December 9, 2020, a timer pulse set too high with a handler too 5983 slow could lock up the event loop. It now guarantees other things will 5984 get a chance to run between timer calls, even if that means not keeping up 5985 with the requested interval. 5986 */ 5987 version(with_timer) { 5988 version(use_arsd_core) 5989 alias Timer = arsd.core.Timer; // FIXME should probably wrap it for a stable api 5990 else 5991 class Timer { 5992 // FIXME: needs pause and unpause 5993 // FIXME: I might add overloads for ones that take a count of 5994 // how many elapsed since last time (on Windows, it will divide 5995 // the ticks thing given, on Linux it is just available) and 5996 // maybe one that takes an instance of the Timer itself too 5997 /// Create a timer with a callback when it triggers. 5998 this(int intervalInMilliseconds, void delegate() onPulse) @trusted { 5999 assert(onPulse !is null); 6000 6001 this.intervalInMilliseconds = intervalInMilliseconds; 6002 this.onPulse = onPulse; 6003 6004 version(Windows) { 6005 /* 6006 handle = SetTimer(null, handle, intervalInMilliseconds, &timerCallback); 6007 if(handle == 0) 6008 throw new WindowsApiException("SetTimer", GetLastError()); 6009 */ 6010 6011 // thanks to Archival 998 for the WaitableTimer blocks 6012 handle = CreateWaitableTimer(null, false, null); 6013 long initialTime = -intervalInMilliseconds; 6014 if(handle is null || !SetWaitableTimer(handle, cast(LARGE_INTEGER*)&initialTime, intervalInMilliseconds, &timerCallback, handle, false)) 6015 throw new WindowsApiException("SetWaitableTimer", GetLastError()); 6016 6017 mapping[handle] = this; 6018 6019 } else version(Emscripten) { 6020 } else version(linux) { 6021 static import ep = core.sys.linux.epoll; 6022 6023 import core.sys.linux.timerfd; 6024 6025 fd = timerfd_create(CLOCK_MONOTONIC, 0); 6026 if(fd == -1) 6027 throw new Exception("timer create failed"); 6028 6029 mapping[fd] = this; 6030 6031 itimerspec value = makeItimerspec(intervalInMilliseconds); 6032 6033 if(timerfd_settime(fd, 0, &value, null) == -1) 6034 throw new Exception("couldn't make pulse timer"); 6035 6036 version(with_eventloop) { 6037 import arsd.eventloop; 6038 addFileEventListeners(fd, &trigger, null, null); 6039 } else { 6040 prepareEventLoop(); 6041 6042 ep.epoll_event ev = void; 6043 ev.events = ep.EPOLLIN; 6044 ev.data.fd = fd; 6045 ep.epoll_ctl(epollFd, ep.EPOLL_CTL_ADD, fd, &ev); 6046 } 6047 } else featureNotImplemented(); 6048 } 6049 6050 private int intervalInMilliseconds; 6051 6052 // just cuz I sometimes call it this. 6053 alias dispose = destroy; 6054 6055 /// Stop and destroy the timer object. 6056 void destroy() { 6057 version(Windows) { 6058 staticDestroy(handle); 6059 handle = null; 6060 } else version(linux) { 6061 staticDestroy(fd); 6062 fd = -1; 6063 } else featureNotImplemented(); 6064 } 6065 6066 version(Windows) 6067 static void staticDestroy(HANDLE handle) { 6068 if(handle) { 6069 // KillTimer(null, handle); 6070 CancelWaitableTimer(cast(void*)handle); 6071 mapping.remove(handle); 6072 CloseHandle(handle); 6073 } 6074 } 6075 else version(Emscripten) 6076 static void staticDestroy(int fd) @system { 6077 assert(0); 6078 } 6079 else version(linux) 6080 static void staticDestroy(int fd) @system { 6081 if(fd != -1) { 6082 import unix = core.sys.posix.unistd; 6083 static import ep = core.sys.linux.epoll; 6084 6085 version(with_eventloop) { 6086 import arsd.eventloop; 6087 removeFileEventListeners(fd); 6088 } else { 6089 ep.epoll_event ev = void; 6090 ev.events = ep.EPOLLIN; 6091 ev.data.fd = fd; 6092 6093 ep.epoll_ctl(epollFd, ep.EPOLL_CTL_DEL, fd, &ev); 6094 } 6095 unix.close(fd); 6096 mapping.remove(fd); 6097 } 6098 } 6099 6100 ~this() { 6101 version(Windows) { if(handle) 6102 cleanupQueue.queue!staticDestroy(handle); 6103 } else version(linux) { if(fd != -1) 6104 cleanupQueue.queue!staticDestroy(fd); 6105 } 6106 } 6107 6108 void changeTime(int intervalInMilliseconds) 6109 { 6110 this.intervalInMilliseconds = intervalInMilliseconds; 6111 version(Windows) 6112 { 6113 if(handle) 6114 { 6115 //handle = SetTimer(null, handle, intervalInMilliseconds, &timerCallback); 6116 long initialTime = -intervalInMilliseconds; 6117 if(handle is null || !SetWaitableTimer(handle, cast(LARGE_INTEGER*)&initialTime, intervalInMilliseconds, &timerCallback, handle, false)) 6118 throw new WindowsApiException("couldn't change pulse timer", GetLastError()); 6119 } 6120 } else version(linux) { 6121 import core.sys.linux.timerfd; 6122 6123 itimerspec value = makeItimerspec(intervalInMilliseconds); 6124 if(timerfd_settime(fd, 0, &value, null) == -1) { 6125 throw new Exception("couldn't change pulse timer"); 6126 } 6127 } else { 6128 assert(false, "Timer.changeTime(int) is not implemented for this platform"); 6129 } 6130 } 6131 6132 6133 private: 6134 6135 void delegate() onPulse; 6136 6137 int lastEventLoopRoundTriggered; 6138 6139 version(linux) { 6140 static auto makeItimerspec(int intervalInMilliseconds) { 6141 import core.sys.linux.timerfd; 6142 6143 itimerspec value; 6144 value.it_value.tv_sec = cast(int) (intervalInMilliseconds / 1000); 6145 value.it_value.tv_nsec = (intervalInMilliseconds % 1000) * 1000_000; 6146 6147 value.it_interval.tv_sec = cast(int) (intervalInMilliseconds / 1000); 6148 value.it_interval.tv_nsec = (intervalInMilliseconds % 1000) * 1000_000; 6149 6150 return value; 6151 } 6152 } 6153 6154 void trigger() { 6155 version(linux) { 6156 import unix = core.sys.posix.unistd; 6157 long val; 6158 unix.read(fd, &val, val.sizeof); // gotta clear the pipe 6159 } else version(Windows) { 6160 if(this.lastEventLoopRoundTriggered == eventLoopRound) 6161 return; // never try to actually run faster than the event loop 6162 lastEventLoopRoundTriggered = eventLoopRound; 6163 } else featureNotImplemented(); 6164 6165 onPulse(); 6166 } 6167 6168 version(Windows) 6169 void rearm() { 6170 6171 } 6172 6173 version(Windows) 6174 extern(Windows) 6175 //static void timerCallback(HWND, UINT, UINT_PTR timer, DWORD dwTime) nothrow { 6176 static void timerCallback(HANDLE timer, DWORD lowTime, DWORD hiTime) nothrow { 6177 if(Timer* t = timer in mapping) { 6178 try 6179 (*t).trigger(); 6180 catch(Exception e) { sdpy_abort(e); assert(0); } 6181 } 6182 } 6183 6184 version(Windows) { 6185 //UINT_PTR handle; 6186 //static Timer[UINT_PTR] mapping; 6187 HANDLE handle; 6188 __gshared Timer[HANDLE] mapping; 6189 } else version(linux) { 6190 int fd = -1; 6191 __gshared Timer[int] mapping; 6192 } else version(OSXCocoa) { 6193 } else static assert(0, "timer not supported"); 6194 } 6195 } 6196 6197 version(Windows) 6198 private int eventLoopRound; 6199 6200 version(Windows) 6201 /// 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 6202 class WindowsHandleReader { 6203 /// 6204 this(void delegate() onReady, HANDLE handle) { 6205 this.onReady = onReady; 6206 this.handle = handle; 6207 6208 mapping[handle] = this; 6209 6210 enable(); 6211 } 6212 6213 version(use_arsd_core) 6214 ICoreEventLoop.UnregisterToken unregisterToken; 6215 6216 /// 6217 void enable() { 6218 version(use_arsd_core) { 6219 unregisterToken = getThisThreadEventLoop(EventLoopType.Ui).addCallbackOnHandleReady(handle, new CallbackHelper(&ready)); 6220 } else { 6221 auto el = EventLoop.get().impl; 6222 el.handles ~= handle; 6223 } 6224 } 6225 6226 /// 6227 void disable() { 6228 version(use_arsd_core) { 6229 unregisterToken.unregister(); 6230 } else { 6231 auto el = EventLoop.get().impl; 6232 for(int i = 0; i < el.handles.length; i++) { 6233 if(el.handles[i] is handle) { 6234 el.handles[i] = el.handles[$-1]; 6235 el.handles = el.handles[0 .. $-1]; 6236 return; 6237 } 6238 } 6239 } 6240 } 6241 6242 void dispose() { 6243 disable(); 6244 if(handle) 6245 mapping.remove(handle); 6246 handle = null; 6247 } 6248 6249 void ready() { 6250 if(onReady) 6251 onReady(); 6252 } 6253 6254 HANDLE handle; 6255 void delegate() onReady; 6256 6257 __gshared WindowsHandleReader[HANDLE] mapping; 6258 } 6259 6260 version(Posix) 6261 /// Lets you add files to the event loop for reading. Use at your own risk. 6262 class PosixFdReader { 6263 /// 6264 this(void delegate() onReady, int fd, bool captureReads = true, bool captureWrites = false) { 6265 this((int, bool, bool) { onReady(); }, fd, captureReads, captureWrites); 6266 } 6267 6268 /// 6269 this(void delegate(int) onReady, int fd, bool captureReads = true, bool captureWrites = false) { 6270 this((int fd, bool, bool) { onReady(fd); }, fd, captureReads, captureWrites); 6271 } 6272 6273 /// 6274 this(void delegate(int fd, bool read, bool write) onReady, int fd, bool captureReads = true, bool captureWrites = false) { 6275 this.onReady = onReady; 6276 this.fd = fd; 6277 this.captureWrites = captureWrites; 6278 this.captureReads = captureReads; 6279 6280 mapping[fd] = this; 6281 6282 version(with_eventloop) { 6283 import arsd.eventloop; 6284 addFileEventListeners(fd, &readyel); 6285 } else { 6286 enable(); 6287 } 6288 } 6289 6290 bool captureReads; 6291 bool captureWrites; 6292 6293 version(use_arsd_core) { 6294 import arsd.core; 6295 ICoreEventLoop.UnregisterToken unregisterToken; 6296 } 6297 6298 version(with_eventloop) {} else 6299 /// 6300 void enable() @system { 6301 enabled = true; 6302 6303 version(use_arsd_core) { 6304 unregisterToken = getThisThreadEventLoop(EventLoopType.Ui).addCallbackOnFdReadable(fd, new CallbackHelper( 6305 () { onReady(fd, true, false); } 6306 )); 6307 // FIXME: what if it is writeable? 6308 6309 } else version(linux) { 6310 prepareEventLoop(); 6311 static import ep = core.sys.linux.epoll; 6312 ep.epoll_event ev = void; 6313 ev.events = (captureReads ? ep.EPOLLIN : 0) | (captureWrites ? ep.EPOLLOUT : 0); 6314 // writeln("enable ", fd, " ", captureReads, " ", captureWrites); 6315 ev.data.fd = fd; 6316 ep.epoll_ctl(epollFd, ep.EPOLL_CTL_ADD, fd, &ev); 6317 } else { 6318 6319 } 6320 } 6321 6322 version(with_eventloop) {} else 6323 /// 6324 void disable() @system { 6325 enabled = false; 6326 6327 version(use_arsd_core) { 6328 unregisterToken.unregister(); 6329 } else 6330 version(linux) { 6331 prepareEventLoop(); 6332 static import ep = core.sys.linux.epoll; 6333 ep.epoll_event ev = void; 6334 ev.events = (captureReads ? ep.EPOLLIN : 0) | (captureWrites ? ep.EPOLLOUT : 0); 6335 // writeln("disable ", fd, " ", captureReads, " ", captureWrites); 6336 ev.data.fd = fd; 6337 ep.epoll_ctl(epollFd, ep.EPOLL_CTL_DEL, fd, &ev); 6338 } 6339 } 6340 6341 version(with_eventloop) {} else 6342 /// 6343 void dispose() { 6344 if(enabled) 6345 disable(); 6346 if(fd != -1) 6347 mapping.remove(fd); 6348 fd = -1; 6349 } 6350 6351 void delegate(int, bool, bool) onReady; 6352 6353 version(with_eventloop) 6354 void readyel() { 6355 onReady(fd, true, true); 6356 } 6357 6358 void ready(uint flags) { 6359 version(Emscripten) { 6360 assert(0); 6361 } else version(linux) { 6362 static import ep = core.sys.linux.epoll; 6363 onReady(fd, (flags & ep.EPOLLIN) ? true : false, (flags & ep.EPOLLOUT) ? true : false); 6364 } else { 6365 import core.sys.posix.poll; 6366 onReady(fd, (flags & POLLIN) ? true : false, (flags & POLLOUT) ? true : false); 6367 } 6368 } 6369 6370 void hup(uint flags) { 6371 if(onHup) 6372 onHup(); 6373 } 6374 6375 void delegate() onHup; 6376 6377 int fd = -1; 6378 private bool enabled; 6379 __gshared PosixFdReader[int] mapping; 6380 } 6381 6382 // basic functions to access the clipboard 6383 /+ 6384 6385 6386 http://msdn.microsoft.com/en-us/library/windows/desktop/ff729168%28v=vs.85%29.aspx 6387 http://msdn.microsoft.com/en-us/library/windows/desktop/ms649039%28v=vs.85%29.aspx 6388 http://msdn.microsoft.com/en-us/library/windows/desktop/ms649035%28v=vs.85%29.aspx 6389 http://msdn.microsoft.com/en-us/library/windows/desktop/ms649051%28v=vs.85%29.aspx 6390 http://msdn.microsoft.com/en-us/library/windows/desktop/ms649037%28v=vs.85%29.aspx 6391 http://msdn.microsoft.com/en-us/library/windows/desktop/ms649035%28v=vs.85%29.aspx 6392 http://msdn.microsoft.com/en-us/library/windows/desktop/ms649016%28v=vs.85%29.aspx 6393 6394 +/ 6395 6396 /++ 6397 this does a delegate because it is actually an async call on X... 6398 the receiver may never be called if the clipboard is empty or unavailable 6399 gets plain text from the clipboard. 6400 +/ 6401 void getClipboardText(SimpleWindow clipboardOwner, void delegate(in char[]) receiver) @system { 6402 version(Windows) { 6403 HWND hwndOwner = clipboardOwner ? clipboardOwner.impl.hwnd : null; 6404 if(OpenClipboard(hwndOwner) == 0) 6405 throw new WindowsApiException("OpenClipboard", GetLastError()); 6406 scope(exit) 6407 CloseClipboard(); 6408 // see: https://docs.microsoft.com/en-us/windows/win32/api/winuser/nf-winuser-getpriorityclipboardformat 6409 if(auto dataHandle = GetClipboardData(CF_UNICODETEXT)) { 6410 6411 if(auto data = cast(wchar*) GlobalLock(dataHandle)) { 6412 scope(exit) 6413 GlobalUnlock(dataHandle); 6414 6415 // FIXME: CR/LF conversions 6416 // FIXME: I might not have to copy it now that the receiver is in char[] instead of string 6417 int len = 0; 6418 auto d = data; 6419 while(*d) { 6420 d++; 6421 len++; 6422 } 6423 string s; 6424 s.reserve(len); 6425 foreach(dchar ch; data[0 .. len]) { 6426 s ~= ch; 6427 } 6428 receiver(s); 6429 } 6430 } 6431 } else version(X11) { 6432 getX11Selection!"CLIPBOARD"(clipboardOwner, receiver); 6433 } else version(OSXCocoa) { 6434 throw new NotYetImplementedException(); 6435 } else version(Emscripten) { 6436 throw new NotYetImplementedException(); 6437 } else static assert(0); 6438 } 6439 6440 // FIXME: a clipboard listener might be cool btw 6441 6442 /++ 6443 this does a delegate because it is actually an async call on X... 6444 the receiver may never be called if the clipboard is empty or unavailable 6445 gets image from the clipboard. 6446 6447 templated because it introduces an optional dependency on arsd.bmp 6448 +/ 6449 void getClipboardImage()(SimpleWindow clipboardOwner, void delegate(MemoryImage) receiver) { 6450 version(Windows) { 6451 HWND hwndOwner = clipboardOwner ? clipboardOwner.impl.hwnd : null; 6452 if(OpenClipboard(hwndOwner) == 0) 6453 throw new WindowsApiException("OpenClipboard", GetLastError()); 6454 scope(exit) 6455 CloseClipboard(); 6456 if(auto dataHandle = GetClipboardData(CF_DIBV5)) { 6457 if(auto data = cast(ubyte*) GlobalLock(dataHandle)) { 6458 scope(exit) 6459 GlobalUnlock(dataHandle); 6460 6461 auto len = GlobalSize(dataHandle); 6462 6463 import arsd.bmp; 6464 auto img = readBmp(data[0 .. len], false); 6465 receiver(img); 6466 } 6467 } 6468 } else version(X11) { 6469 getX11Selection!"CLIPBOARD"(clipboardOwner, receiver); 6470 } else version(OSXCocoa) { 6471 throw new NotYetImplementedException(); 6472 } else version(Emscripten) { 6473 throw new NotYetImplementedException(); 6474 } else static assert(0); 6475 } 6476 6477 /// Copies some text to the clipboard. 6478 void setClipboardText(SimpleWindow clipboardOwner, string text) { 6479 assert(clipboardOwner !is null); 6480 version(Windows) { 6481 if(OpenClipboard(clipboardOwner.impl.hwnd) == 0) 6482 throw new WindowsApiException("OpenClipboard", GetLastError()); 6483 scope(exit) 6484 CloseClipboard(); 6485 EmptyClipboard(); 6486 auto sz = sizeOfConvertedWstring(text, WindowsStringConversionFlags.convertNewLines | WindowsStringConversionFlags.zeroTerminate); 6487 auto handle = GlobalAlloc(GMEM_MOVEABLE, sz * 2); // zero terminated wchars 6488 if(handle is null) throw new WindowsApiException("GlobalAlloc", GetLastError()); 6489 if(auto data = cast(wchar*) GlobalLock(handle)) { 6490 auto slice = data[0 .. sz]; 6491 scope(failure) 6492 GlobalUnlock(handle); 6493 6494 auto str = makeWindowsString(text, slice, WindowsStringConversionFlags.convertNewLines | WindowsStringConversionFlags.zeroTerminate); 6495 6496 GlobalUnlock(handle); 6497 SetClipboardData(CF_UNICODETEXT, handle); 6498 } 6499 } else version(X11) { 6500 setX11Selection!"CLIPBOARD"(clipboardOwner, text); 6501 } else version(OSXCocoa) { 6502 throw new NotYetImplementedException(); 6503 } else version(Emscripten) { 6504 throw new NotYetImplementedException(); 6505 } else static assert(0); 6506 } 6507 6508 void setClipboardImage()(SimpleWindow clipboardOwner, MemoryImage img) { 6509 assert(clipboardOwner !is null); 6510 version(Windows) { 6511 if(OpenClipboard(clipboardOwner.impl.hwnd) == 0) 6512 throw new WindowsApiException("OpenClipboard", GetLastError()); 6513 scope(exit) 6514 CloseClipboard(); 6515 EmptyClipboard(); 6516 6517 6518 import arsd.bmp; 6519 ubyte[] mdata; 6520 mdata.reserve(img.width * img.height); 6521 void sink(ubyte b) { 6522 mdata ~= b; 6523 } 6524 writeBmpIndirect(img, &sink, false); 6525 6526 auto handle = GlobalAlloc(GMEM_MOVEABLE, mdata.length); 6527 if(handle is null) throw new WindowsApiException("GlobalAlloc", GetLastError()); 6528 if(auto data = cast(ubyte*) GlobalLock(handle)) { 6529 auto slice = data[0 .. mdata.length]; 6530 scope(failure) 6531 GlobalUnlock(handle); 6532 6533 slice[] = mdata[]; 6534 6535 GlobalUnlock(handle); 6536 SetClipboardData(CF_DIB, handle); 6537 } 6538 } else version(X11) { 6539 static class X11SetSelectionHandler_Image : X11SetSelectionHandler { 6540 mixin X11SetSelectionHandler_Basics; 6541 private const(ubyte)[] mdata; 6542 private const(ubyte)[] mdata_original; 6543 this(MemoryImage img) { 6544 import arsd.bmp; 6545 6546 mdata.reserve(img.width * img.height); 6547 void sink(ubyte b) { 6548 mdata ~= b; 6549 } 6550 writeBmpIndirect(img, &sink, true); 6551 6552 mdata_original = mdata; 6553 } 6554 6555 Atom[] availableFormats() { 6556 auto display = XDisplayConnection.get; 6557 return [ 6558 GetAtom!"image/bmp"(display), 6559 GetAtom!"TARGETS"(display) 6560 ]; 6561 } 6562 6563 ubyte[] getData(Atom format, return scope ubyte[] data) { 6564 if(mdata.length < data.length) { 6565 data[0 .. mdata.length] = mdata[]; 6566 auto ret = data[0 .. mdata.length]; 6567 mdata = mdata[$..$]; 6568 return ret; 6569 } else { 6570 data[] = mdata[0 .. data.length]; 6571 mdata = mdata[data.length .. $]; 6572 return data[]; 6573 } 6574 } 6575 6576 void done() { 6577 mdata = mdata_original; 6578 } 6579 } 6580 6581 setX11Selection!"CLIPBOARD"(clipboardOwner, new X11SetSelectionHandler_Image(img)); 6582 } else version(OSXCocoa) { 6583 throw new NotYetImplementedException(); 6584 } else version(Emscripten) { 6585 throw new NotYetImplementedException(); 6586 } else static assert(0); 6587 } 6588 6589 6590 version(X11) { 6591 // and the PRIMARY on X, be sure to put these in static if(UsingSimpledisplayX11) 6592 6593 private __gshared Atom*[] interredAtoms; // for discardAndRecreate 6594 6595 // FIXME: do a GetAtomUpfront too that just queues all at CT and combines it all. 6596 /// Platform-specific for X11. 6597 /// History: On February 21, 2021, I changed the default value of `create` to be true. 6598 @property Atom GetAtom(string name, bool create = true)(Display* display) { 6599 __gshared static Atom a; 6600 if(!a) { 6601 a = XInternAtom(display, name, !create); 6602 // FIXME: might need to synchronize this and attach it to the actual object 6603 interredAtoms ~= &a; 6604 } 6605 if(a == None) 6606 throw new Exception("XInternAtom " ~ name ~ " " ~ (create ? "true":"false")); 6607 return a; 6608 } 6609 6610 /// Platform-specific for X11 - gets atom names as a string. 6611 string getAtomName(Atom atom, Display* display) { 6612 auto got = XGetAtomName(display, atom); 6613 scope(exit) XFree(got); 6614 import core.stdc.string; 6615 string s = got[0 .. strlen(got)].idup; 6616 return s; 6617 } 6618 6619 /// Asserts ownership of PRIMARY and copies the text into a buffer that clients can request later. 6620 void setPrimarySelection(SimpleWindow window, string text) { 6621 setX11Selection!"PRIMARY"(window, text); 6622 } 6623 6624 /// Asserts ownership of SECONDARY and copies the text into a buffer that clients can request later. 6625 void setSecondarySelection(SimpleWindow window, string text) { 6626 setX11Selection!"SECONDARY"(window, text); 6627 } 6628 6629 interface X11SetSelectionHandler { 6630 // should include TARGETS right now 6631 Atom[] availableFormats(); 6632 // Return the slice of data you filled, empty slice if done. 6633 // this is to support the incremental thing 6634 ubyte[] getData(Atom format, return scope ubyte[] data); 6635 6636 void done(); 6637 6638 void handleRequest(XEvent); 6639 6640 bool matchesIncr(Window, Atom); 6641 void sendMoreIncr(XPropertyEvent*); 6642 } 6643 6644 mixin template X11SetSelectionHandler_Basics() { 6645 Window incrWindow; 6646 Atom incrAtom; 6647 Atom selectionAtom; 6648 Atom formatAtom; 6649 ubyte[] toSend; 6650 bool matchesIncr(Window w, Atom a) { 6651 return incrAtom && incrAtom == a && w == incrWindow; 6652 } 6653 void sendMoreIncr(XPropertyEvent* event) { 6654 auto display = XDisplayConnection.get; 6655 6656 XChangeProperty (display, 6657 incrWindow, 6658 incrAtom, 6659 formatAtom, 6660 8 /* bits */, PropModeReplace, 6661 toSend.ptr, cast(int) toSend.length); 6662 6663 if(toSend.length != 0) { 6664 toSend = this.getData(formatAtom, toSend[]); 6665 } else { 6666 this.done(); 6667 incrWindow = None; 6668 incrAtom = None; 6669 selectionAtom = None; 6670 formatAtom = None; 6671 toSend = null; 6672 } 6673 } 6674 void handleRequest(XEvent ev) { 6675 6676 auto display = XDisplayConnection.get; 6677 6678 XSelectionRequestEvent* event = &ev.xselectionrequest; 6679 XSelectionEvent selectionEvent; 6680 selectionEvent.type = EventType.SelectionNotify; 6681 selectionEvent.display = event.display; 6682 selectionEvent.requestor = event.requestor; 6683 selectionEvent.selection = event.selection; 6684 selectionEvent.time = event.time; 6685 selectionEvent.target = event.target; 6686 6687 bool supportedType() { 6688 foreach(t; this.availableFormats()) 6689 if(t == event.target) 6690 return true; 6691 return false; 6692 } 6693 6694 if(event.property == None) { 6695 selectionEvent.property = event.target; 6696 6697 XSendEvent(display, selectionEvent.requestor, false, 0, cast(XEvent*) &selectionEvent); 6698 XFlush(display); 6699 } if(event.target == GetAtom!"TARGETS"(display)) { 6700 /* respond with the supported types */ 6701 auto tlist = this.availableFormats(); 6702 XChangeProperty(display, event.requestor, event.property, XA_ATOM, 32, PropModeReplace, cast(void*)tlist.ptr, cast(int) tlist.length); 6703 selectionEvent.property = event.property; 6704 6705 XSendEvent(display, selectionEvent.requestor, false, 0, cast(XEvent*) &selectionEvent); 6706 XFlush(display); 6707 } else if(supportedType()) { 6708 auto buffer = new ubyte[](1024 * 64); 6709 auto toSend = this.getData(event.target, buffer[]); 6710 6711 if(toSend.length < 32 * 1024) { 6712 // small enough to send directly... 6713 selectionEvent.property = event.property; 6714 XChangeProperty (display, 6715 selectionEvent.requestor, 6716 selectionEvent.property, 6717 event.target, 6718 8 /* bits */, 0 /* PropModeReplace */, 6719 toSend.ptr, cast(int) toSend.length); 6720 6721 XSendEvent(display, selectionEvent.requestor, false, 0, cast(XEvent*) &selectionEvent); 6722 XFlush(display); 6723 } else { 6724 // large, let's send incrementally 6725 arch_ulong l = toSend.length; 6726 6727 // if I wanted other events from this window don't want to clear that out.... 6728 XWindowAttributes xwa; 6729 XGetWindowAttributes(display, selectionEvent.requestor, &xwa); 6730 6731 XSelectInput(display, selectionEvent.requestor, cast(EventMask) (xwa.your_event_mask | EventMask.PropertyChangeMask)); 6732 6733 incrWindow = event.requestor; 6734 incrAtom = event.property; 6735 formatAtom = event.target; 6736 selectionAtom = event.selection; 6737 this.toSend = toSend; 6738 6739 selectionEvent.property = event.property; 6740 XChangeProperty (display, 6741 selectionEvent.requestor, 6742 selectionEvent.property, 6743 GetAtom!"INCR"(display), 6744 32 /* bits */, PropModeReplace, 6745 &l, 1); 6746 6747 XSendEvent(display, selectionEvent.requestor, false, 0, cast(XEvent*) &selectionEvent); 6748 XFlush(display); 6749 } 6750 //if(after) 6751 //after(); 6752 } else { 6753 debug(sdpy_clip) { 6754 writeln("Unsupported data ", getAtomName(event.target, display)); 6755 } 6756 selectionEvent.property = None; // I don't know how to handle this type... 6757 XSendEvent(display, selectionEvent.requestor, false, EventMask.NoEventMask, cast(XEvent*) &selectionEvent); 6758 XFlush(display); 6759 } 6760 } 6761 } 6762 6763 class X11SetSelectionHandler_Text : X11SetSelectionHandler { 6764 mixin X11SetSelectionHandler_Basics; 6765 private const(ubyte)[] text; 6766 private const(ubyte)[] text_original; 6767 this(string text) { 6768 this.text = cast(const ubyte[]) text; 6769 this.text_original = this.text; 6770 } 6771 Atom[] availableFormats() { 6772 auto display = XDisplayConnection.get; 6773 return [ 6774 GetAtom!"UTF8_STRING"(display), 6775 GetAtom!"text/plain"(display), 6776 XA_STRING, 6777 GetAtom!"TARGETS"(display) 6778 ]; 6779 } 6780 6781 ubyte[] getData(Atom format, return scope ubyte[] data) { 6782 if(text.length < data.length) { 6783 data[0 .. text.length] = text[]; 6784 return data[0 .. text.length]; 6785 } else { 6786 data[] = text[0 .. data.length]; 6787 text = text[data.length .. $]; 6788 return data[]; 6789 } 6790 } 6791 6792 void done() { 6793 text = text_original; 6794 } 6795 } 6796 6797 /// 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?!) 6798 void setX11Selection(string atomName)(SimpleWindow window, string text, void delegate() after = null) { 6799 setX11Selection!atomName(window, new X11SetSelectionHandler_Text(text), after); 6800 } 6801 6802 private __gshared bool mightShortCircuitClipboard; 6803 6804 void setX11Selection(string atomName)(SimpleWindow window, X11SetSelectionHandler data, void delegate() after = null) { 6805 assert(window !is null); 6806 6807 auto display = XDisplayConnection.get(); 6808 static if (atomName == "PRIMARY") Atom a = XA_PRIMARY; 6809 else static if (atomName == "SECONDARY") Atom a = XA_SECONDARY; 6810 else Atom a = GetAtom!atomName(display); 6811 6812 if(mightShortCircuitClipboard) 6813 if(auto ptr = a in window.impl.setSelectionHandlers) { 6814 // we already have it, don't even need to inform the X server 6815 // sdpyPrintDebugString("short circuit in set"); 6816 *ptr = data; 6817 return; 6818 } 6819 6820 // we don't have it, tell X we want it 6821 XSetSelectionOwner(display, a, window.impl.window, 0 /* CurrentTime */); 6822 window.impl.setSelectionHandlers[a] = data; 6823 mightShortCircuitClipboard = true; 6824 } 6825 6826 /+ 6827 /++ 6828 History: 6829 Added September 28, 2024 6830 +/ 6831 bool hasX11Selection(string atomName)(SimpleWindow window) { 6832 auto display = XDisplayConnection.get(); 6833 static if (atomName == "PRIMARY") Atom a = XA_PRIMARY; 6834 else static if (atomName == "SECONDARY") Atom a = XA_SECONDARY; 6835 else Atom a = GetAtom!atomName(display); 6836 6837 if(a in window.impl.setSelectionHandlers) 6838 return true; 6839 else 6840 return false; 6841 } 6842 +/ 6843 6844 /// 6845 void getPrimarySelection(SimpleWindow window, void delegate(in char[]) handler) { 6846 getX11Selection!"PRIMARY"(window, handler); 6847 } 6848 6849 // added July 28, 2020 6850 // undocumented as experimental tho 6851 interface X11GetSelectionHandler { 6852 void handleData(Atom target, in ubyte[] data); 6853 Atom findBestFormat(Atom[] answer); 6854 6855 void prepareIncremental(Window, Atom); 6856 bool matchesIncr(Window, Atom); 6857 void handleIncrData(Atom, in ubyte[] data); 6858 } 6859 6860 mixin template X11GetSelectionHandler_Basics() { 6861 Window incrWindow; 6862 Atom incrAtom; 6863 6864 void prepareIncremental(Window w, Atom a) { 6865 incrWindow = w; 6866 incrAtom = a; 6867 } 6868 bool matchesIncr(Window w, Atom a) { 6869 return incrWindow == w && incrAtom == a; 6870 } 6871 6872 Atom incrFormatAtom; 6873 ubyte[] incrData; 6874 void handleIncrData(Atom format, in ubyte[] data) { 6875 incrFormatAtom = format; 6876 6877 if(data.length) 6878 incrData ~= data; 6879 else 6880 handleData(incrFormatAtom, incrData); 6881 6882 } 6883 } 6884 6885 static class X11GetSelectionHandler_Text : X11GetSelectionHandler { 6886 this(void delegate(in char[]) handler) { 6887 this.handler = handler; 6888 } 6889 6890 mixin X11GetSelectionHandler_Basics; 6891 6892 void delegate(in char[]) handler; 6893 6894 void handleData(Atom target, in ubyte[] data) { 6895 // import std.stdio; writeln(target, " ", data); 6896 if(target == GetAtom!"UTF8_STRING"(XDisplayConnection.get) || target == XA_STRING || target == GetAtom!"text/plain"(XDisplayConnection.get)) 6897 handler(cast(const char[]) data); 6898 else if(target == None && data is null) 6899 handler(null); // no suitable selection exists 6900 } 6901 6902 Atom findBestFormat(Atom[] answer) { 6903 Atom best = None; 6904 foreach(option; answer) { 6905 if(option == GetAtom!"UTF8_STRING"(XDisplayConnection.get)) { 6906 best = option; 6907 break; 6908 } else if(option == XA_STRING) { 6909 best = option; 6910 } else if(option == GetAtom!"text/plain"(XDisplayConnection.get)) { 6911 best = option; 6912 } 6913 } 6914 return best; 6915 } 6916 } 6917 6918 /// 6919 void getX11Selection(string atomName)(SimpleWindow window, void delegate(in char[]) handler, Time timestamp = 0 /* CurrentTime */) { 6920 assert(window !is null); 6921 6922 auto display = XDisplayConnection.get(); 6923 6924 static if (atomName == "PRIMARY") Atom atom = XA_PRIMARY; 6925 else static if (atomName == "SECONDARY") Atom atom = XA_SECONDARY; 6926 else Atom atom = GetAtom!atomName(display); 6927 6928 if(mightShortCircuitClipboard) 6929 if(auto ptr = atom in window.impl.setSelectionHandlers) { 6930 if(auto txt = (cast(X11SetSelectionHandler_Text) *ptr)) { 6931 // we already have it! short circuit everything 6932 6933 // sdpyPrintDebugString("short circuit in get"); 6934 handler(cast(char[]) txt.text_original); 6935 return; 6936 } 6937 } 6938 6939 window.impl.getSelectionHandlers[atom] = new X11GetSelectionHandler_Text(handler); 6940 6941 auto target = GetAtom!"TARGETS"(display); 6942 6943 // SDD_DATA is "simpledisplay.d data" 6944 XConvertSelection(display, atom, target, GetAtom!("SDD_DATA", true)(display), window.impl.window, timestamp); 6945 } 6946 6947 /// Gets the image on the clipboard, if there is one. Added July 2020. 6948 /// only supports bmps. using this function will import arsd.bmp. 6949 void getX11Selection(string atomName)(SimpleWindow window, void delegate(MemoryImage) handler) { 6950 assert(window !is null); 6951 6952 auto display = XDisplayConnection.get(); 6953 auto atom = GetAtom!atomName(display); 6954 6955 static class X11GetSelectionHandler_Image : X11GetSelectionHandler { 6956 this(void delegate(MemoryImage) handler) { 6957 this.handler = handler; 6958 } 6959 6960 mixin X11GetSelectionHandler_Basics; 6961 6962 void delegate(MemoryImage) handler; 6963 6964 void handleData(Atom target, in ubyte[] data) { 6965 if(target == GetAtom!"image/bmp"(XDisplayConnection.get)) { 6966 import arsd.bmp; 6967 handler(readBmp(data)); 6968 } 6969 } 6970 6971 Atom findBestFormat(Atom[] answer) { 6972 Atom best = None; 6973 foreach(option; answer) { 6974 if(option == GetAtom!"image/bmp"(XDisplayConnection.get)) { 6975 best = option; 6976 } 6977 } 6978 return best; 6979 } 6980 6981 } 6982 6983 6984 window.impl.getSelectionHandlers[atom] = new X11GetSelectionHandler_Image(handler); 6985 6986 auto target = GetAtom!"TARGETS"(display); 6987 6988 // SDD_DATA is "simpledisplay.d data" 6989 XConvertSelection(display, atom, target, GetAtom!("SDD_DATA", true)(display), window.impl.window, 0 /*CurrentTime*/); 6990 } 6991 6992 6993 /// 6994 void[] getX11PropertyData(Window window, Atom property, Atom type = AnyPropertyType) { 6995 Atom actualType; 6996 int actualFormat; 6997 arch_ulong actualItems; 6998 arch_ulong bytesRemaining; 6999 void* data; 7000 7001 auto display = XDisplayConnection.get(); 7002 if(XGetWindowProperty(display, window, property, 0, 0x7fffffff, false, type, &actualType, &actualFormat, &actualItems, &bytesRemaining, &data) == Success) { 7003 if(actualFormat == 0) 7004 return null; 7005 else { 7006 int byteLength; 7007 if(actualFormat == 32) { 7008 // 32 means it is a C long... which is variable length 7009 actualFormat = cast(int) arch_long.sizeof * 8; 7010 } 7011 7012 // then it is just a bit count 7013 byteLength = cast(int) (actualItems * actualFormat / 8); 7014 7015 auto d = new ubyte[](byteLength); 7016 d[] = cast(ubyte[]) data[0 .. byteLength]; 7017 XFree(data); 7018 return d; 7019 } 7020 } 7021 return null; 7022 } 7023 7024 /* defined in the systray spec */ 7025 enum SYSTEM_TRAY_REQUEST_DOCK = 0; 7026 enum SYSTEM_TRAY_BEGIN_MESSAGE = 1; 7027 enum SYSTEM_TRAY_CANCEL_MESSAGE = 2; 7028 7029 7030 /** Global hotkey handler. Simpledisplay will usually create one for you, but if you want to use subclassing 7031 * instead of delegates, you can subclass this, and override `doHandle()` method. */ 7032 public class GlobalHotkey { 7033 KeyEvent key; 7034 void delegate () handler; 7035 7036 void doHandle () { if (handler !is null) handler(); } /// this will be called by hotkey manager 7037 7038 /// Create from initialzed KeyEvent object 7039 this (KeyEvent akey, void delegate () ahandler=null) { 7040 if (akey.key == 0 || !GlobalHotkeyManager.isGoodModifierMask(akey.modifierState)) throw new Exception("invalid global hotkey"); 7041 key = akey; 7042 handler = ahandler; 7043 } 7044 7045 /// Create from emacs-like key name ("C-M-Y", etc.) 7046 this (const(char)[] akey, void delegate () ahandler=null) { 7047 key = KeyEvent.parse(akey); 7048 if (key.key == 0 || !GlobalHotkeyManager.isGoodModifierMask(key.modifierState)) throw new Exception("invalid global hotkey"); 7049 handler = ahandler; 7050 } 7051 7052 } 7053 7054 private extern(C) int XGrabErrorHandler (Display* dpy, XErrorEvent* evt) nothrow @nogc { 7055 //conwriteln("failed to grab key"); 7056 GlobalHotkeyManager.ghfailed = true; 7057 return 0; 7058 } 7059 7060 private extern(C) int XShmErrorHandler (Display* dpy, XErrorEvent* evt) nothrow @nogc { 7061 Image.impl.xshmfailed = true; 7062 return 0; 7063 } 7064 7065 private __gshared int errorHappened; 7066 private extern(C) int adrlogger (Display* dpy, XErrorEvent* evt) nothrow @nogc { 7067 import core.stdc.stdio; 7068 char[265] buffer; 7069 XGetErrorText(dpy, evt.error_code, buffer.ptr, cast(int) buffer.length); 7070 debug printf("X Error %d: %s / Serial: %lld, Opcode: %d.%d, XID: 0x%llx\n", evt.error_code, buffer.ptr, cast(long) evt.serial, evt.request_code, evt.minor_code, cast(long) evt.resourceid); 7071 errorHappened = true; 7072 return 0; 7073 } 7074 7075 /++ 7076 Global hotkey manager. It contains static methods to manage global hotkeys. 7077 7078 --- 7079 try { 7080 GlobalHotkeyManager.register("M-H-A", delegate () { hideShowWindows(); }); 7081 } catch (Exception e) { 7082 conwriteln("ERROR registering hotkey!"); 7083 } 7084 EventLoop.get.run(); 7085 --- 7086 7087 The key strings are based on Emacs. In practical terms, 7088 `M` means `alt` and `H` means the Windows logo key. `C` 7089 is `ctrl`. 7090 7091 $(WARNING 7092 This is X-specific right now. If you are on 7093 Windows, try [registerHotKey] instead. 7094 7095 We will probably merge these into a single 7096 interface later. 7097 ) 7098 +/ 7099 public class GlobalHotkeyManager : CapableOfHandlingNativeEvent { 7100 version(X11) { 7101 void recreateAfterDisconnect() { 7102 throw new Exception("NOT IMPLEMENTED"); 7103 } 7104 void discardConnectionState() { 7105 throw new Exception("NOT IMPLEMENTED"); 7106 } 7107 } 7108 7109 private static immutable uint[8] masklist = [ 0, 7110 KeyOrButtonMask.LockMask, 7111 KeyOrButtonMask.Mod2Mask, 7112 KeyOrButtonMask.Mod3Mask, 7113 KeyOrButtonMask.LockMask|KeyOrButtonMask.Mod2Mask, 7114 KeyOrButtonMask.LockMask|KeyOrButtonMask.Mod3Mask, 7115 KeyOrButtonMask.LockMask|KeyOrButtonMask.Mod2Mask|KeyOrButtonMask.Mod3Mask, 7116 KeyOrButtonMask.Mod2Mask|KeyOrButtonMask.Mod3Mask, 7117 ]; 7118 private __gshared GlobalHotkeyManager ghmanager; 7119 private __gshared bool ghfailed = false; 7120 7121 private static bool isGoodModifierMask (uint modmask) pure nothrow @safe @nogc { 7122 if (modmask == 0) return false; 7123 if (modmask&(KeyOrButtonMask.LockMask|KeyOrButtonMask.Mod2Mask|KeyOrButtonMask.Mod3Mask)) return false; 7124 if (modmask&~(KeyOrButtonMask.Mod5Mask-1)) return false; 7125 return true; 7126 } 7127 7128 private static uint cleanupModifiers (uint modmask) pure nothrow @safe @nogc { 7129 modmask &= ~(KeyOrButtonMask.LockMask|KeyOrButtonMask.Mod2Mask|KeyOrButtonMask.Mod3Mask); // remove caps, num, scroll 7130 modmask &= (KeyOrButtonMask.Mod5Mask-1); // and other modifiers 7131 return modmask; 7132 } 7133 7134 private static uint keyEvent2KeyCode() (scope auto ref const KeyEvent ke) { 7135 uint keycode = cast(uint)ke.key; 7136 auto dpy = XDisplayConnection.get; 7137 return XKeysymToKeycode(dpy, keycode); 7138 } 7139 7140 private static ulong keyCode2Hash() (uint keycode, uint modstate) pure nothrow @safe @nogc { return ((cast(ulong)modstate)<<32)|keycode; } 7141 7142 private __gshared GlobalHotkey[ulong] globalHotkeyList; 7143 7144 NativeEventHandler getNativeEventHandler () { 7145 return delegate int (XEvent e) { 7146 if (e.type != EventType.KeyPress) return 1; 7147 auto kev = cast(const(XKeyEvent)*)&e; 7148 auto hash = keyCode2Hash(e.xkey.keycode, cleanupModifiers(e.xkey.state)); 7149 if (auto ghkp = hash in globalHotkeyList) { 7150 try { 7151 ghkp.doHandle(); 7152 } catch (Exception e) { 7153 import core.stdc.stdio : stderr, fprintf; 7154 stderr.fprintf("HOTKEY HANDLER EXCEPTION: %.*s", cast(uint)e.msg.length, e.msg.ptr); 7155 } 7156 } 7157 return 1; 7158 }; 7159 } 7160 7161 private this () { 7162 auto dpy = XDisplayConnection.get; 7163 auto root = RootWindow(dpy, DefaultScreen(dpy)); 7164 CapableOfHandlingNativeEvent.nativeHandleMapping[root] = this; 7165 XDisplayConnection.addRootInput(EventMask.KeyPressMask); 7166 } 7167 7168 /// Register new global hotkey with initialized `GlobalHotkey` object. 7169 /// This function will throw if it failed to register hotkey (i.e. hotkey is invalid or already taken). 7170 static void register (GlobalHotkey gh) { 7171 if (gh is null) return; 7172 if (gh.key.key == 0 || !isGoodModifierMask(gh.key.modifierState)) throw new Exception("invalid global hotkey"); 7173 7174 auto dpy = XDisplayConnection.get; 7175 immutable keycode = keyEvent2KeyCode(gh.key); 7176 7177 auto hash = keyCode2Hash(keycode, gh.key.modifierState); 7178 if (hash in globalHotkeyList) throw new Exception("duplicate global hotkey"); 7179 if (ghmanager is null) ghmanager = new GlobalHotkeyManager(); 7180 XSync(dpy, 0/*False*/); 7181 7182 Window root = RootWindow(dpy, DefaultScreen(dpy)); 7183 XErrorHandler savedErrorHandler = XSetErrorHandler(&XGrabErrorHandler); 7184 ghfailed = false; 7185 foreach (immutable uint ormask; masklist[]) { 7186 XGrabKey(dpy, keycode, gh.key.modifierState|ormask, /*grab_window*/root, /*owner_events*/0/*False*/, GrabMode.GrabModeAsync, GrabMode.GrabModeAsync); 7187 } 7188 XSync(dpy, 0/*False*/); 7189 XSetErrorHandler(savedErrorHandler); 7190 7191 if (ghfailed) { 7192 savedErrorHandler = XSetErrorHandler(&XGrabErrorHandler); 7193 foreach (immutable uint ormask; masklist[]) XUngrabKey(dpy, keycode, gh.key.modifierState|ormask, /*grab_window*/root); 7194 XSync(dpy, 0/*False*/); 7195 XSetErrorHandler(savedErrorHandler); 7196 throw new Exception("cannot register global hotkey"); 7197 } 7198 7199 globalHotkeyList[hash] = gh; 7200 } 7201 7202 /// Ditto 7203 static void register (const(char)[] akey, void delegate () ahandler) { 7204 register(new GlobalHotkey(akey, ahandler)); 7205 } 7206 7207 private static void removeByHash (ulong hash) { 7208 if (auto ghp = hash in globalHotkeyList) { 7209 auto dpy = XDisplayConnection.get; 7210 immutable keycode = keyEvent2KeyCode(ghp.key); 7211 Window root = RootWindow(dpy, DefaultScreen(dpy)); 7212 XSync(dpy, 0/*False*/); 7213 XErrorHandler savedErrorHandler = XSetErrorHandler(&XGrabErrorHandler); 7214 foreach (immutable uint ormask; masklist[]) XUngrabKey(dpy, keycode, ghp.key.modifierState|ormask, /*grab_window*/root); 7215 XSync(dpy, 0/*False*/); 7216 XSetErrorHandler(savedErrorHandler); 7217 globalHotkeyList.remove(hash); 7218 } 7219 } 7220 7221 /// Register new global hotkey with previously used `GlobalHotkey` object. 7222 /// It is safe to unregister unknown or invalid hotkey. 7223 static void unregister (GlobalHotkey gh) { 7224 //TODO: add second AA for faster search? prolly doesn't worth it. 7225 if (gh is null) return; 7226 foreach (const ref kv; globalHotkeyList.byKeyValue) { 7227 if (kv.value is gh) { 7228 removeByHash(kv.key); 7229 return; 7230 } 7231 } 7232 } 7233 7234 /// Ditto. 7235 static void unregister (const(char)[] key) { 7236 auto kev = KeyEvent.parse(key); 7237 immutable keycode = keyEvent2KeyCode(kev); 7238 removeByHash(keyCode2Hash(keycode, kev.modifierState)); 7239 } 7240 } 7241 } 7242 7243 version(Windows) { 7244 /++ 7245 See [SyntheticInput.sendSyntheticInput] instead for cross-platform applications. 7246 7247 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). 7248 +/ 7249 void sendSyntheticInput(wstring s) { 7250 INPUT[] inputs; 7251 inputs.reserve(s.length * 2); 7252 7253 foreach(wchar c; s) { 7254 INPUT input; 7255 input.type = INPUT_KEYBOARD; 7256 input.ki.wScan = c; 7257 input.ki.dwFlags = KEYEVENTF_UNICODE; 7258 inputs ~= input; 7259 7260 input.ki.dwFlags |= KEYEVENTF_KEYUP; 7261 inputs ~= input; 7262 } 7263 7264 if(SendInput(cast(int) inputs.length, inputs.ptr, INPUT.sizeof) != inputs.length) { 7265 throw new WindowsApiException("SendInput", GetLastError()); 7266 } 7267 7268 } 7269 7270 7271 // global hotkey helper function 7272 7273 /// Platform-specific for Windows. Registers a global hotkey. Returns a registration ID. See [GlobalHotkeyManager] for Linux. Maybe some day I will merge these. 7274 int registerHotKey(SimpleWindow window, UINT modifiers, UINT vk, void delegate() handler) @system { 7275 __gshared int hotkeyId = 0; 7276 int id = ++hotkeyId; 7277 if(!RegisterHotKey(window.impl.hwnd, id, modifiers, vk)) 7278 throw new Exception("RegisterHotKey"); 7279 7280 __gshared void delegate()[WPARAM][HWND] handlers; 7281 7282 handlers[window.impl.hwnd][id] = handler; 7283 7284 int delegate(HWND, UINT, WPARAM, LPARAM, out int) oldHandler; 7285 7286 auto nativeEventHandler = delegate int(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam, out int mustReturn) { 7287 switch(msg) { 7288 // http://msdn.microsoft.com/en-us/library/windows/desktop/ms646279%28v=vs.85%29.aspx 7289 case WM_HOTKEY: 7290 if(auto list = hwnd in handlers) { 7291 if(auto h = wParam in *list) { 7292 (*h)(); 7293 return 0; 7294 } 7295 } 7296 goto default; 7297 default: 7298 } 7299 if(oldHandler) 7300 return oldHandler(hwnd, msg, wParam, lParam, mustReturn); 7301 return 1; // pass it on 7302 }; 7303 7304 if(window.handleNativeEvent.funcptr !is nativeEventHandler.funcptr) { 7305 oldHandler = window.handleNativeEvent; 7306 window.handleNativeEvent = nativeEventHandler; 7307 } 7308 7309 return id; 7310 } 7311 7312 /// Platform-specific for Windows. Unregisters a key. The id is the value returned by [registerHotKey]. 7313 void unregisterHotKey(SimpleWindow window, int id) { 7314 if(!UnregisterHotKey(window.impl.hwnd, id)) 7315 throw new WindowsApiException("UnregisterHotKey", GetLastError()); 7316 } 7317 } 7318 7319 version (X11) { 7320 pragma(lib, "dl"); 7321 import core.sys.posix.dlfcn; 7322 } 7323 7324 /++ 7325 Allows for sending synthetic input to the X server via the Xtst 7326 extension or on Windows using SendInput. 7327 7328 Please remember user input is meant to be user - don't use this 7329 if you have some other alternative! 7330 7331 History: 7332 Added May 17, 2020 with the X implementation. 7333 7334 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.) 7335 Bugs: 7336 All methods on OSX Cocoa will throw not yet implemented exceptions. 7337 +/ 7338 struct SyntheticInput { 7339 @disable this(); 7340 7341 private int* refcount; 7342 7343 version(X11) { 7344 private void* lib; 7345 7346 private extern(C) { 7347 void function(Display*, uint keycode, bool press, arch_ulong delay) XTestFakeKeyEvent; 7348 void function(Display*, uint button, bool press, arch_ulong delay) XTestFakeButtonEvent; 7349 } 7350 } 7351 7352 /// The dummy param must be 0. 7353 this(int dummy) { 7354 version(X11) { 7355 lib = dlopen("libXtst.so", RTLD_NOW); 7356 if(lib is null) 7357 throw new Exception("cannot load xtest lib extension"); 7358 scope(failure) 7359 dlclose(lib); 7360 7361 XTestFakeButtonEvent = cast(typeof(XTestFakeButtonEvent)) dlsym(lib, "XTestFakeButtonEvent"); 7362 XTestFakeKeyEvent = cast(typeof(XTestFakeKeyEvent)) dlsym(lib, "XTestFakeKeyEvent"); 7363 7364 if(XTestFakeKeyEvent is null) 7365 throw new Exception("No XTestFakeKeyEvent"); 7366 if(XTestFakeButtonEvent is null) 7367 throw new Exception("No XTestFakeButtonEvent"); 7368 } 7369 7370 refcount = new int; 7371 *refcount = 1; 7372 } 7373 7374 this(this) { 7375 if(refcount) 7376 *refcount += 1; 7377 } 7378 7379 ~this() { 7380 if(refcount) { 7381 *refcount -= 1; 7382 if(*refcount == 0) 7383 // I commented this because if I close the lib before 7384 // XCloseDisplay, it is liable to segfault... so just 7385 // gonna keep it loaded if it is loaded, no big deal 7386 // anyway. 7387 {} // dlclose(lib); 7388 } 7389 } 7390 7391 /++ 7392 Simulates typing a string into the keyboard. 7393 7394 Bugs: 7395 On X11, this ONLY works with basic ascii! On Windows, it can handle more. 7396 7397 Not implemented except on Windows and X11. 7398 +/ 7399 void sendSyntheticInput(string s) { 7400 version(Windows) { 7401 INPUT[] inputs; 7402 inputs.reserve(s.length * 2); 7403 7404 auto ei = GetMessageExtraInfo(); 7405 7406 foreach(wchar c; s) { 7407 INPUT input; 7408 input.type = INPUT_KEYBOARD; 7409 input.ki.wScan = c; 7410 input.ki.dwFlags = KEYEVENTF_UNICODE; 7411 input.ki.dwExtraInfo = ei; 7412 inputs ~= input; 7413 7414 input.ki.dwFlags |= KEYEVENTF_KEYUP; 7415 inputs ~= input; 7416 } 7417 7418 if(SendInput(cast(int) inputs.length, inputs.ptr, INPUT.sizeof) != inputs.length) { 7419 throw new WindowsApiException("SendInput", GetLastError()); 7420 } 7421 } else version(X11) { 7422 int delay = 0; 7423 foreach(ch; s) { 7424 pressKey(cast(Key) ch, true, delay); 7425 pressKey(cast(Key) ch, false, delay); 7426 delay += 5; 7427 } 7428 } else throw new NotYetImplementedException(); 7429 } 7430 7431 /++ 7432 Sends a fake press or release key event. 7433 7434 Please note you need to call [flushGui] or return to the event loop for this to actually be sent on X11. 7435 7436 Bugs: 7437 The `delay` parameter is not implemented yet on Windows. 7438 7439 Not implemented except on Windows and X11. 7440 +/ 7441 void pressKey(Key key, bool pressed, int delay = 0) { 7442 version(Windows) { 7443 INPUT input; 7444 input.type = INPUT_KEYBOARD; 7445 input.ki.wVk = cast(ushort) key; 7446 7447 input.ki.dwFlags = pressed ? 0 : KEYEVENTF_KEYUP; 7448 input.ki.dwExtraInfo = GetMessageExtraInfo(); 7449 7450 if(SendInput(1, &input, INPUT.sizeof) != 1) { 7451 throw new WindowsApiException("SendInput", GetLastError()); 7452 } 7453 } else version(X11) { 7454 XTestFakeKeyEvent(XDisplayConnection.get, XKeysymToKeycode(XDisplayConnection.get, key), pressed, delay + pressed ? 0 : 5); 7455 } else throw new NotYetImplementedException(); 7456 } 7457 7458 /++ 7459 Sends a fake mouse button press or release event. 7460 7461 Please note you need to call [flushGui] or return to the event loop for this to actually be sent on X11. 7462 7463 `pressed` param must be `true` if button is `wheelUp` or `wheelDown`. 7464 7465 Bugs: 7466 The `delay` parameter is not implemented yet on Windows. 7467 7468 The backButton and forwardButton will throw NotYetImplementedException on Windows. 7469 7470 All arguments will throw NotYetImplementedException on OSX Cocoa. 7471 +/ 7472 void pressMouseButton(MouseButton button, bool pressed, int delay = 0) { 7473 version(Windows) { 7474 INPUT input; 7475 input.type = INPUT_MOUSE; 7476 input.mi.dwExtraInfo = GetMessageExtraInfo(); 7477 7478 // input.mi.mouseData for a wheel event 7479 7480 switch(button) { 7481 case MouseButton.left: input.mi.dwFlags = pressed ? MOUSEEVENTF_LEFTDOWN : MOUSEEVENTF_LEFTUP; break; 7482 case MouseButton.middle: input.mi.dwFlags = pressed ? MOUSEEVENTF_MIDDLEDOWN : MOUSEEVENTF_MIDDLEUP; break; 7483 case MouseButton.right: input.mi.dwFlags = pressed ? MOUSEEVENTF_RIGHTDOWN : MOUSEEVENTF_RIGHTUP; break; 7484 case MouseButton.wheelUp: 7485 case MouseButton.wheelDown: 7486 input.mi.dwFlags = MOUSEEVENTF_WHEEL; 7487 input.mi.mouseData = button == MouseButton.wheelUp ? 120 : -120; 7488 break; 7489 case MouseButton.backButton: throw new NotYetImplementedException(); 7490 case MouseButton.forwardButton: throw new NotYetImplementedException(); 7491 default: 7492 } 7493 7494 if(SendInput(1, &input, INPUT.sizeof) != 1) { 7495 throw new WindowsApiException("SendInput", GetLastError()); 7496 } 7497 } else version(X11) { 7498 int btn; 7499 7500 switch(button) { 7501 case MouseButton.left: btn = 1; break; 7502 case MouseButton.middle: btn = 2; break; 7503 case MouseButton.right: btn = 3; break; 7504 case MouseButton.wheelUp: btn = 4; break; 7505 case MouseButton.wheelDown: btn = 5; break; 7506 case MouseButton.backButton: btn = 8; break; 7507 case MouseButton.forwardButton: btn = 9; break; 7508 default: 7509 } 7510 7511 assert(btn); 7512 7513 XTestFakeButtonEvent(XDisplayConnection.get, btn, pressed, delay); 7514 } else throw new NotYetImplementedException(); 7515 } 7516 7517 /// 7518 static void moveMouseArrowBy(int dx, int dy) { 7519 version(Windows) { 7520 INPUT input; 7521 input.type = INPUT_MOUSE; 7522 input.mi.dwExtraInfo = GetMessageExtraInfo(); 7523 input.mi.dx = dx; 7524 input.mi.dy = dy; 7525 input.mi.dwFlags = MOUSEEVENTF_MOVE; 7526 7527 if(SendInput(1, &input, INPUT.sizeof) != 1) { 7528 throw new WindowsApiException("SendInput", GetLastError()); 7529 } 7530 } else version(X11) { 7531 auto disp = XDisplayConnection.get(); 7532 XWarpPointer(disp, None, None, 0, 0, 0, 0, dx, dy); 7533 XFlush(disp); 7534 } else throw new NotYetImplementedException(); 7535 } 7536 7537 /// 7538 static void moveMouseArrowTo(int x, int y) { 7539 version(Windows) { 7540 INPUT input; 7541 input.type = INPUT_MOUSE; 7542 input.mi.dwExtraInfo = GetMessageExtraInfo(); 7543 input.mi.dx = x; 7544 input.mi.dy = y; 7545 input.mi.dwFlags = MOUSEEVENTF_MOVE | MOUSEEVENTF_ABSOLUTE; 7546 7547 if(SendInput(1, &input, INPUT.sizeof) != 1) { 7548 throw new WindowsApiException("SendInput", GetLastError()); 7549 } 7550 } else version(X11) { 7551 auto disp = XDisplayConnection.get(); 7552 auto root = RootWindow(disp, DefaultScreen(disp)); 7553 XWarpPointer(disp, None, root, 0, 0, 0, 0, x, y); 7554 XFlush(disp); 7555 } else throw new NotYetImplementedException(); 7556 } 7557 } 7558 7559 7560 7561 /++ 7562 [ScreenPainter] operations can use different operations to combine the color with the color on screen. 7563 7564 See_Also: 7565 $(LIST 7566 *[ScreenPainter] 7567 *[ScreenPainter.rasterOp] 7568 ) 7569 +/ 7570 enum RasterOp { 7571 normal, /// Replaces the pixel. 7572 xor, /// Uses bitwise xor to draw. 7573 } 7574 7575 // being phobos-free keeps the size WAY down 7576 private const(char)* toStringz(string s) { return (s ~ '\0').ptr; } 7577 package(arsd) const(wchar)* toWStringz(wstring s) { return (s ~ '\0').ptr; } 7578 package(arsd) const(wchar)* toWStringz(string s) { 7579 wstring r; 7580 foreach(dchar c; s) 7581 r ~= c; 7582 r ~= '\0'; 7583 return r.ptr; 7584 } 7585 private string[] split(in void[] a, char c) { 7586 string[] ret; 7587 size_t previous = 0; 7588 foreach(i, char ch; cast(ubyte[]) a) { 7589 if(ch == c) { 7590 ret ~= cast(string) a[previous .. i]; 7591 previous = i + 1; 7592 } 7593 } 7594 if(previous != a.length) 7595 ret ~= cast(string) a[previous .. $]; 7596 return ret; 7597 } 7598 7599 version(without_opengl) { 7600 enum OpenGlOptions { 7601 no, 7602 } 7603 } else { 7604 /++ 7605 Determines if you want an OpenGL context created on the new window. 7606 7607 7608 See more: [#topics-3d|in the 3d topic]. 7609 7610 --- 7611 import arsd.simpledisplay; 7612 void main() { 7613 auto window = new SimpleWindow(500, 500, "OpenGL Test", OpenGlOptions.yes); 7614 7615 // Set up the matrix 7616 window.setAsCurrentOpenGlContext(); // make this window active 7617 7618 // This is called on each frame, we will draw our scene 7619 window.redrawOpenGlScene = delegate() { 7620 7621 }; 7622 7623 window.eventLoop(0); 7624 } 7625 --- 7626 +/ 7627 enum OpenGlOptions { 7628 no, /// No OpenGL context is created 7629 yes, /// Yes, create an OpenGL context 7630 } 7631 7632 version(X11) { 7633 static if (!SdpyIsUsingIVGLBinds) { 7634 7635 7636 struct __GLXFBConfigRec {} 7637 alias GLXFBConfig = __GLXFBConfigRec*; 7638 7639 //pragma(lib, "GL"); 7640 //pragma(lib, "GLU"); 7641 interface GLX { 7642 extern(C) nothrow @nogc { 7643 XVisualInfo* glXChooseVisual(Display *dpy, int screen, 7644 const int *attrib_list); 7645 7646 void glXCopyContext(Display *dpy, GLXContext src, 7647 GLXContext dst, arch_ulong mask); 7648 7649 GLXContext glXCreateContext(Display *dpy, XVisualInfo *vis, 7650 GLXContext share_list, Bool direct); 7651 7652 GLXPixmap glXCreateGLXPixmap(Display *dpy, XVisualInfo *vis, 7653 Pixmap pixmap); 7654 7655 void glXDestroyContext(Display *dpy, GLXContext ctx); 7656 7657 void glXDestroyGLXPixmap(Display *dpy, GLXPixmap pix); 7658 7659 int glXGetConfig(Display *dpy, XVisualInfo *vis, 7660 int attrib, int *value); 7661 7662 GLXContext glXGetCurrentContext(); 7663 7664 GLXDrawable glXGetCurrentDrawable(); 7665 7666 Bool glXIsDirect(Display *dpy, GLXContext ctx); 7667 7668 Bool glXMakeCurrent(Display *dpy, GLXDrawable drawable, 7669 GLXContext ctx); 7670 7671 Bool glXQueryExtension(Display *dpy, int *error_base, int *event_base); 7672 7673 Bool glXQueryVersion(Display *dpy, int *major, int *minor); 7674 7675 void glXSwapBuffers(Display *dpy, GLXDrawable drawable); 7676 7677 void glXUseXFont(Font font, int first, int count, int list_base); 7678 7679 void glXWaitGL(); 7680 7681 void glXWaitX(); 7682 7683 7684 GLXFBConfig* glXChooseFBConfig (Display*, int, int*, int*); 7685 int glXGetFBConfigAttrib (Display*, GLXFBConfig, int, int*); 7686 XVisualInfo* glXGetVisualFromFBConfig (Display*, GLXFBConfig); 7687 7688 char* glXQueryExtensionsString (Display*, int); 7689 void* glXGetProcAddress (const(char)*); 7690 7691 } 7692 } 7693 7694 version(OSX) 7695 mixin DynamicLoad!(GLX, "GL", 0, openGlLibrariesSuccessfullyLoaded) glx; 7696 else 7697 mixin DynamicLoad!(GLX, "GLX", 0, openGlLibrariesSuccessfullyLoaded) glx; 7698 shared static this() { 7699 glx.loadDynamicLibrary(); 7700 } 7701 7702 alias glbindGetProcAddress = glXGetProcAddress; 7703 } 7704 } else version(Windows) { 7705 /* it is done below by interface GL */ 7706 } else 7707 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."); 7708 } 7709 7710 deprecated("Sorry, I misspelled it in the first version! Use `Resizability` instead.") 7711 alias Resizablity = Resizability; 7712 7713 /// When you create a SimpleWindow, you can see its resizability to be one of these via the constructor... 7714 enum Resizability { 7715 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. 7716 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. 7717 /++ 7718 $(PITFALL 7719 Planned for the future but not implemented. 7720 ) 7721 7722 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. 7723 7724 History: 7725 Added November 11, 2022, but not yet implemented and may not be for some time. 7726 +/ 7727 /*@__future*/ allowResizingMaintainingAspectRatio, 7728 /++ 7729 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. 7730 7731 History: 7732 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. 7733 7734 Your programs should not be affected, as they will continue to function as if the user simply never resized the window at all. 7735 +/ 7736 automaticallyScaleIfPossible, 7737 } 7738 /// ditto 7739 alias Resizeability = Resizability; 7740 7741 7742 /++ 7743 Alignment for [ScreenPainter.drawText]. Left, Center, or Right may be combined with VerticalTop, VerticalCenter, or VerticalBottom via bitwise or. 7744 +/ 7745 enum TextAlignment : uint { 7746 Left = 0, /// 7747 Center = 1, /// 7748 Right = 2, /// 7749 7750 VerticalTop = 0, /// 7751 VerticalCenter = 4, /// 7752 VerticalBottom = 8, /// 7753 } 7754 7755 public import arsd.color; // no longer stand alone... :-( but i need a common type for this to work with images easily. 7756 alias Rectangle = arsd.color.Rectangle; 7757 7758 7759 /++ 7760 Keyboard press and release events. 7761 +/ 7762 struct KeyEvent { 7763 /// see table below. Always use the symbolic names, even for ASCII characters, since the actual numbers vary across platforms. See [Key] 7764 Key key; 7765 ubyte hardwareCode; /// A platform and hardware specific code for the key 7766 bool pressed; /// true if the key was just pressed, false if it was just released. note: released events aren't always sent... 7767 7768 deprecated("This never actually worked anyway, you should do a character event handler instead.") dchar character; 7769 7770 uint modifierState; /// see enum [ModifierState]. They are bitwise combined together. 7771 7772 SimpleWindow window; /// associated Window 7773 7774 /++ 7775 A view into the upcoming buffer holding coming character events that are sent if and only if neither 7776 the alt or super modifier keys are pressed (check this with `!(modifierState & (ModifierState.window | ModifierState.alt))` 7777 to predict if char events are actually coming.. 7778 7779 Only available on X systems since this information is not given ahead of time elsewhere. 7780 (Well, you COULD probably dig it up, but as far as I know right now, it isn't terribly pretty.) 7781 7782 I'm adding this because it is useful to the terminal emulator, but given its platform specificness 7783 and potential quirks I'd recommend avoiding it. 7784 7785 History: 7786 Added April 26, 2021 (dub v9.5) 7787 +/ 7788 version(X11) 7789 dchar[] charsPossible; 7790 7791 // convert key event to simplified string representation a-la emacs 7792 const(char)[] toStrBuf(bool growdest=false) (char[] dest) const nothrow @trusted { 7793 uint dpos = 0; 7794 void put (const(char)[] s...) nothrow @trusted { 7795 static if (growdest) { 7796 foreach (char ch; s) if (dpos < dest.length) dest.ptr[dpos++] = ch; else { dest ~= ch; ++dpos; } 7797 } else { 7798 foreach (char ch; s) if (dpos < dest.length) dest.ptr[dpos++] = ch; 7799 } 7800 } 7801 7802 void putMod (ModifierState mod, Key key, string text) nothrow @trusted { 7803 if ((this.modifierState&mod) != 0 && (this.pressed || this.key != key)) put(text); 7804 } 7805 7806 if (!this.key && !(this.modifierState&(ModifierState.ctrl|ModifierState.alt|ModifierState.shift|ModifierState.windows))) return null; 7807 7808 // put modifiers 7809 // releasing modifier keys can produce bizarre things like "Ctrl+Ctrl", so hack around it 7810 putMod(ModifierState.ctrl, Key.Ctrl, "Ctrl+"); 7811 putMod(ModifierState.alt, Key.Alt, "Alt+"); 7812 putMod(ModifierState.windows, Key.Shift, "Windows+"); 7813 putMod(ModifierState.shift, Key.Shift, "Shift+"); 7814 7815 if (this.key) { 7816 foreach (string kn; __traits(allMembers, Key)) { 7817 if (this.key == __traits(getMember, Key, kn)) { 7818 // HACK! 7819 static if (kn == "N0") put("0"); 7820 else static if (kn == "N1") put("1"); 7821 else static if (kn == "N2") put("2"); 7822 else static if (kn == "N3") put("3"); 7823 else static if (kn == "N4") put("4"); 7824 else static if (kn == "N5") put("5"); 7825 else static if (kn == "N6") put("6"); 7826 else static if (kn == "N7") put("7"); 7827 else static if (kn == "N8") put("8"); 7828 else static if (kn == "N9") put("9"); 7829 else put(kn); 7830 return dest[0..dpos]; 7831 } 7832 } 7833 put("Unknown"); 7834 } else { 7835 if (dpos && dest[dpos-1] == '+') --dpos; 7836 } 7837 return dest[0..dpos]; 7838 } 7839 7840 string toStr() () { return cast(string)toStrBuf!true(null); } // it is safe to cast here 7841 7842 /** Parse string into key name with modifiers. It accepts things like: 7843 * 7844 * C-H-1 -- emacs style (ctrl, and windows, and 1) 7845 * 7846 * Ctrl+Win+1 -- windows style 7847 * 7848 * Ctrl-Win-1 -- '-' is a valid delimiter too 7849 * 7850 * Ctrl Win 1 -- and space 7851 * 7852 * and even "Win + 1 + Ctrl". 7853 */ 7854 static KeyEvent parse (const(char)[] name, bool* ignoreModsOut=null, int* updown=null) nothrow @trusted @nogc { 7855 auto nanchor = name; // keep it anchored, 'cause `name` may have NO_INTERIOR set 7856 7857 // remove trailing spaces 7858 while (name.length && name[$-1] <= ' ') name = name[0..$-1]; 7859 7860 // tokens delimited by blank, '+', or '-' 7861 // null on eol 7862 const(char)[] getToken () nothrow @trusted @nogc { 7863 // remove leading spaces and delimiters 7864 while (name.length && (name[0] <= ' ' || name[0] == '+' || name[0] == '-')) name = name[1..$]; 7865 if (name.length == 0) return null; // oops, no more tokens 7866 // get token 7867 size_t epos = 0; 7868 while (epos < name.length && name[epos] > ' ' && name[epos] != '+' && name[epos] != '-') ++epos; 7869 assert(epos > 0 && epos <= name.length); 7870 auto res = name[0..epos]; 7871 name = name[epos..$]; 7872 return res; 7873 } 7874 7875 static bool strEquCI (const(char)[] s0, const(char)[] s1) pure nothrow @trusted @nogc { 7876 if (s0.length != s1.length) return false; 7877 foreach (immutable ci, char c0; s0) { 7878 if (c0 >= 'A' && c0 <= 'Z') c0 += 32; // poor man's tolower 7879 char c1 = s1[ci]; 7880 if (c1 >= 'A' && c1 <= 'Z') c1 += 32; // poor man's tolower 7881 if (c0 != c1) return false; 7882 } 7883 return true; 7884 } 7885 7886 if (ignoreModsOut !is null) *ignoreModsOut = false; 7887 if (updown !is null) *updown = -1; 7888 KeyEvent res; 7889 res.key = cast(Key)0; // just in case 7890 const(char)[] tk, tkn; // last token 7891 bool allowEmascStyle = true; 7892 bool ignoreModifiers = false; 7893 tokenloop: for (;;) { 7894 tk = tkn; 7895 tkn = getToken(); 7896 //k8: yay, i took "Bloody Mess" trait from Fallout! 7897 if (tkn.length != 0 && tk.length == 0) { tk = tkn; continue tokenloop; } 7898 if (tkn.length == 0 && tk.length == 0) break; // no more tokens 7899 if (allowEmascStyle && tkn.length != 0) { 7900 if (tk.length == 1) { 7901 char mdc = tk[0]; 7902 if (mdc >= 'a' && mdc <= 'z') mdc -= 32; // poor man's toupper() 7903 if (mdc == 'C' && (res.modifierState&ModifierState.ctrl) == 0) {res.modifierState |= ModifierState.ctrl; continue tokenloop; } 7904 if (mdc == 'M' && (res.modifierState&ModifierState.alt) == 0) { res.modifierState |= ModifierState.alt; continue tokenloop; } 7905 if (mdc == 'H' && (res.modifierState&ModifierState.windows) == 0) { res.modifierState |= ModifierState.windows; continue tokenloop; } 7906 if (mdc == 'S' && (res.modifierState&ModifierState.shift) == 0) { res.modifierState |= ModifierState.shift; continue tokenloop; } 7907 if (mdc == '*') { ignoreModifiers = true; continue tokenloop; } 7908 if (mdc == 'U' || mdc == 'R') { if (updown !is null) *updown = 0; continue tokenloop; } 7909 if (mdc == 'D' || mdc == 'P') { if (updown !is null) *updown = 1; continue tokenloop; } 7910 } 7911 } 7912 allowEmascStyle = false; 7913 if (strEquCI(tk, "Ctrl")) { res.modifierState |= ModifierState.ctrl; continue tokenloop; } 7914 if (strEquCI(tk, "Alt")) { res.modifierState |= ModifierState.alt; continue tokenloop; } 7915 if (strEquCI(tk, "Win") || strEquCI(tk, "Windows")) { res.modifierState |= ModifierState.windows; continue tokenloop; } 7916 if (strEquCI(tk, "Shift")) { res.modifierState |= ModifierState.shift; continue tokenloop; } 7917 if (strEquCI(tk, "Release")) { if (updown !is null) *updown = 0; continue tokenloop; } 7918 if (strEquCI(tk, "Press")) { if (updown !is null) *updown = 1; continue tokenloop; } 7919 if (tk == "*") { ignoreModifiers = true; continue tokenloop; } 7920 if (tk.length == 0) continue; 7921 // try key name 7922 if (res.key == 0) { 7923 // little hack 7924 if (tk.length == 1 && tk[0] >= '0' && tk[0] <= '9') { 7925 final switch (tk[0]) { 7926 case '0': tk = "N0"; break; 7927 case '1': tk = "N1"; break; 7928 case '2': tk = "N2"; break; 7929 case '3': tk = "N3"; break; 7930 case '4': tk = "N4"; break; 7931 case '5': tk = "N5"; break; 7932 case '6': tk = "N6"; break; 7933 case '7': tk = "N7"; break; 7934 case '8': tk = "N8"; break; 7935 case '9': tk = "N9"; break; 7936 } 7937 } 7938 foreach (string kn; __traits(allMembers, Key)) { 7939 if (strEquCI(tk, kn)) { res.key = __traits(getMember, Key, kn); continue tokenloop; } 7940 } 7941 } 7942 // unknown or duplicate key name, get out of here 7943 break; 7944 } 7945 if (ignoreModsOut !is null) *ignoreModsOut = ignoreModifiers; 7946 return res; // something 7947 } 7948 7949 bool opEquals() (const(char)[] name) const nothrow @trusted @nogc { 7950 enum modmask = (ModifierState.ctrl|ModifierState.alt|ModifierState.shift|ModifierState.windows); 7951 void doModKey (ref uint mask, ref Key kk, Key k, ModifierState mst) { 7952 if (kk == k) { mask |= mst; kk = cast(Key)0; } 7953 } 7954 bool ignoreMods; 7955 int updown; 7956 auto ke = KeyEvent.parse(name, &ignoreMods, &updown); 7957 if ((updown == 0 && this.pressed) || (updown == 1 && !this.pressed)) return false; 7958 if (this.key != ke.key) { 7959 // things like "ctrl+alt" are complicated 7960 uint tkm = this.modifierState&modmask; 7961 uint kkm = ke.modifierState&modmask; 7962 Key tk = this.key; 7963 // ke 7964 doModKey(kkm, ke.key, Key.Ctrl, ModifierState.ctrl); 7965 doModKey(kkm, ke.key, Key.Alt, ModifierState.alt); 7966 doModKey(kkm, ke.key, Key.Windows, ModifierState.windows); 7967 doModKey(kkm, ke.key, Key.Shift, ModifierState.shift); 7968 // this 7969 doModKey(tkm, tk, Key.Ctrl, ModifierState.ctrl); 7970 doModKey(tkm, tk, Key.Alt, ModifierState.alt); 7971 doModKey(tkm, tk, Key.Windows, ModifierState.windows); 7972 doModKey(tkm, tk, Key.Shift, ModifierState.shift); 7973 return (tk == ke.key && tkm == kkm); 7974 } 7975 return (ignoreMods || ((this.modifierState&modmask) == (ke.modifierState&modmask))); 7976 } 7977 } 7978 7979 /// Sets the application name. 7980 @property string ApplicationName(string name) { 7981 return _applicationName = name; 7982 } 7983 7984 string _applicationName; 7985 7986 /// ditto 7987 @property string ApplicationName() { 7988 if(_applicationName is null) { 7989 import core.runtime; 7990 return Runtime.args[0]; 7991 } 7992 return _applicationName; 7993 } 7994 7995 7996 /// Type of a [MouseEvent]. 7997 enum MouseEventType : int { 7998 motion = 0, /// The mouse moved inside the window 7999 buttonPressed = 1, /// A mouse button was pressed or the wheel was spun 8000 buttonReleased = 2, /// A mouse button was released 8001 } 8002 8003 // FIXME: mouse move should be distinct from presses+releases, so we can avoid subscribing to those events in X unnecessarily 8004 /++ 8005 Listen for this on your event listeners if you are interested in mouse action. 8006 8007 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. 8008 8009 Examples: 8010 8011 This will draw boxes on the window with the mouse as you hold the left button. 8012 --- 8013 import arsd.simpledisplay; 8014 8015 void main() { 8016 auto window = new SimpleWindow(); 8017 8018 window.eventLoop(0, 8019 (MouseEvent ev) { 8020 if(ev.modifierState & ModifierState.leftButtonDown) { 8021 auto painter = window.draw(); 8022 painter.fillColor = Color.red; 8023 painter.outlineColor = Color.black; 8024 painter.drawRectangle(Point(ev.x / 16 * 16, ev.y / 16 * 16), 16, 16); 8025 } 8026 } 8027 ); 8028 } 8029 --- 8030 +/ 8031 struct MouseEvent { 8032 MouseEventType type; /// movement, press, release, double click. See [MouseEventType] 8033 8034 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. 8035 int y; /// Current Y position of the cursor when the event fired. 8036 8037 int dx; /// Change in X position since last report 8038 int dy; /// Change in Y position since last report 8039 8040 MouseButton button; /// See [MouseButton] 8041 int modifierState; /// See [ModifierState] 8042 8043 version(X11) 8044 private Time timestamp; 8045 8046 /// Returns a linear representation of mouse button, 8047 /// for use with static arrays. Guaranteed to be >= 0 && <= 15 8048 /// 8049 /// Its implementation is based on range-limiting `core.bitop.bsf(button) + 1`. 8050 @property ubyte buttonLinear() const { 8051 import core.bitop; 8052 if(button == 0) 8053 return 0; 8054 return (bsf(button) + 1) & 0b1111; 8055 } 8056 8057 bool doubleClick; /// was it a double click? Only set on type == [MouseEventType.buttonPressed] 8058 8059 SimpleWindow window; /// The window in which the event happened. 8060 8061 Point globalCoordinates() { 8062 Point p; 8063 if(window is null) 8064 throw new Exception("wtf"); 8065 static if(UsingSimpledisplayX11) { 8066 Window child; 8067 XTranslateCoordinates( 8068 XDisplayConnection.get, 8069 window.impl.window, 8070 RootWindow(XDisplayConnection.get, DefaultScreen(XDisplayConnection.get)), 8071 x, y, &p.x, &p.y, &child); 8072 return p; 8073 } else version(Windows) { 8074 POINT[1] points; 8075 points[0].x = x; 8076 points[0].y = y; 8077 MapWindowPoints( 8078 window.impl.hwnd, 8079 null, 8080 points.ptr, 8081 points.length 8082 ); 8083 p.x = points[0].x; 8084 p.y = points[0].y; 8085 8086 return p; 8087 } else version(OSXCocoa) { 8088 throw new NotYetImplementedException(); 8089 } else version(Emscripten) { 8090 throw new NotYetImplementedException(); 8091 } else static assert(0); 8092 } 8093 8094 bool opEquals() (const(char)[] str) pure nothrow @trusted @nogc { return equStr(this, str); } 8095 8096 /** 8097 can contain emacs-like modifier prefix 8098 case-insensitive names: 8099 lmbX/leftX 8100 rmbX/rightX 8101 mmbX/middleX 8102 wheelX 8103 motion (no prefix allowed) 8104 'X' is either "up" or "down" (or "-up"/"-down"); if omited, means "down" 8105 */ 8106 static bool equStr() (scope auto ref const MouseEvent event, const(char)[] str) pure nothrow @trusted @nogc { 8107 if (str.length == 0) return false; // just in case 8108 debug(arsd_mevent_strcmp) { import iv.cmdcon; conwriteln("str=<", str, ">"); } 8109 enum Flag : uint { Up = 0x8000_0000U, Down = 0x4000_0000U, Any = 0x1000_0000U } 8110 auto anchor = str; 8111 uint mods = 0; // uint.max == any 8112 // interesting bits in kmod 8113 uint kmodmask = 8114 ModifierState.shift| 8115 ModifierState.ctrl| 8116 ModifierState.alt| 8117 ModifierState.windows| 8118 ModifierState.leftButtonDown| 8119 ModifierState.middleButtonDown| 8120 ModifierState.rightButtonDown| 8121 0; 8122 uint lastButt = uint.max; // otherwise, bit 31 means "down" 8123 bool wasButtons = false; 8124 while (str.length) { 8125 if (str.ptr[0] <= ' ') { 8126 while (str.length && str.ptr[0] <= ' ') str = str[1..$]; 8127 continue; 8128 } 8129 // one-letter modifier? 8130 if (str.length >= 2 && str.ptr[1] == '-') { 8131 switch (str.ptr[0]) { 8132 case '*': // "any" modifier (cannot be undone) 8133 mods = mods.max; 8134 break; 8135 case 'C': case 'c': // emacs "ctrl" 8136 if (mods != mods.max) mods |= ModifierState.ctrl; 8137 break; 8138 case 'M': case 'm': // emacs "meta" 8139 if (mods != mods.max) mods |= ModifierState.alt; 8140 break; 8141 case 'S': case 's': // emacs "shift" 8142 if (mods != mods.max) mods |= ModifierState.shift; 8143 break; 8144 case 'H': case 'h': // emacs "hyper" (aka winkey) 8145 if (mods != mods.max) mods |= ModifierState.windows; 8146 break; 8147 default: 8148 return false; // unknown modifier 8149 } 8150 str = str[2..$]; 8151 continue; 8152 } 8153 // word 8154 char[16] buf = void; // locased 8155 auto wep = 0; 8156 while (str.length) { 8157 immutable char ch = str.ptr[0]; 8158 if (ch <= ' ' || ch == '-') break; 8159 str = str[1..$]; 8160 if (wep > buf.length) return false; // too long 8161 if (ch >= 'A' && ch <= 'Z') buf.ptr[wep++] = cast(char)(ch+32); // poor man tolower 8162 else if (ch >= 'a' && ch <= 'z') buf.ptr[wep++] = ch; 8163 else return false; // invalid char 8164 } 8165 if (wep == 0) return false; // just in case 8166 uint bnum; 8167 enum UpDown { None = -1, Up, Down, Any } 8168 auto updown = UpDown.None; // 0: up; 1: down 8169 switch (buf[0..wep]) { 8170 // left button 8171 case "lmbup": case "leftup": updown = UpDown.Up; goto case "lmb"; 8172 case "lmbdown": case "leftdown": updown = UpDown.Down; goto case "lmb"; 8173 case "lmbany": case "leftany": updown = UpDown.Any; goto case "lmb"; 8174 case "lmb": case "left": bnum = 0; break; 8175 // middle button 8176 case "mmbup": case "middleup": updown = UpDown.Up; goto case "mmb"; 8177 case "mmbdown": case "middledown": updown = UpDown.Down; goto case "mmb"; 8178 case "mmbany": case "middleany": updown = UpDown.Any; goto case "mmb"; 8179 case "mmb": case "middle": bnum = 1; break; 8180 // right button 8181 case "rmbup": case "rightup": updown = UpDown.Up; goto case "rmb"; 8182 case "rmbdown": case "rightdown": updown = UpDown.Down; goto case "rmb"; 8183 case "rmbany": case "rightany": updown = UpDown.Any; goto case "rmb"; 8184 case "rmb": case "right": bnum = 2; break; 8185 // wheel 8186 case "wheelup": updown = UpDown.Up; goto case "wheel"; 8187 case "wheeldown": updown = UpDown.Down; goto case "wheel"; 8188 case "wheelany": updown = UpDown.Any; goto case "wheel"; 8189 case "wheel": bnum = 3; break; 8190 // motion 8191 case "motion": bnum = 7; break; 8192 // unknown 8193 default: return false; 8194 } 8195 debug(arsd_mevent_strcmp) { import iv.cmdcon; conprintfln(" 0: mods=0x%08x; bnum=%u; updown=%s [%s]", mods, bnum, updown, str); } 8196 // parse possible "-up" or "-down" 8197 if (updown == UpDown.None && bnum < 7 && str.length > 0 && str.ptr[0] == '-') { 8198 wep = 0; 8199 foreach (immutable idx, immutable char ch; str[1..$]) { 8200 if (ch <= ' ' || ch == '-') break; 8201 assert(idx == wep); // for now; trick 8202 if (wep > buf.length) { wep = 0; break; } // too long 8203 if (ch >= 'A' && ch <= 'Z') buf.ptr[wep++] = cast(char)(ch+32); // poor man tolower 8204 else if (ch >= 'a' && ch <= 'z') buf.ptr[wep++] = ch; 8205 else { wep = 0; break; } // invalid char 8206 } 8207 if (wep == 2 && buf[0..wep] == "up") updown = UpDown.Up; 8208 else if (wep == 4 && buf[0..wep] == "down") updown = UpDown.Down; 8209 else if (wep == 3 && buf[0..wep] == "any") updown = UpDown.Any; 8210 // remove parsed part 8211 if (updown != UpDown.None) str = str[wep+1..$]; 8212 } 8213 if (updown == UpDown.None) { 8214 updown = UpDown.Down; 8215 } 8216 wasButtons = wasButtons || (bnum <= 2); 8217 //assert(updown != UpDown.None); 8218 debug(arsd_mevent_strcmp) { import iv.cmdcon; conprintfln(" 1: mods=0x%08x; bnum=%u; updown=%s [%s]", mods, bnum, updown, str); } 8219 // if we have a previous button, it goes to modifiers (unless it is a wheel or motion) 8220 if (lastButt != lastButt.max) { 8221 if ((lastButt&0xff) >= 3) return false; // wheel or motion 8222 if (mods != mods.max) { 8223 uint butbit = 0; 8224 final switch (lastButt&0x03) { 8225 case 0: butbit = ModifierState.leftButtonDown; break; 8226 case 1: butbit = ModifierState.middleButtonDown; break; 8227 case 2: butbit = ModifierState.rightButtonDown; break; 8228 } 8229 if (lastButt&Flag.Down) mods |= butbit; 8230 else if (lastButt&Flag.Up) mods &= ~butbit; 8231 else if (lastButt&Flag.Any) kmodmask &= ~butbit; 8232 } 8233 } 8234 // remember last button 8235 lastButt = bnum|(updown == UpDown.Up ? Flag.Up : updown == UpDown.Any ? Flag.Any : Flag.Down); 8236 } 8237 // no button -- nothing to do 8238 if (lastButt == lastButt.max) return false; 8239 // done parsing, check if something's left 8240 foreach (immutable char ch; str) if (ch > ' ') return false; // oops 8241 // remove action button from mask 8242 if ((lastButt&0xff) < 3) { 8243 final switch (lastButt&0x03) { 8244 case 0: kmodmask &= ~cast(uint)ModifierState.leftButtonDown; break; 8245 case 1: kmodmask &= ~cast(uint)ModifierState.middleButtonDown; break; 8246 case 2: kmodmask &= ~cast(uint)ModifierState.rightButtonDown; break; 8247 } 8248 } 8249 // special case: "Motion" means "ignore buttons" 8250 if ((lastButt&0xff) == 7 && !wasButtons) { 8251 debug(arsd_mevent_strcmp) { import iv.cmdcon; conwriteln(" *: special motion"); } 8252 kmodmask &= ~cast(uint)(ModifierState.leftButtonDown|ModifierState.middleButtonDown|ModifierState.rightButtonDown); 8253 } 8254 uint kmod = event.modifierState&kmodmask; 8255 debug(arsd_mevent_strcmp) { import iv.cmdcon; conprintfln(" *: mods=0x%08x; lastButt=0x%08x; kmod=0x%08x; type=%s", mods, lastButt, kmod, event.type); } 8256 // check modifier state 8257 if (mods != mods.max) { 8258 if (kmod != mods) return false; 8259 } 8260 // now check type 8261 if ((lastButt&0xff) == 7) { 8262 // motion 8263 if (event.type != MouseEventType.motion) return false; 8264 } else if ((lastButt&0xff) == 3) { 8265 // wheel 8266 if (lastButt&Flag.Up) return (event.type == MouseEventType.buttonPressed && event.button == MouseButton.wheelUp); 8267 if (lastButt&Flag.Down) return (event.type == MouseEventType.buttonPressed && event.button == MouseButton.wheelDown); 8268 if (lastButt&Flag.Any) return (event.type == MouseEventType.buttonPressed && (event.button == MouseButton.wheelUp || event.button == MouseButton.wheelUp)); 8269 return false; 8270 } else { 8271 // buttons 8272 if (((lastButt&Flag.Down) != 0 && event.type != MouseEventType.buttonPressed) || 8273 ((lastButt&Flag.Up) != 0 && event.type != MouseEventType.buttonReleased)) 8274 { 8275 return false; 8276 } 8277 // button number 8278 switch (lastButt&0x03) { 8279 case 0: if (event.button != MouseButton.left) return false; break; 8280 case 1: if (event.button != MouseButton.middle) return false; break; 8281 case 2: if (event.button != MouseButton.right) return false; break; 8282 default: return false; 8283 } 8284 } 8285 return true; 8286 } 8287 } 8288 8289 version(arsd_mevent_strcmp_test) unittest { 8290 MouseEvent event; 8291 event.type = MouseEventType.buttonPressed; 8292 event.button = MouseButton.left; 8293 event.modifierState = ModifierState.ctrl; 8294 assert(event == "C-LMB"); 8295 assert(event != "C-LMBUP"); 8296 assert(event != "C-LMB-UP"); 8297 assert(event != "C-S-LMB"); 8298 assert(event == "*-LMB"); 8299 assert(event != "*-LMB-UP"); 8300 8301 event.type = MouseEventType.buttonReleased; 8302 assert(event != "C-LMB"); 8303 assert(event == "C-LMBUP"); 8304 assert(event == "C-LMB-UP"); 8305 assert(event != "C-S-LMB"); 8306 assert(event != "*-LMB"); 8307 assert(event == "*-LMB-UP"); 8308 8309 event.button = MouseButton.right; 8310 event.modifierState |= ModifierState.shift; 8311 event.type = MouseEventType.buttonPressed; 8312 assert(event != "C-LMB"); 8313 assert(event != "C-LMBUP"); 8314 assert(event != "C-LMB-UP"); 8315 assert(event != "C-S-LMB"); 8316 assert(event != "*-LMB"); 8317 assert(event != "*-LMB-UP"); 8318 8319 assert(event != "C-RMB"); 8320 assert(event != "C-RMBUP"); 8321 assert(event != "C-RMB-UP"); 8322 assert(event == "C-S-RMB"); 8323 assert(event == "*-RMB"); 8324 assert(event != "*-RMB-UP"); 8325 } 8326 8327 /// This gives a few more options to drawing lines and such 8328 struct Pen { 8329 Color color; /// the foreground color 8330 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. 8331 Style style; /// See [Style] 8332 /+ 8333 // From X.h 8334 8335 #define LineSolid 0 8336 #define LineOnOffDash 1 8337 #define LineDoubleDash 2 8338 LineDou- The full path of the line is drawn, but the 8339 bleDash even dashes are filled differently from the 8340 odd dashes (see fill-style) with CapButt 8341 style used where even and odd dashes meet. 8342 8343 8344 8345 /* capStyle */ 8346 8347 #define CapNotLast 0 8348 #define CapButt 1 8349 #define CapRound 2 8350 #define CapProjecting 3 8351 8352 /* joinStyle */ 8353 8354 #define JoinMiter 0 8355 #define JoinRound 1 8356 #define JoinBevel 2 8357 8358 /* fillStyle */ 8359 8360 #define FillSolid 0 8361 #define FillTiled 1 8362 #define FillStippled 2 8363 #define FillOpaqueStippled 3 8364 8365 8366 +/ 8367 /// Style of lines drawn 8368 enum Style { 8369 Solid, /// a solid line 8370 Dashed, /// a dashed line 8371 Dotted, /// a dotted line 8372 } 8373 } 8374 8375 8376 /++ 8377 Represents an in-memory image in the format that the GUI expects, but with its raw data available to your program. 8378 8379 8380 On Windows, this means a device-independent bitmap. On X11, it is an XImage. 8381 8382 $(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.) 8383 8384 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. 8385 8386 If you intend to draw an image to screen several times, you will want to convert it into a [Sprite]. 8387 8388 $(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. 8389 8390 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! 8391 8392 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!) 8393 8394 Please call `destroy(image);` when you are done with it. The easiest way to do this is with scope: 8395 8396 --- 8397 auto image = new Image(256, 256); 8398 scope(exit) destroy(image); 8399 --- 8400 8401 As long as you don't hold on to it outside the scope. 8402 8403 I might change it to be an owned pointer at some point in the future. 8404 8405 ) 8406 8407 Drawing pixels on the image may be simple, using the `opIndexAssign` function, but 8408 you can also often get a fair amount of speedup by getting the raw data format and 8409 writing some custom code. 8410 8411 FIXME INSERT EXAMPLES HERE 8412 8413 8414 +/ 8415 final class Image { 8416 /// 8417 this(int width, int height, bool forcexshm=false, bool enableAlpha = false) { 8418 this.width = width; 8419 this.height = height; 8420 this.enableAlpha = enableAlpha; 8421 8422 impl.createImage(width, height, forcexshm, enableAlpha); 8423 } 8424 8425 /// 8426 this(Size size, bool forcexshm=false, bool enableAlpha = false) { 8427 this(size.width, size.height, forcexshm, enableAlpha); 8428 } 8429 8430 private bool suppressDestruction; 8431 8432 version(X11) 8433 this(XImage* handle) { 8434 this.handle = handle; 8435 this.rawData = cast(ubyte*) handle.data; 8436 this.width = handle.width; 8437 this.height = handle.height; 8438 this.enableAlpha = handle.depth == 32; 8439 suppressDestruction = true; 8440 } 8441 8442 ~this() { 8443 if(suppressDestruction) return; 8444 impl.dispose(); 8445 } 8446 8447 // these numbers are used for working with rawData itself, skipping putPixel and getPixel 8448 /// if you do the math yourself you might be able to optimize it. Call these functions only once and cache the value. 8449 pure const @system nothrow { 8450 /* 8451 To use these to draw a blue rectangle with size WxH at position X,Y... 8452 8453 // make certain that it will fit before we proceed 8454 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! 8455 8456 // gather all the values you'll need up front. These can be kept until the image changes size if you want 8457 // (though calculating them isn't really that expensive). 8458 auto nextLineAdjustment = img.adjustmentForNextLine(); 8459 auto offR = img.redByteOffset(); 8460 auto offB = img.blueByteOffset(); 8461 auto offG = img.greenByteOffset(); 8462 auto bpp = img.bytesPerPixel(); 8463 8464 auto data = img.getDataPointer(); 8465 8466 // figure out the starting byte offset 8467 auto offset = img.offsetForTopLeftPixel() + nextLineAdjustment*Y + bpp * X; 8468 8469 auto startOfLine = data + offset; // get our pointer lined up on the first pixel 8470 8471 // and now our drawing loop for the rectangle 8472 foreach(y; 0 .. H) { 8473 auto data = startOfLine; // we keep the start of line separately so moving to the next line is simple and portable 8474 foreach(x; 0 .. W) { 8475 // write our color 8476 data[offR] = 0; 8477 data[offG] = 0; 8478 data[offB] = 255; 8479 8480 data += bpp; // moving to the next pixel is just an addition... 8481 } 8482 startOfLine += nextLineAdjustment; 8483 } 8484 8485 8486 As you can see, the loop itself was very simple thanks to the calculations being moved outside. 8487 8488 FIXME: I wonder if I can make the pixel formats consistently 32 bit across platforms, so the color offsets 8489 can be made into a bitmask or something so we can write them as *uint... 8490 */ 8491 8492 /// 8493 int offsetForTopLeftPixel() { 8494 version(X11) { 8495 return 0; 8496 } else version(Windows) { 8497 if(enableAlpha) { 8498 return (width * 4) * (height - 1); 8499 } else { 8500 return (((cast(int) width * 3 + 3) / 4) * 4) * (height - 1); 8501 } 8502 } else version(OSXCocoa) { 8503 return 0 ; //throw new NotYetImplementedException(); 8504 } else version(Emscripten) { 8505 return 0; 8506 } else static assert(0, "fill in this info for other OSes"); 8507 } 8508 8509 /// 8510 int offsetForPixel(int x, int y) { 8511 version(X11) { 8512 auto offset = (y * width + x) * 4; 8513 return offset; 8514 } else version(Windows) { 8515 if(enableAlpha) { 8516 auto itemsPerLine = width * 4; 8517 // remember, bmps are upside down 8518 auto offset = itemsPerLine * (height - y - 1) + x * 4; 8519 return offset; 8520 } else { 8521 auto itemsPerLine = ((cast(int) width * 3 + 3) / 4) * 4; 8522 // remember, bmps are upside down 8523 auto offset = itemsPerLine * (height - y - 1) + x * 3; 8524 return offset; 8525 } 8526 } else version(OSXCocoa) { 8527 return (y * width + x) * 4 ; //throw new NotYetImplementedException(); 8528 } else version(Emscripten) { 8529 return (y * width + x) * 4 ; //throw new NotYetImplementedException(); 8530 } else static assert(0, "fill in this info for other OSes"); 8531 } 8532 8533 /// 8534 int adjustmentForNextLine() { 8535 version(X11) { 8536 return width * 4; 8537 } else version(Windows) { 8538 // windows bmps are upside down, so the adjustment is actually negative 8539 if(enableAlpha) 8540 return - (cast(int) width * 4); 8541 else 8542 return -((cast(int) width * 3 + 3) / 4) * 4; 8543 } else version(OSXCocoa) { 8544 return width * 4 ; //throw new NotYetImplementedException(); 8545 } else version(Emscripten) { 8546 return width * 4 ; //throw new NotYetImplementedException(); 8547 } else static assert(0, "fill in this info for other OSes"); 8548 } 8549 8550 /// once you have the position of a pixel, use these to get to the proper color 8551 int redByteOffset() { 8552 version(X11) { 8553 return 2; 8554 } else version(Windows) { 8555 return 2; 8556 } else version(OSXCocoa) { 8557 return 2 ; //throw new NotYetImplementedException(); 8558 } else version(Emscripten) { 8559 return 2 ; //throw new NotYetImplementedException(); 8560 } else static assert(0, "fill in this info for other OSes"); 8561 } 8562 8563 /// 8564 int greenByteOffset() { 8565 version(X11) { 8566 return 1; 8567 } else version(Windows) { 8568 return 1; 8569 } else version(OSXCocoa) { 8570 return 1 ; //throw new NotYetImplementedException(); 8571 } else version(Emscripten) { 8572 return 1 ; //throw new NotYetImplementedException(); 8573 } else static assert(0, "fill in this info for other OSes"); 8574 } 8575 8576 /// 8577 int blueByteOffset() { 8578 version(X11) { 8579 return 0; 8580 } else version(Windows) { 8581 return 0; 8582 } else version(OSXCocoa) { 8583 return 0 ; //throw new NotYetImplementedException(); 8584 } else version(Emscripten) { 8585 return 0 ; //throw new NotYetImplementedException(); 8586 } else static assert(0, "fill in this info for other OSes"); 8587 } 8588 8589 /// Only valid if [enableAlpha] is true 8590 int alphaByteOffset() { 8591 version(X11) { 8592 return 3; 8593 } else version(Windows) { 8594 return 3; 8595 } else version(OSXCocoa) { 8596 return 3; //throw new NotYetImplementedException(); 8597 } else version(Emscripten) { 8598 return 3 ; //throw new NotYetImplementedException(); 8599 } else static assert(0, "fill in this info for other OSes"); 8600 } 8601 } 8602 8603 /// 8604 final void putPixel(int x, int y, Color c) { 8605 if(x < 0 || x >= width) 8606 return; 8607 if(y < 0 || y >= height) 8608 return; 8609 8610 impl.setPixel(x, y, c); 8611 } 8612 8613 /// 8614 final Color getPixel(int x, int y) { 8615 if(x < 0 || x >= width) 8616 return Color.transparent; 8617 if(y < 0 || y >= height) 8618 return Color.transparent; 8619 8620 version(OSXCocoa) throw new NotYetImplementedException(); else 8621 return impl.getPixel(x, y); 8622 } 8623 8624 /// 8625 final void opIndexAssign(Color c, int x, int y) { 8626 putPixel(x, y, c); 8627 } 8628 8629 /// 8630 TrueColorImage toTrueColorImage() { 8631 auto tci = new TrueColorImage(width, height); 8632 convertToRgbaBytes(tci.imageData.bytes); 8633 return tci; 8634 } 8635 8636 /// 8637 static Image fromMemoryImage(MemoryImage i, bool enableAlpha = false, bool premultiply = true) { 8638 auto tci = i.getAsTrueColorImage(); 8639 auto img = new Image(tci.width, tci.height, false, enableAlpha); 8640 static if(UsingSimpledisplayX11) 8641 img.premultiply = premultiply; 8642 img.setRgbaBytes(tci.imageData.bytes); 8643 return img; 8644 } 8645 8646 /// this is here for interop with arsd.image. where can be a TrueColorImage's data member 8647 /// if you pass in a buffer, it will put it right there. length must be width*height*4 already 8648 /// if you pass null, it will allocate a new one. 8649 ubyte[] getRgbaBytes(ubyte[] where = null) { 8650 if(where is null) 8651 where = new ubyte[this.width*this.height*4]; 8652 convertToRgbaBytes(where); 8653 return where; 8654 } 8655 8656 /// this is here for interop with arsd.image. from can be a TrueColorImage's data member 8657 void setRgbaBytes(in ubyte[] from ) { 8658 assert(from.length == this.width * this.height * 4); 8659 setFromRgbaBytes(from); 8660 } 8661 8662 // FIXME: make properly cross platform by getting rgba right 8663 8664 /// warning: this is not portable across platforms because the data format can change 8665 ubyte* getDataPointer() { 8666 return impl.rawData; 8667 } 8668 8669 /// for use with getDataPointer 8670 final int bytesPerLine() const pure @safe nothrow { 8671 version(Windows) 8672 return enableAlpha ? (width * 4) : (((cast(int) width * 3 + 3) / 4) * 4); 8673 else version(X11) 8674 return 4 * width; 8675 else version(OSXCocoa) 8676 return 4 * width; 8677 else static assert(0); 8678 } 8679 8680 /// for use with getDataPointer 8681 final int bytesPerPixel() const pure @safe nothrow { 8682 version(Windows) 8683 return enableAlpha ? 4 : 3; 8684 else version(X11) 8685 return 4; 8686 else version(OSXCocoa) 8687 return 4; 8688 else static assert(0); 8689 } 8690 8691 /// 8692 immutable int width; 8693 8694 /// 8695 immutable int height; 8696 8697 /// 8698 immutable bool enableAlpha; 8699 //private: 8700 mixin NativeImageImplementation!() impl; 8701 } 8702 8703 /++ 8704 A convenience function to pop up a window displaying the image. 8705 If you pass a win, it will draw the image in it. Otherwise, it will 8706 create a window with the size of the image and run its event loop, closing 8707 when a key is pressed. 8708 8709 History: 8710 `BlockingMode` parameter added on December 8, 2021. Previously, it would 8711 always block until the application quit which could cause bizarre behavior 8712 inside a more complex application. Now, the default is to block until 8713 this window closes if it is the only event loop running, and otherwise, 8714 not to block at all and just pop up the display window asynchronously. 8715 +/ 8716 void displayImage(Image image, SimpleWindow win = null, BlockingMode bm = BlockingMode.untilWindowCloses | BlockingMode.onlyIfNotNested) { 8717 if(win is null) { 8718 win = new SimpleWindow(image); 8719 { 8720 auto p = win.draw; 8721 p.drawImage(Point(0, 0), image); 8722 } 8723 win.eventLoopWithBlockingMode( 8724 bm, 0, 8725 (KeyEvent ev) { 8726 if (ev.pressed && (ev.key == Key.Escape || ev.key == Key.Space)) win.close(); 8727 } ); 8728 } else { 8729 win.image = image; 8730 } 8731 } 8732 8733 enum FontWeight : int { 8734 dontcare = 0, 8735 thin = 100, 8736 extralight = 200, 8737 light = 300, 8738 regular = 400, 8739 medium = 500, 8740 semibold = 600, 8741 bold = 700, 8742 extrabold = 800, 8743 heavy = 900 8744 } 8745 8746 /++ 8747 Interface with the common functionality for font measurements between [OperatingSystemFont] and [DrawableFont]. 8748 8749 History: 8750 Added October 24, 2022. The methods were already on [OperatingSystemFont] before that. 8751 +/ 8752 interface MeasurableFont { 8753 /++ 8754 Returns true if it is a monospace font, meaning each of the 8755 glyphs (at least the ascii characters) have matching width 8756 and no kerning, so you can determine the display width of some 8757 strings by simply multiplying the string width by [averageWidth]. 8758 8759 (Please note that multiply doesn't $(I actually) work in general, 8760 consider characters like tab and newline, but it does sometimes.) 8761 +/ 8762 bool isMonospace(); 8763 8764 /++ 8765 The average width of glyphs in the font, traditionally equal to the 8766 width of the lowercase x. Can be used to estimate bounding boxes, 8767 especially if the font [isMonospace]. 8768 8769 Given in pixels. 8770 +/ 8771 int averageWidth(); 8772 /++ 8773 The height of the bounding box of a line. 8774 +/ 8775 int height(); 8776 /++ 8777 The maximum ascent of a glyph above the baseline. 8778 8779 Given in pixels. 8780 +/ 8781 int ascent(); 8782 /++ 8783 The maximum descent of a glyph below the baseline. For example, how low the g might go. 8784 8785 Given in pixels. 8786 +/ 8787 int descent(); 8788 /++ 8789 The display width of the given string, and if you provide a window, it will use it to 8790 make the pixel count on screen more accurate too, but this shouldn't generally be necessary. 8791 8792 Given in pixels. 8793 +/ 8794 int stringWidth(scope const(char)[] s, SimpleWindow window = null); 8795 8796 } 8797 8798 // FIXME: i need a font cache and it needs to handle disconnects. 8799 8800 /++ 8801 Represents a font loaded off the operating system or the X server. 8802 8803 8804 While the api here is unified cross platform, the fonts are not necessarily 8805 available, even across machines of the same platform, so be sure to always check 8806 for null (using [isNull]) and have a fallback plan. 8807 8808 When you have a font you like, use [ScreenPainter.setFont] to load it for drawing. 8809 8810 Worst case, a null font will automatically fall back to the default font loaded 8811 for your system. 8812 +/ 8813 class OperatingSystemFont : MeasurableFont { 8814 // FIXME: when the X Connection is lost, these need to be invalidated! 8815 // that means I need to store the original stuff again to reconstruct it too. 8816 8817 version(Emscripten) { 8818 void* font; 8819 } else version(X11) { 8820 XFontStruct* font; 8821 XFontSet fontset; 8822 8823 version(with_xft) { 8824 XftFont* xftFont; 8825 bool isXft; 8826 } 8827 } else version(Windows) { 8828 HFONT font; 8829 int width_; 8830 int height_; 8831 } else version(OSXCocoa) { 8832 NSFont font; 8833 } else static assert(0); 8834 8835 /++ 8836 Constructs the class and immediately calls [load]. 8837 +/ 8838 this(string name, int size = 0, FontWeight weight = FontWeight.dontcare, bool italic = false) { 8839 load(name, size, weight, italic); 8840 } 8841 8842 /++ 8843 Constructs the object, but does nothing. Call one of [load] or [loadDefault] to populate the object. 8844 8845 You can also call the platform-specific [loadXft], [loadCoreX], or [loadWin32] functions if appropriate for you. 8846 8847 History: 8848 Added January 24, 2021. 8849 +/ 8850 this() { 8851 // this space intentionally left blank 8852 } 8853 8854 /++ 8855 Constructs a copy of the given font object. 8856 8857 History: 8858 Added January 7, 2023. 8859 +/ 8860 this(OperatingSystemFont font) { 8861 if(font is null || font.loadedInfo is LoadedInfo.init) 8862 loadDefault(); 8863 else 8864 load(font.loadedInfo.tupleof); 8865 } 8866 8867 /++ 8868 Loads specifically with the Xft library - a freetype font from a fontconfig string. 8869 8870 History: 8871 Added November 13, 2020. 8872 +/ 8873 version(with_xft) 8874 bool loadXft(string name, int size = 0, FontWeight weight = FontWeight.dontcare, bool italic = false) { 8875 unload(); 8876 8877 if(!XftLibrary.attempted) { 8878 XftLibrary.loadDynamicLibrary(); 8879 } 8880 8881 if(!XftLibrary.loadSuccessful) 8882 return false; 8883 8884 auto display = XDisplayConnection.get; 8885 8886 char[256] nameBuffer = void; 8887 int nbp = 0; 8888 8889 void add(in char[] a) { 8890 nameBuffer[nbp .. nbp + a.length] = a[]; 8891 nbp += a.length; 8892 } 8893 add(name); 8894 8895 if(size) { 8896 add(":size="); 8897 add(toInternal!string(size)); 8898 } 8899 if(weight != FontWeight.dontcare && weight != 400) { 8900 if(weight < 400) 8901 add(":style=Light"); 8902 else 8903 add(":style=Bold"); 8904 add(":weight="); 8905 add(weightToString(weight)); 8906 } 8907 if(italic) { 8908 if(weight == FontWeight.dontcare) 8909 add(":style=Italic"); 8910 add(":slant=100"); 8911 } 8912 8913 nameBuffer[nbp] = 0; 8914 8915 this.xftFont = XftFontOpenName( 8916 display, 8917 DefaultScreen(display), 8918 nameBuffer.ptr 8919 ); 8920 8921 this.isXft = true; 8922 8923 if(xftFont !is null) { 8924 isMonospace_ = stringWidth("x") == stringWidth("M"); 8925 ascent_ = xftFont.ascent; 8926 descent_ = xftFont.descent; 8927 } 8928 8929 return !isNull(); 8930 } 8931 8932 /++ 8933 Lists available fonts from the system that match the given pattern, finding names that are suitable for passing to [OperatingSystemFont]'s constructor. 8934 8935 8936 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. 8937 8938 If `pattern` is null, it returns all available font families. 8939 8940 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. 8941 8942 The format of the pattern is platform-specific. 8943 8944 History: 8945 Added May 1, 2021 (dub v9.5) 8946 +/ 8947 static void listFonts(string pattern, bool delegate(in char[] name) handler) { 8948 version(Windows) { 8949 auto hdc = GetDC(null); 8950 scope(exit) ReleaseDC(null, hdc); 8951 LOGFONT logfont; 8952 static extern(Windows) int proc(const LOGFONT* lf, const TEXTMETRIC* tm, DWORD type, LPARAM p) { 8953 auto localHandler = *(cast(typeof(handler)*) p); 8954 return localHandler(lf.lfFaceName[].sliceCString) ? 1 : 0; 8955 } 8956 EnumFontFamiliesEx(hdc, &logfont, &proc, cast(LPARAM) &handler, 0); 8957 } else version(X11) { 8958 //import core.stdc.stdio; 8959 bool done = false; 8960 version(with_xft) { 8961 if(!XftLibrary.attempted) { 8962 XftLibrary.loadDynamicLibrary(); 8963 } 8964 8965 if(!XftLibrary.loadSuccessful) 8966 goto skipXft; 8967 8968 if(!FontConfigLibrary.attempted) 8969 FontConfigLibrary.loadDynamicLibrary(); 8970 if(!FontConfigLibrary.loadSuccessful) 8971 goto skipXft; 8972 8973 { 8974 auto got = XftListFonts(XDisplayConnection.get, 0, null, "family".ptr, "style".ptr, null); 8975 if(got is null) 8976 goto skipXft; 8977 scope(exit) FcFontSetDestroy(got); 8978 8979 auto fontPatterns = got.fonts[0 .. got.nfont]; 8980 foreach(candidate; fontPatterns) { 8981 char* where, whereStyle; 8982 8983 char* pmg = FcNameUnparse(candidate); 8984 8985 //FcPatternGetString(candidate, "family", 0, &where); 8986 //FcPatternGetString(candidate, "style", 0, &whereStyle); 8987 //if(where && whereStyle) { 8988 if(pmg) { 8989 if(!handler(pmg.sliceCString)) 8990 return; 8991 //printf("%s || %s %s\n", pmg, where, whereStyle); 8992 } 8993 } 8994 } 8995 } 8996 8997 skipXft: 8998 8999 if(pattern is null) 9000 pattern = "*"; 9001 9002 int count; 9003 auto coreFontsRaw = XListFonts(XDisplayConnection.get, pattern.toStringz, 10000 /* max return */, &count); 9004 scope(exit) XFreeFontNames(coreFontsRaw); 9005 9006 auto coreFonts = coreFontsRaw[0 .. count]; 9007 9008 foreach(font; coreFonts) { 9009 char[128] tmp; 9010 tmp[0 ..5] = "core:"; 9011 auto cf = font.sliceCString; 9012 if(5 + cf.length > tmp.length) 9013 assert(0, "a font name was too long, sorry i didn't bother implementing a fallback"); 9014 tmp[5 .. 5 + cf.length] = cf; 9015 if(!handler(tmp[0 .. 5 + cf.length])) 9016 return; 9017 } 9018 } 9019 } 9020 9021 /++ 9022 Returns the raw content of the ttf file, if possible. This allows you to use OperatingSystemFont 9023 to look up fonts that you then pass to things like [arsd.ttf.OpenGlLimitedFont] or [arsd.nanovega]. 9024 9025 Returns null if impossible. It is impossible if the loaded font is not a local TTF file or if the 9026 underlying system doesn't support returning the raw bytes. 9027 9028 History: 9029 Added September 10, 2021 (dub v10.3) 9030 +/ 9031 ubyte[] getTtfBytes() { 9032 if(isNull) 9033 return null; 9034 9035 version(Windows) { 9036 auto dc = GetDC(null); 9037 auto orig = SelectObject(dc, font); 9038 9039 scope(exit) { 9040 SelectObject(dc, orig); 9041 ReleaseDC(null, dc); 9042 } 9043 9044 auto res = GetFontData(dc, 0 /* whole file */, 0 /* offset */, null, 0); 9045 if(res == GDI_ERROR) 9046 return null; 9047 9048 ubyte[] buffer = new ubyte[](res); 9049 res = GetFontData(dc, 0 /* whole file */, 0 /* offset */, buffer.ptr, cast(DWORD) buffer.length); 9050 if(res == GDI_ERROR) 9051 return null; // wtf really tbh 9052 9053 return buffer; 9054 } else version(with_xft) { 9055 if(isXft && xftFont) { 9056 if(!FontConfigLibrary.attempted) 9057 FontConfigLibrary.loadDynamicLibrary(); 9058 if(!FontConfigLibrary.loadSuccessful) 9059 return null; 9060 9061 char* file; 9062 if (FcPatternGetString(xftFont.pattern, "file", 0, &file) == 0 /*FcResultMatch*/) { 9063 if (file !is null && file[0]) { 9064 import core.stdc.stdio; 9065 auto fp = fopen(file, "rb"); 9066 if(fp is null) 9067 return null; 9068 scope(exit) 9069 fclose(fp); 9070 fseek(fp, 0, SEEK_END); 9071 ubyte[] buffer = new ubyte[](ftell(fp)); 9072 fseek(fp, 0, SEEK_SET); 9073 9074 auto got = fread(buffer.ptr, 1, buffer.length, fp); 9075 if(got != buffer.length) 9076 return null; 9077 9078 return buffer; 9079 } 9080 } 9081 } 9082 return null; 9083 } else throw new NotYetImplementedException(); 9084 } 9085 9086 // see also: XftLockFace(font) which gives a FT_Face. from /usr/include/X11/Xft/Xft.h line 352 9087 9088 private string weightToString(FontWeight weight) { 9089 with(FontWeight) 9090 final switch(weight) { 9091 case dontcare: return "*"; 9092 case thin: return "extralight"; 9093 case extralight: return "extralight"; 9094 case light: return "light"; 9095 case regular: return "regular"; 9096 case medium: return "medium"; 9097 case semibold: return "demibold"; 9098 case bold: return "bold"; 9099 case extrabold: return "demibold"; 9100 case heavy: return "black"; 9101 } 9102 } 9103 9104 /++ 9105 Loads specifically a Core X font - rendered on the X server without antialiasing. Best performance. 9106 9107 History: 9108 Added November 13, 2020. Before then, this code was integrated in the [load] function. 9109 +/ 9110 version(X11) 9111 bool loadCoreX(string name, int size = 0, FontWeight weight = FontWeight.dontcare, bool italic = false) { 9112 unload(); 9113 9114 string xfontstr; 9115 9116 if(name.length > 3 && name[0 .. 3] == "-*-") { 9117 // this is kinda a disgusting hack but if the user sends an exact 9118 // string I'd like to honor it... 9119 xfontstr = name; 9120 } else { 9121 string weightstr = weightToString(weight); 9122 string sizestr; 9123 if(size == 0) 9124 sizestr = "*"; 9125 else 9126 sizestr = toInternal!string(size); 9127 xfontstr = "-*-"~name~"-"~weightstr~"-"~(italic ? "i" : "r")~"-*-*-"~sizestr~"-*-*-*-*-*-*-*\0"; 9128 } 9129 9130 // writeln(xfontstr); 9131 9132 auto display = XDisplayConnection.get; 9133 9134 font = XLoadQueryFont(display, xfontstr.ptr); 9135 if(font is null) 9136 return false; 9137 9138 char** lol; 9139 int lol2; 9140 char* lol3; 9141 fontset = XCreateFontSet(display, xfontstr.ptr, &lol, &lol2, &lol3); 9142 9143 prepareFontInfo(); 9144 9145 return !isNull(); 9146 } 9147 9148 version(X11) 9149 private void prepareFontInfo() { 9150 if(font !is null) { 9151 isMonospace_ = stringWidth("l") == stringWidth("M"); 9152 ascent_ = font.max_bounds.ascent; 9153 descent_ = font.max_bounds.descent; 9154 } 9155 } 9156 9157 version(OSXCocoa) 9158 private void prepareFontInfo() { 9159 if(font !is null) { 9160 isMonospace_ = font.isFixedPitch; 9161 ascent_ = cast(int) font.ascender; 9162 descent_ = cast(int) - font.descender; 9163 } 9164 } 9165 9166 9167 /++ 9168 Loads a Windows font. You probably want to use [load] instead to be more generic. 9169 9170 History: 9171 Added November 13, 2020. Before then, this code was integrated in the [load] function. 9172 +/ 9173 version(Windows) 9174 bool loadWin32(string name, int size = 0, FontWeight weight = FontWeight.dontcare, bool italic = false, HDC hdc = null) { 9175 unload(); 9176 9177 WCharzBuffer buffer = WCharzBuffer(name); 9178 font = CreateFont(size, 0, 0, 0, cast(int) weight, italic, 0, 0, 0, 0, 0, 0, 0, buffer.ptr); 9179 9180 prepareFontInfo(hdc); 9181 9182 return !isNull(); 9183 } 9184 9185 version(Windows) 9186 void prepareFontInfo(HDC hdc = null) { 9187 if(font is null) 9188 return; 9189 9190 TEXTMETRIC tm; 9191 auto dc = hdc ? hdc : GetDC(null); 9192 auto orig = SelectObject(dc, font); 9193 GetTextMetrics(dc, &tm); 9194 SelectObject(dc, orig); 9195 if(hdc is null) 9196 ReleaseDC(null, dc); 9197 9198 width_ = tm.tmAveCharWidth; 9199 height_ = tm.tmHeight; 9200 ascent_ = tm.tmAscent; 9201 descent_ = tm.tmDescent; 9202 // 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. 9203 isMonospace_ = (tm.tmPitchAndFamily & TMPF_FIXED_PITCH) == 0; 9204 } 9205 9206 9207 /++ 9208 `name` is a font name, but it can also be a more complicated string parsed in an OS-specific way. 9209 9210 On X, you may prefix a name with `core:` to bypass the freetype engine causing this function to forward to [loadCoreX]. Otherwise, 9211 it calls [loadXft] if the library is available. If the library or font is not available on Xft, it falls back on [loadCoreX]. 9212 9213 On Windows, it forwards directly to [loadWin32]. 9214 9215 Params: 9216 name = font name. This is looked up by the operating system and may be interpreted differently across platforms or user machines and their preferences. 9217 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. 9218 weight = approximate boldness, results may vary. 9219 italic = try to get a slanted version of the given font. 9220 9221 History: 9222 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. 9223 +/ 9224 bool load(string name, int size = 0, FontWeight weight = FontWeight.dontcare, bool italic = false) { 9225 this.loadedInfo = LoadedInfo(name, size, weight, italic); 9226 version(X11) { 9227 version(with_xft) { 9228 if(name.length > 5 && name[0 .. 5] == "core:") { 9229 goto core; 9230 } 9231 9232 if(loadXft(name, size, weight, italic)) 9233 return true; 9234 // if xft fails, fallback to core to avoid breaking 9235 // code that already depended on this. 9236 } 9237 9238 core: 9239 9240 if(name.length > 5 && name[0 .. 5] == "core:") { 9241 name = name[5 .. $]; 9242 } 9243 9244 return loadCoreX(name, size, weight, italic); 9245 } else version(Windows) { 9246 return loadWin32(name, size, weight, italic); 9247 } else version(OSXCocoa) { 9248 return loadCocoa(name, size, weight, italic); 9249 } else static assert(0); 9250 } 9251 9252 version(OSXCocoa) 9253 bool loadCocoa(string name, int size, FontWeight weight, bool italic) { 9254 unload(); 9255 9256 font = NSFont.fontWithName(MacString(name).borrow, size); // FIXME: weight and italic? 9257 prepareFontInfo(); 9258 9259 return !isNull(); 9260 } 9261 9262 private struct LoadedInfo { 9263 string name; 9264 int size; 9265 FontWeight weight; 9266 bool italic; 9267 } 9268 private LoadedInfo loadedInfo; 9269 9270 /// 9271 void unload() { 9272 if(isNull()) 9273 return; 9274 9275 version(X11) { 9276 auto display = XDisplayConnection.display; 9277 9278 if(display is null) 9279 return; 9280 9281 version(with_xft) { 9282 if(isXft) { 9283 if(xftFont) 9284 XftFontClose(display, xftFont); 9285 isXft = false; 9286 xftFont = null; 9287 return; 9288 } 9289 } 9290 9291 if(font && font !is ScreenPainterImplementation.defaultfont) 9292 XFreeFont(display, font); 9293 if(fontset && fontset !is ScreenPainterImplementation.defaultfontset) 9294 XFreeFontSet(display, fontset); 9295 9296 font = null; 9297 fontset = null; 9298 } else version(Windows) { 9299 DeleteObject(font); 9300 font = null; 9301 } else version(OSXCocoa) { 9302 font.release(); 9303 font = null; 9304 } else static assert(0); 9305 } 9306 9307 private bool isMonospace_; 9308 9309 /++ 9310 History: 9311 Added January 16, 2021 9312 +/ 9313 bool isMonospace() { 9314 return isMonospace_; 9315 } 9316 9317 /++ 9318 Returns the average width of the font, conventionally defined as the width of the lowercase 'x' character. 9319 9320 History: 9321 Added March 26, 2020 9322 Documented January 16, 2021 9323 +/ 9324 int averageWidth() { 9325 version(X11) { 9326 return stringWidth("x"); 9327 } version(OSXCocoa) { 9328 return stringWidth("x"); 9329 } else version(Windows) 9330 return width_; 9331 else assert(0); 9332 } 9333 9334 /++ 9335 Returns the width of the string as drawn on the specified window, or the default screen if the window is null. 9336 9337 History: 9338 Added January 16, 2021 9339 +/ 9340 int stringWidth(scope const(char)[] s, SimpleWindow window = null) { 9341 // FIXME: what about tab? 9342 if(isNull) 9343 return 0; 9344 9345 version(X11) { 9346 version(with_xft) 9347 if(isXft && xftFont !is null) { 9348 //return xftFont.max_advance_width; 9349 XGlyphInfo extents; 9350 XftTextExtentsUtf8(XDisplayConnection.get, xftFont, s.ptr, cast(int) s.length, &extents); 9351 // writeln(extents); 9352 return extents.xOff; 9353 } 9354 if(font is null) 9355 return 0; 9356 else if(fontset) { 9357 XRectangle rect; 9358 Xutf8TextExtents(fontset, s.ptr, cast(int) s.length, null, &rect); 9359 9360 return rect.width; 9361 } else { 9362 return XTextWidth(font, s.ptr, cast(int) s.length); 9363 } 9364 } else version(Windows) { 9365 WCharzBuffer buffer = WCharzBuffer(s); 9366 9367 return stringWidth(buffer.slice, window); 9368 } else version(OSXCocoa) { 9369 /+ 9370 int charCount = [string length]; 9371 CGGlyph glyphs[charCount]; 9372 CGRect rects[charCount]; 9373 9374 CTFontGetGlyphsForCharacters(theCTFont, (const unichar*)[string cStringUsingEncoding:NSUnicodeStringEncoding], glyphs, charCount); 9375 CTFontGetBoundingRectsForGlyphs(theCTFont, kCTFontDefaultOrientation, glyphs, rects, charCount); 9376 9377 int totalwidth = 0, maxheight = 0; 9378 for (int i=0; i < charCount; i++) 9379 { 9380 totalwidth += rects[i].size.width; 9381 maxheight = maxheight < rects[i].size.height ? rects[i].size.height : maxheight; 9382 } 9383 9384 dim = CGSizeMake(totalwidth, maxheight); 9385 +/ 9386 9387 return 16; // FIXME 9388 } 9389 else assert(0); 9390 } 9391 9392 version(Windows) 9393 /// ditto 9394 int stringWidth(scope const(wchar)[] s, SimpleWindow window = null) { 9395 if(isNull) 9396 return 0; 9397 version(Windows) { 9398 SIZE size; 9399 9400 prepareContext(window); 9401 scope(exit) releaseContext(); 9402 9403 GetTextExtentPoint32W(dc, s.ptr, cast(int) s.length, &size); 9404 9405 return size.cx; 9406 } else { 9407 // std.conv can do this easily but it is slow to import and i don't think it is worth it 9408 static assert(0, "not implemented yet"); 9409 //return stringWidth(s, window); 9410 } 9411 } 9412 9413 private { 9414 int prepRefcount; 9415 9416 version(Windows) { 9417 HDC dc; 9418 HANDLE orig; 9419 HWND hwnd; 9420 } 9421 } 9422 /++ 9423 [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. 9424 9425 History: 9426 Added January 23, 2021 9427 +/ 9428 void prepareContext(SimpleWindow window = null) { 9429 prepRefcount++; 9430 if(prepRefcount == 1) { 9431 version(Windows) { 9432 hwnd = window is null ? null : window.impl.hwnd; 9433 dc = GetDC(hwnd); 9434 orig = SelectObject(dc, font); 9435 } 9436 } 9437 } 9438 /// ditto 9439 void releaseContext() { 9440 prepRefcount--; 9441 if(prepRefcount == 0) { 9442 version(Windows) { 9443 SelectObject(dc, orig); 9444 ReleaseDC(hwnd, dc); 9445 hwnd = null; 9446 dc = null; 9447 orig = null; 9448 } 9449 } 9450 } 9451 9452 /+ 9453 FIXME: I think I need advance and kerning pair 9454 9455 int advance(dchar from, dchar to) { } // use dchar.init for first item in string 9456 +/ 9457 9458 /++ 9459 Returns the height of the font. 9460 9461 History: 9462 Added March 26, 2020 9463 Documented January 16, 2021 9464 +/ 9465 int height() { 9466 version(X11) { 9467 version(with_xft) 9468 if(isXft && xftFont !is null) { 9469 return xftFont.ascent + xftFont.descent; // i don't use height here because it doesn't include the baseline pixel 9470 } 9471 if(font is null) 9472 return 0; 9473 return font.max_bounds.ascent + font.max_bounds.descent; 9474 } else version(Windows) { 9475 return height_; 9476 } else version(OSXCocoa) { 9477 if(font is null) 9478 return 0; 9479 return cast(int) (font.ascender + font.descender + 0.9 /* to round up */); // font.capHeight 9480 } 9481 else assert(0); 9482 } 9483 9484 private int ascent_; 9485 private int descent_; 9486 9487 /++ 9488 Max ascent above the baseline. 9489 9490 History: 9491 Added January 22, 2021 9492 +/ 9493 int ascent() { 9494 return ascent_; 9495 } 9496 9497 /++ 9498 Max descent below the baseline. 9499 9500 History: 9501 Added January 22, 2021 9502 +/ 9503 int descent() { 9504 return descent_; 9505 } 9506 9507 /++ 9508 Loads the default font used by [ScreenPainter] if none others are loaded. 9509 9510 Returns: 9511 This method mutates the `this` object, but then returns `this` for 9512 easy chaining like: 9513 9514 --- 9515 auto font = foo.isNull ? foo : foo.loadDefault 9516 --- 9517 9518 History: 9519 Added previously, but left unimplemented until January 24, 2021. 9520 +/ 9521 OperatingSystemFont loadDefault() { 9522 unload(); 9523 9524 loadedInfo = LoadedInfo.init; 9525 9526 version(X11) { 9527 // another option would be https://tronche.com/gui/x/xlib/graphics/font-metrics/XQueryFont.html 9528 // but meh since sdpy does its own thing, this should be ok too 9529 9530 ScreenPainterImplementation.ensureDefaultFontLoaded(); 9531 this.font = ScreenPainterImplementation.defaultfont; 9532 this.fontset = ScreenPainterImplementation.defaultfontset; 9533 9534 prepareFontInfo(); 9535 return this; 9536 } else version(Windows) { 9537 ScreenPainterImplementation.ensureDefaultFontLoaded(); 9538 this.font = ScreenPainterImplementation.defaultGuiFont; 9539 9540 prepareFontInfo(); 9541 return this; 9542 } else version(OSXCocoa) { 9543 this.font = NSFont.systemFontOfSize(15); 9544 9545 prepareFontInfo(); 9546 9547 // import std.stdio; writeln("Load default: ", this.height()); 9548 return this; 9549 } else throw new NotYetImplementedException(); 9550 } 9551 9552 /// 9553 bool isNull() { 9554 version(with_xft) 9555 if(isXft) 9556 return xftFont is null; 9557 return font is null; 9558 } 9559 9560 /* Metrics */ 9561 /+ 9562 GetABCWidth 9563 GetKerningPairs 9564 9565 if I do it right, I can size it all here, and match 9566 what happens when I draw the full string with the OS functions. 9567 9568 subclasses might do the same thing while getting the glyphs on images 9569 struct GlyphInfo { 9570 int glyph; 9571 9572 size_t stringIdxStart; 9573 size_t stringIdxEnd; 9574 9575 Rectangle boundingBox; 9576 } 9577 GlyphInfo[] getCharBoxes() { 9578 // XftTextExtentsUtf8 9579 return null; 9580 9581 } 9582 +/ 9583 9584 ~this() { 9585 if(!thisIsGuiThread) return; // FIXME: leaks if multithreaded gc 9586 unload(); 9587 } 9588 } 9589 9590 version(Windows) 9591 private string sliceCString(const(wchar)[] w) { 9592 return makeUtf8StringFromWindowsString(cast(wchar*) w.ptr); 9593 } 9594 9595 private inout(char)[] sliceCString(inout(char)* s) { 9596 import core.stdc.string; 9597 auto len = strlen(s); 9598 return s[0 .. len]; 9599 } 9600 9601 version(OSXCocoa) 9602 alias PaintingHandle = NSObject; 9603 else 9604 alias PaintingHandle = NativeWindowHandle; 9605 9606 /** 9607 The 2D drawing proxy. You acquire one of these with [SimpleWindow.draw] rather 9608 than constructing it directly. Then, it is reference counted so you can pass it 9609 at around and when the last ref goes out of scope, the buffered drawing activities 9610 are all carried out. 9611 9612 9613 Most functions use the outlineColor instead of taking a color themselves. 9614 ScreenPainter is reference counted and draws its buffer to the screen when its 9615 final reference goes out of scope. 9616 */ 9617 struct ScreenPainter { 9618 CapableOfBeingDrawnUpon window; 9619 this(CapableOfBeingDrawnUpon window, PaintingHandle handle, bool manualInvalidations) { 9620 this.window = window; 9621 if(window.closed) 9622 return; // null painter is now allowed so no need to throw anymore, this likely happens at the end of a program anyway 9623 //currentClipRectangle = arsd.color.Rectangle(0, 0, window.width, window.height); 9624 currentClipRectangle = arsd.color.Rectangle(short.min, short.min, short.max, short.max); 9625 if(window.activeScreenPainter !is null) { 9626 impl = window.activeScreenPainter; 9627 if(impl.referenceCount == 0) { 9628 impl.window = window; 9629 impl.create(handle); 9630 } 9631 impl.manualInvalidations = manualInvalidations; 9632 impl.referenceCount++; 9633 // writeln("refcount ++ ", impl.referenceCount); 9634 } else { 9635 impl = new ScreenPainterImplementation; 9636 impl.window = window; 9637 impl.create(handle); 9638 impl.referenceCount = 1; 9639 impl.manualInvalidations = manualInvalidations; 9640 window.activeScreenPainter = impl; 9641 // writeln("constructed"); 9642 } 9643 9644 copyActiveOriginals(); 9645 } 9646 9647 /++ 9648 EXPERIMENTAL. subject to change. 9649 9650 When you draw a cursor, you can draw this to notify your window of where it is, 9651 for IME systems to use. 9652 +/ 9653 void notifyCursorPosition(int x, int y, int width, int height) { 9654 if(auto w = cast(SimpleWindow) window) { 9655 w.setIMEPopupLocation(x + _originX + width, y + _originY + height); 9656 } 9657 } 9658 9659 /++ 9660 If you are using manual invalidations, this informs the 9661 window system that a section needs to be redrawn. 9662 9663 If you didn't opt into manual invalidation, you don't 9664 have to call this. 9665 9666 History: 9667 Added December 30, 2021 (dub v10.5) 9668 +/ 9669 void invalidateRect(Rectangle rect) { 9670 if(impl is null) return; 9671 9672 // transform(rect) 9673 rect.left += _originX; 9674 rect.right += _originX; 9675 rect.top += _originY; 9676 rect.bottom += _originY; 9677 9678 impl.invalidateRect(rect); 9679 } 9680 9681 private Pen originalPen; 9682 private Color originalFillColor; 9683 private arsd.color.Rectangle originalClipRectangle; 9684 private OperatingSystemFont originalFont; 9685 void copyActiveOriginals() { 9686 if(impl is null) return; 9687 originalPen = impl._activePen; 9688 originalFillColor = impl._fillColor; 9689 originalClipRectangle = impl._clipRectangle; 9690 version(OSXCocoa) {} else 9691 originalFont = impl._activeFont; 9692 } 9693 9694 ~this() { 9695 if(impl is null) return; 9696 impl.referenceCount--; 9697 //writeln("refcount -- ", impl.referenceCount); 9698 if(impl.referenceCount == 0) { 9699 // writeln("destructed"); 9700 impl.dispose(); 9701 *window.activeScreenPainter = ScreenPainterImplementation.init; 9702 // writeln("paint finished"); 9703 } else { 9704 // there is still an active reference, reset stuff so the 9705 // next user doesn't get weirdness via the reference 9706 this.rasterOp = RasterOp.normal; 9707 pen = originalPen; 9708 fillColor = originalFillColor; 9709 if(originalFont) 9710 setFont(originalFont); 9711 impl.setClipRectangle(originalClipRectangle.left, originalClipRectangle.top, originalClipRectangle.width, originalClipRectangle.height); 9712 } 9713 } 9714 9715 this(this) { 9716 if(impl is null) return; 9717 impl.referenceCount++; 9718 //writeln("refcount ++ ", impl.referenceCount); 9719 9720 copyActiveOriginals(); 9721 } 9722 9723 private int _originX; 9724 private int _originY; 9725 @property int originX() { return _originX; } 9726 @property int originY() { return _originY; } 9727 @property int originX(int a) { 9728 _originX = a; 9729 return _originX; 9730 } 9731 @property int originY(int a) { 9732 _originY = a; 9733 return _originY; 9734 } 9735 arsd.color.Rectangle currentClipRectangle; // set BEFORE doing any transformations 9736 private void transform(ref Point p) { 9737 if(impl is null) return; 9738 p.x += _originX; 9739 p.y += _originY; 9740 } 9741 9742 // this needs to be checked BEFORE the originX/Y transformation 9743 private bool isClipped(Point p) { 9744 return !currentClipRectangle.contains(p); 9745 } 9746 private bool isClipped(Point p, int width, int height) { 9747 return !currentClipRectangle.overlaps(arsd.color.Rectangle(p, Size(width + 1, height + 1))); 9748 } 9749 private bool isClipped(Point p, Size s) { 9750 return !currentClipRectangle.overlaps(arsd.color.Rectangle(p, Size(s.width + 1, s.height + 1))); 9751 } 9752 private bool isClipped(Point p, Point p2) { 9753 // need to ensure the end points are actually included inside, so the +1 does that 9754 return !currentClipRectangle.overlaps(arsd.color.Rectangle(p, p2 + Point(1, 1))); 9755 } 9756 9757 9758 /++ 9759 Sets the clipping region for drawing. If width == 0 && height == 0, disabled clipping. 9760 9761 Returns: 9762 The old clip rectangle. 9763 9764 History: 9765 Return value was `void` prior to May 10, 2021. 9766 9767 +/ 9768 arsd.color.Rectangle setClipRectangle(Point pt, int width, int height) { 9769 if(impl is null) return currentClipRectangle; 9770 if(pt == currentClipRectangle.upperLeft && width == currentClipRectangle.width && height == currentClipRectangle.height) 9771 return currentClipRectangle; // no need to do anything 9772 auto old = currentClipRectangle; 9773 currentClipRectangle = arsd.color.Rectangle(pt, Size(width, height)); 9774 transform(pt); 9775 9776 impl.setClipRectangle(pt.x, pt.y, width, height); 9777 9778 return old; 9779 } 9780 9781 /// ditto 9782 arsd.color.Rectangle setClipRectangle(arsd.color.Rectangle rect) { 9783 if(impl is null) return currentClipRectangle; 9784 return setClipRectangle(rect.upperLeft, rect.width, rect.height); 9785 } 9786 9787 /// 9788 void setFont(OperatingSystemFont font) { 9789 if(impl is null) return; 9790 impl.setFont(font); 9791 } 9792 9793 /// 9794 int fontHeight() { 9795 if(impl is null) return 0; 9796 return impl.fontHeight(); 9797 } 9798 9799 private Pen activePen; 9800 9801 /// 9802 @property void pen(Pen p) { 9803 if(impl is null) return; 9804 activePen = p; 9805 impl.pen(p); 9806 } 9807 9808 /// 9809 @scriptable 9810 @property void outlineColor(Color c) { 9811 if(impl is null) return; 9812 if(activePen.color == c) 9813 return; 9814 activePen.color = c; 9815 impl.pen(activePen); 9816 } 9817 9818 /// 9819 @scriptable 9820 @property void fillColor(Color c) { 9821 if(impl is null) return; 9822 impl.fillColor(c); 9823 } 9824 9825 /// 9826 @property void rasterOp(RasterOp op) { 9827 if(impl is null) return; 9828 impl.rasterOp(op); 9829 } 9830 9831 9832 void updateDisplay() { 9833 // FIXME this should do what the dtor does 9834 } 9835 9836 /// 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) 9837 void scrollArea(Point upperLeft, int width, int height, int dx, int dy) { 9838 if(impl is null) return; 9839 if(isClipped(upperLeft, width, height)) return; 9840 transform(upperLeft); 9841 version(Windows) { 9842 // http://msdn.microsoft.com/en-us/library/windows/desktop/bb787589%28v=vs.85%29.aspx 9843 RECT scroll = RECT(upperLeft.x, upperLeft.y, upperLeft.x + width, upperLeft.y + height); 9844 RECT clip = scroll; 9845 RECT uncovered; 9846 HRGN hrgn; 9847 if(!ScrollDC(impl.hdc, -dx, -dy, &scroll, &clip, hrgn, &uncovered)) 9848 throw new WindowsApiException("ScrollDC", GetLastError()); 9849 9850 } else version(X11) { 9851 // FIXME: clip stuff outside this rectangle 9852 XCopyArea(impl.display, impl.d, impl.d, impl.gc, upperLeft.x, upperLeft.y, width, height, upperLeft.x - dx, upperLeft.y - dy); 9853 } else version(OSXCocoa) { 9854 throw new NotYetImplementedException(); 9855 } else static assert(0); 9856 } 9857 9858 /// 9859 void clear(Color color = Color.white()) { 9860 if(impl is null) return; 9861 fillColor = color; 9862 outlineColor = color; 9863 drawRectangle(Point(0, 0), window.width, window.height); 9864 } 9865 9866 /++ 9867 Draws a pixmap (represented by the [Sprite] class) on the drawable. 9868 9869 Params: 9870 upperLeft = point on the window where the upper left corner of the image will be drawn 9871 imageUpperLeft = point on the image to start the slice to draw 9872 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. 9873 History: 9874 The `imageUpperLeft` and `sliceSize` parameters were added on March 11, 2021 (dub v9.3.0) 9875 +/ 9876 void drawPixmap(Sprite s, Point upperLeft, Point imageUpperLeft = Point(0, 0), Size sliceSize = Size(0, 0)) { 9877 if(impl is null) return; 9878 if(isClipped(upperLeft, s.width, s.height)) return; 9879 transform(upperLeft); 9880 impl.drawPixmap(s, upperLeft.x, upperLeft.y, imageUpperLeft.x, imageUpperLeft.y, sliceSize.width, sliceSize.height); 9881 } 9882 9883 /// 9884 void drawImage(Point upperLeft, Image i, Point upperLeftOfImage = Point(0, 0), int w = 0, int h = 0) { 9885 if(impl is null) return; 9886 //if(isClipped(upperLeft, w, h)) return; // FIXME 9887 transform(upperLeft); 9888 if(w == 0 || w > i.width) 9889 w = i.width; 9890 if(h == 0 || h > i.height) 9891 h = i.height; 9892 if(upperLeftOfImage.x < 0) 9893 upperLeftOfImage.x = 0; 9894 if(upperLeftOfImage.y < 0) 9895 upperLeftOfImage.y = 0; 9896 9897 impl.drawImage(upperLeft.x, upperLeft.y, i, upperLeftOfImage.x, upperLeftOfImage.y, w, h); 9898 } 9899 9900 /// 9901 Size textSize(in char[] text) { 9902 if(impl is null) return Size(0, 0); 9903 return impl.textSize(text); 9904 } 9905 9906 /++ 9907 Draws a string in the window with the set font (see [setFont] to change it). 9908 9909 Params: 9910 upperLeft = the upper left point of the bounding box of the text 9911 text = the string to draw 9912 lowerRight = the lower right point of the bounding box of the text. If 0, 0, there is no lower right bound. 9913 alignment = A [arsd.docs.general_concepts#bitflags|combination] of [TextAlignment] flags 9914 +/ 9915 @scriptable 9916 void drawText(Point upperLeft, in char[] text, Point lowerRight = Point(0, 0), uint alignment = 0) { 9917 if(impl is null) return; 9918 if(lowerRight.x != 0 || lowerRight.y != 0) { 9919 if(isClipped(upperLeft, lowerRight)) return; 9920 transform(lowerRight); 9921 } else { 9922 if(isClipped(upperLeft, textSize(text))) return; 9923 } 9924 transform(upperLeft); 9925 impl.drawText(upperLeft.x, upperLeft.y, lowerRight.x, lowerRight.y, text, alignment); 9926 } 9927 9928 /++ 9929 Draws text using a custom font. 9930 9931 This is still MAJOR work in progress. 9932 9933 Creating a [DrawableFont] can be tricky and require additional dependencies. 9934 +/ 9935 void drawText(DrawableFont font, Point upperLeft, in char[] text) { 9936 if(impl is null) return; 9937 if(isClipped(upperLeft, Point(int.max, int.max))) return; 9938 transform(upperLeft); 9939 font.drawString(this, upperLeft, text); 9940 } 9941 9942 version(Windows) 9943 void drawText(Point upperLeft, scope const(wchar)[] text) { 9944 if(impl is null) return; 9945 if(isClipped(upperLeft, Point(int.max, int.max))) return; 9946 transform(upperLeft); 9947 9948 if(text.length && text[$-1] == '\n') 9949 text = text[0 .. $-1]; // tailing newlines are weird on windows... 9950 9951 TextOutW(impl.hdc, upperLeft.x, upperLeft.y, text.ptr, cast(int) text.length); 9952 } 9953 9954 static struct TextDrawingContext { 9955 Point boundingBoxUpperLeft; 9956 Point boundingBoxLowerRight; 9957 9958 Point currentLocation; 9959 9960 Point lastDrewUpperLeft; 9961 Point lastDrewLowerRight; 9962 9963 // how do i do right aligned rich text? 9964 // i kinda want to do a pre-made drawing then right align 9965 // draw the whole block. 9966 // 9967 // That's exactly the diff: inline vs block stuff. 9968 9969 // I need to get coordinates of an inline section out too, 9970 // not just a bounding box, but a series of bounding boxes 9971 // should be ok. Consider what's needed to detect a click 9972 // on a link in the middle of a paragraph breaking a line. 9973 // 9974 // Generally, we should be able to get the rectangles of 9975 // any portion we draw. 9976 // 9977 // It also needs to tell what text is left if it overflows 9978 // out of the box, so we can do stuff like float images around 9979 // it. It should not attempt to draw a letter that would be 9980 // clipped. 9981 // 9982 // I might also turn off word wrap stuff. 9983 } 9984 9985 void drawText(TextDrawingContext context, in char[] text, uint alignment = 0) { 9986 if(impl is null) return; 9987 // FIXME 9988 } 9989 9990 /// Drawing an individual pixel is slow. Avoid it if possible. 9991 void drawPixel(Point where) { 9992 if(impl is null) return; 9993 if(isClipped(where)) return; 9994 transform(where); 9995 impl.drawPixel(where.x, where.y); 9996 } 9997 9998 9999 /// Draws a pen using the current pen / outlineColor 10000 @scriptable 10001 void drawLine(Point starting, Point ending) { 10002 if(impl is null) return; 10003 if(isClipped(starting, ending)) return; 10004 transform(starting); 10005 transform(ending); 10006 impl.drawLine(starting.x, starting.y, ending.x, ending.y); 10007 } 10008 10009 /// Draws a rectangle using the current pen/outline color for the border and brush/fill color for the insides 10010 /// The outer lines, inclusive of x = 0, y = 0, x = width - 1, and y = height - 1 are drawn with the outlineColor 10011 /// The rest of the pixels are drawn with the fillColor. If fillColor is transparent, those pixels are not drawn. 10012 @scriptable 10013 void drawRectangle(Point upperLeft, int width, int height) { 10014 if(impl is null) return; 10015 if(isClipped(upperLeft, width, height)) return; 10016 transform(upperLeft); 10017 impl.drawRectangle(upperLeft.x, upperLeft.y, width, height); 10018 } 10019 10020 /// ditto 10021 void drawRectangle(Point upperLeft, Size size) { 10022 if(impl is null) return; 10023 if(isClipped(upperLeft, size.width, size.height)) return; 10024 transform(upperLeft); 10025 impl.drawRectangle(upperLeft.x, upperLeft.y, size.width, size.height); 10026 } 10027 10028 /// ditto 10029 void drawRectangle(Point upperLeft, Point lowerRightInclusive) { 10030 if(impl is null) return; 10031 if(isClipped(upperLeft, lowerRightInclusive + Point(1, 1))) return; 10032 transform(upperLeft); 10033 transform(lowerRightInclusive); 10034 impl.drawRectangle(upperLeft.x, upperLeft.y, 10035 lowerRightInclusive.x - upperLeft.x + 1, lowerRightInclusive.y - upperLeft.y + 1); 10036 } 10037 10038 // overload added on May 12, 2021 10039 /// ditto 10040 void drawRectangle(Rectangle rect) { 10041 drawRectangle(rect.upperLeft, rect.size); 10042 } 10043 10044 /// Arguments are the points of the bounding rectangle 10045 void drawEllipse(Point upperLeft, Point lowerRight) { 10046 if(impl is null) return; 10047 if(isClipped(upperLeft, lowerRight)) return; 10048 transform(upperLeft); 10049 transform(lowerRight); 10050 impl.drawEllipse(upperLeft.x, upperLeft.y, lowerRight.x, lowerRight.y); 10051 } 10052 10053 /++ 10054 Draws an arc inside the bounding box given by `upperLeft`, `width`, and `height`, from the angle (`start` / 64) degrees for (`length` / 64) degrees of rotation. 10055 10056 10057 If `length` is positive, it travels counter-clockwise from `start`. If negative, it goes clockwise. `start` == 0 at the three o'clock position of the bounding box - the center of the line at the right-hand side of the screen. 10058 10059 The arc is outlined with the current pen and filled with the current fill. On Windows, the line segments back to the middle are also drawn unless you have a full length ellipse. 10060 10061 Bugs: 10062 They still don't exactly match in outlining the arc with straight lines (Windows does, Linux doesn't for now). 10063 10064 The arc outline on Linux sometimes goes over the target. 10065 10066 The fill on Windows sometimes stops short. 10067 10068 History: 10069 This function was broken af, totally inconsistent on platforms until September 24, 2021. 10070 10071 The interpretation of the final argument was incorrectly documented and implemented until August 2, 2024. 10072 +/ 10073 void drawArc(Point upperLeft, int width, int height, int start, int length) { 10074 if(impl is null) return; 10075 // FIXME: not actually implemented 10076 if(isClipped(upperLeft, width, height)) return; 10077 transform(upperLeft); 10078 impl.drawArc(upperLeft.x, upperLeft.y, width, height, start, length); 10079 } 10080 10081 /// this function draws a circle with the drawEllipse() function above, it requires the upper left point and the radius 10082 void drawCircle(Point upperLeft, int diameter) { 10083 drawEllipse(upperLeft, Point(upperLeft.x + diameter, upperLeft.y + diameter)); 10084 } 10085 10086 /++ 10087 Draws a rectangle with rounded corners. It is outlined with the current foreground pen and filled with the current background brush. 10088 10089 10090 Bugs: 10091 Not implemented on Mac; it will instead draw a non-rounded rectangle for now. 10092 10093 History: 10094 Added August 3, 2024 10095 +/ 10096 void drawRectangleRounded(Rectangle rect, int borderRadius) { 10097 drawRectangleRounded(rect.upperLeft, rect.lowerRight, borderRadius); 10098 } 10099 10100 /// ditto 10101 void drawRectangleRounded(Point upperLeft, Size size, int borderRadius) { 10102 drawRectangleRounded(upperLeft, upperLeft + Point(size.width, size.height), borderRadius); 10103 } 10104 10105 /// ditto 10106 void drawRectangleRounded(Point upperLeft, Point lowerRight, int borderRadius) { 10107 if(borderRadius <= 0) { 10108 drawRectangle(upperLeft, lowerRight); 10109 return; 10110 } 10111 10112 transform(upperLeft); 10113 transform(lowerRight); 10114 10115 impl.drawRectangleRounded(upperLeft, lowerRight, borderRadius); 10116 } 10117 10118 /// . 10119 void drawPolygon(Point[] vertexes) { 10120 if(impl is null) return; 10121 assert(vertexes.length); 10122 int minX = int.max, minY = int.max, maxX = int.min, maxY = int.min; 10123 foreach(ref vertex; vertexes) { 10124 if(vertex.x < minX) 10125 minX = vertex.x; 10126 if(vertex.y < minY) 10127 minY = vertex.y; 10128 if(vertex.x > maxX) 10129 maxX = vertex.x; 10130 if(vertex.y > maxY) 10131 maxY = vertex.y; 10132 transform(vertex); 10133 } 10134 if(isClipped(Point(minX, maxY), Point(maxX + 1, maxY + 1))) return; 10135 impl.drawPolygon(vertexes); 10136 } 10137 10138 /// ditto 10139 void drawPolygon(Point[] vertexes...) { 10140 if(impl is null) return; 10141 drawPolygon(vertexes); 10142 } 10143 10144 10145 // and do a draw/fill in a single call maybe. Windows can do it... but X can't, though it could do two calls. 10146 10147 //mixin NativeScreenPainterImplementation!() impl; 10148 10149 10150 // HACK: if I mixin the impl directly, it won't let me override the copy 10151 // constructor! The linker complains about there being multiple definitions. 10152 // I'll make the best of it and reference count it though. 10153 ScreenPainterImplementation* impl; 10154 } 10155 10156 // HACK: I need a pointer to the implementation so it's separate 10157 struct ScreenPainterImplementation { 10158 CapableOfBeingDrawnUpon window; 10159 int referenceCount; 10160 mixin NativeScreenPainterImplementation!(); 10161 } 10162 10163 // FIXME: i haven't actually tested the sprite class on MS Windows 10164 10165 /** 10166 Sprites are optimized for fast drawing on the screen, but slow for direct pixel 10167 access. They are best for drawing a relatively unchanging image repeatedly on the screen. 10168 10169 10170 On X11, this corresponds to an `XPixmap`. On Windows, it still uses a bitmap, 10171 though I'm not sure that's ideal and the implementation might change. 10172 10173 You create one by giving a window and an image. It optimizes for that window, 10174 and copies the image into it to use as the initial picture. Creating a sprite 10175 can be quite slow (especially over a network connection) so you should do it 10176 as little as possible and just hold on to your sprite handles after making them. 10177 simpledisplay does try to do its best though, using the XSHM extension if available, 10178 but you should still write your code as if it will always be slow. 10179 10180 Then you can use `sprite.drawAt(painter, point);` to draw it, which should be 10181 a fast operation - much faster than drawing the Image itself every time. 10182 10183 `Sprite` represents a scarce resource which should be freed when you 10184 are done with it. Use the `dispose` method to do this. Do not use a `Sprite` 10185 after it has been disposed. If you are unsure about this, don't take chances, 10186 just let the garbage collector do it for you. But ideally, you can manage its 10187 lifetime more efficiently. 10188 10189 $(NOTE `Sprite`, like the rest of simpledisplay's `ScreenPainter`, does not 10190 support alpha blending in its drawing at this time. That might change in the 10191 future, but if you need alpha blending right now, use OpenGL instead. See 10192 `gamehelpers.d` for a similar class to `Sprite` that uses OpenGL: `OpenGlTexture`.) 10193 10194 Update: on April 23, 2021, I finally added alpha blending support. You must opt 10195 in by setting the enableAlpha = true in the constructor. 10196 */ 10197 class Sprite : CapableOfBeingDrawnUpon { 10198 10199 /// 10200 ScreenPainter draw() { 10201 return ScreenPainter(this, handle, false); 10202 } 10203 10204 /++ 10205 Copies the sprite's current state into a [TrueColorImage]. 10206 10207 Be warned: this can be a very slow operation 10208 10209 History: 10210 Actually implemented on March 14, 2021 10211 +/ 10212 TrueColorImage takeScreenshot() { 10213 return trueColorImageFromNativeHandle(handle, width, height); 10214 } 10215 10216 void delegate() paintingFinishedDg() { return null; } 10217 bool closed() { return false; } 10218 ScreenPainterImplementation* activeScreenPainter_; 10219 protected ScreenPainterImplementation* activeScreenPainter() { return activeScreenPainter_; } 10220 protected void activeScreenPainter(ScreenPainterImplementation* i) { activeScreenPainter_ = i; } 10221 10222 version(Windows) 10223 private ubyte* rawData; 10224 // FIXME: sprites are lost when disconnecting from X! We need some way to invalidate them... 10225 // ditto on the XPicture stuff 10226 10227 version(X11) { 10228 private static XRenderPictFormat* RGB24; 10229 private static XRenderPictFormat* ARGB32; 10230 10231 private Picture xrenderPicture; 10232 } 10233 10234 version(X11) 10235 private static void requireXRender() { 10236 if(!XRenderLibrary.loadAttempted) { 10237 XRenderLibrary.loadDynamicLibrary(); 10238 } 10239 10240 if(!XRenderLibrary.loadSuccessful) 10241 throw new Exception("XRender library load failure"); 10242 10243 auto display = XDisplayConnection.get; 10244 10245 // FIXME: if we migrate X displays, these need to be changed 10246 if(RGB24 is null) 10247 RGB24 = XRenderFindStandardFormat(display, PictStandardRGB24); 10248 if(ARGB32 is null) 10249 ARGB32 = XRenderFindStandardFormat(display, PictStandardARGB32); 10250 } 10251 10252 protected this() {} 10253 10254 this(SimpleWindow win, int width, int height, bool enableAlpha = false) { 10255 this._width = width; 10256 this._height = height; 10257 this.enableAlpha = enableAlpha; 10258 10259 version(X11) { 10260 auto display = XDisplayConnection.get(); 10261 10262 if(enableAlpha) { 10263 requireXRender(); 10264 } 10265 10266 handle = XCreatePixmap(display, cast(Drawable) win.window, width, height, enableAlpha ? 32 : DefaultDepthOfDisplay(display)); 10267 10268 if(enableAlpha) { 10269 XRenderPictureAttributes attrs; 10270 xrenderPicture = XRenderCreatePicture(display, handle, ARGB32, 0, &attrs); 10271 } 10272 } else version(Windows) { 10273 version(CRuntime_DigitalMars) { 10274 //if(enableAlpha) 10275 //throw new Exception("Alpha support not available, try recompiling with -m32mscoff"); 10276 } 10277 10278 BITMAPINFO infoheader; 10279 infoheader.bmiHeader.biSize = infoheader.bmiHeader.sizeof; 10280 infoheader.bmiHeader.biWidth = width; 10281 infoheader.bmiHeader.biHeight = height; 10282 infoheader.bmiHeader.biPlanes = 1; 10283 infoheader.bmiHeader.biBitCount = enableAlpha ? 32 : 24; 10284 infoheader.bmiHeader.biCompression = BI_RGB; 10285 10286 // FIXME: this should prolly be a device dependent bitmap... 10287 handle = CreateDIBSection( 10288 null, 10289 &infoheader, 10290 DIB_RGB_COLORS, 10291 cast(void**) &rawData, 10292 null, 10293 0); 10294 10295 if(handle is null) 10296 throw new WindowsApiException("couldn't create pixmap", GetLastError()); 10297 } 10298 } 10299 10300 /// Makes a sprite based on the image with the initial contents from the Image 10301 this(SimpleWindow win, Image i) { 10302 this(win, i.width, i.height, i.enableAlpha); 10303 10304 version(X11) { 10305 auto display = XDisplayConnection.get(); 10306 auto gc = XCreateGC(display, this.handle, 0, null); 10307 scope(exit) XFreeGC(display, gc); 10308 if(i.usingXshm) 10309 XShmPutImage(display, cast(Drawable) handle, gc, i.handle, 0, 0, 0, 0, i.width, i.height, false); 10310 else 10311 XPutImage(display, cast(Drawable) handle, gc, i.handle, 0, 0, 0, 0, i.width, i.height); 10312 } else version(Windows) { 10313 auto itemsPerLine = enableAlpha ? (4 * width) : (((cast(int) width * 3 + 3) / 4) * 4); 10314 auto arrLength = itemsPerLine * height; 10315 rawData[0..arrLength] = i.rawData[0..arrLength]; 10316 } else version(OSXCocoa) { 10317 // FIXME: I have no idea if this is even any good 10318 auto colorSpace = CGColorSpaceCreateDeviceRGB(); 10319 handle = CGBitmapContextCreate(null, width, height, 8, 4*width, 10320 colorSpace, 10321 kCGImageAlphaPremultipliedLast 10322 |kCGBitmapByteOrder32Big); 10323 CGColorSpaceRelease(colorSpace); 10324 auto rawData = CGBitmapContextGetData(handle); 10325 10326 auto rdl = (width * height * 4); 10327 rawData[0 .. rdl] = i.rawData[0 .. rdl]; 10328 } else static assert(0); 10329 } 10330 10331 /++ 10332 Draws the image on the specified painter at the specified point. The point is the upper-left point where the image will be drawn. 10333 10334 Params: 10335 where = point on the window where the upper left corner of the image will be drawn 10336 imageUpperLeft = point on the image to start the slice to draw 10337 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. 10338 History: 10339 The `imageUpperLeft` and `sliceSize` parameters were added on March 11, 2021 (dub v9.3.0) 10340 +/ 10341 void drawAt(ScreenPainter painter, Point where, Point imageUpperLeft = Point(0, 0), Size sliceSize = Size(0, 0)) { 10342 painter.drawPixmap(this, where, imageUpperLeft, sliceSize); 10343 } 10344 10345 /// Call this when you're ready to get rid of it 10346 void dispose() { 10347 version(X11) { 10348 staticDispose(xrenderPicture, handle); 10349 xrenderPicture = None; 10350 handle = None; 10351 } else version(Windows) { 10352 staticDispose(handle); 10353 handle = null; 10354 } else version(OSXCocoa) { 10355 staticDispose(handle); 10356 handle = null; 10357 } else static assert(0); 10358 10359 } 10360 10361 version(X11) 10362 static void staticDispose(Picture xrenderPicture, Pixmap handle) { 10363 if(xrenderPicture) 10364 XRenderFreePicture(XDisplayConnection.get, xrenderPicture); 10365 if(handle) 10366 XFreePixmap(XDisplayConnection.get(), handle); 10367 } 10368 else version(Windows) 10369 static void staticDispose(HBITMAP handle) { 10370 if(handle) 10371 DeleteObject(handle); 10372 } 10373 else version(OSXCocoa) 10374 static void staticDispose(CGContextRef context) { 10375 if(context) 10376 CGContextRelease(context); 10377 } 10378 10379 ~this() { 10380 version(X11) { if(xrenderPicture || handle) 10381 cleanupQueue.queue!staticDispose(xrenderPicture, handle); 10382 } else version(Windows) { if(handle) 10383 cleanupQueue.queue!staticDispose(handle); 10384 } else version(OSXCocoa) { if(handle) 10385 cleanupQueue.queue!staticDispose(handle); 10386 } else static assert(0); 10387 } 10388 10389 /// 10390 final @property int width() { return _width; } 10391 10392 /// 10393 final @property int height() { return _height; } 10394 10395 /// 10396 static Sprite fromMemoryImage(SimpleWindow win, MemoryImage img, bool enableAlpha = false) { 10397 return new Sprite(win, Image.fromMemoryImage(img, enableAlpha)); 10398 } 10399 10400 auto nativeHandle() { 10401 return handle; 10402 } 10403 10404 private: 10405 10406 int _width; 10407 int _height; 10408 bool enableAlpha; 10409 version(X11) 10410 Pixmap handle; 10411 else version(Windows) 10412 HBITMAP handle; 10413 else version(OSXCocoa) 10414 CGContextRef handle; 10415 else version(Emscripten) 10416 void* handle; 10417 else static assert(0); 10418 } 10419 10420 /++ 10421 Represents a display-side gradient pseudo-image. Actually construct it with [LinearGradient], [RadialGradient], or [ConicalGradient]. 10422 10423 History: 10424 Added November 20, 2021 (dub v10.4) 10425 +/ 10426 version(OSXCocoa) {} else // NotYetImplementedException 10427 abstract class Gradient : Sprite { 10428 protected this(int w, int h) { 10429 version(X11) { 10430 Sprite.requireXRender(); 10431 10432 super(); 10433 enableAlpha = true; 10434 _width = w; 10435 _height = h; 10436 } else version(Windows) { 10437 super(null, w, h, true); // on Windows i'm just making a bitmap myself 10438 } 10439 } 10440 10441 version(Windows) 10442 final void forEachPixel(scope Color delegate(int x, int y) dg) @system { 10443 auto ptr = rawData; 10444 foreach(j; 0 .. _height) 10445 foreach(i; 0 .. _width) { 10446 auto color = dg(i, _height - j - 1); // cuz of upside down bitmap 10447 *rawData = (color.a * color.b) / 255; rawData++; 10448 *rawData = (color.a * color.g) / 255; rawData++; 10449 *rawData = (color.a * color.r) / 255; rawData++; 10450 *rawData = color.a; rawData++; 10451 } 10452 } 10453 10454 version(X11) 10455 protected void helper(scope Stop[] stops, scope Picture delegate(scope XFixed[] stopsPositions, scope XRenderColor[] colors) dg) { 10456 assert(stops.length > 0); 10457 assert(stops.length <= 16, "I got lazy with buffers"); 10458 10459 XFixed[16] stopsPositions = void; 10460 XRenderColor[16] colors = void; 10461 10462 foreach(idx, stop; stops) { 10463 stopsPositions[idx] = cast(int)(stop.percentage * ushort.max); 10464 auto c = stop.c; 10465 colors[idx] = XRenderColor( 10466 cast(ushort)(c.r * ushort.max / 255), 10467 cast(ushort)(c.g * ushort.max / 255), 10468 cast(ushort)(c.b * ushort.max / 255), 10469 cast(ushort)(c.a * ubyte.max) // max value here is fractional 10470 ); 10471 } 10472 10473 xrenderPicture = dg(stopsPositions, colors); 10474 } 10475 10476 /// 10477 static struct Stop { 10478 float percentage; /// between 0 and 1.0 10479 Color c; 10480 } 10481 } 10482 10483 /++ 10484 Creates a linear gradient between p1 and p2. 10485 10486 X ONLY RIGHT NOW 10487 10488 History: 10489 Added November 20, 2021 (dub v10.4) 10490 10491 Bugs: 10492 Not yet implemented on Windows. 10493 +/ 10494 version(OSXCocoa) {} else // NotYetImplementedException 10495 class LinearGradient : Gradient { 10496 /++ 10497 10498 +/ 10499 this(Point p1, Point p2, Stop[] stops...) { 10500 super(p2.x, p2.y); 10501 10502 version(X11) { 10503 XLinearGradient gradient; 10504 gradient.p1 = XPointFixed(p1.x * ushort.max, p1.y * ushort.max); 10505 gradient.p2 = XPointFixed(p2.x * ushort.max, p2.y * ushort.max); 10506 10507 helper(stops, (scope XFixed[] stopsPositions, scope XRenderColor[] colors) { 10508 return XRenderCreateLinearGradient( 10509 XDisplayConnection.get, 10510 &gradient, 10511 stopsPositions.ptr, 10512 colors.ptr, 10513 cast(int) stops.length); 10514 }); 10515 } else version(Windows) { 10516 // FIXME 10517 forEachPixel((int x, int y) { 10518 import core.stdc.math; 10519 10520 //sqrtf( 10521 10522 return Color.transparent; 10523 // return Color(x * 2, y * 2, 0, 128); // this result is so beautiful 10524 }); 10525 } 10526 } 10527 } 10528 10529 /++ 10530 A conical gradient goes from color to color around a circumference from a center point. 10531 10532 X ONLY RIGHT NOW 10533 10534 History: 10535 Added November 20, 2021 (dub v10.4) 10536 10537 Bugs: 10538 Not yet implemented on Windows. 10539 +/ 10540 version(OSXCocoa) {} else // NotYetImplementedException 10541 class ConicalGradient : Gradient { 10542 /++ 10543 10544 +/ 10545 this(Point center, float angleInDegrees, Stop[] stops...) { 10546 super(center.x * 2, center.y * 2); 10547 10548 version(X11) { 10549 XConicalGradient gradient; 10550 gradient.center = XPointFixed(center.x * ushort.max, center.y * ushort.max); 10551 gradient.angle = cast(int)(angleInDegrees * ushort.max); 10552 10553 helper(stops, (scope XFixed[] stopsPositions, scope XRenderColor[] colors) { 10554 return XRenderCreateConicalGradient( 10555 XDisplayConnection.get, 10556 &gradient, 10557 stopsPositions.ptr, 10558 colors.ptr, 10559 cast(int) stops.length); 10560 }); 10561 } else version(Windows) { 10562 // FIXME 10563 forEachPixel((int x, int y) { 10564 import core.stdc.math; 10565 10566 //sqrtf( 10567 10568 return Color.transparent; 10569 // return Color(x * 2, y * 2, 0, 128); // this result is so beautiful 10570 }); 10571 10572 } 10573 } 10574 } 10575 10576 /++ 10577 A radial gradient goes from color to color based on distance from the center. 10578 It is like rings of color. 10579 10580 X ONLY RIGHT NOW 10581 10582 10583 More specifically, you create two circles: an inner circle and an outer circle. 10584 The gradient is only drawn in the area outside the inner circle but inside the outer 10585 circle. The closest line between those two circles forms the line for the gradient 10586 and the stops are calculated the same as the [LinearGradient]. Then it just sweeps around. 10587 10588 History: 10589 Added November 20, 2021 (dub v10.4) 10590 10591 Bugs: 10592 Not yet implemented on Windows. 10593 +/ 10594 version(OSXCocoa) {} else // NotYetImplementedException 10595 class RadialGradient : Gradient { 10596 /++ 10597 10598 +/ 10599 this(Point innerCenter, float innerRadius, Point outerCenter, float outerRadius, Stop[] stops...) { 10600 super(cast(int)(outerCenter.x + outerRadius + 0.5), cast(int)(outerCenter.y + outerRadius + 0.5)); 10601 10602 version(X11) { 10603 XRadialGradient gradient; 10604 gradient.inner = XCircle(innerCenter.x * ushort.max, innerCenter.y * ushort.max, cast(int) (innerRadius * ushort.max)); 10605 gradient.outer = XCircle(outerCenter.x * ushort.max, outerCenter.y * ushort.max, cast(int) (outerRadius * ushort.max)); 10606 10607 helper(stops, (scope XFixed[] stopsPositions, scope XRenderColor[] colors) { 10608 return XRenderCreateRadialGradient( 10609 XDisplayConnection.get, 10610 &gradient, 10611 stopsPositions.ptr, 10612 colors.ptr, 10613 cast(int) stops.length); 10614 }); 10615 } else version(Windows) { 10616 // FIXME 10617 forEachPixel((int x, int y) { 10618 import core.stdc.math; 10619 10620 //sqrtf( 10621 10622 return Color.transparent; 10623 // return Color(x * 2, y * 2, 0, 128); // this result is so beautiful 10624 }); 10625 } 10626 } 10627 } 10628 10629 10630 10631 /+ 10632 NOT IMPLEMENTED 10633 10634 A display-stored image optimized for relatively quick drawing, like 10635 [Sprite], but this one supports alpha channel blending and does NOT 10636 support direct drawing upon it with a [ScreenPainter]. 10637 10638 You can think of it as an [arsd.game.OpenGlTexture] for usage with a 10639 plain [ScreenPainter]... sort of. 10640 10641 On X11, it requires the Xrender extension and library. This is available 10642 almost everywhere though. 10643 10644 History: 10645 Added November 14, 2020 but NOT ACTUALLY IMPLEMENTED 10646 +/ 10647 version(none) 10648 class AlphaSprite { 10649 /++ 10650 Copies the given image into it. 10651 +/ 10652 this(MemoryImage img) { 10653 10654 if(!XRenderLibrary.loadAttempted) { 10655 XRenderLibrary.loadDynamicLibrary(); 10656 10657 // FIXME: this needs to be reconstructed when the X server changes 10658 repopulateX(); 10659 } 10660 if(!XRenderLibrary.loadSuccessful) 10661 throw new Exception("XRender library load failure"); 10662 10663 // I probably need to put the alpha mask in a separate Picture 10664 // ugh 10665 // maybe the Sprite itself can have an alpha bitmask anyway 10666 10667 10668 auto display = XDisplayConnection.get(); 10669 pixmap = XCreatePixmap(display, cast(Drawable) win.window, width, height, DefaultDepthOfDisplay(display)); 10670 10671 10672 XRenderPictureAttributes attrs; 10673 10674 handle = XRenderCreatePicture( 10675 XDisplayConnection.get, 10676 pixmap, 10677 RGBA, 10678 0, 10679 &attrs 10680 ); 10681 10682 } 10683 10684 // maybe i'll use the create gradient functions too with static factories.. 10685 10686 void drawAt(ScreenPainter painter, Point where) { 10687 //painter.drawPixmap(this, where); 10688 10689 XRenderPictureAttributes attrs; 10690 10691 auto pic = XRenderCreatePicture( 10692 XDisplayConnection.get, 10693 painter.impl.d, 10694 RGB, 10695 0, 10696 &attrs 10697 ); 10698 10699 XRenderComposite( 10700 XDisplayConnection.get, 10701 3, // PictOpOver 10702 handle, 10703 None, 10704 pic, 10705 0, // src 10706 0, 10707 0, // mask 10708 0, 10709 10, // dest 10710 10, 10711 100, // width 10712 100 10713 ); 10714 10715 /+ 10716 XRenderFreePicture( 10717 XDisplayConnection.get, 10718 pic 10719 ); 10720 10721 XRenderFreePicture( 10722 XDisplayConnection.get, 10723 fill 10724 ); 10725 +/ 10726 // on Windows you can stretch but Xrender still can't :( 10727 } 10728 10729 static XRenderPictFormat* RGB; 10730 static XRenderPictFormat* RGBA; 10731 static void repopulateX() { 10732 auto display = XDisplayConnection.get; 10733 RGB = XRenderFindStandardFormat(display, PictStandardRGB24); 10734 RGBA = XRenderFindStandardFormat(display, PictStandardARGB32); 10735 } 10736 10737 XPixmap pixmap; 10738 Picture handle; 10739 } 10740 10741 /// 10742 interface CapableOfBeingDrawnUpon { 10743 /// 10744 ScreenPainter draw(); 10745 /// 10746 int width(); 10747 /// 10748 int height(); 10749 protected ScreenPainterImplementation* activeScreenPainter(); 10750 protected void activeScreenPainter(ScreenPainterImplementation*); 10751 bool closed(); 10752 10753 void delegate() paintingFinishedDg(); 10754 10755 /// Be warned: this can be a very slow operation 10756 TrueColorImage takeScreenshot(); 10757 } 10758 10759 /// 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]. 10760 void flushGui() { 10761 version(X11) { 10762 auto dpy = XDisplayConnection.get(); 10763 XLockDisplay(dpy); 10764 scope(exit) XUnlockDisplay(dpy); 10765 XFlush(dpy); 10766 } 10767 } 10768 10769 /++ 10770 Runs the given code in the GUI thread when its event loop 10771 is available, blocking until it completes. This allows you 10772 to create and manipulate windows from another thread without 10773 invoking undefined behavior. 10774 10775 If this is the gui thread, it runs the code immediately. 10776 10777 If no gui thread exists yet, the current thread is assumed 10778 to be it. Attempting to create windows or run the event loop 10779 in any other thread will cause an assertion failure. 10780 10781 10782 $(TIP 10783 Did you know you can use UFCS on delegate literals? 10784 10785 () { 10786 // code here 10787 }.runInGuiThread; 10788 ) 10789 10790 Returns: 10791 `true` if the function was called, `false` if it was not. 10792 The function may not be called because the gui thread had 10793 already terminated by the time you called this. 10794 10795 History: 10796 Added April 10, 2020 (v7.2.0) 10797 10798 Return value added and implementation tweaked to avoid locking 10799 at program termination on February 24, 2021 (v9.2.1). 10800 +/ 10801 bool runInGuiThread(scope void delegate() dg) @trusted { 10802 claimGuiThread(); 10803 10804 if(thisIsGuiThread) { 10805 dg(); 10806 return true; 10807 } 10808 10809 if(guiThreadTerminating) 10810 return false; 10811 10812 import core.sync.semaphore; 10813 static Semaphore sc; 10814 if(sc is null) 10815 sc = new Semaphore(); 10816 10817 static RunQueueMember* rqm; 10818 if(rqm is null) 10819 rqm = new RunQueueMember; 10820 rqm.dg = cast(typeof(rqm.dg)) dg; 10821 rqm.signal = sc; 10822 rqm.thrown = null; 10823 10824 synchronized(runInGuiThreadLock) { 10825 runInGuiThreadQueue ~= rqm; 10826 } 10827 10828 if(!SimpleWindow.eventWakeUp()) 10829 throw new Error("runInGuiThread impossible; eventWakeUp failed"); 10830 10831 rqm.signal.wait(); 10832 auto t = rqm.thrown; 10833 10834 if(t) 10835 throw t; 10836 10837 return true; 10838 } 10839 10840 // note it runs sync if this is the gui thread.... 10841 void runInGuiThreadAsync(void delegate() dg, void delegate(Exception) nothrow handleError = null) nothrow { 10842 claimGuiThread(); 10843 10844 try { 10845 10846 if(thisIsGuiThread) { 10847 dg(); 10848 return; 10849 } 10850 10851 if(guiThreadTerminating) 10852 return; 10853 10854 RunQueueMember* rqm = new RunQueueMember; 10855 rqm.dg = cast(typeof(rqm.dg)) dg; 10856 rqm.signal = null; 10857 rqm.thrown = null; 10858 10859 synchronized(runInGuiThreadLock) { 10860 runInGuiThreadQueue ~= rqm; 10861 } 10862 10863 if(!SimpleWindow.eventWakeUp()) 10864 throw new Error("runInGuiThread impossible; eventWakeUp failed"); 10865 } catch(Exception e) { 10866 if(handleError) 10867 handleError(e); 10868 } 10869 } 10870 10871 private void runPendingRunInGuiThreadDelegates() { 10872 more: 10873 RunQueueMember* next; 10874 synchronized(runInGuiThreadLock) { 10875 if(runInGuiThreadQueue.length) { 10876 next = runInGuiThreadQueue[0]; 10877 runInGuiThreadQueue = runInGuiThreadQueue[1 .. $]; 10878 } else { 10879 next = null; 10880 } 10881 } 10882 10883 if(next) { 10884 try { 10885 next.dg(); 10886 next.thrown = null; 10887 } catch(Throwable t) { 10888 next.thrown = t; 10889 } 10890 10891 if(next.signal) 10892 next.signal.notify(); 10893 10894 goto more; 10895 } 10896 } 10897 10898 private void claimGuiThread() nothrow { 10899 import core.atomic; 10900 if(cas(&guiThreadExists_, false, true)) 10901 thisIsGuiThread = true; 10902 } 10903 10904 private struct RunQueueMember { 10905 void delegate() dg; 10906 import core.sync.semaphore; 10907 Semaphore signal; 10908 Throwable thrown; 10909 } 10910 10911 private __gshared RunQueueMember*[] runInGuiThreadQueue; 10912 private __gshared Object runInGuiThreadLock = new Object; // intentional CTFE 10913 private bool thisIsGuiThread = false; 10914 private shared bool guiThreadExists_ = false; 10915 private shared bool guiThreadTerminating = false; 10916 10917 /++ 10918 Returns `true` if a gui thread exists, that is, a thread running the simpledisplay.d 10919 event loop. All windows must be exclusively created and managed by a single thread. 10920 10921 If no gui thread exists, simpledisplay.d will automatically adopt the current thread 10922 when you call one of its constructors. 10923 10924 If a gui thread exists, you should check [thisThreadRunningGui] to see if it is this 10925 one. If so, you can run gui functions on it. If not, don't. The helper functions 10926 [runInGuiThread] and [runInGuiThreadAsync] can be used to help you with this automatically. 10927 10928 The reason this function is available is in case you want to message pass between a gui 10929 thread and your current thread. If no gui thread exists or if this is the gui thread, 10930 you're liable to deadlock when trying to communicate since you'd end up talking to yourself. 10931 10932 History: 10933 Added December 3, 2021 (dub v10.5) 10934 +/ 10935 public bool guiThreadExists() { 10936 return guiThreadExists_; 10937 } 10938 10939 /++ 10940 Returns `true` if this thread is either running or set to be running the 10941 simpledisplay.d gui core event loop because it owns windows. 10942 10943 It is important to keep gui-related functionality in the right thread, so you will 10944 want to `runInGuiThread` when you call them (with some specific exceptions called 10945 out in those specific functions' documentation). Notably, all windows must be 10946 created and managed only from the gui thread. 10947 10948 Will return false if simpledisplay's other functions haven't been called 10949 yet; check [guiThreadExists] in addition to this. 10950 10951 History: 10952 Added December 3, 2021 (dub v10.5) 10953 +/ 10954 public bool thisThreadRunningGui() { 10955 return thisIsGuiThread; 10956 } 10957 10958 /++ 10959 Function to help temporarily print debugging info. It will bypass any stdout/err redirection 10960 and go to the controlling tty or console (attaching to the parent and/or allocating one as 10961 needed on Windows. Please note it may overwrite output from other programs in the parent and the 10962 allocated one will not survive if your program crashes. Use the `fileOverride` to print to a log 10963 file instead if you are in one of those situations). 10964 10965 It does not support outputting very many types; just strings and ints are likely to actually work. 10966 10967 It will perform very slowly and swallows any errors that may occur. Moreover, the specific output 10968 is unspecified meaning I can change it at any time. The only point of this function is to help 10969 in temporary use for printf-style debugging. It is NOT nogc, but you can use the `debug` keyword 10970 and the compiler will cheat for you. It is, however, formally nothrow and trusted to ease its use 10971 in those contexts. 10972 10973 $(WARNING 10974 I reserve the right to change this function at any time. You can use it if it helps you 10975 but do not rely on it for anything permanent. 10976 ) 10977 10978 History: 10979 Added December 3, 2021. Not formally supported under any stable tag. 10980 +/ 10981 void sdpyPrintDebugString(string fileOverride = null, T...)(T t) nothrow @trusted { 10982 try { 10983 version(Windows) { 10984 import core.sys.windows.wincon; 10985 if(!AttachConsole(ATTACH_PARENT_PROCESS)) 10986 AllocConsole(); 10987 const(char)* fn = "CONOUT$"; 10988 } else version(Posix) { 10989 const(char)* fn = "/dev/tty"; 10990 } else static assert(0, "Function not implemented for your system"); 10991 10992 if(fileOverride.length) 10993 fn = fileOverride.ptr; 10994 10995 import core.stdc.stdio; 10996 auto fp = fopen(fn, "wt"); 10997 if(fp is null) return; 10998 scope(exit) fclose(fp); 10999 11000 string str; 11001 foreach(item; t) { 11002 static if(is(typeof(item) : const(char)[])) 11003 str ~= item; 11004 else 11005 str ~= toInternal!string(item); 11006 str ~= " "; 11007 } 11008 str ~= "\n"; 11009 11010 fwrite(str.ptr, 1, str.length, fp); 11011 fflush(fp); 11012 } catch(Exception e) { 11013 // sorry no hope 11014 } 11015 } 11016 11017 private void guiThreadFinalize() { 11018 assert(thisIsGuiThread); 11019 11020 guiThreadTerminating = true; // don't add any more from this point on 11021 runPendingRunInGuiThreadDelegates(); 11022 } 11023 11024 /+ 11025 interface IPromise { 11026 void reportProgress(int current, int max, string message); 11027 11028 /+ // not formally in cuz of templates but still 11029 IPromise Then(); 11030 IPromise Catch(); 11031 IPromise Finally(); 11032 +/ 11033 } 11034 11035 /+ 11036 auto promise = async({ ... }); 11037 promise.Then(whatever). 11038 Then(whateverelse). 11039 Catch((exception) { }); 11040 11041 11042 A promise is run inside a fiber and it looks something like: 11043 11044 try { 11045 auto res = whatever(); 11046 auto res2 = whateverelse(res); 11047 } catch(Exception e) { 11048 { }(e); 11049 } 11050 11051 When a thing succeeds, it is passed as an arg to the next 11052 +/ 11053 class Promise(T) : IPromise { 11054 auto Then() { return null; } 11055 auto Catch() { return null; } 11056 auto Finally() { return null; } 11057 11058 // wait for it to resolve and return the value, or rethrow the error if that occurred. 11059 // cannot be called from the gui thread, but this is caught at runtime instead of compile time. 11060 T await(); 11061 } 11062 11063 interface Task { 11064 } 11065 11066 interface Resolvable(T) : Task { 11067 void run(); 11068 11069 void resolve(T); 11070 11071 Resolvable!T then(void delegate(T)); // returns a new promise 11072 Resolvable!T error(Throwable); // js catch 11073 Resolvable!T completed(); // js finally 11074 11075 } 11076 11077 /++ 11078 Runs `work` in a helper thread and sends its return value back to the main gui 11079 thread as the argument to `uponCompletion`. If `work` throws, the exception is 11080 sent to the `uponThrown` if given, or if null, rethrown from the event loop to 11081 kill the program. 11082 11083 You can call reportProgress(position, max, message) to update your parent window 11084 on your progress. 11085 11086 I should also use `shared` methods. FIXME 11087 11088 History: 11089 Added March 6, 2021 (dub version 9.3). 11090 +/ 11091 void runInWorkerThread(T)(T delegate(Task) work, void delegate(T) uponCompletion) { 11092 uponCompletion(work(null)); 11093 } 11094 11095 +/ 11096 11097 /// Used internal to dispatch events to various classes. 11098 interface CapableOfHandlingNativeEvent { 11099 NativeEventHandler getNativeEventHandler(); 11100 11101 /*private*//*protected*/ __gshared CapableOfHandlingNativeEvent[NativeWindowHandle] nativeHandleMapping; 11102 11103 version(X11) { 11104 // if this is impossible, you are allowed to just throw from it 11105 // Note: if you call it from another object, set a flag cuz the manger will call you again 11106 void recreateAfterDisconnect(); 11107 // discard any *connection specific* state, but keep enough that you 11108 // can be recreated if possible. discardConnectionState() is always called immediately 11109 // before recreateAfterDisconnect(), so you can set a flag there to decide if 11110 // you need initialization order 11111 void discardConnectionState(); 11112 } 11113 } 11114 11115 version(X11) 11116 /++ 11117 State of keys on mouse events, especially motion. 11118 11119 Do not trust the actual integer values in this, they are platform-specific. Always use the names. 11120 +/ 11121 enum ModifierState : uint { 11122 shift = 1, /// 11123 capsLock = 2, /// 11124 ctrl = 4, /// 11125 alt = 8, /// Not always available on Windows 11126 windows = 64, /// ditto 11127 numLock = 16, /// 11128 11129 leftButtonDown = 256, /// these aren't available on Windows for key events, so don't use them for that unless your app is X only. 11130 middleButtonDown = 512, /// ditto 11131 rightButtonDown = 1024, /// ditto 11132 } 11133 else version(Emscripten) 11134 enum ModifierState : uint { 11135 shift = 1, /// 11136 capsLock = 2, /// 11137 ctrl = 4, /// 11138 alt = 8, /// Not always available on Windows 11139 windows = 64, /// ditto 11140 numLock = 16, /// 11141 11142 leftButtonDown = 256, /// these aren't available on Windows for key events, so don't use them for that unless your app is X only. 11143 middleButtonDown = 512, /// ditto 11144 rightButtonDown = 1024, /// ditto 11145 } 11146 else version(Windows) 11147 /// ditto 11148 enum ModifierState : uint { 11149 shift = 4, /// 11150 ctrl = 8, /// 11151 11152 // i'm not sure if the next two are available 11153 alt = 256, /// not always available on Windows 11154 windows = 512, /// ditto 11155 11156 capsLock = 1024, /// 11157 numLock = 2048, /// 11158 11159 leftButtonDown = 1, /// not available on key events 11160 middleButtonDown = 16, /// ditto 11161 rightButtonDown = 2, /// ditto 11162 11163 backButtonDown = 0x20, /// not available on X 11164 forwardButtonDown = 0x40, /// ditto 11165 } 11166 else version(OSXCocoa) 11167 // FIXME FIXME NotYetImplementedException 11168 enum ModifierState : uint { 11169 shift = 1, /// 11170 capsLock = 2, /// 11171 ctrl = 4, /// 11172 alt = 8, /// Not always available on Windows 11173 windows = 64, /// ditto 11174 numLock = 16, /// 11175 11176 leftButtonDown = 256, /// these aren't available on Windows for key events, so don't use them for that unless your app is X only. 11177 middleButtonDown = 512, /// ditto 11178 rightButtonDown = 1024, /// ditto 11179 } 11180 11181 /// The names assume a right-handed mouse. These are bitwise combined on the events that use them. 11182 enum MouseButton : int { 11183 none = 0, 11184 left = 1, /// 11185 right = 2, /// 11186 middle = 4, /// 11187 wheelUp = 8, /// 11188 wheelDown = 16, /// 11189 backButton = 32, /// often found on the thumb and used for back in browsers 11190 forwardButton = 64, /// often found on the thumb and used for forward in browsers 11191 } 11192 11193 /// Corresponds to the values found in MouseEvent.buttonLinear, being equal to `core.bitop.bsf(button) + 1` 11194 enum MouseButtonLinear : ubyte { 11195 left = 1, /// 11196 right, /// 11197 middle, /// 11198 wheelUp, /// 11199 wheelDown, /// 11200 backButton, /// often found on the thumb and used for back in browsers 11201 forwardButton, /// often found on the thumb and used for forward in browsers 11202 } 11203 11204 version(WebAssembly) { 11205 /// Do not trust the numeric values as they are platform-specific. Always use the symbolic name. 11206 enum Key { 11207 Escape = 0xff1b, /// 11208 F1 = 0xffbe, /// 11209 F2 = 0xffbf, /// 11210 F3 = 0xffc0, /// 11211 F4 = 0xffc1, /// 11212 F5 = 0xffc2, /// 11213 F6 = 0xffc3, /// 11214 F7 = 0xffc4, /// 11215 F8 = 0xffc5, /// 11216 F9 = 0xffc6, /// 11217 F10 = 0xffc7, /// 11218 F11 = 0xffc8, /// 11219 F12 = 0xffc9, /// 11220 PrintScreen = 0xff61, /// 11221 ScrollLock = 0xff14, /// 11222 Pause = 0xff13, /// 11223 Grave = 0x60, /// The $(BACKTICK) ~ key 11224 // number keys across the top of the keyboard 11225 N1 = 0x31, /// Number key atop the keyboard 11226 N2 = 0x32, /// 11227 N3 = 0x33, /// 11228 N4 = 0x34, /// 11229 N5 = 0x35, /// 11230 N6 = 0x36, /// 11231 N7 = 0x37, /// 11232 N8 = 0x38, /// 11233 N9 = 0x39, /// 11234 N0 = 0x30, /// 11235 Dash = 0x2d, /// 11236 Equals = 0x3d, /// 11237 Backslash = 0x5c, /// The \ | key 11238 Backspace = 0xff08, /// 11239 Insert = 0xff63, /// 11240 Home = 0xff50, /// 11241 PageUp = 0xff55, /// 11242 Delete = 0xffff, /// 11243 End = 0xff57, /// 11244 PageDown = 0xff56, /// 11245 Up = 0xff52, /// 11246 Down = 0xff54, /// 11247 Left = 0xff51, /// 11248 Right = 0xff53, /// 11249 11250 Tab = 0xff09, /// 11251 Q = 0x71, /// 11252 W = 0x77, /// 11253 E = 0x65, /// 11254 R = 0x72, /// 11255 T = 0x74, /// 11256 Y = 0x79, /// 11257 U = 0x75, /// 11258 I = 0x69, /// 11259 O = 0x6f, /// 11260 P = 0x70, /// 11261 LeftBracket = 0x5b, /// the [ { key 11262 RightBracket = 0x5d, /// the ] } key 11263 CapsLock = 0xffe5, /// 11264 A = 0x61, /// 11265 S = 0x73, /// 11266 D = 0x64, /// 11267 F = 0x66, /// 11268 G = 0x67, /// 11269 H = 0x68, /// 11270 J = 0x6a, /// 11271 K = 0x6b, /// 11272 L = 0x6c, /// 11273 Semicolon = 0x3b, /// 11274 Apostrophe = 0x27, /// 11275 Enter = 0xff0d, /// 11276 Shift = 0xffe1, /// 11277 Z = 0x7a, /// 11278 X = 0x78, /// 11279 C = 0x63, /// 11280 V = 0x76, /// 11281 B = 0x62, /// 11282 N = 0x6e, /// 11283 M = 0x6d, /// 11284 Comma = 0x2c, /// 11285 Period = 0x2e, /// 11286 Slash = 0x2f, /// the / ? key 11287 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 11288 Ctrl = 0xffe3, /// 11289 Windows = 0xffeb, /// 11290 Alt = 0xffe9, /// 11291 Space = 0x20, /// 11292 Alt_r = 0xffea, /// ditto of shift_r 11293 Windows_r = 0xffec, /// 11294 Menu = 0xff67, /// 11295 Ctrl_r = 0xffe4, /// 11296 11297 NumLock = 0xff7f, /// 11298 Divide = 0xffaf, /// The / key on the number pad 11299 Multiply = 0xffaa, /// The * key on the number pad 11300 Minus = 0xffad, /// The - key on the number pad 11301 Plus = 0xffab, /// The + key on the number pad 11302 PadEnter = 0xff8d, /// Numberpad enter key 11303 Pad1 = 0xff9c, /// Numberpad keys 11304 Pad2 = 0xff99, /// 11305 Pad3 = 0xff9b, /// 11306 Pad4 = 0xff96, /// 11307 Pad5 = 0xff9d, /// 11308 Pad6 = 0xff98, /// 11309 Pad7 = 0xff95, /// 11310 Pad8 = 0xff97, /// 11311 Pad9 = 0xff9a, /// 11312 Pad0 = 0xff9e, /// 11313 PadDot = 0xff9f, /// 11314 } 11315 } version(X11) { 11316 // FIXME: match ASCII whenever we can. Most of it is already there, 11317 // but there's a few exceptions and mismatches with Windows 11318 11319 /// Do not trust the numeric values as they are platform-specific. Always use the symbolic name. 11320 enum Key { 11321 Escape = 0xff1b, /// 11322 F1 = 0xffbe, /// 11323 F2 = 0xffbf, /// 11324 F3 = 0xffc0, /// 11325 F4 = 0xffc1, /// 11326 F5 = 0xffc2, /// 11327 F6 = 0xffc3, /// 11328 F7 = 0xffc4, /// 11329 F8 = 0xffc5, /// 11330 F9 = 0xffc6, /// 11331 F10 = 0xffc7, /// 11332 F11 = 0xffc8, /// 11333 F12 = 0xffc9, /// 11334 PrintScreen = 0xff61, /// 11335 ScrollLock = 0xff14, /// 11336 Pause = 0xff13, /// 11337 Grave = 0x60, /// The $(BACKTICK) ~ key 11338 // number keys across the top of the keyboard 11339 N1 = 0x31, /// Number key atop the keyboard 11340 N2 = 0x32, /// 11341 N3 = 0x33, /// 11342 N4 = 0x34, /// 11343 N5 = 0x35, /// 11344 N6 = 0x36, /// 11345 N7 = 0x37, /// 11346 N8 = 0x38, /// 11347 N9 = 0x39, /// 11348 N0 = 0x30, /// 11349 Dash = 0x2d, /// 11350 Equals = 0x3d, /// 11351 Backslash = 0x5c, /// The \ | key 11352 Backspace = 0xff08, /// 11353 Insert = 0xff63, /// 11354 Home = 0xff50, /// 11355 PageUp = 0xff55, /// 11356 Delete = 0xffff, /// 11357 End = 0xff57, /// 11358 PageDown = 0xff56, /// 11359 Up = 0xff52, /// 11360 Down = 0xff54, /// 11361 Left = 0xff51, /// 11362 Right = 0xff53, /// 11363 11364 Tab = 0xff09, /// 11365 Q = 0x71, /// 11366 W = 0x77, /// 11367 E = 0x65, /// 11368 R = 0x72, /// 11369 T = 0x74, /// 11370 Y = 0x79, /// 11371 U = 0x75, /// 11372 I = 0x69, /// 11373 O = 0x6f, /// 11374 P = 0x70, /// 11375 LeftBracket = 0x5b, /// the [ { key 11376 RightBracket = 0x5d, /// the ] } key 11377 CapsLock = 0xffe5, /// 11378 A = 0x61, /// 11379 S = 0x73, /// 11380 D = 0x64, /// 11381 F = 0x66, /// 11382 G = 0x67, /// 11383 H = 0x68, /// 11384 J = 0x6a, /// 11385 K = 0x6b, /// 11386 L = 0x6c, /// 11387 Semicolon = 0x3b, /// 11388 Apostrophe = 0x27, /// 11389 Enter = 0xff0d, /// 11390 Shift = 0xffe1, /// 11391 Z = 0x7a, /// 11392 X = 0x78, /// 11393 C = 0x63, /// 11394 V = 0x76, /// 11395 B = 0x62, /// 11396 N = 0x6e, /// 11397 M = 0x6d, /// 11398 Comma = 0x2c, /// 11399 Period = 0x2e, /// 11400 Slash = 0x2f, /// the / ? key 11401 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 11402 Ctrl = 0xffe3, /// 11403 Windows = 0xffeb, /// 11404 Alt = 0xffe9, /// 11405 Space = 0x20, /// 11406 Alt_r = 0xffea, /// ditto of shift_r 11407 Windows_r = 0xffec, /// 11408 Menu = 0xff67, /// 11409 Ctrl_r = 0xffe4, /// 11410 11411 NumLock = 0xff7f, /// 11412 Divide = 0xffaf, /// The / key on the number pad 11413 Multiply = 0xffaa, /// The * key on the number pad 11414 Minus = 0xffad, /// The - key on the number pad 11415 Plus = 0xffab, /// The + key on the number pad 11416 PadEnter = 0xff8d, /// Numberpad enter key 11417 Pad1 = 0xff9c, /// Numberpad keys 11418 Pad2 = 0xff99, /// 11419 Pad3 = 0xff9b, /// 11420 Pad4 = 0xff96, /// 11421 Pad5 = 0xff9d, /// 11422 Pad6 = 0xff98, /// 11423 Pad7 = 0xff95, /// 11424 Pad8 = 0xff97, /// 11425 Pad9 = 0xff9a, /// 11426 Pad0 = 0xff9e, /// 11427 PadDot = 0xff9f, /// 11428 } 11429 } else version(Windows) { 11430 // the character here is for en-us layouts and for illustration only 11431 // if you actually want to get characters, wait for character events 11432 // (the argument to your event handler is simply a dchar) 11433 // those will be converted by the OS for the right locale. 11434 11435 enum Key { 11436 Escape = 0x1b, 11437 F1 = 0x70, 11438 F2 = 0x71, 11439 F3 = 0x72, 11440 F4 = 0x73, 11441 F5 = 0x74, 11442 F6 = 0x75, 11443 F7 = 0x76, 11444 F8 = 0x77, 11445 F9 = 0x78, 11446 F10 = 0x79, 11447 F11 = 0x7a, 11448 F12 = 0x7b, 11449 PrintScreen = 0x2c, 11450 ScrollLock = 0x91, 11451 Pause = 0x13, 11452 Grave = 0xc0, 11453 // number keys across the top of the keyboard 11454 N1 = 0x31, 11455 N2 = 0x32, 11456 N3 = 0x33, 11457 N4 = 0x34, 11458 N5 = 0x35, 11459 N6 = 0x36, 11460 N7 = 0x37, 11461 N8 = 0x38, 11462 N9 = 0x39, 11463 N0 = 0x30, 11464 Dash = 0xbd, 11465 Equals = 0xbb, 11466 Backslash = 0xdc, 11467 Backspace = 0x08, 11468 Insert = 0x2d, 11469 Home = 0x24, 11470 PageUp = 0x21, 11471 Delete = 0x2e, 11472 End = 0x23, 11473 PageDown = 0x22, 11474 Up = 0x26, 11475 Down = 0x28, 11476 Left = 0x25, 11477 Right = 0x27, 11478 11479 Tab = 0x09, 11480 Q = 0x51, 11481 W = 0x57, 11482 E = 0x45, 11483 R = 0x52, 11484 T = 0x54, 11485 Y = 0x59, 11486 U = 0x55, 11487 I = 0x49, 11488 O = 0x4f, 11489 P = 0x50, 11490 LeftBracket = 0xdb, 11491 RightBracket = 0xdd, 11492 CapsLock = 0x14, 11493 A = 0x41, 11494 S = 0x53, 11495 D = 0x44, 11496 F = 0x46, 11497 G = 0x47, 11498 H = 0x48, 11499 J = 0x4a, 11500 K = 0x4b, 11501 L = 0x4c, 11502 Semicolon = 0xba, 11503 Apostrophe = 0xde, 11504 Enter = 0x0d, 11505 Shift = 0x10, 11506 Z = 0x5a, 11507 X = 0x58, 11508 C = 0x43, 11509 V = 0x56, 11510 B = 0x42, 11511 N = 0x4e, 11512 M = 0x4d, 11513 Comma = 0xbc, 11514 Period = 0xbe, 11515 Slash = 0xbf, 11516 Shift_r = 0xa1, // FIXME Note: this isn't sent on all computers, sometimes it just sends Shift, so don't rely on it 11517 Ctrl = 0x11, 11518 Windows = 0x5b, 11519 Alt = -5, // FIXME 11520 Space = 0x20, 11521 Alt_r = 0xffea, // ditto of shift_r 11522 Windows_r = 0x5c, // ditto of shift_r 11523 Menu = 0x5d, 11524 Ctrl_r = 0xa3, // ditto of shift_r 11525 11526 NumLock = 0x90, 11527 Divide = 0x6f, 11528 Multiply = 0x6a, 11529 Minus = 0x6d, 11530 Plus = 0x6b, 11531 PadEnter = -8, // FIXME 11532 Pad1 = 0x61, 11533 Pad2 = 0x62, 11534 Pad3 = 0x63, 11535 Pad4 = 0x64, 11536 Pad5 = 0x65, 11537 Pad6 = 0x66, 11538 Pad7 = 0x67, 11539 Pad8 = 0x68, 11540 Pad9 = 0x69, 11541 Pad0 = 0x60, 11542 PadDot = 0x6e, 11543 } 11544 11545 // I'm keeping this around for reference purposes 11546 // ideally all these buttons will be listed for all platforms, 11547 // but now now I'm just focusing on my US keyboard 11548 version(none) 11549 enum Key { 11550 LBUTTON = 0x01, 11551 RBUTTON = 0x02, 11552 CANCEL = 0x03, 11553 MBUTTON = 0x04, 11554 //static if (_WIN32_WINNT > = 0x500) { 11555 XBUTTON1 = 0x05, 11556 XBUTTON2 = 0x06, 11557 //} 11558 BACK = 0x08, 11559 TAB = 0x09, 11560 CLEAR = 0x0C, 11561 RETURN = 0x0D, 11562 SHIFT = 0x10, 11563 CONTROL = 0x11, 11564 MENU = 0x12, 11565 PAUSE = 0x13, 11566 CAPITAL = 0x14, 11567 KANA = 0x15, 11568 HANGEUL = 0x15, 11569 HANGUL = 0x15, 11570 JUNJA = 0x17, 11571 FINAL = 0x18, 11572 HANJA = 0x19, 11573 KANJI = 0x19, 11574 ESCAPE = 0x1B, 11575 CONVERT = 0x1C, 11576 NONCONVERT = 0x1D, 11577 ACCEPT = 0x1E, 11578 MODECHANGE = 0x1F, 11579 SPACE = 0x20, 11580 PRIOR = 0x21, 11581 NEXT = 0x22, 11582 END = 0x23, 11583 HOME = 0x24, 11584 LEFT = 0x25, 11585 UP = 0x26, 11586 RIGHT = 0x27, 11587 DOWN = 0x28, 11588 SELECT = 0x29, 11589 PRINT = 0x2A, 11590 EXECUTE = 0x2B, 11591 SNAPSHOT = 0x2C, 11592 INSERT = 0x2D, 11593 DELETE = 0x2E, 11594 HELP = 0x2F, 11595 LWIN = 0x5B, 11596 RWIN = 0x5C, 11597 APPS = 0x5D, 11598 SLEEP = 0x5F, 11599 NUMPAD0 = 0x60, 11600 NUMPAD1 = 0x61, 11601 NUMPAD2 = 0x62, 11602 NUMPAD3 = 0x63, 11603 NUMPAD4 = 0x64, 11604 NUMPAD5 = 0x65, 11605 NUMPAD6 = 0x66, 11606 NUMPAD7 = 0x67, 11607 NUMPAD8 = 0x68, 11608 NUMPAD9 = 0x69, 11609 MULTIPLY = 0x6A, 11610 ADD = 0x6B, 11611 SEPARATOR = 0x6C, 11612 SUBTRACT = 0x6D, 11613 DECIMAL = 0x6E, 11614 DIVIDE = 0x6F, 11615 F1 = 0x70, 11616 F2 = 0x71, 11617 F3 = 0x72, 11618 F4 = 0x73, 11619 F5 = 0x74, 11620 F6 = 0x75, 11621 F7 = 0x76, 11622 F8 = 0x77, 11623 F9 = 0x78, 11624 F10 = 0x79, 11625 F11 = 0x7A, 11626 F12 = 0x7B, 11627 F13 = 0x7C, 11628 F14 = 0x7D, 11629 F15 = 0x7E, 11630 F16 = 0x7F, 11631 F17 = 0x80, 11632 F18 = 0x81, 11633 F19 = 0x82, 11634 F20 = 0x83, 11635 F21 = 0x84, 11636 F22 = 0x85, 11637 F23 = 0x86, 11638 F24 = 0x87, 11639 NUMLOCK = 0x90, 11640 SCROLL = 0x91, 11641 LSHIFT = 0xA0, 11642 RSHIFT = 0xA1, 11643 LCONTROL = 0xA2, 11644 RCONTROL = 0xA3, 11645 LMENU = 0xA4, 11646 RMENU = 0xA5, 11647 //static if (_WIN32_WINNT > = 0x500) { 11648 BROWSER_BACK = 0xA6, 11649 BROWSER_FORWARD = 0xA7, 11650 BROWSER_REFRESH = 0xA8, 11651 BROWSER_STOP = 0xA9, 11652 BROWSER_SEARCH = 0xAA, 11653 BROWSER_FAVORITES = 0xAB, 11654 BROWSER_HOME = 0xAC, 11655 VOLUME_MUTE = 0xAD, 11656 VOLUME_DOWN = 0xAE, 11657 VOLUME_UP = 0xAF, 11658 MEDIA_NEXT_TRACK = 0xB0, 11659 MEDIA_PREV_TRACK = 0xB1, 11660 MEDIA_STOP = 0xB2, 11661 MEDIA_PLAY_PAUSE = 0xB3, 11662 LAUNCH_MAIL = 0xB4, 11663 LAUNCH_MEDIA_SELECT = 0xB5, 11664 LAUNCH_APP1 = 0xB6, 11665 LAUNCH_APP2 = 0xB7, 11666 //} 11667 OEM_1 = 0xBA, 11668 //static if (_WIN32_WINNT > = 0x500) { 11669 OEM_PLUS = 0xBB, 11670 OEM_COMMA = 0xBC, 11671 OEM_MINUS = 0xBD, 11672 OEM_PERIOD = 0xBE, 11673 //} 11674 OEM_2 = 0xBF, 11675 OEM_3 = 0xC0, 11676 OEM_4 = 0xDB, 11677 OEM_5 = 0xDC, 11678 OEM_6 = 0xDD, 11679 OEM_7 = 0xDE, 11680 OEM_8 = 0xDF, 11681 //static if (_WIN32_WINNT > = 0x500) { 11682 OEM_102 = 0xE2, 11683 //} 11684 PROCESSKEY = 0xE5, 11685 //static if (_WIN32_WINNT > = 0x500) { 11686 PACKET = 0xE7, 11687 //} 11688 ATTN = 0xF6, 11689 CRSEL = 0xF7, 11690 EXSEL = 0xF8, 11691 EREOF = 0xF9, 11692 PLAY = 0xFA, 11693 ZOOM = 0xFB, 11694 NONAME = 0xFC, 11695 PA1 = 0xFD, 11696 OEM_CLEAR = 0xFE, 11697 } 11698 11699 } else version(OSXCocoa) { 11700 enum Key { 11701 Escape = 53, 11702 F1 = 122, 11703 F2 = 120, 11704 F3 = 99, 11705 F4 = 118, 11706 F5 = 96, 11707 F6 = 97, 11708 F7 = 98, 11709 F8 = 100, 11710 F9 = 101, 11711 F10 = 109, 11712 F11 = 103, 11713 F12 = 111, 11714 PrintScreen = 105, 11715 ScrollLock = 107, 11716 Pause = 113, 11717 Grave = 50, 11718 // number keys across the top of the keyboard 11719 N1 = 18, 11720 N2 = 19, 11721 N3 = 20, 11722 N4 = 21, 11723 N5 = 23, 11724 N6 = 22, 11725 N7 = 26, 11726 N8 = 28, 11727 N9 = 25, 11728 N0 = 29, 11729 Dash = 27, 11730 Equals = 24, 11731 Backslash = 42, 11732 Backspace = 51, 11733 Insert = 114, 11734 Home = 115, 11735 PageUp = 116, 11736 Delete = 117, 11737 End = 119, 11738 PageDown = 121, 11739 Up = 126, 11740 Down = 125, 11741 Left = 123, 11742 Right = 124, 11743 11744 Tab = 48, 11745 Q = 12, 11746 W = 13, 11747 E = 14, 11748 R = 15, 11749 T = 17, 11750 Y = 16, 11751 U = 32, 11752 I = 34, 11753 O = 31, 11754 P = 35, 11755 LeftBracket = 33, 11756 RightBracket = 30, 11757 CapsLock = 57, 11758 A = 0, 11759 S = 1, 11760 D = 2, 11761 F = 3, 11762 G = 5, 11763 H = 4, 11764 J = 38, 11765 K = 40, 11766 L = 37, 11767 Semicolon = 41, 11768 Apostrophe = 39, 11769 Enter = 36, 11770 Shift = 56, 11771 Z = 6, 11772 X = 7, 11773 C = 8, 11774 V = 9, 11775 B = 11, 11776 N = 45, 11777 M = 46, 11778 Comma = 43, 11779 Period = 47, 11780 Slash = 44, 11781 Shift_r = -4, // FIXME Note: this isn't sent on all computers, sometimes it just sends Shift, so don't rely on it 11782 Ctrl = 59, 11783 Windows = 55, 11784 Alt = 58, 11785 Space = 49, 11786 Alt_r = -3, // ditto of shift_r 11787 Windows_r = -2, 11788 Menu = 110, 11789 Ctrl_r = -1, 11790 11791 NumLock = 1, 11792 Divide = 75, 11793 Multiply = 67, 11794 Minus = 78, 11795 Plus = 69, 11796 PadEnter = 76, 11797 Pad1 = 83, 11798 Pad2 = 84, 11799 Pad3 = 85, 11800 Pad4 = 86, 11801 Pad5 = 87, 11802 Pad6 = 88, 11803 Pad7 = 89, 11804 Pad8 = 91, 11805 Pad9 = 92, 11806 Pad0 = 82, 11807 PadDot = 65, 11808 } 11809 11810 } 11811 11812 char keyToLetterCharAssumingLotsOfThingsThatYouMightBetterNotAssume(Key key) { 11813 version(OSXCocoa) { 11814 return char.init; // FIXME 11815 } else { 11816 return cast(char)(key - Key.A + 'a'); 11817 } 11818 } 11819 11820 /* Additional utilities */ 11821 11822 11823 Color fromHsl(real h, real s, real l) { 11824 return arsd.color.fromHsl([h,s,l]); 11825 } 11826 11827 11828 11829 /* ********** What follows is the system-specific implementations *********/ 11830 version(Windows) { 11831 11832 11833 // helpers for making HICONs from MemoryImages 11834 class WindowsIcon { 11835 struct Win32Icon { 11836 align(1): 11837 uint biSize; 11838 int biWidth; 11839 int biHeight; 11840 ushort biPlanes; 11841 ushort biBitCount; 11842 uint biCompression; 11843 uint biSizeImage; 11844 int biXPelsPerMeter; 11845 int biYPelsPerMeter; 11846 uint biClrUsed; 11847 uint biClrImportant; 11848 // RGBQUAD[colorCount] biColors; 11849 /* Pixels: 11850 Uint8 pixels[] 11851 */ 11852 /* Mask: 11853 Uint8 mask[] 11854 */ 11855 } 11856 11857 ubyte[] fromMemoryImage(MemoryImage mi, out int icon_len, out int width, out int height) { 11858 11859 assert(mi.width <= 256, "image too wide"); 11860 assert(mi.height <= 256, "image too tall"); 11861 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 11862 assert(mi.height % 4 == 0, "image not multiple of 4 height"); 11863 11864 int icon_plen = mi.width * mi.height * 4; 11865 int icon_mlen = mi.width * mi.height / 8; 11866 11867 int colorCount = 0; 11868 icon_len = 40 + icon_plen + icon_mlen + cast(int) RGBQUAD.sizeof * colorCount; 11869 11870 ubyte[] memory = new ubyte[](Win32Icon.sizeof + icon_plen + icon_mlen); 11871 Win32Icon* icon_win32 = cast(Win32Icon*) memory.ptr; 11872 11873 auto data = memory[Win32Icon.sizeof .. $]; 11874 11875 width = mi.width; 11876 height = mi.height; 11877 11878 auto trueColorImage = mi.getAsTrueColorImage(); 11879 11880 icon_win32.biSize = 40; 11881 icon_win32.biWidth = mi.width; 11882 icon_win32.biHeight = mi.height*2; 11883 icon_win32.biPlanes = 1; 11884 icon_win32.biBitCount = 32; 11885 icon_win32.biSizeImage = icon_plen + icon_mlen; 11886 11887 int offset = 0; 11888 int andOff = icon_plen * 8; // the and offset is in bits 11889 11890 // leaving the and mask as the default 0 so the rgba alpha blend 11891 // does its thing instead 11892 for(int y = height - 1; y >= 0; y--) { 11893 int off2 = y * width * 4; 11894 foreach(x; 0 .. width) { 11895 data[offset + 2] = trueColorImage.imageData.bytes[off2 + 0]; 11896 data[offset + 1] = trueColorImage.imageData.bytes[off2 + 1]; 11897 data[offset + 0] = trueColorImage.imageData.bytes[off2 + 2]; 11898 data[offset + 3] = trueColorImage.imageData.bytes[off2 + 3]; 11899 11900 offset += 4; 11901 off2 += 4; 11902 } 11903 } 11904 11905 return memory; 11906 } 11907 11908 this(MemoryImage mi) { 11909 int icon_len, width, height; 11910 11911 auto icon_win32 = fromMemoryImage(mi, icon_len, width, height); 11912 11913 /* 11914 PNG* png = readPnpngData); 11915 PNGHeader pngh = getHeader(png); 11916 void* icon_win32; 11917 if(pngh.depth == 4) { 11918 auto i = new Win32Icon!(16); 11919 i.fromPNG(png, pngh, icon_len, width, height); 11920 icon_win32 = i; 11921 } 11922 else if(pngh.depth == 8) { 11923 auto i = new Win32Icon!(256); 11924 i.fromPNG(png, pngh, icon_len, width, height); 11925 icon_win32 = i; 11926 } else assert(0); 11927 */ 11928 11929 hIcon = CreateIconFromResourceEx(icon_win32.ptr, icon_len, true, 0x00030000, width, height, 0); 11930 11931 if(hIcon is null) throw new WindowsApiException("CreateIconFromResourceEx", GetLastError()); 11932 } 11933 11934 ~this() { 11935 if(!thisIsGuiThread) return; // FIXME: leaks if multithreaded gc 11936 DestroyIcon(hIcon); 11937 } 11938 11939 HICON hIcon; 11940 } 11941 11942 11943 11944 11945 11946 11947 alias int delegate(HWND, UINT, WPARAM, LPARAM, out int) NativeEventHandler; 11948 alias HWND NativeWindowHandle; 11949 11950 extern(Windows) 11951 LRESULT WndProc(HWND hWnd, UINT iMessage, WPARAM wParam, LPARAM lParam) nothrow { 11952 try { 11953 if(SimpleWindow.handleNativeGlobalEvent !is null) { 11954 // it returns zero if the message is handled, so we won't do anything more there 11955 // do I like that though? 11956 int mustReturn; 11957 auto ret = SimpleWindow.handleNativeGlobalEvent(hWnd, iMessage, wParam, lParam, mustReturn); 11958 if(mustReturn) 11959 return ret; 11960 } 11961 11962 if(auto window = hWnd in CapableOfHandlingNativeEvent.nativeHandleMapping) { 11963 if(window.getNativeEventHandler !is null) { 11964 int mustReturn; 11965 auto ret = window.getNativeEventHandler()(hWnd, iMessage, wParam, lParam, mustReturn); 11966 if(mustReturn) 11967 return ret; 11968 } 11969 if(auto w = cast(SimpleWindow) (*window)) 11970 return w.windowProcedure(hWnd, iMessage, wParam, lParam); 11971 else 11972 return DefWindowProc(hWnd, iMessage, wParam, lParam); 11973 } else { 11974 return DefWindowProc(hWnd, iMessage, wParam, lParam); 11975 } 11976 } catch (Exception e) { 11977 try { 11978 sdpy_abort(e); 11979 return 0; 11980 } catch(Exception e) { assert(0); } 11981 } 11982 } 11983 11984 void sdpy_abort(Throwable e) nothrow { 11985 try 11986 MessageBoxA(null, (e.toString() ~ "\0").ptr, "Exception caught in WndProc", 0); 11987 catch(Exception e) 11988 MessageBoxA(null, "Exception.toString threw too!", "Exception caught in WndProc", 0); 11989 ExitProcess(1); 11990 } 11991 11992 mixin template NativeScreenPainterImplementation() { 11993 HDC hdc; 11994 HWND hwnd; 11995 //HDC windowHdc; 11996 HBITMAP oldBmp; 11997 11998 void create(PaintingHandle window) { 11999 hwnd = window; 12000 12001 if(auto sw = cast(SimpleWindow) this.window) { 12002 // drawing on a window, double buffer 12003 auto windowHdc = GetDC(hwnd); 12004 12005 auto buffer = sw.impl.buffer; 12006 if(buffer is null) { 12007 hdc = windowHdc; 12008 windowDc = true; 12009 } else { 12010 hdc = CreateCompatibleDC(windowHdc); 12011 12012 ReleaseDC(hwnd, windowHdc); 12013 12014 oldBmp = SelectObject(hdc, buffer); 12015 } 12016 } else { 12017 // drawing on something else, draw directly 12018 hdc = CreateCompatibleDC(null); 12019 SelectObject(hdc, window); 12020 } 12021 12022 // X doesn't draw a text background, so neither should we 12023 SetBkMode(hdc, TRANSPARENT); 12024 12025 ensureDefaultFontLoaded(); 12026 12027 if(defaultGuiFont) { 12028 SelectObject(hdc, defaultGuiFont); 12029 // DeleteObject(defaultGuiFont); 12030 } 12031 } 12032 12033 static HFONT defaultGuiFont; 12034 static void ensureDefaultFontLoaded() { 12035 static bool triedDefaultGuiFont = false; 12036 if(!triedDefaultGuiFont) { 12037 NONCLIENTMETRICS params; 12038 params.cbSize = params.sizeof; 12039 if(SystemParametersInfo(SPI_GETNONCLIENTMETRICS, params.sizeof, ¶ms, 0)) { 12040 defaultGuiFont = CreateFontIndirect(¶ms.lfMessageFont); 12041 } 12042 triedDefaultGuiFont = true; 12043 } 12044 } 12045 12046 private OperatingSystemFont _activeFont; 12047 12048 void setFont(OperatingSystemFont font) { 12049 _activeFont = font; 12050 if(font && font.font) { 12051 if(SelectObject(hdc, font.font) == HGDI_ERROR) { 12052 // error... how to handle tho? 12053 } else { 12054 12055 } 12056 } 12057 else if(defaultGuiFont) 12058 SelectObject(hdc, defaultGuiFont); 12059 } 12060 12061 arsd.color.Rectangle _clipRectangle; 12062 12063 void setClipRectangle(int x, int y, int width, int height) { 12064 auto old = _clipRectangle; 12065 _clipRectangle = arsd.color.Rectangle(Point(x, y), Size(width, height)); 12066 if(old == _clipRectangle) 12067 return; 12068 12069 if(width == 0 || height == 0) { 12070 SelectClipRgn(hdc, null); 12071 } else { 12072 auto region = CreateRectRgn(x, y, x + width, y + height); 12073 SelectClipRgn(hdc, region); 12074 DeleteObject(region); 12075 } 12076 } 12077 12078 12079 // just because we can on Windows... 12080 //void create(Image image); 12081 12082 void invalidateRect(Rectangle invalidRect) { 12083 RECT rect; 12084 rect.left = invalidRect.left; 12085 rect.right = invalidRect.right; 12086 rect.top = invalidRect.top; 12087 rect.bottom = invalidRect.bottom; 12088 InvalidateRect(hwnd, &rect, false); 12089 } 12090 bool manualInvalidations; 12091 12092 void dispose() { 12093 // FIXME: this.window.width/height is probably wrong 12094 // BitBlt(windowHdc, 0, 0, this.window.width, this.window.height, hdc, 0, 0, SRCCOPY); 12095 // ReleaseDC(hwnd, windowHdc); 12096 12097 // FIXME: it shouldn't invalidate the whole thing in all cases... it would be ideal to do this right 12098 if(cast(SimpleWindow) this.window) { 12099 if(!manualInvalidations) 12100 InvalidateRect(hwnd, cast(RECT*)null, false); // no need to erase bg as the whole thing gets bitblt'd ove 12101 } 12102 12103 if(originalPen !is null) 12104 SelectObject(hdc, originalPen); 12105 if(currentPen !is null) 12106 DeleteObject(currentPen); 12107 if(originalBrush !is null) 12108 SelectObject(hdc, originalBrush); 12109 if(currentBrush !is null) 12110 DeleteObject(currentBrush); 12111 12112 SelectObject(hdc, oldBmp); 12113 12114 if(windowDc) 12115 ReleaseDC(hwnd, hdc); 12116 else 12117 DeleteDC(hdc); 12118 12119 if(window.paintingFinishedDg !is null) 12120 window.paintingFinishedDg()(); 12121 } 12122 12123 bool windowDc; 12124 HPEN originalPen; 12125 HPEN currentPen; 12126 12127 Pen _activePen; 12128 12129 Color _outlineColor; 12130 12131 @property void pen(Pen p) { 12132 _activePen = p; 12133 _outlineColor = p.color; 12134 12135 HPEN pen; 12136 if(p.color.a == 0) { 12137 pen = GetStockObject(NULL_PEN); 12138 } else { 12139 int style = PS_SOLID; 12140 final switch(p.style) { 12141 case Pen.Style.Solid: 12142 style = PS_SOLID; 12143 break; 12144 case Pen.Style.Dashed: 12145 style = PS_DASH; 12146 break; 12147 case Pen.Style.Dotted: 12148 style = PS_DOT; 12149 break; 12150 } 12151 pen = CreatePen(style, p.width, RGB(p.color.r, p.color.g, p.color.b)); 12152 } 12153 auto orig = SelectObject(hdc, pen); 12154 if(originalPen is null) 12155 originalPen = orig; 12156 12157 if(currentPen !is null) 12158 DeleteObject(currentPen); 12159 12160 currentPen = pen; 12161 12162 // the outline is like a foreground since it's done that way on X 12163 SetTextColor(hdc, RGB(p.color.r, p.color.g, p.color.b)); 12164 12165 } 12166 12167 @property void rasterOp(RasterOp op) { 12168 int mode; 12169 final switch(op) { 12170 case RasterOp.normal: 12171 mode = R2_COPYPEN; 12172 break; 12173 case RasterOp.xor: 12174 mode = R2_XORPEN; 12175 break; 12176 } 12177 SetROP2(hdc, mode); 12178 } 12179 12180 HBRUSH originalBrush; 12181 HBRUSH currentBrush; 12182 Color _fillColor = Color(1, 1, 1, 1); // what are the odds that they'd set this?? 12183 @property void fillColor(Color c) { 12184 if(c == _fillColor) 12185 return; 12186 _fillColor = c; 12187 HBRUSH brush; 12188 if(c.a == 0) { 12189 brush = GetStockObject(HOLLOW_BRUSH); 12190 } else { 12191 brush = CreateSolidBrush(RGB(c.r, c.g, c.b)); 12192 } 12193 auto orig = SelectObject(hdc, brush); 12194 if(originalBrush is null) 12195 originalBrush = orig; 12196 12197 if(currentBrush !is null) 12198 DeleteObject(currentBrush); 12199 12200 currentBrush = brush; 12201 12202 // background color is NOT set because X doesn't draw text backgrounds 12203 // SetBkColor(hdc, RGB(255, 255, 255)); 12204 } 12205 12206 void drawImage(int x, int y, Image i, int ix, int iy, int w, int h) { 12207 BITMAP bm; 12208 12209 HDC hdcMem = CreateCompatibleDC(hdc); 12210 HBITMAP hbmOld = SelectObject(hdcMem, i.handle); 12211 12212 GetObject(i.handle, bm.sizeof, &bm); 12213 12214 // or should I AlphaBlend!??!?! 12215 BitBlt(hdc, x, y, w /* bm.bmWidth */, /*bm.bmHeight*/ h, hdcMem, ix, iy, SRCCOPY); 12216 12217 SelectObject(hdcMem, hbmOld); 12218 DeleteDC(hdcMem); 12219 } 12220 12221 void drawPixmap(Sprite s, int x, int y, int ix, int iy, int w, int h) { 12222 BITMAP bm; 12223 12224 HDC hdcMem = CreateCompatibleDC(hdc); 12225 HBITMAP hbmOld = SelectObject(hdcMem, s.handle); 12226 12227 GetObject(s.handle, bm.sizeof, &bm); 12228 12229 version(CRuntime_DigitalMars) goto noalpha; 12230 12231 // or should I AlphaBlend!??!?! note it is supposed to be premultiplied http://www.fengyuan.com/article/alphablend.html 12232 if(s.enableAlpha) { 12233 auto dw = w ? w : bm.bmWidth; 12234 auto dh = h ? h : bm.bmHeight; 12235 BLENDFUNCTION bf; 12236 bf.BlendOp = AC_SRC_OVER; 12237 bf.SourceConstantAlpha = 255; 12238 bf.AlphaFormat = AC_SRC_ALPHA; 12239 AlphaBlend(hdc, x, y, dw, dh, hdcMem, ix, iy, dw, dh, bf); 12240 } else { 12241 noalpha: 12242 BitBlt(hdc, x, y, w ? w : bm.bmWidth, h ? h : bm.bmHeight, hdcMem, ix, iy, SRCCOPY); 12243 } 12244 12245 SelectObject(hdcMem, hbmOld); 12246 DeleteDC(hdcMem); 12247 } 12248 12249 Size textSize(scope const(char)[] text) { 12250 bool dummyX; 12251 if(text.length == 0) { 12252 text = " "; 12253 dummyX = true; 12254 } 12255 RECT rect; 12256 WCharzBuffer buffer = WCharzBuffer(text); 12257 DrawTextW(hdc, buffer.ptr, cast(int) buffer.length, &rect, DT_CALCRECT | DT_NOPREFIX); 12258 return Size(dummyX ? 0 : rect.right, rect.bottom); 12259 } 12260 12261 void drawText(int x, int y, int x2, int y2, scope const(char)[] text, uint alignment) { 12262 if(text.length && text[$-1] == '\n') 12263 text = text[0 .. $-1]; // tailing newlines are weird on windows... 12264 if(text.length && text[$-1] == '\r') 12265 text = text[0 .. $-1]; 12266 12267 WCharzBuffer buffer = WCharzBuffer(text, WindowsStringConversionFlags.convertNewLines); 12268 if(x2 == 0 && y2 == 0) { 12269 TextOutW(hdc, x, y, buffer.ptr, cast(int) buffer.length); 12270 } else { 12271 RECT rect; 12272 rect.left = x; 12273 rect.top = y; 12274 rect.right = x2; 12275 rect.bottom = y2; 12276 12277 uint mode = DT_LEFT; 12278 if(alignment & TextAlignment.Right) 12279 mode = DT_RIGHT; 12280 else if(alignment & TextAlignment.Center) 12281 mode = DT_CENTER; 12282 12283 // FIXME: vcenter on windows only works with single line, but I want it to work in all cases 12284 if(alignment & TextAlignment.VerticalCenter) 12285 mode |= DT_VCENTER | DT_SINGLELINE; 12286 12287 DrawTextW(hdc, buffer.ptr, cast(int) buffer.length, &rect, mode | DT_NOPREFIX); 12288 } 12289 12290 /* 12291 uint mode; 12292 12293 if(alignment & TextAlignment.Center) 12294 mode = TA_CENTER; 12295 12296 SetTextAlign(hdc, mode); 12297 */ 12298 } 12299 12300 int fontHeight() { 12301 TEXTMETRIC metric; 12302 if(GetTextMetricsW(hdc, &metric)) { 12303 return metric.tmHeight; 12304 } 12305 12306 return 16; // idk just guessing here, maybe we should throw 12307 } 12308 12309 void drawPixel(int x, int y) { 12310 SetPixel(hdc, x, y, RGB(_activePen.color.r, _activePen.color.g, _activePen.color.b)); 12311 } 12312 12313 // The basic shapes, outlined 12314 12315 void drawLine(int x1, int y1, int x2, int y2) { 12316 MoveToEx(hdc, x1, y1, null); 12317 LineTo(hdc, x2, y2); 12318 } 12319 12320 void drawRectangle(int x, int y, int width, int height) { 12321 // FIXME: with a wider pen this might not draw quite right. im not sure. 12322 gdi.Rectangle(hdc, x, y, x + width, y + height); 12323 } 12324 12325 void drawRectangleRounded(Point upperLeft, Point lowerRight, int borderRadius) { 12326 RoundRect( 12327 hdc, 12328 upperLeft.x, upperLeft.y, 12329 lowerRight.x, lowerRight.y, 12330 borderRadius, borderRadius 12331 ); 12332 } 12333 12334 /// Arguments are the points of the bounding rectangle 12335 void drawEllipse(int x1, int y1, int x2, int y2) { 12336 Ellipse(hdc, x1, y1, x2, y2); 12337 } 12338 12339 void drawArc(int x1, int y1, int width, int height, int start, int length) { 12340 //if(length > 360*64) 12341 //length = 360*64; 12342 12343 if((start == 0 && length == 360*64)) { 12344 drawEllipse(x1, y1, x1 + width, y1 + height); 12345 } else { 12346 import core.stdc.math; 12347 12348 bool clockwise = false; 12349 if(length < 0) { 12350 clockwise = true; 12351 length = -length; 12352 } 12353 12354 double startAngle = cast(double) start / 64.0 / 180.0 * 3.14159265358979323; 12355 double endAngle = cast(double) (start + length) / 64.0 / 180.0 * 3.14159265358979323; 12356 12357 auto c1 = cast(int) (cos(startAngle) * width / 2.0 + double(x1) + double(width) / 2.0); 12358 auto c2 = cast(int) (-sin(startAngle) * height / 2.0 + double(y1) + double(height) / 2.0); 12359 auto c3 = cast(int) (cos(endAngle) * width / 2.0 + double(x1) + double(width) / 2.0); 12360 auto c4 = cast(int) (-sin(endAngle) * height / 2.0 + double(y1) + double(height) / 2.0); 12361 12362 if(clockwise) { 12363 auto t1 = c1; 12364 auto t2 = c2; 12365 c1 = c3; 12366 c2 = c4; 12367 c3 = t1; 12368 c4 = t2; 12369 } 12370 12371 //if(_activePen.color.a) 12372 //Arc(hdc, x1, y1, x1 + width + 0, y1 + height + 0, c1, c2, c3, c4); 12373 //if(_fillColor.a) 12374 12375 Pie(hdc, x1, y1, x1 + width + 0, y1 + height + 0, c1, c2, c3, c4); 12376 } 12377 } 12378 12379 void drawPolygon(Point[] vertexes) { 12380 POINT[] points; 12381 points.length = vertexes.length; 12382 12383 foreach(i, p; vertexes) { 12384 points[i].x = p.x; 12385 points[i].y = p.y; 12386 } 12387 12388 Polygon(hdc, points.ptr, cast(int) points.length); 12389 } 12390 } 12391 12392 12393 // Mix this into the SimpleWindow class 12394 mixin template NativeSimpleWindowImplementation() { 12395 int curHidden = 0; // counter 12396 __gshared static bool[string] knownWinClasses; 12397 static bool altPressed = false; 12398 12399 HANDLE oldCursor; 12400 12401 void hideCursor () { 12402 if(curHidden == 0) 12403 oldCursor = SetCursor(null); 12404 ++curHidden; 12405 } 12406 12407 void showCursor () { 12408 --curHidden; 12409 if(curHidden == 0) { 12410 SetCursor(currentCursor is null ? oldCursor : currentCursor); // show it immediately without waiting for mouse movement 12411 } 12412 } 12413 12414 12415 int minWidth = 0, minHeight = 0, maxWidth = int.max, maxHeight = int.max; 12416 12417 void setMinSize (int minwidth, int minheight) { 12418 minWidth = minwidth; 12419 minHeight = minheight; 12420 } 12421 void setMaxSize (int maxwidth, int maxheight) { 12422 maxWidth = maxwidth; 12423 maxHeight = maxheight; 12424 } 12425 12426 // FIXME i'm not sure that Windows has this functionality 12427 // though it is nonessential anyway. 12428 void setResizeGranularity (int granx, int grany) {} 12429 12430 ScreenPainter getPainter(bool manualInvalidations) { 12431 return ScreenPainter(this, hwnd, manualInvalidations); 12432 } 12433 12434 HBITMAP buffer; 12435 12436 void setTitle(string title) { 12437 WCharzBuffer bfr = WCharzBuffer(title); 12438 SetWindowTextW(hwnd, bfr.ptr); 12439 } 12440 12441 string getTitle() { 12442 auto len = GetWindowTextLengthW(hwnd); 12443 if (!len) 12444 return null; 12445 wchar[256] tmpBuffer; 12446 wchar[] buffer = (len <= tmpBuffer.length) ? tmpBuffer[] : new wchar[len]; 12447 auto len2 = GetWindowTextW(hwnd, buffer.ptr, cast(int) buffer.length); 12448 auto str = buffer[0 .. len2]; 12449 return makeUtf8StringFromWindowsString(str); 12450 } 12451 12452 void move(int x, int y) { 12453 RECT rect; 12454 GetWindowRect(hwnd, &rect); 12455 // move it while maintaining the same size... 12456 MoveWindow(hwnd, x, y, rect.right - rect.left, rect.bottom - rect.top, true); 12457 } 12458 12459 void resize(int w, int h) { 12460 RECT rect; 12461 GetWindowRect(hwnd, &rect); 12462 12463 RECT client; 12464 GetClientRect(hwnd, &client); 12465 12466 rect.right = rect.right - client.right + w; 12467 rect.bottom = rect.bottom - client.bottom + h; 12468 12469 // same position, new size for the client rectangle 12470 MoveWindow(hwnd, rect.left, rect.top, rect.right, rect.bottom, true); 12471 12472 updateOpenglViewportIfNeeded(w, h); 12473 } 12474 12475 void moveResize (int x, int y, int w, int h) { 12476 // what's given is the client rectangle, we need to adjust 12477 12478 RECT rect; 12479 rect.left = x; 12480 rect.top = y; 12481 rect.right = w + x; 12482 rect.bottom = h + y; 12483 if(!AdjustWindowRect(&rect, GetWindowLong(hwnd, GWL_STYLE), GetMenu(hwnd) !is null)) 12484 throw new WindowsApiException("AdjustWindowRect", GetLastError()); 12485 12486 MoveWindow(hwnd, rect.left, rect.top, rect.right - rect.left, rect.bottom - rect.top, true); 12487 updateOpenglViewportIfNeeded(w, h); 12488 if (windowResized !is null) windowResized(w, h); 12489 } 12490 12491 version(without_opengl) {} else { 12492 HGLRC ghRC; 12493 HDC ghDC; 12494 } 12495 12496 void createWindow(int width, int height, string title, OpenGlOptions opengl, SimpleWindow parent) { 12497 string cnamec; 12498 if (sdpyWindowClassStr is null) loadBinNameToWindowClassName(); 12499 if (sdpyWindowClassStr is null || sdpyWindowClassStr[0] == 0) { 12500 cnamec = "DSimpleWindow"; 12501 } else { 12502 cnamec = sdpyWindowClass; 12503 } 12504 12505 WCharzBuffer cn = WCharzBuffer(cnamec); 12506 12507 HINSTANCE hInstance = cast(HINSTANCE) GetModuleHandle(null); 12508 12509 if(cnamec !in knownWinClasses) { 12510 WNDCLASSEX wc; 12511 12512 // FIXME: I might be able to use cbWndExtra to hold the pointer back 12513 // to the object. Maybe. 12514 wc.cbSize = wc.sizeof; 12515 wc.cbClsExtra = 0; 12516 wc.cbWndExtra = 0; 12517 wc.hbrBackground = cast(HBRUSH) (COLOR_WINDOW+1); // GetStockObject(WHITE_BRUSH); 12518 wc.hCursor = LoadCursorW(null, IDC_ARROW); 12519 wc.hIcon = LoadIcon(hInstance, null); 12520 wc.hInstance = hInstance; 12521 wc.lpfnWndProc = &WndProc; 12522 wc.lpszClassName = cn.ptr; 12523 wc.hIconSm = null; 12524 wc.style = CS_HREDRAW | CS_VREDRAW | CS_DBLCLKS; 12525 if(!RegisterClassExW(&wc)) 12526 throw new WindowsApiException("RegisterClassExW", GetLastError()); 12527 knownWinClasses[cnamec] = true; 12528 } 12529 12530 int style; 12531 uint flags = WS_EX_ACCEPTFILES; // accept drag-drop files 12532 12533 // FIXME: windowType and customizationFlags 12534 final switch(windowType) { 12535 case WindowTypes.normal: 12536 if(resizability == Resizability.fixedSize) { 12537 style = WS_SYSMENU | WS_OVERLAPPED | WS_CAPTION; 12538 } else { 12539 style = WS_OVERLAPPEDWINDOW; 12540 } 12541 break; 12542 case WindowTypes.undecorated: 12543 style = WS_POPUP | WS_SYSMENU; 12544 break; 12545 case WindowTypes.eventOnly: 12546 _hidden = true; 12547 break; 12548 case WindowTypes.tooltip: 12549 case WindowTypes.dnd: 12550 case WindowTypes.comboBoxDropdown: 12551 case WindowTypes.dropdownMenu: 12552 case WindowTypes.popupMenu: 12553 case WindowTypes.notification: 12554 style = WS_POPUP; 12555 flags |= WS_EX_NOACTIVATE; 12556 break; 12557 case WindowTypes.dialog: 12558 style = WS_OVERLAPPEDWINDOW; 12559 break; 12560 case WindowTypes.nestedChild: 12561 style = WS_CHILD; 12562 break; 12563 case WindowTypes.minimallyWrapped: 12564 assert(0, "construct minimally wrapped through the other ctor overlad"); 12565 } 12566 12567 if ((customizationFlags & WindowFlags.extraComposite) != 0) 12568 flags |= WS_EX_LAYERED; // composite window for better performance and effects support 12569 12570 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 12571 CW_USEDEFAULT, CW_USEDEFAULT, width, height, 12572 parent is null ? null : parent.impl.hwnd, null, hInstance, null); 12573 12574 if(!hwnd) 12575 throw new WindowsApiException("CreateWindowEx", GetLastError()); 12576 12577 if ((customizationFlags & WindowFlags.extraComposite) != 0) 12578 setOpacity(255); 12579 12580 SimpleWindow.nativeMapping[hwnd] = this; 12581 CapableOfHandlingNativeEvent.nativeHandleMapping[hwnd] = this; 12582 12583 if(windowType == WindowTypes.eventOnly) 12584 return; 12585 12586 HDC hdc = GetDC(hwnd); 12587 12588 if(!hdc) 12589 throw new WindowsApiException("GetDC", GetLastError()); 12590 12591 version(without_opengl) {} 12592 else { 12593 if(opengl == OpenGlOptions.yes) { 12594 if(!openGlLibrariesSuccessfullyLoaded) throw new Exception("OpenGL libraries did not load"); 12595 static if (SdpyIsUsingIVGLBinds) {if (glbindGetProcAddress("glHint") is null) assert(0, "GL: error loading OpenGL"); } // loads all necessary functions 12596 static if (SdpyIsUsingIVGLBinds) import iv.glbinds; // override druntime windows imports 12597 ghDC = hdc; 12598 PIXELFORMATDESCRIPTOR pfd; 12599 12600 pfd.nSize = PIXELFORMATDESCRIPTOR.sizeof; 12601 pfd.nVersion = 1; 12602 pfd.dwFlags = PFD_DRAW_TO_WINDOW | PFD_SUPPORT_OPENGL | PFD_DOUBLEBUFFER; 12603 pfd.dwLayerMask = PFD_MAIN_PLANE; 12604 pfd.iPixelType = PFD_TYPE_RGBA; 12605 pfd.cColorBits = 24; 12606 pfd.cDepthBits = 24; 12607 pfd.cAccumBits = 0; 12608 pfd.cStencilBits = 8; // any reasonable OpenGL implementation should support this anyway 12609 12610 auto pixelformat = ChoosePixelFormat(hdc, &pfd); 12611 12612 if (pixelformat == 0) 12613 throw new WindowsApiException("ChoosePixelFormat", GetLastError()); 12614 12615 if (SetPixelFormat(hdc, pixelformat, &pfd) == 0) 12616 throw new WindowsApiException("SetPixelFormat", GetLastError()); 12617 12618 if (sdpyOpenGLContextVersion && wglCreateContextAttribsARB is null) { 12619 // windoze is idiotic: we have to have OpenGL context to get function addresses 12620 // so we will create fake context to get that stupid address 12621 auto tmpcc = wglCreateContext(ghDC); 12622 if (tmpcc !is null) { 12623 scope(exit) { wglMakeCurrent(ghDC, null); wglDeleteContext(tmpcc); } 12624 wglMakeCurrent(ghDC, tmpcc); 12625 wglInitOtherFunctions(); 12626 } 12627 } 12628 12629 if (wglCreateContextAttribsARB !is null && sdpyOpenGLContextVersion) { 12630 int[9] contextAttribs = [ 12631 WGL_CONTEXT_MAJOR_VERSION_ARB, (sdpyOpenGLContextVersion>>8), 12632 WGL_CONTEXT_MINOR_VERSION_ARB, (sdpyOpenGLContextVersion&0xff), 12633 WGL_CONTEXT_PROFILE_MASK_ARB, (sdpyOpenGLContextCompatible ? WGL_CONTEXT_COMPATIBILITY_PROFILE_BIT_ARB : WGL_CONTEXT_CORE_PROFILE_BIT_ARB), 12634 // for modern context, set "forward compatibility" flag too 12635 (sdpyOpenGLContextCompatible ? 0/*None*/ : WGL_CONTEXT_FLAGS_ARB), WGL_CONTEXT_FORWARD_COMPATIBLE_BIT_ARB, 12636 0/*None*/, 12637 ]; 12638 ghRC = wglCreateContextAttribsARB(ghDC, null, contextAttribs.ptr); 12639 if (ghRC is null && sdpyOpenGLContextAllowFallback) { 12640 // activate fallback mode 12641 // 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; 12642 ghRC = wglCreateContext(ghDC); 12643 } 12644 if (ghRC is null) 12645 throw new WindowsApiException("wglCreateContextAttribsARB", GetLastError()); 12646 } else { 12647 // try to do at least something 12648 if (sdpyOpenGLContextAllowFallback || sdpyOpenGLContextVersion == 0) { 12649 sdpyOpenGLContextVersion = 0; 12650 ghRC = wglCreateContext(ghDC); 12651 } 12652 if (ghRC is null) 12653 throw new WindowsApiException("wglCreateContext", GetLastError()); 12654 } 12655 } 12656 } 12657 12658 if(opengl == OpenGlOptions.no) { 12659 buffer = CreateCompatibleBitmap(hdc, width, height); 12660 12661 auto hdcBmp = CreateCompatibleDC(hdc); 12662 // make sure it's filled with a blank slate 12663 auto oldBmp = SelectObject(hdcBmp, buffer); 12664 auto oldBrush = SelectObject(hdcBmp, GetStockObject(WHITE_BRUSH)); 12665 auto oldPen = SelectObject(hdcBmp, GetStockObject(WHITE_PEN)); 12666 gdi.Rectangle(hdcBmp, 0, 0, width, height); 12667 SelectObject(hdcBmp, oldBmp); 12668 SelectObject(hdcBmp, oldBrush); 12669 SelectObject(hdcBmp, oldPen); 12670 DeleteDC(hdcBmp); 12671 12672 bmpWidth = width; 12673 bmpHeight = height; 12674 12675 ReleaseDC(hwnd, hdc); // we keep this in opengl mode since it is a class member now 12676 } 12677 12678 // We want the window's client area to match the image size 12679 RECT rcClient, rcWindow; 12680 POINT ptDiff; 12681 GetClientRect(hwnd, &rcClient); 12682 GetWindowRect(hwnd, &rcWindow); 12683 ptDiff.x = (rcWindow.right - rcWindow.left) - rcClient.right; 12684 ptDiff.y = (rcWindow.bottom - rcWindow.top) - rcClient.bottom; 12685 MoveWindow(hwnd,rcWindow.left, rcWindow.top, width + ptDiff.x, height + ptDiff.y, true); 12686 12687 if ((customizationFlags&WindowFlags.dontAutoShow) == 0) { 12688 ShowWindow(hwnd, SW_SHOWNORMAL); 12689 } else { 12690 _hidden = true; 12691 } 12692 this._visibleForTheFirstTimeCalled = false; // hack! 12693 } 12694 12695 12696 void dispose() { 12697 if(buffer) 12698 DeleteObject(buffer); 12699 } 12700 12701 void closeWindow() { 12702 if(ghRC) { 12703 wglDeleteContext(ghRC); 12704 ghRC = null; 12705 } 12706 DestroyWindow(hwnd); 12707 } 12708 12709 bool setOpacity(ubyte alpha) { 12710 return SetLayeredWindowAttributes(hwnd, 0, alpha, LWA_ALPHA) == TRUE; 12711 } 12712 12713 HANDLE currentCursor; 12714 12715 // returns zero if it recognized the event 12716 static int triggerEvents(HWND hwnd, uint msg, WPARAM wParam, LPARAM lParam, int offsetX, int offsetY, SimpleWindow wind) { 12717 MouseEvent mouse; 12718 12719 void mouseEvent(bool isScreen, ulong mods) { 12720 auto x = LOWORD(lParam); 12721 auto y = HIWORD(lParam); 12722 if(isScreen) { 12723 POINT p; 12724 p.x = x; 12725 p.y = y; 12726 ScreenToClient(hwnd, &p); 12727 x = cast(ushort) p.x; 12728 y = cast(ushort) p.y; 12729 } 12730 12731 if(wind.resizability == Resizability.automaticallyScaleIfPossible) { 12732 x = cast(ushort)( x * wind._virtualWidth / wind._width ); 12733 y = cast(ushort)( y * wind._virtualHeight / wind._height ); 12734 } 12735 12736 mouse.x = x + offsetX; 12737 mouse.y = y + offsetY; 12738 12739 wind.mdx(mouse); 12740 mouse.modifierState = cast(int) mods; 12741 mouse.window = wind; 12742 12743 if(wind.handleMouseEvent) 12744 wind.handleMouseEvent(mouse); 12745 } 12746 12747 switch(msg) { 12748 case WM_GETMINMAXINFO: 12749 MINMAXINFO* mmi = cast(MINMAXINFO*) lParam; 12750 12751 if(wind.minWidth > 0) { 12752 RECT rect; 12753 rect.left = 100; 12754 rect.top = 100; 12755 rect.right = wind.minWidth + 100; 12756 rect.bottom = wind.minHeight + 100; 12757 if(!AdjustWindowRect(&rect, GetWindowLong(wind.hwnd, GWL_STYLE), GetMenu(wind.hwnd) !is null)) 12758 throw new WindowsApiException("AdjustWindowRect", GetLastError()); 12759 12760 mmi.ptMinTrackSize.x = rect.right - rect.left; 12761 mmi.ptMinTrackSize.y = rect.bottom - rect.top; 12762 } 12763 12764 if(wind.maxWidth < int.max) { 12765 RECT rect; 12766 rect.left = 100; 12767 rect.top = 100; 12768 rect.right = wind.maxWidth + 100; 12769 rect.bottom = wind.maxHeight + 100; 12770 if(!AdjustWindowRect(&rect, GetWindowLong(wind.hwnd, GWL_STYLE), GetMenu(wind.hwnd) !is null)) 12771 throw new WindowsApiException("AdjustWindowRect", GetLastError()); 12772 12773 mmi.ptMaxTrackSize.x = rect.right - rect.left; 12774 mmi.ptMaxTrackSize.y = rect.bottom - rect.top; 12775 } 12776 break; 12777 case WM_CHAR: 12778 wchar c = cast(wchar) wParam; 12779 if(wind.handleCharEvent) 12780 wind.handleCharEvent(cast(dchar) c); 12781 break; 12782 case WM_SETFOCUS: 12783 case WM_KILLFOCUS: 12784 wind._focused = (msg == WM_SETFOCUS); 12785 if (msg == WM_SETFOCUS) altPressed = false; //k8: reset alt state on defocus (it is better than nothing...) 12786 if(wind.onFocusChange) 12787 wind.onFocusChange(msg == WM_SETFOCUS); 12788 break; 12789 12790 case WM_SYSKEYDOWN: 12791 goto case; 12792 case WM_SYSKEYUP: 12793 if(lParam & (1 << 29)) { 12794 goto case; 12795 } else { 12796 // no window has keyboard focus 12797 goto default; 12798 } 12799 case WM_KEYDOWN: 12800 case WM_KEYUP: 12801 KeyEvent ev; 12802 ev.key = cast(Key) wParam; 12803 ev.pressed = (msg == WM_KEYDOWN || msg == WM_SYSKEYDOWN); 12804 if (wParam == 0x12) ev.key = Key.Alt; // windows does it this way 12805 12806 ev.hardwareCode = (lParam & 0xff0000) >> 16; 12807 12808 if(GetKeyState(Key.Shift)&0x8000 || GetKeyState(Key.Shift_r)&0x8000) 12809 ev.modifierState |= ModifierState.shift; 12810 //k8: this doesn't work; thanks for nothing, windows 12811 /*if(GetKeyState(Key.Alt)&0x8000 || GetKeyState(Key.Alt_r)&0x8000) 12812 ev.modifierState |= ModifierState.alt;*/ 12813 // this never seems to actually be set 12814 // if (lParam & 0x2000 /* KF_ALTDOWN */) ev.modifierState |= ModifierState.alt; else ev.modifierState &= ~ModifierState.alt; 12815 12816 if (wParam == 0x12) { 12817 altPressed = (msg == WM_SYSKEYDOWN); 12818 } 12819 12820 if(msg == WM_KEYDOWN || msg == WM_KEYUP) { 12821 altPressed = false; 12822 } 12823 // sdpyPrintDebugString(altPressed ? "alt down" : " up "); 12824 12825 if (altPressed) ev.modifierState |= ModifierState.alt; else ev.modifierState &= ~ModifierState.alt; 12826 if(GetKeyState(Key.Ctrl)&0x8000 || GetKeyState(Key.Ctrl_r)&0x8000) 12827 ev.modifierState |= ModifierState.ctrl; 12828 if(GetKeyState(Key.Windows)&0x8000 || GetKeyState(Key.Windows_r)&0x8000) 12829 ev.modifierState |= ModifierState.windows; 12830 if(GetKeyState(Key.NumLock)) 12831 ev.modifierState |= ModifierState.numLock; 12832 if(GetKeyState(Key.CapsLock)) 12833 ev.modifierState |= ModifierState.capsLock; 12834 12835 /+ 12836 // we always want to send the character too, so let's convert it 12837 ubyte[256] state; 12838 wchar[16] buffer; 12839 GetKeyboardState(state.ptr); 12840 ToUnicodeEx(wParam, lParam, state.ptr, buffer.ptr, buffer.length, 0, null); 12841 12842 foreach(dchar d; buffer) { 12843 ev.character = d; 12844 break; 12845 } 12846 +/ 12847 12848 ev.window = wind; 12849 if(wind.handleKeyEvent) 12850 wind.handleKeyEvent(ev); 12851 break; 12852 case 0x020a /*WM_MOUSEWHEEL*/: 12853 // send click 12854 mouse.type = cast(MouseEventType) 1; 12855 mouse.button = ((GET_WHEEL_DELTA_WPARAM(wParam) > 0) ? MouseButton.wheelUp : MouseButton.wheelDown); 12856 mouseEvent(true, LOWORD(wParam)); 12857 12858 // also send release 12859 mouse.type = cast(MouseEventType) 2; 12860 mouse.button = ((GET_WHEEL_DELTA_WPARAM(wParam) > 0) ? MouseButton.wheelUp : MouseButton.wheelDown); 12861 mouseEvent(true, LOWORD(wParam)); 12862 break; 12863 case WM_MOUSEMOVE: 12864 mouse.type = cast(MouseEventType) 0; 12865 mouseEvent(false, wParam); 12866 break; 12867 case WM_LBUTTONDOWN: 12868 case WM_LBUTTONDBLCLK: 12869 mouse.type = cast(MouseEventType) 1; 12870 mouse.button = MouseButton.left; 12871 mouse.doubleClick = msg == WM_LBUTTONDBLCLK; 12872 mouseEvent(false, wParam); 12873 break; 12874 case WM_LBUTTONUP: 12875 mouse.type = cast(MouseEventType) 2; 12876 mouse.button = MouseButton.left; 12877 mouseEvent(false, wParam); 12878 break; 12879 case WM_RBUTTONDOWN: 12880 case WM_RBUTTONDBLCLK: 12881 mouse.type = cast(MouseEventType) 1; 12882 mouse.button = MouseButton.right; 12883 mouse.doubleClick = msg == WM_RBUTTONDBLCLK; 12884 mouseEvent(false, wParam); 12885 break; 12886 case WM_RBUTTONUP: 12887 mouse.type = cast(MouseEventType) 2; 12888 mouse.button = MouseButton.right; 12889 mouseEvent(false, wParam); 12890 break; 12891 case WM_MBUTTONDOWN: 12892 case WM_MBUTTONDBLCLK: 12893 mouse.type = cast(MouseEventType) 1; 12894 mouse.button = MouseButton.middle; 12895 mouse.doubleClick = msg == WM_MBUTTONDBLCLK; 12896 mouseEvent(false, wParam); 12897 break; 12898 case WM_MBUTTONUP: 12899 mouse.type = cast(MouseEventType) 2; 12900 mouse.button = MouseButton.middle; 12901 mouseEvent(false, wParam); 12902 break; 12903 case WM_XBUTTONDOWN: 12904 case WM_XBUTTONDBLCLK: 12905 mouse.type = cast(MouseEventType) 1; 12906 mouse.button = HIWORD(wParam) == 1 ? MouseButton.backButton : MouseButton.forwardButton; 12907 mouse.doubleClick = msg == WM_XBUTTONDBLCLK; 12908 mouseEvent(false, wParam); 12909 return 1; // MSDN says special treatment here, return TRUE to bypass simulation programs 12910 case WM_XBUTTONUP: 12911 mouse.type = cast(MouseEventType) 2; 12912 mouse.button = HIWORD(wParam) == 1 ? MouseButton.backButton : MouseButton.forwardButton; 12913 mouseEvent(false, wParam); 12914 return 1; // see: https://msdn.microsoft.com/en-us/library/windows/desktop/ms646246(v=vs.85).aspx 12915 12916 default: return 1; 12917 } 12918 return 0; 12919 } 12920 12921 HWND hwnd; 12922 private int oldWidth; 12923 private int oldHeight; 12924 private bool inSizeMove; 12925 12926 /++ 12927 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. 12928 12929 History: 12930 Added November 23, 2021 12931 12932 Not fully stable, may be moved out of the impl struct. 12933 12934 Default value changed to `true` on February 15, 2021 12935 +/ 12936 bool doLiveResizing = true; 12937 12938 package int bmpWidth; 12939 package int bmpHeight; 12940 12941 // the extern(Windows) wndproc should just forward to this 12942 LRESULT windowProcedure(HWND hwnd, uint msg, WPARAM wParam, LPARAM lParam) { 12943 try { 12944 assert(hwnd is this.hwnd); 12945 12946 if(triggerEvents(hwnd, msg, wParam, lParam, 0, 0, this)) 12947 switch(msg) { 12948 case WM_MENUCHAR: // menu active but key not associated with a thing. 12949 // you would ideally use this for like a search function but sdpy not that ideally designed. alas. 12950 // The main things we can do are select, execute, close, or ignore 12951 // the default is ignore, but it doesn't *just* ignore it - it also dings an audio alert to 12952 // the user. This can be a bit annoying for sdpy things so instead im overriding and setting it 12953 // to close, which can be really annoying when you hit the wrong button. but meh i think for sdpy 12954 // that's the lesser bad choice rn. Can always override by returning true in triggerEvents.... 12955 12956 // returns the value in the *high order word* of the return value 12957 // hence the << 16 12958 return 1 << 16; // MNC_CLOSE, close the menu without dinging at the user 12959 case WM_SETCURSOR: 12960 if(cast(HWND) wParam !is hwnd) 12961 return 0; // further processing elsewhere 12962 12963 if(LOWORD(lParam) == HTCLIENT && (this.curHidden > 0 || currentCursor !is null)) { 12964 SetCursor(this.curHidden > 0 ? null : currentCursor); 12965 return 1; 12966 } else { 12967 return DefWindowProc(hwnd, msg, wParam, lParam); 12968 } 12969 //break; 12970 12971 case WM_CLOSE: 12972 if (this.closeQuery !is null) this.closeQuery(); else this.close(); 12973 break; 12974 case WM_DESTROY: 12975 if (this.visibilityChanged !is null && this._visible) this.visibilityChanged(false); 12976 12977 if (this.onDestroyed !is null) try { this.onDestroyed(); } catch (Exception e) {} // sorry 12978 SimpleWindow.nativeMapping.remove(hwnd); 12979 CapableOfHandlingNativeEvent.nativeHandleMapping.remove(hwnd); 12980 12981 bool anyImportant = false; 12982 foreach(SimpleWindow w; SimpleWindow.nativeMapping) 12983 if(w.beingOpenKeepsAppOpen) { 12984 anyImportant = true; 12985 break; 12986 } 12987 if(!anyImportant) { 12988 PostQuitMessage(0); 12989 } 12990 break; 12991 case 0x02E0 /*WM_DPICHANGED*/: 12992 this.actualDpi_ = LOWORD(wParam); // hiword is the y param but it is the same per docs 12993 12994 RECT* prcNewWindow = cast(RECT*)lParam; 12995 // docs say this is the recommended position and we should honor it 12996 SetWindowPos(hwnd, 12997 null, 12998 prcNewWindow.left, 12999 prcNewWindow.top, 13000 prcNewWindow.right - prcNewWindow.left, 13001 prcNewWindow.bottom - prcNewWindow.top, 13002 SWP_NOZORDER | SWP_NOACTIVATE); 13003 13004 // doing this because of https://github.com/microsoft/Windows-classic-samples/blob/main/Samples/DPIAwarenessPerWindow/client/DpiAwarenessContext.cpp 13005 // im not sure it is completely correct 13006 // but without it the tabs and such do look weird as things change. 13007 if(SystemParametersInfoForDpi) { 13008 LOGFONT lfText; 13009 SystemParametersInfoForDpi(SPI_GETICONTITLELOGFONT, lfText.sizeof, &lfText, FALSE, this.actualDpi_); 13010 HFONT hFontNew = CreateFontIndirect(&lfText); 13011 if (hFontNew) 13012 { 13013 //DeleteObject(hFontOld); 13014 static extern(Windows) BOOL helper(HWND hWnd, LPARAM lParam) { 13015 SendMessage(hWnd, WM_SETFONT, cast(WPARAM)lParam, MAKELPARAM(TRUE, 0)); 13016 return TRUE; 13017 } 13018 EnumChildWindows(hwnd, &helper, cast(LPARAM) hFontNew); 13019 } 13020 } 13021 13022 if(this.onDpiChanged) 13023 this.onDpiChanged(); 13024 break; 13025 case WM_ENTERIDLE: 13026 // when a menu is up, it stops normal event processing (modal message loop) 13027 // but this at least gives us a chance to SOMETIMES catch up 13028 // FIXME: I can use SetTimer while idle to keep working i think... but idk when i'd destroy it. 13029 SimpleWindow.processAllCustomEvents; 13030 SimpleWindow.processAllCustomEvents; 13031 SleepEx(0, true); 13032 break; 13033 case WM_SIZE: 13034 if(wParam == 1 /* SIZE_MINIMIZED */) 13035 break; 13036 _width = LOWORD(lParam); 13037 _height = HIWORD(lParam); 13038 13039 // I want to avoid tearing in the windows (my code is inefficient 13040 // so this is a hack around that) so while sizing, we don't trigger, 13041 // but we do want to trigger on events like mazimize. 13042 if(!inSizeMove || doLiveResizing) 13043 goto size_changed; 13044 break; 13045 /+ 13046 case WM_SIZING: 13047 writeln("size"); 13048 break; 13049 +/ 13050 // I don't like the tearing I get when redrawing on WM_SIZE 13051 // (I know there's other ways to fix that but I don't like that behavior anyway) 13052 // so instead it is going to redraw only at the end of a size. 13053 case 0x0231: /* WM_ENTERSIZEMOVE */ 13054 inSizeMove = true; 13055 break; 13056 case 0x0232: /* WM_EXITSIZEMOVE */ 13057 inSizeMove = false; 13058 13059 size_changed: 13060 13061 // nothing relevant changed, don't bother redrawing 13062 if(oldWidth == _width && oldHeight == _height) { 13063 if(msg == 0x0232) 13064 goto finalize_resize; 13065 break; 13066 } 13067 13068 // note: OpenGL windows don't use a backing bmp, so no need to change them 13069 // if resizability is anything other than allowResizing, it is meant to either stretch the one image or just do nothing 13070 if(openglMode == OpenGlOptions.no) { // && resizability == Resizability.allowResizing) { 13071 // gotta get the double buffer bmp to match the window 13072 // FIXME: could this be more efficient? it never relinquishes a large bitmap 13073 13074 // if it is auto-scaled, we keep the backing bitmap the same size all the time 13075 if(resizability != Resizability.automaticallyScaleIfPossible) 13076 if(_width > bmpWidth || _height > bmpHeight) { 13077 auto hdc = GetDC(hwnd); 13078 auto oldBuffer = buffer; 13079 buffer = CreateCompatibleBitmap(hdc, _width, _height); 13080 13081 auto hdcBmp = CreateCompatibleDC(hdc); 13082 auto oldBmp = SelectObject(hdcBmp, buffer); 13083 13084 auto hdcOldBmp = CreateCompatibleDC(hdc); 13085 auto oldOldBmp = SelectObject(hdcOldBmp, oldBuffer); 13086 13087 /+ 13088 RECT r; 13089 r.left = 0; 13090 r.top = 0; 13091 r.right = width; 13092 r.bottom = height; 13093 auto c = Color.green; 13094 auto brush = CreateSolidBrush(RGB(c.r, c.g, c.b)); 13095 FillRect(hdcBmp, &r, brush); 13096 DeleteObject(brush); 13097 +/ 13098 13099 BitBlt(hdcBmp, 0, 0, bmpWidth, bmpHeight, hdcOldBmp, 0, 0, SRCCOPY); 13100 13101 bmpWidth = _width; 13102 bmpHeight = _height; 13103 13104 SelectObject(hdcOldBmp, oldOldBmp); 13105 DeleteDC(hdcOldBmp); 13106 13107 SelectObject(hdcBmp, oldBmp); 13108 DeleteDC(hdcBmp); 13109 13110 ReleaseDC(hwnd, hdc); 13111 13112 DeleteObject(oldBuffer); 13113 } 13114 } 13115 13116 updateOpenglViewportIfNeeded(_width, _height); 13117 13118 if(resizability != Resizability.automaticallyScaleIfPossible) 13119 if(windowResized !is null) 13120 windowResized(_width, _height); 13121 13122 /+ 13123 if(inSizeMove) { 13124 // SimpleWindow.processAllCustomEvents(); 13125 // SimpleWindow.processAllCustomEvents(); 13126 13127 //RedrawWindow(hwnd, null, null, RDW_ERASE | RDW_INVALIDATE | RDW_ALLCHILDREN); 13128 //sdpyPrintDebugString("redraw b"); 13129 } else { 13130 +/ { 13131 finalize_resize: 13132 // when it is all done, make sure everything is freshly drawn or there might be 13133 // weird bugs left. 13134 SimpleWindow.processAllCustomEvents(); 13135 SimpleWindow.processAllCustomEvents(); 13136 13137 RedrawWindow(hwnd, null, null, RDW_ERASE | RDW_INVALIDATE | RDW_ALLCHILDREN); 13138 // sdpyPrintDebugString("redraw"); 13139 } 13140 13141 oldWidth = this._width; 13142 oldHeight = this._height; 13143 break; 13144 case WM_ERASEBKGND: 13145 // call `visibleForTheFirstTime` here, so we can do initialization as early as possible 13146 if (!this._visibleForTheFirstTimeCalled) { 13147 this._visibleForTheFirstTimeCalled = true; 13148 if (this.visibleForTheFirstTime !is null) { 13149 this.visibleForTheFirstTime(); 13150 } 13151 } 13152 // block it in OpenGL mode, 'cause no sane person will (or should) draw windows controls over OpenGL scene 13153 version(without_opengl) {} else { 13154 if (openglMode == OpenGlOptions.yes) return 1; 13155 } 13156 // call windows default handler, so it can paint standard controls 13157 goto default; 13158 case WM_CTLCOLORBTN: 13159 case WM_CTLCOLORSTATIC: 13160 SetBkMode(cast(HDC) wParam, TRANSPARENT); 13161 return cast(typeof(return)) //GetStockObject(NULL_BRUSH); 13162 GetSysColorBrush(COLOR_3DFACE); 13163 //break; 13164 case WM_SHOWWINDOW: 13165 auto before = this._visible; 13166 this._visible = (wParam != 0); 13167 if (!this._visibleForTheFirstTimeCalled && this._visible) { 13168 this._visibleForTheFirstTimeCalled = true; 13169 if (this.visibleForTheFirstTime !is null) { 13170 this.visibleForTheFirstTime(); 13171 } 13172 } 13173 if (this.visibilityChanged !is null && this._visible != before) this.visibilityChanged(this._visible); 13174 break; 13175 case WM_PAINT: { 13176 if (!this._visibleForTheFirstTimeCalled) { 13177 this._visibleForTheFirstTimeCalled = true; 13178 if (this.visibleForTheFirstTime !is null) { 13179 this.visibleForTheFirstTime(); 13180 } 13181 } 13182 13183 BITMAP bm; 13184 PAINTSTRUCT ps; 13185 13186 HDC hdc = BeginPaint(hwnd, &ps); 13187 13188 if(openglMode == OpenGlOptions.no) { 13189 13190 HDC hdcMem = CreateCompatibleDC(hdc); 13191 HBITMAP hbmOld = SelectObject(hdcMem, buffer); 13192 13193 GetObject(buffer, bm.sizeof, &bm); 13194 13195 // FIXME: only BitBlt the invalidated rectangle, not the whole thing 13196 if(resizability == Resizability.automaticallyScaleIfPossible) 13197 StretchBlt(hdc, 0, 0, this._width, this._height, hdcMem, 0, 0, bm.bmWidth, bm.bmHeight, SRCCOPY); 13198 else 13199 BitBlt(hdc, 0, 0, bm.bmWidth, bm.bmHeight, hdcMem, 0, 0, SRCCOPY); 13200 //BitBlt(hdc, ps.rcPaint.left, ps.rcPaint.top, ps.rcPaint.right - ps.rcPaint.left, ps.rcPaint.top - ps.rcPaint.bottom, hdcMem, 0, 0, SRCCOPY); 13201 13202 SelectObject(hdcMem, hbmOld); 13203 DeleteDC(hdcMem); 13204 EndPaint(hwnd, &ps); 13205 } else { 13206 EndPaint(hwnd, &ps); 13207 version(without_opengl) {} else 13208 redrawOpenGlSceneSoon(); 13209 } 13210 } break; 13211 default: 13212 return DefWindowProc(hwnd, msg, wParam, lParam); 13213 } 13214 return 0; 13215 13216 } 13217 catch(Throwable t) { 13218 sdpyPrintDebugString(t.toString); 13219 return 0; 13220 } 13221 } 13222 } 13223 13224 mixin template NativeImageImplementation() { 13225 HBITMAP handle; 13226 ubyte* rawData; 13227 13228 final: 13229 13230 Color getPixel(int x, int y) @system { 13231 auto itemsPerLine = ((cast(int) width * 3 + 3) / 4) * 4; 13232 // remember, bmps are upside down 13233 auto offset = itemsPerLine * (height - y - 1) + x * 3; 13234 13235 Color c; 13236 if(enableAlpha) 13237 c.a = rawData[offset + 3]; 13238 else 13239 c.a = 255; 13240 c.b = rawData[offset + 0]; 13241 c.g = rawData[offset + 1]; 13242 c.r = rawData[offset + 2]; 13243 c.unPremultiply(); 13244 return c; 13245 } 13246 13247 void setPixel(int x, int y, Color c) @system { 13248 auto itemsPerLine = ((cast(int) width * 3 + 3) / 4) * 4; 13249 // remember, bmps are upside down 13250 auto offset = itemsPerLine * (height - y - 1) + x * 3; 13251 13252 if(enableAlpha) 13253 c.premultiply(); 13254 13255 rawData[offset + 0] = c.b; 13256 rawData[offset + 1] = c.g; 13257 rawData[offset + 2] = c.r; 13258 if(enableAlpha) 13259 rawData[offset + 3] = c.a; 13260 } 13261 13262 void convertToRgbaBytes(ubyte[] where) @system { 13263 assert(where.length == this.width * this.height * 4); 13264 13265 auto itemsPerLine = ((cast(int) width * 3 + 3) / 4) * 4; 13266 int idx = 0; 13267 int offset = itemsPerLine * (height - 1); 13268 // remember, bmps are upside down 13269 for(int y = height - 1; y >= 0; y--) { 13270 auto offsetStart = offset; 13271 for(int x = 0; x < width; x++) { 13272 where[idx + 0] = rawData[offset + 2]; // r 13273 where[idx + 1] = rawData[offset + 1]; // g 13274 where[idx + 2] = rawData[offset + 0]; // b 13275 if(enableAlpha) { 13276 where[idx + 3] = rawData[offset + 3]; // a 13277 unPremultiplyRgba(where[idx .. idx + 4]); 13278 offset++; 13279 } else 13280 where[idx + 3] = 255; // a 13281 idx += 4; 13282 offset += 3; 13283 } 13284 13285 offset = offsetStart - itemsPerLine; 13286 } 13287 } 13288 13289 void setFromRgbaBytes(in ubyte[] what) @system { 13290 assert(what.length == this.width * this.height * 4); 13291 13292 auto itemsPerLine = enableAlpha ? (width * 4) : (((cast(int) width * 3 + 3) / 4) * 4); 13293 int idx = 0; 13294 int offset = itemsPerLine * (height - 1); 13295 // remember, bmps are upside down 13296 for(int y = height - 1; y >= 0; y--) { 13297 auto offsetStart = offset; 13298 for(int x = 0; x < width; x++) { 13299 if(enableAlpha) { 13300 auto a = what[idx + 3]; 13301 13302 rawData[offset + 2] = (a * what[idx + 0]) / 255; // r 13303 rawData[offset + 1] = (a * what[idx + 1]) / 255; // g 13304 rawData[offset + 0] = (a * what[idx + 2]) / 255; // b 13305 rawData[offset + 3] = a; // a 13306 //premultiplyBgra(rawData[offset .. offset + 4]); 13307 offset++; 13308 } else { 13309 rawData[offset + 2] = what[idx + 0]; // r 13310 rawData[offset + 1] = what[idx + 1]; // g 13311 rawData[offset + 0] = what[idx + 2]; // b 13312 } 13313 idx += 4; 13314 offset += 3; 13315 } 13316 13317 offset = offsetStart - itemsPerLine; 13318 } 13319 } 13320 13321 13322 void createImage(int width, int height, bool forcexshm=false, bool enableAlpha = false) { 13323 BITMAPINFO infoheader; 13324 infoheader.bmiHeader.biSize = infoheader.bmiHeader.sizeof; 13325 infoheader.bmiHeader.biWidth = width; 13326 infoheader.bmiHeader.biHeight = height; 13327 infoheader.bmiHeader.biPlanes = 1; 13328 infoheader.bmiHeader.biBitCount = enableAlpha ? 32: 24; 13329 infoheader.bmiHeader.biCompression = BI_RGB; 13330 13331 handle = CreateDIBSection( 13332 null, 13333 &infoheader, 13334 DIB_RGB_COLORS, 13335 cast(void**) &rawData, 13336 null, 13337 0); 13338 if(handle is null) 13339 throw new WindowsApiException("create image failed", GetLastError()); 13340 13341 } 13342 13343 void dispose() { 13344 DeleteObject(handle); 13345 } 13346 } 13347 13348 enum KEY_ESCAPE = 27; 13349 } 13350 13351 version(Emscripten) { 13352 alias int delegate(void*) NativeEventHandler; 13353 alias void* NativeWindowHandle; 13354 13355 mixin template NativeSimpleWindowImplementation() { } 13356 mixin template NativeScreenPainterImplementation() { } 13357 mixin template NativeImageImplementation() { } 13358 } 13359 13360 version(X11) { 13361 /// This is the default font used. You might change this before doing anything else with 13362 /// the library if you want to try something else. Surround that in `static if(UsingSimpledisplayX11)` 13363 /// for cross-platform compatibility. 13364 //__gshared string xfontstr = "-*-dejavu sans-medium-r-*-*-12-*-*-*-*-*-*-*"; 13365 //__gshared string xfontstr = "-*-dejavu sans-medium-r-*-*-12-*-*-*-*-*-*-*"; 13366 __gshared string xfontstr = "-*-lucida-medium-r-normal-sans-12-*-*-*-*-*-*-*"; 13367 //__gshared string xfontstr = "-*-fixed-medium-r-*-*-14-*-*-*-*-*-*-*"; 13368 13369 alias int delegate(XEvent) NativeEventHandler; 13370 alias Window NativeWindowHandle; 13371 13372 enum KEY_ESCAPE = 9; 13373 13374 mixin template NativeScreenPainterImplementation() { 13375 Display* display; 13376 Drawable d; 13377 Drawable destiny; 13378 13379 // FIXME: should the gc be static too so it isn't recreated every time draw is called? 13380 GC gc; 13381 13382 __gshared bool fontAttempted; 13383 13384 __gshared XFontStruct* defaultfont; 13385 __gshared XFontSet defaultfontset; 13386 13387 XFontStruct* font; 13388 XFontSet fontset; 13389 13390 void create(PaintingHandle window) { 13391 this.display = XDisplayConnection.get(); 13392 13393 Drawable buffer = None; 13394 if(auto sw = cast(SimpleWindow) this.window) { 13395 buffer = sw.impl.buffer; 13396 this.destiny = cast(Drawable) window; 13397 } else { 13398 buffer = cast(Drawable) window; 13399 this.destiny = None; 13400 } 13401 13402 this.d = cast(Drawable) buffer; 13403 13404 auto dgc = DefaultGC(display, DefaultScreen(display)); 13405 13406 this.gc = XCreateGC(display, d, 0, null); 13407 13408 XCopyGC(display, dgc, 0xffffffff, this.gc); 13409 13410 ensureDefaultFontLoaded(); 13411 13412 font = defaultfont; 13413 fontset = defaultfontset; 13414 13415 if(font) { 13416 XSetFont(display, gc, font.fid); 13417 } 13418 } 13419 13420 static void ensureDefaultFontLoaded() { 13421 if(!fontAttempted) { 13422 auto display = XDisplayConnection.get; 13423 auto font = XLoadQueryFont(display, xfontstr.ptr); 13424 // if the user font choice fails, fixed is pretty reliable (required by X to start!) and not bad either 13425 if(font is null) { 13426 xfontstr = "-*-fixed-medium-r-*-*-13-*-*-*-*-*-*-*"; 13427 font = XLoadQueryFont(display, xfontstr.ptr); 13428 } 13429 13430 char** lol; 13431 int lol2; 13432 char* lol3; 13433 auto fontset = XCreateFontSet(display, xfontstr.ptr, &lol, &lol2, &lol3); 13434 13435 fontAttempted = true; 13436 13437 defaultfont = font; 13438 defaultfontset = fontset; 13439 } 13440 } 13441 13442 arsd.color.Rectangle _clipRectangle; 13443 void setClipRectangle(int x, int y, int width, int height) { 13444 auto old = _clipRectangle; 13445 _clipRectangle = arsd.color.Rectangle(Point(x, y), Size(width, height)); 13446 if(old == _clipRectangle) 13447 return; 13448 13449 if(width == 0 || height == 0) { 13450 XSetClipMask(display, gc, None); 13451 13452 if(xrenderPicturePainter) { 13453 13454 XRectangle[1] rects; 13455 rects[0] = XRectangle(short.min, short.min, short.max, short.max); 13456 XRenderSetPictureClipRectangles(display, xrenderPicturePainter, 0, 0, rects.ptr, cast(int) rects.length); 13457 } 13458 13459 version(with_xft) { 13460 if(xftDraw is null) 13461 return; 13462 XftDrawSetClip(xftDraw, null); 13463 } 13464 } else { 13465 XRectangle[1] rects; 13466 rects[0] = XRectangle(cast(short)(x), cast(short)(y), cast(short) width, cast(short) height); 13467 XSetClipRectangles(XDisplayConnection.get, gc, 0, 0, rects.ptr, 1, 0); 13468 13469 if(xrenderPicturePainter) 13470 XRenderSetPictureClipRectangles(display, xrenderPicturePainter, 0, 0, rects.ptr, cast(int) rects.length); 13471 13472 version(with_xft) { 13473 if(xftDraw is null) 13474 return; 13475 XftDrawSetClipRectangles(xftDraw, 0, 0, rects.ptr, 1); 13476 } 13477 } 13478 } 13479 13480 version(with_xft) { 13481 XftFont* xftFont; 13482 XftDraw* xftDraw; 13483 13484 XftColor xftColor; 13485 13486 void updateXftColor() { 13487 if(xftFont is null) 13488 return; 13489 13490 // not bothering with XftColorFree since p sure i don't need it on 24 bit displays.... 13491 XRenderColor colorIn = XRenderColor(_outlineColor.r * 255, _outlineColor.g * 255, _outlineColor.b * 255, _outlineColor.a * 255); 13492 13493 XftColorAllocValue( 13494 display, 13495 DefaultVisual(display, DefaultScreen(display)), 13496 DefaultColormap(display, 0), 13497 &colorIn, 13498 &xftColor 13499 ); 13500 } 13501 } 13502 13503 void enableXftDraw() { 13504 if(xftDraw is null) { 13505 xftDraw = XftDrawCreate( 13506 display, 13507 d, 13508 DefaultVisual(display, DefaultScreen(display)), 13509 DefaultColormap(display, 0) 13510 ); 13511 13512 updateXftColor(); 13513 } 13514 } 13515 13516 private OperatingSystemFont _activeFont; 13517 void setFont(OperatingSystemFont font) { 13518 _activeFont = font; 13519 version(with_xft) { 13520 if(font && font.isXft && font.xftFont) 13521 this.xftFont = font.xftFont; 13522 else 13523 this.xftFont = null; 13524 13525 if(this.xftFont) { 13526 enableXftDraw(); 13527 return; 13528 } 13529 } 13530 13531 if(font && font.font) { 13532 this.font = font.font; 13533 this.fontset = font.fontset; 13534 XSetFont(display, gc, font.font.fid); 13535 } else { 13536 this.font = defaultfont; 13537 this.fontset = defaultfontset; 13538 } 13539 13540 } 13541 13542 private Picture xrenderPicturePainter; 13543 13544 bool manualInvalidations; 13545 void invalidateRect(Rectangle invalidRect) { 13546 // FIXME if manualInvalidations 13547 } 13548 13549 void dispose() { 13550 this.rasterOp = RasterOp.normal; 13551 13552 if(xrenderPicturePainter) { 13553 XRenderFreePicture(display, xrenderPicturePainter); 13554 xrenderPicturePainter = None; 13555 } 13556 13557 // FIXME: this.window.width/height is probably wrong 13558 13559 // src x,y then dest x, y 13560 if(destiny != None) { 13561 // FIXME: if manual invalidations we can actually only copy some of the area. 13562 // if(manualInvalidations) 13563 XSetClipMask(display, gc, None); 13564 XCopyArea(display, d, destiny, gc, 0, 0, this.window.width, this.window.height, 0, 0); 13565 } 13566 13567 XFreeGC(display, gc); 13568 13569 version(with_xft) 13570 if(xftDraw) { 13571 XftDrawDestroy(xftDraw); 13572 xftDraw = null; 13573 } 13574 13575 /+ 13576 // this should prolly legit never be used since if it destroys the font handle from a OperatingSystemFont, it also ruins a reusable resource. 13577 if(font && font !is defaultfont) { 13578 XFreeFont(display, font); 13579 font = null; 13580 } 13581 if(fontset && fontset !is defaultfontset) { 13582 XFreeFontSet(display, fontset); 13583 fontset = null; 13584 } 13585 +/ 13586 XFlush(display); 13587 13588 if(window.paintingFinishedDg !is null) 13589 window.paintingFinishedDg()(); 13590 } 13591 13592 bool backgroundIsNotTransparent = true; 13593 bool foregroundIsNotTransparent = true; 13594 13595 bool _penInitialized = false; 13596 Pen _activePen; 13597 13598 Color _outlineColor; 13599 Color _fillColor; 13600 13601 @property void pen(Pen p) { 13602 if(_penInitialized && p == _activePen) { 13603 return; 13604 } 13605 _penInitialized = true; 13606 _activePen = p; 13607 _outlineColor = p.color; 13608 13609 int style; 13610 13611 byte dashLength; 13612 13613 final switch(p.style) { 13614 case Pen.Style.Solid: 13615 style = 0 /*LineSolid*/; 13616 break; 13617 case Pen.Style.Dashed: 13618 style = 1 /*LineOnOffDash*/; 13619 dashLength = 4; 13620 break; 13621 case Pen.Style.Dotted: 13622 style = 1 /*LineOnOffDash*/; 13623 dashLength = 1; 13624 break; 13625 } 13626 13627 XSetLineAttributes(display, gc, p.width, style, style == 0 ? 3 : 0, 0); 13628 if(dashLength) 13629 XSetDashes(display, gc, 0, &dashLength, 1); 13630 13631 if(p.color.a == 0) { 13632 foregroundIsNotTransparent = false; 13633 return; 13634 } 13635 13636 foregroundIsNotTransparent = true; 13637 13638 XSetForeground(display, gc, colorToX(p.color, display)); 13639 13640 version(with_xft) 13641 updateXftColor(); 13642 } 13643 13644 RasterOp _currentRasterOp; 13645 bool _currentRasterOpInitialized = false; 13646 @property void rasterOp(RasterOp op) { 13647 if(_currentRasterOpInitialized && _currentRasterOp == op) 13648 return; 13649 _currentRasterOp = op; 13650 _currentRasterOpInitialized = true; 13651 int mode; 13652 final switch(op) { 13653 case RasterOp.normal: 13654 mode = GXcopy; 13655 break; 13656 case RasterOp.xor: 13657 mode = GXxor; 13658 break; 13659 } 13660 XSetFunction(display, gc, mode); 13661 } 13662 13663 13664 bool _fillColorInitialized = false; 13665 13666 @property void fillColor(Color c) { 13667 if(_fillColorInitialized && _fillColor == c) 13668 return; // already good, no need to waste time calling it 13669 _fillColor = c; 13670 _fillColorInitialized = true; 13671 if(c.a == 0) { 13672 backgroundIsNotTransparent = false; 13673 return; 13674 } 13675 13676 backgroundIsNotTransparent = true; 13677 13678 XSetBackground(display, gc, colorToX(c, display)); 13679 13680 } 13681 13682 void swapColors() { 13683 auto tmp = _fillColor; 13684 fillColor = _outlineColor; 13685 auto newPen = _activePen; 13686 newPen.color = tmp; 13687 pen(newPen); 13688 } 13689 13690 uint colorToX(Color c, Display* display) { 13691 auto visual = DefaultVisual(display, DefaultScreen(display)); 13692 import core.bitop; 13693 uint color = 0; 13694 { 13695 auto startBit = bsf(visual.red_mask); 13696 auto lastBit = bsr(visual.red_mask); 13697 auto r = cast(uint) c.r; 13698 r >>= 7 - (lastBit - startBit); 13699 r <<= startBit; 13700 color |= r; 13701 } 13702 { 13703 auto startBit = bsf(visual.green_mask); 13704 auto lastBit = bsr(visual.green_mask); 13705 auto g = cast(uint) c.g; 13706 g >>= 7 - (lastBit - startBit); 13707 g <<= startBit; 13708 color |= g; 13709 } 13710 { 13711 auto startBit = bsf(visual.blue_mask); 13712 auto lastBit = bsr(visual.blue_mask); 13713 auto b = cast(uint) c.b; 13714 b >>= 7 - (lastBit - startBit); 13715 b <<= startBit; 13716 color |= b; 13717 } 13718 13719 13720 13721 return color; 13722 } 13723 13724 void drawImage(int x, int y, Image i, int ix, int iy, int w, int h) { 13725 // source x, source y 13726 if(ix >= i.width) return; 13727 if(iy >= i.height) return; 13728 if(ix + w > i.width) w = i.width - ix; 13729 if(iy + h > i.height) h = i.height - iy; 13730 if(i.usingXshm) 13731 XShmPutImage(display, d, gc, i.handle, ix, iy, x, y, w, h, false); 13732 else 13733 XPutImage(display, d, gc, i.handle, ix, iy, x, y, w, h); 13734 } 13735 13736 void drawPixmap(Sprite s, int x, int y, int ix, int iy, int w, int h) { 13737 if(s.enableAlpha) { 13738 // the Sprite must be created first, meaning if we're here, XRender is already loaded 13739 if(this.xrenderPicturePainter == None) { 13740 XRenderPictureAttributes attrs; 13741 // FIXME: I can prolly reuse this as long as the pixmap itself is valid. 13742 xrenderPicturePainter = XRenderCreatePicture(display, d, Sprite.RGB24, 0, &attrs); 13743 13744 // need to initialize the clip 13745 XRectangle[1] rects; 13746 rects[0] = XRectangle(cast(short)(_clipRectangle.left), cast(short)(_clipRectangle.top), cast(short) _clipRectangle.width, cast(short) _clipRectangle.height); 13747 13748 if(_clipRectangle != Rectangle.init) 13749 XRenderSetPictureClipRectangles(display, xrenderPicturePainter, 0, 0, rects.ptr, cast(int) rects.length); 13750 } 13751 13752 XRenderComposite( 13753 display, 13754 3, // PicOpOver 13755 s.xrenderPicture, 13756 None, 13757 this.xrenderPicturePainter, 13758 ix, 13759 iy, 13760 0, 13761 0, 13762 x, 13763 y, 13764 w ? w : s.width, 13765 h ? h : s.height 13766 ); 13767 } else { 13768 XCopyArea(display, s.handle, d, gc, ix, iy, w ? w : s.width, h ? h : s.height, x, y); 13769 } 13770 } 13771 13772 int fontHeight() { 13773 version(with_xft) 13774 if(xftFont !is null) 13775 return xftFont.height; 13776 if(font) 13777 return font.max_bounds.ascent + font.max_bounds.descent; 13778 return 12; // pretty common default... 13779 } 13780 13781 int textWidth(in char[] line) { 13782 version(with_xft) 13783 if(xftFont) { 13784 if(line.length == 0) 13785 return 0; 13786 XGlyphInfo extents; 13787 XftTextExtentsUtf8(display, xftFont, line.ptr, cast(int) line.length, &extents); 13788 return extents.width; 13789 } 13790 13791 if(fontset) { 13792 if(line.length == 0) 13793 return 0; 13794 XRectangle rect; 13795 Xutf8TextExtents(fontset, line.ptr, cast(int) line.length, null, &rect); 13796 13797 return rect.width; 13798 } 13799 13800 if(font) 13801 // FIXME: unicode 13802 return XTextWidth( font, line.ptr, cast(int) line.length); 13803 else 13804 return fontHeight / 2 * cast(int) line.length; // if no font is loaded, it is prolly Fixed, which is a 2:1 ratio 13805 } 13806 13807 Size textSize(in char[] text) { 13808 auto maxWidth = 0; 13809 auto lineHeight = fontHeight; 13810 int h = text.length ? 0 : lineHeight + 4; // if text is empty, it still gives the line height 13811 foreach(line; text.split('\n')) { 13812 int textWidth = this.textWidth(line); 13813 if(textWidth > maxWidth) 13814 maxWidth = textWidth; 13815 h += lineHeight + 4; 13816 } 13817 return Size(maxWidth, h); 13818 } 13819 13820 void drawText(in int x, in int y, in int x2, in int y2, in char[] originalText, in uint alignment) { 13821 const(char)[] text; 13822 version(with_xft) 13823 if(xftFont) { 13824 text = originalText; 13825 goto loaded; 13826 } 13827 13828 if(fontset) 13829 text = originalText; 13830 else { 13831 text.reserve(originalText.length); 13832 // the first 256 unicode codepoints are the same as ascii and latin-1, which is what X expects, so we can keep all those 13833 // then strip the rest so there isn't garbage 13834 foreach(dchar ch; originalText) 13835 if(ch < 256) 13836 text ~= cast(ubyte) ch; 13837 else 13838 text ~= 191; // FIXME: using a random character (upside down question mark) to fill the space 13839 } 13840 loaded: 13841 if(text.length == 0) 13842 return; 13843 13844 // FIXME: should we clip it to the bounding box? 13845 int textHeight = fontHeight; 13846 13847 auto lines = text.split('\n'); 13848 13849 const lineHeight = textHeight; 13850 textHeight *= lines.length; 13851 13852 int cy = y; 13853 13854 if(alignment & TextAlignment.VerticalBottom) { 13855 if(y2 <= 0) 13856 return; 13857 auto h = y2 - y; 13858 if(h > textHeight) { 13859 cy += h - textHeight; 13860 cy -= lineHeight / 2; 13861 } 13862 } else if(alignment & TextAlignment.VerticalCenter) { 13863 if(y2 <= 0) 13864 return; 13865 auto h = y2 - y; 13866 if(textHeight < h) { 13867 cy += (h - textHeight) / 2; 13868 //cy -= lineHeight / 4; 13869 } 13870 } 13871 13872 foreach(line; text.split('\n')) { 13873 int textWidth = this.textWidth(line); 13874 13875 int px = x, py = cy; 13876 13877 if(alignment & TextAlignment.Center) { 13878 if(x2 <= 0) 13879 return; 13880 auto w = x2 - x; 13881 if(w > textWidth) 13882 px += (w - textWidth) / 2; 13883 } else if(alignment & TextAlignment.Right) { 13884 if(x2 <= 0) 13885 return; 13886 auto pos = x2 - textWidth; 13887 if(pos > x) 13888 px = pos; 13889 } 13890 13891 version(with_xft) 13892 if(xftFont) { 13893 XftDrawStringUtf8(xftDraw, &xftColor, xftFont, px, py + xftFont.ascent, line.ptr, cast(int) line.length); 13894 13895 goto carry_on; 13896 } 13897 13898 if(fontset) 13899 Xutf8DrawString(display, d, fontset, gc, px, py + (font ? font.max_bounds.ascent : lineHeight), line.ptr, cast(int) line.length); 13900 else 13901 XDrawString(display, d, gc, px, py + (font ? font.max_bounds.ascent : lineHeight), line.ptr, cast(int) line.length); 13902 carry_on: 13903 cy += lineHeight + 4; 13904 } 13905 } 13906 13907 void drawPixel(int x, int y) { 13908 XDrawPoint(display, d, gc, x, y); 13909 } 13910 13911 // The basic shapes, outlined 13912 13913 void drawLine(int x1, int y1, int x2, int y2) { 13914 if(foregroundIsNotTransparent) 13915 XDrawLine(display, d, gc, x1, y1, x2, y2); 13916 } 13917 13918 void drawRectangle(int x, int y, int width, int height) { 13919 if(backgroundIsNotTransparent) { 13920 swapColors(); 13921 XFillRectangle(display, d, gc, x+1, y+1, width-2, height-2); // Need to ensure pixels are only drawn once... 13922 swapColors(); 13923 } 13924 // 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 13925 if(foregroundIsNotTransparent) 13926 XDrawRectangle(display, d, gc, x + _activePen.width / 2, y + _activePen.width / 2, width - 1 - _activePen.width / 2, height - 1 - _activePen.width / 2); 13927 } 13928 13929 void drawRectangleRounded(Point upperLeft, Point lowerRight, int borderRadius) { 13930 int[4] radii = borderRadius; 13931 auto r = Rectangle(upperLeft, lowerRight); 13932 13933 if(backgroundIsNotTransparent) { 13934 swapColors(); 13935 // FIXME these overlap and thus draw the pixels multiple times 13936 XFillRectangle(display, d, gc, r.left, r.top + borderRadius/2, r.width, r.height - borderRadius); 13937 XFillRectangle(display, d, gc, r.left + borderRadius/2, r.top, r.width - borderRadius, r.height); 13938 swapColors(); 13939 } 13940 13941 drawLine(r.left + borderRadius / 2, r.top, r.right - borderRadius / 2, r.top); 13942 drawLine(r.left + borderRadius / 2, r.bottom-1, r.right - borderRadius / 2, r.bottom-1); 13943 drawLine(r.left, r.top + borderRadius / 2, r.left, r.bottom - borderRadius / 2); 13944 drawLine(r.right - 1, r.top + borderRadius / 2, r.right - 1, r.bottom - borderRadius / 2); 13945 13946 //drawRectangle(r.left + borderRadius/2, r.top, r.width - borderRadius, r.height); 13947 13948 drawArc(r.upperLeft.x, r.upperLeft.y, radii[0], radii[0], 90*64, 90*64); 13949 drawArc(r.upperRight.x - radii[1], r.upperRight.y, radii[1] - 1, radii[1], 0*64, 90*64); 13950 drawArc(r.lowerLeft.x, r.lowerLeft.y - radii[2], radii[2], radii[2] - 1, 180*64, 90*64); 13951 drawArc(r.lowerRight.x - radii[3], r.lowerRight.y - radii[3], radii[3] - 1, radii[3] - 1, 270*64, 90*64); 13952 } 13953 13954 13955 /// Arguments are the points of the bounding rectangle 13956 void drawEllipse(int x1, int y1, int x2, int y2) { 13957 drawArc(x1, y1, x2 - x1, y2 - y1, 0, 360 * 64); 13958 } 13959 13960 // NOTE: start and finish are in units of degrees * 64 13961 void drawArc(int x1, int y1, int width, int height, int start, int length) { 13962 if(backgroundIsNotTransparent) { 13963 swapColors(); 13964 XFillArc(display, d, gc, x1, y1, width, height, start, length); 13965 swapColors(); 13966 } 13967 if(foregroundIsNotTransparent) { 13968 XDrawArc(display, d, gc, x1, y1, width, height, start, length); 13969 13970 // Windows draws the straight lines on the edges too so FIXME sort of 13971 } 13972 } 13973 13974 void drawPolygon(Point[] vertexes) { 13975 XPoint[16] pointsBuffer; 13976 XPoint[] points; 13977 if(vertexes.length <= pointsBuffer.length) 13978 points = pointsBuffer[0 .. vertexes.length]; 13979 else 13980 points.length = vertexes.length; 13981 13982 foreach(i, p; vertexes) { 13983 points[i].x = cast(short) p.x; 13984 points[i].y = cast(short) p.y; 13985 } 13986 13987 if(backgroundIsNotTransparent) { 13988 swapColors(); 13989 XFillPolygon(display, d, gc, points.ptr, cast(int) points.length, PolygonShape.Complex, CoordMode.CoordModeOrigin); 13990 swapColors(); 13991 } 13992 if(foregroundIsNotTransparent) { 13993 XDrawLines(display, d, gc, points.ptr, cast(int) points.length, CoordMode.CoordModeOrigin); 13994 } 13995 } 13996 } 13997 13998 /* XRender { */ 13999 14000 struct XRenderColor { 14001 ushort red; 14002 ushort green; 14003 ushort blue; 14004 ushort alpha; 14005 } 14006 14007 alias Picture = XID; 14008 alias PictFormat = XID; 14009 14010 struct XGlyphInfo { 14011 ushort width; 14012 ushort height; 14013 short x; 14014 short y; 14015 short xOff; 14016 short yOff; 14017 } 14018 14019 struct XRenderDirectFormat { 14020 short red; 14021 short redMask; 14022 short green; 14023 short greenMask; 14024 short blue; 14025 short blueMask; 14026 short alpha; 14027 short alphaMask; 14028 } 14029 14030 struct XRenderPictFormat { 14031 PictFormat id; 14032 int type; 14033 int depth; 14034 XRenderDirectFormat direct; 14035 Colormap colormap; 14036 } 14037 14038 enum PictFormatID = (1 << 0); 14039 enum PictFormatType = (1 << 1); 14040 enum PictFormatDepth = (1 << 2); 14041 enum PictFormatRed = (1 << 3); 14042 enum PictFormatRedMask =(1 << 4); 14043 enum PictFormatGreen = (1 << 5); 14044 enum PictFormatGreenMask=(1 << 6); 14045 enum PictFormatBlue = (1 << 7); 14046 enum PictFormatBlueMask =(1 << 8); 14047 enum PictFormatAlpha = (1 << 9); 14048 enum PictFormatAlphaMask=(1 << 10); 14049 enum PictFormatColormap =(1 << 11); 14050 14051 struct XRenderPictureAttributes { 14052 int repeat; 14053 Picture alpha_map; 14054 int alpha_x_origin; 14055 int alpha_y_origin; 14056 int clip_x_origin; 14057 int clip_y_origin; 14058 Pixmap clip_mask; 14059 Bool graphics_exposures; 14060 int subwindow_mode; 14061 int poly_edge; 14062 int poly_mode; 14063 Atom dither; 14064 Bool component_alpha; 14065 } 14066 14067 alias int XFixed; 14068 14069 struct XPointFixed { 14070 XFixed x, y; 14071 } 14072 14073 struct XCircle { 14074 XFixed x; 14075 XFixed y; 14076 XFixed radius; 14077 } 14078 14079 struct XTransform { 14080 XFixed[3][3] matrix; 14081 } 14082 14083 struct XFilters { 14084 int nfilter; 14085 char **filter; 14086 int nalias; 14087 short *alias_; 14088 } 14089 14090 struct XIndexValue { 14091 c_ulong pixel; 14092 ushort red, green, blue, alpha; 14093 } 14094 14095 struct XAnimCursor { 14096 Cursor cursor; 14097 c_ulong delay; 14098 } 14099 14100 struct XLinearGradient { 14101 XPointFixed p1; 14102 XPointFixed p2; 14103 } 14104 14105 struct XRadialGradient { 14106 XCircle inner; 14107 XCircle outer; 14108 } 14109 14110 struct XConicalGradient { 14111 XPointFixed center; 14112 XFixed angle; /* in degrees */ 14113 } 14114 14115 enum PictStandardARGB32 = 0; 14116 enum PictStandardRGB24 = 1; 14117 enum PictStandardA8 = 2; 14118 enum PictStandardA4 = 3; 14119 enum PictStandardA1 = 4; 14120 enum PictStandardNUM = 5; 14121 14122 interface XRender { 14123 extern(C) @nogc: 14124 14125 Bool XRenderQueryExtension (Display *dpy, int *event_basep, int *error_basep); 14126 14127 Status XRenderQueryVersion (Display *dpy, 14128 int *major_versionp, 14129 int *minor_versionp); 14130 14131 Status XRenderQueryFormats (Display *dpy); 14132 14133 int XRenderQuerySubpixelOrder (Display *dpy, int screen); 14134 14135 Bool XRenderSetSubpixelOrder (Display *dpy, int screen, int subpixel); 14136 14137 XRenderPictFormat * 14138 XRenderFindVisualFormat (Display *dpy, const Visual *visual); 14139 14140 XRenderPictFormat * 14141 XRenderFindFormat (Display *dpy, 14142 c_ulong mask, 14143 const XRenderPictFormat *templ, 14144 int count); 14145 XRenderPictFormat * 14146 XRenderFindStandardFormat (Display *dpy, 14147 int format); 14148 14149 XIndexValue * 14150 XRenderQueryPictIndexValues(Display *dpy, 14151 const XRenderPictFormat *format, 14152 int *num); 14153 14154 Picture XRenderCreatePicture( 14155 Display *dpy, 14156 Drawable drawable, 14157 const XRenderPictFormat *format, 14158 c_ulong valuemask, 14159 const XRenderPictureAttributes *attributes); 14160 14161 void XRenderChangePicture (Display *dpy, 14162 Picture picture, 14163 c_ulong valuemask, 14164 const XRenderPictureAttributes *attributes); 14165 14166 void 14167 XRenderSetPictureClipRectangles (Display *dpy, 14168 Picture picture, 14169 int xOrigin, 14170 int yOrigin, 14171 const XRectangle *rects, 14172 int n); 14173 14174 void 14175 XRenderSetPictureClipRegion (Display *dpy, 14176 Picture picture, 14177 Region r); 14178 14179 void 14180 XRenderSetPictureTransform (Display *dpy, 14181 Picture picture, 14182 XTransform *transform); 14183 14184 void 14185 XRenderFreePicture (Display *dpy, 14186 Picture picture); 14187 14188 void 14189 XRenderComposite (Display *dpy, 14190 int op, 14191 Picture src, 14192 Picture mask, 14193 Picture dst, 14194 int src_x, 14195 int src_y, 14196 int mask_x, 14197 int mask_y, 14198 int dst_x, 14199 int dst_y, 14200 uint width, 14201 uint height); 14202 14203 14204 Picture XRenderCreateSolidFill (Display *dpy, 14205 const XRenderColor *color); 14206 14207 Picture XRenderCreateLinearGradient (Display *dpy, 14208 const XLinearGradient *gradient, 14209 const XFixed *stops, 14210 const XRenderColor *colors, 14211 int nstops); 14212 14213 Picture XRenderCreateRadialGradient (Display *dpy, 14214 const XRadialGradient *gradient, 14215 const XFixed *stops, 14216 const XRenderColor *colors, 14217 int nstops); 14218 14219 Picture XRenderCreateConicalGradient (Display *dpy, 14220 const XConicalGradient *gradient, 14221 const XFixed *stops, 14222 const XRenderColor *colors, 14223 int nstops); 14224 14225 14226 14227 Cursor 14228 XRenderCreateCursor (Display *dpy, 14229 Picture source, 14230 uint x, 14231 uint y); 14232 14233 XFilters * 14234 XRenderQueryFilters (Display *dpy, Drawable drawable); 14235 14236 void 14237 XRenderSetPictureFilter (Display *dpy, 14238 Picture picture, 14239 const char *filter, 14240 XFixed *params, 14241 int nparams); 14242 14243 Cursor 14244 XRenderCreateAnimCursor (Display *dpy, 14245 int ncursor, 14246 XAnimCursor *cursors); 14247 } 14248 14249 __gshared bool XRenderLibrarySuccessfullyLoaded = true; 14250 mixin DynamicLoad!(XRender, "Xrender", 1, XRenderLibrarySuccessfullyLoaded) XRenderLibrary; 14251 14252 /* XRender } */ 14253 14254 /* Xrandr { */ 14255 14256 struct XRRMonitorInfo { 14257 Atom name; 14258 Bool primary; 14259 Bool automatic; 14260 int noutput; 14261 int x; 14262 int y; 14263 int width; 14264 int height; 14265 int mwidth; 14266 int mheight; 14267 /*RROutput*/ void *outputs; 14268 } 14269 14270 struct XRRScreenChangeNotifyEvent { 14271 int type; /* event base */ 14272 c_ulong serial; /* # of last request processed by server */ 14273 Bool send_event; /* true if this came from a SendEvent request */ 14274 Display *display; /* Display the event was read from */ 14275 Window window; /* window which selected for this event */ 14276 Window root; /* Root window for changed screen */ 14277 Time timestamp; /* when the screen change occurred */ 14278 Time config_timestamp; /* when the last configuration change */ 14279 ushort/*SizeID*/ size_index; 14280 ushort/*SubpixelOrder*/ subpixel_order; 14281 ushort/*Rotation*/ rotation; 14282 int width; 14283 int height; 14284 int mwidth; 14285 int mheight; 14286 } 14287 14288 enum RRScreenChangeNotify = 0; 14289 14290 enum RRScreenChangeNotifyMask = 1; 14291 14292 __gshared int xrrEventBase = -1; 14293 14294 14295 interface XRandr { 14296 extern(C) @nogc: 14297 Bool XRRQueryExtension (Display *dpy, int *event_base_return, int *error_base_return); 14298 Status XRRQueryVersion (Display *dpy, int *major_version_return, int *minor_version_return); 14299 14300 XRRMonitorInfo * XRRGetMonitors(Display *dpy, Window window, Bool get_active, int *nmonitors); 14301 void XRRFreeMonitors(XRRMonitorInfo *monitors); 14302 14303 void XRRSelectInput(Display *dpy, Window window, int mask); 14304 } 14305 14306 __gshared bool XRandrLibrarySuccessfullyLoaded = true; 14307 mixin DynamicLoad!(XRandr, "Xrandr", 2, XRandrLibrarySuccessfullyLoaded) XRandrLibrary; 14308 /* Xrandr } */ 14309 14310 /* Xft { */ 14311 14312 // actually freetype 14313 alias void FT_Face; 14314 14315 // actually fontconfig 14316 private alias FcBool = int; 14317 alias void FcCharSet; 14318 alias void FcPattern; 14319 alias void FcResult; 14320 enum FcEndian { FcEndianBig, FcEndianLittle } 14321 struct FcFontSet { 14322 int nfont; 14323 int sfont; 14324 FcPattern** fonts; 14325 } 14326 14327 // actually XRegion 14328 struct BOX { 14329 short x1, x2, y1, y2; 14330 } 14331 struct _XRegion { 14332 c_long size; 14333 c_long numRects; 14334 BOX* rects; 14335 BOX extents; 14336 } 14337 14338 alias Region = _XRegion*; 14339 14340 // ok actually Xft 14341 14342 struct XftFontInfo; 14343 14344 struct XftFont { 14345 int ascent; 14346 int descent; 14347 int height; 14348 int max_advance_width; 14349 FcCharSet* charset; 14350 FcPattern* pattern; 14351 } 14352 14353 struct XftDraw; 14354 14355 struct XftColor { 14356 c_ulong pixel; 14357 XRenderColor color; 14358 } 14359 14360 struct XftCharSpec { 14361 dchar ucs4; 14362 short x; 14363 short y; 14364 } 14365 14366 struct XftCharFontSpec { 14367 XftFont *font; 14368 dchar ucs4; 14369 short x; 14370 short y; 14371 } 14372 14373 struct XftGlyphSpec { 14374 uint glyph; 14375 short x; 14376 short y; 14377 } 14378 14379 struct XftGlyphFontSpec { 14380 XftFont *font; 14381 uint glyph; 14382 short x; 14383 short y; 14384 } 14385 14386 interface Xft { 14387 extern(C) @nogc pure: 14388 14389 Bool XftColorAllocName (Display *dpy, 14390 const Visual *visual, 14391 Colormap cmap, 14392 const char *name, 14393 XftColor *result); 14394 14395 Bool XftColorAllocValue (Display *dpy, 14396 Visual *visual, 14397 Colormap cmap, 14398 const XRenderColor *color, 14399 XftColor *result); 14400 14401 void XftColorFree (Display *dpy, 14402 Visual *visual, 14403 Colormap cmap, 14404 XftColor *color); 14405 14406 Bool XftDefaultHasRender (Display *dpy); 14407 14408 Bool XftDefaultSet (Display *dpy, FcPattern *defaults); 14409 14410 void XftDefaultSubstitute (Display *dpy, int screen, FcPattern *pattern); 14411 14412 XftDraw * XftDrawCreate (Display *dpy, 14413 Drawable drawable, 14414 Visual *visual, 14415 Colormap colormap); 14416 14417 XftDraw * XftDrawCreateBitmap (Display *dpy, 14418 Pixmap bitmap); 14419 14420 XftDraw * XftDrawCreateAlpha (Display *dpy, 14421 Pixmap pixmap, 14422 int depth); 14423 14424 void XftDrawChange (XftDraw *draw, 14425 Drawable drawable); 14426 14427 Display * XftDrawDisplay (XftDraw *draw); 14428 14429 Drawable XftDrawDrawable (XftDraw *draw); 14430 14431 Colormap XftDrawColormap (XftDraw *draw); 14432 14433 Visual * XftDrawVisual (XftDraw *draw); 14434 14435 void XftDrawDestroy (XftDraw *draw); 14436 14437 Picture XftDrawPicture (XftDraw *draw); 14438 14439 Picture XftDrawSrcPicture (XftDraw *draw, const XftColor *color); 14440 14441 void XftDrawGlyphs (XftDraw *draw, 14442 const XftColor *color, 14443 XftFont *pub, 14444 int x, 14445 int y, 14446 const uint *glyphs, 14447 int nglyphs); 14448 14449 void XftDrawString8 (XftDraw *draw, 14450 const XftColor *color, 14451 XftFont *pub, 14452 int x, 14453 int y, 14454 const char *string, 14455 int len); 14456 14457 void XftDrawString16 (XftDraw *draw, 14458 const XftColor *color, 14459 XftFont *pub, 14460 int x, 14461 int y, 14462 const wchar *string, 14463 int len); 14464 14465 void XftDrawString32 (XftDraw *draw, 14466 const XftColor *color, 14467 XftFont *pub, 14468 int x, 14469 int y, 14470 const dchar *string, 14471 int len); 14472 14473 void XftDrawStringUtf8 (XftDraw *draw, 14474 const XftColor *color, 14475 XftFont *pub, 14476 int x, 14477 int y, 14478 const char *string, 14479 int len); 14480 void XftDrawStringUtf16 (XftDraw *draw, 14481 const XftColor *color, 14482 XftFont *pub, 14483 int x, 14484 int y, 14485 const char *string, 14486 FcEndian endian, 14487 int len); 14488 14489 void XftDrawCharSpec (XftDraw *draw, 14490 const XftColor *color, 14491 XftFont *pub, 14492 const XftCharSpec *chars, 14493 int len); 14494 14495 void XftDrawCharFontSpec (XftDraw *draw, 14496 const XftColor *color, 14497 const XftCharFontSpec *chars, 14498 int len); 14499 14500 void XftDrawGlyphSpec (XftDraw *draw, 14501 const XftColor *color, 14502 XftFont *pub, 14503 const XftGlyphSpec *glyphs, 14504 int len); 14505 14506 void XftDrawGlyphFontSpec (XftDraw *draw, 14507 const XftColor *color, 14508 const XftGlyphFontSpec *glyphs, 14509 int len); 14510 14511 void XftDrawRect (XftDraw *draw, 14512 const XftColor *color, 14513 int x, 14514 int y, 14515 uint width, 14516 uint height); 14517 14518 Bool XftDrawSetClip (XftDraw *draw, 14519 Region r); 14520 14521 14522 Bool XftDrawSetClipRectangles (XftDraw *draw, 14523 int xOrigin, 14524 int yOrigin, 14525 const XRectangle *rects, 14526 int n); 14527 14528 void XftDrawSetSubwindowMode (XftDraw *draw, 14529 int mode); 14530 14531 void XftGlyphExtents (Display *dpy, 14532 XftFont *pub, 14533 const uint *glyphs, 14534 int nglyphs, 14535 XGlyphInfo *extents); 14536 14537 void XftTextExtents8 (Display *dpy, 14538 XftFont *pub, 14539 const char *string, 14540 int len, 14541 XGlyphInfo *extents); 14542 14543 void XftTextExtents16 (Display *dpy, 14544 XftFont *pub, 14545 const wchar *string, 14546 int len, 14547 XGlyphInfo *extents); 14548 14549 void XftTextExtents32 (Display *dpy, 14550 XftFont *pub, 14551 const dchar *string, 14552 int len, 14553 XGlyphInfo *extents); 14554 14555 void XftTextExtentsUtf8 (Display *dpy, 14556 XftFont *pub, 14557 const char *string, 14558 int len, 14559 XGlyphInfo *extents); 14560 14561 void XftTextExtentsUtf16 (Display *dpy, 14562 XftFont *pub, 14563 const char *string, 14564 FcEndian endian, 14565 int len, 14566 XGlyphInfo *extents); 14567 14568 FcPattern * XftFontMatch (Display *dpy, 14569 int screen, 14570 const FcPattern *pattern, 14571 FcResult *result); 14572 14573 XftFont * XftFontOpen (Display *dpy, int screen, ...); 14574 14575 XftFont * XftFontOpenName (Display *dpy, int screen, const char *name); 14576 14577 XftFont * XftFontOpenXlfd (Display *dpy, int screen, const char *xlfd); 14578 14579 FT_Face XftLockFace (XftFont *pub); 14580 14581 void XftUnlockFace (XftFont *pub); 14582 14583 XftFontInfo * XftFontInfoCreate (Display *dpy, const FcPattern *pattern); 14584 14585 void XftFontInfoDestroy (Display *dpy, XftFontInfo *fi); 14586 14587 dchar XftFontInfoHash (const XftFontInfo *fi); 14588 14589 FcBool XftFontInfoEqual (const XftFontInfo *a, const XftFontInfo *b); 14590 14591 XftFont * XftFontOpenInfo (Display *dpy, 14592 FcPattern *pattern, 14593 XftFontInfo *fi); 14594 14595 XftFont * XftFontOpenPattern (Display *dpy, FcPattern *pattern); 14596 14597 XftFont * XftFontCopy (Display *dpy, XftFont *pub); 14598 14599 void XftFontClose (Display *dpy, XftFont *pub); 14600 14601 FcBool XftInitFtLibrary(); 14602 void XftFontLoadGlyphs (Display *dpy, 14603 XftFont *pub, 14604 FcBool need_bitmaps, 14605 const uint *glyphs, 14606 int nglyph); 14607 14608 void XftFontUnloadGlyphs (Display *dpy, 14609 XftFont *pub, 14610 const uint *glyphs, 14611 int nglyph); 14612 14613 FcBool XftFontCheckGlyph (Display *dpy, 14614 XftFont *pub, 14615 FcBool need_bitmaps, 14616 uint glyph, 14617 uint *missing, 14618 int *nmissing); 14619 14620 FcBool XftCharExists (Display *dpy, 14621 XftFont *pub, 14622 dchar ucs4); 14623 14624 uint XftCharIndex (Display *dpy, 14625 XftFont *pub, 14626 dchar ucs4); 14627 FcBool XftInit (const char *config); 14628 14629 int XftGetVersion (); 14630 14631 FcFontSet * XftListFonts (Display *dpy, 14632 int screen, 14633 ...); 14634 14635 FcPattern *XftNameParse (const char *name); 14636 14637 void XftGlyphRender (Display *dpy, 14638 int op, 14639 Picture src, 14640 XftFont *pub, 14641 Picture dst, 14642 int srcx, 14643 int srcy, 14644 int x, 14645 int y, 14646 const uint *glyphs, 14647 int nglyphs); 14648 14649 void XftGlyphSpecRender (Display *dpy, 14650 int op, 14651 Picture src, 14652 XftFont *pub, 14653 Picture dst, 14654 int srcx, 14655 int srcy, 14656 const XftGlyphSpec *glyphs, 14657 int nglyphs); 14658 14659 void XftCharSpecRender (Display *dpy, 14660 int op, 14661 Picture src, 14662 XftFont *pub, 14663 Picture dst, 14664 int srcx, 14665 int srcy, 14666 const XftCharSpec *chars, 14667 int len); 14668 void XftGlyphFontSpecRender (Display *dpy, 14669 int op, 14670 Picture src, 14671 Picture dst, 14672 int srcx, 14673 int srcy, 14674 const XftGlyphFontSpec *glyphs, 14675 int nglyphs); 14676 14677 void XftCharFontSpecRender (Display *dpy, 14678 int op, 14679 Picture src, 14680 Picture dst, 14681 int srcx, 14682 int srcy, 14683 const XftCharFontSpec *chars, 14684 int len); 14685 14686 void XftTextRender8 (Display *dpy, 14687 int op, 14688 Picture src, 14689 XftFont *pub, 14690 Picture dst, 14691 int srcx, 14692 int srcy, 14693 int x, 14694 int y, 14695 const char *string, 14696 int len); 14697 void XftTextRender16 (Display *dpy, 14698 int op, 14699 Picture src, 14700 XftFont *pub, 14701 Picture dst, 14702 int srcx, 14703 int srcy, 14704 int x, 14705 int y, 14706 const wchar *string, 14707 int len); 14708 14709 void XftTextRender16BE (Display *dpy, 14710 int op, 14711 Picture src, 14712 XftFont *pub, 14713 Picture dst, 14714 int srcx, 14715 int srcy, 14716 int x, 14717 int y, 14718 const char *string, 14719 int len); 14720 14721 void XftTextRender16LE (Display *dpy, 14722 int op, 14723 Picture src, 14724 XftFont *pub, 14725 Picture dst, 14726 int srcx, 14727 int srcy, 14728 int x, 14729 int y, 14730 const char *string, 14731 int len); 14732 14733 void XftTextRender32 (Display *dpy, 14734 int op, 14735 Picture src, 14736 XftFont *pub, 14737 Picture dst, 14738 int srcx, 14739 int srcy, 14740 int x, 14741 int y, 14742 const dchar *string, 14743 int len); 14744 14745 void XftTextRender32BE (Display *dpy, 14746 int op, 14747 Picture src, 14748 XftFont *pub, 14749 Picture dst, 14750 int srcx, 14751 int srcy, 14752 int x, 14753 int y, 14754 const char *string, 14755 int len); 14756 14757 void XftTextRender32LE (Display *dpy, 14758 int op, 14759 Picture src, 14760 XftFont *pub, 14761 Picture dst, 14762 int srcx, 14763 int srcy, 14764 int x, 14765 int y, 14766 const char *string, 14767 int len); 14768 14769 void XftTextRenderUtf8 (Display *dpy, 14770 int op, 14771 Picture src, 14772 XftFont *pub, 14773 Picture dst, 14774 int srcx, 14775 int srcy, 14776 int x, 14777 int y, 14778 const char *string, 14779 int len); 14780 14781 void XftTextRenderUtf16 (Display *dpy, 14782 int op, 14783 Picture src, 14784 XftFont *pub, 14785 Picture dst, 14786 int srcx, 14787 int srcy, 14788 int x, 14789 int y, 14790 const char *string, 14791 FcEndian endian, 14792 int len); 14793 FcPattern * XftXlfdParse (const char *xlfd_orig, Bool ignore_scalable, Bool complete); 14794 14795 } 14796 14797 interface FontConfig { 14798 extern(C) @nogc pure: 14799 int FcPatternGetString(const FcPattern *p, const char *object, int n, char ** s); 14800 void FcFontSetDestroy(FcFontSet*); 14801 char* FcNameUnparse(const FcPattern *); 14802 } 14803 14804 mixin DynamicLoad!(Xft, "Xft", 2, librariesSuccessfullyLoaded) XftLibrary; 14805 mixin DynamicLoad!(FontConfig, "fontconfig", 1, librariesSuccessfullyLoaded) FontConfigLibrary; 14806 14807 14808 /* Xft } */ 14809 14810 class XDisconnectException : Exception { 14811 bool userRequested; 14812 this(bool userRequested = true) { 14813 this.userRequested = userRequested; 14814 super("X disconnected"); 14815 } 14816 } 14817 14818 /++ 14819 Platform-specific for X11. Traps errors for the duration of `dg`. Avoid calling this from inside a call to this. 14820 14821 Please note that it returns 14822 +/ 14823 XErrorEvent[] trapXErrors(scope void delegate() dg) { 14824 14825 static XErrorEvent[] errorBuffer; 14826 14827 static extern(C) int handler (Display* dpy, XErrorEvent* evt) nothrow { 14828 errorBuffer ~= *evt; 14829 return 0; 14830 } 14831 14832 auto savedErrorHandler = XSetErrorHandler(&handler); 14833 14834 try { 14835 dg(); 14836 } finally { 14837 XSync(XDisplayConnection.get, 0/*False*/); 14838 XSetErrorHandler(savedErrorHandler); 14839 } 14840 14841 auto bfr = errorBuffer; 14842 errorBuffer = null; 14843 14844 return bfr; 14845 } 14846 14847 /// Platform-specific for X11. A singleton class (well, all its methods are actually static... so more like a namespace) wrapping a `Display*`. 14848 class XDisplayConnection { 14849 private __gshared Display* display; 14850 private __gshared XIM xim; 14851 private __gshared char* displayName; 14852 14853 private __gshared int connectionSequence_; 14854 private __gshared bool isLocal_; 14855 14856 /// use this for lazy caching when reconnection 14857 static int connectionSequenceNumber() { return connectionSequence_; } 14858 14859 /++ 14860 Guesses if the connection appears to be local. 14861 14862 History: 14863 Added June 3, 2021 14864 +/ 14865 static @property bool isLocal() nothrow @trusted @nogc { 14866 return isLocal_; 14867 } 14868 14869 /// Attempts recreation of state, may require application assistance 14870 /// You MUST call this OUTSIDE the event loop. Let the exception kill the loop, 14871 /// then call this, and if successful, reenter the loop. 14872 static void discardAndRecreate(string newDisplayString = null) { 14873 if(insideXEventLoop) 14874 throw new Error("You MUST call discardAndRecreate from OUTSIDE the event loop"); 14875 14876 // 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 14877 auto chnenhm = CapableOfHandlingNativeEvent.nativeHandleMapping.dup; 14878 14879 foreach(handle; chnenhm) { 14880 handle.discardConnectionState(); 14881 } 14882 14883 discardState(); 14884 14885 if(newDisplayString !is null) 14886 setDisplayName(newDisplayString); 14887 14888 auto display = get(); 14889 14890 foreach(handle; chnenhm) { 14891 handle.recreateAfterDisconnect(); 14892 } 14893 } 14894 14895 private __gshared EventMask rootEventMask; 14896 14897 /++ 14898 Requests the specified input from the root window on the connection, in addition to any other request. 14899 14900 14901 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. 14902 14903 $(WARNING it calls XSelectInput itself, which will override any other root window input you have!) 14904 +/ 14905 static void addRootInput(EventMask mask) { 14906 auto old = rootEventMask; 14907 rootEventMask |= mask; 14908 get(); // to ensure display connected 14909 if(display !is null && rootEventMask != old) 14910 XSelectInput(display, RootWindow(display, DefaultScreen(display)), rootEventMask); 14911 } 14912 14913 static void discardState() { 14914 freeImages(); 14915 14916 foreach(atomPtr; interredAtoms) 14917 *atomPtr = 0; 14918 interredAtoms = null; 14919 interredAtoms.assumeSafeAppend(); 14920 14921 ScreenPainterImplementation.fontAttempted = false; 14922 ScreenPainterImplementation.defaultfont = null; 14923 ScreenPainterImplementation.defaultfontset = null; 14924 14925 Image.impl.xshmQueryCompleted = false; 14926 Image.impl._xshmAvailable = false; 14927 14928 SimpleWindow.nativeMapping = null; 14929 CapableOfHandlingNativeEvent.nativeHandleMapping = null; 14930 // GlobalHotkeyManager 14931 14932 display = null; 14933 xim = null; 14934 } 14935 14936 // Do you want to know why do we need all this horrible-looking code? See comment at the bottom. 14937 private static void createXIM () { 14938 import core.stdc.locale : setlocale, LC_ALL; 14939 import core.stdc.stdio : stderr, fprintf; 14940 import core.stdc.stdlib : free; 14941 import core.stdc.string : strdup; 14942 14943 static immutable string[3] mtry = [ "", "@im=local", "@im=" ]; 14944 14945 auto olocale = strdup(setlocale(LC_ALL, null)); 14946 setlocale(LC_ALL, (sdx_isUTF8Locale ? "" : "en_US.UTF-8")); 14947 scope(exit) { setlocale(LC_ALL, olocale); free(olocale); } 14948 14949 //fprintf(stderr, "opening IM...\n"); 14950 foreach (string s; mtry) { 14951 XSetLocaleModifiers(s.ptr); // it's safe, as `s` is string literal 14952 if ((xim = XOpenIM(display, null, null, null)) !is null) return; 14953 } 14954 fprintf(stderr, "createXIM: XOpenIM failed!\n"); 14955 } 14956 14957 // for X11 we will keep all XShm-allocated images in this list, so we can free 'em on connection closing. 14958 // we'll use glibc malloc()/free(), 'cause `unregisterImage()` can be called from object dtor. 14959 static struct ImgList { 14960 size_t img; // class; hide it from GC 14961 ImgList* next; 14962 } 14963 14964 static __gshared ImgList* imglist = null; 14965 static __gshared bool imglistLocked = false; // true: don't register and unregister images 14966 14967 static void registerImage (Image img) { 14968 if (!imglistLocked && img !is null) { 14969 import core.stdc.stdlib : malloc; 14970 auto it = cast(ImgList*)malloc(ImgList.sizeof); 14971 assert(it !is null); // do proper checks 14972 it.img = cast(size_t)cast(void*)img; 14973 it.next = imglist; 14974 imglist = it; 14975 version(sdpy_debug_xshm) { import core.stdc.stdio : printf; printf("registering image %p\n", cast(void*)img); } 14976 } 14977 } 14978 14979 static void unregisterImage (Image img) { 14980 if (!imglistLocked && img !is null) { 14981 import core.stdc.stdlib : free; 14982 ImgList* prev = null; 14983 ImgList* cur = imglist; 14984 while (cur !is null) { 14985 if (cur.img == cast(size_t)cast(void*)img) break; // i found her! 14986 prev = cur; 14987 cur = cur.next; 14988 } 14989 if (cur !is null) { 14990 if (prev is null) imglist = cur.next; else prev.next = cur.next; 14991 free(cur); 14992 version(sdpy_debug_xshm) { import core.stdc.stdio : printf; printf("unregistering image %p\n", cast(void*)img); } 14993 } else { 14994 version(sdpy_debug_xshm) { import core.stdc.stdio : printf; printf("trying to unregister unknown image %p\n", cast(void*)img); } 14995 } 14996 } 14997 } 14998 14999 static void freeImages () { // needed for discardAndRecreate 15000 imglistLocked = true; 15001 scope(exit) imglistLocked = false; 15002 ImgList* cur = imglist; 15003 ImgList* next = null; 15004 while (cur !is null) { 15005 import core.stdc.stdlib : free; 15006 next = cur.next; 15007 version(sdpy_debug_xshm) { import core.stdc.stdio : printf; printf("disposing image %p\n", cast(void*)cur.img); } 15008 (cast(Image)cast(void*)cur.img).dispose(); 15009 free(cur); 15010 cur = next; 15011 } 15012 imglist = null; 15013 } 15014 15015 /// can be used to override normal handling of display name 15016 /// from environment and/or command line 15017 static setDisplayName(string newDisplayName) { 15018 displayName = cast(char*) (newDisplayName ~ '\0'); 15019 } 15020 15021 /// resets to the default display string 15022 static resetDisplayName() { 15023 displayName = null; 15024 } 15025 15026 /// 15027 static Display* get() { 15028 if(display is null) { 15029 if(!librariesSuccessfullyLoaded) 15030 throw new Exception("Unable to load X11 client libraries"); 15031 display = XOpenDisplay(displayName); 15032 15033 isLocal_ = false; 15034 15035 connectionSequence_++; 15036 if(display is null) 15037 throw new Exception("Unable to open X display"); 15038 15039 auto str = display.display_name; 15040 // this is a bit of a hack but like if it looks like a unix socket we assume it is local 15041 // and otherwise it probably isn't 15042 if(str is null || (str[0] != ':' && str[0] != '/')) 15043 isLocal_ = false; 15044 else 15045 isLocal_ = true; 15046 15047 XSetErrorHandler(&adrlogger); 15048 15049 debug(sdpy_x_errors) { 15050 XSynchronize(display, true); 15051 15052 extern(C) int wtf() { 15053 if(errorHappened) { 15054 asm { int 3; } 15055 errorHappened = false; 15056 } 15057 return 0; 15058 } 15059 XSetAfterFunction(display, &wtf); 15060 } 15061 15062 15063 XSetIOErrorHandler(&x11ioerrCB); 15064 Bool sup; 15065 XkbSetDetectableAutoRepeat(display, 1, &sup); // so we will not receive KeyRelease until key is really released 15066 createXIM(); 15067 version(with_eventloop) { 15068 import arsd.eventloop; 15069 addFileEventListeners(display.fd, &eventListener, null, null); 15070 } 15071 } 15072 15073 return display; 15074 } 15075 15076 extern(C) 15077 static int x11ioerrCB(Display* dpy) { 15078 throw new XDisconnectException(false); 15079 } 15080 15081 version(with_eventloop) { 15082 import arsd.eventloop; 15083 static void eventListener(OsFileHandle fd) { 15084 //this.mtLock(); 15085 //scope(exit) this.mtUnlock(); 15086 while(XPending(display)) 15087 doXNextEvent(display); 15088 } 15089 } 15090 15091 // close connection on program exit -- we need this to properly free all images 15092 static ~this () { 15093 // the gui thread must clean up after itself or else Xlib might deadlock 15094 // using this flag on any thread destruction is the easiest way i know of 15095 // (shared static this is run by the LAST thread to exit, which may not be 15096 // the gui thread, and normal static this run by ALL threads, so we gotta check.) 15097 if(thisIsGuiThread) 15098 close(); 15099 } 15100 15101 /// 15102 static void close() { 15103 if(display is null) 15104 return; 15105 15106 version(with_eventloop) { 15107 import arsd.eventloop; 15108 removeFileEventListeners(display.fd); 15109 } 15110 15111 // now remove all registered images to prevent shared memory leaks 15112 freeImages(); 15113 15114 // tbh I don't know why it is doing this but like if this happens to run 15115 // from the other thread there's frequent hanging inside here. 15116 if(thisIsGuiThread) 15117 XCloseDisplay(display); 15118 display = null; 15119 } 15120 } 15121 15122 mixin template NativeImageImplementation() { 15123 XImage* handle; 15124 ubyte* rawData; 15125 15126 XShmSegmentInfo shminfo; 15127 bool premultiply = true; 15128 15129 __gshared bool xshmQueryCompleted; 15130 __gshared bool _xshmAvailable; 15131 public static @property bool xshmAvailable() { 15132 if(!xshmQueryCompleted) { 15133 int i1, i2, i3; 15134 xshmQueryCompleted = true; 15135 15136 if(!XDisplayConnection.isLocal) 15137 _xshmAvailable = false; 15138 else 15139 _xshmAvailable = XQueryExtension(XDisplayConnection.get(), "MIT-SHM", &i1, &i2, &i3) != 0; 15140 } 15141 return _xshmAvailable; 15142 } 15143 15144 bool usingXshm; 15145 final: 15146 15147 private __gshared bool xshmfailed; 15148 15149 void createImage(int width, int height, bool forcexshm=false, bool enableAlpha = false) { 15150 auto display = XDisplayConnection.get(); 15151 assert(display !is null); 15152 auto screen = DefaultScreen(display); 15153 15154 // it will only use shared memory for somewhat largish images, 15155 // since otherwise we risk wasting shared memory handles on a lot of little ones 15156 if (xshmAvailable && (forcexshm || (width > 100 && height > 100))) { 15157 15158 15159 // it is possible for the query extension to return true, the DISPLAY check to pass, yet 15160 // the actual use still fails. For example, if the program is in a container and permission denied 15161 // on shared memory, or if it is a local thing forwarded to a remote server, etc. 15162 // 15163 // If it does fail, we need to detect it now, abort the xshm and fall back to core protocol. 15164 15165 15166 // synchronize so preexisting buffers are clear 15167 XSync(display, false); 15168 xshmfailed = false; 15169 15170 auto oldErrorHandler = XSetErrorHandler(&XShmErrorHandler); 15171 15172 15173 usingXshm = true; 15174 handle = XShmCreateImage( 15175 display, 15176 DefaultVisual(display, screen), 15177 enableAlpha ? 32: 24, 15178 ImageFormat.ZPixmap, 15179 null, 15180 &shminfo, 15181 width, height); 15182 if(handle is null) 15183 goto abortXshm1; 15184 15185 if(handle.bytes_per_line != 4 * width) 15186 goto abortXshm2; 15187 15188 shminfo.shmid = shmget(IPC_PRIVATE, handle.bytes_per_line * height, IPC_CREAT | 511 /* 0777 */); 15189 if(shminfo.shmid < 0) 15190 goto abortXshm3; 15191 handle.data = shminfo.shmaddr = rawData = cast(ubyte*) shmat(shminfo.shmid, null, 0); 15192 if(rawData == cast(ubyte*) -1) 15193 goto abortXshm4; 15194 shminfo.readOnly = 0; 15195 XShmAttach(display, &shminfo); 15196 15197 // and now to the final error check to ensure it actually worked. 15198 XSync(display, false); 15199 if(xshmfailed) 15200 goto abortXshm5; 15201 15202 XSetErrorHandler(oldErrorHandler); 15203 15204 XDisplayConnection.registerImage(this); 15205 // if I don't flush here there's a chance the dtor will run before the 15206 // ctor and lead to a bad value X error. While this hurts the efficiency 15207 // it is local anyway so prolly better to keep it simple 15208 XFlush(display); 15209 15210 return; 15211 15212 abortXshm5: 15213 shmdt(shminfo.shmaddr); 15214 rawData = null; 15215 15216 abortXshm4: 15217 shmctl(shminfo.shmid, IPC_RMID, null); 15218 15219 abortXshm3: 15220 // nothing needed, the shmget failed so there's nothing to free 15221 15222 abortXshm2: 15223 XDestroyImage(handle); 15224 handle = null; 15225 15226 abortXshm1: 15227 XSetErrorHandler(oldErrorHandler); 15228 usingXshm = false; 15229 handle = null; 15230 15231 shminfo = typeof(shminfo).init; 15232 15233 _xshmAvailable = false; // don't try again in the future 15234 15235 // writeln("fallingback"); 15236 15237 goto fallback; 15238 15239 } else { 15240 fallback: 15241 15242 if (forcexshm) throw new Exception("can't create XShm Image"); 15243 // This actually needs to be malloc to avoid a double free error when XDestroyImage is called 15244 import core.stdc.stdlib : malloc; 15245 rawData = cast(ubyte*) malloc(width * height * 4); 15246 15247 handle = XCreateImage( 15248 display, 15249 DefaultVisual(display, screen), 15250 enableAlpha ? 32 : 24, // bpp 15251 ImageFormat.ZPixmap, 15252 0, // offset 15253 rawData, 15254 width, height, 15255 enableAlpha ? 32 : 8 /* FIXME */, 4 * width); // padding, bytes per line 15256 } 15257 } 15258 15259 void dispose() { 15260 // note: this calls free(rawData) for us 15261 if(handle) { 15262 if (usingXshm) { 15263 XDisplayConnection.unregisterImage(this); 15264 if (XDisplayConnection.get()) XShmDetach(XDisplayConnection.get(), &shminfo); 15265 } 15266 XDestroyImage(handle); 15267 if(usingXshm) { 15268 shmdt(shminfo.shmaddr); 15269 shmctl(shminfo.shmid, IPC_RMID, null); 15270 } 15271 handle = null; 15272 } 15273 } 15274 15275 Color getPixel(int x, int y) @system { 15276 auto offset = (y * width + x) * 4; 15277 Color c; 15278 c.a = enableAlpha ? rawData[offset + 3] : 255; 15279 c.b = rawData[offset + 0]; 15280 c.g = rawData[offset + 1]; 15281 c.r = rawData[offset + 2]; 15282 if(enableAlpha && premultiply) 15283 c.unPremultiply; 15284 return c; 15285 } 15286 15287 void setPixel(int x, int y, Color c) @system { 15288 if(enableAlpha && premultiply) 15289 c.premultiply(); 15290 auto offset = (y * width + x) * 4; 15291 rawData[offset + 0] = c.b; 15292 rawData[offset + 1] = c.g; 15293 rawData[offset + 2] = c.r; 15294 if(enableAlpha) 15295 rawData[offset + 3] = c.a; 15296 } 15297 15298 void convertToRgbaBytes(ubyte[] where) @system { 15299 assert(where.length == this.width * this.height * 4); 15300 15301 // if rawData had a length.... 15302 //assert(rawData.length == where.length); 15303 for(int idx = 0; idx < where.length; idx += 4) { 15304 where[idx + 0] = rawData[idx + 2]; // r 15305 where[idx + 1] = rawData[idx + 1]; // g 15306 where[idx + 2] = rawData[idx + 0]; // b 15307 where[idx + 3] = enableAlpha ? rawData[idx + 3] : 255; // a 15308 15309 if(enableAlpha && premultiply) 15310 unPremultiplyRgba(where[idx .. idx + 4]); 15311 } 15312 } 15313 15314 void setFromRgbaBytes(in ubyte[] where) @system { 15315 assert(where.length == this.width * this.height * 4); 15316 15317 // if rawData had a length.... 15318 //assert(rawData.length == where.length); 15319 for(int idx = 0; idx < where.length; idx += 4) { 15320 rawData[idx + 2] = where[idx + 0]; // r 15321 rawData[idx + 1] = where[idx + 1]; // g 15322 rawData[idx + 0] = where[idx + 2]; // b 15323 if(enableAlpha) { 15324 rawData[idx + 3] = where[idx + 3]; // a 15325 if(premultiply) 15326 premultiplyBgra(rawData[idx .. idx + 4]); 15327 } 15328 } 15329 } 15330 15331 } 15332 15333 mixin template NativeSimpleWindowImplementation() { 15334 GC gc; 15335 Window window; 15336 Display* display; 15337 15338 Pixmap buffer; 15339 int bufferw, bufferh; // size of the buffer; can be bigger than window 15340 XIC xic; // input context 15341 int curHidden = 0; // counter 15342 Cursor blankCurPtr = 0; 15343 int cursorSequenceNumber = 0; 15344 int warpEventCount = 0; // number of mouse movement events to eat 15345 15346 __gshared X11SetSelectionHandler[Atom] setSelectionHandlers; // FIXME: make sure this is not accessed from other threads. it might be ok to make it TLS 15347 X11GetSelectionHandler[Atom] getSelectionHandlers; 15348 15349 version(without_opengl) {} else 15350 GLXContext glc; 15351 15352 private void fixFixedSize(bool forced=false) (int width, int height) { 15353 if (forced || this.resizability == Resizability.fixedSize) { 15354 //{ import core.stdc.stdio; printf("fixing size to: %dx%d\n", width, height); } 15355 XSizeHints sh; 15356 static if (!forced) { 15357 c_long spr; 15358 XGetWMNormalHints(display, window, &sh, &spr); 15359 sh.flags |= PMaxSize | PMinSize; 15360 } else { 15361 sh.flags = PMaxSize | PMinSize; 15362 } 15363 sh.min_width = width; 15364 sh.min_height = height; 15365 sh.max_width = width; 15366 sh.max_height = height; 15367 XSetWMNormalHints(display, window, &sh); 15368 //XFlush(display); 15369 } 15370 } 15371 15372 ScreenPainter getPainter(bool manualInvalidations) { 15373 return ScreenPainter(this, window, manualInvalidations); 15374 } 15375 15376 void move(int x, int y) { 15377 XMoveWindow(display, window, x, y); 15378 } 15379 15380 void resize(int w, int h) { 15381 if (w < 1) w = 1; 15382 if (h < 1) h = 1; 15383 XResizeWindow(display, window, w, h); 15384 15385 // calling this now to avoid waiting for the server to 15386 // acknowledge the resize; draws without returning to the 15387 // event loop will thus actually work. the server's event 15388 // btw might overrule this and resize it again 15389 recordX11Resize(display, this, w, h); 15390 15391 updateOpenglViewportIfNeeded(w, h); 15392 } 15393 15394 void moveResize (int x, int y, int w, int h) { 15395 if (w < 1) w = 1; 15396 if (h < 1) h = 1; 15397 XMoveResizeWindow(display, window, x, y, w, h); 15398 updateOpenglViewportIfNeeded(w, h); 15399 } 15400 15401 void hideCursor () { 15402 if (curHidden++ == 0) { 15403 if (!blankCurPtr || cursorSequenceNumber != XDisplayConnection.connectionSequenceNumber) { 15404 static const(char)[1] cmbmp = 0; 15405 XColor blackcolor = { 0, 0, 0, 0, 0, 0 }; 15406 Pixmap pm = XCreateBitmapFromData(display, window, cmbmp.ptr, 1, 1); 15407 blankCurPtr = XCreatePixmapCursor(display, pm, pm, &blackcolor, &blackcolor, 0, 0); 15408 cursorSequenceNumber = XDisplayConnection.connectionSequenceNumber; 15409 XFreePixmap(display, pm); 15410 } 15411 XDefineCursor(display, window, blankCurPtr); 15412 } 15413 } 15414 15415 void showCursor () { 15416 if (--curHidden == 0) XUndefineCursor(display, window); 15417 } 15418 15419 void warpMouse (int x, int y) { 15420 // here i will send dummy "ignore next mouse motion" event, 15421 // 'cause `XWarpPointer()` sends synthesised mouse motion, 15422 // and we don't need to report it to the user (as warping is 15423 // used when the user needs movement deltas). 15424 //XClientMessageEvent xclient; 15425 XEvent e; 15426 e.xclient.type = EventType.ClientMessage; 15427 e.xclient.window = window; 15428 e.xclient.message_type = GetAtom!("_X11SDPY_INSMME_FLAG_EVENT_", true)(display); // let's hope nobody else will use such stupid name ;-) 15429 e.xclient.format = 32; 15430 e.xclient.data.l[0] = 0; 15431 debug(x11sdpy_warp_debug) { import core.stdc.stdio : printf; printf("X11: sending \"INSMME\"...\n"); } 15432 //{ 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]); } 15433 XSendEvent(display, window, false, EventMask.NoEventMask, /*cast(XEvent*)&xclient*/&e); 15434 // now warp pointer... 15435 debug(x11sdpy_warp_debug) { import core.stdc.stdio : printf; printf("X11: sending \"warp\"...\n"); } 15436 XWarpPointer(display, None, window, 0, 0, 0, 0, x, y); 15437 // ...and flush 15438 debug(x11sdpy_warp_debug) { import core.stdc.stdio : printf; printf("X11: flushing...\n"); } 15439 XFlush(display); 15440 } 15441 15442 void sendDummyEvent () { 15443 // here i will send dummy event to ping event queue 15444 XEvent e; 15445 e.xclient.type = EventType.ClientMessage; 15446 e.xclient.window = window; 15447 e.xclient.message_type = GetAtom!("_X11SDPY_DUMMY_EVENT_", true)(display); // let's hope nobody else will use such stupid name ;-) 15448 e.xclient.format = 32; 15449 e.xclient.data.l[0] = 0; 15450 XSendEvent(display, window, false, EventMask.NoEventMask, /*cast(XEvent*)&xclient*/&e); 15451 XFlush(display); 15452 } 15453 15454 void setTitle(string title) { 15455 if (title.ptr is null) title = ""; 15456 auto XA_UTF8 = XInternAtom(display, "UTF8_STRING".ptr, false); 15457 auto XA_NETWM_NAME = XInternAtom(display, "_NET_WM_NAME".ptr, false); 15458 XTextProperty windowName; 15459 windowName.value = title.ptr; 15460 windowName.encoding = XA_UTF8; //XA_STRING; 15461 windowName.format = 8; 15462 windowName.nitems = cast(uint)title.length; 15463 XSetWMName(display, window, &windowName); 15464 char[1024] namebuf = 0; 15465 auto maxlen = namebuf.length-1; 15466 if (maxlen > title.length) maxlen = title.length; 15467 namebuf[0..maxlen] = title[0..maxlen]; 15468 XStoreName(display, window, namebuf.ptr); 15469 XChangeProperty(display, window, XA_NETWM_NAME, XA_UTF8, 8, PropModeReplace, title.ptr, cast(uint)title.length); 15470 flushGui(); // without this OpenGL windows has a LONG delay before changing title 15471 } 15472 15473 string[] getTitles() { 15474 auto XA_UTF8 = XInternAtom(display, "UTF8_STRING".ptr, false); 15475 auto XA_NETWM_NAME = XInternAtom(display, "_NET_WM_NAME".ptr, false); 15476 XTextProperty textProp; 15477 if (XGetTextProperty(display, window, &textProp, XA_NETWM_NAME) != 0 || XGetWMName(display, window, &textProp) != 0) { 15478 if ((textProp.encoding == XA_UTF8 || textProp.encoding == XA_STRING) && textProp.format == 8) { 15479 return textProp.value[0 .. textProp.nitems].idup.split('\0'); 15480 } else 15481 return []; 15482 } else 15483 return null; 15484 } 15485 15486 string getTitle() { 15487 auto titles = getTitles(); 15488 return titles.length ? titles[0] : null; 15489 } 15490 15491 void setMinSize (int minwidth, int minheight) { 15492 import core.stdc.config : c_long; 15493 if (minwidth < 1) minwidth = 1; 15494 if (minheight < 1) minheight = 1; 15495 XSizeHints sh; 15496 c_long spr; 15497 XGetWMNormalHints(display, window, &sh, &spr); 15498 sh.min_width = minwidth; 15499 sh.min_height = minheight; 15500 sh.flags |= PMinSize; 15501 XSetWMNormalHints(display, window, &sh); 15502 flushGui(); 15503 } 15504 15505 void setMaxSize (int maxwidth, int maxheight) { 15506 import core.stdc.config : c_long; 15507 if (maxwidth < 1) maxwidth = 1; 15508 if (maxheight < 1) maxheight = 1; 15509 XSizeHints sh; 15510 c_long spr; 15511 XGetWMNormalHints(display, window, &sh, &spr); 15512 sh.max_width = maxwidth; 15513 sh.max_height = maxheight; 15514 sh.flags |= PMaxSize; 15515 XSetWMNormalHints(display, window, &sh); 15516 flushGui(); 15517 } 15518 15519 void setResizeGranularity (int granx, int grany) { 15520 import core.stdc.config : c_long; 15521 if (granx < 1) granx = 1; 15522 if (grany < 1) grany = 1; 15523 XSizeHints sh; 15524 c_long spr; 15525 XGetWMNormalHints(display, window, &sh, &spr); 15526 sh.width_inc = granx; 15527 sh.height_inc = grany; 15528 sh.flags |= PResizeInc; 15529 XSetWMNormalHints(display, window, &sh); 15530 flushGui(); 15531 } 15532 15533 void setOpacity (uint opacity) { 15534 arch_ulong o = opacity; 15535 if (opacity == uint.max) 15536 XDeleteProperty(display, window, XInternAtom(display, "_NET_WM_WINDOW_OPACITY".ptr, false)); 15537 else 15538 XChangeProperty(display, window, XInternAtom(display, "_NET_WM_WINDOW_OPACITY".ptr, false), 15539 XA_CARDINAL, 32, PropModeReplace, &o, 1); 15540 } 15541 15542 void createWindow(int width, int height, string title, in OpenGlOptions opengl, SimpleWindow parent) @trusted { 15543 version(without_opengl) {} else if(opengl == OpenGlOptions.yes && !openGlLibrariesSuccessfullyLoaded) throw new Exception("OpenGL libraries did not load"); 15544 display = XDisplayConnection.get(); 15545 auto screen = DefaultScreen(display); 15546 15547 bool overrideRedirect = false; 15548 if( 15549 windowType == WindowTypes.dropdownMenu || windowType == WindowTypes.popupMenu || 15550 windowType == WindowTypes.tooltip || 15551 windowType == WindowTypes.notification || 15552 windowType == WindowTypes.dnd || 15553 windowType == WindowTypes.comboBoxDropdown || 15554 (customizationFlags & WindowFlags.overrideRedirect) 15555 )// || windowType == WindowTypes.nestedChild) 15556 overrideRedirect = true; 15557 15558 version(without_opengl) {} 15559 else { 15560 if(opengl == OpenGlOptions.yes) { 15561 GLXFBConfig fbconf = null; 15562 XVisualInfo* vi = null; 15563 bool useLegacy = false; 15564 static if (SdpyIsUsingIVGLBinds) {if (glbindGetProcAddress("glHint") is null) assert(0, "GL: error loading OpenGL"); } // loads all necessary functions 15565 if (sdpyOpenGLContextVersion != 0 && glXCreateContextAttribsARB_present()) { 15566 int[23] visualAttribs = [ 15567 GLX_X_RENDERABLE , 1/*True*/, 15568 GLX_DRAWABLE_TYPE, GLX_WINDOW_BIT, 15569 GLX_RENDER_TYPE , GLX_RGBA_BIT, 15570 GLX_X_VISUAL_TYPE, GLX_TRUE_COLOR, 15571 GLX_RED_SIZE , 8, 15572 GLX_GREEN_SIZE , 8, 15573 GLX_BLUE_SIZE , 8, 15574 GLX_ALPHA_SIZE , 8, 15575 GLX_DEPTH_SIZE , 24, 15576 GLX_STENCIL_SIZE , 8, 15577 GLX_DOUBLEBUFFER , 1/*True*/, 15578 0/*None*/, 15579 ]; 15580 int fbcount; 15581 GLXFBConfig* fbc = glXChooseFBConfig(display, screen, visualAttribs.ptr, &fbcount); 15582 if (fbcount == 0) { 15583 useLegacy = true; // try to do at least something 15584 } else { 15585 // pick the FB config/visual with the most samples per pixel 15586 int bestidx = -1, bestns = -1; 15587 foreach (int fbi; 0..fbcount) { 15588 int sb, samples; 15589 glXGetFBConfigAttrib(display, fbc[fbi], GLX_SAMPLE_BUFFERS, &sb); 15590 glXGetFBConfigAttrib(display, fbc[fbi], GLX_SAMPLES, &samples); 15591 if (bestidx < 0 || sb && samples > bestns) { bestidx = fbi; bestns = samples; } 15592 } 15593 //{ import core.stdc.stdio; printf("found gl visual with %d samples\n", bestns); } 15594 fbconf = fbc[bestidx]; 15595 // Be sure to free the FBConfig list allocated by glXChooseFBConfig() 15596 XFree(fbc); 15597 vi = cast(XVisualInfo*)glXGetVisualFromFBConfig(display, fbconf); 15598 } 15599 } 15600 if (vi is null || useLegacy) { 15601 static immutable GLint[5] attrs = [ GLX_RGBA, GLX_DEPTH_SIZE, 24, GLX_DOUBLEBUFFER, None ]; 15602 vi = cast(XVisualInfo*)glXChooseVisual(display, 0, attrs.ptr); 15603 useLegacy = true; 15604 } 15605 if (vi is null) throw new Exception("no open gl visual found"); 15606 15607 XSetWindowAttributes swa; 15608 auto root = RootWindow(display, screen); 15609 swa.colormap = XCreateColormap(display, root, vi.visual, AllocNone); 15610 15611 swa.override_redirect = overrideRedirect; 15612 15613 window = XCreateWindow(display, (windowType != WindowTypes.nestedChild || parent is null) ? root : parent.impl.window, 15614 0, 0, width, height, 15615 0, vi.depth, 1 /* InputOutput */, vi.visual, CWColormap | CWOverrideRedirect, &swa); 15616 15617 // now try to use `glXCreateContextAttribsARB()` if it's here 15618 if (!useLegacy) { 15619 // request fairly advanced context, even with stencil buffer! 15620 int[9] contextAttribs = [ 15621 GLX_CONTEXT_MAJOR_VERSION_ARB, (sdpyOpenGLContextVersion>>8), 15622 GLX_CONTEXT_MINOR_VERSION_ARB, (sdpyOpenGLContextVersion&0xff), 15623 /*GLX_CONTEXT_PROFILE_MASK_ARB*/0x9126, (sdpyOpenGLContextCompatible ? /*GLX_CONTEXT_COMPATIBILITY_PROFILE_BIT_ARB*/0x02 : /*GLX_CONTEXT_CORE_PROFILE_BIT_ARB*/ 0x01), 15624 // for modern context, set "forward compatibility" flag too 15625 (sdpyOpenGLContextCompatible ? None : /*GLX_CONTEXT_FLAGS_ARB*/ 0x2094), /*GLX_CONTEXT_FORWARD_COMPATIBLE_BIT_ARB*/ 0x02, 15626 0/*None*/, 15627 ]; 15628 glc = glXCreateContextAttribsARB(display, fbconf, null, 1/*True*/, contextAttribs.ptr); 15629 if (glc is null && sdpyOpenGLContextAllowFallback) { 15630 sdpyOpenGLContextVersion = 0; 15631 glc = glXCreateContext(display, vi, null, /*GL_TRUE*/1); 15632 } 15633 //{ import core.stdc.stdio; printf("using modern ogl v%d.%d\n", contextAttribs[1], contextAttribs[3]); } 15634 } else { 15635 // fallback to old GLX call 15636 if (sdpyOpenGLContextAllowFallback || sdpyOpenGLContextVersion == 0) { 15637 sdpyOpenGLContextVersion = 0; 15638 glc = glXCreateContext(display, vi, null, /*GL_TRUE*/1); 15639 } 15640 } 15641 // sync to ensure any errors generated are processed 15642 XSync(display, 0/*False*/); 15643 //{ import core.stdc.stdio; printf("ogl is here\n"); } 15644 if(glc is null) 15645 throw new Exception("glc"); 15646 } 15647 } 15648 15649 if(opengl == OpenGlOptions.no) { 15650 15651 XSetWindowAttributes swa; 15652 swa.background_pixel = WhitePixel(display, screen); 15653 swa.border_pixel = BlackPixel(display, screen); 15654 swa.override_redirect = overrideRedirect; 15655 auto root = RootWindow(display, screen); 15656 swa.colormap = XCreateColormap(display, root, DefaultVisual(display, screen), AllocNone); 15657 15658 window = XCreateWindow(display, (windowType != WindowTypes.nestedChild || parent is null) ? root : parent.impl.window, 15659 0, 0, width, height, 15660 // I'm commenting that CWBackPixel thing just because it actually causes flicker for no apparent benefit. 15661 0, CopyFromParent, 1 /* InputOutput */, cast(Visual*) CopyFromParent, CWColormap /*| CWBackPixel*/ | CWBorderPixel | CWOverrideRedirect, &swa); 15662 15663 15664 15665 /* 15666 window = XCreateSimpleWindow( 15667 display, 15668 parent is null ? RootWindow(display, screen) : parent.impl.window, 15669 0, 0, // x, y 15670 width, height, 15671 1, // border width 15672 BlackPixel(display, screen), // border 15673 WhitePixel(display, screen)); // background 15674 */ 15675 15676 buffer = XCreatePixmap(display, cast(Drawable) window, width, height, DefaultDepthOfDisplay(display)); 15677 bufferw = width; 15678 bufferh = height; 15679 15680 gc = DefaultGC(display, screen); 15681 15682 // clear out the buffer to get us started... 15683 XSetForeground(display, gc, WhitePixel(display, screen)); 15684 XFillRectangle(display, cast(Drawable) buffer, gc, 0, 0, width, height); 15685 XSetForeground(display, gc, BlackPixel(display, screen)); 15686 } 15687 15688 // input context 15689 //TODO: create this only for top-level windows, and reuse that? 15690 populateXic(); 15691 15692 if (sdpyWindowClassStr is null) loadBinNameToWindowClassName(); 15693 if (sdpyWindowClassStr is null) sdpyWindowClass = "DSimpleWindow"; 15694 // window class 15695 if (sdpyWindowClassStr !is null && sdpyWindowClassStr[0]) { 15696 //{ import core.stdc.stdio; printf("winclass: [%s]\n", sdpyWindowClassStr); } 15697 XClassHint klass; 15698 XWMHints wh; 15699 if(this.customizationFlags & WindowFlags.managesChildWindowFocus) { 15700 wh.input = true; 15701 wh.flags |= InputHint; 15702 } 15703 XSizeHints size; 15704 klass.res_name = sdpyWindowClassStr; 15705 klass.res_class = sdpyWindowClassStr; 15706 XSetWMProperties(display, window, null, null, null, 0, &size, &wh, &klass); 15707 } 15708 15709 setTitle(title); 15710 SimpleWindow.nativeMapping[window] = this; 15711 CapableOfHandlingNativeEvent.nativeHandleMapping[window] = this; 15712 15713 // This gives our window a close button 15714 if (windowType != WindowTypes.eventOnly) { 15715 Atom[2] atoms = [GetAtom!"WM_DELETE_WINDOW"(display), GetAtom!"WM_TAKE_FOCUS"(display)]; 15716 int useAtoms; 15717 if(this.customizationFlags & WindowFlags.managesChildWindowFocus) { 15718 useAtoms = 2; 15719 } else { 15720 useAtoms = 1; 15721 } 15722 assert(useAtoms <= atoms.length); 15723 XSetWMProtocols(display, window, atoms.ptr, useAtoms); 15724 } 15725 15726 // FIXME: windowType and customizationFlags 15727 Atom[8] wsatoms; // here, due to goto 15728 int wmsacount = 0; // here, due to goto 15729 15730 try 15731 final switch(windowType) { 15732 case WindowTypes.normal: 15733 setNetWMWindowType(GetAtom!"_NET_WM_WINDOW_TYPE_NORMAL"(display)); 15734 break; 15735 case WindowTypes.undecorated: 15736 motifHideDecorations(); 15737 setNetWMWindowType(GetAtom!"_NET_WM_WINDOW_TYPE_NORMAL"(display)); 15738 break; 15739 case WindowTypes.eventOnly: 15740 _hidden = true; 15741 XSelectInput(display, window, EventMask.StructureNotifyMask); // without this, we won't get destroy notification 15742 goto hiddenWindow; 15743 //break; 15744 case WindowTypes.nestedChild: 15745 // handled in XCreateWindow calls 15746 break; 15747 15748 case WindowTypes.dropdownMenu: 15749 motifHideDecorations(); 15750 setNetWMWindowType(GetAtom!"_NET_WM_WINDOW_TYPE_DROPDOWN_MENU"(display)); 15751 customizationFlags |= WindowFlags.skipTaskbar | WindowFlags.alwaysOnTop; 15752 break; 15753 case WindowTypes.popupMenu: 15754 motifHideDecorations(); 15755 setNetWMWindowType(GetAtom!"_NET_WM_WINDOW_TYPE_POPUP_MENU"(display)); 15756 customizationFlags |= WindowFlags.skipTaskbar | WindowFlags.alwaysOnTop; 15757 break; 15758 case WindowTypes.notification: 15759 motifHideDecorations(); 15760 setNetWMWindowType(GetAtom!"_NET_WM_WINDOW_TYPE_NOTIFICATION"(display)); 15761 customizationFlags |= WindowFlags.skipTaskbar | WindowFlags.alwaysOnTop; 15762 break; 15763 case WindowTypes.minimallyWrapped: 15764 assert(0, "don't create a minimallyWrapped thing explicitly!"); 15765 15766 case WindowTypes.dialog: 15767 setNetWMWindowType(GetAtom!"_NET_WM_WINDOW_TYPE_DIALOG"(display)); 15768 break; 15769 case WindowTypes.comboBoxDropdown: 15770 motifHideDecorations(); 15771 setNetWMWindowType(GetAtom!"_NET_WM_WINDOW_TYPE_COMBO"(display)); 15772 customizationFlags |= WindowFlags.skipTaskbar | WindowFlags.alwaysOnTop; 15773 break; 15774 case WindowTypes.tooltip: 15775 motifHideDecorations(); 15776 setNetWMWindowType(GetAtom!"_NET_WM_WINDOW_TYPE_TOOLTIP"(display)); 15777 customizationFlags |= WindowFlags.skipTaskbar | WindowFlags.alwaysOnTop; 15778 break; 15779 case WindowTypes.dnd: 15780 motifHideDecorations(); 15781 setNetWMWindowType(GetAtom!"_NET_WM_WINDOW_TYPE_DND"(display)); 15782 customizationFlags |= WindowFlags.skipTaskbar | WindowFlags.alwaysOnTop; 15783 break; 15784 /+ 15785 case WindowTypes.menu: 15786 atoms[0] = GetAtom!"_NET_WM_WINDOW_TYPE_MENU"(display); 15787 motifHideDecorations(); 15788 break; 15789 case WindowTypes.desktop: 15790 atoms[0] = GetAtom!"_NET_WM_WINDOW_TYPE_DESKTOP"(display); 15791 break; 15792 case WindowTypes.dock: 15793 atoms[0] = GetAtom!"_NET_WM_WINDOW_TYPE_DOCK"(display); 15794 break; 15795 case WindowTypes.toolbar: 15796 atoms[0] = GetAtom!"_NET_WM_WINDOW_TYPE_TOOLBAR"(display); 15797 break; 15798 case WindowTypes.menu: 15799 atoms[0] = GetAtom!"_NET_WM_WINDOW_TYPE_MENU"(display); 15800 break; 15801 case WindowTypes.utility: 15802 atoms[0] = GetAtom!"_NET_WM_WINDOW_TYPE_UTILITY"(display); 15803 break; 15804 case WindowTypes.splash: 15805 atoms[0] = GetAtom!"_NET_WM_WINDOW_TYPE_SPLASH"(display); 15806 break; 15807 case WindowTypes.notification: 15808 atoms[0] = GetAtom!"_NET_WM_WINDOW_TYPE_NOTIFICATION"(display); 15809 break; 15810 +/ 15811 } 15812 catch(Exception e) { 15813 // XInternAtom failed, prolly a WM 15814 // that doesn't support these things 15815 } 15816 15817 if (customizationFlags&WindowFlags.skipTaskbar) wsatoms[wmsacount++] = GetAtom!("_NET_WM_STATE_SKIP_TASKBAR", true)(display); 15818 // the two following flags may be ignored by WM 15819 if (customizationFlags&WindowFlags.alwaysOnTop) wsatoms[wmsacount++] = GetAtom!("_NET_WM_STATE_ABOVE", true)(display); 15820 if (customizationFlags&WindowFlags.alwaysOnBottom) wsatoms[wmsacount++] = GetAtom!("_NET_WM_STATE_BELOW", true)(display); 15821 15822 if (wmsacount != 0) XChangeProperty(display, window, GetAtom!("_NET_WM_STATE", true)(display), XA_ATOM, 32 /* bits */,0 /*PropModeReplace*/, wsatoms.ptr, wmsacount); 15823 15824 if (this.resizability == Resizability.fixedSize || (opengl == OpenGlOptions.no && this.resizability != Resizability.allowResizing)) fixFixedSize!true(width, height); 15825 15826 // What would be ideal here is if they only were 15827 // selected if there was actually an event handler 15828 // for them... 15829 15830 selectDefaultInput((customizationFlags & WindowFlags.alwaysRequestMouseMotionEvents)?true:false); 15831 15832 hiddenWindow: 15833 15834 // set the pid property for lookup later by window managers 15835 // a standard convenience 15836 import core.sys.posix.unistd; 15837 arch_ulong pid = getpid(); 15838 15839 XChangeProperty( 15840 display, 15841 impl.window, 15842 GetAtom!("_NET_WM_PID", true)(display), 15843 XA_CARDINAL, 15844 32 /* bits */, 15845 0 /*PropModeReplace*/, 15846 &pid, 15847 1); 15848 15849 if(isTransient && parent) { // customizationFlags & WindowFlags.transient) { 15850 if(parent is null) assert(0); 15851 // sdpyPrintDebugString("transient"); 15852 XChangeProperty( 15853 display, 15854 impl.window, 15855 GetAtom!("WM_TRANSIENT_FOR", true)(display), 15856 XA_WINDOW, 15857 32 /* bits */, 15858 0 /*PropModeReplace*/, 15859 &parent.impl.window, 15860 1); 15861 15862 } 15863 15864 if(windowType != WindowTypes.eventOnly && (customizationFlags&WindowFlags.dontAutoShow) == 0) { 15865 XMapWindow(display, window); 15866 } else { 15867 _hidden = true; 15868 } 15869 } 15870 15871 void populateXic() { 15872 if (XDisplayConnection.xim !is null) { 15873 xic = XCreateIC(XDisplayConnection.xim, 15874 /*XNInputStyle*/"inputStyle".ptr, XIMPreeditNothing|XIMStatusNothing, 15875 /*XNClientWindow*/"clientWindow".ptr, window, 15876 /*XNFocusWindow*/"focusWindow".ptr, window, 15877 null); 15878 if (xic is null) { 15879 import core.stdc.stdio : stderr, fprintf; 15880 fprintf(stderr, "XCreateIC failed for window %u\n", cast(uint)window); 15881 } 15882 } 15883 } 15884 15885 void selectDefaultInput(bool forceIncludeMouseMotion) { 15886 auto mask = EventMask.ExposureMask | 15887 EventMask.KeyPressMask | 15888 EventMask.KeyReleaseMask | 15889 EventMask.PropertyChangeMask | 15890 EventMask.FocusChangeMask | 15891 EventMask.StructureNotifyMask | 15892 EventMask.SubstructureNotifyMask | 15893 EventMask.VisibilityChangeMask 15894 | EventMask.ButtonPressMask 15895 | EventMask.ButtonReleaseMask 15896 ; 15897 15898 // xshm is our shortcut for local connections 15899 if(XDisplayConnection.isLocal || forceIncludeMouseMotion) 15900 mask |= EventMask.PointerMotionMask; 15901 else 15902 mask |= EventMask.ButtonMotionMask; 15903 15904 XSelectInput(display, window, mask); 15905 } 15906 15907 15908 void setNetWMWindowType(Atom type) { 15909 Atom[2] atoms; 15910 15911 atoms[0] = type; 15912 // generic fallback 15913 atoms[1] = GetAtom!"_NET_WM_WINDOW_TYPE_NORMAL"(display); 15914 15915 XChangeProperty( 15916 display, 15917 impl.window, 15918 GetAtom!"_NET_WM_WINDOW_TYPE"(display), 15919 XA_ATOM, 15920 32 /* bits */, 15921 0 /*PropModeReplace*/, 15922 atoms.ptr, 15923 cast(int) atoms.length); 15924 } 15925 15926 void motifHideDecorations(bool hide = true) { 15927 MwmHints hints; 15928 hints.flags = MWM_HINTS_DECORATIONS; 15929 hints.decorations = hide ? 0 : 1; 15930 15931 XChangeProperty( 15932 display, 15933 impl.window, 15934 GetAtom!"_MOTIF_WM_HINTS"(display), 15935 GetAtom!"_MOTIF_WM_HINTS"(display), 15936 32 /* bits */, 15937 0 /*PropModeReplace*/, 15938 &hints, 15939 hints.sizeof / 4); 15940 } 15941 15942 /*k8: unused 15943 void createOpenGlContext() { 15944 15945 } 15946 */ 15947 15948 void closeWindow() { 15949 // I can't close this or a child window closing will 15950 // break events for everyone. So I'm just leaking it right 15951 // now and that is probably perfectly fine... 15952 version(none) 15953 if (customEventFDRead != -1) { 15954 import core.sys.posix.unistd : close; 15955 auto same = customEventFDRead == customEventFDWrite; 15956 15957 close(customEventFDRead); 15958 if(!same) 15959 close(customEventFDWrite); 15960 customEventFDRead = -1; 15961 customEventFDWrite = -1; 15962 } 15963 15964 version(without_opengl) {} else 15965 if(glc !is null) { 15966 glXDestroyContext(display, glc); 15967 glc = null; 15968 } 15969 15970 if(buffer) 15971 XFreePixmap(display, buffer); 15972 bufferw = bufferh = 0; 15973 if (blankCurPtr && cursorSequenceNumber == XDisplayConnection.connectionSequenceNumber) XFreeCursor(display, blankCurPtr); 15974 XDestroyWindow(display, window); 15975 XFlush(display); 15976 } 15977 15978 void dispose() { 15979 } 15980 15981 bool destroyed = false; 15982 } 15983 15984 bool insideXEventLoop; 15985 } 15986 15987 version(X11) { 15988 15989 int mouseDoubleClickTimeout = 350; /// Double click timeout. X only, you probably shouldn't change this. 15990 15991 private class ResizeEvent { 15992 int width, height; 15993 } 15994 15995 void recordX11ResizeAsync(Display* display, SimpleWindow win, int width, int height) { 15996 if(win.windowType == WindowTypes.minimallyWrapped) 15997 return; 15998 15999 if(win.pendingResizeEvent is null) { 16000 win.pendingResizeEvent = new ResizeEvent(); 16001 win.addEventListener((ResizeEvent re) { 16002 recordX11Resize(XDisplayConnection.get, win, re.width, re.height); 16003 }); 16004 } 16005 win.pendingResizeEvent.width = width; 16006 win.pendingResizeEvent.height = height; 16007 if(!win.eventQueued!ResizeEvent) { 16008 win.postEvent(win.pendingResizeEvent); 16009 } 16010 } 16011 16012 void recordX11Resize(Display* display, SimpleWindow win, int width, int height) { 16013 if(win.windowType == WindowTypes.minimallyWrapped) 16014 return; 16015 if(win.closed) 16016 return; 16017 16018 if(width != win.width || height != win.height) { 16019 16020 // writeln("RESIZE: ", width, "x", height, " was ", win._width, "x", win._height, " window: ", win.windowType, "-", win.title, " ", win.window); 16021 win._width = width; 16022 win._height = height; 16023 16024 if(win.openglMode == OpenGlOptions.no) { 16025 // FIXME: could this be more efficient? 16026 16027 if (win.bufferw < width || win.bufferh < height) { 16028 //{ 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); } 16029 // grow the internal buffer to match the window... 16030 auto newPixmap = XCreatePixmap(display, cast(Drawable) win.window, width, height, DefaultDepthOfDisplay(display)); 16031 { 16032 GC xgc = XCreateGC(win.display, cast(Drawable)win.window, 0, null); 16033 XCopyGC(win.display, win.gc, 0xffffffff, xgc); 16034 scope(exit) XFreeGC(win.display, xgc); 16035 XSetClipMask(win.display, xgc, None); 16036 XSetForeground(win.display, xgc, 0); 16037 XFillRectangle(display, cast(Drawable)newPixmap, xgc, 0, 0, width, height); 16038 } 16039 XCopyArea(display, 16040 cast(Drawable) win.buffer, 16041 cast(Drawable) newPixmap, 16042 win.gc, 0, 0, 16043 win.bufferw < width ? win.bufferw : win.width, 16044 win.bufferh < height ? win.bufferh : win.height, 16045 0, 0); 16046 16047 XFreePixmap(display, win.buffer); 16048 win.buffer = newPixmap; 16049 win.bufferw = width; 16050 win.bufferh = height; 16051 } 16052 16053 // clear unused parts of the buffer 16054 if (win.bufferw > width || win.bufferh > height) { 16055 GC xgc = XCreateGC(win.display, cast(Drawable)win.window, 0, null); 16056 XCopyGC(win.display, win.gc, 0xffffffff, xgc); 16057 scope(exit) XFreeGC(win.display, xgc); 16058 XSetClipMask(win.display, xgc, None); 16059 XSetForeground(win.display, xgc, 0); 16060 immutable int maxw = (win.bufferw > width ? win.bufferw : width); 16061 immutable int maxh = (win.bufferh > height ? win.bufferh : height); 16062 XFillRectangle(win.display, cast(Drawable)win.buffer, xgc, width, 0, maxw, maxh); // let X11 do clipping 16063 XFillRectangle(win.display, cast(Drawable)win.buffer, xgc, 0, height, maxw, maxh); // let X11 do clipping 16064 } 16065 16066 } 16067 16068 win.updateOpenglViewportIfNeeded(width, height); 16069 16070 win.fixFixedSize(width, height); //k8: this does nothing on my FluxBox; wtf?! 16071 16072 if(win.resizability != Resizability.automaticallyScaleIfPossible) 16073 if(win.windowResized !is null) { 16074 XUnlockDisplay(display); 16075 scope(exit) XLockDisplay(display); 16076 win.windowResized(width, height); 16077 } 16078 } 16079 } 16080 16081 16082 /// Platform-specific, you might use it when doing a custom event loop. 16083 bool doXNextEvent(Display* display) { 16084 bool done; 16085 XEvent e; 16086 XNextEvent(display, &e); 16087 version(sddddd) { 16088 if(auto win = e.xany.window in CapableOfHandlingNativeEvent.nativeHandleMapping) { 16089 if(typeid(cast(Object) *win) == NotificationAreaIcon.classinfo) 16090 writeln("event for: ", e.xany.window, "; type is ", to!string(cast(EventType)e.type)); 16091 } 16092 } 16093 16094 // filter out compose events 16095 if (XFilterEvent(&e, None)) { 16096 //{ import core.stdc.stdio : printf; printf("XFilterEvent filtered!\n"); } 16097 //NOTE: we should ungrab keyboard here, but simpledisplay doesn't use keyboard grabbing (yet) 16098 return false; 16099 } 16100 // process keyboard mapping changes 16101 if (e.type == EventType.KeymapNotify) { 16102 //{ import core.stdc.stdio : printf; printf("KeymapNotify processed!\n"); } 16103 XRefreshKeyboardMapping(&e.xmapping); 16104 return false; 16105 } 16106 16107 version(with_eventloop) 16108 import arsd.eventloop; 16109 16110 if(SimpleWindow.handleNativeGlobalEvent !is null) { 16111 // see windows impl's comments 16112 XUnlockDisplay(display); 16113 scope(exit) XLockDisplay(display); 16114 auto ret = SimpleWindow.handleNativeGlobalEvent(e); 16115 if(ret == 0) 16116 return done; 16117 } 16118 16119 16120 if(auto win = e.xany.window in CapableOfHandlingNativeEvent.nativeHandleMapping) { 16121 if(win.getNativeEventHandler !is null) { 16122 XUnlockDisplay(display); 16123 scope(exit) XLockDisplay(display); 16124 auto ret = win.getNativeEventHandler()(e); 16125 if(ret == 0) 16126 return done; 16127 } 16128 } 16129 16130 if(xrrEventBase != -1 && e.type == xrrEventBase + RRScreenChangeNotify) { 16131 if(auto win = e.xany.window in SimpleWindow.nativeMapping) { 16132 // we get this because of the RRScreenChangeNotifyMask 16133 16134 // this isn't actually an ideal way to do it since it wastes time 16135 // but meh it is simple and it works. 16136 win.actualDpiLoadAttempted = false; 16137 SimpleWindow.xRandrInfoLoadAttemped = false; 16138 win.updateActualDpi(); // trigger a reload 16139 } 16140 } 16141 16142 switch(e.type) { 16143 case EventType.SelectionClear: 16144 // writeln("SelectionClear"); 16145 if(auto win = e.xselectionclear.window in SimpleWindow.nativeMapping) { 16146 // FIXME so it is supposed to finish any in progress transfers... but idk... 16147 // writeln("SelectionClear"); 16148 } 16149 SimpleWindow.impl.setSelectionHandlers.remove(e.xselectionclear.selection); 16150 mightShortCircuitClipboard = false; 16151 break; 16152 case EventType.SelectionRequest: 16153 if(auto win = e.xselectionrequest.owner in SimpleWindow.nativeMapping) 16154 if(auto ssh = e.xselectionrequest.selection in SimpleWindow.impl.setSelectionHandlers) { 16155 // printf("SelectionRequest %s\n", XGetAtomName(e.xselectionrequest.display, e.xselectionrequest.target)); 16156 XUnlockDisplay(display); 16157 scope(exit) XLockDisplay(display); 16158 (*ssh).handleRequest(e); 16159 } 16160 break; 16161 case EventType.PropertyNotify: 16162 // import core.stdc.stdio; printf("PropertyNotify %s %d\n", XGetAtomName(e.xproperty.display, e.xproperty.atom), e.xproperty.state); 16163 16164 foreach(ssh; SimpleWindow.impl.setSelectionHandlers) { 16165 if(ssh.matchesIncr(e.xproperty.window, e.xproperty.atom) && e.xproperty.state == PropertyNotification.PropertyDelete) 16166 ssh.sendMoreIncr(&e.xproperty); 16167 } 16168 16169 16170 if(auto win = e.xproperty.window in SimpleWindow.nativeMapping) 16171 if(auto handler = e.xproperty.atom in win.getSelectionHandlers) { 16172 if(handler.matchesIncr(e.xproperty.window, e.xproperty.atom) && e.xproperty.state == PropertyNotification.PropertyNewValue) { 16173 Atom target; 16174 int format; 16175 arch_ulong bytesafter, length; 16176 void* value; 16177 16178 ubyte[] s; 16179 Atom targetToKeep; 16180 16181 XGetWindowProperty( 16182 e.xproperty.display, 16183 e.xproperty.window, 16184 e.xproperty.atom, 16185 0, 16186 100000 /* length */, 16187 true, /* erase it to signal we got it and want more */ 16188 0 /*AnyPropertyType*/, 16189 &target, &format, &length, &bytesafter, &value); 16190 16191 if(!targetToKeep) 16192 targetToKeep = target; 16193 16194 auto id = (cast(ubyte*) value)[0 .. length]; 16195 16196 handler.handleIncrData(targetToKeep, id); 16197 if(length == 0) { 16198 win.getSelectionHandlers.remove(e.xproperty.atom); 16199 } 16200 16201 XFree(value); 16202 } 16203 } 16204 break; 16205 case EventType.SelectionNotify: 16206 // import std.stdio; writefln("SelectionNotify %06x %06x", e.xselection.requestor, e.xproperty.atom); 16207 if(auto win = e.xselection.requestor in SimpleWindow.nativeMapping) 16208 if(auto handler = e.xproperty.atom in win.getSelectionHandlers) { 16209 if(e.xselection.property == None) { // || e.xselection.property == GetAtom!("NULL", true)(e.xselection.display)) { 16210 XUnlockDisplay(display); 16211 scope(exit) XLockDisplay(display); 16212 handler.handleData(None, null); 16213 win.getSelectionHandlers.remove(e.xproperty.atom); 16214 } else { 16215 Atom target; 16216 int format; 16217 arch_ulong bytesafter, length; 16218 void* value; 16219 XGetWindowProperty( 16220 e.xselection.display, 16221 e.xselection.requestor, 16222 e.xselection.property, 16223 0, 16224 100000 /* length */, 16225 //false, /* don't erase it */ 16226 true, /* do erase it lol */ 16227 0 /*AnyPropertyType*/, 16228 &target, &format, &length, &bytesafter, &value); 16229 16230 // FIXME: I don't have to copy it now since it is in char[] instead of string 16231 16232 { 16233 XUnlockDisplay(display); 16234 scope(exit) XLockDisplay(display); 16235 16236 if(target == XA_ATOM) { 16237 // initial request, see what they are able to work with and request the best one 16238 // we can handle, if available 16239 16240 Atom[] answer = (cast(Atom*) value)[0 .. length]; 16241 Atom best = handler.findBestFormat(answer); 16242 16243 /+ 16244 writeln("got ", answer); 16245 foreach(a; answer) 16246 writeln(XGetAtomName(display, a).stringz); 16247 writeln("best ", best); 16248 +/ 16249 16250 if(best != None) { 16251 // actually request the best format 16252 XConvertSelection(e.xselection.display, e.xselection.selection, best, GetAtom!("SDD_DATA", true)(display), e.xselection.requestor, 0 /*CurrentTime*/); 16253 } 16254 } else if(target == GetAtom!"INCR"(display)) { 16255 // incremental 16256 16257 handler.prepareIncremental(e.xselection.requestor, e.xselection.property); 16258 16259 // signal the sending program that we see 16260 // the incr and are ready to receive more. 16261 XDeleteProperty( 16262 e.xselection.display, 16263 e.xselection.requestor, 16264 e.xselection.property); 16265 } else { 16266 // unsupported type... maybe, forward, then we done with it 16267 if(target != None) { 16268 handler.handleData(target, cast(ubyte[]) value[0 .. length]); 16269 win.getSelectionHandlers.remove(e.xproperty.atom); 16270 } 16271 } 16272 } 16273 XFree(value); 16274 /* 16275 XDeleteProperty( 16276 e.xselection.display, 16277 e.xselection.requestor, 16278 e.xselection.property); 16279 */ 16280 } 16281 } 16282 break; 16283 case EventType.ConfigureNotify: 16284 auto event = e.xconfigure; 16285 if(auto win = event.window in SimpleWindow.nativeMapping) { 16286 if(win.windowType == WindowTypes.minimallyWrapped) 16287 break; 16288 //version(sdddd) { writeln(" w=", event.width, "; h=", event.height); } 16289 16290 /+ 16291 The ICCCM says window managers must send a synthetic event when the window 16292 is moved but NOT when it is resized. In the resize case, an event is sent 16293 with position (0, 0) which can be wrong and break the dpi calculations. 16294 16295 So we only consider the synthetic events from the WM and otherwise 16296 need to wait for some other event to get the position which... sucks. 16297 16298 I'd rather not have windows changing their layout on mouse motion after 16299 switching monitors... might be forced to but for now just ignoring it. 16300 16301 Easiest way to switch monitors without sending a size position is by 16302 maximize or fullscreen in a setup like mine, but on most setups those 16303 work on the monitor it is already living on, so it should be ok most the 16304 time. 16305 +/ 16306 if(event.send_event) { 16307 win.screenPositionKnown = true; 16308 win.screenPositionX = event.x; 16309 win.screenPositionY = event.y; 16310 win.updateActualDpi(); 16311 } 16312 16313 win.updateIMEPopupLocation(); 16314 recordX11ResizeAsync(display, *win, event.width, event.height); 16315 } 16316 break; 16317 case EventType.Expose: 16318 if(auto win = e.xexpose.window in SimpleWindow.nativeMapping) { 16319 if(win.windowType == WindowTypes.minimallyWrapped) 16320 break; 16321 // if it is closing from a popup menu, it can get 16322 // an Expose event right by the end and trigger a 16323 // BadDrawable error ... we'll just check 16324 // closed to handle that. 16325 if((*win).closed) break; 16326 if((*win).openglMode == OpenGlOptions.no) { 16327 bool doCopy = true;// e.xexpose.count == 0; // the count is better if we copy all area but meh 16328 if (win.handleExpose !is null) doCopy = !win.handleExpose(e.xexpose.x, e.xexpose.y, e.xexpose.width, e.xexpose.height, e.xexpose.count); 16329 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); 16330 } else { 16331 // need to redraw the scene somehow 16332 if(e.xexpose.count == 0) { // only do the last one since redrawOpenGlSceneNow always does it all 16333 XUnlockDisplay(display); 16334 scope(exit) XLockDisplay(display); 16335 version(without_opengl) {} else 16336 win.redrawOpenGlSceneSoon(); 16337 } 16338 } 16339 } 16340 break; 16341 case EventType.FocusIn: 16342 case EventType.FocusOut: 16343 mightShortCircuitClipboard = false; // if the focus has changed, good chance the clipboard cache is invalidated too, kinda hacky but always better to skip when unnecessary than use when we shouldn't have 16344 16345 if(auto win = e.xfocus.window in SimpleWindow.nativeMapping) { 16346 /+ 16347 16348 void info(string detail) { 16349 string s; 16350 // import std.conv; 16351 // import std.datetime; 16352 s ~= to!string(Clock.currTime); 16353 s ~= " "; 16354 s ~= e.type == EventType.FocusIn ? "in " : "out"; 16355 s ~= " "; 16356 s ~= win.windowType == WindowTypes.nestedChild ? "child " : "main "; 16357 s ~= e.xfocus.mode == NotifyModes.NotifyNormal ? " normal ": " grabbed "; 16358 s ~= detail; 16359 s ~= " "; 16360 16361 sdpyPrintDebugString(s); 16362 16363 } 16364 16365 switch(e.xfocus.detail) { 16366 case NotifyDetail.NotifyAncestor: info("Ancestor"); break; 16367 case NotifyDetail.NotifyVirtual: info("Virtual"); break; 16368 case NotifyDetail.NotifyInferior: info("Inferior"); break; 16369 case NotifyDetail.NotifyNonlinear: info("Nonlinear"); break; 16370 case NotifyDetail.NotifyNonlinearVirtual: info("nlinearvirtual"); break; 16371 case NotifyDetail.NotifyPointer: info("pointer"); break; 16372 case NotifyDetail.NotifyPointerRoot: info("pointerroot"); break; 16373 case NotifyDetail.NotifyDetailNone: info("none"); break; 16374 default: 16375 16376 } 16377 +/ 16378 16379 16380 if(e.xfocus.detail == NotifyDetail.NotifyPointer) 16381 break; // just ignore these they seem irrelevant 16382 16383 auto old = win._focused; 16384 win._focused = e.type == EventType.FocusIn; 16385 16386 // yes, we are losing the focus, but to our own child. that's actually kinda keeping it. 16387 if(e.type == EventType.FocusOut && e.xfocus.detail == NotifyDetail.NotifyInferior) 16388 win._focused = true; 16389 16390 if(win.demandingAttention) 16391 demandAttention(*win, false); 16392 16393 win.updateIMEFocused(); 16394 16395 if(old != win._focused && win.onFocusChange) { 16396 XUnlockDisplay(display); 16397 scope(exit) XLockDisplay(display); 16398 win.onFocusChange(win._focused); 16399 } 16400 } 16401 break; 16402 case EventType.VisibilityNotify: 16403 if(auto win = e.xfocus.window in SimpleWindow.nativeMapping) { 16404 auto before = (*win)._visible; 16405 (*win)._visible = (e.xvisibility.state != VisibilityNotify.VisibilityFullyObscured); 16406 if (e.xvisibility.state == VisibilityNotify.VisibilityFullyObscured) { 16407 if (win.visibilityChanged !is null && before == true) { 16408 XUnlockDisplay(display); 16409 scope(exit) XLockDisplay(display); 16410 win.visibilityChanged(false); 16411 } 16412 } else { 16413 if (win.visibilityChanged !is null && before == false) { 16414 XUnlockDisplay(display); 16415 scope(exit) XLockDisplay(display); 16416 win.visibilityChanged(true); 16417 } 16418 } 16419 } 16420 break; 16421 case EventType.ClientMessage: 16422 if (e.xclient.message_type == GetAtom!("_X11SDPY_INSMME_FLAG_EVENT_", true)(e.xany.display)) { 16423 // "ignore next mouse motion" event, increment ignore counter for teh window 16424 if (auto win = e.xclient.window in SimpleWindow.nativeMapping) { 16425 ++(*win).warpEventCount; 16426 debug(x11sdpy_warp_debug) { import core.stdc.stdio : printf; printf("X11: got \"INSMME\" message, new count=%d\n", (*win).warpEventCount); } 16427 } else { 16428 debug(x11sdpy_warp_debug) { import core.stdc.stdio : printf; printf("X11: got \"INSMME\" WTF?!!\n"); } 16429 } 16430 } else if(e.xclient.data.l[0] == GetAtom!"WM_DELETE_WINDOW"(e.xany.display)) { 16431 // user clicked the close button on the window manager 16432 if(auto win = e.xclient.window in SimpleWindow.nativeMapping) { 16433 XUnlockDisplay(display); 16434 scope(exit) XLockDisplay(display); 16435 if ((*win).closeQuery !is null) (*win).closeQuery(); else (*win).close(); 16436 } 16437 16438 } else if(e.xclient.data.l[0] == GetAtom!"WM_TAKE_FOCUS"(e.xany.display)) { 16439 // writeln("HAPPENED"); 16440 // user clicked the close button on the window manager 16441 if(auto win = e.xclient.window in SimpleWindow.nativeMapping) { 16442 XUnlockDisplay(display); 16443 scope(exit) XLockDisplay(display); 16444 16445 auto setTo = *win; 16446 16447 if(win.setRequestedInputFocus !is null) { 16448 auto s = win.setRequestedInputFocus(); 16449 if(s !is null) { 16450 setTo = s; 16451 } 16452 } 16453 16454 assert(setTo !is null); 16455 16456 // FIXME: so this is actually supposed to focus to a relevant child window if appropriate 16457 16458 // sdpyPrintDebugString("WM_TAKE_FOCUS ", setTo.impl.window); 16459 XSetInputFocus(display, setTo.impl.window, RevertToParent, e.xclient.data.l[1]); 16460 } 16461 } else if(e.xclient.message_type == GetAtom!"MANAGER"(e.xany.display)) { 16462 foreach(nai; NotificationAreaIcon.activeIcons) 16463 nai.newManager(); 16464 } else if(auto win = e.xclient.window in SimpleWindow.nativeMapping) { 16465 16466 bool xDragWindow = true; 16467 if(xDragWindow && e.xclient.message_type == GetAtom!"XdndStatus"(e.xany.display)) { 16468 //XDefineCursor(display, xDragWindow.impl.window, 16469 //writeln("XdndStatus ", e.xclient.data.l); 16470 } 16471 if(auto dh = win.dropHandler) { 16472 16473 static Atom[3] xFormatsBuffer; 16474 static Atom[] xFormats; 16475 16476 void resetXFormats() { 16477 xFormatsBuffer[] = 0; 16478 xFormats = xFormatsBuffer[]; 16479 } 16480 16481 if(e.xclient.message_type == GetAtom!"XdndEnter"(e.xany.display)) { 16482 // on Windows it is supposed to return the effect you actually do FIXME 16483 16484 auto sourceWindow = e.xclient.data.l[0]; 16485 16486 xFormatsBuffer[0] = e.xclient.data.l[2]; 16487 xFormatsBuffer[1] = e.xclient.data.l[3]; 16488 xFormatsBuffer[2] = e.xclient.data.l[4]; 16489 16490 if(e.xclient.data.l[1] & 1) { 16491 // can just grab it all but like we don't necessarily need them... 16492 xFormats = cast(Atom[]) getX11PropertyData(sourceWindow, GetAtom!"XdndTypeList"(display), XA_ATOM); 16493 } else { 16494 int len; 16495 foreach(fmt; xFormatsBuffer) 16496 if(fmt) len++; 16497 xFormats = xFormatsBuffer[0 .. len]; 16498 } 16499 16500 auto pkg = DropPackage(*win, e.xclient.data.l[0], 0, xFormats); 16501 16502 dh.dragEnter(&pkg); 16503 } else if(e.xclient.message_type == GetAtom!"XdndPosition"(e.xany.display)) { 16504 16505 auto pack = e.xclient.data.l[2]; 16506 16507 auto result = dh.dragOver(Point((pack & 0xffff0000) >> 16, pack & 0xffff)); // FIXME: translate screen coordinates back to window coords 16508 16509 16510 XClientMessageEvent xclient; 16511 16512 xclient.type = EventType.ClientMessage; 16513 xclient.window = e.xclient.data.l[0]; 16514 xclient.message_type = GetAtom!"XdndStatus"(display); 16515 xclient.format = 32; 16516 xclient.data.l[0] = win.impl.window; 16517 xclient.data.l[1] = (result.action != DragAndDropAction.none) ? 1 : 0; // will accept 16518 auto r = result.consistentWithin; 16519 xclient.data.l[2] = ((cast(short) r.left) << 16) | (cast(short) r.top); 16520 xclient.data.l[3] = ((cast(short) r.width) << 16) | (cast(short) r.height); 16521 xclient.data.l[4] = dndActionAtom(e.xany.display, result.action); 16522 16523 XSendEvent( 16524 display, 16525 e.xclient.data.l[0], 16526 false, 16527 EventMask.NoEventMask, 16528 cast(XEvent*) &xclient 16529 ); 16530 16531 16532 } else if(e.xclient.message_type == GetAtom!"XdndLeave"(e.xany.display)) { 16533 //writeln("XdndLeave"); 16534 // drop cancelled. 16535 // data.l[0] is the source window 16536 dh.dragLeave(); 16537 16538 resetXFormats(); 16539 } else if(e.xclient.message_type == GetAtom!"XdndDrop"(e.xany.display)) { 16540 // drop happening, should fetch data, then send finished 16541 // writeln("XdndDrop"); 16542 16543 auto pkg = DropPackage(*win, e.xclient.data.l[0], e.xclient.data.l[2], xFormats); 16544 16545 dh.drop(&pkg); 16546 16547 resetXFormats(); 16548 } else if(e.xclient.message_type == GetAtom!"XdndFinished"(e.xany.display)) { 16549 // writeln("XdndFinished"); 16550 16551 dh.finish(); 16552 } 16553 16554 } 16555 } 16556 break; 16557 case EventType.MapNotify: 16558 if(auto win = e.xmap.window in SimpleWindow.nativeMapping) { 16559 auto before = (*win)._visible; 16560 (*win)._visible = true; 16561 if (!(*win)._visibleForTheFirstTimeCalled) { 16562 (*win)._visibleForTheFirstTimeCalled = true; 16563 if ((*win).visibleForTheFirstTime !is null) { 16564 XUnlockDisplay(display); 16565 scope(exit) XLockDisplay(display); 16566 (*win).visibleForTheFirstTime(); 16567 } 16568 } 16569 if ((*win).visibilityChanged !is null && before == false) { 16570 XUnlockDisplay(display); 16571 scope(exit) XLockDisplay(display); 16572 (*win).visibilityChanged(true); 16573 } 16574 } 16575 break; 16576 case EventType.UnmapNotify: 16577 if(auto win = e.xunmap.window in SimpleWindow.nativeMapping) { 16578 auto before = (*win)._visible; 16579 win._visible = false; 16580 if (win.visibilityChanged !is null && before == true) { 16581 XUnlockDisplay(display); 16582 scope(exit) XLockDisplay(display); 16583 win.visibilityChanged(false); 16584 } 16585 } 16586 break; 16587 case EventType.DestroyNotify: 16588 if(auto win = e.xdestroywindow.window in SimpleWindow.nativeMapping) { 16589 if(win.destroyed) 16590 break; // might get a notification both for itself and from its parent 16591 if (win.onDestroyed !is null) try { win.onDestroyed(); } catch (Exception e) {} // sorry 16592 win._closed = true; // just in case 16593 win.destroyed = true; 16594 if (win.xic !is null) { 16595 XDestroyIC(win.xic); 16596 win.xic = null; // just in case 16597 } 16598 SimpleWindow.nativeMapping.remove(e.xdestroywindow.window); 16599 bool anyImportant = false; 16600 foreach(SimpleWindow w; SimpleWindow.nativeMapping) 16601 if(w.beingOpenKeepsAppOpen) { 16602 anyImportant = true; 16603 break; 16604 } 16605 if(!anyImportant) { 16606 EventLoop.quitApplication(); 16607 done = true; 16608 } 16609 } 16610 auto window = e.xdestroywindow.window; 16611 if(window in CapableOfHandlingNativeEvent.nativeHandleMapping) 16612 CapableOfHandlingNativeEvent.nativeHandleMapping.remove(window); 16613 16614 version(with_eventloop) { 16615 if(done) exit(); 16616 } 16617 break; 16618 16619 case EventType.MotionNotify: 16620 MouseEvent mouse; 16621 auto event = e.xmotion; 16622 16623 mouse.type = MouseEventType.motion; 16624 mouse.x = event.x; 16625 mouse.y = event.y; 16626 mouse.modifierState = event.state; 16627 16628 mouse.timestamp = event.time; 16629 16630 if(auto win = e.xmotion.window in SimpleWindow.nativeMapping) { 16631 mouse.window = *win; 16632 if (win.warpEventCount > 0) { 16633 debug(x11sdpy_warp_debug) { import core.stdc.stdio : printf; printf("X11: got \"warp motion\" message, current count=%d\n", (*win).warpEventCount); } 16634 --(*win).warpEventCount; 16635 (*win).mdx(mouse); // so deltas will be correctly updated 16636 } else { 16637 win.warpEventCount = 0; // just in case 16638 (*win).mdx(mouse); 16639 if((*win).handleMouseEvent) { 16640 XUnlockDisplay(display); 16641 scope(exit) XLockDisplay(display); 16642 (*win).handleMouseEvent(mouse); 16643 } 16644 } 16645 } 16646 16647 version(with_eventloop) 16648 send(mouse); 16649 break; 16650 case EventType.ButtonPress: 16651 case EventType.ButtonRelease: 16652 MouseEvent mouse; 16653 auto event = e.xbutton; 16654 16655 mouse.timestamp = event.time; 16656 16657 mouse.type = cast(MouseEventType) (e.type == EventType.ButtonPress ? 1 : 2); 16658 mouse.x = event.x; 16659 mouse.y = event.y; 16660 16661 static Time lastMouseDownTime = 0; 16662 static int lastMouseDownButton = -1; 16663 16664 mouse.doubleClick = e.type == EventType.ButtonPress && event.button == lastMouseDownButton && (event.time - lastMouseDownTime) < mouseDoubleClickTimeout; 16665 if(e.type == EventType.ButtonPress) { 16666 lastMouseDownTime = event.time; 16667 lastMouseDownButton = event.button; 16668 } 16669 16670 switch(event.button) { 16671 case 1: mouse.button = MouseButton.left; break; // left 16672 case 2: mouse.button = MouseButton.middle; break; // middle 16673 case 3: mouse.button = MouseButton.right; break; // right 16674 case 4: mouse.button = MouseButton.wheelUp; break; // scroll up 16675 case 5: mouse.button = MouseButton.wheelDown; break; // scroll down 16676 case 6: break; // idk 16677 case 7: break; // idk 16678 case 8: mouse.button = MouseButton.backButton; break; 16679 case 9: mouse.button = MouseButton.forwardButton; break; 16680 default: 16681 } 16682 16683 // FIXME: double check this 16684 mouse.modifierState = event.state; 16685 16686 //mouse.modifierState = event.detail; 16687 16688 if(auto win = e.xbutton.window in SimpleWindow.nativeMapping) { 16689 mouse.window = *win; 16690 (*win).mdx(mouse); 16691 if((*win).handleMouseEvent) { 16692 XUnlockDisplay(display); 16693 scope(exit) XLockDisplay(display); 16694 (*win).handleMouseEvent(mouse); 16695 } 16696 } 16697 version(with_eventloop) 16698 send(mouse); 16699 break; 16700 16701 case EventType.KeyPress: 16702 case EventType.KeyRelease: 16703 //if (e.type == EventType.KeyPress) { import core.stdc.stdio : stderr, fprintf; fprintf(stderr, "X11 keyboard event!\n"); } 16704 KeyEvent ke; 16705 ke.pressed = e.type == EventType.KeyPress; 16706 ke.hardwareCode = cast(ubyte) e.xkey.keycode; 16707 16708 auto sym = XKeycodeToKeysym( 16709 XDisplayConnection.get(), 16710 e.xkey.keycode, 16711 0); 16712 16713 ke.key = cast(Key) sym;//e.xkey.keycode; 16714 16715 ke.modifierState = e.xkey.state; 16716 16717 // writefln("%x", sym); 16718 wchar_t[128] charbuf = void; // buffer for XwcLookupString; composed value can consist of many chars! 16719 int charbuflen = 0; // return value of XwcLookupString 16720 if (ke.pressed) { 16721 auto win = e.xkey.window in SimpleWindow.nativeMapping; 16722 if (win !is null && win.xic !is null) { 16723 //{ import core.stdc.stdio : printf; printf("using xic!\n"); } 16724 Status status; 16725 charbuflen = XwcLookupString(win.xic, &e.xkey, charbuf.ptr, cast(int)charbuf.length, &sym, &status); 16726 //{ import core.stdc.stdio : printf; printf("charbuflen=%d\n", charbuflen); } 16727 } else { 16728 //{ import core.stdc.stdio : printf; printf("NOT using xic!\n"); } 16729 // If XIM initialization failed, don't process intl chars. Sorry, boys and girls. 16730 char[16] buffer; 16731 auto res = XLookupString(&e.xkey, buffer.ptr, buffer.length, null, null); 16732 if (res && buffer[0] < 128) charbuf[charbuflen++] = cast(wchar_t)buffer[0]; 16733 } 16734 } 16735 16736 // if there's no char, subst one 16737 if (charbuflen == 0) { 16738 switch (sym) { 16739 case 0xff09: charbuf[charbuflen++] = '\t'; break; 16740 case 0xff8d: // keypad enter 16741 case 0xff0d: charbuf[charbuflen++] = '\n'; break; 16742 default : // ignore 16743 } 16744 } 16745 16746 if (auto win = e.xkey.window in SimpleWindow.nativeMapping) { 16747 ke.window = *win; 16748 16749 16750 if(win.inputProxy) 16751 win = &win.inputProxy; 16752 16753 // char events are separate since they are on Windows too 16754 // also, xcompose can generate long char sequences 16755 // don't send char events if Meta and/or Hyper is pressed 16756 // TODO: ctrl+char should only send control chars; not yet 16757 if ((e.xkey.state&ModifierState.ctrl) != 0) { 16758 if (charbuflen > 1 || charbuf[0] >= ' ') charbuflen = 0; 16759 } 16760 16761 dchar[32] charsComingBuffer; 16762 int charsComingPosition; 16763 dchar[] charsComing = charsComingBuffer[]; 16764 16765 if (ke.pressed && charbuflen > 0) { 16766 // FIXME: I think Windows sends these on releases... we should try to match that, but idk about repeats. 16767 foreach (immutable dchar ch; charbuf[0..charbuflen]) { 16768 if(charsComingPosition >= charsComing.length) 16769 charsComing.length = charsComingPosition + 8; 16770 16771 charsComing[charsComingPosition++] = ch; 16772 } 16773 16774 charsComing = charsComing[0 .. charsComingPosition]; 16775 } else { 16776 charsComing = null; 16777 } 16778 16779 ke.charsPossible = charsComing; 16780 16781 if (win.handleKeyEvent) { 16782 XUnlockDisplay(display); 16783 scope(exit) XLockDisplay(display); 16784 win.handleKeyEvent(ke); 16785 } 16786 16787 // Super and alt modifier keys never actually send the chars, they are assumed to be special. 16788 if ((e.xkey.state&(ModifierState.alt|ModifierState.windows)) == 0 && win.handleCharEvent) { 16789 XUnlockDisplay(display); 16790 scope(exit) XLockDisplay(display); 16791 foreach(ch; charsComing) 16792 win.handleCharEvent(ch); 16793 } 16794 } 16795 16796 version(with_eventloop) 16797 send(ke); 16798 break; 16799 default: 16800 } 16801 16802 return done; 16803 } 16804 } 16805 16806 /* *************************************** */ 16807 /* Done with simpledisplay stuff */ 16808 /* *************************************** */ 16809 16810 // Necessary C library bindings follow 16811 version(Windows) {} else 16812 version(Emscripten) {} else 16813 version(X11) { 16814 16815 extern(C) int eventfd (uint initval, int flags) nothrow @trusted @nogc; 16816 16817 // X11 bindings needed here 16818 /* 16819 A little of this is from the bindings project on 16820 D Source and some of it is copy/paste from the C 16821 header. 16822 16823 The DSource listing consistently used D's long 16824 where C used long. That's wrong - C long is 32 bit, so 16825 it should be int in D. I changed that here. 16826 16827 Note: 16828 This isn't complete, just took what I needed for myself. 16829 */ 16830 16831 import core.stdc.stddef : wchar_t; 16832 16833 interface XLib { 16834 extern(C) nothrow @nogc { 16835 char* XResourceManagerString(Display*); 16836 void XrmInitialize(); 16837 XrmDatabase XrmGetStringDatabase(char* data); 16838 bool XrmGetResource(XrmDatabase, const char*, const char*, char**, XrmValue*); 16839 16840 Cursor XCreateFontCursor(Display*, uint shape); 16841 int XDefineCursor(Display* display, Window w, Cursor cursor); 16842 int XUndefineCursor(Display* display, Window w); 16843 16844 Pixmap XCreateBitmapFromData(Display* display, Drawable d, const(char)* data, uint width, uint height); 16845 Cursor XCreatePixmapCursor(Display* display, Pixmap source, Pixmap mask, XColor* foreground_color, XColor* background_color, uint x, uint y); 16846 int XFreeCursor(Display* display, Cursor cursor); 16847 16848 int XLookupString(XKeyEvent *event_struct, char *buffer_return, int bytes_buffer, KeySym *keysym_return, void *status_in_out); 16849 16850 int XwcLookupString(XIC ic, XKeyPressedEvent* event, wchar_t* buffer_return, int wchars_buffer, KeySym* keysym_return, Status* status_return); 16851 16852 XVaNestedList XVaCreateNestedList(int unused, ...); 16853 16854 char *XKeysymToString(KeySym keysym); 16855 KeySym XKeycodeToKeysym( 16856 Display* /* display */, 16857 KeyCode /* keycode */, 16858 int /* index */ 16859 ); 16860 16861 int XConvertSelection(Display *display, Atom selection, Atom target, Atom property, Window requestor, Time time); 16862 16863 int XFree(void*); 16864 int XDeleteProperty(Display *display, Window w, Atom property); 16865 16866 int XChangeProperty(Display *display, Window w, Atom property, Atom type, int format, int mode, scope const void *data, int nelements); 16867 16868 int XGetWindowProperty(Display *display, Window w, Atom property, arch_long 16869 long_offset, arch_long long_length, Bool del, Atom req_type, Atom 16870 *actual_type_return, int *actual_format_return, arch_ulong 16871 *nitems_return, arch_ulong *bytes_after_return, void** prop_return); 16872 Atom* XListProperties(Display *display, Window w, int *num_prop_return); 16873 Status XGetTextProperty(Display *display, Window w, XTextProperty *text_prop_return, Atom property); 16874 Status XQueryTree(Display *display, Window w, Window *root_return, Window *parent_return, Window **children_return, uint *nchildren_return); 16875 16876 int XSetSelectionOwner(Display *display, Atom selection, Window owner, Time time); 16877 16878 Window XGetSelectionOwner(Display *display, Atom selection); 16879 16880 XVisualInfo* XGetVisualInfo(Display*, c_long, XVisualInfo*, int*); 16881 16882 char** XListFonts(Display*, const char*, int, int*); 16883 void XFreeFontNames(char**); 16884 16885 Display* XOpenDisplay(const char*); 16886 int XCloseDisplay(Display*); 16887 16888 int function() XSynchronize(Display*, bool); 16889 int function() XSetAfterFunction(Display*, int function() proc); 16890 16891 Bool XQueryExtension(Display*, const char*, int*, int*, int*); 16892 16893 Bool XSupportsLocale(); 16894 char* XSetLocaleModifiers(const(char)* modifier_list); 16895 XOM XOpenOM(Display* display, _XrmHashBucketRec* rdb, const(char)* res_name, const(char)* res_class); 16896 Status XCloseOM(XOM om); 16897 16898 XIM XOpenIM(Display* dpy, _XrmHashBucketRec* rdb, const(char)* res_name, const(char)* res_class); 16899 Status XCloseIM(XIM im); 16900 16901 char* XGetIMValues(XIM im, ...) /*_X_SENTINEL(0)*/; 16902 char* XSetIMValues(XIM im, ...) /*_X_SENTINEL(0)*/; 16903 Display* XDisplayOfIM(XIM im); 16904 char* XLocaleOfIM(XIM im); 16905 XIC XCreateIC(XIM im, ...) /*_X_SENTINEL(0)*/; 16906 void XDestroyIC(XIC ic); 16907 void XSetICFocus(XIC ic); 16908 void XUnsetICFocus(XIC ic); 16909 //wchar_t* XwcResetIC(XIC ic); 16910 char* XmbResetIC(XIC ic); 16911 char* Xutf8ResetIC(XIC ic); 16912 char* XSetICValues(XIC ic, ...) /*_X_SENTINEL(0)*/; 16913 char* XGetICValues(XIC ic, ...) /*_X_SENTINEL(0)*/; 16914 XIM XIMOfIC(XIC ic); 16915 16916 uint XSendEvent(Display* display, Window w, Bool propagate, arch_long event_mask, XEvent* event_send); 16917 16918 16919 XFontStruct *XLoadQueryFont(Display *display, scope const char *name); 16920 int XFreeFont(Display *display, XFontStruct *font_struct); 16921 int XSetFont(Display* display, GC gc, Font font); 16922 int XTextWidth(XFontStruct*, scope const char*, int); 16923 16924 int XSetLineAttributes(Display *display, GC gc, uint line_width, int line_style, int cap_style, int join_style); 16925 int XSetDashes(Display *display, GC gc, int dash_offset, scope const byte* dash_list, int n); 16926 16927 Window XCreateSimpleWindow( 16928 Display* /* display */, 16929 Window /* parent */, 16930 int /* x */, 16931 int /* y */, 16932 uint /* width */, 16933 uint /* height */, 16934 uint /* border_width */, 16935 uint /* border */, 16936 uint /* background */ 16937 ); 16938 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); 16939 16940 int XReparentWindow(Display*, Window, Window, int, int); 16941 int XClearWindow(Display*, Window); 16942 int XMoveResizeWindow(Display*, Window, int, int, uint, uint); 16943 int XMoveWindow(Display*, Window, int, int); 16944 int XResizeWindow(Display *display, Window w, uint width, uint height); 16945 16946 Colormap XCreateColormap(Display *display, Window w, Visual *visual, int alloc); 16947 16948 Status XMatchVisualInfo(Display *display, int screen, int depth, int class_, XVisualInfo *vinfo_return); 16949 16950 Status XGetWindowAttributes(Display*, Window, XWindowAttributes*); 16951 16952 XImage *XCreateImage( 16953 Display* /* display */, 16954 Visual* /* visual */, 16955 uint /* depth */, 16956 int /* format */, 16957 int /* offset */, 16958 ubyte* /* data */, 16959 uint /* width */, 16960 uint /* height */, 16961 int /* bitmap_pad */, 16962 int /* bytes_per_line */ 16963 ); 16964 16965 Status XInitImage (XImage* image); 16966 16967 Atom XInternAtom( 16968 Display* /* display */, 16969 const char* /* atom_name */, 16970 Bool /* only_if_exists */ 16971 ); 16972 16973 Status XInternAtoms(Display*, const char**, int, Bool, Atom*); 16974 char* XGetAtomName(Display*, Atom); 16975 Status XGetAtomNames(Display*, Atom*, int count, char**); 16976 16977 int XPutImage( 16978 Display* /* display */, 16979 Drawable /* d */, 16980 GC /* gc */, 16981 XImage* /* image */, 16982 int /* src_x */, 16983 int /* src_y */, 16984 int /* dest_x */, 16985 int /* dest_y */, 16986 uint /* width */, 16987 uint /* height */ 16988 ); 16989 16990 XImage *XGetImage(Display *display, Drawable d, int x, int y, uint width, uint height, c_ulong plane_mask, int format); 16991 16992 16993 int XDestroyWindow( 16994 Display* /* display */, 16995 Window /* w */ 16996 ); 16997 16998 int XDestroyImage(XImage*); 16999 17000 int XSelectInput( 17001 Display* /* display */, 17002 Window /* w */, 17003 EventMask /* event_mask */ 17004 ); 17005 17006 int XMapWindow( 17007 Display* /* display */, 17008 Window /* w */ 17009 ); 17010 17011 Status XIconifyWindow(Display*, Window, int); 17012 int XMapRaised(Display*, Window); 17013 int XMapSubwindows(Display*, Window); 17014 17015 int XNextEvent( 17016 Display* /* display */, 17017 XEvent* /* event_return */ 17018 ); 17019 17020 int XMaskEvent(Display*, arch_long, XEvent*); 17021 17022 Bool XFilterEvent(XEvent *event, Window window); 17023 int XRefreshKeyboardMapping(XMappingEvent *event_map); 17024 17025 Status XSetWMProtocols( 17026 Display* /* display */, 17027 Window /* w */, 17028 Atom* /* protocols */, 17029 int /* count */ 17030 ); 17031 17032 void XSetWMNormalHints(Display *display, Window w, XSizeHints *hints); 17033 Status XGetWMNormalHints(Display *display, Window w, XSizeHints *hints, c_long* supplied_return); 17034 17035 17036 Status XInitThreads(); 17037 void XLockDisplay (Display* display); 17038 void XUnlockDisplay (Display* display); 17039 17040 void XSetWMProperties(Display*, Window, XTextProperty*, XTextProperty*, char**, int, XSizeHints*, XWMHints*, XClassHint*); 17041 17042 int XSetWindowBackground (Display* display, Window w, c_ulong background_pixel); 17043 int XSetWindowBackgroundPixmap (Display* display, Window w, Pixmap background_pixmap); 17044 //int XSetWindowBorder (Display* display, Window w, c_ulong border_pixel); 17045 //int XSetWindowBorderPixmap (Display* display, Window w, Pixmap border_pixmap); 17046 //int XSetWindowBorderWidth (Display* display, Window w, uint width); 17047 17048 17049 // check out Xft too: http://www.keithp.com/~keithp/render/Xft.tutorial 17050 int XDrawString(Display*, Drawable, GC, int, int, scope const char*, int); 17051 int XDrawLine(Display*, Drawable, GC, int, int, int, int); 17052 int XDrawRectangle(Display*, Drawable, GC, int, int, uint, uint); 17053 int XDrawArc(Display*, Drawable, GC, int, int, uint, uint, int, int); 17054 int XFillRectangle(Display*, Drawable, GC, int, int, uint, uint); 17055 int XFillArc(Display*, Drawable, GC, int, int, uint, uint, int, int); 17056 int XDrawPoint(Display*, Drawable, GC, int, int); 17057 int XSetForeground(Display*, GC, uint); 17058 int XSetBackground(Display*, GC, uint); 17059 17060 XFontSet XCreateFontSet(Display*, const char*, char***, int*, char**); 17061 void XFreeFontSet(Display*, XFontSet); 17062 void Xutf8DrawString(Display*, Drawable, XFontSet, GC, int, int, scope const char*, int); 17063 void Xutf8DrawText(Display*, Drawable, GC, int, int, XmbTextItem*, int); 17064 17065 int Xutf8TextExtents(XFontSet font_set, const char *, int num_bytes, XRectangle *overall_ink_return, XRectangle *overall_logical_return); 17066 17067 17068 //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); 17069 17070 void XDrawText(Display*, Drawable, GC, int, int, XTextItem*, int); 17071 int XSetFunction(Display*, GC, int); 17072 17073 GC XCreateGC(Display*, Drawable, uint, void*); 17074 int XCopyGC(Display*, GC, uint, GC); 17075 int XFreeGC(Display*, GC); 17076 17077 bool XCheckWindowEvent(Display*, Window, int, XEvent*); 17078 bool XCheckMaskEvent(Display*, int, XEvent*); 17079 17080 int XPending(Display*); 17081 int XEventsQueued(Display* display, int mode); 17082 17083 Pixmap XCreatePixmap(Display*, Drawable, uint, uint, uint); 17084 int XFreePixmap(Display*, Pixmap); 17085 int XCopyArea(Display*, Drawable, Drawable, GC, int, int, uint, uint, int, int); 17086 int XFlush(Display*); 17087 int XBell(Display*, int); 17088 int XSync(Display*, bool); 17089 17090 int XGrabKey (Display* display, int keycode, uint modifiers, Window grab_window, Bool owner_events, int pointer_mode, int keyboard_mode); 17091 int XUngrabKey (Display* display, int keycode, uint modifiers, Window grab_window); 17092 17093 int XGrabKeyboard(Display*, Window, Bool, int, int, Time); 17094 int XUngrabKeyboard(Display*, Time); 17095 17096 KeyCode XKeysymToKeycode (Display* display, KeySym keysym); 17097 17098 KeySym XStringToKeysym(const char *string); 17099 17100 Bool XCheckTypedEvent(Display *display, int event_type, XEvent *event_return); 17101 17102 Window XDefaultRootWindow(Display*); 17103 17104 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); 17105 17106 int XUngrabButton(Display *display, uint button, uint modifiers, Window grab_window); 17107 17108 int XDrawLines(Display*, Drawable, GC, XPoint*, int, CoordMode); 17109 int XFillPolygon(Display*, Drawable, GC, XPoint*, int, PolygonShape, CoordMode); 17110 17111 Status XAllocColor(Display*, Colormap, XColor*); 17112 17113 int XWithdrawWindow(Display*, Window, int); 17114 int XUnmapWindow(Display*, Window); 17115 int XLowerWindow(Display*, Window); 17116 int XRaiseWindow(Display*, Window); 17117 17118 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); 17119 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); 17120 17121 int XGetInputFocus(Display*, Window*, int*); 17122 int XSetInputFocus(Display*, Window, int, Time); 17123 17124 XErrorHandler XSetErrorHandler(XErrorHandler); 17125 17126 int XGetErrorText(Display*, int, char*, int); 17127 17128 Bool XkbSetDetectableAutoRepeat(Display* dpy, Bool detectable, Bool* supported); 17129 17130 17131 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); 17132 int XUngrabPointer(Display *display, Time time); 17133 int XChangeActivePointerGrab(Display *display, uint event_mask, Cursor cursor, Time time); 17134 17135 int XCopyPlane(Display*, Drawable, Drawable, GC, int, int, uint, uint, int, int, arch_ulong); 17136 17137 Status XGetGeometry(Display*, Drawable, Window*, int*, int*, uint*, uint*, uint*, uint*); 17138 int XSetClipMask(Display*, GC, Pixmap); 17139 int XSetClipOrigin(Display*, GC, int, int); 17140 17141 void XSetClipRectangles(Display*, GC, int, int, XRectangle*, int, int); 17142 17143 void XSetWMName(Display*, Window, XTextProperty*); 17144 Status XGetWMName(Display*, Window, XTextProperty*); 17145 int XStoreName(Display* display, Window w, const(char)* window_name); 17146 17147 XIOErrorHandler XSetIOErrorHandler (XIOErrorHandler handler); 17148 17149 } 17150 } 17151 17152 interface Xext { 17153 extern(C) nothrow @nogc { 17154 Status XShmAttach(Display*, XShmSegmentInfo*); 17155 Status XShmDetach(Display*, XShmSegmentInfo*); 17156 Status XShmPutImage( 17157 Display* /* dpy */, 17158 Drawable /* d */, 17159 GC /* gc */, 17160 XImage* /* image */, 17161 int /* src_x */, 17162 int /* src_y */, 17163 int /* dst_x */, 17164 int /* dst_y */, 17165 uint /* src_width */, 17166 uint /* src_height */, 17167 Bool /* send_event */ 17168 ); 17169 17170 Status XShmQueryExtension(Display*); 17171 17172 XImage *XShmCreateImage( 17173 Display* /* dpy */, 17174 Visual* /* visual */, 17175 uint /* depth */, 17176 int /* format */, 17177 char* /* data */, 17178 XShmSegmentInfo* /* shminfo */, 17179 uint /* width */, 17180 uint /* height */ 17181 ); 17182 17183 Pixmap XShmCreatePixmap( 17184 Display* /* dpy */, 17185 Drawable /* d */, 17186 char* /* data */, 17187 XShmSegmentInfo* /* shminfo */, 17188 uint /* width */, 17189 uint /* height */, 17190 uint /* depth */ 17191 ); 17192 17193 } 17194 } 17195 17196 // this requires -lXpm 17197 //int XpmCreatePixmapFromData(Display*, Drawable, scope const char**, Pixmap*, Pixmap*, void*); // FIXME: void* should be XpmAttributes 17198 17199 17200 mixin DynamicLoad!(XLib, "X11", 6, librariesSuccessfullyLoaded) xlib; 17201 mixin DynamicLoad!(Xext, "Xext", 6, librariesSuccessfullyLoaded) xext; 17202 shared static this() { 17203 xlib.loadDynamicLibrary(); 17204 xext.loadDynamicLibrary(); 17205 } 17206 17207 17208 extern(C) nothrow @nogc { 17209 17210 alias XrmDatabase = void*; 17211 struct XrmValue { 17212 uint size; 17213 void* addr; 17214 } 17215 17216 struct XVisualInfo { 17217 Visual* visual; 17218 VisualID visualid; 17219 int screen; 17220 uint depth; 17221 int c_class; 17222 c_ulong red_mask; 17223 c_ulong green_mask; 17224 c_ulong blue_mask; 17225 int colormap_size; 17226 int bits_per_rgb; 17227 } 17228 17229 enum VisualNoMask= 0x0; 17230 enum VisualIDMask= 0x1; 17231 enum VisualScreenMask=0x2; 17232 enum VisualDepthMask= 0x4; 17233 enum VisualClassMask= 0x8; 17234 enum VisualRedMaskMask=0x10; 17235 enum VisualGreenMaskMask=0x20; 17236 enum VisualBlueMaskMask=0x40; 17237 enum VisualColormapSizeMask=0x80; 17238 enum VisualBitsPerRGBMask=0x100; 17239 enum VisualAllMask= 0x1FF; 17240 17241 enum AnyKey = 0; 17242 enum AnyModifier = 1 << 15; 17243 17244 // XIM and other crap 17245 struct _XOM {} 17246 struct _XIM {} 17247 struct _XIC {} 17248 alias XOM = _XOM*; 17249 alias XIM = _XIM*; 17250 alias XIC = _XIC*; 17251 17252 alias XVaNestedList = void*; 17253 17254 alias XIMStyle = arch_ulong; 17255 enum : arch_ulong { 17256 XIMPreeditArea = 0x0001, 17257 XIMPreeditCallbacks = 0x0002, 17258 XIMPreeditPosition = 0x0004, 17259 XIMPreeditNothing = 0x0008, 17260 XIMPreeditNone = 0x0010, 17261 XIMStatusArea = 0x0100, 17262 XIMStatusCallbacks = 0x0200, 17263 XIMStatusNothing = 0x0400, 17264 XIMStatusNone = 0x0800, 17265 } 17266 17267 17268 /* X Shared Memory Extension functions */ 17269 //pragma(lib, "Xshm"); 17270 alias arch_ulong ShmSeg; 17271 struct XShmSegmentInfo { 17272 ShmSeg shmseg; 17273 int shmid; 17274 ubyte* shmaddr; 17275 Bool readOnly; 17276 } 17277 17278 // and the necessary OS functions 17279 int shmget(int, size_t, int); 17280 void* shmat(int, scope const void*, int); 17281 int shmdt(scope const void*); 17282 int shmctl (int shmid, int cmd, void* ptr /*struct shmid_ds *buf*/); 17283 17284 enum IPC_PRIVATE = 0; 17285 enum IPC_CREAT = 512; 17286 enum IPC_RMID = 0; 17287 17288 /* MIT-SHM end */ 17289 17290 17291 enum MappingType:int { 17292 MappingModifier =0, 17293 MappingKeyboard =1, 17294 MappingPointer =2 17295 } 17296 17297 /* ImageFormat -- PutImage, GetImage */ 17298 enum ImageFormat:int { 17299 XYBitmap =0, /* depth 1, XYFormat */ 17300 XYPixmap =1, /* depth == drawable depth */ 17301 ZPixmap =2 /* depth == drawable depth */ 17302 } 17303 17304 enum ModifierName:int { 17305 ShiftMapIndex =0, 17306 LockMapIndex =1, 17307 ControlMapIndex =2, 17308 Mod1MapIndex =3, 17309 Mod2MapIndex =4, 17310 Mod3MapIndex =5, 17311 Mod4MapIndex =6, 17312 Mod5MapIndex =7 17313 } 17314 17315 enum ButtonMask:int { 17316 Button1Mask =1<<8, 17317 Button2Mask =1<<9, 17318 Button3Mask =1<<10, 17319 Button4Mask =1<<11, 17320 Button5Mask =1<<12, 17321 AnyModifier =1<<15/* used in GrabButton, GrabKey */ 17322 } 17323 17324 enum KeyOrButtonMask:uint { 17325 ShiftMask =1<<0, 17326 LockMask =1<<1, 17327 ControlMask =1<<2, 17328 Mod1Mask =1<<3, 17329 Mod2Mask =1<<4, 17330 Mod3Mask =1<<5, 17331 Mod4Mask =1<<6, 17332 Mod5Mask =1<<7, 17333 Button1Mask =1<<8, 17334 Button2Mask =1<<9, 17335 Button3Mask =1<<10, 17336 Button4Mask =1<<11, 17337 Button5Mask =1<<12, 17338 AnyModifier =1<<15/* used in GrabButton, GrabKey */ 17339 } 17340 17341 enum ButtonName:int { 17342 Button1 =1, 17343 Button2 =2, 17344 Button3 =3, 17345 Button4 =4, 17346 Button5 =5 17347 } 17348 17349 /* Notify modes */ 17350 enum NotifyModes:int 17351 { 17352 NotifyNormal =0, 17353 NotifyGrab =1, 17354 NotifyUngrab =2, 17355 NotifyWhileGrabbed =3 17356 } 17357 enum NotifyHint = 1; /* for MotionNotify events */ 17358 17359 /* Notify detail */ 17360 enum NotifyDetail:int 17361 { 17362 NotifyAncestor =0, 17363 NotifyVirtual =1, 17364 NotifyInferior =2, 17365 NotifyNonlinear =3, 17366 NotifyNonlinearVirtual =4, 17367 NotifyPointer =5, 17368 NotifyPointerRoot =6, 17369 NotifyDetailNone =7 17370 } 17371 17372 /* Visibility notify */ 17373 17374 enum VisibilityNotify:int 17375 { 17376 VisibilityUnobscured =0, 17377 VisibilityPartiallyObscured =1, 17378 VisibilityFullyObscured =2 17379 } 17380 17381 17382 enum WindowStackingMethod:int 17383 { 17384 Above =0, 17385 Below =1, 17386 TopIf =2, 17387 BottomIf =3, 17388 Opposite =4 17389 } 17390 17391 /* Circulation request */ 17392 enum CirculationRequest:int 17393 { 17394 PlaceOnTop =0, 17395 PlaceOnBottom =1 17396 } 17397 17398 enum PropertyNotification:int 17399 { 17400 PropertyNewValue =0, 17401 PropertyDelete =1 17402 } 17403 17404 enum ColorMapNotification:int 17405 { 17406 ColormapUninstalled =0, 17407 ColormapInstalled =1 17408 } 17409 17410 17411 struct _XPrivate {} 17412 struct _XrmHashBucketRec {} 17413 17414 alias void* XPointer; 17415 alias void* XExtData; 17416 17417 version( X86_64 ) { 17418 alias ulong XID; 17419 alias ulong arch_ulong; 17420 alias long arch_long; 17421 } else version (AArch64) { 17422 alias ulong XID; 17423 alias ulong arch_ulong; 17424 alias long arch_long; 17425 } else { 17426 alias uint XID; 17427 alias uint arch_ulong; 17428 alias int arch_long; 17429 } 17430 17431 alias XID Window; 17432 alias XID Drawable; 17433 alias XID Pixmap; 17434 17435 alias arch_ulong Atom; 17436 alias int Bool; 17437 alias Display XDisplay; 17438 17439 alias int ByteOrder; 17440 alias arch_ulong Time; 17441 alias void ScreenFormat; 17442 17443 struct XImage { 17444 int width, height; /* size of image */ 17445 int xoffset; /* number of pixels offset in X direction */ 17446 ImageFormat format; /* XYBitmap, XYPixmap, ZPixmap */ 17447 void *data; /* pointer to image data */ 17448 ByteOrder byte_order; /* data byte order, LSBFirst, MSBFirst */ 17449 int bitmap_unit; /* quant. of scanline 8, 16, 32 */ 17450 int bitmap_bit_order; /* LSBFirst, MSBFirst */ 17451 int bitmap_pad; /* 8, 16, 32 either XY or ZPixmap */ 17452 int depth; /* depth of image */ 17453 int bytes_per_line; /* accelarator to next line */ 17454 int bits_per_pixel; /* bits per pixel (ZPixmap) */ 17455 arch_ulong red_mask; /* bits in z arrangment */ 17456 arch_ulong green_mask; 17457 arch_ulong blue_mask; 17458 XPointer obdata; /* hook for the object routines to hang on */ 17459 static struct F { /* image manipulation routines */ 17460 XImage* function( 17461 XDisplay* /* display */, 17462 Visual* /* visual */, 17463 uint /* depth */, 17464 int /* format */, 17465 int /* offset */, 17466 ubyte* /* data */, 17467 uint /* width */, 17468 uint /* height */, 17469 int /* bitmap_pad */, 17470 int /* bytes_per_line */) create_image; 17471 int function(XImage *) destroy_image; 17472 arch_ulong function(XImage *, int, int) get_pixel; 17473 int function(XImage *, int, int, arch_ulong) put_pixel; 17474 XImage* function(XImage *, int, int, uint, uint) sub_image; 17475 int function(XImage *, arch_long) add_pixel; 17476 } 17477 F f; 17478 } 17479 version(X86_64) static assert(XImage.sizeof == 136); 17480 else version(X86) static assert(XImage.sizeof == 88); 17481 17482 struct XCharStruct { 17483 short lbearing; /* origin to left edge of raster */ 17484 short rbearing; /* origin to right edge of raster */ 17485 short width; /* advance to next char's origin */ 17486 short ascent; /* baseline to top edge of raster */ 17487 short descent; /* baseline to bottom edge of raster */ 17488 ushort attributes; /* per char flags (not predefined) */ 17489 } 17490 17491 /* 17492 * To allow arbitrary information with fonts, there are additional properties 17493 * returned. 17494 */ 17495 struct XFontProp { 17496 Atom name; 17497 arch_ulong card32; 17498 } 17499 17500 alias Atom Font; 17501 17502 struct XFontStruct { 17503 XExtData *ext_data; /* Hook for extension to hang data */ 17504 Font fid; /* Font ID for this font */ 17505 uint direction; /* Direction the font is painted */ 17506 uint min_char_or_byte2; /* First character */ 17507 uint max_char_or_byte2; /* Last character */ 17508 uint min_byte1; /* First row that exists (for two-byte fonts) */ 17509 uint max_byte1; /* Last row that exists (for two-byte fonts) */ 17510 Bool all_chars_exist; /* Flag if all characters have nonzero size */ 17511 uint default_char; /* Char to print for undefined character */ 17512 int n_properties; /* How many properties there are */ 17513 XFontProp *properties; /* Pointer to array of additional properties*/ 17514 XCharStruct min_bounds; /* Minimum bounds over all existing char*/ 17515 XCharStruct max_bounds; /* Maximum bounds over all existing char*/ 17516 XCharStruct *per_char; /* first_char to last_char information */ 17517 int ascent; /* Max extent above baseline for spacing */ 17518 int descent; /* Max descent below baseline for spacing */ 17519 } 17520 17521 17522 /* 17523 * Definitions of specific events. 17524 */ 17525 struct XKeyEvent 17526 { 17527 int type; /* of event */ 17528 arch_ulong serial; /* # of last request processed by server */ 17529 Bool send_event; /* true if this came from a SendEvent request */ 17530 Display *display; /* Display the event was read from */ 17531 Window window; /* "event" window it is reported relative to */ 17532 Window root; /* root window that the event occurred on */ 17533 Window subwindow; /* child window */ 17534 Time time; /* milliseconds */ 17535 int x, y; /* pointer x, y coordinates in event window */ 17536 int x_root, y_root; /* coordinates relative to root */ 17537 KeyOrButtonMask state; /* key or button mask */ 17538 uint keycode; /* detail */ 17539 Bool same_screen; /* same screen flag */ 17540 } 17541 version(X86_64) static assert(XKeyEvent.sizeof == 96); 17542 alias XKeyEvent XKeyPressedEvent; 17543 alias XKeyEvent XKeyReleasedEvent; 17544 17545 struct XButtonEvent 17546 { 17547 int type; /* of event */ 17548 arch_ulong serial; /* # of last request processed by server */ 17549 Bool send_event; /* true if this came from a SendEvent request */ 17550 Display *display; /* Display the event was read from */ 17551 Window window; /* "event" window it is reported relative to */ 17552 Window root; /* root window that the event occurred on */ 17553 Window subwindow; /* child window */ 17554 Time time; /* milliseconds */ 17555 int x, y; /* pointer x, y coordinates in event window */ 17556 int x_root, y_root; /* coordinates relative to root */ 17557 KeyOrButtonMask state; /* key or button mask */ 17558 uint button; /* detail */ 17559 Bool same_screen; /* same screen flag */ 17560 } 17561 alias XButtonEvent XButtonPressedEvent; 17562 alias XButtonEvent XButtonReleasedEvent; 17563 17564 struct XMotionEvent{ 17565 int type; /* of event */ 17566 arch_ulong serial; /* # of last request processed by server */ 17567 Bool send_event; /* true if this came from a SendEvent request */ 17568 Display *display; /* Display the event was read from */ 17569 Window window; /* "event" window reported relative to */ 17570 Window root; /* root window that the event occurred on */ 17571 Window subwindow; /* child window */ 17572 Time time; /* milliseconds */ 17573 int x, y; /* pointer x, y coordinates in event window */ 17574 int x_root, y_root; /* coordinates relative to root */ 17575 KeyOrButtonMask state; /* key or button mask */ 17576 byte is_hint; /* detail */ 17577 Bool same_screen; /* same screen flag */ 17578 } 17579 alias XMotionEvent XPointerMovedEvent; 17580 17581 struct XCrossingEvent{ 17582 int type; /* of event */ 17583 arch_ulong serial; /* # of last request processed by server */ 17584 Bool send_event; /* true if this came from a SendEvent request */ 17585 Display *display; /* Display the event was read from */ 17586 Window window; /* "event" window reported relative to */ 17587 Window root; /* root window that the event occurred on */ 17588 Window subwindow; /* child window */ 17589 Time time; /* milliseconds */ 17590 int x, y; /* pointer x, y coordinates in event window */ 17591 int x_root, y_root; /* coordinates relative to root */ 17592 NotifyModes mode; /* NotifyNormal, NotifyGrab, NotifyUngrab */ 17593 NotifyDetail detail; 17594 /* 17595 * NotifyAncestor, NotifyVirtual, NotifyInferior, 17596 * NotifyNonlinear,NotifyNonlinearVirtual 17597 */ 17598 Bool same_screen; /* same screen flag */ 17599 Bool focus; /* Boolean focus */ 17600 KeyOrButtonMask state; /* key or button mask */ 17601 } 17602 alias XCrossingEvent XEnterWindowEvent; 17603 alias XCrossingEvent XLeaveWindowEvent; 17604 17605 struct XFocusChangeEvent{ 17606 int type; /* FocusIn or FocusOut */ 17607 arch_ulong serial; /* # of last request processed by server */ 17608 Bool send_event; /* true if this came from a SendEvent request */ 17609 Display *display; /* Display the event was read from */ 17610 Window window; /* window of event */ 17611 NotifyModes mode; /* NotifyNormal, NotifyWhileGrabbed, 17612 NotifyGrab, NotifyUngrab */ 17613 NotifyDetail detail; 17614 /* 17615 * NotifyAncestor, NotifyVirtual, NotifyInferior, 17616 * NotifyNonlinear,NotifyNonlinearVirtual, NotifyPointer, 17617 * NotifyPointerRoot, NotifyDetailNone 17618 */ 17619 } 17620 alias XFocusChangeEvent XFocusInEvent; 17621 alias XFocusChangeEvent XFocusOutEvent; 17622 17623 enum CWBackPixmap = (1L<<0); 17624 enum CWBackPixel = (1L<<1); 17625 enum CWBorderPixmap = (1L<<2); 17626 enum CWBorderPixel = (1L<<3); 17627 enum CWBitGravity = (1L<<4); 17628 enum CWWinGravity = (1L<<5); 17629 enum CWBackingStore = (1L<<6); 17630 enum CWBackingPlanes = (1L<<7); 17631 enum CWBackingPixel = (1L<<8); 17632 enum CWOverrideRedirect = (1L<<9); 17633 enum CWSaveUnder = (1L<<10); 17634 enum CWEventMask = (1L<<11); 17635 enum CWDontPropagate = (1L<<12); 17636 enum CWColormap = (1L<<13); 17637 enum CWCursor = (1L<<14); 17638 17639 struct XWindowAttributes { 17640 int x, y; /* location of window */ 17641 int width, height; /* width and height of window */ 17642 int border_width; /* border width of window */ 17643 int depth; /* depth of window */ 17644 Visual *visual; /* the associated visual structure */ 17645 Window root; /* root of screen containing window */ 17646 int class_; /* InputOutput, InputOnly*/ 17647 int bit_gravity; /* one of the bit gravity values */ 17648 int win_gravity; /* one of the window gravity values */ 17649 int backing_store; /* NotUseful, WhenMapped, Always */ 17650 arch_ulong backing_planes; /* planes to be preserved if possible */ 17651 arch_ulong backing_pixel; /* value to be used when restoring planes */ 17652 Bool save_under; /* boolean, should bits under be saved? */ 17653 Colormap colormap; /* color map to be associated with window */ 17654 Bool map_installed; /* boolean, is color map currently installed*/ 17655 int map_state; /* IsUnmapped, IsUnviewable, IsViewable */ 17656 arch_long all_event_masks; /* set of events all people have interest in*/ 17657 arch_long your_event_mask; /* my event mask */ 17658 arch_long do_not_propagate_mask; /* set of events that should not propagate */ 17659 Bool override_redirect; /* boolean value for override-redirect */ 17660 Screen *screen; /* back pointer to correct screen */ 17661 } 17662 17663 enum IsUnmapped = 0; 17664 enum IsUnviewable = 1; 17665 enum IsViewable = 2; 17666 17667 struct XSetWindowAttributes { 17668 Pixmap background_pixmap;/* background, None, or ParentRelative */ 17669 arch_ulong background_pixel;/* background pixel */ 17670 Pixmap border_pixmap; /* border of the window or CopyFromParent */ 17671 arch_ulong border_pixel;/* border pixel value */ 17672 int bit_gravity; /* one of bit gravity values */ 17673 int win_gravity; /* one of the window gravity values */ 17674 int backing_store; /* NotUseful, WhenMapped, Always */ 17675 arch_ulong backing_planes;/* planes to be preserved if possible */ 17676 arch_ulong backing_pixel;/* value to use in restoring planes */ 17677 Bool save_under; /* should bits under be saved? (popups) */ 17678 arch_long event_mask; /* set of events that should be saved */ 17679 arch_long do_not_propagate_mask;/* set of events that should not propagate */ 17680 Bool override_redirect; /* boolean value for override_redirect */ 17681 Colormap colormap; /* color map to be associated with window */ 17682 Cursor cursor; /* cursor to be displayed (or None) */ 17683 } 17684 17685 17686 alias int Status; 17687 17688 17689 enum EventMask:int 17690 { 17691 NoEventMask =0, 17692 KeyPressMask =1<<0, 17693 KeyReleaseMask =1<<1, 17694 ButtonPressMask =1<<2, 17695 ButtonReleaseMask =1<<3, 17696 EnterWindowMask =1<<4, 17697 LeaveWindowMask =1<<5, 17698 PointerMotionMask =1<<6, 17699 PointerMotionHintMask =1<<7, 17700 Button1MotionMask =1<<8, 17701 Button2MotionMask =1<<9, 17702 Button3MotionMask =1<<10, 17703 Button4MotionMask =1<<11, 17704 Button5MotionMask =1<<12, 17705 ButtonMotionMask =1<<13, 17706 KeymapStateMask =1<<14, 17707 ExposureMask =1<<15, 17708 VisibilityChangeMask =1<<16, 17709 StructureNotifyMask =1<<17, 17710 ResizeRedirectMask =1<<18, 17711 SubstructureNotifyMask =1<<19, 17712 SubstructureRedirectMask=1<<20, 17713 FocusChangeMask =1<<21, 17714 PropertyChangeMask =1<<22, 17715 ColormapChangeMask =1<<23, 17716 OwnerGrabButtonMask =1<<24 17717 } 17718 17719 struct MwmHints { 17720 c_ulong flags; 17721 c_ulong functions; 17722 c_ulong decorations; 17723 c_long input_mode; 17724 c_ulong status; 17725 } 17726 17727 enum { 17728 MWM_HINTS_FUNCTIONS = (1L << 0), 17729 MWM_HINTS_DECORATIONS = (1L << 1), 17730 17731 MWM_FUNC_ALL = (1L << 0), 17732 MWM_FUNC_RESIZE = (1L << 1), 17733 MWM_FUNC_MOVE = (1L << 2), 17734 MWM_FUNC_MINIMIZE = (1L << 3), 17735 MWM_FUNC_MAXIMIZE = (1L << 4), 17736 MWM_FUNC_CLOSE = (1L << 5), 17737 17738 MWM_DECOR_ALL = (1L << 0), 17739 MWM_DECOR_BORDER = (1L << 1), 17740 MWM_DECOR_RESIZEH = (1L << 2), 17741 MWM_DECOR_TITLE = (1L << 3), 17742 MWM_DECOR_MENU = (1L << 4), 17743 MWM_DECOR_MINIMIZE = (1L << 5), 17744 MWM_DECOR_MAXIMIZE = (1L << 6), 17745 } 17746 17747 import core.stdc.config : c_long, c_ulong; 17748 17749 /* Size hints mask bits */ 17750 17751 enum USPosition = (1L << 0) /* user specified x, y */; 17752 enum USSize = (1L << 1) /* user specified width, height */; 17753 enum PPosition = (1L << 2) /* program specified position */; 17754 enum PSize = (1L << 3) /* program specified size */; 17755 enum PMinSize = (1L << 4) /* program specified minimum size */; 17756 enum PMaxSize = (1L << 5) /* program specified maximum size */; 17757 enum PResizeInc = (1L << 6) /* program specified resize increments */; 17758 enum PAspect = (1L << 7) /* program specified min and max aspect ratios */; 17759 enum PBaseSize = (1L << 8); 17760 enum PWinGravity = (1L << 9); 17761 enum PAllHints = (PPosition|PSize| PMinSize|PMaxSize| PResizeInc|PAspect); 17762 struct XSizeHints { 17763 arch_long flags; /* marks which fields in this structure are defined */ 17764 int x, y; /* Obsolete */ 17765 int width, height; /* Obsolete */ 17766 int min_width, min_height; 17767 int max_width, max_height; 17768 int width_inc, height_inc; 17769 struct Aspect { 17770 int x; /* numerator */ 17771 int y; /* denominator */ 17772 } 17773 17774 Aspect min_aspect; 17775 Aspect max_aspect; 17776 int base_width, base_height; 17777 int win_gravity; 17778 /* this structure may be extended in the future */ 17779 } 17780 17781 17782 17783 enum EventType:int 17784 { 17785 KeyPress =2, 17786 KeyRelease =3, 17787 ButtonPress =4, 17788 ButtonRelease =5, 17789 MotionNotify =6, 17790 EnterNotify =7, 17791 LeaveNotify =8, 17792 FocusIn =9, 17793 FocusOut =10, 17794 KeymapNotify =11, 17795 Expose =12, 17796 GraphicsExpose =13, 17797 NoExpose =14, 17798 VisibilityNotify =15, 17799 CreateNotify =16, 17800 DestroyNotify =17, 17801 UnmapNotify =18, 17802 MapNotify =19, 17803 MapRequest =20, 17804 ReparentNotify =21, 17805 ConfigureNotify =22, 17806 ConfigureRequest =23, 17807 GravityNotify =24, 17808 ResizeRequest =25, 17809 CirculateNotify =26, 17810 CirculateRequest =27, 17811 PropertyNotify =28, 17812 SelectionClear =29, 17813 SelectionRequest =30, 17814 SelectionNotify =31, 17815 ColormapNotify =32, 17816 ClientMessage =33, 17817 MappingNotify =34, 17818 LASTEvent =35 /* must be bigger than any event # */ 17819 } 17820 /* generated on EnterWindow and FocusIn when KeyMapState selected */ 17821 struct XKeymapEvent 17822 { 17823 int type; 17824 arch_ulong serial; /* # of last request processed by server */ 17825 Bool send_event; /* true if this came from a SendEvent request */ 17826 Display *display; /* Display the event was read from */ 17827 Window window; 17828 byte[32] key_vector; 17829 } 17830 17831 struct XExposeEvent 17832 { 17833 int type; 17834 arch_ulong serial; /* # of last request processed by server */ 17835 Bool send_event; /* true if this came from a SendEvent request */ 17836 Display *display; /* Display the event was read from */ 17837 Window window; 17838 int x, y; 17839 int width, height; 17840 int count; /* if non-zero, at least this many more */ 17841 } 17842 17843 struct XGraphicsExposeEvent{ 17844 int type; 17845 arch_ulong serial; /* # of last request processed by server */ 17846 Bool send_event; /* true if this came from a SendEvent request */ 17847 Display *display; /* Display the event was read from */ 17848 Drawable drawable; 17849 int x, y; 17850 int width, height; 17851 int count; /* if non-zero, at least this many more */ 17852 int major_code; /* core is CopyArea or CopyPlane */ 17853 int minor_code; /* not defined in the core */ 17854 } 17855 17856 struct XNoExposeEvent{ 17857 int type; 17858 arch_ulong serial; /* # of last request processed by server */ 17859 Bool send_event; /* true if this came from a SendEvent request */ 17860 Display *display; /* Display the event was read from */ 17861 Drawable drawable; 17862 int major_code; /* core is CopyArea or CopyPlane */ 17863 int minor_code; /* not defined in the core */ 17864 } 17865 17866 struct XVisibilityEvent{ 17867 int type; 17868 arch_ulong serial; /* # of last request processed by server */ 17869 Bool send_event; /* true if this came from a SendEvent request */ 17870 Display *display; /* Display the event was read from */ 17871 Window window; 17872 VisibilityNotify state; /* Visibility state */ 17873 } 17874 17875 struct XCreateWindowEvent{ 17876 int type; 17877 arch_ulong serial; /* # of last request processed by server */ 17878 Bool send_event; /* true if this came from a SendEvent request */ 17879 Display *display; /* Display the event was read from */ 17880 Window parent; /* parent of the window */ 17881 Window window; /* window id of window created */ 17882 int x, y; /* window location */ 17883 int width, height; /* size of window */ 17884 int border_width; /* border width */ 17885 Bool override_redirect; /* creation should be overridden */ 17886 } 17887 17888 struct XDestroyWindowEvent 17889 { 17890 int type; 17891 arch_ulong serial; /* # of last request processed by server */ 17892 Bool send_event; /* true if this came from a SendEvent request */ 17893 Display *display; /* Display the event was read from */ 17894 Window event; 17895 Window window; 17896 } 17897 17898 struct XUnmapEvent 17899 { 17900 int type; 17901 arch_ulong serial; /* # of last request processed by server */ 17902 Bool send_event; /* true if this came from a SendEvent request */ 17903 Display *display; /* Display the event was read from */ 17904 Window event; 17905 Window window; 17906 Bool from_configure; 17907 } 17908 17909 struct XMapEvent 17910 { 17911 int type; 17912 arch_ulong serial; /* # of last request processed by server */ 17913 Bool send_event; /* true if this came from a SendEvent request */ 17914 Display *display; /* Display the event was read from */ 17915 Window event; 17916 Window window; 17917 Bool override_redirect; /* Boolean, is override set... */ 17918 } 17919 17920 struct XMapRequestEvent 17921 { 17922 int type; 17923 arch_ulong serial; /* # of last request processed by server */ 17924 Bool send_event; /* true if this came from a SendEvent request */ 17925 Display *display; /* Display the event was read from */ 17926 Window parent; 17927 Window window; 17928 } 17929 17930 struct XReparentEvent 17931 { 17932 int type; 17933 arch_ulong serial; /* # of last request processed by server */ 17934 Bool send_event; /* true if this came from a SendEvent request */ 17935 Display *display; /* Display the event was read from */ 17936 Window event; 17937 Window window; 17938 Window parent; 17939 int x, y; 17940 Bool override_redirect; 17941 } 17942 17943 struct XConfigureEvent 17944 { 17945 int type; 17946 arch_ulong serial; /* # of last request processed by server */ 17947 Bool send_event; /* true if this came from a SendEvent request */ 17948 Display *display; /* Display the event was read from */ 17949 Window event; 17950 Window window; 17951 int x, y; 17952 int width, height; 17953 int border_width; 17954 Window above; 17955 Bool override_redirect; 17956 } 17957 17958 struct XGravityEvent 17959 { 17960 int type; 17961 arch_ulong serial; /* # of last request processed by server */ 17962 Bool send_event; /* true if this came from a SendEvent request */ 17963 Display *display; /* Display the event was read from */ 17964 Window event; 17965 Window window; 17966 int x, y; 17967 } 17968 17969 struct XResizeRequestEvent 17970 { 17971 int type; 17972 arch_ulong serial; /* # of last request processed by server */ 17973 Bool send_event; /* true if this came from a SendEvent request */ 17974 Display *display; /* Display the event was read from */ 17975 Window window; 17976 int width, height; 17977 } 17978 17979 struct XConfigureRequestEvent 17980 { 17981 int type; 17982 arch_ulong serial; /* # of last request processed by server */ 17983 Bool send_event; /* true if this came from a SendEvent request */ 17984 Display *display; /* Display the event was read from */ 17985 Window parent; 17986 Window window; 17987 int x, y; 17988 int width, height; 17989 int border_width; 17990 Window above; 17991 WindowStackingMethod detail; /* Above, Below, TopIf, BottomIf, Opposite */ 17992 arch_ulong value_mask; 17993 } 17994 17995 struct XCirculateEvent 17996 { 17997 int type; 17998 arch_ulong serial; /* # of last request processed by server */ 17999 Bool send_event; /* true if this came from a SendEvent request */ 18000 Display *display; /* Display the event was read from */ 18001 Window event; 18002 Window window; 18003 CirculationRequest place; /* PlaceOnTop, PlaceOnBottom */ 18004 } 18005 18006 struct XCirculateRequestEvent 18007 { 18008 int type; 18009 arch_ulong serial; /* # of last request processed by server */ 18010 Bool send_event; /* true if this came from a SendEvent request */ 18011 Display *display; /* Display the event was read from */ 18012 Window parent; 18013 Window window; 18014 CirculationRequest place; /* PlaceOnTop, PlaceOnBottom */ 18015 } 18016 18017 struct XPropertyEvent 18018 { 18019 int type; 18020 arch_ulong serial; /* # of last request processed by server */ 18021 Bool send_event; /* true if this came from a SendEvent request */ 18022 Display *display; /* Display the event was read from */ 18023 Window window; 18024 Atom atom; 18025 Time time; 18026 PropertyNotification state; /* NewValue, Deleted */ 18027 } 18028 18029 struct XSelectionClearEvent 18030 { 18031 int type; 18032 arch_ulong serial; /* # of last request processed by server */ 18033 Bool send_event; /* true if this came from a SendEvent request */ 18034 Display *display; /* Display the event was read from */ 18035 Window window; 18036 Atom selection; 18037 Time time; 18038 } 18039 18040 struct XSelectionRequestEvent 18041 { 18042 int type; 18043 arch_ulong serial; /* # of last request processed by server */ 18044 Bool send_event; /* true if this came from a SendEvent request */ 18045 Display *display; /* Display the event was read from */ 18046 Window owner; 18047 Window requestor; 18048 Atom selection; 18049 Atom target; 18050 Atom property; 18051 Time time; 18052 } 18053 18054 struct XSelectionEvent 18055 { 18056 int type; 18057 arch_ulong serial; /* # of last request processed by server */ 18058 Bool send_event; /* true if this came from a SendEvent request */ 18059 Display *display; /* Display the event was read from */ 18060 Window requestor; 18061 Atom selection; 18062 Atom target; 18063 Atom property; /* ATOM or None */ 18064 Time time; 18065 } 18066 version(X86_64) static assert(XSelectionClearEvent.sizeof == 56); 18067 18068 struct XColormapEvent 18069 { 18070 int type; 18071 arch_ulong serial; /* # of last request processed by server */ 18072 Bool send_event; /* true if this came from a SendEvent request */ 18073 Display *display; /* Display the event was read from */ 18074 Window window; 18075 Colormap colormap; /* COLORMAP or None */ 18076 Bool new_; /* C++ */ 18077 ColorMapNotification state; /* ColormapInstalled, ColormapUninstalled */ 18078 } 18079 version(X86_64) static assert(XColormapEvent.sizeof == 56); 18080 18081 struct XClientMessageEvent 18082 { 18083 int type; 18084 arch_ulong serial; /* # of last request processed by server */ 18085 Bool send_event; /* true if this came from a SendEvent request */ 18086 Display *display; /* Display the event was read from */ 18087 Window window; 18088 Atom message_type; 18089 int format; 18090 union Data{ 18091 byte[20] b; 18092 short[10] s; 18093 arch_ulong[5] l; 18094 } 18095 Data data; 18096 18097 } 18098 version(X86_64) static assert(XClientMessageEvent.sizeof == 96); 18099 18100 struct XMappingEvent 18101 { 18102 int type; 18103 arch_ulong serial; /* # of last request processed by server */ 18104 Bool send_event; /* true if this came from a SendEvent request */ 18105 Display *display; /* Display the event was read from */ 18106 Window window; /* unused */ 18107 MappingType request; /* one of MappingModifier, MappingKeyboard, 18108 MappingPointer */ 18109 int first_keycode; /* first keycode */ 18110 int count; /* defines range of change w. first_keycode*/ 18111 } 18112 18113 struct XErrorEvent 18114 { 18115 int type; 18116 Display *display; /* Display the event was read from */ 18117 XID resourceid; /* resource id */ 18118 arch_ulong serial; /* serial number of failed request */ 18119 ubyte error_code; /* error code of failed request */ 18120 ubyte request_code; /* Major op-code of failed request */ 18121 ubyte minor_code; /* Minor op-code of failed request */ 18122 } 18123 18124 struct XAnyEvent 18125 { 18126 int type; 18127 arch_ulong serial; /* # of last request processed by server */ 18128 Bool send_event; /* true if this came from a SendEvent request */ 18129 Display *display;/* Display the event was read from */ 18130 Window window; /* window on which event was requested in event mask */ 18131 } 18132 18133 union XEvent{ 18134 int type; /* must not be changed; first element */ 18135 XAnyEvent xany; 18136 XKeyEvent xkey; 18137 XButtonEvent xbutton; 18138 XMotionEvent xmotion; 18139 XCrossingEvent xcrossing; 18140 XFocusChangeEvent xfocus; 18141 XExposeEvent xexpose; 18142 XGraphicsExposeEvent xgraphicsexpose; 18143 XNoExposeEvent xnoexpose; 18144 XVisibilityEvent xvisibility; 18145 XCreateWindowEvent xcreatewindow; 18146 XDestroyWindowEvent xdestroywindow; 18147 XUnmapEvent xunmap; 18148 XMapEvent xmap; 18149 XMapRequestEvent xmaprequest; 18150 XReparentEvent xreparent; 18151 XConfigureEvent xconfigure; 18152 XGravityEvent xgravity; 18153 XResizeRequestEvent xresizerequest; 18154 XConfigureRequestEvent xconfigurerequest; 18155 XCirculateEvent xcirculate; 18156 XCirculateRequestEvent xcirculaterequest; 18157 XPropertyEvent xproperty; 18158 XSelectionClearEvent xselectionclear; 18159 XSelectionRequestEvent xselectionrequest; 18160 XSelectionEvent xselection; 18161 XColormapEvent xcolormap; 18162 XClientMessageEvent xclient; 18163 XMappingEvent xmapping; 18164 XErrorEvent xerror; 18165 XKeymapEvent xkeymap; 18166 arch_ulong[24] pad; 18167 } 18168 18169 18170 struct Display { 18171 XExtData *ext_data; /* hook for extension to hang data */ 18172 _XPrivate *private1; 18173 int fd; /* Network socket. */ 18174 int private2; 18175 int proto_major_version;/* major version of server's X protocol */ 18176 int proto_minor_version;/* minor version of servers X protocol */ 18177 char *vendor; /* vendor of the server hardware */ 18178 XID private3; 18179 XID private4; 18180 XID private5; 18181 int private6; 18182 XID function(Display*)resource_alloc;/* allocator function */ 18183 ByteOrder byte_order; /* screen byte order, LSBFirst, MSBFirst */ 18184 int bitmap_unit; /* padding and data requirements */ 18185 int bitmap_pad; /* padding requirements on bitmaps */ 18186 ByteOrder bitmap_bit_order; /* LeastSignificant or MostSignificant */ 18187 int nformats; /* number of pixmap formats in list */ 18188 ScreenFormat *pixmap_format; /* pixmap format list */ 18189 int private8; 18190 int release; /* release of the server */ 18191 _XPrivate *private9; 18192 _XPrivate *private10; 18193 int qlen; /* Length of input event queue */ 18194 arch_ulong last_request_read; /* seq number of last event read */ 18195 arch_ulong request; /* sequence number of last request. */ 18196 XPointer private11; 18197 XPointer private12; 18198 XPointer private13; 18199 XPointer private14; 18200 uint max_request_size; /* maximum number 32 bit words in request*/ 18201 _XrmHashBucketRec *db; 18202 int function (Display*)private15; 18203 char *display_name; /* "host:display" string used on this connect*/ 18204 int default_screen; /* default screen for operations */ 18205 int nscreens; /* number of screens on this server*/ 18206 Screen *screens; /* pointer to list of screens */ 18207 arch_ulong motion_buffer; /* size of motion buffer */ 18208 arch_ulong private16; 18209 int min_keycode; /* minimum defined keycode */ 18210 int max_keycode; /* maximum defined keycode */ 18211 XPointer private17; 18212 XPointer private18; 18213 int private19; 18214 byte *xdefaults; /* contents of defaults from server */ 18215 /* there is more to this structure, but it is private to Xlib */ 18216 } 18217 18218 // I got these numbers from a C program as a sanity test 18219 version(X86_64) { 18220 static assert(Display.sizeof == 296); 18221 static assert(XPointer.sizeof == 8); 18222 static assert(XErrorEvent.sizeof == 40); 18223 static assert(XAnyEvent.sizeof == 40); 18224 static assert(XMappingEvent.sizeof == 56); 18225 static assert(XEvent.sizeof == 192); 18226 } else version (AArch64) { 18227 // omit check for aarch64 18228 } else { 18229 static assert(Display.sizeof == 176); 18230 static assert(XPointer.sizeof == 4); 18231 static assert(XEvent.sizeof == 96); 18232 } 18233 18234 struct Depth 18235 { 18236 int depth; /* this depth (Z) of the depth */ 18237 int nvisuals; /* number of Visual types at this depth */ 18238 Visual *visuals; /* list of visuals possible at this depth */ 18239 } 18240 18241 alias void* GC; 18242 alias c_ulong VisualID; 18243 alias XID Colormap; 18244 alias XID Cursor; 18245 alias XID KeySym; 18246 alias uint KeyCode; 18247 enum None = 0; 18248 } 18249 18250 version(without_opengl) {} 18251 else { 18252 extern(C) nothrow @nogc { 18253 18254 18255 static if(!SdpyIsUsingIVGLBinds) { 18256 enum GLX_USE_GL= 1; /* support GLX rendering */ 18257 enum GLX_BUFFER_SIZE= 2; /* depth of the color buffer */ 18258 enum GLX_LEVEL= 3; /* level in plane stacking */ 18259 enum GLX_RGBA= 4; /* true if RGBA mode */ 18260 enum GLX_DOUBLEBUFFER= 5; /* double buffering supported */ 18261 enum GLX_STEREO= 6; /* stereo buffering supported */ 18262 enum GLX_AUX_BUFFERS= 7; /* number of aux buffers */ 18263 enum GLX_RED_SIZE= 8; /* number of red component bits */ 18264 enum GLX_GREEN_SIZE= 9; /* number of green component bits */ 18265 enum GLX_BLUE_SIZE= 10; /* number of blue component bits */ 18266 enum GLX_ALPHA_SIZE= 11; /* number of alpha component bits */ 18267 enum GLX_DEPTH_SIZE= 12; /* number of depth bits */ 18268 enum GLX_STENCIL_SIZE= 13; /* number of stencil bits */ 18269 enum GLX_ACCUM_RED_SIZE= 14; /* number of red accum bits */ 18270 enum GLX_ACCUM_GREEN_SIZE= 15; /* number of green accum bits */ 18271 enum GLX_ACCUM_BLUE_SIZE= 16; /* number of blue accum bits */ 18272 enum GLX_ACCUM_ALPHA_SIZE= 17; /* number of alpha accum bits */ 18273 18274 18275 //XVisualInfo* glXChooseVisual(Display *dpy, int screen, in int *attrib_list); 18276 18277 18278 18279 enum GL_TRUE = 1; 18280 enum GL_FALSE = 0; 18281 } 18282 18283 alias XID GLXContextID; 18284 alias XID GLXPixmap; 18285 alias XID GLXDrawable; 18286 alias XID GLXPbuffer; 18287 alias XID GLXWindow; 18288 alias XID GLXFBConfigID; 18289 alias void* GLXContext; 18290 18291 } 18292 } 18293 18294 enum AllocNone = 0; 18295 18296 extern(C) { 18297 /* WARNING, this type not in Xlib spec */ 18298 extern(C) alias XIOErrorHandler = int function (Display* display); 18299 } 18300 18301 extern(C) nothrow 18302 alias XErrorHandler = int function(Display*, XErrorEvent*); 18303 18304 extern(C) nothrow @nogc { 18305 struct Screen{ 18306 XExtData *ext_data; /* hook for extension to hang data */ 18307 Display *display; /* back pointer to display structure */ 18308 Window root; /* Root window id. */ 18309 int width, height; /* width and height of screen */ 18310 int mwidth, mheight; /* width and height of in millimeters */ 18311 int ndepths; /* number of depths possible */ 18312 Depth *depths; /* list of allowable depths on the screen */ 18313 int root_depth; /* bits per pixel */ 18314 Visual *root_visual; /* root visual */ 18315 GC default_gc; /* GC for the root root visual */ 18316 Colormap cmap; /* default color map */ 18317 uint white_pixel; 18318 uint black_pixel; /* White and Black pixel values */ 18319 int max_maps, min_maps; /* max and min color maps */ 18320 int backing_store; /* Never, WhenMapped, Always */ 18321 bool save_unders; 18322 int root_input_mask; /* initial root input mask */ 18323 } 18324 18325 struct Visual 18326 { 18327 XExtData *ext_data; /* hook for extension to hang data */ 18328 VisualID visualid; /* visual id of this visual */ 18329 int class_; /* class of screen (monochrome, etc.) */ 18330 c_ulong red_mask, green_mask, blue_mask; /* mask values */ 18331 int bits_per_rgb; /* log base 2 of distinct color values */ 18332 int map_entries; /* color map entries */ 18333 } 18334 18335 alias Display* _XPrivDisplay; 18336 18337 extern(D) Screen* ScreenOfDisplay(Display* dpy, int scr) @system { 18338 assert(dpy !is null); 18339 return &dpy.screens[scr]; 18340 } 18341 18342 extern(D) Window RootWindow(Display *dpy,int scr) { 18343 return ScreenOfDisplay(dpy,scr).root; 18344 } 18345 18346 struct XWMHints { 18347 arch_long flags; 18348 Bool input; 18349 int initial_state; 18350 Pixmap icon_pixmap; 18351 Window icon_window; 18352 int icon_x, icon_y; 18353 Pixmap icon_mask; 18354 XID window_group; 18355 } 18356 18357 struct XClassHint { 18358 char* res_name; 18359 char* res_class; 18360 } 18361 18362 extern(D) int DefaultScreen(Display *dpy) { 18363 return dpy.default_screen; 18364 } 18365 18366 extern(D) int DefaultDepth(Display* dpy, int scr) { return ScreenOfDisplay(dpy, scr).root_depth; } 18367 extern(D) int DisplayWidth(Display* dpy, int scr) { return ScreenOfDisplay(dpy, scr).width; } 18368 extern(D) int DisplayHeight(Display* dpy, int scr) { return ScreenOfDisplay(dpy, scr).height; } 18369 extern(D) int DisplayWidthMM(Display* dpy, int scr) { return ScreenOfDisplay(dpy, scr).mwidth; } 18370 extern(D) int DisplayHeightMM(Display* dpy, int scr) { return ScreenOfDisplay(dpy, scr).mheight; } 18371 extern(D) auto DefaultColormap(Display* dpy, int scr) { return ScreenOfDisplay(dpy, scr).cmap; } 18372 18373 extern(D) int ConnectionNumber(Display* dpy) { return dpy.fd; } 18374 18375 enum int AnyPropertyType = 0; 18376 enum int Success = 0; 18377 18378 enum int RevertToNone = None; 18379 enum int PointerRoot = 1; 18380 enum Time CurrentTime = 0; 18381 enum int RevertToPointerRoot = PointerRoot; 18382 enum int RevertToParent = 2; 18383 18384 extern(D) int DefaultDepthOfDisplay(Display* dpy) { 18385 return ScreenOfDisplay(dpy, DefaultScreen(dpy)).root_depth; 18386 } 18387 18388 extern(D) Visual* DefaultVisual(Display *dpy,int scr) { 18389 return ScreenOfDisplay(dpy,scr).root_visual; 18390 } 18391 18392 extern(D) GC DefaultGC(Display *dpy,int scr) { 18393 return ScreenOfDisplay(dpy,scr).default_gc; 18394 } 18395 18396 extern(D) uint BlackPixel(Display *dpy,int scr) { 18397 return ScreenOfDisplay(dpy,scr).black_pixel; 18398 } 18399 18400 extern(D) uint WhitePixel(Display *dpy,int scr) { 18401 return ScreenOfDisplay(dpy,scr).white_pixel; 18402 } 18403 18404 alias void* XFontSet; // i think 18405 struct XmbTextItem { 18406 char* chars; 18407 int nchars; 18408 int delta; 18409 XFontSet font_set; 18410 } 18411 18412 struct XTextItem { 18413 char* chars; 18414 int nchars; 18415 int delta; 18416 Font font; 18417 } 18418 18419 enum { 18420 GXclear = 0x0, /* 0 */ 18421 GXand = 0x1, /* src AND dst */ 18422 GXandReverse = 0x2, /* src AND NOT dst */ 18423 GXcopy = 0x3, /* src */ 18424 GXandInverted = 0x4, /* NOT src AND dst */ 18425 GXnoop = 0x5, /* dst */ 18426 GXxor = 0x6, /* src XOR dst */ 18427 GXor = 0x7, /* src OR dst */ 18428 GXnor = 0x8, /* NOT src AND NOT dst */ 18429 GXequiv = 0x9, /* NOT src XOR dst */ 18430 GXinvert = 0xa, /* NOT dst */ 18431 GXorReverse = 0xb, /* src OR NOT dst */ 18432 GXcopyInverted = 0xc, /* NOT src */ 18433 GXorInverted = 0xd, /* NOT src OR dst */ 18434 GXnand = 0xe, /* NOT src OR NOT dst */ 18435 GXset = 0xf, /* 1 */ 18436 } 18437 enum QueueMode : int { 18438 QueuedAlready, 18439 QueuedAfterReading, 18440 QueuedAfterFlush 18441 } 18442 18443 enum GrabMode { GrabModeSync = 0, GrabModeAsync = 1 } 18444 18445 struct XPoint { 18446 short x; 18447 short y; 18448 } 18449 18450 enum CoordMode:int { 18451 CoordModeOrigin = 0, 18452 CoordModePrevious = 1 18453 } 18454 18455 enum PolygonShape:int { 18456 Complex = 0, 18457 Nonconvex = 1, 18458 Convex = 2 18459 } 18460 18461 struct XTextProperty { 18462 const(char)* value; /* same as Property routines */ 18463 Atom encoding; /* prop type */ 18464 int format; /* prop data format: 8, 16, or 32 */ 18465 arch_ulong nitems; /* number of data items in value */ 18466 } 18467 18468 version( X86_64 ) { 18469 static assert(XTextProperty.sizeof == 32); 18470 } 18471 18472 18473 struct XGCValues { 18474 int function_; /* logical operation */ 18475 arch_ulong plane_mask;/* plane mask */ 18476 arch_ulong foreground;/* foreground pixel */ 18477 arch_ulong background;/* background pixel */ 18478 int line_width; /* line width */ 18479 int line_style; /* LineSolid, LineOnOffDash, LineDoubleDash */ 18480 int cap_style; /* CapNotLast, CapButt, 18481 CapRound, CapProjecting */ 18482 int join_style; /* JoinMiter, JoinRound, JoinBevel */ 18483 int fill_style; /* FillSolid, FillTiled, 18484 FillStippled, FillOpaeueStippled */ 18485 int fill_rule; /* EvenOddRule, WindingRule */ 18486 int arc_mode; /* ArcChord, ArcPieSlice */ 18487 Pixmap tile; /* tile pixmap for tiling operations */ 18488 Pixmap stipple; /* stipple 1 plane pixmap for stipping */ 18489 int ts_x_origin; /* offset for tile or stipple operations */ 18490 int ts_y_origin; 18491 Font font; /* default text font for text operations */ 18492 int subwindow_mode; /* ClipByChildren, IncludeInferiors */ 18493 Bool graphics_exposures;/* boolean, should exposures be generated */ 18494 int clip_x_origin; /* origin for clipping */ 18495 int clip_y_origin; 18496 Pixmap clip_mask; /* bitmap clipping; other calls for rects */ 18497 int dash_offset; /* patterned/dashed line information */ 18498 char dashes; 18499 } 18500 18501 struct XColor { 18502 arch_ulong pixel; 18503 ushort red, green, blue; 18504 byte flags; 18505 byte pad; 18506 } 18507 18508 struct XRectangle { 18509 short x; 18510 short y; 18511 ushort width; 18512 ushort height; 18513 } 18514 18515 enum ClipByChildren = 0; 18516 enum IncludeInferiors = 1; 18517 18518 enum Atom XA_PRIMARY = 1; 18519 enum Atom XA_SECONDARY = 2; 18520 enum Atom XA_STRING = 31; 18521 enum Atom XA_CARDINAL = 6; 18522 enum Atom XA_WM_NAME = 39; 18523 enum Atom XA_ATOM = 4; 18524 enum Atom XA_WINDOW = 33; 18525 enum Atom XA_WM_HINTS = 35; 18526 enum int PropModeAppend = 2; 18527 enum int PropModeReplace = 0; 18528 enum int PropModePrepend = 1; 18529 18530 enum int CopyFromParent = 0; 18531 enum int InputOutput = 1; 18532 18533 // XWMHints 18534 enum InputHint = 1 << 0; 18535 enum StateHint = 1 << 1; 18536 enum IconPixmapHint = (1L << 2); 18537 enum IconWindowHint = (1L << 3); 18538 enum IconPositionHint = (1L << 4); 18539 enum IconMaskHint = (1L << 5); 18540 enum WindowGroupHint = (1L << 6); 18541 enum AllHints = (InputHint|StateHint|IconPixmapHint|IconWindowHint|IconPositionHint|IconMaskHint|WindowGroupHint); 18542 enum XUrgencyHint = (1L << 8); 18543 18544 // GC Components 18545 enum GCFunction = (1L<<0); 18546 enum GCPlaneMask = (1L<<1); 18547 enum GCForeground = (1L<<2); 18548 enum GCBackground = (1L<<3); 18549 enum GCLineWidth = (1L<<4); 18550 enum GCLineStyle = (1L<<5); 18551 enum GCCapStyle = (1L<<6); 18552 enum GCJoinStyle = (1L<<7); 18553 enum GCFillStyle = (1L<<8); 18554 enum GCFillRule = (1L<<9); 18555 enum GCTile = (1L<<10); 18556 enum GCStipple = (1L<<11); 18557 enum GCTileStipXOrigin = (1L<<12); 18558 enum GCTileStipYOrigin = (1L<<13); 18559 enum GCFont = (1L<<14); 18560 enum GCSubwindowMode = (1L<<15); 18561 enum GCGraphicsExposures= (1L<<16); 18562 enum GCClipXOrigin = (1L<<17); 18563 enum GCClipYOrigin = (1L<<18); 18564 enum GCClipMask = (1L<<19); 18565 enum GCDashOffset = (1L<<20); 18566 enum GCDashList = (1L<<21); 18567 enum GCArcMode = (1L<<22); 18568 enum GCLastBit = 22; 18569 18570 18571 enum int WithdrawnState = 0; 18572 enum int NormalState = 1; 18573 enum int IconicState = 3; 18574 18575 } 18576 } else version (OSXCocoa) { 18577 18578 /+ 18579 DON'T FORGET TO MARK THE CLASSES `extern`!! can cause "unrecognized selector sent to class" errors if you do. 18580 +/ 18581 18582 private __gshared AppDelegate globalAppDelegate; 18583 18584 extern(Objective-C) 18585 class AppDelegate : NSObject, NSApplicationDelegate { 18586 override static AppDelegate alloc() @selector("alloc"); 18587 18588 18589 void sdpyCustomEventWakeup(NSid arg) @selector("sdpyCustomEventWakeup:") { 18590 SimpleWindow.processAllCustomEvents(); 18591 } 18592 18593 override void applicationWillFinishLaunching(NSNotification notification) @selector("applicationWillFinishLaunching:") { 18594 immutable style = NSWindowStyleMask.resizable | 18595 NSWindowStyleMask.closable | 18596 NSWindowStyleMask.miniaturizable | 18597 NSWindowStyleMask.titled; 18598 18599 NSMenu mainMenu = NSMenu.alloc.init(MacString("Main").borrow); 18600 18601 { 18602 auto item = mainMenu.addItem(MacString("Test").borrow, null, MacString("").borrow); 18603 auto menu = NSMenu.alloc.init(MacString("Test2").borrow); 18604 mainMenu.setSubmenu(menu, item); 18605 18606 auto newItem = menu.addItem(MacString("Quit").borrow, sel_registerName("terminate:"), MacString("q").borrow); 18607 newItem.target = NSApp; 18608 auto newItem2 = menu.addItem(MacString("Disabled").borrow, sel_registerName("doesnotexist:"), MacString("x").borrow); 18609 newItem2.target = NSApp; 18610 } 18611 18612 { 18613 auto item = mainMenu.addItem(MacString("Test3").borrow, null, MacString("").borrow); 18614 auto menu = NSMenu.alloc.init(MacString("Test4").borrow); // this is the title actually used 18615 mainMenu.setSubmenu(menu, item); 18616 18617 auto newItem = menu.addItem(MacString("Quit2").borrow, sel_registerName("stop:"), MacString("s").borrow); 18618 menu.addItem(MacString("Pulse").borrow, sel_registerName("simpledisplay_pulse:"), MacString("p").borrow); 18619 } 18620 18621 18622 NSApp.menu = mainMenu; 18623 18624 18625 // auto controller = ViewController.alloc.init; 18626 18627 // auto timer = NSTimer.schedule(1.0, cast(NSid) view, sel_registerName("simpledisplay_pulse:"), null, true); 18628 18629 /+ 18630 this.window = window; 18631 this.controller = controller; 18632 +/ 18633 } 18634 18635 override void applicationDidFinishLaunching(NSNotification notification) @selector("applicationDidFinishLaunching:") { 18636 NSApplication.shared_.activateIgnoringOtherApps(true); 18637 } 18638 override bool applicationShouldTerminateAfterLastWindowClosed(NSNotification notification) @selector("applicationShouldTerminateAfterLastWindowClosed:") { 18639 return true; 18640 } 18641 } 18642 18643 extern(Objective-C) 18644 class SDWindowDelegate : NSObject, NSWindowDelegate { 18645 override static SDWindowDelegate alloc() @selector("alloc"); 18646 override SDWindowDelegate init() @selector("init"); 18647 18648 SimpleWindow simpleWindow; 18649 18650 override void windowWillClose(NSNotification notification) @selector("windowWillClose:") { 18651 auto window = cast(void*) notification.object; 18652 18653 // FIXME: do i need to release it? 18654 SimpleWindow.nativeMapping.remove(window); 18655 } 18656 18657 override NSSize windowWillResize(NSWindow sender, NSSize frameSize) @selector("windowWillResize:toSize:") { 18658 if(simpleWindow.windowResized) { 18659 // FIXME: automaticallyScaleIfPossible behaviors 18660 18661 simpleWindow._width = cast(int) frameSize.width; 18662 simpleWindow._height = cast(int) frameSize.height; 18663 18664 simpleWindow.view.setFrameSize(frameSize); 18665 18666 /+ 18667 auto size = simpleWindow.view.frame.size; 18668 writeln(cast(int) size.width, "x", cast(int) size.height); 18669 +/ 18670 18671 simpleWindow.createNewDrawingContext(simpleWindow._width, simpleWindow._height); 18672 18673 simpleWindow.windowResized(simpleWindow._width, simpleWindow._height); 18674 18675 // simpleWindow.view.setNeedsDisplay(true); 18676 } 18677 18678 return frameSize; 18679 } 18680 18681 /+ 18682 override void windowDidResize(NSNotification notification) @selector("windowDidResize:") { 18683 if(simpleWindow.windowResized) { 18684 auto window = simpleWindow.window; 18685 auto rect = window.contentRectForFrameRect(window.frame); 18686 import std.stdio; writeln(window.frame.size); 18687 simpleWindow.windowResized(cast(int) rect.size.width, cast(int) rect.size.height); 18688 } 18689 } 18690 +/ 18691 } 18692 18693 extern(Objective-C) 18694 class SDGraphicsView : NSView { 18695 SimpleWindow simpleWindow; 18696 18697 override static SDGraphicsView alloc() @selector("alloc"); 18698 override SDGraphicsView init() @selector("init");/* { 18699 super.init(); 18700 return this; 18701 }*/ 18702 18703 override void drawRect(NSRect rect) @selector("drawRect:") { 18704 auto curCtx = NSGraphicsContext.currentContext.graphicsPort; 18705 auto cgImage = CGBitmapContextCreateImage(simpleWindow.drawingContext); 18706 auto size = CGSize(CGBitmapContextGetWidth(simpleWindow.drawingContext), CGBitmapContextGetHeight(simpleWindow.drawingContext)); 18707 CGContextDrawImage(curCtx, CGRect(CGPoint(0, 0), size), cgImage); 18708 CGImageRelease(cgImage); 18709 } 18710 18711 extern(D) 18712 private void mouseHelper(NSEvent event, MouseEventType type, MouseButton button) { 18713 MouseEvent me; 18714 me.type = type; 18715 18716 auto pos = event.locationInWindow; 18717 18718 me.x = cast(int) pos.x; 18719 me.y = cast(int) (simpleWindow.height - pos.y); 18720 18721 me.dx = 0; // FIXME 18722 me.dy = 0; // FIXME 18723 18724 me.button = button; 18725 me.modifierState = cast(uint) event.modifierFlags; 18726 me.window = simpleWindow; 18727 18728 me.doubleClick = false; 18729 18730 if(simpleWindow && simpleWindow.handleMouseEvent) 18731 simpleWindow.handleMouseEvent(me); 18732 } 18733 18734 override void mouseDown(NSEvent event) @selector("mouseDown:") { 18735 // writeln(event.pressedMouseButtons); 18736 18737 mouseHelper(event, MouseEventType.buttonPressed, MouseButton.left); 18738 } 18739 override void mouseDragged(NSEvent event) @selector("mouseDragged:") { 18740 mouseHelper(event, MouseEventType.motion, MouseButton.left); 18741 } 18742 override void mouseUp(NSEvent event) @selector("mouseUp:") { 18743 mouseHelper(event, MouseEventType.buttonReleased, MouseButton.left); 18744 } 18745 override void mouseMoved(NSEvent event) @selector("mouseMoved:") { 18746 mouseHelper(event, MouseEventType.motion, MouseButton.left); // button wrong prolly 18747 } 18748 /+ 18749 // FIXME 18750 override void mouseEntered(NSEvent event) @selector("mouseEntered:") { 18751 mouseHelper(event, MouseEventType.buttonPressed, MouseButton.left); 18752 } 18753 override void mouseExited(NSEvent event) @selector("mouseExited:") { 18754 mouseHelper(event, MouseEventType.buttonPressed, MouseButton.left); 18755 } 18756 +/ 18757 18758 override void rightMouseDown(NSEvent event) @selector("rightMouseDown:") { 18759 mouseHelper(event, MouseEventType.buttonPressed, MouseButton.right); 18760 } 18761 override void rightMouseDragged(NSEvent event) @selector("rightMouseDragged:") { 18762 mouseHelper(event, MouseEventType.motion, MouseButton.right); 18763 } 18764 override void rightMouseUp(NSEvent event) @selector("rightMouseUp:") { 18765 mouseHelper(event, MouseEventType.buttonReleased, MouseButton.right); 18766 } 18767 18768 override void otherMouseDown(NSEvent event) @selector("otherMouseDown:") { 18769 mouseHelper(event, MouseEventType.buttonPressed, MouseButton.middle); 18770 } 18771 override void otherMouseDragged(NSEvent event) @selector("otherMouseDragged:") { 18772 mouseHelper(event, MouseEventType.motion, MouseButton.middle); 18773 } 18774 override void otherMouseUp(NSEvent event) @selector("otherMouseUp:") { 18775 mouseHelper(event, MouseEventType.buttonReleased, MouseButton.middle); 18776 } 18777 18778 override void scrollWheel(NSEvent event) @selector("scrollWheel:") { 18779 // import std.stdio; writeln(event.deltaY); 18780 } 18781 18782 override void keyDown(NSEvent event) @selector("keyDown:") { 18783 // the event may have multiple characters, and we send them all at once. 18784 if (simpleWindow.handleCharEvent) { 18785 auto chars = DeifiedNSString(event.characters); 18786 foreach (dchar dc; chars.str) 18787 simpleWindow.handleCharEvent(dc); 18788 } 18789 18790 keyHelper(event, true); 18791 } 18792 18793 override void keyUp(NSEvent event) @selector("keyUp:") { 18794 keyHelper(event, false); 18795 } 18796 18797 extern(D) 18798 private void keyHelper(NSEvent event, bool pressed) { 18799 if(simpleWindow.handleKeyEvent) { 18800 KeyEvent ev; 18801 ev.key = cast(Key) event.keyCode;// (event.specialKey ? event.specialKey : event.keyCode); 18802 ev.pressed = pressed; 18803 ev.hardwareCode = cast(ubyte) event.keyCode; 18804 ev.modifierState = cast(uint) event.modifierFlags; 18805 ev.window = simpleWindow; 18806 18807 simpleWindow.handleKeyEvent(ev); 18808 } 18809 } 18810 18811 override bool isFlipped() @selector("isFlipped") { 18812 return true; 18813 } 18814 override bool acceptsFirstResponder() @selector("acceptsFirstResponder") { 18815 return true; 18816 } 18817 18818 void simpledisplay_pulse(NSTimer timer) @selector("simpledisplay_pulse:") { 18819 if(simpleWindow && simpleWindow.handlePulse) 18820 simpleWindow.handlePulse(); 18821 /+ 18822 setNeedsDisplay = true; 18823 +/ 18824 } 18825 } 18826 18827 private: 18828 alias const(void)* CFStringRef; 18829 alias const(void)* CFAllocatorRef; 18830 alias const(void)* CFTypeRef; 18831 alias const(void)* CGColorSpaceRef; 18832 alias const(void)* CGImageRef; 18833 alias ulong CGBitmapInfo; 18834 alias NSGraphicsContext CGContextRef; 18835 18836 alias NSPoint CGPoint; 18837 alias NSSize CGSize; 18838 alias NSRect CGRect; 18839 18840 struct CGAffineTransform { 18841 double a, b, c, d, tx, ty; 18842 } 18843 18844 enum NSApplicationActivationPolicyRegular = 0; 18845 enum NSBackingStoreBuffered = 2; 18846 enum kCFStringEncodingUTF8 = 0x08000100; 18847 18848 enum : size_t { 18849 NSBorderlessWindowMask = 0, 18850 NSTitledWindowMask = 1 << 0, 18851 NSClosableWindowMask = 1 << 1, 18852 NSMiniaturizableWindowMask = 1 << 2, 18853 NSResizableWindowMask = 1 << 3, 18854 NSTexturedBackgroundWindowMask = 1 << 8 18855 } 18856 18857 enum : ulong { 18858 kCGImageAlphaNone, 18859 kCGImageAlphaPremultipliedLast, 18860 kCGImageAlphaPremultipliedFirst, 18861 kCGImageAlphaLast, 18862 kCGImageAlphaFirst, 18863 kCGImageAlphaNoneSkipLast, 18864 kCGImageAlphaNoneSkipFirst 18865 } 18866 enum : ulong { 18867 kCGBitmapAlphaInfoMask = 0x1F, 18868 kCGBitmapFloatComponents = (1 << 8), 18869 kCGBitmapByteOrderMask = 0x7000, 18870 kCGBitmapByteOrderDefault = (0 << 12), 18871 kCGBitmapByteOrder16Little = (1 << 12), 18872 kCGBitmapByteOrder32Little = (2 << 12), 18873 kCGBitmapByteOrder16Big = (3 << 12), 18874 kCGBitmapByteOrder32Big = (4 << 12) 18875 } 18876 enum CGPathDrawingMode { 18877 kCGPathFill, 18878 kCGPathEOFill, 18879 kCGPathStroke, 18880 kCGPathFillStroke, 18881 kCGPathEOFillStroke 18882 } 18883 enum objc_AssociationPolicy : size_t { 18884 OBJC_ASSOCIATION_ASSIGN = 0, 18885 OBJC_ASSOCIATION_RETAIN_NONATOMIC = 1, 18886 OBJC_ASSOCIATION_COPY_NONATOMIC = 3, 18887 OBJC_ASSOCIATION_RETAIN = 0x301, //01401, 18888 OBJC_ASSOCIATION_COPY = 0x303 //01403 18889 } 18890 18891 extern(C) { 18892 CGContextRef CGBitmapContextCreate(void* data, size_t width, size_t height, size_t bitsPerComponent, size_t bytesPerRow, CGColorSpaceRef colorspace, CGBitmapInfo bitmapInfo); 18893 void CGContextRelease(CGContextRef c); 18894 ubyte* CGBitmapContextGetData(CGContextRef c); 18895 CGImageRef CGBitmapContextCreateImage(CGContextRef c); 18896 size_t CGBitmapContextGetWidth(CGContextRef c); 18897 size_t CGBitmapContextGetHeight(CGContextRef c); 18898 18899 CGColorSpaceRef CGColorSpaceCreateDeviceRGB(); 18900 void CGColorSpaceRelease(CGColorSpaceRef cs); 18901 18902 void CGContextSetRGBStrokeColor(CGContextRef c, double red, double green, double blue, double alpha); 18903 void CGContextSetRGBFillColor(CGContextRef c, double red, double green, double blue, double alpha); 18904 void CGContextDrawImage(CGContextRef c, CGRect rect, CGImageRef image); 18905 void CGContextShowTextAtPoint(CGContextRef c, double x, double y, const(char)* str, size_t length); 18906 void CGContextStrokeLineSegments(CGContextRef c, const(CGPoint)* points, size_t count); 18907 void CGContextSetLineDash(CGContextRef c, CGFloat phase, const CGFloat *lengths, size_t count); 18908 18909 void CGContextBeginPath(CGContextRef c); 18910 void CGContextDrawPath(CGContextRef c, CGPathDrawingMode mode); 18911 void CGContextAddEllipseInRect(CGContextRef c, CGRect rect); 18912 void CGContextAddArc(CGContextRef c, double x, double y, double radius, double startAngle, double endAngle, long clockwise); 18913 void CGContextAddRect(CGContextRef c, CGRect rect); 18914 void CGContextAddLines(CGContextRef c, const(CGPoint)* points, size_t count); 18915 void CGContextSaveGState(CGContextRef c); 18916 void CGContextRestoreGState(CGContextRef c); 18917 void CGContextSelectFont(CGContextRef c, const(char)* name, double size, ulong textEncoding); 18918 CGAffineTransform CGContextGetTextMatrix(CGContextRef c); 18919 void CGContextSetTextMatrix(CGContextRef c, CGAffineTransform t); 18920 18921 void CGImageRelease(CGImageRef image); 18922 } 18923 } else static assert(0, "Unsupported operating system"); 18924 18925 18926 version(OSXCocoa) { 18927 // I don't know anything about the Mac, but a couple years ago, KennyTM on the newsgroup wrote this for me 18928 // 18929 // http://forum.dlang.org/thread/innr0v$1deh$1@digitalmars.com?page=4#post-int88l:24uaf:241:40digitalmars.com 18930 // https://github.com/kennytm/simpledisplay.d/blob/osx/simpledisplay.d 18931 // 18932 // and it is about time I merged it in here. It is available with -version=OSXCocoa until someone tests it for me! 18933 // Probably won't even fully compile right now 18934 18935 private enum double PI = 3.14159265358979323; 18936 18937 alias NSWindow NativeWindowHandle; 18938 alias void delegate(NSid) NativeEventHandler; 18939 18940 enum KEY_ESCAPE = 27; 18941 18942 mixin template NativeImageImplementation() { 18943 CGContextRef context; 18944 ubyte* rawData; 18945 18946 final: 18947 18948 void convertToRgbaBytes(ubyte[] where) @system { 18949 assert(where.length == this.width * this.height * 4); 18950 18951 // if rawData had a length.... 18952 //assert(rawData.length == where.length); 18953 for(long idx = 0; idx < where.length; idx += 4) { 18954 auto alpha = rawData[idx + 3]; 18955 if(alpha == 255) { 18956 where[idx + 0] = rawData[idx + 0]; // r 18957 where[idx + 1] = rawData[idx + 1]; // g 18958 where[idx + 2] = rawData[idx + 2]; // b 18959 where[idx + 3] = rawData[idx + 3]; // a 18960 } else { 18961 where[idx + 0] = cast(ubyte)(rawData[idx + 0] * 255 / alpha); // r 18962 where[idx + 1] = cast(ubyte)(rawData[idx + 1] * 255 / alpha); // g 18963 where[idx + 2] = cast(ubyte)(rawData[idx + 2] * 255 / alpha); // b 18964 where[idx + 3] = rawData[idx + 3]; // a 18965 18966 } 18967 } 18968 } 18969 18970 void setFromRgbaBytes(in ubyte[] where) @system { 18971 // FIXME: this is probably wrong 18972 assert(where.length == this.width * this.height * 4); 18973 18974 // if rawData had a length.... 18975 //assert(rawData.length == where.length); 18976 for(long idx = 0; idx < where.length; idx += 4) { 18977 auto alpha = where[idx + 3]; 18978 if(alpha == 255) { 18979 rawData[idx + 0] = where[idx + 0]; // r 18980 rawData[idx + 1] = where[idx + 1]; // g 18981 rawData[idx + 2] = where[idx + 2]; // b 18982 rawData[idx + 3] = where[idx + 3]; // a 18983 } else if(alpha == 0) { 18984 rawData[idx + 0] = 0; 18985 rawData[idx + 1] = 0; 18986 rawData[idx + 2] = 0; 18987 rawData[idx + 3] = 0; 18988 } else { 18989 rawData[idx + 0] = cast(ubyte)(where[idx + 0] * 255 / alpha); // r 18990 rawData[idx + 1] = cast(ubyte)(where[idx + 1] * 255 / alpha); // g 18991 rawData[idx + 2] = cast(ubyte)(where[idx + 2] * 255 / alpha); // b 18992 rawData[idx + 3] = where[idx + 3]; // a 18993 } 18994 } 18995 } 18996 18997 18998 void createImage(int width, int height, bool forcexshm=false, bool ignored = false) { 18999 auto colorSpace = CGColorSpaceCreateDeviceRGB(); 19000 context = CGBitmapContextCreate(null, width, height, 8, 4*width, colorSpace, kCGImageAlphaPremultipliedLast |kCGBitmapByteOrder32Big); 19001 CGColorSpaceRelease(colorSpace); 19002 rawData = CGBitmapContextGetData(context); 19003 } 19004 void dispose() { 19005 CGContextRelease(context); 19006 } 19007 19008 void setPixel(int x, int y, Color c) @system { 19009 auto offset = (y * width + x) * 4; 19010 if (c.a == 255) { 19011 rawData[offset + 0] = c.r; 19012 rawData[offset + 1] = c.g; 19013 rawData[offset + 2] = c.b; 19014 rawData[offset + 3] = c.a; 19015 } else { 19016 rawData[offset + 0] = cast(ubyte)(c.r*c.a/255); 19017 rawData[offset + 1] = cast(ubyte)(c.g*c.a/255); 19018 rawData[offset + 2] = cast(ubyte)(c.b*c.a/255); 19019 rawData[offset + 3] = c.a; 19020 } 19021 } 19022 } 19023 19024 mixin template NativeScreenPainterImplementation() { 19025 CGContextRef context; 19026 ubyte[4] _outlineComponents; 19027 NSView view; 19028 19029 Pen _activePen; 19030 Color _fillColor; 19031 Rectangle _clipRectangle; 19032 OperatingSystemFont _font; 19033 19034 OperatingSystemFont getFont() { 19035 if(_font is null) { 19036 static OperatingSystemFont _defaultFont; 19037 if(_defaultFont is null) { 19038 _defaultFont = new OperatingSystemFont(); 19039 _defaultFont.loadDefault(); 19040 } 19041 _font = _defaultFont; 19042 } 19043 19044 return _font; 19045 } 19046 19047 void create(PaintingHandle window) { 19048 // this.destiny = window; 19049 if(auto sw = cast(SimpleWindow) this.window) { 19050 context = sw.drawingContext; 19051 view = sw.view; 19052 } else { 19053 throw new NotYetImplementedException(); 19054 } 19055 } 19056 19057 void dispose() { 19058 view.setNeedsDisplay(true); 19059 } 19060 19061 bool manualInvalidations; 19062 void invalidateRect(Rectangle invalidRect) { } 19063 19064 // NotYetImplementedException 19065 void rasterOp(RasterOp op) { 19066 } 19067 void setClipRectangle(int, int, int, int) { 19068 } 19069 Size textSize(in char[] txt) { 19070 auto font = getFont(); 19071 return Size(font.stringWidth(txt), font.height()); 19072 } 19073 19074 void setFont(OperatingSystemFont font) { 19075 _font = font; 19076 //font.font.setInContext(context); 19077 } 19078 int fontHeight() { 19079 auto font = getFont(); 19080 return font.height; 19081 } 19082 19083 // end 19084 19085 void pen(Pen pen) { 19086 _activePen = pen; 19087 auto color = pen.color; // FIXME 19088 double alphaComponent = color.a/255.0f; 19089 CGContextSetRGBStrokeColor(context, color.r/255.0f, color.g/255.0f, color.b/255.0f, alphaComponent); 19090 19091 double[2] patternBuffer; 19092 double[] pattern; 19093 final switch(pen.style) { 19094 case Pen.Style.Solid: 19095 pattern = null; 19096 break; 19097 case Pen.Style.Dashed: 19098 patternBuffer[0] = 4; 19099 patternBuffer[1] = 1; 19100 pattern = patternBuffer[]; 19101 break; 19102 case Pen.Style.Dotted: 19103 patternBuffer[0] = 1; 19104 patternBuffer[1] = 1; 19105 pattern = patternBuffer[]; 19106 break; 19107 } 19108 19109 CGContextSetLineDash(context, 0, pattern.ptr, pattern.length); 19110 19111 if (color.a != 255) { 19112 _outlineComponents[0] = cast(ubyte)(color.r*color.a/255); 19113 _outlineComponents[1] = cast(ubyte)(color.g*color.a/255); 19114 _outlineComponents[2] = cast(ubyte)(color.b*color.a/255); 19115 _outlineComponents[3] = color.a; 19116 } else { 19117 _outlineComponents[0] = color.r; 19118 _outlineComponents[1] = color.g; 19119 _outlineComponents[2] = color.b; 19120 _outlineComponents[3] = color.a; 19121 } 19122 } 19123 19124 @property void fillColor(Color color) { 19125 CGContextSetRGBFillColor(context, color.r/255.0f, color.g/255.0f, color.b/255.0f, color.a/255.0f); 19126 } 19127 19128 void drawImage(int x, int y, Image image, int ulx, int upy, int width, int height) { 19129 // NotYetImplementedException for upper left/width/height 19130 auto cgImage = CGBitmapContextCreateImage(image.context); 19131 auto size = CGSize(CGBitmapContextGetWidth(image.context), CGBitmapContextGetHeight(image.context)); 19132 CGContextDrawImage(context, CGRect(CGPoint(x, y), size), cgImage); 19133 CGImageRelease(cgImage); 19134 } 19135 19136 void drawPixmap(Sprite s, int x, int y, int ix, int iy, int w, int h) { 19137 // FIXME: is this efficient? 19138 auto cgImage = CGBitmapContextCreateImage(s.handle); 19139 auto size = CGSize(CGBitmapContextGetWidth(s.handle), CGBitmapContextGetHeight(s.handle)); 19140 CGContextDrawImage(context, CGRect(CGPoint(x, y), size), cgImage); 19141 CGImageRelease(cgImage); 19142 } 19143 19144 19145 void drawText(int x, int y, int x2, int y2, in char[] text, uint alignment) { 19146 // FIXME: alignment 19147 if (_outlineComponents[3] != 0) { 19148 CGContextSaveGState(context); 19149 auto invAlpha = 1.0f/_outlineComponents[3]; 19150 CGContextSetRGBFillColor(context, _outlineComponents[0]*invAlpha, 19151 _outlineComponents[1]*invAlpha, 19152 _outlineComponents[2]*invAlpha, 19153 _outlineComponents[3]/255.0f); 19154 19155 19156 19157 // FIXME: should we clip it to the bounding box? 19158 int textHeight = fontHeight; 19159 19160 auto lines = text.split('\n'); 19161 19162 const lineHeight = textHeight; 19163 textHeight *= lines.length; 19164 19165 int cy = y; 19166 19167 if(alignment & TextAlignment.VerticalBottom) { 19168 if(y2 <= 0) 19169 return; 19170 auto h = y2 - y; 19171 if(h > textHeight) { 19172 cy += h - textHeight; 19173 cy -= lineHeight / 2; 19174 } 19175 } else if(alignment & TextAlignment.VerticalCenter) { 19176 if(y2 <= 0) 19177 return; 19178 auto h = y2 - y; 19179 if(textHeight < h) { 19180 cy += (h - textHeight) / 2; 19181 //cy -= lineHeight / 4; 19182 } 19183 } 19184 19185 foreach(line; text.split('\n')) { 19186 int textWidth = this.textSize(line).width; 19187 19188 int px = x, py = cy; 19189 19190 if(alignment & TextAlignment.Center) { 19191 if(x2 <= 0) 19192 return; 19193 auto w = x2 - x; 19194 if(w > textWidth) 19195 px += (w - textWidth) / 2; 19196 } else if(alignment & TextAlignment.Right) { 19197 if(x2 <= 0) 19198 return; 19199 auto pos = x2 - textWidth; 19200 if(pos > x) 19201 px = pos; 19202 } 19203 19204 CGContextShowTextAtPoint(context, px, py + getFont.ascent /* this is cuz this picks baseline but i want bounding box */, line.ptr, line.length); 19205 19206 carry_on: 19207 cy += lineHeight + 4; 19208 } 19209 19210 // auto cfstr = cast(NSid)createCFString(text); 19211 // objc_msgSend(cfstr, sel_registerName("drawAtPoint:withAttributes:"), 19212 // NSPoint(x, y), null); 19213 // CFRelease(cfstr); 19214 CGContextRestoreGState(context); 19215 } 19216 } 19217 19218 void drawPixel(int x, int y) { 19219 auto rawData = CGBitmapContextGetData(context); 19220 auto width = CGBitmapContextGetWidth(context); 19221 auto height = CGBitmapContextGetHeight(context); 19222 auto offset = ((height - y - 1) * width + x) * 4; 19223 rawData[offset .. offset+4] = _outlineComponents; 19224 } 19225 19226 void drawLine(int x1, int y1, int x2, int y2) { 19227 CGPoint[2] linePoints; 19228 linePoints[0] = CGPoint(x1, y1); 19229 linePoints[1] = CGPoint(x2, y2); 19230 CGContextStrokeLineSegments(context, linePoints.ptr, linePoints.length); 19231 } 19232 19233 void drawRectangleRounded(Point upperLeft, Point lowerRight, int borderRadius) { 19234 drawRectangle(upperLeft.x, upperLeft.y, lowerRight.x - upperLeft.x, lowerRight.y - upperLeft.y); // FIXME not rounded 19235 } 19236 19237 void drawRectangle(int x, int y, int width, int height) { 19238 CGContextBeginPath(context); 19239 auto rect = CGRect(CGPoint(x, y), CGSize(width, height)); 19240 CGContextAddRect(context, rect); 19241 CGContextDrawPath(context, CGPathDrawingMode.kCGPathFillStroke); 19242 } 19243 19244 void drawEllipse(int x1, int y1, int x2, int y2) { 19245 CGContextBeginPath(context); 19246 auto rect = CGRect(CGPoint(x1, y1), CGSize(x2-x1, y2-y1)); 19247 CGContextAddEllipseInRect(context, rect); 19248 CGContextDrawPath(context, CGPathDrawingMode.kCGPathFillStroke); 19249 } 19250 19251 void drawArc(int x1, int y1, int width, int height, int start, int length) { 19252 // @@@BUG@@@ Does not support elliptic arc (width != height). 19253 CGContextBeginPath(context); 19254 int clockwise = 0; 19255 if(length < 0) { 19256 clockwise = 1; 19257 length = -length; 19258 } 19259 CGContextAddArc(context, x1+width*0.5f, y1+height*0.5f, width, 19260 start*PI/(180*64), (start+length)*PI/(180*64), clockwise); 19261 CGContextDrawPath(context, CGPathDrawingMode.kCGPathFillStroke); 19262 } 19263 19264 void drawPolygon(Point[] intPoints) { 19265 CGContextBeginPath(context); 19266 CGPoint[16] pointsBuffer; 19267 CGPoint[] points; 19268 if(intPoints.length <= pointsBuffer.length) 19269 points = pointsBuffer[0 .. intPoints.length]; 19270 else 19271 points = new CGPoint[](intPoints.length); 19272 19273 foreach(idx, pt; intPoints) 19274 points[idx] = CGPoint(pt.x, pt.y); 19275 19276 CGContextAddLines(context, points.ptr, points.length); 19277 CGContextDrawPath(context, CGPathDrawingMode.kCGPathFillStroke); 19278 } 19279 } 19280 19281 private bool appInitialized = false; 19282 void initializeApp() { 19283 if(appInitialized) 19284 return; 19285 synchronized { 19286 if(appInitialized) 19287 return; 19288 19289 auto app = NSApp(); // ensure the is initialized 19290 19291 auto dg = AppDelegate.alloc; 19292 globalAppDelegate = dg; 19293 NSApp.delegate_ = dg; 19294 19295 NSApp.setActivationPolicy(NSApplicationActivationPolicy.regular); 19296 19297 appInitialized = true; 19298 } 19299 } 19300 19301 mixin template NativeSimpleWindowImplementation() { 19302 void createWindow(int width, int height, string title, OpenGlOptions opengl, SimpleWindow parent) { 19303 initializeApp(); 19304 19305 auto contentRect = NSRect(NSPoint(0, 0), NSSize(width, height)); 19306 19307 auto window = NSWindow.alloc.initWithContentRect( 19308 contentRect, 19309 NSWindowStyleMask.resizable | NSWindowStyleMask.closable | NSWindowStyleMask.miniaturizable | NSWindowStyleMask.titled, 19310 NSBackingStoreType.buffered, 19311 true 19312 ); 19313 19314 SimpleWindow.nativeMapping[cast(void*) window] = this; 19315 19316 window.title = MacString(title).borrow; 19317 19318 auto dg = SDWindowDelegate.alloc.init; 19319 dg.simpleWindow = this; 19320 window.delegate_ = dg; 19321 19322 auto view = SDGraphicsView.alloc.init; 19323 assert(view !is null); 19324 window.contentView = view; 19325 this.view = view; 19326 view.simpleWindow = this; 19327 19328 window.center(); 19329 19330 window.makeKeyAndOrderFront(null); 19331 19332 // no need to make a bitmap on mac since everything is double buffered already 19333 19334 // create area to draw on. 19335 createNewDrawingContext(width, height); 19336 19337 window.setBackgroundColor(NSColor.whiteColor); 19338 } 19339 19340 void createNewDrawingContext(int width, int height) { 19341 // FIXME need to preserve info from the old context too i think... maybe. or at least setNeedsDisplay 19342 if(this.drawingContext) 19343 CGContextRelease(this.drawingContext); 19344 auto colorSpace = CGColorSpaceCreateDeviceRGB(); 19345 this.drawingContext = CGBitmapContextCreate(null, width, height, 8, 4*width, colorSpace, kCGImageAlphaPremultipliedLast |kCGBitmapByteOrder32Big); 19346 CGColorSpaceRelease(colorSpace); 19347 CGContextSelectFont(drawingContext, "Lucida Grande", 12.0f, 1); 19348 auto matrix = CGContextGetTextMatrix(drawingContext); 19349 matrix.c = -matrix.c; 19350 matrix.d = -matrix.d; 19351 CGContextSetTextMatrix(drawingContext, matrix); 19352 19353 } 19354 19355 void dispose() { 19356 closeWindow(); 19357 // window.release(); // closing the window does this automatically i think 19358 } 19359 void closeWindow() { 19360 if(timer) 19361 timer.invalidate(); 19362 window.close(); 19363 } 19364 19365 ScreenPainter getPainter(bool manualInvalidations) { 19366 return ScreenPainter(this, this.window, manualInvalidations); 19367 } 19368 19369 NSWindow window; 19370 NSTimer timer; 19371 NSView view; 19372 CGContextRef drawingContext; 19373 } 19374 } 19375 19376 version(without_opengl) {} else 19377 extern(System) nothrow @nogc { 19378 //enum uint GL_VERSION = 0x1F02; 19379 //const(char)* glGetString (/*GLenum*/uint); 19380 version(X11) { 19381 static if (!SdpyIsUsingIVGLBinds) { 19382 19383 enum GLX_X_RENDERABLE = 0x8012; 19384 enum GLX_DRAWABLE_TYPE = 0x8010; 19385 enum GLX_RENDER_TYPE = 0x8011; 19386 enum GLX_X_VISUAL_TYPE = 0x22; 19387 enum GLX_TRUE_COLOR = 0x8002; 19388 enum GLX_WINDOW_BIT = 0x00000001; 19389 enum GLX_RGBA_BIT = 0x00000001; 19390 enum GLX_COLOR_INDEX_BIT = 0x00000002; 19391 enum GLX_SAMPLE_BUFFERS = 0x186a0; 19392 enum GLX_SAMPLES = 0x186a1; 19393 enum GLX_CONTEXT_MAJOR_VERSION_ARB = 0x2091; 19394 enum GLX_CONTEXT_MINOR_VERSION_ARB = 0x2092; 19395 } 19396 19397 // GLX_EXT_swap_control 19398 alias glXSwapIntervalEXT = void function (Display* dpy, /*GLXDrawable*/Drawable drawable, int interval); 19399 private __gshared glXSwapIntervalEXT _glx_swapInterval_fn = null; 19400 19401 //k8: ugly code to prevent warnings when sdpy is compiled into .a 19402 extern(System) { 19403 alias glXCreateContextAttribsARB_fna = GLXContext function (Display *dpy, GLXFBConfig config, GLXContext share_context, /*Bool*/int direct, const(int)* attrib_list); 19404 } 19405 private __gshared /*glXCreateContextAttribsARB_fna*/void* glXCreateContextAttribsARBFn = cast(void*)1; //HACK! 19406 19407 // this made public so we don't have to get it again and again 19408 public bool glXCreateContextAttribsARB_present () @system { 19409 if (glXCreateContextAttribsARBFn is cast(void*)1) { 19410 // get it 19411 glXCreateContextAttribsARBFn = cast(void*)glbindGetProcAddress("glXCreateContextAttribsARB"); 19412 //{ import core.stdc.stdio; printf("checking glXCreateContextAttribsARB: %shere\n", (glXCreateContextAttribsARBFn !is null ? "".ptr : "not ".ptr)); } 19413 } 19414 return (glXCreateContextAttribsARBFn !is null); 19415 } 19416 19417 // this made public so we don't have to get it again and again 19418 public GLXContext glXCreateContextAttribsARB (Display *dpy, GLXFBConfig config, GLXContext share_context, /*Bool*/int direct, const(int)* attrib_list) @system { 19419 if (!glXCreateContextAttribsARB_present()) assert(0, "glXCreateContextAttribsARB is not present"); 19420 return (cast(glXCreateContextAttribsARB_fna)glXCreateContextAttribsARBFn)(dpy, config, share_context, direct, attrib_list); 19421 } 19422 19423 // extern(C) private __gshared int function(int) glXSwapIntervalSGI; // seems totally redundant to the tohers 19424 extern(C) private __gshared int function(int) glXSwapIntervalMESA; 19425 19426 void glxSetVSync (Display* dpy, /*GLXDrawable*/Drawable drawable, bool wait) { 19427 if (cast(void*)_glx_swapInterval_fn is cast(void*)1) return; 19428 if (_glx_swapInterval_fn is null) { 19429 _glx_swapInterval_fn = cast(glXSwapIntervalEXT)glXGetProcAddress("glXSwapIntervalEXT"); 19430 if (_glx_swapInterval_fn is null) { 19431 _glx_swapInterval_fn = cast(glXSwapIntervalEXT)1; 19432 return; 19433 } 19434 version(sdddd) { debug writeln("glXSwapIntervalEXT found!"); } 19435 } 19436 19437 if(glXSwapIntervalMESA is null) { 19438 // it seems to require both to actually take effect on many computers 19439 // idk why 19440 glXSwapIntervalMESA = cast(typeof(glXSwapIntervalMESA)) glXGetProcAddress("glXSwapIntervalMESA"); 19441 if(glXSwapIntervalMESA is null) 19442 glXSwapIntervalMESA = cast(typeof(glXSwapIntervalMESA)) 1; 19443 } 19444 19445 if(cast(void*) glXSwapIntervalMESA > cast(void*) 1) 19446 glXSwapIntervalMESA(wait ? 1 : 0); 19447 19448 _glx_swapInterval_fn(dpy, drawable, (wait ? 1 : 0)); 19449 } 19450 } else version(Windows) { 19451 static if (!SdpyIsUsingIVGLBinds) { 19452 enum GL_TRUE = 1; 19453 enum GL_FALSE = 0; 19454 19455 public void* glbindGetProcAddress (const(char)* name) { 19456 void* res = wglGetProcAddress(name); 19457 if (res is null) { 19458 /+ 19459 //{ import core.stdc.stdio; printf("GL: '%s' not found (0)\n", name); } 19460 import core.sys.windows.windef, core.sys.windows.winbase; 19461 __gshared HINSTANCE dll = null; 19462 if (dll is null) { 19463 dll = LoadLibraryA("opengl32.dll"); 19464 if (dll is null) return null; // <32, but idc 19465 } 19466 res = GetProcAddress(dll, name); 19467 +/ 19468 res = GetProcAddress(gl.libHandle, name); 19469 } 19470 //{ import core.stdc.stdio; printf(" GL: '%s' is 0x%08x\n", name, cast(uint)res); } 19471 return res; 19472 } 19473 } 19474 19475 19476 private __gshared extern(System) BOOL function(int) wglSwapIntervalEXT; 19477 void wglSetVSync(bool wait) { 19478 if(wglSwapIntervalEXT is null) { 19479 wglSwapIntervalEXT = cast(typeof(wglSwapIntervalEXT)) wglGetProcAddress("wglSwapIntervalEXT"); 19480 if(wglSwapIntervalEXT is null) 19481 wglSwapIntervalEXT = cast(typeof(wglSwapIntervalEXT)) 1; 19482 } 19483 if(cast(void*) wglSwapIntervalEXT is cast(void*) 1) 19484 return; 19485 19486 wglSwapIntervalEXT(wait ? 1 : 0); 19487 } 19488 19489 enum WGL_CONTEXT_MAJOR_VERSION_ARB = 0x2091; 19490 enum WGL_CONTEXT_MINOR_VERSION_ARB = 0x2092; 19491 enum WGL_CONTEXT_LAYER_PLANE_ARB = 0x2093; 19492 enum WGL_CONTEXT_FLAGS_ARB = 0x2094; 19493 enum WGL_CONTEXT_PROFILE_MASK_ARB = 0x9126; 19494 19495 enum WGL_CONTEXT_DEBUG_BIT_ARB = 0x0001; 19496 enum WGL_CONTEXT_FORWARD_COMPATIBLE_BIT_ARB = 0x0002; 19497 19498 enum WGL_CONTEXT_CORE_PROFILE_BIT_ARB = 0x00000001; 19499 enum WGL_CONTEXT_COMPATIBILITY_PROFILE_BIT_ARB = 0x00000002; 19500 19501 alias wglCreateContextAttribsARB_fna = HGLRC function (HDC hDC, HGLRC hShareContext, const(int)* attribList); 19502 __gshared wglCreateContextAttribsARB_fna wglCreateContextAttribsARB = null; 19503 19504 void wglInitOtherFunctions () { 19505 if (wglCreateContextAttribsARB is null) { 19506 wglCreateContextAttribsARB = cast(wglCreateContextAttribsARB_fna)glbindGetProcAddress("wglCreateContextAttribsARB"); 19507 } 19508 } 19509 } 19510 19511 static if (!SdpyIsUsingIVGLBinds) { 19512 19513 interface GL { 19514 extern(System) @nogc nothrow: 19515 19516 void glGetIntegerv(int, void*); 19517 void glMatrixMode(int); 19518 void glPushMatrix(); 19519 void glLoadIdentity(); 19520 void glOrtho(double, double, double, double, double, double); 19521 void glFrustum(double, double, double, double, double, double); 19522 19523 void glPopMatrix(); 19524 void glEnable(int); 19525 void glDisable(int); 19526 void glClear(int); 19527 void glBegin(int); 19528 void glVertex2f(float, float); 19529 void glVertex3f(float, float, float); 19530 void glEnd(); 19531 void glColor3b(byte, byte, byte); 19532 void glColor3ub(ubyte, ubyte, ubyte); 19533 void glColor4b(byte, byte, byte, byte); 19534 void glColor4ub(ubyte, ubyte, ubyte, ubyte); 19535 void glColor3i(int, int, int); 19536 void glColor3ui(uint, uint, uint); 19537 void glColor4i(int, int, int, int); 19538 void glColor4ui(uint, uint, uint, uint); 19539 void glColor3f(float, float, float); 19540 void glColor4f(float, float, float, float); 19541 void glTranslatef(float, float, float); 19542 void glScalef(float, float, float); 19543 version(X11) { 19544 void glSecondaryColor3b(byte, byte, byte); 19545 void glSecondaryColor3ub(ubyte, ubyte, ubyte); 19546 void glSecondaryColor3i(int, int, int); 19547 void glSecondaryColor3ui(uint, uint, uint); 19548 void glSecondaryColor3f(float, float, float); 19549 } 19550 19551 void glDrawElements(int, int, int, void*); 19552 19553 void glRotatef(float, float, float, float); 19554 19555 uint glGetError(); 19556 19557 void glDeleteTextures(int, uint*); 19558 19559 19560 void glRasterPos2i(int, int); 19561 void glDrawPixels(int, int, uint, uint, void*); 19562 void glClearColor(float, float, float, float); 19563 19564 19565 void glPixelStorei(uint, int); 19566 19567 void glGenTextures(uint, uint*); 19568 void glBindTexture(int, int); 19569 void glTexParameteri(uint, uint, int); 19570 void glTexParameterf(uint/*GLenum*/ target, uint/*GLenum*/ pname, float param); 19571 void glTexImage2D(int, int, int, int, int, int, int, int, scope const void*); 19572 void glTexSubImage2D(uint/*GLenum*/ target, int level, int xoffset, int yoffset, 19573 /*GLsizei*/int width, /*GLsizei*/int height, 19574 uint/*GLenum*/ format, uint/*GLenum*/ type, scope const void* pixels); 19575 void glTexEnvf(uint/*GLenum*/ target, uint/*GLenum*/ pname, float param); 19576 19577 void glLineWidth(int); 19578 19579 19580 void glTexCoord2f(float, float); 19581 void glVertex2i(int, int); 19582 void glBlendFunc (int, int); 19583 void glDepthFunc (int); 19584 void glViewport(int, int, int, int); 19585 19586 void glClearDepth(double); 19587 19588 void glReadBuffer(uint); 19589 void glReadPixels(int, int, int, int, int, int, void*); 19590 19591 void glScissor(GLint x, GLint y, GLsizei width, GLsizei height); 19592 19593 void glFlush(); 19594 void glFinish(); 19595 19596 version(Windows) { 19597 BOOL wglCopyContext(HGLRC, HGLRC, UINT); 19598 HGLRC wglCreateContext(HDC); 19599 HGLRC wglCreateLayerContext(HDC, int); 19600 BOOL wglDeleteContext(HGLRC); 19601 BOOL wglDescribeLayerPlane(HDC, int, int, UINT, LPLAYERPLANEDESCRIPTOR); 19602 HGLRC wglGetCurrentContext(); 19603 HDC wglGetCurrentDC(); 19604 int wglGetLayerPaletteEntries(HDC, int, int, int, COLORREF*); 19605 PROC wglGetProcAddress(LPCSTR); 19606 BOOL wglMakeCurrent(HDC, HGLRC); 19607 BOOL wglRealizeLayerPalette(HDC, int, BOOL); 19608 int wglSetLayerPaletteEntries(HDC, int, int, int, const(COLORREF)*); 19609 BOOL wglShareLists(HGLRC, HGLRC); 19610 BOOL wglSwapLayerBuffers(HDC, UINT); 19611 BOOL wglUseFontBitmapsA(HDC, DWORD, DWORD, DWORD); 19612 BOOL wglUseFontBitmapsW(HDC, DWORD, DWORD, DWORD); 19613 BOOL wglUseFontOutlinesA(HDC, DWORD, DWORD, DWORD, FLOAT, FLOAT, int, LPGLYPHMETRICSFLOAT); 19614 BOOL wglUseFontOutlinesW(HDC, DWORD, DWORD, DWORD, FLOAT, FLOAT, int, LPGLYPHMETRICSFLOAT); 19615 } 19616 19617 } 19618 19619 interface GL3 { 19620 extern(System) @nogc nothrow: 19621 19622 void glGenVertexArrays(GLsizei, GLuint*); 19623 void glBindVertexArray(GLuint); 19624 void glDeleteVertexArrays(GLsizei, const(GLuint)*); 19625 void glGenerateMipmap(GLenum); 19626 void glBufferSubData(GLenum, GLintptr, GLsizeiptr, const(GLvoid)*); 19627 void glStencilMask(GLuint); 19628 void glStencilFunc(GLenum, GLint, GLuint); 19629 void glGetShaderInfoLog(GLuint, GLsizei, GLsizei*, GLchar*); 19630 void glGetProgramInfoLog(GLuint, GLsizei, GLsizei*, GLchar*); 19631 GLuint glCreateProgram(); 19632 GLuint glCreateShader(GLenum); 19633 void glShaderSource(GLuint, GLsizei, const(GLchar*)*, const(GLint)*); 19634 void glCompileShader(GLuint); 19635 void glGetShaderiv(GLuint, GLenum, GLint*); 19636 void glAttachShader(GLuint, GLuint); 19637 void glBindAttribLocation(GLuint, GLuint, const(GLchar)*); 19638 void glLinkProgram(GLuint); 19639 void glGetProgramiv(GLuint, GLenum, GLint*); 19640 void glDeleteProgram(GLuint); 19641 void glDeleteShader(GLuint); 19642 GLint glGetUniformLocation(GLuint, const(GLchar)*); 19643 void glGenBuffers(GLsizei, GLuint*); 19644 19645 void glUniform1f(GLint location, GLfloat v0); 19646 void glUniform2f(GLint location, GLfloat v0, GLfloat v1); 19647 void glUniform3f(GLint location, GLfloat v0, GLfloat v1, GLfloat v2); 19648 void glUniform4f(GLint location, GLfloat v0, GLfloat v1, GLfloat v2, GLfloat v3); 19649 void glUniform1i(GLint location, GLint v0); 19650 void glUniform2i(GLint location, GLint v0, GLint v1); 19651 void glUniform3i(GLint location, GLint v0, GLint v1, GLint v2); 19652 void glUniform4i(GLint location, GLint v0, GLint v1, GLint v2, GLint v3); 19653 void glUniform1ui(GLint location, GLuint v0); 19654 void glUniform2ui(GLint location, GLuint v0, GLuint v1); 19655 void glUniform3ui(GLint location, GLuint v0, GLuint v1, GLuint v2); 19656 void glUniform4ui(GLint location, GLuint v0, GLuint v1, GLuint v2, GLuint v3); 19657 void glUniform1fv(GLint location, GLsizei count, const GLfloat *value); 19658 void glUniform2fv(GLint location, GLsizei count, const GLfloat *value); 19659 void glUniform3fv(GLint location, GLsizei count, const GLfloat *value); 19660 void glUniform4fv(GLint location, GLsizei count, const GLfloat *value); 19661 void glUniform1iv(GLint location, GLsizei count, const GLint *value); 19662 void glUniform2iv(GLint location, GLsizei count, const GLint *value); 19663 void glUniform3iv(GLint location, GLsizei count, const GLint *value); 19664 void glUniform4iv(GLint location, GLsizei count, const GLint *value); 19665 void glUniform1uiv(GLint location, GLsizei count, const GLuint *value); 19666 void glUniform2uiv(GLint location, GLsizei count, const GLuint *value); 19667 void glUniform3uiv(GLint location, GLsizei count, const GLuint *value); 19668 void glUniform4uiv(GLint location, GLsizei count, const GLuint *value); 19669 void glUniformMatrix2fv(GLint location, GLsizei count, GLboolean transpose, const GLfloat *value); 19670 void glUniformMatrix3fv(GLint location, GLsizei count, GLboolean transpose, const GLfloat *value); 19671 void glUniformMatrix4fv(GLint location, GLsizei count, GLboolean transpose, const GLfloat *value); 19672 void glUniformMatrix2x3fv(GLint location, GLsizei count, GLboolean transpose, const GLfloat *value); 19673 void glUniformMatrix3x2fv(GLint location, GLsizei count, GLboolean transpose, const GLfloat *value); 19674 void glUniformMatrix2x4fv(GLint location, GLsizei count, GLboolean transpose, const GLfloat *value); 19675 void glUniformMatrix4x2fv(GLint location, GLsizei count, GLboolean transpose, const GLfloat *value); 19676 void glUniformMatrix3x4fv(GLint location, GLsizei count, GLboolean transpose, const GLfloat *value); 19677 void glUniformMatrix4x3fv(GLint location, GLsizei count, GLboolean transpose, const GLfloat *value); 19678 19679 void glColorMask(GLboolean, GLboolean, GLboolean, GLboolean); 19680 void glStencilOpSeparate(GLenum, GLenum, GLenum, GLenum); 19681 void glDrawArrays(GLenum, GLint, GLsizei); 19682 void glStencilOp(GLenum, GLenum, GLenum); 19683 void glUseProgram(GLuint); 19684 void glCullFace(GLenum); 19685 void glFrontFace(GLenum); 19686 void glActiveTexture(GLenum); 19687 void glBindBuffer(GLenum, GLuint); 19688 void glBufferData(GLenum, GLsizeiptr, const(void)*, GLenum); 19689 void glEnableVertexAttribArray(GLuint); 19690 void glVertexAttribPointer(GLuint, GLint, GLenum, GLboolean, GLsizei, const(void)*); 19691 void glUniform1i(GLint, GLint); 19692 void glUniform2fv(GLint, GLsizei, const(GLfloat)*); 19693 void glDisableVertexAttribArray(GLuint); 19694 void glDeleteBuffers(GLsizei, const(GLuint)*); 19695 void glBlendFuncSeparate(GLenum, GLenum, GLenum, GLenum); 19696 void glLogicOp (GLenum opcode); 19697 void glFramebufferTexture2D (GLenum target, GLenum attachment, GLenum textarget, GLuint texture, GLint level); 19698 void glDeleteFramebuffers (GLsizei n, const(GLuint)* framebuffers); 19699 void glGenFramebuffers (GLsizei n, GLuint* framebuffers); 19700 GLenum glCheckFramebufferStatus (GLenum target); 19701 void glBindFramebuffer (GLenum target, GLuint framebuffer); 19702 } 19703 19704 interface GL4 { 19705 extern(System) @nogc nothrow: 19706 19707 void glTextureSubImage2D(uint texture, int level, int xoffset, int yoffset, 19708 /*GLsizei*/int width, /*GLsizei*/int height, 19709 uint/*GLenum*/ format, uint/*GLenum*/ type, scope const void* pixels); 19710 } 19711 19712 interface GLU { 19713 extern(System) @nogc nothrow: 19714 19715 void gluLookAt(double, double, double, double, double, double, double, double, double); 19716 void gluPerspective(double, double, double, double); 19717 19718 char* gluErrorString(uint); 19719 } 19720 19721 19722 enum GL_RED = 0x1903; 19723 enum GL_ALPHA = 0x1906; 19724 19725 enum uint GL_FRONT = 0x0404; 19726 19727 enum uint GL_BLEND = 0x0be2; 19728 enum uint GL_LEQUAL = 0x0203; 19729 19730 19731 enum uint GL_RGB = 0x1907; 19732 enum uint GL_BGRA = 0x80e1; 19733 enum uint GL_RGBA = 0x1908; 19734 enum uint GL_RGBA8 = 0x8058; 19735 enum uint GL_TEXTURE_2D = 0x0DE1; 19736 enum uint GL_TEXTURE_MIN_FILTER = 0x2801; 19737 enum uint GL_NEAREST = 0x2600; 19738 enum uint GL_LINEAR = 0x2601; 19739 enum uint GL_TEXTURE_MAG_FILTER = 0x2800; 19740 enum uint GL_TEXTURE_WRAP_S = 0x2802; 19741 enum uint GL_TEXTURE_WRAP_T = 0x2803; 19742 enum uint GL_REPEAT = 0x2901; 19743 enum uint GL_CLAMP = 0x2900; 19744 enum uint GL_CLAMP_TO_EDGE = 0x812F; 19745 enum uint GL_CLAMP_TO_BORDER = 0x812D; 19746 enum uint GL_DECAL = 0x2101; 19747 enum uint GL_MODULATE = 0x2100; 19748 enum uint GL_TEXTURE_ENV = 0x2300; 19749 enum uint GL_TEXTURE_ENV_MODE = 0x2200; 19750 enum uint GL_REPLACE = 0x1E01; 19751 enum uint GL_LIGHTING = 0x0B50; 19752 enum uint GL_DITHER = 0x0BD0; 19753 19754 enum uint GL_NO_ERROR = 0; 19755 19756 19757 19758 enum int GL_VIEWPORT = 0x0BA2; 19759 enum int GL_MODELVIEW = 0x1700; 19760 enum int GL_TEXTURE = 0x1702; 19761 enum int GL_PROJECTION = 0x1701; 19762 enum int GL_DEPTH_TEST = 0x0B71; 19763 19764 enum int GL_COLOR_BUFFER_BIT = 0x00004000; 19765 enum int GL_ACCUM_BUFFER_BIT = 0x00000200; 19766 enum int GL_DEPTH_BUFFER_BIT = 0x00000100; 19767 enum uint GL_STENCIL_BUFFER_BIT = 0x00000400; 19768 19769 enum int GL_POINTS = 0x0000; 19770 enum int GL_LINES = 0x0001; 19771 enum int GL_LINE_LOOP = 0x0002; 19772 enum int GL_LINE_STRIP = 0x0003; 19773 enum int GL_TRIANGLES = 0x0004; 19774 enum int GL_TRIANGLE_STRIP = 5; 19775 enum int GL_TRIANGLE_FAN = 6; 19776 enum int GL_QUADS = 7; 19777 enum int GL_QUAD_STRIP = 8; 19778 enum int GL_POLYGON = 9; 19779 19780 alias GLvoid = void; 19781 alias GLboolean = ubyte; 19782 alias GLint = int; 19783 alias GLuint = uint; 19784 alias GLenum = uint; 19785 alias GLchar = char; 19786 alias GLsizei = int; 19787 alias GLfloat = float; 19788 alias GLintptr = size_t; 19789 alias GLsizeiptr = ptrdiff_t; 19790 19791 19792 enum uint GL_INVALID_ENUM = 0x0500; 19793 19794 enum uint GL_ZERO = 0; 19795 enum uint GL_ONE = 1; 19796 19797 enum uint GL_BYTE = 0x1400; 19798 enum uint GL_UNSIGNED_BYTE = 0x1401; 19799 enum uint GL_SHORT = 0x1402; 19800 enum uint GL_UNSIGNED_SHORT = 0x1403; 19801 enum uint GL_INT = 0x1404; 19802 enum uint GL_UNSIGNED_INT = 0x1405; 19803 enum uint GL_FLOAT = 0x1406; 19804 enum uint GL_2_BYTES = 0x1407; 19805 enum uint GL_3_BYTES = 0x1408; 19806 enum uint GL_4_BYTES = 0x1409; 19807 enum uint GL_DOUBLE = 0x140A; 19808 19809 enum uint GL_STREAM_DRAW = 0x88E0; 19810 19811 enum uint GL_CCW = 0x0901; 19812 19813 enum uint GL_STENCIL_TEST = 0x0B90; 19814 enum uint GL_SCISSOR_TEST = 0x0C11; 19815 19816 enum uint GL_EQUAL = 0x0202; 19817 enum uint GL_NOTEQUAL = 0x0205; 19818 19819 enum uint GL_ALWAYS = 0x0207; 19820 enum uint GL_KEEP = 0x1E00; 19821 19822 enum uint GL_INCR = 0x1E02; 19823 19824 enum uint GL_INCR_WRAP = 0x8507; 19825 enum uint GL_DECR_WRAP = 0x8508; 19826 19827 enum uint GL_CULL_FACE = 0x0B44; 19828 enum uint GL_BACK = 0x0405; 19829 19830 enum uint GL_FRAGMENT_SHADER = 0x8B30; 19831 enum uint GL_VERTEX_SHADER = 0x8B31; 19832 19833 enum uint GL_COMPILE_STATUS = 0x8B81; 19834 enum uint GL_LINK_STATUS = 0x8B82; 19835 19836 enum uint GL_ELEMENT_ARRAY_BUFFER = 0x8893; 19837 19838 enum uint GL_STATIC_DRAW = 0x88E4; 19839 19840 enum uint GL_UNPACK_ALIGNMENT = 0x0CF5; 19841 enum uint GL_UNPACK_ROW_LENGTH = 0x0CF2; 19842 enum uint GL_UNPACK_SKIP_PIXELS = 0x0CF4; 19843 enum uint GL_UNPACK_SKIP_ROWS = 0x0CF3; 19844 19845 enum uint GL_GENERATE_MIPMAP = 0x8191; 19846 enum uint GL_LINEAR_MIPMAP_LINEAR = 0x2703; 19847 19848 enum uint GL_TEXTURE0 = 0x84C0U; 19849 enum uint GL_TEXTURE1 = 0x84C1U; 19850 19851 enum uint GL_ARRAY_BUFFER = 0x8892; 19852 19853 enum uint GL_SRC_COLOR = 0x0300; 19854 enum uint GL_ONE_MINUS_SRC_COLOR = 0x0301; 19855 enum uint GL_SRC_ALPHA = 0x0302; 19856 enum uint GL_ONE_MINUS_SRC_ALPHA = 0x0303; 19857 enum uint GL_DST_ALPHA = 0x0304; 19858 enum uint GL_ONE_MINUS_DST_ALPHA = 0x0305; 19859 enum uint GL_DST_COLOR = 0x0306; 19860 enum uint GL_ONE_MINUS_DST_COLOR = 0x0307; 19861 enum uint GL_SRC_ALPHA_SATURATE = 0x0308; 19862 19863 enum uint GL_INVERT = 0x150AU; 19864 19865 enum uint GL_DEPTH_STENCIL = 0x84F9U; 19866 enum uint GL_UNSIGNED_INT_24_8 = 0x84FAU; 19867 19868 enum uint GL_FRAMEBUFFER = 0x8D40U; 19869 enum uint GL_COLOR_ATTACHMENT0 = 0x8CE0U; 19870 enum uint GL_DEPTH_STENCIL_ATTACHMENT = 0x821AU; 19871 19872 enum uint GL_FRAMEBUFFER_COMPLETE = 0x8CD5U; 19873 enum uint GL_FRAMEBUFFER_INCOMPLETE_ATTACHMENT = 0x8CD6U; 19874 enum uint GL_FRAMEBUFFER_INCOMPLETE_MISSING_ATTACHMENT = 0x8CD7U; 19875 enum uint GL_FRAMEBUFFER_INCOMPLETE_DIMENSIONS = 0x8CD9U; 19876 enum uint GL_FRAMEBUFFER_UNSUPPORTED = 0x8CDDU; 19877 19878 enum uint GL_COLOR_LOGIC_OP = 0x0BF2U; 19879 enum uint GL_CLEAR = 0x1500U; 19880 enum uint GL_COPY = 0x1503U; 19881 enum uint GL_XOR = 0x1506U; 19882 19883 enum uint GL_FRAMEBUFFER_BINDING = 0x8CA6U; 19884 19885 enum uint GL_TEXTURE_LOD_BIAS = 0x8501; 19886 19887 } 19888 } 19889 19890 /++ 19891 History: 19892 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. 19893 +/ 19894 __gshared bool gluSuccessfullyLoaded = true; 19895 19896 version(without_opengl) {} else { 19897 static if(!SdpyIsUsingIVGLBinds) { 19898 version(Windows) { 19899 mixin DynamicLoad!(GL, "opengl32", 1, openGlLibrariesSuccessfullyLoaded) gl; 19900 mixin DynamicLoad!(GLU, "glu32", 1, gluSuccessfullyLoaded) glu; 19901 } else { 19902 mixin DynamicLoad!(GL, "GL", 1, openGlLibrariesSuccessfullyLoaded) gl; 19903 mixin DynamicLoad!(GLU, "GLU", 3, gluSuccessfullyLoaded) glu; 19904 } 19905 mixin DynamicLoadSupplementalOpenGL!(GL3) gl3; 19906 19907 19908 shared static this() { 19909 gl.loadDynamicLibrary(); 19910 19911 // FIXME: this is NOT actually required and should NOT fail if it is not loaded 19912 // unless those functions are actually used 19913 // go to mark b openGlLibrariesSuccessfullyLoaded = false; 19914 glu.loadDynamicLibrary(); 19915 } 19916 } 19917 } 19918 19919 /++ 19920 Convenience method for converting D arrays to opengl buffer data 19921 19922 I would LOVE to overload it with the original glBufferData, but D won't 19923 let me since glBufferData is a function pointer :( 19924 19925 Added: August 25, 2020 (version 8.5) 19926 +/ 19927 version(without_opengl) {} else 19928 void glBufferDataSlice(GLenum target, const(void[]) data, GLenum usage) { 19929 glBufferData(target, data.length, data.ptr, usage); 19930 } 19931 19932 /++ 19933 History: 19934 Added September 1, 2024 19935 +/ 19936 version(without_opengl) {} else 19937 void glBufferSubDataSlice(GLenum target, size_t offset, const(void[]) data, GLenum usage) { 19938 glBufferSubData(target, offset, data.length, data.ptr); 19939 } 19940 19941 /++ 19942 Convenience class for using opengl shaders. 19943 19944 Ensure that you've loaded opengl 3+ and set your active 19945 context before trying to use this. 19946 19947 Added: August 25, 2020 (version 8.5) 19948 +/ 19949 version(without_opengl) {} else 19950 final class OpenGlShader { 19951 private int shaderProgram_; 19952 private @property void shaderProgram(int a) { 19953 shaderProgram_ = a; 19954 } 19955 /// Get the program ID for use in OpenGL functions. 19956 public @property int shaderProgram() { 19957 return shaderProgram_; 19958 } 19959 19960 /++ 19961 19962 +/ 19963 static struct Source { 19964 uint type; /// GL_FRAGMENT_SHADER, GL_VERTEX_SHADER, etc. 19965 string code; /// 19966 } 19967 19968 /++ 19969 Helper method to just compile some shader code and check for errors 19970 while you do glCreateShader, etc. on the outside yourself. 19971 19972 This just does `glShaderSource` and `glCompileShader` for the given code. 19973 19974 If you the OpenGlShader class constructor, you never need to call this yourself. 19975 +/ 19976 static void compile(int sid, Source code) { 19977 const(char)*[1] buffer; 19978 int[1] lengthBuffer; 19979 19980 buffer[0] = code.code.ptr; 19981 lengthBuffer[0] = cast(int) code.code.length; 19982 19983 glShaderSource(sid, cast(int) buffer.length, buffer.ptr, lengthBuffer.ptr); 19984 glCompileShader(sid); 19985 19986 int success; 19987 glGetShaderiv(sid, GL_COMPILE_STATUS, &success); 19988 if(!success) { 19989 char[512] info; 19990 int len; 19991 glGetShaderInfoLog(sid, info.length, &len, info.ptr); 19992 19993 throw new Exception("Shader compile failure: " ~ cast(immutable) info[0 .. len]); 19994 } 19995 } 19996 19997 /++ 19998 Calls `glLinkProgram` and throws if error a occurs. 19999 20000 If you the OpenGlShader class constructor, you never need to call this yourself. 20001 +/ 20002 static void link(int shaderProgram) { 20003 glLinkProgram(shaderProgram); 20004 int success; 20005 glGetProgramiv(shaderProgram, GL_LINK_STATUS, &success); 20006 if(!success) { 20007 char[512] info; 20008 int len; 20009 glGetProgramInfoLog(shaderProgram, info.length, &len, info.ptr); 20010 20011 throw new Exception("Shader link failure: " ~ cast(immutable) info[0 .. len]); 20012 } 20013 } 20014 20015 /++ 20016 Constructs the shader object by calling `glCreateProgram`, then 20017 compiling each given [Source], and finally, linking them together. 20018 20019 Throws: on compile or link failure. 20020 +/ 20021 this(Source[] codes...) { 20022 shaderProgram = glCreateProgram(); 20023 20024 int[16] shadersBufferStack; 20025 20026 int[] shadersBuffer = codes.length <= shadersBufferStack.length ? 20027 shadersBufferStack[0 .. codes.length] : 20028 new int[](codes.length); 20029 20030 foreach(idx, code; codes) { 20031 shadersBuffer[idx] = glCreateShader(code.type); 20032 20033 compile(shadersBuffer[idx], code); 20034 20035 glAttachShader(shaderProgram, shadersBuffer[idx]); 20036 } 20037 20038 link(shaderProgram); 20039 20040 foreach(s; shadersBuffer) 20041 glDeleteShader(s); 20042 } 20043 20044 /// Calls `glUseProgram(this.shaderProgram)` 20045 void use() { 20046 glUseProgram(this.shaderProgram); 20047 } 20048 20049 /// Deletes the program. 20050 void delete_() { 20051 glDeleteProgram(shaderProgram); 20052 shaderProgram = 0; 20053 } 20054 20055 /++ 20056 [OpenGlShader.uniforms].name gives you one of these. 20057 20058 You can get the id out of it or just assign 20059 +/ 20060 static struct Uniform { 20061 /// the id passed to glUniform* 20062 int id; 20063 20064 /// Assigns the 4 floats. You will probably have to call this via the .opAssign name 20065 void opAssign(float x, float y, float z, float w) { 20066 if(id != -1) 20067 glUniform4f(id, x, y, z, w); 20068 } 20069 20070 void opAssign(float x) { 20071 if(id != -1) 20072 glUniform1f(id, x); 20073 } 20074 20075 void opAssign(float x, float y) { 20076 if(id != -1) 20077 glUniform2f(id, x, y); 20078 } 20079 20080 void opAssign(T)(T t) { 20081 t.glUniform(id); 20082 } 20083 } 20084 20085 static struct UniformsHelper { 20086 OpenGlShader _shader; 20087 20088 @property Uniform opDispatch(string name)() { 20089 auto i = glGetUniformLocation(_shader.shaderProgram, name.ptr); 20090 // FIXME: decide what to do here; the exception is liable to be swallowed by the event syste 20091 //if(i == -1) 20092 //throw new Exception("Could not find uniform " ~ name); 20093 return Uniform(i); 20094 } 20095 20096 @property void opDispatch(string name, T)(T t) { 20097 Uniform f = this.opDispatch!name; 20098 t.glUniform(f); 20099 } 20100 } 20101 20102 /++ 20103 Gives access to the uniforms through dot access. 20104 `OpenGlShader.Uniform = shader.uniforms.foo; // calls glGetUniformLocation(this, "foo"); 20105 +/ 20106 @property UniformsHelper uniforms() { return UniformsHelper(this); } 20107 } 20108 20109 version(without_opengl) {} else { 20110 /++ 20111 A static container of experimental types and value constructors for opengl 3+ shaders. 20112 20113 20114 You can declare variables like: 20115 20116 ``` 20117 OGL.vec3f something; 20118 ``` 20119 20120 But generally it would be used with [OpenGlShader]'s uniform helpers like 20121 20122 ``` 20123 shader.uniforms.mouse = OGL.vec(mouseX, mouseY); // or OGL.vec2f if you want to be more specific 20124 ``` 20125 20126 This is still extremely experimental, not very useful at this point, and thus subject to change at random. 20127 20128 20129 History: 20130 Added December 7, 2021. Not yet stable. 20131 +/ 20132 final class OGL { 20133 static: 20134 20135 private template typeFromSpecifier(string specifier) { 20136 static if(specifier == "f") 20137 alias typeFromSpecifier = GLfloat; 20138 else static if(specifier == "i") 20139 alias typeFromSpecifier = GLint; 20140 else static if(specifier == "ui") 20141 alias typeFromSpecifier = GLuint; 20142 else static assert(0, "I don't know this ogl type suffix " ~ specifier); 20143 } 20144 20145 private template CommonType(T...) { 20146 static if(T.length == 1) 20147 alias CommonType = T[0]; 20148 else static if(is(typeof(true ? T[0].init : T[1].init) C)) 20149 alias CommonType = CommonType!(C, T[2 .. $]); 20150 } 20151 20152 private template typesToSpecifier(T...) { 20153 static if(is(CommonType!T == float)) 20154 enum typesToSpecifier = "f"; 20155 else static if(is(CommonType!T == int)) 20156 enum typesToSpecifier = "i"; 20157 else static if(is(CommonType!T == uint)) 20158 enum typesToSpecifier = "ui"; 20159 else static assert(0, "I can't find a gl type suffix for common type " ~ CommonType!T.stringof); 20160 } 20161 20162 private template genNames(size_t dim, size_t dim2 = 0) { 20163 string helper() { 20164 string s; 20165 if(dim2) { 20166 static if(__VERSION__ < 2102) 20167 s ~= "type["~(dim + '0')~"]["~(dim2 + '0')~"] matrix = void;"; // stupid compiler bug 20168 else 20169 s ~= "type["~(dim + '0')~"]["~(dim2 + '0')~"] matrix = 0;"; 20170 } else { 20171 if(dim > 0) s ~= "type x = 0;"; 20172 if(dim > 1) s ~= "type y = 0;"; 20173 if(dim > 2) s ~= "type z = 0;"; 20174 if(dim > 3) s ~= "type w = 0;"; 20175 } 20176 20177 s ~= "this(typeof(this.tupleof) args) { this.tupleof = args; }"; 20178 if(dim2) 20179 s ~= "this(type["~(dim*dim2).stringof~"] t) { (cast(typeof(t)) this.matrix)[] = t[]; }"; 20180 20181 return s; 20182 } 20183 20184 enum genNames = helper(); 20185 } 20186 20187 // there's vec, arrays of vec, mat, and arrays of mat 20188 template opDispatch(string name) 20189 if(name.length > 4 && (name[0 .. 3] == "vec" || name[0 .. 3] == "mat")) 20190 { 20191 static if(name[4] == 'x') { 20192 enum dimX = cast(int) (name[3] - '0'); 20193 static assert(dimX > 0 && dimX <= 4, "Bad dimension for OGL X type " ~ name[3]); 20194 20195 enum dimY = cast(int) (name[5] - '0'); 20196 static assert(dimY > 0 && dimY <= 4, "Bad dimension for OGL Y type " ~ name[5]); 20197 20198 enum isArray = name[$ - 1] == 'v'; 20199 enum typeSpecifier = isArray ? name[6 .. $ - 1] : name[6 .. $]; 20200 alias type = typeFromSpecifier!typeSpecifier; 20201 } else { 20202 enum dim = cast(int) (name[3] - '0'); 20203 static assert(dim > 0 && dim <= 4, "Bad dimension for OGL type " ~ name[3]); 20204 enum isArray = name[$ - 1] == 'v'; 20205 enum typeSpecifier = isArray ? name[4 .. $ - 1] : name[4 .. $]; 20206 alias type = typeFromSpecifier!typeSpecifier; 20207 } 20208 20209 align(1) 20210 struct opDispatch { 20211 align(1): 20212 static if(name[4] == 'x') 20213 mixin(genNames!(dimX, dimY)); 20214 else 20215 mixin(genNames!dim); 20216 20217 private void glUniform(OpenGlShader.Uniform assignTo) { 20218 glUniform(assignTo.id); 20219 } 20220 private void glUniform(int assignTo) { 20221 static if(name[4] == 'x') { 20222 static if(name[3] == name[5]) { 20223 // import std.stdio; writeln(name, " ", this.matrix, dimX, " ", dimY); 20224 mixin("glUniformMatrix" ~ name[5 .. $] ~ "v")(assignTo, 1, true, &this.matrix[0][0]); 20225 } else { 20226 mixin("glUniformMatrix" ~ name[3 .. $] ~ "v")(assignTo, 1, false, this.matrix.ptr); 20227 } 20228 } else 20229 mixin("glUniform" ~ name[3 .. $])(assignTo, this.tupleof); 20230 } 20231 } 20232 } 20233 20234 auto vec(T...)(T members) { 20235 return typeof(this).opDispatch!("vec" ~ toInternal!string(cast(int) T.length)~ typesToSpecifier!T)(members); 20236 } 20237 } 20238 20239 void checkGlError() { 20240 auto error = glGetError(); 20241 int[] errors; 20242 string[] errorStrings; 20243 while(error != GL_NO_ERROR) { 20244 errors ~= error; 20245 switch(error) { 20246 case 0x0500: errorStrings ~= "GL_INVALID_ENUM"; break; 20247 case 0x0501: errorStrings ~= "GL_INVALID_VALUE"; break; 20248 case 0x0502: errorStrings ~= "GL_INVALID_OPERATION"; break; 20249 case 0x0503: errorStrings ~= "GL_STACK_OVERFLOW"; break; 20250 case 0x0504: errorStrings ~= "GL_STACK_UNDERFLOW"; break; 20251 case 0x0505: errorStrings ~= "GL_OUT_OF_MEMORY"; break; 20252 default: errorStrings ~= "idk"; 20253 } 20254 error = glGetError(); 20255 } 20256 if(errors.length) 20257 throw ArsdException!"glGetError"(errors, errorStrings); 20258 } 20259 20260 /++ 20261 A matrix for simple uses that easily integrates with [OpenGlShader]. 20262 20263 Might not be useful to you since it only as some simple functions and 20264 probably isn't that fast. 20265 20266 Note it uses an inline static array for its storage, so copying it 20267 may be expensive. 20268 +/ 20269 struct BasicMatrix(int columns, int rows, T = float) { 20270 static import core.stdc.math; 20271 static if(is(T == float)) { 20272 alias cos = core.stdc.math.cosf; 20273 alias sin = core.stdc.math.sinf; 20274 } else { 20275 alias cos = core.stdc.math.cos; 20276 alias sin = core.stdc.math.sin; 20277 } 20278 20279 T[columns * rows] data = 0.0; 20280 20281 /++ 20282 20283 +/ 20284 this(T[columns * rows] data) { 20285 this.data = data; 20286 } 20287 20288 /++ 20289 Basic operations that operate *in place*. 20290 +/ 20291 static if(columns == 4 && rows == 4) 20292 void translate(T x, T y, T z) { 20293 BasicMatrix m = [ 20294 1, 0, 0, x, 20295 0, 1, 0, y, 20296 0, 0, 1, z, 20297 0, 0, 0, 1 20298 ]; 20299 20300 this *= m; 20301 } 20302 20303 /// ditto 20304 static if(columns == 4 && rows == 4) 20305 void scale(T x, T y, T z) { 20306 BasicMatrix m = [ 20307 x, 0, 0, 0, 20308 0, y, 0, 0, 20309 0, 0, z, 0, 20310 0, 0, 0, 1 20311 ]; 20312 20313 this *= m; 20314 } 20315 20316 /// ditto 20317 static if(columns == 4 && rows == 4) 20318 void rotateX(T theta) { 20319 BasicMatrix m = [ 20320 1, 0, 0, 0, 20321 0, cos(theta), -sin(theta), 0, 20322 0, sin(theta), cos(theta), 0, 20323 0, 0, 0, 1 20324 ]; 20325 20326 this *= m; 20327 } 20328 20329 /// ditto 20330 static if(columns == 4 && rows == 4) 20331 void rotateY(T theta) { 20332 BasicMatrix m = [ 20333 cos(theta), 0, sin(theta), 0, 20334 0, 1, 0, 0, 20335 -sin(theta), 0, cos(theta), 0, 20336 0, 0, 0, 1 20337 ]; 20338 20339 this *= m; 20340 } 20341 20342 /// ditto 20343 static if(columns == 4 && rows == 4) 20344 void rotateZ(T theta) { 20345 BasicMatrix m = [ 20346 cos(theta), -sin(theta), 0, 0, 20347 sin(theta), cos(theta), 0, 0, 20348 0, 0, 1, 0, 20349 0, 0, 0, 1 20350 ]; 20351 20352 this *= m; 20353 } 20354 20355 /++ 20356 20357 +/ 20358 static if(columns == rows) 20359 static BasicMatrix identity() { 20360 BasicMatrix m; 20361 foreach(i; 0 .. columns) 20362 m.data[0 + i + i * columns] = 1.0; 20363 return m; 20364 } 20365 20366 static if(columns == rows) 20367 void loadIdentity() { 20368 this = identity(); 20369 } 20370 20371 static if(columns == 4 && rows == 4) 20372 static BasicMatrix ortho(T l, T r, T b, T t, T n, T f) { 20373 return BasicMatrix([ 20374 2/(r-l), 0, 0, -(r+l)/(r-l), 20375 0, 2/(t-b), 0, -(t+b)/(t-b), 20376 0, 0, -2/(f-n), -(f+n)/(f-n), 20377 0, 0, 0, 1 20378 ]); 20379 } 20380 20381 static if(columns == 4 && rows == 4) 20382 void loadOrtho(T l, T r, T b, T t, T n, T f) { 20383 this = ortho(l, r, b, t, n, f); 20384 } 20385 20386 void opOpAssign(string op : "+")(const BasicMatrix rhs) { 20387 this.data[] += rhs.data; 20388 } 20389 void opOpAssign(string op : "-")(const BasicMatrix rhs) { 20390 this.data[] -= rhs.data; 20391 } 20392 void opOpAssign(string op : "*")(const T rhs) { 20393 this.data[] *= rhs; 20394 } 20395 void opOpAssign(string op : "/")(const T rhs) { 20396 this.data[] /= rhs; 20397 } 20398 void opOpAssign(string op : "*", BM : BasicMatrix!(rhsColumns, rhsRows, rhsT), int rhsColumns, int rhsRows, rhsT)(const BM rhs) { 20399 static assert(columns == rhsRows); 20400 auto multiplySize = columns; 20401 20402 auto tmp = this.data; // copy cuz it is a value type 20403 20404 int idx = 0; 20405 foreach(r; 0 .. rows) 20406 foreach(c; 0 .. columns) { 20407 T sum = 0.0; 20408 20409 foreach(i; 0 .. multiplySize) 20410 sum += this.data[r * columns + i] * rhs.data[i * rhsColumns + c]; 20411 20412 tmp[idx++] = sum; 20413 } 20414 20415 this.data = tmp; 20416 } 20417 } 20418 20419 unittest { 20420 auto m = BasicMatrix!(2, 2)([ 20421 1, 2, 20422 3, 4 20423 ]); 20424 20425 auto m2 = BasicMatrix!(2, 2)([ 20426 5, 6, 20427 7, 8 20428 ]); 20429 20430 //import std.conv; 20431 m *= m2; 20432 assert(m.data == [ 20433 19, 22, 20434 43, 50 20435 ]);//, to!string(m.data)); 20436 } 20437 20438 20439 20440 class GlObjectBase { 20441 protected uint _vao; 20442 protected uint _elementsCount; 20443 20444 protected uint element_buffer; 20445 20446 void gen() { 20447 glGenVertexArrays(1, &_vao); 20448 } 20449 20450 void bind() { 20451 glBindVertexArray(_vao); 20452 } 20453 20454 void dispose() { 20455 glDeleteVertexArrays(1, &_vao); 20456 } 20457 20458 void draw() { 20459 bind(); 20460 glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, element_buffer); 20461 glDrawElements(GL_TRIANGLES, _elementsCount, GL_UNSIGNED_INT, null); 20462 } 20463 } 20464 20465 /++ 20466 20467 +/ 20468 class GlObject(T) : GlObjectBase { 20469 protected uint VBO; 20470 20471 this(T[] arr, uint[] indices) { 20472 gen(); 20473 bind(); 20474 20475 glGenBuffers(1, &VBO); 20476 glGenBuffers(1, &element_buffer); 20477 20478 glBindBuffer(GL_ARRAY_BUFFER, VBO); 20479 glBufferDataSlice(GL_ARRAY_BUFFER, arr, GL_STATIC_DRAW); 20480 20481 glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, element_buffer); 20482 glBufferDataSlice(GL_ELEMENT_ARRAY_BUFFER, indices, GL_STATIC_DRAW); 20483 _elementsCount = cast(int) indices.length; 20484 20485 foreach(int idx, memberName; __traits(allMembers, T)) { 20486 static if(memberName != "__ctor") { 20487 static if(is(typeof(__traits(getMember, T, memberName)) == float[N], size_t N)) { 20488 glVertexAttribPointer(idx, N, GL_FLOAT, GL_FALSE, T.sizeof, cast(void*) __traits(getMember, T, memberName).offsetof); 20489 glEnableVertexAttribArray(idx); 20490 } else static assert(0); } 20491 } 20492 } 20493 20494 static string generateShaderDefinitions() { 20495 string code; 20496 20497 foreach(idx, memberName; __traits(allMembers, T)) { 20498 // never use stringof ladies and gents it has a LU thing at the end of it 20499 static if(memberName != "__ctor") 20500 code ~= "layout (location = " ~ idx.stringof[0..$-2] ~ ") in " ~ typeToGl!(typeof(__traits(getMember, T, memberName))) ~ " " ~ memberName ~ ";\n"; 20501 } 20502 20503 return code; 20504 } 20505 } 20506 20507 private string typeToGl(T)() { 20508 static if(is(T == float[4])) 20509 return "vec4"; 20510 else static if(is(T == float[3])) 20511 return "vec3"; 20512 else static if(is(T == float[2])) 20513 return "vec2"; 20514 else static assert(0, T.stringof); 20515 } 20516 20517 20518 } 20519 20520 version(Emscripten) { 20521 20522 } else version(linux) { 20523 version(with_eventloop) {} else { 20524 private int epollFd = -1; 20525 void prepareEventLoop() { 20526 if(epollFd != -1) 20527 return; // already initialized, no need to do it again 20528 import ep = core.sys.linux.epoll; 20529 20530 epollFd = ep.epoll_create1(ep.EPOLL_CLOEXEC); 20531 if(epollFd == -1) 20532 throw new Exception("epoll create failure"); 20533 } 20534 } 20535 } else version(Posix) { 20536 void prepareEventLoop() {} 20537 } 20538 20539 version(X11) { 20540 import core.stdc.locale : LC_ALL; // rdmd fix 20541 __gshared bool sdx_isUTF8Locale; 20542 20543 // This whole crap is used to initialize X11 locale, so that you can use XIM methods later. 20544 // Yes, there are people with non-utf locale (it's me, Ketmar!), but XIM (composing) will 20545 // not work right if app/X11 locale is not utf. This sux. That's why all that "utf detection" 20546 // anal magic is here. I (Ketmar) hope you like it. 20547 // We will use `sdx_isUTF8Locale` on XIM creation to enforce UTF-8 locale, so XCompose will 20548 // always return correct unicode symbols. The detection is here 'cause user can change locale 20549 // later. 20550 20551 // NOTE: IT IS VERY IMPORTANT THAT THIS BE THE LAST STATIC CTOR OF THE FILE since it tests librariesSuccessfullyLoaded 20552 shared static this () @system { 20553 if(!librariesSuccessfullyLoaded) 20554 return; 20555 20556 import core.stdc.locale : setlocale, LC_ALL, LC_CTYPE; 20557 20558 // this doesn't hurt; it may add some locking, but the speed is still 20559 // allows doing 60 FPS videogames; also, ignore the result, as most 20560 // users will probably won't do mulththreaded X11 anyway (and I (ketmar) 20561 // never seen this failing). 20562 if (XInitThreads() == 0) { import core.stdc.stdio; fprintf(stderr, "XInitThreads() failed!\n"); } 20563 20564 setlocale(LC_ALL, ""); 20565 // check if out locale is UTF-8 20566 auto lct = setlocale(LC_CTYPE, null); 20567 if (lct is null) { 20568 sdx_isUTF8Locale = false; 20569 } else { 20570 for (size_t idx = 0; lct[idx] && lct[idx+1] && lct[idx+2]; ++idx) { 20571 if ((lct[idx+0] == 'u' || lct[idx+0] == 'U') && 20572 (lct[idx+1] == 't' || lct[idx+1] == 'T') && 20573 (lct[idx+2] == 'f' || lct[idx+2] == 'F')) 20574 { 20575 sdx_isUTF8Locale = true; 20576 break; 20577 } 20578 } 20579 } 20580 //{ import core.stdc.stdio : stderr, fprintf; fprintf(stderr, "UTF8: %s\n", sdx_isUTF8Locale ? "tan".ptr : "ona".ptr); } 20581 } 20582 } 20583 20584 class ExperimentalTextComponent2 { 20585 /+ 20586 Stage 1: get it working monospace 20587 Stage 2: use proportional font 20588 Stage 3: allow changes in inline style 20589 Stage 4: allow new fonts and sizes in the middle 20590 Stage 5: optimize gap buffer 20591 Stage 6: optimize layout 20592 Stage 7: word wrap 20593 Stage 8: justification 20594 Stage 9: editing, selection, etc. 20595 20596 Operations: 20597 insert text 20598 overstrike text 20599 select 20600 cut 20601 modify 20602 +/ 20603 20604 /++ 20605 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. 20606 +/ 20607 this(SimpleWindow window) { 20608 this.window = window; 20609 } 20610 20611 private SimpleWindow window; 20612 20613 20614 /++ 20615 When you render a [ComponentInFlow], it returns an arbitrary number of these interfaces 20616 representing the internal parts. The first pass is focused on the x parameter, then the 20617 renderer is responsible for going back to the parts in the current line and calling 20618 adjustDownForAscent to change the y params. 20619 +/ 20620 static interface ComponentRenderHelper { 20621 20622 /+ 20623 When you do an edit, possibly stuff on the same line previously need to move (to adjust 20624 the baseline), stuff subsequent needs to move (adjust x) and possibly stuff below needs 20625 to move (adjust y to make room for new line) until you get back to the same position, 20626 then you can stop - if one thing is unchanged, nothing after it is changed too. 20627 20628 Word wrap might change this as if can rewrap tons of stuff, but the same idea applies, 20629 once you reach something that is unchanged, you can stop. 20630 +/ 20631 20632 void adjustDownForAscent(int amount); // at the end of the line it needs to do these 20633 20634 int ascent() const; 20635 int descent() const; 20636 20637 int advance() const; 20638 20639 bool endsWithExplititLineBreak() const; 20640 } 20641 20642 static interface RenderResult { 20643 /++ 20644 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. 20645 +/ 20646 void popFront(); 20647 @property bool empty() const; 20648 @property ComponentRenderHelper front() const; 20649 20650 void repositionForNextLine(Point baseline, int availableWidth); 20651 } 20652 20653 static interface ComponentInFlow { 20654 void draw(ScreenPainter painter); 20655 //RenderResult render(Point baseline, int availableWidth); // FIXME: it needs to be able to say "my cache is good, nothing different" 20656 20657 bool startsWithExplicitLineBreak() const; 20658 } 20659 20660 static class TextFlowComponent : ComponentInFlow { 20661 bool startsWithExplicitLineBreak() const { return false; } // FIXME: if it is block this can return true 20662 20663 Color foreground; 20664 Color background; 20665 20666 OperatingSystemFont font; // should NEVER be null 20667 20668 ubyte attributes; // underline, strike through, display on new block 20669 20670 version(Windows) 20671 const(wchar)[] content; 20672 else 20673 const(char)[] content; // this should NEVER have a newline, except at the end 20674 20675 RenderedComponent[] rendered; // entirely controlled by [rerender] 20676 20677 // could prolly put some spacing around it too like margin / padding 20678 20679 this(Color f, Color b, OperatingSystemFont font, ubyte attr, const(char)[] c) 20680 in { assert(font !is null); 20681 assert(!font.isNull); } 20682 do 20683 { 20684 this.foreground = f; 20685 this.background = b; 20686 this.font = font; 20687 20688 this.attributes = attr; 20689 version(Windows) { 20690 auto conversionFlags = 0;//WindowsStringConversionFlags.convertNewLines; 20691 auto sz = sizeOfConvertedWstring(c, conversionFlags); 20692 auto buffer = new wchar[](sz); 20693 this.content = makeWindowsString(c, buffer, conversionFlags); 20694 } else { 20695 this.content = c.dup; 20696 } 20697 } 20698 20699 void draw(ScreenPainter painter) { 20700 painter.setFont(this.font); 20701 painter.outlineColor = this.foreground; 20702 painter.fillColor = Color.transparent; 20703 foreach(rendered; this.rendered) { 20704 // the component works in term of baseline, 20705 // but the painter works in term of upper left bounding box 20706 // so need to translate that 20707 20708 if(this.background.a) { 20709 painter.fillColor = this.background; 20710 painter.outlineColor = this.background; 20711 20712 painter.drawRectangle(Point(rendered.startX, rendered.startY - this.font.ascent), Size(rendered.width, this.font.height)); 20713 20714 painter.outlineColor = this.foreground; 20715 painter.fillColor = Color.transparent; 20716 } 20717 20718 painter.drawText(Point(rendered.startX, rendered.startY - this.font.ascent), rendered.slice); 20719 20720 // FIXME: strike through, underline, highlight selection, etc. 20721 } 20722 } 20723 } 20724 20725 // I could split the parts into words on render 20726 // for easier word-wrap, each one being an unbreakable "inline-block" 20727 private TextFlowComponent[] parts; 20728 private int needsRerenderFrom; 20729 20730 void addPart(Color f, Color b, OperatingSystemFont font, ubyte attr, const(char)[] c) { 20731 // FIXME: needsRerenderFrom. Basically if the bounding box and baseline is the same as the previous thing, it can prolly just stop. 20732 parts ~= new TextFlowComponent(f, b, font, attr, c); 20733 } 20734 20735 static struct RenderedComponent { 20736 int startX; 20737 int startY; 20738 short width; 20739 // 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! 20740 // for individual chars in here you've gotta process on demand 20741 version(Windows) 20742 const(wchar)[] slice; 20743 else 20744 const(char)[] slice; 20745 } 20746 20747 20748 void rerender(Rectangle boundingBox) { 20749 Point baseline = boundingBox.upperLeft; 20750 20751 this.boundingBox.left = boundingBox.left; 20752 this.boundingBox.top = boundingBox.top; 20753 20754 auto remainingParts = parts; 20755 20756 int largestX; 20757 20758 20759 foreach(part; parts) 20760 part.font.prepareContext(window); 20761 scope(exit) 20762 foreach(part; parts) 20763 part.font.releaseContext(); 20764 20765 calculateNextLine: 20766 20767 int nextLineHeight = 0; 20768 int nextBiggestDescent = 0; 20769 20770 foreach(part; remainingParts) { 20771 auto height = part.font.ascent; 20772 if(height > nextLineHeight) 20773 nextLineHeight = height; 20774 if(part.font.descent > nextBiggestDescent) 20775 nextBiggestDescent = part.font.descent; 20776 if(part.content.length && part.content[$-1] == '\n') 20777 break; 20778 } 20779 20780 baseline.y += nextLineHeight; 20781 auto lineStart = baseline; 20782 20783 while(remainingParts.length) { 20784 remainingParts[0].rendered = null; 20785 20786 bool eol; 20787 if(remainingParts[0].content.length && remainingParts[0].content[$-1] == '\n') 20788 eol = true; 20789 20790 // FIXME: word wrap 20791 auto font = remainingParts[0].font; 20792 auto slice = remainingParts[0].content[0 .. $ - (eol ? 1 : 0)]; 20793 auto width = font.stringWidth(slice, window); 20794 remainingParts[0].rendered ~= RenderedComponent(baseline.x, baseline.y, cast(short) width, slice); 20795 20796 remainingParts = remainingParts[1 .. $]; 20797 baseline.x += width; 20798 20799 if(eol) { 20800 baseline.y += nextBiggestDescent; 20801 if(baseline.x > largestX) 20802 largestX = baseline.x; 20803 baseline.x = lineStart.x; 20804 goto calculateNextLine; 20805 } 20806 } 20807 20808 if(baseline.x > largestX) 20809 largestX = baseline.x; 20810 20811 this.boundingBox.right = largestX; 20812 this.boundingBox.bottom = baseline.y; 20813 } 20814 20815 // you must call rerender first! 20816 void draw(ScreenPainter painter) { 20817 foreach(part; parts) { 20818 part.draw(painter); 20819 } 20820 } 20821 20822 struct IdentifyResult { 20823 TextFlowComponent part; 20824 int charIndexInPart; 20825 int totalCharIndex = -1; // if this is -1, it just means the end 20826 20827 Rectangle boundingBox; 20828 } 20829 20830 IdentifyResult identify(Point pt, bool exact = false) { 20831 if(parts.length == 0) 20832 return IdentifyResult(null, 0); 20833 20834 if(pt.y < boundingBox.top) { 20835 if(exact) 20836 return IdentifyResult(null, 1); 20837 return IdentifyResult(parts[0], 0); 20838 } 20839 if(pt.y > boundingBox.bottom) { 20840 if(exact) 20841 return IdentifyResult(null, 2); 20842 return IdentifyResult(parts[$-1], cast(int) parts[$-1].content.length); 20843 } 20844 20845 int tci = 0; 20846 20847 // I should probably like binary search this or something... 20848 foreach(ref part; parts) { 20849 foreach(rendered; part.rendered) { 20850 auto rect = Rectangle(rendered.startX, rendered.startY - part.font.ascent, rendered.startX + rendered.width, rendered.startY + part.font.descent); 20851 if(rect.contains(pt)) { 20852 auto x = pt.x - rendered.startX; 20853 auto estimatedIdx = x / part.font.averageWidth; 20854 20855 if(estimatedIdx < 0) 20856 estimatedIdx = 0; 20857 20858 if(estimatedIdx > rendered.slice.length) 20859 estimatedIdx = cast(int) rendered.slice.length; 20860 20861 int idx; 20862 int x1, x2; 20863 if(part.font.isMonospace) { 20864 auto w = part.font.averageWidth; 20865 if(!exact && x > (estimatedIdx + 1) * w) 20866 return IdentifyResult(null, 4); 20867 idx = estimatedIdx; 20868 x1 = idx * w; 20869 x2 = (idx + 1) * w; 20870 } else { 20871 idx = estimatedIdx; 20872 20873 part.font.prepareContext(window); 20874 scope(exit) part.font.releaseContext(); 20875 20876 // int iterations; 20877 20878 while(true) { 20879 // iterations++; 20880 x1 = idx ? part.font.stringWidth(rendered.slice[0 .. idx - 1]) : 0; 20881 x2 = part.font.stringWidth(rendered.slice[0 .. idx]); // should be the maximum since `averageWidth` kinda lies. 20882 20883 x1 += rendered.startX; 20884 x2 += rendered.startX; 20885 20886 if(pt.x < x1) { 20887 if(idx == 0) { 20888 if(exact) 20889 return IdentifyResult(null, 6); 20890 else 20891 break; 20892 } 20893 idx--; 20894 } else if(pt.x > x2) { 20895 idx++; 20896 if(idx > rendered.slice.length) { 20897 if(exact) 20898 return IdentifyResult(null, 5); 20899 else 20900 break; 20901 } 20902 } else if(pt.x >= x1 && pt.x <= x2) { 20903 if(idx) 20904 idx--; // point it at the original index 20905 break; // we fit 20906 } 20907 } 20908 20909 // writeln(iterations) 20910 } 20911 20912 20913 return IdentifyResult(part, idx, tci + idx, Rectangle(x1, rect.top, x2, rect.bottom)); // FIXME: utf-8? 20914 } 20915 } 20916 tci += cast(int) part.content.length; // FIXME: utf-8? 20917 } 20918 return IdentifyResult(null, 3); 20919 } 20920 20921 Rectangle boundingBox; // only set after [rerender] 20922 20923 // text will be positioned around the exclusion zone 20924 static struct ExclusionZone { 20925 20926 } 20927 20928 ExclusionZone[] exclusionZones; 20929 } 20930 20931 20932 // Don't use this yet. When I'm happy with it, I will move it to the 20933 // regular module namespace. 20934 mixin template ExperimentalTextComponent() { 20935 20936 static: 20937 20938 alias Rectangle = arsd.color.Rectangle; 20939 20940 struct ForegroundColor { 20941 Color color; 20942 alias color this; 20943 20944 this(Color c) { 20945 color = c; 20946 } 20947 20948 this(int r, int g, int b, int a = 255) { 20949 color = Color(r, g, b, a); 20950 } 20951 20952 static ForegroundColor opDispatch(string s)() if(__traits(compiles, ForegroundColor(mixin("Color." ~ s)))) { 20953 return ForegroundColor(mixin("Color." ~ s)); 20954 } 20955 } 20956 20957 struct BackgroundColor { 20958 Color color; 20959 alias color this; 20960 20961 this(Color c) { 20962 color = c; 20963 } 20964 20965 this(int r, int g, int b, int a = 255) { 20966 color = Color(r, g, b, a); 20967 } 20968 20969 static BackgroundColor opDispatch(string s)() if(__traits(compiles, BackgroundColor(mixin("Color." ~ s)))) { 20970 return BackgroundColor(mixin("Color." ~ s)); 20971 } 20972 } 20973 20974 static class InlineElement { 20975 string text; 20976 20977 BlockElement containingBlock; 20978 20979 Color color = Color.black; 20980 Color backgroundColor = Color.transparent; 20981 ushort styles; 20982 20983 string font; 20984 int fontSize; 20985 20986 int lineHeight; 20987 20988 void* identifier; 20989 20990 Rectangle boundingBox; 20991 int[] letterXs; // FIXME: maybe i should do bounding boxes for every character 20992 20993 bool isMergeCompatible(InlineElement other) { 20994 return 20995 containingBlock is other.containingBlock && 20996 color == other.color && 20997 backgroundColor == other.backgroundColor && 20998 styles == other.styles && 20999 font == other.font && 21000 fontSize == other.fontSize && 21001 lineHeight == other.lineHeight && 21002 true; 21003 } 21004 21005 int xOfIndex(size_t index) { 21006 if(index < letterXs.length) 21007 return letterXs[index]; 21008 else 21009 return boundingBox.right; 21010 } 21011 21012 InlineElement clone() { 21013 auto ie = new InlineElement(); 21014 ie.tupleof = this.tupleof; 21015 return ie; 21016 } 21017 21018 InlineElement getPreviousInlineElement() { 21019 InlineElement prev = null; 21020 foreach(ie; this.containingBlock.parts) { 21021 if(ie is this) 21022 break; 21023 prev = ie; 21024 } 21025 if(prev is null) { 21026 BlockElement pb; 21027 BlockElement cb = this.containingBlock; 21028 moar: 21029 foreach(ie; this.containingBlock.containingLayout.blocks) { 21030 if(ie is cb) 21031 break; 21032 pb = ie; 21033 } 21034 if(pb is null) 21035 return null; 21036 if(pb.parts.length == 0) { 21037 cb = pb; 21038 goto moar; 21039 } 21040 21041 prev = pb.parts[$-1]; 21042 21043 } 21044 return prev; 21045 } 21046 21047 InlineElement getNextInlineElement() { 21048 InlineElement next = null; 21049 foreach(idx, ie; this.containingBlock.parts) { 21050 if(ie is this) { 21051 if(idx + 1 < this.containingBlock.parts.length) 21052 next = this.containingBlock.parts[idx + 1]; 21053 break; 21054 } 21055 } 21056 if(next is null) { 21057 BlockElement n; 21058 foreach(idx, ie; this.containingBlock.containingLayout.blocks) { 21059 if(ie is this.containingBlock) { 21060 if(idx + 1 < this.containingBlock.containingLayout.blocks.length) 21061 n = this.containingBlock.containingLayout.blocks[idx + 1]; 21062 break; 21063 } 21064 } 21065 if(n is null) 21066 return null; 21067 21068 if(n.parts.length) 21069 next = n.parts[0]; 21070 else {} // FIXME 21071 21072 } 21073 return next; 21074 } 21075 21076 } 21077 21078 // Block elements are used entirely for positioning inline elements, 21079 // which are the things that are actually drawn. 21080 class BlockElement { 21081 InlineElement[] parts; 21082 uint alignment; 21083 21084 int whiteSpace; // pre, pre-wrap, wrap 21085 21086 TextLayout containingLayout; 21087 21088 // inputs 21089 Point where; 21090 Size minimumSize; 21091 Size maximumSize; 21092 Rectangle[] excludedBoxes; // like if you want it to write around a floated image or something. Coordinates are relative to the bounding box. 21093 void* identifier; 21094 21095 Rectangle margin; 21096 Rectangle padding; 21097 21098 // outputs 21099 Rectangle[] boundingBoxes; 21100 } 21101 21102 struct TextIdentifyResult { 21103 InlineElement element; 21104 int offset; 21105 21106 private TextIdentifyResult fixupNewline() { 21107 if(element !is null && offset < element.text.length && element.text[offset] == '\n') { 21108 offset--; 21109 } else if(element !is null && offset == element.text.length && element.text.length > 1 && element.text[$-1] == '\n') { 21110 offset--; 21111 } 21112 return this; 21113 } 21114 } 21115 21116 class TextLayout { 21117 BlockElement[] blocks; 21118 Rectangle boundingBox_; 21119 Rectangle boundingBox() { return boundingBox_; } 21120 void boundingBox(Rectangle r) { 21121 if(r != boundingBox_) { 21122 boundingBox_ = r; 21123 layoutInvalidated = true; 21124 } 21125 } 21126 21127 Rectangle contentBoundingBox() { 21128 Rectangle r; 21129 foreach(block; blocks) 21130 foreach(ie; block.parts) { 21131 if(ie.boundingBox.right > r.right) 21132 r.right = ie.boundingBox.right; 21133 if(ie.boundingBox.bottom > r.bottom) 21134 r.bottom = ie.boundingBox.bottom; 21135 } 21136 return r; 21137 } 21138 21139 BlockElement[] getBlocks() { 21140 return blocks; 21141 } 21142 21143 InlineElement[] getTexts() { 21144 InlineElement[] elements; 21145 foreach(block; blocks) 21146 elements ~= block.parts; 21147 return elements; 21148 } 21149 21150 string getPlainText() { 21151 string text; 21152 foreach(block; blocks) 21153 foreach(part; block.parts) 21154 text ~= part.text; 21155 return text; 21156 } 21157 21158 string getHtml() { 21159 return null; // FIXME 21160 } 21161 21162 this(Rectangle boundingBox) { 21163 this.boundingBox = boundingBox; 21164 } 21165 21166 BlockElement addBlock(InlineElement after = null, Rectangle margin = Rectangle(0, 0, 0, 0), Rectangle padding = Rectangle(0, 0, 0, 0)) { 21167 auto be = new BlockElement(); 21168 be.containingLayout = this; 21169 if(after is null) 21170 blocks ~= be; 21171 else { 21172 foreach(idx, b; blocks) { 21173 if(b is after.containingBlock) { 21174 blocks = blocks[0 .. idx + 1] ~ be ~ blocks[idx + 1 .. $]; 21175 break; 21176 } 21177 } 21178 } 21179 return be; 21180 } 21181 21182 void clear() { 21183 blocks = null; 21184 selectionStart = selectionEnd = caret = Caret.init; 21185 } 21186 21187 void addText(Args...)(Args args) { 21188 if(blocks.length == 0) 21189 addBlock(); 21190 21191 InlineElement ie = new InlineElement(); 21192 foreach(idx, arg; args) { 21193 static if(is(typeof(arg) == ForegroundColor)) 21194 ie.color = arg; 21195 else static if(is(typeof(arg) == TextFormat)) { 21196 if(arg & 0x8000) // ~TextFormat.something turns it off 21197 ie.styles &= arg; 21198 else 21199 ie.styles |= arg; 21200 } else static if(is(typeof(arg) == string)) { 21201 static if(idx == 0 && args.length > 1) 21202 static assert(0, "Put styles before the string."); 21203 size_t lastLineIndex; 21204 foreach(cidx, char a; arg) { 21205 if(a == '\n') { 21206 ie.text = arg[lastLineIndex .. cidx + 1]; 21207 lastLineIndex = cidx + 1; 21208 ie.containingBlock = blocks[$-1]; 21209 blocks[$-1].parts ~= ie.clone; 21210 ie.text = null; 21211 } else { 21212 21213 } 21214 } 21215 21216 ie.text = arg[lastLineIndex .. $]; 21217 ie.containingBlock = blocks[$-1]; 21218 blocks[$-1].parts ~= ie.clone; 21219 caret = Caret(this, blocks[$-1].parts[$-1], cast(int) blocks[$-1].parts[$-1].text.length); 21220 } 21221 } 21222 21223 invalidateLayout(); 21224 } 21225 21226 void tryMerge(InlineElement into, InlineElement what) { 21227 if(!into.isMergeCompatible(what)) { 21228 return; // cannot merge, different configs 21229 } 21230 21231 // cool, can merge, bring text together... 21232 into.text ~= what.text; 21233 21234 // and remove what 21235 for(size_t a = 0; a < what.containingBlock.parts.length; a++) { 21236 if(what.containingBlock.parts[a] is what) { 21237 for(size_t i = a; i < what.containingBlock.parts.length - 1; i++) 21238 what.containingBlock.parts[i] = what.containingBlock.parts[i + 1]; 21239 what.containingBlock.parts = what.containingBlock.parts[0 .. $-1]; 21240 21241 } 21242 } 21243 21244 // FIXME: ensure no other carets have a reference to it 21245 } 21246 21247 /// exact = true means return null if no match. otherwise, get the closest one that makes sense for a mouse click. 21248 TextIdentifyResult identify(int x, int y, bool exact = false) { 21249 TextIdentifyResult inexactMatch; 21250 foreach(block; blocks) { 21251 foreach(part; block.parts) { 21252 if(x >= part.boundingBox.left && x < part.boundingBox.right && y >= part.boundingBox.top && y < part.boundingBox.bottom) { 21253 21254 // FIXME binary search 21255 int tidx; 21256 int lastX; 21257 foreach_reverse(idxo, lx; part.letterXs) { 21258 int idx = cast(int) idxo; 21259 if(lx <= x) { 21260 if(lastX && lastX - x < x - lx) 21261 tidx = idx + 1; 21262 else 21263 tidx = idx; 21264 break; 21265 } 21266 lastX = lx; 21267 } 21268 21269 return TextIdentifyResult(part, tidx).fixupNewline; 21270 } else if(!exact) { 21271 // we're not in the box, but are we on the same line? 21272 if(y >= part.boundingBox.top && y < part.boundingBox.bottom) 21273 inexactMatch = TextIdentifyResult(part, x == 0 ? 0 : cast(int) part.text.length); 21274 } 21275 } 21276 } 21277 21278 if(!exact && inexactMatch is TextIdentifyResult.init && blocks.length && blocks[$-1].parts.length) 21279 return TextIdentifyResult(blocks[$-1].parts[$-1], cast(int) blocks[$-1].parts[$-1].text.length).fixupNewline; 21280 21281 return exact ? TextIdentifyResult.init : inexactMatch.fixupNewline; 21282 } 21283 21284 void moveCaretToPixelCoordinates(int x, int y) { 21285 auto result = identify(x, y); 21286 caret.inlineElement = result.element; 21287 caret.offset = result.offset; 21288 } 21289 21290 void selectToPixelCoordinates(int x, int y) { 21291 auto result = identify(x, y); 21292 21293 if(y < caretLastDrawnY1) { 21294 // on a previous line, carat is selectionEnd 21295 selectionEnd = caret; 21296 21297 selectionStart = Caret(this, result.element, result.offset); 21298 } else if(y > caretLastDrawnY2) { 21299 // on a later line 21300 selectionStart = caret; 21301 21302 selectionEnd = Caret(this, result.element, result.offset); 21303 } else { 21304 // on the same line... 21305 if(x <= caretLastDrawnX) { 21306 selectionEnd = caret; 21307 selectionStart = Caret(this, result.element, result.offset); 21308 } else { 21309 selectionStart = caret; 21310 selectionEnd = Caret(this, result.element, result.offset); 21311 } 21312 21313 } 21314 } 21315 21316 21317 /// Call this if the inputs change. It will reflow everything 21318 void redoLayout(ScreenPainter painter) { 21319 //painter.setClipRectangle(boundingBox); 21320 auto pos = Point(boundingBox.left, boundingBox.top); 21321 21322 int lastHeight; 21323 void nl() { 21324 pos.x = boundingBox.left; 21325 pos.y += lastHeight; 21326 } 21327 foreach(block; blocks) { 21328 nl(); 21329 foreach(part; block.parts) { 21330 part.letterXs = null; 21331 21332 auto size = painter.textSize(part.text); 21333 version(Windows) 21334 if(part.text.length && part.text[$-1] == '\n') 21335 size.height /= 2; // windows counts the new line at the end, but we don't want that 21336 21337 part.boundingBox = Rectangle(pos.x, pos.y, pos.x + size.width, pos.y + size.height); 21338 21339 foreach(idx, char c; part.text) { 21340 // FIXME: unicode 21341 part.letterXs ~= painter.textSize(part.text[0 .. idx]).width + pos.x; 21342 } 21343 21344 pos.x += size.width; 21345 if(pos.x >= boundingBox.right) { 21346 pos.y += size.height; 21347 pos.x = boundingBox.left; 21348 lastHeight = 0; 21349 } else { 21350 lastHeight = size.height; 21351 } 21352 21353 if(part.text.length && part.text[$-1] == '\n') 21354 nl(); 21355 } 21356 } 21357 21358 layoutInvalidated = false; 21359 } 21360 21361 bool layoutInvalidated = true; 21362 void invalidateLayout() { 21363 layoutInvalidated = true; 21364 } 21365 21366 // FIXME: caret can remain sometimes when inserting 21367 // FIXME: inserting at the beginning once you already have something can eff it up. 21368 void drawInto(ScreenPainter painter, bool focused = false) { 21369 if(layoutInvalidated) 21370 redoLayout(painter); 21371 foreach(block; blocks) { 21372 foreach(part; block.parts) { 21373 painter.outlineColor = part.color; 21374 painter.fillColor = part.backgroundColor; 21375 21376 auto pos = part.boundingBox.upperLeft; 21377 auto size = part.boundingBox.size; 21378 21379 painter.drawText(pos, part.text); 21380 if(part.styles & TextFormat.underline) 21381 painter.drawLine(Point(pos.x, pos.y + size.height - 4), Point(pos.x + size.width, pos.y + size.height - 4)); 21382 if(part.styles & TextFormat.strikethrough) 21383 painter.drawLine(Point(pos.x, pos.y + size.height/2), Point(pos.x + size.width, pos.y + size.height/2)); 21384 } 21385 } 21386 21387 // on every redraw, I will force the caret to be 21388 // redrawn too, in order to eliminate perceived lag 21389 // when moving around with the mouse. 21390 eraseCaret(painter); 21391 21392 if(focused) { 21393 highlightSelection(painter); 21394 drawCaret(painter); 21395 } 21396 } 21397 21398 Color selectionXorColor = Color(255, 255, 127); 21399 21400 void highlightSelection(ScreenPainter painter) { 21401 if(selectionStart is selectionEnd) 21402 return; // no selection 21403 21404 if(selectionStart.inlineElement is null) return; 21405 if(selectionEnd.inlineElement is null) return; 21406 21407 assert(selectionStart.inlineElement !is null); 21408 assert(selectionEnd.inlineElement !is null); 21409 21410 painter.rasterOp = RasterOp.xor; 21411 painter.outlineColor = Color.transparent; 21412 painter.fillColor = selectionXorColor; 21413 21414 auto at = selectionStart.inlineElement; 21415 auto atOffset = selectionStart.offset; 21416 bool done; 21417 while(at) { 21418 auto box = at.boundingBox; 21419 if(atOffset < at.letterXs.length) 21420 box.left = at.letterXs[atOffset]; 21421 21422 if(at is selectionEnd.inlineElement) { 21423 if(selectionEnd.offset < at.letterXs.length) 21424 box.right = at.letterXs[selectionEnd.offset]; 21425 done = true; 21426 } 21427 21428 painter.drawRectangle(box.upperLeft, box.width, box.height); 21429 21430 if(done) 21431 break; 21432 21433 at = at.getNextInlineElement(); 21434 atOffset = 0; 21435 } 21436 } 21437 21438 int caretLastDrawnX, caretLastDrawnY1, caretLastDrawnY2; 21439 bool caretShowingOnScreen = false; 21440 void drawCaret(ScreenPainter painter) { 21441 //painter.setClipRectangle(boundingBox); 21442 int x, y1, y2; 21443 if(caret.inlineElement is null) { 21444 x = boundingBox.left; 21445 y1 = boundingBox.top + 2; 21446 y2 = boundingBox.top + painter.fontHeight; 21447 } else { 21448 x = caret.inlineElement.xOfIndex(caret.offset); 21449 y1 = caret.inlineElement.boundingBox.top + 2; 21450 y2 = caret.inlineElement.boundingBox.bottom - 2; 21451 } 21452 21453 if(caretShowingOnScreen && (x != caretLastDrawnX || y1 != caretLastDrawnY1 || y2 != caretLastDrawnY2)) 21454 eraseCaret(painter); 21455 21456 painter.pen = Pen(Color.white, 1); 21457 painter.rasterOp = RasterOp.xor; 21458 painter.drawLine( 21459 Point(x, y1), 21460 Point(x, y2) 21461 ); 21462 painter.rasterOp = RasterOp.normal; 21463 caretShowingOnScreen = !caretShowingOnScreen; 21464 21465 if(caretShowingOnScreen) { 21466 caretLastDrawnX = x; 21467 caretLastDrawnY1 = y1; 21468 caretLastDrawnY2 = y2; 21469 } 21470 } 21471 21472 Rectangle caretBoundingBox() { 21473 int x, y1, y2; 21474 if(caret.inlineElement is null) { 21475 x = boundingBox.left; 21476 y1 = boundingBox.top + 2; 21477 y2 = boundingBox.top + 16; 21478 } else { 21479 x = caret.inlineElement.xOfIndex(caret.offset); 21480 y1 = caret.inlineElement.boundingBox.top + 2; 21481 y2 = caret.inlineElement.boundingBox.bottom - 2; 21482 } 21483 21484 return Rectangle(x, y1, x + 1, y2); 21485 } 21486 21487 void eraseCaret(ScreenPainter painter) { 21488 //painter.setClipRectangle(boundingBox); 21489 if(!caretShowingOnScreen) return; 21490 painter.pen = Pen(Color.white, 1); 21491 painter.rasterOp = RasterOp.xor; 21492 painter.drawLine( 21493 Point(caretLastDrawnX, caretLastDrawnY1), 21494 Point(caretLastDrawnX, caretLastDrawnY2) 21495 ); 21496 21497 caretShowingOnScreen = false; 21498 painter.rasterOp = RasterOp.normal; 21499 } 21500 21501 /// Caret movement api 21502 /// These should give the user a logical result based on what they see on screen... 21503 /// thus they locate predominately by *pixels* not char index. (These will generally coincide with monospace fonts tho!) 21504 void moveUp() { 21505 if(caret.inlineElement is null) return; 21506 auto x = caret.inlineElement.xOfIndex(caret.offset); 21507 auto y = caret.inlineElement.boundingBox.top + 2; 21508 21509 y -= caret.inlineElement.boundingBox.bottom - caret.inlineElement.boundingBox.top; 21510 if(y < 0) 21511 return; 21512 21513 auto i = identify(x, y); 21514 21515 if(i.element) { 21516 caret.inlineElement = i.element; 21517 caret.offset = i.offset; 21518 } 21519 } 21520 void moveDown() { 21521 if(caret.inlineElement is null) return; 21522 auto x = caret.inlineElement.xOfIndex(caret.offset); 21523 auto y = caret.inlineElement.boundingBox.bottom - 2; 21524 21525 y += caret.inlineElement.boundingBox.bottom - caret.inlineElement.boundingBox.top; 21526 21527 auto i = identify(x, y); 21528 if(i.element) { 21529 caret.inlineElement = i.element; 21530 caret.offset = i.offset; 21531 } 21532 } 21533 void moveLeft() { 21534 if(caret.inlineElement is null) return; 21535 if(caret.offset) 21536 caret.offset--; 21537 else { 21538 auto p = caret.inlineElement.getPreviousInlineElement(); 21539 if(p) { 21540 caret.inlineElement = p; 21541 if(p.text.length && p.text[$-1] == '\n') 21542 caret.offset = cast(int) p.text.length - 1; 21543 else 21544 caret.offset = cast(int) p.text.length; 21545 } 21546 } 21547 } 21548 void moveRight() { 21549 if(caret.inlineElement is null) return; 21550 if(caret.offset < caret.inlineElement.text.length && caret.inlineElement.text[caret.offset] != '\n') { 21551 caret.offset++; 21552 } else { 21553 auto p = caret.inlineElement.getNextInlineElement(); 21554 if(p) { 21555 caret.inlineElement = p; 21556 caret.offset = 0; 21557 } 21558 } 21559 } 21560 void moveHome() { 21561 if(caret.inlineElement is null) return; 21562 auto x = 0; 21563 auto y = caret.inlineElement.boundingBox.top + 2; 21564 21565 auto i = identify(x, y); 21566 21567 if(i.element) { 21568 caret.inlineElement = i.element; 21569 caret.offset = i.offset; 21570 } 21571 } 21572 void moveEnd() { 21573 if(caret.inlineElement is null) return; 21574 auto x = int.max; 21575 auto y = caret.inlineElement.boundingBox.top + 2; 21576 21577 auto i = identify(x, y); 21578 21579 if(i.element) { 21580 caret.inlineElement = i.element; 21581 caret.offset = i.offset; 21582 } 21583 21584 } 21585 void movePageUp(ref Caret caret) {} 21586 void movePageDown(ref Caret caret) {} 21587 21588 void moveDocumentStart(ref Caret caret) { 21589 if(blocks.length && blocks[0].parts.length) 21590 caret = Caret(this, blocks[0].parts[0], 0); 21591 else 21592 caret = Caret.init; 21593 } 21594 21595 void moveDocumentEnd(ref Caret caret) { 21596 if(blocks.length) { 21597 auto parts = blocks[$-1].parts; 21598 if(parts.length) { 21599 caret = Caret(this, parts[$-1], cast(int) parts[$-1].text.length); 21600 } else { 21601 caret = Caret.init; 21602 } 21603 } else 21604 caret = Caret.init; 21605 } 21606 21607 void deleteSelection() { 21608 if(selectionStart is selectionEnd) 21609 return; 21610 21611 if(selectionStart.inlineElement is null) return; 21612 if(selectionEnd.inlineElement is null) return; 21613 21614 assert(selectionStart.inlineElement !is null); 21615 assert(selectionEnd.inlineElement !is null); 21616 21617 auto at = selectionStart.inlineElement; 21618 21619 if(selectionEnd.inlineElement is at) { 21620 // same element, need to chop out 21621 at.text = at.text[0 .. selectionStart.offset] ~ at.text[selectionEnd.offset .. $]; 21622 at.letterXs = at.letterXs[0 .. selectionStart.offset] ~ at.letterXs[selectionEnd.offset .. $]; 21623 selectionEnd.offset -= selectionEnd.offset - selectionStart.offset; 21624 } else { 21625 // different elements, we can do it with slicing 21626 at.text = at.text[0 .. selectionStart.offset]; 21627 if(selectionStart.offset < at.letterXs.length) 21628 at.letterXs = at.letterXs[0 .. selectionStart.offset]; 21629 21630 at = at.getNextInlineElement(); 21631 21632 while(at) { 21633 if(at is selectionEnd.inlineElement) { 21634 at.text = at.text[selectionEnd.offset .. $]; 21635 if(selectionEnd.offset < at.letterXs.length) 21636 at.letterXs = at.letterXs[selectionEnd.offset .. $]; 21637 selectionEnd.offset = 0; 21638 break; 21639 } else { 21640 auto cfd = at; 21641 cfd.text = null; // delete the whole thing 21642 21643 at = at.getNextInlineElement(); 21644 21645 if(cfd.text.length == 0) { 21646 // and remove cfd 21647 for(size_t a = 0; a < cfd.containingBlock.parts.length; a++) { 21648 if(cfd.containingBlock.parts[a] is cfd) { 21649 for(size_t i = a; i < cfd.containingBlock.parts.length - 1; i++) 21650 cfd.containingBlock.parts[i] = cfd.containingBlock.parts[i + 1]; 21651 cfd.containingBlock.parts = cfd.containingBlock.parts[0 .. $-1]; 21652 21653 } 21654 } 21655 } 21656 } 21657 } 21658 } 21659 21660 caret = selectionEnd; 21661 selectNone(); 21662 21663 invalidateLayout(); 21664 21665 } 21666 21667 /// Plain text editing api. These work at the current caret inside the selected inline element. 21668 void insert(in char[] text) { 21669 foreach(dchar ch; text) 21670 insert(ch); 21671 } 21672 /// ditto 21673 void insert(dchar ch) { 21674 21675 bool selectionDeleted = false; 21676 if(selectionStart !is selectionEnd) { 21677 deleteSelection(); 21678 selectionDeleted = true; 21679 } 21680 21681 if(ch == 127) { 21682 delete_(); 21683 return; 21684 } 21685 if(ch == 8) { 21686 if(!selectionDeleted) 21687 backspace(); 21688 return; 21689 } 21690 21691 invalidateLayout(); 21692 21693 if(ch == 13) ch = 10; 21694 auto e = caret.inlineElement; 21695 if(e is null) { 21696 addText("" ~ cast(char) ch) ; // FIXME 21697 return; 21698 } 21699 21700 if(caret.offset == e.text.length) { 21701 e.text ~= cast(char) ch; // FIXME 21702 caret.offset++; 21703 if(ch == 10) { 21704 auto c = caret.inlineElement.clone; 21705 c.text = null; 21706 c.letterXs = null; 21707 insertPartAfter(c,e); 21708 caret = Caret(this, c, 0); 21709 } 21710 } else { 21711 // FIXME cast char sucks 21712 if(ch == 10) { 21713 auto c = caret.inlineElement.clone; 21714 c.text = e.text[caret.offset .. $]; 21715 if(caret.offset < c.letterXs.length) 21716 c.letterXs = e.letterXs[caret.offset .. $]; // FIXME boundingBox 21717 e.text = e.text[0 .. caret.offset] ~ cast(char) ch; 21718 if(caret.offset <= e.letterXs.length) { 21719 e.letterXs = e.letterXs[0 .. caret.offset] ~ 0; // FIXME bounding box 21720 } 21721 insertPartAfter(c,e); 21722 caret = Caret(this, c, 0); 21723 } else { 21724 e.text = e.text[0 .. caret.offset] ~ cast(char) ch ~ e.text[caret.offset .. $]; 21725 caret.offset++; 21726 } 21727 } 21728 } 21729 21730 void insertPartAfter(InlineElement what, InlineElement where) { 21731 foreach(idx, p; where.containingBlock.parts) { 21732 if(p is where) { 21733 if(idx + 1 == where.containingBlock.parts.length) 21734 where.containingBlock.parts ~= what; 21735 else 21736 where.containingBlock.parts = where.containingBlock.parts[0 .. idx + 1] ~ what ~ where.containingBlock.parts[idx + 1 .. $]; 21737 return; 21738 } 21739 } 21740 } 21741 21742 void cleanupStructures() { 21743 for(size_t i = 0; i < blocks.length; i++) { 21744 auto block = blocks[i]; 21745 for(size_t a = 0; a < block.parts.length; a++) { 21746 auto part = block.parts[a]; 21747 if(part.text.length == 0) { 21748 for(size_t b = a; b < block.parts.length - 1; b++) 21749 block.parts[b] = block.parts[b+1]; 21750 block.parts = block.parts[0 .. $-1]; 21751 } 21752 } 21753 if(block.parts.length == 0) { 21754 for(size_t a = i; a < blocks.length - 1; a++) 21755 blocks[a] = blocks[a+1]; 21756 blocks = blocks[0 .. $-1]; 21757 } 21758 } 21759 } 21760 21761 void backspace() { 21762 try_again: 21763 auto e = caret.inlineElement; 21764 if(e is null) 21765 return; 21766 if(caret.offset == 0) { 21767 auto prev = e.getPreviousInlineElement(); 21768 if(prev is null) 21769 return; 21770 auto newOffset = cast(int) prev.text.length; 21771 tryMerge(prev, e); 21772 caret.inlineElement = prev; 21773 caret.offset = prev is null ? 0 : newOffset; 21774 21775 goto try_again; 21776 } else if(caret.offset == e.text.length) { 21777 e.text = e.text[0 .. $-1]; 21778 caret.offset--; 21779 } else { 21780 e.text = e.text[0 .. caret.offset - 1] ~ e.text[caret.offset .. $]; 21781 caret.offset--; 21782 } 21783 //cleanupStructures(); 21784 21785 invalidateLayout(); 21786 } 21787 void delete_() { 21788 if(selectionStart !is selectionEnd) 21789 deleteSelection(); 21790 else { 21791 auto before = caret; 21792 moveRight(); 21793 if(caret != before) { 21794 backspace(); 21795 } 21796 } 21797 21798 invalidateLayout(); 21799 } 21800 void overstrike() {} 21801 21802 /// Selection API. See also: caret movement. 21803 void selectAll() { 21804 moveDocumentStart(selectionStart); 21805 moveDocumentEnd(selectionEnd); 21806 } 21807 bool selectNone() { 21808 if(selectionStart != selectionEnd) { 21809 selectionStart = selectionEnd = Caret.init; 21810 return true; 21811 } 21812 return false; 21813 } 21814 21815 /// Rich text editing api. These allow you to manipulate the meta data of the current element and add new elements. 21816 /// They will modify the current selection if there is one and will splice one in if needed. 21817 void changeAttributes() {} 21818 21819 21820 /// Text search api. They manipulate the selection and/or caret. 21821 void findText(string text) {} 21822 void findIndex(size_t textIndex) {} 21823 21824 // sample event handlers 21825 21826 void handleEvent(KeyEvent event) { 21827 //if(event.type == KeyEvent.Type.KeyPressed) { 21828 21829 //} 21830 } 21831 21832 void handleEvent(dchar ch) { 21833 21834 } 21835 21836 void handleEvent(MouseEvent event) { 21837 21838 } 21839 21840 bool contentEditable; // can it be edited? 21841 bool contentCaretable; // is there a caret/cursor that moves around in there? 21842 bool contentSelectable; // selectable? 21843 21844 Caret caret; 21845 Caret selectionStart; 21846 Caret selectionEnd; 21847 21848 bool insertMode; 21849 } 21850 21851 struct Caret { 21852 TextLayout layout; 21853 InlineElement inlineElement; 21854 int offset; 21855 } 21856 21857 enum TextFormat : ushort { 21858 // decorations 21859 underline = 1, 21860 strikethrough = 2, 21861 21862 // font selectors 21863 21864 bold = 0x4000 | 1, // weight 700 21865 light = 0x4000 | 2, // weight 300 21866 veryBoldOrLight = 0x4000 | 4, // weight 100 with light, weight 900 with bold 21867 // bold | light is really invalid but should give weight 500 21868 // veryBoldOrLight without one of the others should just give the default for the font; it should be ignored. 21869 21870 italic = 0x4000 | 8, 21871 smallcaps = 0x4000 | 16, 21872 } 21873 21874 void* findFont(string family, int weight, TextFormat formats) { 21875 return null; 21876 } 21877 21878 } 21879 21880 /++ 21881 $(PITFALL This is not yet stable and may break in future versions without notice.) 21882 21883 History: 21884 Added February 19, 2021 21885 +/ 21886 /// Group: drag_and_drop 21887 interface DropHandler { 21888 /++ 21889 Called when the drag enters the handler's area. 21890 +/ 21891 DragAndDropAction dragEnter(DropPackage*); 21892 /++ 21893 Called when the drag leaves the handler's area or is 21894 cancelled. You should free your resources when this is called. 21895 +/ 21896 void dragLeave(); 21897 /++ 21898 Called continually as the drag moves over the handler's area. 21899 21900 Returns: feedback to the dragger 21901 +/ 21902 DropParameters dragOver(Point pt); 21903 /++ 21904 The user dropped the data and you should process it now. You can 21905 access the data through the given [DropPackage]. 21906 +/ 21907 void drop(scope DropPackage*); 21908 /++ 21909 Called when the drop is complete. You should free whatever temporary 21910 resources you were using. It is often reasonable to simply forward 21911 this call to [dragLeave]. 21912 +/ 21913 void finish(); 21914 21915 /++ 21916 Parameters returned by [DropHandler.drop]. 21917 +/ 21918 static struct DropParameters { 21919 /++ 21920 Acceptable action over this area. 21921 +/ 21922 DragAndDropAction action; 21923 /++ 21924 Rectangle, in client coordinates, where the dragger can expect the same result during this drag session and thus need not ask again. 21925 21926 If you leave this as Rectangle.init, the dragger will continue to ask and this can waste resources. 21927 +/ 21928 Rectangle consistentWithin; 21929 } 21930 } 21931 21932 /++ 21933 History: 21934 Added February 19, 2021 21935 +/ 21936 /// Group: drag_and_drop 21937 enum DragAndDropAction { 21938 none = 0, 21939 copy, 21940 move, 21941 link, 21942 ask, 21943 custom 21944 } 21945 21946 /++ 21947 An opaque structure representing dropped data. It contains 21948 private, platform-specific data that your `drop` function 21949 should simply forward. 21950 21951 $(PITFALL This is not yet stable and may break in future versions without notice.) 21952 21953 History: 21954 Added February 19, 2021 21955 +/ 21956 /// Group: drag_and_drop 21957 struct DropPackage { 21958 /++ 21959 Lists the available formats as magic numbers. You should compare these 21960 against looked-up formats (see [DraggableData.getFormatId]) you know you support and can 21961 understand the passed data. 21962 +/ 21963 DraggableData.FormatId[] availableFormats() { 21964 version(X11) { 21965 return xFormats; 21966 } else version(Windows) { 21967 if(pDataObj is null) 21968 return null; 21969 21970 typeof(return) ret; 21971 21972 IEnumFORMATETC ef; 21973 if(pDataObj.EnumFormatEtc(DATADIR.DATADIR_GET, &ef) == S_OK) { 21974 FORMATETC fmt; 21975 ULONG fetched; 21976 while(ef.Next(1, &fmt, &fetched) == S_OK) { 21977 if(fetched == 0) 21978 break; 21979 21980 if(fmt.lindex != -1) 21981 continue; 21982 if(fmt.dwAspect != DVASPECT.DVASPECT_CONTENT) 21983 continue; 21984 if(!(fmt.tymed & TYMED.TYMED_HGLOBAL)) 21985 continue; 21986 21987 ret ~= fmt.cfFormat; 21988 } 21989 } 21990 21991 return ret; 21992 } else throw new NotYetImplementedException(); 21993 } 21994 21995 /++ 21996 Gets data from the drop and optionally accepts it. 21997 21998 Returns: 21999 void because the data is fed asynchronously through the `dg` parameter. 22000 22001 Params: 22002 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. 22003 22004 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. 22005 22006 Calling `getData` again after accepting a drop is not permitted. 22007 22008 format = the format you want, from [availableFormats]. Use [DraggableData.getFormatId] to convert from a MIME string or well-known standard format. 22009 22010 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. 22011 22012 Throws: 22013 if `format` was not compatible with the [availableFormats] or if the drop has already been accepted. 22014 22015 History: 22016 Included in first release of [DropPackage]. 22017 +/ 22018 void getData(DragAndDropAction acceptedAction, DraggableData.FormatId format, void delegate(scope ubyte[] data) dg) { 22019 version(X11) { 22020 22021 auto display = XDisplayConnection.get(); 22022 auto selectionAtom = GetAtom!"XdndSelection"(display); 22023 auto best = format; 22024 22025 static class X11GetSelectionHandler_Drop : X11GetSelectionHandler { 22026 22027 XDisplay* display; 22028 Atom selectionAtom; 22029 DraggableData.FormatId best; 22030 DraggableData.FormatId format; 22031 void delegate(scope ubyte[] data) dg; 22032 DragAndDropAction acceptedAction; 22033 Window sourceWindow; 22034 SimpleWindow win; 22035 this(XDisplay* display, SimpleWindow win, Window sourceWindow, DraggableData.FormatId format, Atom selectionAtom, DraggableData.FormatId best, void delegate(scope ubyte[] data) dg, DragAndDropAction acceptedAction) { 22036 this.display = display; 22037 this.win = win; 22038 this.sourceWindow = sourceWindow; 22039 this.format = format; 22040 this.selectionAtom = selectionAtom; 22041 this.best = best; 22042 this.dg = dg; 22043 this.acceptedAction = acceptedAction; 22044 } 22045 22046 22047 mixin X11GetSelectionHandler_Basics; 22048 22049 void handleData(Atom target, in ubyte[] data) { 22050 //if(target == GetAtom!"UTF8_STRING"(XDisplayConnection.get) || target == XA_STRING || target == GetAtom!"text/plain"(XDisplayConnection.get)) 22051 22052 dg(cast(ubyte[]) data); 22053 22054 if(acceptedAction != DragAndDropAction.none) { 22055 auto display = XDisplayConnection.get; 22056 22057 XClientMessageEvent xclient; 22058 22059 xclient.type = EventType.ClientMessage; 22060 xclient.window = sourceWindow; 22061 xclient.message_type = GetAtom!"XdndFinished"(display); 22062 xclient.format = 32; 22063 xclient.data.l[0] = win.impl.window; 22064 xclient.data.l[1] = 1; // drop successful 22065 xclient.data.l[2] = dndActionAtom(display, acceptedAction); 22066 22067 XSendEvent( 22068 display, 22069 sourceWindow, 22070 false, 22071 EventMask.NoEventMask, 22072 cast(XEvent*) &xclient 22073 ); 22074 22075 XFlush(display); 22076 } 22077 } 22078 22079 Atom findBestFormat(Atom[] answer) { 22080 Atom best = None; 22081 foreach(option; answer) { 22082 if(option == format) { 22083 best = option; 22084 break; 22085 } 22086 /* 22087 if(option == GetAtom!"UTF8_STRING"(XDisplayConnection.get)) { 22088 best = option; 22089 break; 22090 } else if(option == XA_STRING) { 22091 best = option; 22092 } else if(option == GetAtom!"text/plain"(XDisplayConnection.get)) { 22093 best = option; 22094 } 22095 */ 22096 } 22097 return best; 22098 } 22099 } 22100 22101 win.impl.getSelectionHandlers[selectionAtom] = new X11GetSelectionHandler_Drop(display, win, sourceWindow, format, selectionAtom, best, dg, acceptedAction); 22102 22103 XConvertSelection(display, selectionAtom, best, GetAtom!("SDD_DATA", true)(display), win.impl.window, dataTimestamp); 22104 22105 } else version(Windows) { 22106 22107 // clean up like DragLeave 22108 // pass effect back up 22109 22110 FORMATETC t; 22111 assert(format >= 0 && format <= ushort.max); 22112 t.cfFormat = cast(ushort) format; 22113 t.lindex = -1; 22114 t.dwAspect = DVASPECT.DVASPECT_CONTENT; 22115 t.tymed = TYMED.TYMED_HGLOBAL; 22116 22117 STGMEDIUM m; 22118 22119 if(pDataObj.GetData(&t, &m) != S_OK) { 22120 // fail 22121 } else { 22122 // succeed, take the data and clean up 22123 22124 // FIXME: ensure it is legit HGLOBAL 22125 auto handle = m.hGlobal; 22126 22127 if(handle) { 22128 auto sz = GlobalSize(handle); 22129 if(auto ptr = cast(ubyte*) GlobalLock(handle)) { 22130 scope(exit) GlobalUnlock(handle); 22131 scope(exit) GlobalFree(handle); 22132 22133 auto data = ptr[0 .. sz]; 22134 22135 dg(data); 22136 } 22137 } 22138 } 22139 } 22140 } 22141 22142 private: 22143 22144 version(X11) { 22145 SimpleWindow win; 22146 Window sourceWindow; 22147 Time dataTimestamp; 22148 22149 Atom[] xFormats; 22150 } 22151 version(Windows) { 22152 IDataObject pDataObj; 22153 } 22154 } 22155 22156 /++ 22157 A generic helper base class for making a drop handler with a preference list of custom types. 22158 This is the base for [TextDropHandler] and [FilesDropHandler] and you can use it for your own 22159 droppers too. 22160 22161 It assumes the whole window it used, but you can subclass to change that. 22162 22163 $(PITFALL This is not yet stable and may break in future versions without notice.) 22164 22165 History: 22166 Added February 19, 2021 22167 +/ 22168 /// Group: drag_and_drop 22169 class GenericDropHandlerBase : DropHandler { 22170 // no fancy state here so no need to do anything here 22171 void finish() { } 22172 void dragLeave() { } 22173 22174 private DragAndDropAction acceptedAction; 22175 private DraggableData.FormatId acceptedFormat; 22176 private void delegate(scope ubyte[]) acceptedHandler; 22177 22178 struct FormatHandler { 22179 DraggableData.FormatId format; 22180 void delegate(scope ubyte[]) handler; 22181 } 22182 22183 protected abstract FormatHandler[] formatHandlers(); 22184 22185 DragAndDropAction dragEnter(DropPackage* pkg) { 22186 debug(sdpy_dnd) { foreach(fmt; pkg.availableFormats()) writeln(fmt, " ", DraggableData.getFormatName(fmt)); } 22187 foreach(fmt; formatHandlers()) 22188 foreach(f; pkg.availableFormats()) 22189 if(f == fmt.format) { 22190 acceptedFormat = f; 22191 acceptedHandler = fmt.handler; 22192 return acceptedAction = DragAndDropAction.copy; 22193 } 22194 return acceptedAction = DragAndDropAction.none; 22195 } 22196 DropParameters dragOver(Point pt) { 22197 return DropParameters(acceptedAction); 22198 } 22199 22200 void drop(scope DropPackage* dropPackage) { 22201 if(!acceptedFormat || acceptedHandler is null) { 22202 debug(sdpy_dnd) { writeln("drop called w/ handler ", acceptedHandler, " and format ", acceptedFormat); } 22203 return; // prolly shouldn't happen anyway... 22204 } 22205 22206 dropPackage.getData(acceptedAction, acceptedFormat, acceptedHandler); 22207 } 22208 } 22209 22210 /++ 22211 A simple handler for making your window accept drops of plain text. 22212 22213 $(PITFALL This is not yet stable and may break in future versions without notice.) 22214 22215 History: 22216 Added February 22, 2021 22217 +/ 22218 /// Group: drag_and_drop 22219 class TextDropHandler : GenericDropHandlerBase { 22220 private void delegate(in char[] text) dg; 22221 22222 /++ 22223 22224 +/ 22225 this(void delegate(in char[] text) dg) { 22226 this.dg = dg; 22227 } 22228 22229 protected override FormatHandler[] formatHandlers() { 22230 version(X11) 22231 return [ 22232 FormatHandler(GetAtom!"UTF8_STRING"(XDisplayConnection.get), &translator), 22233 FormatHandler(GetAtom!"text/plain;charset=utf-8"(XDisplayConnection.get), &translator), 22234 ]; 22235 else version(Windows) 22236 return [ 22237 FormatHandler(CF_UNICODETEXT, &translator), 22238 ]; 22239 else throw new NotYetImplementedException(); 22240 } 22241 22242 private void translator(scope ubyte[] data) { 22243 version(X11) 22244 dg(cast(char[]) data); 22245 else version(Windows) 22246 dg(makeUtf8StringFromWindowsString(cast(wchar[]) data)); 22247 } 22248 } 22249 22250 /++ 22251 A simple handler for making your window accept drops of files, issued to you as file names. 22252 22253 $(PITFALL This is not yet stable and may break in future versions without notice.) 22254 22255 History: 22256 Added February 22, 2021 22257 +/ 22258 /// Group: drag_and_drop 22259 22260 class FilesDropHandler : GenericDropHandlerBase { 22261 private void delegate(in char[][]) dg; 22262 22263 /++ 22264 22265 +/ 22266 this(void delegate(in char[][] fileNames) dg) { 22267 this.dg = dg; 22268 } 22269 22270 protected override FormatHandler[] formatHandlers() { 22271 version(X11) 22272 return [ 22273 FormatHandler(GetAtom!"text/uri-list"(XDisplayConnection.get), &translator), 22274 ]; 22275 else version(Windows) 22276 return [ 22277 FormatHandler(CF_HDROP, &translator), 22278 ]; 22279 else throw new NotYetImplementedException(); 22280 } 22281 22282 private void translator(scope ubyte[] data) @system { 22283 version(X11) { 22284 char[] listString = cast(char[]) data; 22285 char[][16] buffer; 22286 int count; 22287 char[][] result = buffer[]; 22288 22289 void commit(char[] s) { 22290 if(count == result.length) 22291 result.length += 16; 22292 if(s.length > 7 && s[0 ..7] == "file://") 22293 s = s[7 .. $]; // FIXME: also may need to trim out the host and do some entity decoding 22294 result[count++] = s; 22295 } 22296 22297 size_t last; 22298 foreach(idx, char c; listString) { 22299 if(c == '\n') { 22300 commit(listString[last .. idx - 1]); // a \r 22301 last = idx + 1; // a \n 22302 } 22303 } 22304 22305 if(last < listString.length) { 22306 commit(listString[last .. $]); 22307 } 22308 22309 // FIXME: they are uris now, should I translate it to local file names? 22310 // of course the host name is supposed to be there cuz of X rokking... 22311 22312 dg(result[0 .. count]); 22313 } else version(Windows) { 22314 22315 static struct DROPFILES { 22316 DWORD pFiles; 22317 POINT pt; 22318 BOOL fNC; 22319 BOOL fWide; 22320 } 22321 22322 22323 const(char)[][16] buffer; 22324 int count; 22325 const(char)[][] result = buffer[]; 22326 size_t last; 22327 22328 void commitA(in char[] stuff) { 22329 if(count == result.length) 22330 result.length += 16; 22331 result[count++] = stuff; 22332 } 22333 22334 void commitW(in wchar[] stuff) { 22335 commitA(makeUtf8StringFromWindowsString(stuff)); 22336 } 22337 22338 void magic(T)(T chars) { 22339 size_t idx; 22340 while(chars[idx]) { 22341 last = idx; 22342 while(chars[idx]) { 22343 idx++; 22344 } 22345 static if(is(T == char*)) 22346 commitA(chars[last .. idx]); 22347 else 22348 commitW(chars[last .. idx]); 22349 idx++; 22350 } 22351 } 22352 22353 auto df = cast(DROPFILES*) data.ptr; 22354 if(df.fWide) { 22355 wchar* chars = cast(wchar*) (data.ptr + df.pFiles); 22356 magic(chars); 22357 } else { 22358 char* chars = cast(char*) (data.ptr + df.pFiles); 22359 magic(chars); 22360 } 22361 dg(result[0 .. count]); 22362 } 22363 else throw new NotYetImplementedException(); 22364 } 22365 } 22366 22367 /++ 22368 Interface to describe data being dragged. See also [draggable] helper function. 22369 22370 $(PITFALL This is not yet stable and may break in future versions without notice.) 22371 22372 History: 22373 Added February 19, 2021 22374 +/ 22375 interface DraggableData { 22376 version(X11) 22377 alias FormatId = Atom; 22378 else 22379 alias FormatId = uint; 22380 /++ 22381 Gets the platform-specific FormatId associated with the given named format. 22382 22383 This may be a MIME type, but may also be other various strings defined by the 22384 programs you want to interoperate with. 22385 22386 FIXME: sdpy needs to offer data adapter things that look for compatible formats 22387 and convert it to some particular type for you. 22388 +/ 22389 static FormatId getFormatId(string name)() { 22390 version(X11) 22391 return GetAtom!name(XDisplayConnection.get); 22392 else version(Windows) { 22393 static UINT cache; 22394 if(!cache) 22395 cache = RegisterClipboardFormatA(name); 22396 return cache; 22397 } else 22398 throw new NotYetImplementedException(); 22399 } 22400 22401 /++ 22402 Looks up a string to represent the name for the given format, if there is one. 22403 22404 You should avoid using this function because it is slow. It is provided more for 22405 debugging than for primary use. 22406 +/ 22407 static string getFormatName(FormatId format) { 22408 version(X11) { 22409 if(format == 0) 22410 return "None"; 22411 else 22412 return getAtomName(format, XDisplayConnection.get); 22413 } else version(Windows) { 22414 switch(format) { 22415 case CF_UNICODETEXT: return "CF_UNICODETEXT"; 22416 case CF_DIBV5: return "CF_DIBV5"; 22417 case CF_RIFF: return "CF_RIFF"; 22418 case CF_WAVE: return "CF_WAVE"; 22419 case CF_HDROP: return "CF_HDROP"; 22420 default: 22421 char[1024] name; 22422 auto count = GetClipboardFormatNameA(format, name.ptr, name.length); 22423 return name[0 .. count].idup; 22424 } 22425 } else throw new NotYetImplementedException(); 22426 } 22427 22428 FormatId[] availableFormats(); 22429 // Return the slice of data you filled, empty slice if done. 22430 // this is to support the incremental thing 22431 ubyte[] getData(FormatId format, return scope ubyte[] data); 22432 22433 size_t dataLength(FormatId format); 22434 } 22435 22436 /++ 22437 $(PITFALL This is not yet stable and may break in future versions without notice.) 22438 22439 History: 22440 Added February 19, 2021 22441 +/ 22442 DraggableData draggable(string s) { 22443 version(X11) 22444 return new class X11SetSelectionHandler_Text, DraggableData { 22445 this() { 22446 super(s); 22447 } 22448 22449 override FormatId[] availableFormats() { 22450 return X11SetSelectionHandler_Text.availableFormats(); 22451 } 22452 22453 override ubyte[] getData(FormatId format, return scope ubyte[] data) { 22454 return X11SetSelectionHandler_Text.getData(format, data); 22455 } 22456 22457 size_t dataLength(FormatId format) { 22458 return s.length; 22459 } 22460 }; 22461 else version(Windows) 22462 return new class DraggableData { 22463 FormatId[] availableFormats() { 22464 return [CF_UNICODETEXT]; 22465 } 22466 22467 ubyte[] getData(FormatId format, return scope ubyte[] data) { 22468 return cast(ubyte[]) makeWindowsString(s, cast(wchar[]) data, WindowsStringConversionFlags.convertNewLines | WindowsStringConversionFlags.zeroTerminate); 22469 } 22470 22471 size_t dataLength(FormatId format) { 22472 return sizeOfConvertedWstring(s, WindowsStringConversionFlags.convertNewLines | WindowsStringConversionFlags.zeroTerminate) * wchar.sizeof; 22473 } 22474 }; 22475 else 22476 throw new NotYetImplementedException(); 22477 } 22478 22479 /++ 22480 $(PITFALL This is not yet stable and may break in future versions without notice.) 22481 22482 History: 22483 Added February 19, 2021 22484 +/ 22485 /// Group: drag_and_drop 22486 int doDragDrop(SimpleWindow window, DraggableData handler, DragAndDropAction action = DragAndDropAction.copy) 22487 in { 22488 assert(window !is null); 22489 assert(handler !is null); 22490 } 22491 do 22492 { 22493 version(X11) { 22494 auto sh = cast(X11SetSelectionHandler) handler; 22495 if(sh is null) { 22496 // gotta make my own adapter. 22497 sh = new class X11SetSelectionHandler { 22498 mixin X11SetSelectionHandler_Basics; 22499 22500 Atom[] availableFormats() { return handler.availableFormats(); } 22501 ubyte[] getData(Atom format, return scope ubyte[] data) { 22502 return handler.getData(format, data); 22503 } 22504 22505 // since the drop selection is only ever used once it isn't important 22506 // to reset it. 22507 void done() {} 22508 }; 22509 } 22510 return doDragDropX11(window, sh, action); 22511 } else version(Windows) { 22512 return doDragDropWindows(window, handler, action); 22513 } else throw new NotYetImplementedException(); 22514 } 22515 22516 version(Windows) 22517 private int doDragDropWindows(SimpleWindow window, DraggableData handler, DragAndDropAction action = DragAndDropAction.copy) { 22518 IDataObject obj = new class IDataObject { 22519 ULONG refCount; 22520 ULONG AddRef() { 22521 return ++refCount; 22522 } 22523 ULONG Release() { 22524 return --refCount; 22525 } 22526 HRESULT QueryInterface(const (IID)*riid, LPVOID *ppv) { 22527 if (IID_IUnknown == *riid) { 22528 *ppv = cast(void*) cast(IUnknown) this; 22529 } 22530 else if (IID_IDataObject == *riid) { 22531 *ppv = cast(void*) cast(IDataObject) this; 22532 } 22533 else { 22534 *ppv = null; 22535 return E_NOINTERFACE; 22536 } 22537 22538 AddRef(); 22539 return NOERROR; 22540 } 22541 22542 HRESULT DAdvise(FORMATETC* pformatetc, DWORD advf, IAdviseSink pAdvSink, DWORD* pdwConnection) { 22543 // writeln("Advise"); 22544 return E_NOTIMPL; 22545 } 22546 HRESULT DUnadvise(DWORD dwConnection) { 22547 return E_NOTIMPL; 22548 } 22549 HRESULT EnumDAdvise(IEnumSTATDATA* ppenumAdvise) { 22550 // writeln("EnumDAdvise"); 22551 return OLE_E_ADVISENOTSUPPORTED; 22552 } 22553 // tell what formats it supports 22554 22555 FORMATETC[] types; 22556 this() { 22557 FORMATETC t; 22558 foreach(ty; handler.availableFormats()) { 22559 assert(ty <= ushort.max && ty >= 0); 22560 t.cfFormat = cast(ushort) ty; 22561 t.lindex = -1; 22562 t.dwAspect = DVASPECT.DVASPECT_CONTENT; 22563 t.tymed = TYMED.TYMED_HGLOBAL; 22564 } 22565 types ~= t; 22566 } 22567 HRESULT EnumFormatEtc(DWORD dwDirection, IEnumFORMATETC* ppenumFormatEtc) { 22568 if(dwDirection == DATADIR.DATADIR_GET) { 22569 *ppenumFormatEtc = new class IEnumFORMATETC { 22570 ULONG refCount; 22571 ULONG AddRef() { 22572 return ++refCount; 22573 } 22574 ULONG Release() { 22575 return --refCount; 22576 } 22577 HRESULT QueryInterface(const (IID)*riid, LPVOID *ppv) { 22578 if (IID_IUnknown == *riid) { 22579 *ppv = cast(void*) cast(IUnknown) this; 22580 } 22581 else if (IID_IEnumFORMATETC == *riid) { 22582 *ppv = cast(void*) cast(IEnumFORMATETC) this; 22583 } 22584 else { 22585 *ppv = null; 22586 return E_NOINTERFACE; 22587 } 22588 22589 AddRef(); 22590 return NOERROR; 22591 } 22592 22593 22594 int pos; 22595 this() { 22596 pos = 0; 22597 } 22598 22599 HRESULT Clone(IEnumFORMATETC* ppenum) { 22600 // writeln("clone"); 22601 return E_NOTIMPL; // FIXME 22602 } 22603 22604 // Caller is responsible for freeing memory 22605 HRESULT Next(ULONG celt, FORMATETC* rgelt, ULONG* pceltFetched) { 22606 // fetched may be null if celt is one 22607 if(celt != 1) 22608 return E_NOTIMPL; // FIXME 22609 22610 if(celt + pos > types.length) 22611 return S_FALSE; 22612 22613 *rgelt = types[pos++]; 22614 22615 if(pceltFetched !is null) 22616 *pceltFetched = 1; 22617 22618 // writeln("ok celt ", celt); 22619 return S_OK; 22620 } 22621 22622 HRESULT Reset() { 22623 pos = 0; 22624 return S_OK; 22625 } 22626 22627 HRESULT Skip(ULONG celt) { 22628 if(celt + pos <= types.length) { 22629 pos += celt; 22630 return S_OK; 22631 } 22632 return S_FALSE; 22633 } 22634 }; 22635 22636 return S_OK; 22637 } else 22638 return E_NOTIMPL; 22639 } 22640 // given a format, return the format you'd prefer to use cuz it is identical 22641 HRESULT GetCanonicalFormatEtc(FORMATETC* pformatectIn, FORMATETC* pformatetcOut) { 22642 // FIXME: prolly could be better but meh 22643 // writeln("gcf: ", *pformatectIn); 22644 *pformatetcOut = *pformatectIn; 22645 return S_OK; 22646 } 22647 HRESULT GetData(FORMATETC* pformatetcIn, STGMEDIUM* pmedium) { 22648 foreach(ty; types) { 22649 if(ty == *pformatetcIn) { 22650 auto format = ty.cfFormat; 22651 // writeln("A: ", *pformatetcIn, "\nB: ", ty); 22652 STGMEDIUM medium; 22653 medium.tymed = TYMED.TYMED_HGLOBAL; 22654 22655 auto sz = handler.dataLength(format); 22656 auto handle = GlobalAlloc(GMEM_MOVEABLE, sz); 22657 if(handle is null) throw new WindowsApiException("GlobalAlloc", GetLastError()); 22658 if(auto data = cast(wchar*) GlobalLock(handle)) { 22659 auto slice = data[0 .. sz]; 22660 scope(exit) 22661 GlobalUnlock(handle); 22662 22663 handler.getData(format, cast(ubyte[]) slice[]); 22664 } 22665 22666 22667 medium.hGlobal = handle; // FIXME 22668 *pmedium = medium; 22669 return S_OK; 22670 } 22671 } 22672 return DV_E_FORMATETC; 22673 } 22674 HRESULT GetDataHere(FORMATETC* pformatetcIn, STGMEDIUM* pmedium) { 22675 // writeln("GDH: ", *pformatetcIn); 22676 return E_NOTIMPL; // FIXME 22677 } 22678 HRESULT QueryGetData(FORMATETC* pformatetc) { 22679 auto search = *pformatetc; 22680 search.tymed &= TYMED.TYMED_HGLOBAL; 22681 foreach(ty; types) 22682 if(ty == search) { 22683 // writeln("QueryGetData ", search, " ", types[0]); 22684 return S_OK; 22685 } 22686 if(pformatetc.cfFormat==CF_UNICODETEXT) { 22687 //writeln("QueryGetData FALSE ", search, " ", types[0]); 22688 } 22689 return S_FALSE; 22690 } 22691 HRESULT SetData(FORMATETC* pformatetc, STGMEDIUM* pmedium, BOOL fRelease) { 22692 // writeln("SetData: "); 22693 return E_NOTIMPL; 22694 } 22695 }; 22696 22697 22698 IDropSource src = new class IDropSource { 22699 ULONG refCount; 22700 ULONG AddRef() { 22701 return ++refCount; 22702 } 22703 ULONG Release() { 22704 return --refCount; 22705 } 22706 HRESULT QueryInterface(const (IID)*riid, LPVOID *ppv) { 22707 if (IID_IUnknown == *riid) { 22708 *ppv = cast(void*) cast(IUnknown) this; 22709 } 22710 else if (IID_IDropSource == *riid) { 22711 *ppv = cast(void*) cast(IDropSource) this; 22712 } 22713 else { 22714 *ppv = null; 22715 return E_NOINTERFACE; 22716 } 22717 22718 AddRef(); 22719 return NOERROR; 22720 } 22721 22722 int QueryContinueDrag(int fEscapePressed, uint grfKeyState) { 22723 if(fEscapePressed) 22724 return DRAGDROP_S_CANCEL; 22725 if(!(grfKeyState & MK_LBUTTON)) 22726 return DRAGDROP_S_DROP; 22727 return S_OK; 22728 } 22729 22730 int GiveFeedback(uint dwEffect) { 22731 return DRAGDROP_S_USEDEFAULTCURSORS; 22732 } 22733 }; 22734 22735 DWORD effect; 22736 22737 if(action == DragAndDropAction.none) assert(0, "Don't drag something with a none effect."); 22738 22739 DROPEFFECT de = win32DragAndDropAction(action); 22740 22741 // I'm not as concerned about the GC here since DoDragDrop blocks so the stack frame still sane the whole time 22742 // but still prolly a FIXME 22743 22744 auto ret = DoDragDrop(obj, src, de, &effect); 22745 /+ 22746 if(ret == DRAGDROP_S_DROP) 22747 writeln("drop ", effect); 22748 else if(ret == DRAGDROP_S_CANCEL) 22749 writeln("cancel"); 22750 else if(ret == S_OK) 22751 writeln("ok"); 22752 else writeln(ret); 22753 +/ 22754 22755 return ret; 22756 } 22757 22758 version(Windows) 22759 DROPEFFECT win32DragAndDropAction(DragAndDropAction action) { 22760 DROPEFFECT de; 22761 22762 with(DragAndDropAction) 22763 with(DROPEFFECT) 22764 final switch(action) { 22765 case none: de = DROPEFFECT_NONE; break; 22766 case copy: de = DROPEFFECT_COPY; break; 22767 case move: de = DROPEFFECT_MOVE; break; 22768 case link: de = DROPEFFECT_LINK; break; 22769 case ask: throw new Exception("ask not implemented yet"); 22770 case custom: throw new Exception("custom not implemented yet"); 22771 } 22772 22773 return de; 22774 } 22775 22776 22777 /++ 22778 History: 22779 Added February 19, 2021 22780 +/ 22781 /// Group: drag_and_drop 22782 void enableDragAndDrop(SimpleWindow window, DropHandler handler) { 22783 version(X11) { 22784 auto display = XDisplayConnection.get; 22785 22786 Atom atom = 5; // right??? 22787 22788 XChangeProperty( 22789 display, 22790 window.impl.window, 22791 GetAtom!"XdndAware"(display), 22792 XA_ATOM, 22793 32 /* bits */, 22794 PropModeReplace, 22795 &atom, 22796 1); 22797 22798 window.dropHandler = handler; 22799 } else version(Windows) { 22800 22801 initDnd(); 22802 22803 auto dropTarget = new class (handler) IDropTarget { 22804 DropHandler handler; 22805 this(DropHandler handler) { 22806 this.handler = handler; 22807 } 22808 ULONG refCount; 22809 ULONG AddRef() { 22810 return ++refCount; 22811 } 22812 ULONG Release() { 22813 return --refCount; 22814 } 22815 HRESULT QueryInterface(const (IID)*riid, LPVOID *ppv) { 22816 if (IID_IUnknown == *riid) { 22817 *ppv = cast(void*) cast(IUnknown) this; 22818 } 22819 else if (IID_IDropTarget == *riid) { 22820 *ppv = cast(void*) cast(IDropTarget) this; 22821 } 22822 else { 22823 *ppv = null; 22824 return E_NOINTERFACE; 22825 } 22826 22827 AddRef(); 22828 return NOERROR; 22829 } 22830 22831 22832 // /////////////////// 22833 22834 HRESULT DragEnter(IDataObject pDataObj, DWORD grfKeyState, POINTL pt, DWORD* pdwEffect) { 22835 DropPackage dropPackage = DropPackage(pDataObj); 22836 *pdwEffect = win32DragAndDropAction(handler.dragEnter(&dropPackage)); 22837 return S_OK; // https://docs.microsoft.com/en-us/windows/win32/api/oleidl/nf-oleidl-idroptarget-dragenter 22838 } 22839 22840 HRESULT DragLeave() { 22841 handler.dragLeave(); 22842 // release the IDataObject if needed 22843 return S_OK; 22844 } 22845 22846 HRESULT DragOver(DWORD grfKeyState, POINTL pt, DWORD* pdwEffect) { 22847 auto res = handler.dragOver(Point(pt.x, pt.y)); // FIXME: translate screen coordinates back to window coordinates 22848 22849 *pdwEffect = win32DragAndDropAction(res.action); 22850 // same as DragEnter basically 22851 return S_OK; 22852 } 22853 22854 HRESULT Drop(IDataObject pDataObj, DWORD grfKeyState, POINTL pt, DWORD* pdwEffect) { 22855 DropPackage pkg = DropPackage(pDataObj); 22856 handler.drop(&pkg); 22857 22858 return S_OK; 22859 } 22860 }; 22861 // Windows can hold on to the handler and try to call it 22862 // during which time the GC can't see it. so important to 22863 // manually manage this. At some point i'll FIXME and make 22864 // all my com instances manually managed since they supposed 22865 // to respect the refcount. 22866 import core.memory; 22867 GC.addRoot(cast(void*) dropTarget); 22868 22869 if(RegisterDragDrop(window.impl.hwnd, dropTarget) != S_OK) 22870 throw new WindowsApiException("RegisterDragDrop", GetLastError()); 22871 22872 window.dropHandler = handler; 22873 } else throw new NotYetImplementedException(); 22874 } 22875 22876 22877 22878 static if(UsingSimpledisplayX11) { 22879 22880 enum _NET_WM_STATE_ADD = 1; 22881 enum _NET_WM_STATE_REMOVE = 0; 22882 enum _NET_WM_STATE_TOGGLE = 2; 22883 22884 /// X-specific. Use [SimpleWindow.requestAttention] instead for most cases. 22885 void demandAttention(SimpleWindow window, bool needs = true) { 22886 demandAttention(window.impl.window, needs); 22887 } 22888 22889 /// ditto 22890 void demandAttention(Window window, bool needs = true) { 22891 setNetWmStateAtom(window, GetAtom!("_NET_WM_STATE_DEMANDS_ATTENTION", false)(XDisplayConnection.get), needs); 22892 } 22893 22894 void setNetWmStateAtom(Window window, Atom atom, bool set = true, Atom atom2 = None) { 22895 auto display = XDisplayConnection.get(); 22896 if(atom == None) 22897 return; // non-failure error 22898 //auto atom2 = GetAtom!"_NET_WM_STATE_SHADED"(display); 22899 22900 XClientMessageEvent xclient; 22901 22902 xclient.type = EventType.ClientMessage; 22903 xclient.window = window; 22904 xclient.message_type = GetAtom!"_NET_WM_STATE"(display); 22905 xclient.format = 32; 22906 xclient.data.l[0] = set ? _NET_WM_STATE_ADD : _NET_WM_STATE_REMOVE; 22907 xclient.data.l[1] = atom; 22908 xclient.data.l[2] = atom2; 22909 xclient.data.l[3] = 1; 22910 // [3] == source. 0 == unknown, 1 == app, 2 == else 22911 22912 XSendEvent( 22913 display, 22914 RootWindow(display, DefaultScreen(display)), 22915 false, 22916 EventMask.SubstructureRedirectMask | EventMask.SubstructureNotifyMask, 22917 cast(XEvent*) &xclient 22918 ); 22919 22920 /+ 22921 XChangeProperty( 22922 display, 22923 window.impl.window, 22924 GetAtom!"_NET_WM_STATE"(display), 22925 XA_ATOM, 22926 32 /* bits */, 22927 PropModeAppend, 22928 &atom, 22929 1); 22930 +/ 22931 } 22932 22933 private Atom dndActionAtom(Display* display, DragAndDropAction action) { 22934 Atom actionAtom; 22935 with(DragAndDropAction) 22936 final switch(action) { 22937 case none: actionAtom = None; break; 22938 case copy: actionAtom = GetAtom!"XdndActionCopy"(display); break; 22939 case move: actionAtom = GetAtom!"XdndActionMove"(display); break; 22940 case link: actionAtom = GetAtom!"XdndActionLink"(display); break; 22941 case ask: actionAtom = GetAtom!"XdndActionAsk"(display); break; 22942 case custom: actionAtom = GetAtom!"XdndActionCustom"(display); break; 22943 } 22944 22945 return actionAtom; 22946 } 22947 22948 private int doDragDropX11(SimpleWindow window, X11SetSelectionHandler handler, DragAndDropAction action) { 22949 // FIXME: I need to show user feedback somehow. 22950 auto display = XDisplayConnection.get; 22951 22952 auto actionAtom = dndActionAtom(display, action); 22953 assert(actionAtom, "Don't use action none to accept a drop"); 22954 22955 setX11Selection!"XdndSelection"(window, handler, null); 22956 22957 auto oldKeyHandler = window.handleKeyEvent; 22958 scope(exit) window.handleKeyEvent = oldKeyHandler; 22959 22960 auto oldCharHandler = window.handleCharEvent; 22961 scope(exit) window.handleCharEvent = oldCharHandler; 22962 22963 auto oldMouseHandler = window.handleMouseEvent; 22964 scope(exit) window.handleMouseEvent = oldMouseHandler; 22965 22966 Window[Window] eligibility; // 0 == not eligible, otherwise it is the window id of an eligible child 22967 22968 import core.sys.posix.sys.time; 22969 timeval tv; 22970 gettimeofday(&tv, null); 22971 22972 Time dataTimestamp = cast(Time) ( tv.tv_sec * 1000 + tv.tv_usec / 1000 ); 22973 22974 Time lastMouseTimestamp; 22975 22976 bool dnding = true; 22977 Window lastIn = None; 22978 22979 void leave() { 22980 if(lastIn == None) 22981 return; 22982 22983 XEvent ev; 22984 ev.xclient.type = EventType.ClientMessage; 22985 ev.xclient.window = lastIn; 22986 ev.xclient.message_type = GetAtom!("XdndLeave", true)(display); 22987 ev.xclient.format = 32; 22988 ev.xclient.data.l[0] = window.impl.window; 22989 22990 XSendEvent(XDisplayConnection.get, lastIn, false, EventMask.NoEventMask, &ev); 22991 XFlush(display); 22992 22993 lastIn = None; 22994 } 22995 22996 void enter(Window w) { 22997 assert(lastIn == None); 22998 22999 lastIn = w; 23000 23001 XEvent ev; 23002 ev.xclient.type = EventType.ClientMessage; 23003 ev.xclient.window = lastIn; 23004 ev.xclient.message_type = GetAtom!("XdndEnter", true)(display); 23005 ev.xclient.format = 32; 23006 ev.xclient.data.l[0] = window.impl.window; 23007 ev.xclient.data.l[1] = (5 << 24) | 0; // version 5, no more sources. FIXME source types 23008 23009 auto types = handler.availableFormats(); 23010 assert(types.length > 0); 23011 23012 ev.xclient.data.l[2] = types[0]; 23013 if(types.length > 1) 23014 ev.xclient.data.l[3] = types[1]; 23015 if(types.length > 2) 23016 ev.xclient.data.l[4] = types[2]; 23017 23018 // FIXME: other types?!?!? and make sure we skip TARGETS 23019 23020 XSendEvent(XDisplayConnection.get, lastIn, false, EventMask.NoEventMask, &ev); 23021 XFlush(display); 23022 } 23023 23024 void position(int rootX, int rootY) { 23025 assert(lastIn != None); 23026 23027 XEvent ev; 23028 ev.xclient.type = EventType.ClientMessage; 23029 ev.xclient.window = lastIn; 23030 ev.xclient.message_type = GetAtom!("XdndPosition", true)(display); 23031 ev.xclient.format = 32; 23032 ev.xclient.data.l[0] = window.impl.window; 23033 ev.xclient.data.l[1] = 0; // reserved 23034 ev.xclient.data.l[2] = (rootX << 16) | rootY; 23035 ev.xclient.data.l[3] = dataTimestamp; 23036 ev.xclient.data.l[4] = actionAtom; 23037 23038 XSendEvent(XDisplayConnection.get, lastIn, false, EventMask.NoEventMask, &ev); 23039 XFlush(display); 23040 23041 } 23042 23043 void drop() { 23044 XEvent ev; 23045 ev.xclient.type = EventType.ClientMessage; 23046 ev.xclient.window = lastIn; 23047 ev.xclient.message_type = GetAtom!("XdndDrop", true)(display); 23048 ev.xclient.format = 32; 23049 ev.xclient.data.l[0] = window.impl.window; 23050 ev.xclient.data.l[1] = 0; // reserved 23051 ev.xclient.data.l[2] = dataTimestamp; 23052 23053 XSendEvent(XDisplayConnection.get, lastIn, false, EventMask.NoEventMask, &ev); 23054 XFlush(display); 23055 23056 lastIn = None; 23057 dnding = false; 23058 } 23059 23060 // fyi nativeEventHandler can return 0 if it handles it, or otherwise it goes back to the normal handler 23061 // but idk if i should... 23062 23063 window.setEventHandlers( 23064 delegate(KeyEvent ev) { 23065 if(ev.pressed == true && ev.key == Key.Escape) { 23066 // cancel 23067 dnding = false; 23068 } 23069 }, 23070 delegate(MouseEvent ev) { 23071 if(ev.timestamp < lastMouseTimestamp) 23072 return; 23073 23074 lastMouseTimestamp = ev.timestamp; 23075 23076 if(ev.type == MouseEventType.motion) { 23077 auto display = XDisplayConnection.get; 23078 auto root = RootWindow(display, DefaultScreen(display)); 23079 23080 Window topWindow; 23081 int rootX, rootY; 23082 23083 XTranslateCoordinates(display, window.impl.window, root, ev.x, ev.y, &rootX, &rootY, &topWindow); 23084 23085 if(topWindow == None) 23086 return; 23087 23088 top: 23089 if(auto result = topWindow in eligibility) { 23090 auto dropWindow = *result; 23091 if(dropWindow == None) { 23092 leave(); 23093 return; 23094 } 23095 23096 if(dropWindow != lastIn) { 23097 leave(); 23098 enter(dropWindow); 23099 position(rootX, rootY); 23100 } else { 23101 position(rootX, rootY); 23102 } 23103 } else { 23104 // determine eligibility 23105 auto data = cast(Atom[]) getX11PropertyData(topWindow, GetAtom!"XdndAware"(display), XA_ATOM); 23106 if(data.length == 1) { 23107 // in case there is no WM or it isn't reparenting 23108 eligibility[topWindow] = (data[0] == 5) ? topWindow : None; // FIXME I'm supposed to handle older versions too but meh 23109 } else { 23110 23111 Window tryScanChildren(Window search, int maxRecurse) { 23112 // could be reparenting window manager, so gotta check the next few children too 23113 Window child; 23114 int x; 23115 int y; 23116 XTranslateCoordinates(display, window.impl.window, search, ev.x, ev.y, &x, &y, &child); 23117 23118 if(child == None) 23119 return None; 23120 auto data = cast(Atom[]) getX11PropertyData(child, GetAtom!"XdndAware"(display), XA_ATOM); 23121 if(data.length == 1) { 23122 return (data[0] == 5) ? child : None; // FIXME I'm supposed to handle older versions too but meh 23123 } else { 23124 if(maxRecurse) 23125 return tryScanChildren(child, maxRecurse - 1); 23126 else 23127 return None; 23128 } 23129 23130 } 23131 23132 // if a WM puts more than 3 layers on it, like wtf is it doing, screw that. 23133 auto topResult = tryScanChildren(topWindow, 3); 23134 // it is easy to have a false negative due to the mouse going over a WM 23135 // child window like the close button if separate from the frame... so I 23136 // can't really cache negatives, :( 23137 if(topResult != None) { 23138 eligibility[topWindow] = topResult; 23139 goto top; // reload to do the positioning iff eligibility changed lest we endless loop 23140 } 23141 } 23142 23143 } 23144 23145 } else if(ev.type == MouseEventType.buttonReleased) { 23146 drop(); 23147 dnding = false; 23148 } 23149 } 23150 ); 23151 23152 window.grabInput(); 23153 scope(exit) 23154 window.releaseInputGrab(); 23155 23156 23157 EventLoop.get.run(() => dnding); 23158 23159 return 0; 23160 } 23161 23162 /// X-specific 23163 TrueColorImage getWindowNetWmIcon(Window window) { 23164 try { 23165 auto display = XDisplayConnection.get; 23166 23167 auto data = getX11PropertyData (window, GetAtom!"_NET_WM_ICON"(display), XA_CARDINAL); 23168 23169 if (data.length > arch_ulong.sizeof * 2) { 23170 auto meta = cast(arch_ulong[]) (data[0 .. arch_ulong.sizeof * 2]); 23171 // these are an array of rgba images that we have to convert into pixmaps ourself 23172 23173 int width = cast(int) meta[0]; 23174 int height = cast(int) meta[1]; 23175 23176 auto bytes = cast(ubyte[]) (data[arch_ulong.sizeof * 2 .. $]); 23177 23178 static if(arch_ulong.sizeof == 4) { 23179 bytes = bytes[0 .. width * height * 4]; 23180 alias imageData = bytes; 23181 } else static if(arch_ulong.sizeof == 8) { 23182 bytes = bytes[0 .. width * height * 8]; 23183 auto imageData = new ubyte[](4 * width * height); 23184 } else static assert(0); 23185 23186 23187 23188 // this returns ARGB. Remember it is little-endian so 23189 // we have BGRA 23190 // our thing uses RGBA, which in little endian, is ABGR 23191 for(int idx = 0, idx2 = 0; idx < bytes.length; idx += arch_ulong.sizeof, idx2 += 4) { 23192 auto r = bytes[idx + 2]; 23193 auto g = bytes[idx + 1]; 23194 auto b = bytes[idx + 0]; 23195 auto a = bytes[idx + 3]; 23196 23197 imageData[idx2 + 0] = r; 23198 imageData[idx2 + 1] = g; 23199 imageData[idx2 + 2] = b; 23200 imageData[idx2 + 3] = a; 23201 } 23202 23203 return new TrueColorImage(width, height, imageData); 23204 } 23205 23206 return null; 23207 } catch(Exception e) { 23208 return null; 23209 } 23210 } 23211 23212 } /* UsingSimpledisplayX11 */ 23213 23214 23215 void loadBinNameToWindowClassName () { 23216 import core.stdc.stdlib : realloc; 23217 version(linux) { 23218 // args[0] MAY be empty, so we'll just use this 23219 import core.sys.posix.unistd : readlink; 23220 char[1024] ebuf = void; // 1KB should be enough for everyone! 23221 auto len = readlink("/proc/self/exe", ebuf.ptr, ebuf.length); 23222 if (len < 1) return; 23223 } else /*version(Windows)*/ { 23224 import core.runtime : Runtime; 23225 if (Runtime.args.length == 0 || Runtime.args[0].length == 0) return; 23226 auto ebuf = Runtime.args[0]; 23227 auto len = ebuf.length; 23228 } 23229 auto pos = len; 23230 while (pos > 0 && ebuf[pos-1] != '/') --pos; 23231 sdpyWindowClassStr = cast(char*)realloc(sdpyWindowClassStr, len-pos+1); 23232 if (sdpyWindowClassStr is null) return; // oops 23233 sdpyWindowClassStr[0..len-pos+1] = 0; // just in case 23234 sdpyWindowClassStr[0..len-pos] = ebuf[pos..len]; 23235 } 23236 23237 /++ 23238 An interface representing a font that is drawn with custom facilities. 23239 23240 You might want [OperatingSystemFont] instead, which represents 23241 a font loaded and drawn by functions native to the operating system. 23242 23243 WARNING: I might still change this. 23244 +/ 23245 interface DrawableFont : MeasurableFont { 23246 /++ 23247 Please note the point is upperLeft, NOT baseline! This is the point of a bounding box of the string. 23248 23249 Implementations must use the painter's fillColor to draw a rectangle behind the string, 23250 then use the outlineColor to draw the string. It might alpha composite if there's a transparent 23251 fill color, but that's up to the implementation. 23252 +/ 23253 void drawString(ScreenPainter painter, Point upperLeft, in char[] text); 23254 23255 /++ 23256 Requests that the given string is added to the image cache. You should only do this rarely, but 23257 if you have a string that you know will be used over and over again, adding it to a cache can 23258 improve things (assuming the implementation actually has a cache; it is also valid for an implementation 23259 to implement this as a do-nothing method). 23260 +/ 23261 void cacheString(SimpleWindow window, Color foreground, Color background, string text); 23262 } 23263 23264 /++ 23265 Loads a true type font using [arsd.ttf] that can be drawn as images on windows 23266 through a [ScreenPainter]. That module must be compiled in if you choose to use this function. 23267 23268 You should also consider [OperatingSystemFont], which loads and draws a font with 23269 facilities native to the user's operating system. You might also consider 23270 [arsd.ttf.OpenGlLimitedFont] or using [arsd.nanovega] if you are making some kind 23271 of game, as they have their own ways to draw text too. 23272 23273 Be warned: this can be slow, especially on remote connections to the X server, since 23274 it needs to create and transfer bitmaps instead of just text. The [DrawableFont] interface 23275 offers [DrawableFont.cacheString] which can help with this, sometimes. You might want to 23276 experiment in your specific case. 23277 23278 Please note that the return type of [DrawableFont] also includes an implementation of 23279 [MeasurableFont]. 23280 +/ 23281 DrawableFont arsdTtfFont()(in ubyte[] data, int size) { 23282 import arsd.ttf; 23283 static class ArsdTtfFont : DrawableFont { 23284 TtfFont font; 23285 int size; 23286 this(in ubyte[] data, int size) { 23287 font = TtfFont(data); 23288 this.size = size; 23289 23290 23291 auto scale = stbtt_ScaleForPixelHeight(&font.font, size); 23292 int ascent_, descent_, line_gap; 23293 stbtt_GetFontVMetrics(&font.font, &ascent_, &descent_, &line_gap); 23294 23295 int advance, lsb; 23296 stbtt_GetCodepointHMetrics(&font.font, 'x', &advance, &lsb); 23297 xWidth = cast(int) (advance * scale); 23298 stbtt_GetCodepointHMetrics(&font.font, 'M', &advance, &lsb); 23299 MWidth = cast(int) (advance * scale); 23300 } 23301 23302 private int ascent_; 23303 private int descent_; 23304 private int xWidth; 23305 private int MWidth; 23306 23307 bool isMonospace() { 23308 return xWidth == MWidth; 23309 } 23310 int averageWidth() { 23311 return xWidth; 23312 } 23313 int height() { 23314 return size; 23315 } 23316 int ascent() { 23317 return ascent_; 23318 } 23319 int descent() { 23320 return descent_; 23321 } 23322 23323 int stringWidth(scope const(char)[] s, SimpleWindow window = null) { 23324 int width, height; 23325 font.getStringSize(s, size, width, height); 23326 return width; 23327 } 23328 23329 23330 23331 Sprite[string] cache; 23332 23333 void cacheString(SimpleWindow window, Color foreground, Color background, string text) { 23334 auto sprite = new Sprite(window, stringToImage(foreground, background, text)); 23335 cache[text] = sprite; 23336 } 23337 23338 Image stringToImage(Color fg, Color bg, in char[] text) { 23339 int width, height; 23340 auto data = font.renderString(text, size, width, height); 23341 auto image = new TrueColorImage(width, height); 23342 int pos = 0; 23343 foreach(y; 0 .. height) 23344 foreach(x; 0 .. width) { 23345 fg.a = data[0]; 23346 bg.a = 255; 23347 auto color = alphaBlend(fg, bg); 23348 image.imageData.bytes[pos++] = color.r; 23349 image.imageData.bytes[pos++] = color.g; 23350 image.imageData.bytes[pos++] = color.b; 23351 image.imageData.bytes[pos++] = data[0]; 23352 data = data[1 .. $]; 23353 } 23354 assert(data.length == 0); 23355 23356 return Image.fromMemoryImage(image); 23357 } 23358 23359 void drawString(ScreenPainter painter, Point upperLeft, in char[] text) { 23360 Sprite sprite = (text in cache) ? *(text in cache) : null; 23361 23362 auto fg = painter.impl._outlineColor; 23363 auto bg = painter.impl._fillColor; 23364 23365 if(sprite !is null) { 23366 auto w = cast(SimpleWindow) painter.window; 23367 assert(w !is null); 23368 23369 sprite.drawAt(painter, upperLeft); 23370 } else { 23371 painter.drawImage(upperLeft, stringToImage(fg, bg, text)); 23372 } 23373 } 23374 } 23375 23376 return new ArsdTtfFont(data, size); 23377 } 23378 23379 class NotYetImplementedException : Exception { 23380 this(string file = __FILE__, size_t line = __LINE__) { 23381 super("Not yet implemented", file, line); 23382 } 23383 } 23384 23385 /// 23386 __gshared bool librariesSuccessfullyLoaded = true; 23387 /// 23388 __gshared bool openGlLibrariesSuccessfullyLoaded = true; 23389 23390 private mixin template DynamicLoadSupplementalOpenGL(Iface) { 23391 // mixin(staticForeachReplacement!Iface); 23392 static foreach(name; __traits(derivedMembers, Iface)) 23393 mixin("__gshared typeof(&__traits(getMember, Iface, name)) " ~ name ~ ";"); 23394 23395 void loadDynamicLibrary() @nogc { 23396 (cast(void function() @nogc) &loadDynamicLibraryForReal)(); 23397 } 23398 23399 void loadDynamicLibraryForReal() { 23400 foreach(name; __traits(derivedMembers, Iface)) { 23401 mixin("alias tmp = " ~ name ~ ";"); 23402 tmp = cast(typeof(tmp)) glbindGetProcAddress(name); 23403 if(tmp is null) throw new Exception("load failure of function " ~ name ~ " from supplemental OpenGL"); 23404 } 23405 } 23406 } 23407 23408 /+ 23409 private const(char)[] staticForeachReplacement(Iface)() pure { 23410 /* 23411 // just this for gdc 9.... 23412 // when i drop support for it and switch to gdc10, we can put this original back for a slight compile time ram decrease 23413 23414 static foreach(name; __traits(derivedMembers, Iface)) 23415 mixin("__gshared typeof(&__traits(getMember, Iface, name)) " ~ name ~ ";"); 23416 */ 23417 23418 char[] code = new char[](__traits(derivedMembers, Iface).length * 64); 23419 size_t pos; 23420 23421 void append(in char[] what) { 23422 if(pos + what.length > code.length) 23423 code.length = (code.length * 3) / 2; 23424 code[pos .. pos + what.length] = what[]; 23425 pos += what.length; 23426 } 23427 23428 foreach(name; __traits(derivedMembers, Iface)) { 23429 append(`__gshared typeof(&__traits(getMember, Iface, "`); 23430 append(name); 23431 append(`")) `); 23432 append(name); 23433 append(";"); 23434 } 23435 23436 return code[0 .. pos]; 23437 } 23438 +/ 23439 23440 private mixin template DynamicLoad(Iface, string library, int majorVersion, alias success) { 23441 //mixin(staticForeachReplacement!Iface); 23442 static foreach(name; __traits(derivedMembers, Iface)) 23443 mixin("__gshared typeof(&__traits(getMember, Iface, name)) " ~ name ~ ";"); 23444 23445 private __gshared void* libHandle; 23446 private __gshared bool attempted; 23447 23448 void loadDynamicLibrary() @nogc { 23449 (cast(void function() @nogc) &loadDynamicLibraryForReal)(); 23450 } 23451 23452 bool loadAttempted() { 23453 return attempted; 23454 } 23455 bool loadSuccessful() { 23456 return libHandle !is null; 23457 } 23458 23459 void loadDynamicLibraryForReal() { 23460 attempted = true; 23461 version(Posix) { 23462 import core.sys.posix.dlfcn; 23463 version(OSX) { 23464 version(X11) 23465 libHandle = dlopen("/usr/X11/lib/lib" ~ library ~ ".dylib", RTLD_NOW); 23466 else 23467 libHandle = dlopen(library ~ ".dylib", RTLD_NOW); 23468 } else { 23469 version(apitrace) { 23470 if(library == "GL" || library == "GLX") { 23471 libHandle = dlopen("glxtrace.so", RTLD_NOW); 23472 if(libHandle is null) { 23473 assert(false, "Failed to load `glxtrace.so`."); 23474 } 23475 } 23476 else { 23477 libHandle = dlopen("lib" ~ library ~ ".so", RTLD_NOW); 23478 } 23479 } 23480 else { 23481 libHandle = dlopen("lib" ~ library ~ ".so", RTLD_NOW); 23482 } 23483 if(libHandle is null) { 23484 libHandle = dlopen(("lib" ~ library ~ ".so." ~ toInternal!string(majorVersion) ~ "\0").ptr, RTLD_NOW); 23485 } 23486 } 23487 23488 static void* loadsym(void* l, const char* name) { 23489 import core.stdc.stdlib; 23490 if(l is null) 23491 return &abort; 23492 return dlsym(l, name); 23493 } 23494 } else version(Windows) { 23495 import core.sys.windows.winbase; 23496 libHandle = LoadLibrary(library ~ ".dll"); 23497 static void* loadsym(void* l, const char* name) { 23498 import core.stdc.stdlib; 23499 if(l is null) 23500 return &abort; 23501 return GetProcAddress(l, name); 23502 } 23503 } 23504 if(libHandle is null) { 23505 success = false; 23506 //throw new Exception("load failure of library " ~ library); 23507 } 23508 foreach(name; __traits(derivedMembers, Iface)) { 23509 mixin("alias tmp = " ~ name ~ ";"); 23510 tmp = cast(typeof(tmp)) loadsym(libHandle, name); 23511 if(tmp is null) throw new Exception("load failure of function " ~ name ~ " from " ~ library); 23512 } 23513 } 23514 23515 void unloadDynamicLibrary() { 23516 version(Posix) { 23517 import core.sys.posix.dlfcn; 23518 dlclose(libHandle); 23519 } else version(Windows) { 23520 import core.sys.windows.winbase; 23521 FreeLibrary(libHandle); 23522 } 23523 foreach(name; __traits(derivedMembers, Iface)) 23524 mixin(name ~ " = null;"); 23525 } 23526 } 23527 23528 // version(X11) 23529 /++ 23530 Returns the custom scaling factor read out of environment["ARSD_SCALING_FACTOR"]. 23531 23532 $(WARNING 23533 This function is exempted from stability guarantees. 23534 ) 23535 +/ 23536 float customScalingFactorForMonitor(int monitorNumber) @system { 23537 import core.stdc.stdlib; 23538 auto val = getenv("ARSD_SCALING_FACTOR"); 23539 23540 // FIXME: maybe we should assume a default nbased on the dpi thing if this isn't given 23541 if(val is null) 23542 return 1.0; 23543 23544 char[16] buffer = 0; 23545 int pos; 23546 23547 const(char)* at = val; 23548 23549 foreach(item; 0 .. monitorNumber + 1) { 23550 if(*at == 0) 23551 break; // reuse the last number when we at the end of the string 23552 pos = 0; 23553 while(pos + 1 < buffer.length && *at && *at != ';') { 23554 buffer[pos++] = *at; 23555 at++; 23556 } 23557 if(*at) 23558 at++; // skip the semicolon 23559 buffer[pos] = 0; 23560 } 23561 23562 //sdpyPrintDebugString(buffer[0 .. pos]); 23563 23564 import core.stdc.math; 23565 auto f = atof(buffer.ptr); 23566 23567 if(f <= 0.0 || isnan(f) || isinf(f)) 23568 return 1.0; 23569 23570 return f; 23571 } 23572 23573 void guiAbortProcess(string msg) { 23574 import core.stdc.stdlib; 23575 version(Windows) { 23576 WCharzBuffer t = WCharzBuffer(msg); 23577 MessageBoxW(null, t.ptr, "Program Termination"w.ptr, 0); 23578 } else { 23579 import core.stdc.stdio; 23580 fwrite(msg.ptr, 1, msg.length, stderr); 23581 msg = "\n"; 23582 fwrite(msg.ptr, 1, msg.length, stderr); 23583 fflush(stderr); 23584 } 23585 23586 abort(); 23587 } 23588 23589 private int minInternal(int a, int b) { 23590 return (a < b) ? a : b; 23591 } 23592 23593 private alias scriptable = arsd_jsvar_compatible;