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 import arsd.core; 817 818 // FIXME: tetris demo 819 // FIXME: space invaders demo 820 // FIXME: asteroids demo 821 822 /++ $(ID Pong-example) 823 $(H3 Pong) 824 825 This program creates a little Pong-like game. Player one is controlled 826 with the keyboard. Player two is controlled with the mouse. It demos 827 the pulse timer, event handling, and some basic drawing. 828 +/ 829 version(demos) 830 unittest { 831 // dmd example.d simpledisplay.d color.d 832 import arsd.simpledisplay; 833 834 enum paddleMovementSpeed = 8; 835 enum paddleHeight = 48; 836 837 void main() { 838 auto window = new SimpleWindow(600, 400, "Pong game!"); 839 840 int playerOnePosition, playerTwoPosition; 841 int playerOneMovement, playerTwoMovement; 842 int playerOneScore, playerTwoScore; 843 844 int ballX, ballY; 845 int ballDx, ballDy; 846 847 void serve() { 848 import std.random; 849 850 ballX = window.width / 2; 851 ballY = window.height / 2; 852 ballDx = uniform(-4, 4) * 3; 853 ballDy = uniform(-4, 4) * 3; 854 if(ballDx == 0) 855 ballDx = uniform(0, 2) == 0 ? 3 : -3; 856 } 857 858 serve(); 859 860 window.eventLoop(50, // set a 50 ms timer pulls 861 // This runs once per timer pulse 862 delegate () { 863 auto painter = window.draw(); 864 865 painter.clear(); 866 867 // Update everyone's motion 868 playerOnePosition += playerOneMovement; 869 playerTwoPosition += playerTwoMovement; 870 871 ballX += ballDx; 872 ballY += ballDy; 873 874 // Bounce off the top and bottom edges of the window 875 if(ballY + 7 >= window.height) 876 ballDy = -ballDy; 877 if(ballY - 8 <= 0) 878 ballDy = -ballDy; 879 880 // Bounce off the paddle, if it is in position 881 if(ballX - 8 <= 16) { 882 if(ballY + 7 > playerOnePosition && ballY - 8 < playerOnePosition + paddleHeight) { 883 ballDx = -ballDx + 1; // add some speed to keep it interesting 884 ballDy += playerOneMovement; // and y movement based on your controls too 885 ballX = 24; // move it past the paddle so it doesn't wiggle inside 886 } else { 887 // Missed it 888 playerTwoScore ++; 889 serve(); 890 } 891 } 892 893 if(ballX + 7 >= window.width - 16) { // do the same thing but for player 1 894 if(ballY + 7 > playerTwoPosition && ballY - 8 < playerTwoPosition + paddleHeight) { 895 ballDx = -ballDx - 1; 896 ballDy += playerTwoMovement; 897 ballX = window.width - 24; 898 } else { 899 // Missed it 900 playerOneScore ++; 901 serve(); 902 } 903 } 904 905 // Draw the paddles 906 painter.outlineColor = Color.black; 907 painter.drawLine(Point(16, playerOnePosition), Point(16, playerOnePosition + paddleHeight)); 908 painter.drawLine(Point(window.width - 16, playerTwoPosition), Point(window.width - 16, playerTwoPosition + paddleHeight)); 909 910 // Draw the ball 911 painter.fillColor = Color.red; 912 painter.outlineColor = Color.yellow; 913 painter.drawEllipse(Point(ballX - 8, ballY - 8), Point(ballX + 7, ballY + 7)); 914 915 // Draw the score 916 painter.outlineColor = Color.blue; 917 import std.conv; 918 painter.drawText(Point(64, 4), to!string(playerOneScore)); 919 painter.drawText(Point(window.width - 64, 4), to!string(playerTwoScore)); 920 921 }, 922 delegate (KeyEvent event) { 923 // Player 1's controls are the arrow keys on the keyboard 924 if(event.key == Key.Down) 925 playerOneMovement = event.pressed ? paddleMovementSpeed : 0; 926 if(event.key == Key.Up) 927 playerOneMovement = event.pressed ? -paddleMovementSpeed : 0; 928 929 }, 930 delegate (MouseEvent event) { 931 // Player 2's controls are mouse movement while the left button is held down 932 if(event.type == MouseEventType.motion && (event.modifierState & ModifierState.leftButtonDown)) { 933 if(event.dy > 0) 934 playerTwoMovement = paddleMovementSpeed; 935 else if(event.dy < 0) 936 playerTwoMovement = -paddleMovementSpeed; 937 } else { 938 playerTwoMovement = 0; 939 } 940 } 941 ); 942 } 943 } 944 945 /++ $(H3 $(ID example-minesweeper) Minesweeper) 946 947 This minesweeper demo shows how we can implement another classic 948 game with simpledisplay and shows some mouse input and basic output 949 code. 950 +/ 951 version(demos) 952 unittest { 953 import arsd.simpledisplay; 954 955 enum GameSquare { 956 mine = 0, 957 clear, 958 m1, m2, m3, m4, m5, m6, m7, m8 959 } 960 961 enum UserSquare { 962 unknown, 963 revealed, 964 flagged, 965 questioned 966 } 967 968 enum GameState { 969 inProgress, 970 lose, 971 win 972 } 973 974 GameSquare[] board; 975 UserSquare[] userState; 976 GameState gameState; 977 int boardWidth; 978 int boardHeight; 979 980 bool isMine(int x, int y) { 981 if(x < 0 || y < 0 || x >= boardWidth || y >= boardHeight) 982 return false; 983 return board[y * boardWidth + x] == GameSquare.mine; 984 } 985 986 GameState reveal(int x, int y) { 987 if(board[y * boardWidth + x] == GameSquare.clear) { 988 floodFill(userState, boardWidth, boardHeight, 989 UserSquare.unknown, UserSquare.revealed, 990 x, y, 991 (x, y) { 992 if(board[y * boardWidth + x] == GameSquare.clear) 993 return true; 994 else { 995 userState[y * boardWidth + x] = UserSquare.revealed; 996 return false; 997 } 998 }); 999 } else { 1000 userState[y * boardWidth + x] = UserSquare.revealed; 1001 if(isMine(x, y)) 1002 return GameState.lose; 1003 } 1004 1005 foreach(state; userState) { 1006 if(state == UserSquare.unknown || state == UserSquare.questioned) 1007 return GameState.inProgress; 1008 } 1009 1010 return GameState.win; 1011 } 1012 1013 void initializeBoard(int width, int height, int numberOfMines) { 1014 boardWidth = width; 1015 boardHeight = height; 1016 board.length = width * height; 1017 1018 userState.length = width * height; 1019 userState[] = UserSquare.unknown; 1020 1021 import std.algorithm, std.random, std.range; 1022 1023 board[] = GameSquare.clear; 1024 1025 foreach(minePosition; randomSample(iota(0, board.length), numberOfMines)) 1026 board[minePosition] = GameSquare.mine; 1027 1028 int x; 1029 int y; 1030 foreach(idx, ref square; board) { 1031 if(square == GameSquare.clear) { 1032 int danger = 0; 1033 danger += isMine(x-1, y-1)?1:0; 1034 danger += isMine(x-1, y)?1:0; 1035 danger += isMine(x-1, y+1)?1:0; 1036 danger += isMine(x, y-1)?1:0; 1037 danger += isMine(x, y+1)?1:0; 1038 danger += isMine(x+1, y-1)?1:0; 1039 danger += isMine(x+1, y)?1:0; 1040 danger += isMine(x+1, y+1)?1:0; 1041 1042 square = cast(GameSquare) (danger + 1); 1043 } 1044 1045 x++; 1046 if(x == width) { 1047 x = 0; 1048 y++; 1049 } 1050 } 1051 } 1052 1053 void redraw(SimpleWindow window) { 1054 import std.conv; 1055 1056 auto painter = window.draw(); 1057 1058 painter.clear(); 1059 1060 final switch(gameState) with(GameState) { 1061 case inProgress: 1062 break; 1063 case win: 1064 painter.fillColor = Color.green; 1065 painter.drawRectangle(Point(0, 0), window.width, window.height); 1066 return; 1067 case lose: 1068 painter.fillColor = Color.red; 1069 painter.drawRectangle(Point(0, 0), window.width, window.height); 1070 return; 1071 } 1072 1073 int x = 0; 1074 int y = 0; 1075 1076 foreach(idx, square; board) { 1077 auto state = userState[idx]; 1078 1079 final switch(state) with(UserSquare) { 1080 case unknown: 1081 painter.outlineColor = Color.black; 1082 painter.fillColor = Color(128,128,128); 1083 1084 painter.drawRectangle( 1085 Point(x * 20, y * 20), 1086 20, 20 1087 ); 1088 break; 1089 case revealed: 1090 if(square == GameSquare.clear) { 1091 painter.outlineColor = Color.white; 1092 painter.fillColor = Color.white; 1093 1094 painter.drawRectangle( 1095 Point(x * 20, y * 20), 1096 20, 20 1097 ); 1098 } else { 1099 painter.outlineColor = Color.black; 1100 painter.fillColor = Color.white; 1101 1102 painter.drawText( 1103 Point(x * 20, y * 20), 1104 to!string(square)[1..2], 1105 Point(x * 20 + 20, y * 20 + 20), 1106 TextAlignment.Center | TextAlignment.VerticalCenter); 1107 } 1108 break; 1109 case flagged: 1110 painter.outlineColor = Color.black; 1111 painter.fillColor = Color.red; 1112 painter.drawRectangle( 1113 Point(x * 20, y * 20), 1114 20, 20 1115 ); 1116 break; 1117 case questioned: 1118 painter.outlineColor = Color.black; 1119 painter.fillColor = Color.yellow; 1120 painter.drawRectangle( 1121 Point(x * 20, y * 20), 1122 20, 20 1123 ); 1124 break; 1125 } 1126 1127 x++; 1128 if(x == boardWidth) { 1129 x = 0; 1130 y++; 1131 } 1132 } 1133 1134 } 1135 1136 void main() { 1137 auto window = new SimpleWindow(200, 200); 1138 1139 initializeBoard(10, 10, 10); 1140 1141 redraw(window); 1142 window.eventLoop(0, 1143 delegate (MouseEvent me) { 1144 if(me.type != MouseEventType.buttonPressed) 1145 return; 1146 auto x = me.x / 20; 1147 auto y = me.y / 20; 1148 if(x >= 0 && x < boardWidth && y >= 0 && y < boardHeight) { 1149 if(me.button == MouseButton.left) { 1150 gameState = reveal(x, y); 1151 } else { 1152 userState[y*boardWidth+x] = UserSquare.flagged; 1153 } 1154 redraw(window); 1155 } 1156 } 1157 ); 1158 } 1159 } 1160 1161 // FIXME: tetris demo 1162 // FIXME: space invaders demo 1163 // FIXME: asteroids demo 1164 1165 version(OSX) version(DigitalMars) version=OSXCocoa; 1166 1167 version(Emscripten) { 1168 version=allow_unimplemented_features; 1169 version=without_opengl; 1170 } 1171 1172 1173 version(OSXCocoa) { 1174 version=without_opengl; 1175 version=allow_unimplemented_features; 1176 // version=OSXCocoa; 1177 // pragma(linkerDirective, "-framework Cocoa"); 1178 } 1179 1180 version(without_opengl) { 1181 enum SdpyIsUsingIVGLBinds = false; 1182 } else /*version(Posix)*/ { 1183 static if (__traits(compiles, (){import iv.glbinds;})) { 1184 enum SdpyIsUsingIVGLBinds = true; 1185 public import iv.glbinds; 1186 //pragma(msg, "SDPY: using iv.glbinds"); 1187 } else { 1188 enum SdpyIsUsingIVGLBinds = false; 1189 } 1190 //} else { 1191 // enum SdpyIsUsingIVGLBinds = false; 1192 } 1193 1194 version(Windows) { 1195 //import core.sys.windows.windows; 1196 import core.sys.windows.winnls; 1197 import core.sys.windows.windef; 1198 import core.sys.windows.basetyps; 1199 import core.sys.windows.winbase; 1200 import core.sys.windows.winuser; 1201 import core.sys.windows.shellapi; 1202 import core.sys.windows.wingdi; 1203 static import gdi = core.sys.windows.wingdi; // so i 1204 1205 pragma(lib, "gdi32"); 1206 pragma(lib, "user32"); 1207 1208 // for AlphaBlend... a breaking change.... 1209 version(CRuntime_DigitalMars) { } else 1210 pragma(lib, "msimg32"); 1211 1212 // core.sys.windows.dwmapi 1213 private { 1214 /++ 1215 See_also: 1216 https://learn.microsoft.com/en-us/windows/win32/api/dwmapi/nf-dwmapi-dwmgetwindowattribute 1217 +/ 1218 extern extern(Windows) HRESULT DwmGetWindowAttribute( 1219 HWND hwnd, 1220 DWORD dwAttribute, 1221 PVOID pvAttribute, 1222 DWORD cbAttribute 1223 ) nothrow @nogc; 1224 1225 /++ 1226 See_also: 1227 https://learn.microsoft.com/en-us/windows/win32/api/dwmapi/nf-dwmapi-dwmsetwindowattribute 1228 +/ 1229 extern extern(Windows) HRESULT DwmSetWindowAttribute( 1230 HWND hwnd, 1231 DWORD dwAttribute, 1232 LPCVOID pvAttribute, 1233 DWORD cbAttribute 1234 ) nothrow @nogc; 1235 1236 /++ 1237 See_also: 1238 https://learn.microsoft.com/en-us/windows/win32/api/dwmapi/ne-dwmapi-dwmwindowattribute 1239 +/ 1240 enum DWMWINDOWATTRIBUTE { 1241 // incomplete, only declare what we need 1242 1243 /++ 1244 Usage: 1245 pvAttribute → `DWM_WINDOW_CORNER_PREFERENCE*` 1246 1247 $(NOTE 1248 Requires Windows 11 or later. 1249 ) 1250 +/ 1251 DWMWA_WINDOW_CORNER_PREFERENCE = 33, 1252 } 1253 1254 /++ 1255 See_also: 1256 https://learn.microsoft.com/en-us/windows/win32/api/dwmapi/ne-dwmapi-dwm_window_corner_preference 1257 +/ 1258 enum DWM_WINDOW_CORNER_PREFERENCE { 1259 /// System decision 1260 DWMWCP_DEFAULT = 0, 1261 1262 /// Never 1263 DWMWCP_DONOTROUND = 1, 1264 1265 // If "appropriate" 1266 DWMWCP_ROUND = 2, 1267 1268 // If "appropriate", but smaller radius 1269 DWMWCP_ROUNDSMALL = 3 1270 } 1271 1272 bool fromDWM( 1273 DWM_WINDOW_CORNER_PREFERENCE value, 1274 out CornerStyle result, 1275 ) @safe pure nothrow @nogc { 1276 switch (value) with (DWM_WINDOW_CORNER_PREFERENCE) { 1277 case DWMWCP_DEFAULT: 1278 result = CornerStyle.automatic; 1279 return true; 1280 case DWMWCP_DONOTROUND: 1281 result = CornerStyle.rectangular; 1282 return true; 1283 case DWMWCP_ROUND: 1284 result = CornerStyle.rounded; 1285 return true; 1286 case DWMWCP_ROUNDSMALL: 1287 result = CornerStyle.roundedSlightly; 1288 return true; 1289 default: 1290 return false; 1291 } 1292 } 1293 1294 bool toDWM( 1295 CornerStyle value, 1296 out DWM_WINDOW_CORNER_PREFERENCE result, 1297 ) @safe pure nothrow @nogc { 1298 final switch (value) with (DWM_WINDOW_CORNER_PREFERENCE) { 1299 case CornerStyle.automatic: 1300 result = DWMWCP_DEFAULT; 1301 return true; 1302 case CornerStyle.rectangular: 1303 result = DWMWCP_DONOTROUND; 1304 return true; 1305 case CornerStyle.rounded: 1306 result = DWMWCP_ROUND; 1307 return true; 1308 case CornerStyle.roundedSlightly: 1309 result = DWMWCP_ROUNDSMALL; 1310 return true; 1311 } 1312 } 1313 1314 pragma(lib, "dwmapi"); 1315 } 1316 } else version(Emscripten) { 1317 } else version (linux) { 1318 //k8: this is hack for rdmd. sorry. 1319 static import core.sys.linux.epoll; 1320 static import core.sys.linux.timerfd; 1321 } 1322 1323 1324 // FIXME: icons on Windows don't look quite right, I think the transparency mask is off. 1325 1326 // http://wiki.dlang.org/Simpledisplay.d 1327 1328 // see : http://www.sbin.org/doc/Xlib/chapt_09.html section on Keyboard Preferences re: scroll lock led 1329 1330 // Cool stuff: I want right alt and scroll lock to do different stuff for personal use. maybe even right ctrl 1331 // but can i control the scroll lock led 1332 1333 1334 // Note: if you are using Image on X, you might want to do: 1335 /* 1336 static if(UsingSimpledisplayX11) { 1337 if(!Image.impl.xshmAvailable) { 1338 // the images will use the slower XPutImage, you might 1339 // want to consider an alternative method to get better speed 1340 } 1341 } 1342 1343 If the shared memory extension is available though, simpledisplay uses it 1344 for a significant speed boost whenever you draw large Images. 1345 */ 1346 1347 // 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. 1348 1349 // WARNING: if you are using with_eventloop, don't forget to call XFlush(XDisplayConnection.get()); before calling loop()! 1350 1351 /* 1352 Biggest FIXME: 1353 make sure the key event numbers match between X and Windows OR provide symbolic constants on each system 1354 1355 clean up opengl contexts when their windows close 1356 1357 fix resizing the bitmaps/pixmaps 1358 */ 1359 1360 // BTW on Windows: 1361 // -L/SUBSYSTEM:WINDOWS:5.0 1362 // to dmd will make a nice windows binary w/o a console if you want that. 1363 1364 /* 1365 Stuff to add: 1366 1367 use multibyte functions everywhere we can 1368 1369 OpenGL windows 1370 more event stuff 1371 extremely basic windows w/ no decoration for tooltips, splash screens, etc. 1372 1373 1374 resizeEvent 1375 and make the windows non-resizable by default, 1376 or perhaps stretched (if I can find something in X like StretchBlt) 1377 1378 take a screenshot function! 1379 1380 Pens and brushes? 1381 Maybe a global event loop? 1382 1383 Mouse deltas 1384 Key items 1385 */ 1386 1387 /* 1388 From MSDN: 1389 1390 You can also use the GET_X_LPARAM or GET_Y_LPARAM macro to extract the x- or y-coordinate. 1391 1392 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. 1393 1394 */ 1395 1396 version(Emscripten) { 1397 1398 } else version(linux) { 1399 version = X11; 1400 version(without_libnotify) { 1401 // we cool 1402 } 1403 else 1404 version = libnotify; 1405 } 1406 1407 version(libnotify) { 1408 pragma(lib, "dl"); 1409 import core.sys.posix.dlfcn; 1410 1411 void delegate()[int] libnotify_action_delegates; 1412 int libnotify_action_delegates_count; 1413 extern(C) static void libnotify_action_callback_sdpy(void* notification, char* action, void* user_data) { 1414 auto idx = cast(int) user_data; 1415 if(auto dgptr = idx in libnotify_action_delegates) { 1416 (*dgptr)(); 1417 libnotify_action_delegates.remove(idx); 1418 } 1419 } 1420 1421 struct C_DynamicLibrary { 1422 void* handle; 1423 this(string name) { 1424 handle = dlopen((name ~ "\0").ptr, RTLD_NOW); 1425 if(handle is null) 1426 throw new Exception("dlopen"); 1427 } 1428 1429 void close() { 1430 dlclose(handle); 1431 } 1432 1433 ~this() { 1434 // close 1435 } 1436 1437 // FIXME: this looks up by name every time.... 1438 template call(string func, Ret, Args...) { 1439 extern(C) Ret function(Args) fptr; 1440 typeof(fptr) call() { 1441 fptr = cast(typeof(fptr)) dlsym(handle, func); 1442 return fptr; 1443 } 1444 } 1445 } 1446 1447 C_DynamicLibrary* libnotify; 1448 } 1449 1450 version(OSX) { 1451 version(OSXCocoa) {} 1452 else { version = X11; } 1453 } 1454 //version = OSXCocoa; // this was written by KennyTM 1455 version(FreeBSD) 1456 version = X11; 1457 version(Solaris) 1458 version = X11; 1459 1460 version(X11) { 1461 version(without_xft) {} 1462 else version=with_xft; 1463 } 1464 1465 void featureNotImplemented()() { 1466 version(allow_unimplemented_features) 1467 throw new NotYetImplementedException(); 1468 else 1469 static assert(0); 1470 } 1471 1472 // these are so the static asserts don't trigger unless you want to 1473 // add support to it for an OS 1474 version(Windows) 1475 version = with_timer; 1476 version(linux) 1477 version = with_timer; 1478 version(OSXCocoa) 1479 version = with_timer; 1480 1481 version(with_timer) 1482 enum bool SimpledisplayTimerAvailable = true; 1483 else 1484 enum bool SimpledisplayTimerAvailable = false; 1485 1486 /// 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. 1487 version(Windows) 1488 enum bool UsingSimpledisplayWindows = true; 1489 else 1490 enum bool UsingSimpledisplayWindows = false; 1491 1492 /// 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. 1493 version(X11) 1494 enum bool UsingSimpledisplayX11 = true; 1495 else 1496 enum bool UsingSimpledisplayX11 = false; 1497 1498 /// 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. 1499 version(OSXCocoa) 1500 enum bool UsingSimpledisplayCocoa = true; 1501 else 1502 enum bool UsingSimpledisplayCocoa = false; 1503 1504 /// Does this platform support multiple windows? If not, trying to create another will cause it to throw an exception. 1505 version(Windows) 1506 enum multipleWindowsSupported = true; 1507 else version(Emscripten) 1508 enum multipleWindowsSupported = false; 1509 else version(X11) 1510 enum multipleWindowsSupported = true; 1511 else version(OSXCocoa) 1512 enum multipleWindowsSupported = true; 1513 else 1514 static assert(0); 1515 1516 version(without_opengl) 1517 enum bool OpenGlEnabled = false; 1518 else 1519 enum bool OpenGlEnabled = true; 1520 1521 /++ 1522 Adds the necessary pragmas to your application to use the Windows gui subsystem. 1523 If you mix this in above your `main` function, you no longer need to use the linker 1524 flags explicitly. It does the necessary version blocks for various compilers and runtimes. 1525 1526 It does nothing if not compiling for Windows, so you need not version it out yourself. 1527 1528 Please note that Windows gui subsystem applications must NOT use std.stdio's stdout and 1529 stderr writeln. It will fail and throw an exception. 1530 1531 This will NOT work with plain `dmd` on Windows; you must use `dmd -m32mscoff` or `dmd -m64`. 1532 1533 History: 1534 Added November 24, 2021 (dub v10.4) 1535 +/ 1536 mixin template EnableWindowsSubsystem() { 1537 version(Windows) 1538 version(CRuntime_Microsoft) { 1539 pragma(linkerDirective, "/subsystem:windows"); 1540 version(LDC) 1541 pragma(linkerDirective, "/entry:wmainCRTStartup"); 1542 else 1543 pragma(linkerDirective, "/entry:mainCRTStartup"); 1544 } 1545 } 1546 1547 1548 /++ 1549 After selecting a type from [WindowTypes], you may further customize 1550 its behavior by setting one or more of these flags. 1551 1552 1553 The different window types have different meanings of `normal`. If the 1554 window type already is a good match for what you want to do, you should 1555 just use [WindowFlags.normal], the default, which will do the right thing 1556 for your users. 1557 1558 The window flags will not always be honored by the operating system 1559 and window managers; they are hints, not commands. 1560 +/ 1561 enum WindowFlags : int { 1562 normal = 0, /// 1563 skipTaskbar = 1, /// 1564 alwaysOnTop = 2, /// 1565 alwaysOnBottom = 4, /// 1566 cannotBeActivated = 8, /// 1567 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. 1568 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. 1569 /++ 1570 Sets the window as a short-lived child of its parent, but unlike an ordinary child, 1571 it is still a top-level window. This should NOT be set separately for most window types. 1572 1573 A transient window will not keep the application open if its main window closes. 1574 1575 $(PITFALL This may not be correctly implemented and its behavior is subject to change.) 1576 1577 1578 From the ICCM: 1579 1580 $(BLOCKQUOTE 1581 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. 1582 1583 $(CITE https://tronche.com/gui/x/icccm/sec-4.html) 1584 ) 1585 1586 So if you are using a window type that already describes this like [WindowTypes.dropdownMenu] etc., you should not use this flag. 1587 1588 History: 1589 Added February 23, 2021 but not yet stabilized. 1590 +/ 1591 transient = 64, 1592 /++ 1593 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. 1594 1595 This is currently only used for the WM_TAKE_FOCUS protocol on X11 at this time. 1596 1597 History: 1598 Added April 1, 2022 1599 +/ 1600 managesChildWindowFocus = 128, 1601 1602 dontAutoShow = 0x1000_0000, /// Don't automatically show window after creation; you will have to call `show()` manually. 1603 } 1604 1605 /++ 1606 When creating a window, you can pass a type to SimpleWindow's constructor, 1607 then further customize the window by changing `WindowFlags`. 1608 1609 1610 You should mostly only need [normal], [undecorated], and [eventOnly] for normal 1611 use. The others are there to build a foundation for a higher level GUI toolkit, 1612 but are themselves not as high level as you might think from their names. 1613 1614 This list is based on the EMWH spec for X11. 1615 http://standards.freedesktop.org/wm-spec/1.4/ar01s05.html#idm139704063786896 1616 +/ 1617 enum WindowTypes : int { 1618 /// An ordinary application window. 1619 normal, 1620 /// 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. 1621 undecorated, 1622 /// 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. 1623 eventOnly, 1624 /// A drop down menu, such as from a menu bar 1625 dropdownMenu, 1626 /// A popup menu, such as from a right click 1627 popupMenu, 1628 /// A popup bubble notification 1629 notification, 1630 /* 1631 menu, /// a tearable menu bar 1632 splashScreen, /// a loading splash screen for your application 1633 tooltip, /// A tiny window showing temporary help text or something. 1634 comboBoxDropdown, 1635 toolbar 1636 */ 1637 /// a dialog box of some sort 1638 dialog, 1639 /// a child nested inside the parent. You must pass a parent window to the ctor 1640 nestedChild, 1641 1642 /++ 1643 The type you get when you pass in an existing browser handle, which means most 1644 of simpledisplay's fancy things will not be done since they were never set up. 1645 1646 Using this to the main SimpleWindow constructor explicitly will trigger an assertion 1647 failure; you should use the existing handle constructor. 1648 1649 History: 1650 Added November 17, 2022 (previously it would have type `normal`) 1651 +/ 1652 minimallyWrapped 1653 } 1654 1655 1656 private __gshared ushort sdpyOpenGLContextVersion = 0; // default: use legacy call 1657 private __gshared bool sdpyOpenGLContextCompatible = true; // default: allow "deprecated" features 1658 private __gshared char* sdpyWindowClassStr = null; 1659 private __gshared bool sdpyOpenGLContextAllowFallback = false; 1660 1661 /** 1662 Set OpenGL context version to use. This has no effect on non-OpenGL windows. 1663 You may want to change context version if you want to use advanced shaders or 1664 other modern OpenGL techinques. This setting doesn't affect already created 1665 windows. You may use version 2.1 as your default, which should be supported 1666 by any box since 2006, so seems to be a reasonable choice. 1667 1668 Note that by default version is set to `0`, which forces SimpleDisplay to use 1669 old context creation code without any version specified. This is the safest 1670 way to init OpenGL, but it may not give you access to advanced features. 1671 1672 See available OpenGL versions here: https://en.wikipedia.org/wiki/OpenGL 1673 */ 1674 void setOpenGLContextVersion() (ubyte hi, ubyte lo) { sdpyOpenGLContextVersion = cast(ushort)(hi<<8|lo); } 1675 1676 /** 1677 Set OpenGL context mode. Modern (3.0+) OpenGL versions deprecated old fixed 1678 pipeline functions, and without "compatible" mode you won't be able to use 1679 your old non-shader-based code with such contexts. By default SimpleDisplay 1680 creates compatible context, so you can gradually upgrade your OpenGL code if 1681 you want to (or leave it as is, as it should "just work"). 1682 */ 1683 @property void openGLContextCompatible() (bool v) { sdpyOpenGLContextCompatible = v; } 1684 1685 /** 1686 Set to `true` to allow creating OpenGL context with lower version than requested 1687 instead of throwing. If fallback was activated (or legacy OpenGL was requested), 1688 `openGLContextFallbackActivated()` will return `true`. 1689 */ 1690 @property void openGLContextAllowFallback() (bool v) { sdpyOpenGLContextAllowFallback = v; } 1691 1692 /** 1693 After creating OpenGL window, you can check this to see if you got only "legacy" OpenGL context. 1694 */ 1695 @property bool openGLContextFallbackActivated() () { return (sdpyOpenGLContextVersion == 0); } 1696 1697 /++ 1698 History: 1699 Added April 24, 2023 (dub v11.0) 1700 +/ 1701 version(without_opengl) {} else 1702 auto openGLCurrentContext() { 1703 version(Windows) 1704 return wglGetCurrentContext(); 1705 else 1706 return glXGetCurrentContext(); 1707 } 1708 1709 1710 /** 1711 Set window class name for all following `new SimpleWindow()` calls. 1712 1713 WARNING! For Windows, you should set your class name before creating any 1714 window, and NEVER change it after that! 1715 */ 1716 void sdpyWindowClass (const(char)[] v) { 1717 import core.stdc.stdlib : realloc; 1718 if (v.length == 0) v = "SimpleDisplayWindow"; 1719 sdpyWindowClassStr = cast(char*)realloc(sdpyWindowClassStr, v.length+1); 1720 if (sdpyWindowClassStr is null) return; // oops 1721 sdpyWindowClassStr[0..v.length+1] = 0; 1722 sdpyWindowClassStr[0..v.length] = v[]; 1723 } 1724 1725 /** 1726 Get current window class name. 1727 */ 1728 string sdpyWindowClass () @trusted { 1729 if (sdpyWindowClassStr is null) return null; 1730 foreach (immutable idx; 0..size_t.max-1) { 1731 if (sdpyWindowClassStr[idx] == 0) return sdpyWindowClassStr[0..idx].idup; 1732 } 1733 return null; 1734 } 1735 1736 /++ 1737 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. 1738 1739 If you want per-monitor dpi values, check [SimpleWindow.actualDpi], but you can fall back to this if it returns 0. 1740 +/ 1741 float[2] getDpi() { 1742 float[2] dpi; 1743 version(Windows) { 1744 HDC screen = GetDC(null); 1745 dpi[0] = GetDeviceCaps(screen, LOGPIXELSX); 1746 dpi[1] = GetDeviceCaps(screen, LOGPIXELSY); 1747 } else version(X11) { 1748 auto display = XDisplayConnection.get; 1749 auto screen = DefaultScreen(display); 1750 1751 void fallback() { 1752 /+ 1753 // 25.4 millimeters in an inch... 1754 dpi[0] = cast(float) DisplayWidth(display, screen) / DisplayWidthMM(display, screen) * 25.4; 1755 dpi[1] = cast(float) DisplayHeight(display, screen) / DisplayHeightMM(display, screen) * 25.4; 1756 +/ 1757 1758 // the physical size isn't actually as important as the logical size since this is 1759 // all about scaling really 1760 dpi[0] = 96; 1761 dpi[1] = 96; 1762 } 1763 1764 auto xft = getXftDpi(); 1765 if(xft is float.init) 1766 fallback(); 1767 else { 1768 dpi[0] = xft; 1769 dpi[1] = xft; 1770 } 1771 } 1772 1773 return dpi; 1774 } 1775 1776 version(X11) 1777 float getXftDpi() { 1778 auto display = XDisplayConnection.get; 1779 1780 char* resourceString = XResourceManagerString(display); 1781 XrmInitialize(); 1782 1783 if (resourceString) { 1784 auto db = XrmGetStringDatabase(resourceString); 1785 XrmValue value; 1786 char* type; 1787 if (XrmGetResource(db, "Xft.dpi", "String", &type, &value) == true) { 1788 if (value.addr) { 1789 import core.stdc.stdlib; 1790 return atof(cast(char*) value.addr); 1791 } 1792 } 1793 } 1794 1795 return float.init; 1796 } 1797 1798 /++ 1799 Implementation used by [SimpleWindow.takeScreenshot]. 1800 1801 Params: 1802 handle = the native window handle. If `NativeWindowHandle.init`, it will attempt to get the whole screen. 1803 width = the width of the image you wish to capture. If 0, it will attempt to capture the full width of the target. 1804 height = the height of the image you wish to capture. If 0, it will attempt to capture the full height of the target. 1805 x = the x-offset of the image to capture, from the left. 1806 y = the y-offset of the image to capture, from the top. 1807 1808 History: 1809 Added on March 14, 2021 1810 1811 Documented public on September 23, 2021 with full support for null params (dub 10.3) 1812 1813 +/ 1814 TrueColorImage trueColorImageFromNativeHandle(PaintingHandle handle, int width = 0, int height = 0, int x = 0, int y = 0) { 1815 TrueColorImage got; 1816 version(X11) { 1817 auto display = XDisplayConnection.get; 1818 if(handle == 0) 1819 handle = RootWindow(display, DefaultScreen(display)); 1820 1821 if(width == 0 || height == 0) { 1822 Window root; 1823 int xpos, ypos; 1824 uint widthret, heightret, borderret, depthret; 1825 XGetGeometry(display, handle, &root, &xpos, &ypos, &widthret, &heightret, &borderret, &depthret); 1826 1827 if(width == 0) 1828 width = widthret; 1829 if(height == 0) 1830 height = heightret; 1831 } 1832 1833 auto image = XGetImage(display, handle, x, y, width, height, (cast(c_ulong) ~0) /*AllPlanes*/, ImageFormat.ZPixmap); 1834 1835 // https://github.com/adamdruppe/arsd/issues/98 1836 1837 auto i = new Image(image); 1838 got = i.toTrueColorImage(); 1839 1840 XDestroyImage(image); 1841 } else version(Windows) { 1842 auto hdc = GetDC(handle); 1843 scope(exit) ReleaseDC(handle, hdc); 1844 1845 if(width == 0 || height == 0) { 1846 BITMAP bmHeader; 1847 auto bm = GetCurrentObject(hdc, OBJ_BITMAP); 1848 GetObject(bm, BITMAP.sizeof, &bmHeader); 1849 if(width == 0) 1850 width = bmHeader.bmWidth; 1851 if(height == 0) 1852 height = bmHeader.bmHeight; 1853 } 1854 1855 auto i = new Image(width, height); 1856 HDC hdcMem = CreateCompatibleDC(hdc); 1857 HBITMAP hbmOld = SelectObject(hdcMem, i.handle); 1858 BitBlt(hdcMem, x, y, width, height, hdc, 0, 0, SRCCOPY); 1859 SelectObject(hdcMem, hbmOld); 1860 DeleteDC(hdcMem); 1861 1862 got = i.toTrueColorImage(); 1863 } else featureNotImplemented(); 1864 1865 return got; 1866 } 1867 1868 version(Windows) extern(Windows) private alias SetProcessDpiAwarenessContext_t = BOOL function(HANDLE); 1869 version(Windows) extern(Windows) private __gshared UINT function(HWND) GetDpiForWindow; 1870 version(Windows) extern(Windows) private __gshared BOOL function(UINT, UINT, PVOID, UINT, UINT) SystemParametersInfoForDpi; 1871 1872 version(Windows) 1873 shared static this() { 1874 auto lib = LoadLibrary("User32.dll"); 1875 if(lib is null) 1876 return; 1877 //scope(exit) 1878 //FreeLibrary(lib); 1879 1880 SetProcessDpiAwarenessContext_t SetProcessDpiAwarenessContext = cast(SetProcessDpiAwarenessContext_t) GetProcAddress(lib, "SetProcessDpiAwarenessContext"); 1881 1882 if(SetProcessDpiAwarenessContext is null) 1883 return; 1884 1885 enum DPI_AWARENESS_CONTEXT_PER_MONITOR_AWARE_V2 = cast(HANDLE) -4; 1886 if(!SetProcessDpiAwarenessContext(DPI_AWARENESS_CONTEXT_PER_MONITOR_AWARE_V2)) { 1887 //writeln(GetLastError()); 1888 } 1889 1890 GetDpiForWindow = cast(typeof(GetDpiForWindow)) GetProcAddress(lib, "GetDpiForWindow"); 1891 SystemParametersInfoForDpi = cast(typeof(SystemParametersInfoForDpi)) GetProcAddress(lib, "SystemParametersInfoForDpi"); 1892 } 1893 1894 /++ 1895 Blocking mode for event loop calls associated with a window instance. 1896 1897 History: 1898 Added December 8, 2021 (dub v10.5). Prior to that, all calls to 1899 `window.eventLoop` were the same as calls to `EventLoop.get.run`; that 1900 is, all would block until the application quit. 1901 1902 That behavior can still be achieved here with `untilApplicationQuits`, 1903 or explicitly calling the top-level `EventLoop.get.run` function. 1904 +/ 1905 enum BlockingMode { 1906 /++ 1907 The event loop call will block until the whole application is ready 1908 to quit if it is the only one running, but if it is nested inside 1909 another one, it will only block until the window you're calling it on 1910 closes. 1911 +/ 1912 automatic = 0x00, 1913 /++ 1914 The event loop call will only return when the whole application 1915 is ready to quit. This usually means all windows have been closed. 1916 1917 This is appropriate for your main application event loop. 1918 +/ 1919 untilApplicationQuits = 0x01, 1920 /++ 1921 The event loop will return when the window you're calling it on 1922 closes. If there are other windows still open, they may be destroyed 1923 unless you have another event loop running later. 1924 1925 This might be appropriate for a modal dialog box loop. Remember that 1926 other windows are still processing input though, so you can end up 1927 with a lengthy call stack if this happens in a loop, similar to a 1928 recursive function (well, it literally is a recursive function, just 1929 not an obvious looking one). 1930 +/ 1931 untilWindowCloses = 0x02, 1932 /++ 1933 If an event loop is already running, this call will immediately 1934 return, allowing the existing loop to handle it. If not, this call 1935 will block until the condition you bitwise-or into the flag. 1936 1937 The default is to block until the application quits, same as with 1938 the `automatic` setting (since if it were nested, which triggers until 1939 window closes in automatic, this flag would instead not block at all), 1940 but if you used `BlockingMode.onlyIfNotNested | BlockingMode.untilWindowCloses`, 1941 it will only nest until the window closes. You might want that if you are 1942 going to open two windows simultaneously and want closing just one of them 1943 to trigger the event loop return. 1944 +/ 1945 onlyIfNotNested = 0x10, 1946 } 1947 1948 /++ 1949 Window corner visuals preference 1950 +/ 1951 enum CornerStyle { 1952 /++ 1953 Use the default style automatically applied by the system or its window manager/compositor. 1954 +/ 1955 automatic, 1956 1957 /++ 1958 Prefer rectangular window corners 1959 +/ 1960 rectangular, 1961 1962 /++ 1963 Prefer rounded window corners 1964 +/ 1965 rounded, 1966 1967 /++ 1968 Prefer slightly-rounded window corners 1969 +/ 1970 roundedSlightly, 1971 } 1972 1973 /++ 1974 The flagship window class. 1975 1976 1977 SimpleWindow tries to make ordinary windows very easy to create and use without locking you 1978 out of more advanced or complex features of the underlying windowing system. 1979 1980 For many applications, you can simply call `new SimpleWindow(some_width, some_height, "some title")` 1981 and get a suitable window to work with. 1982 1983 From there, you can opt into additional features, like custom resizability and OpenGL support 1984 with the next two constructor arguments. Or, if you need even more, you can set a window type 1985 and customization flags with the final two constructor arguments. 1986 1987 If none of that works for you, you can also create a window using native function calls, then 1988 wrap the window in a SimpleWindow instance by calling `new SimpleWindow(native_handle)`. Remember, 1989 though, if you do this, managing the window is still your own responsibility! Notably, you 1990 will need to destroy it yourself. 1991 +/ 1992 class SimpleWindow : CapableOfHandlingNativeEvent, CapableOfBeingDrawnUpon { 1993 1994 /++ 1995 Copies the window's current state into a [TrueColorImage]. 1996 1997 Be warned: this can be a very slow operation 1998 1999 History: 2000 Actually implemented on March 14, 2021 2001 +/ 2002 TrueColorImage takeScreenshot() { 2003 version(Windows) 2004 return trueColorImageFromNativeHandle(impl.hwnd, _width, _height); 2005 else version(OSXCocoa) 2006 throw new NotYetImplementedException(); 2007 else 2008 return trueColorImageFromNativeHandle(impl.window, _width, _height); 2009 } 2010 2011 /++ 2012 Returns the actual logical DPI for the window on its current display monitor. If the window 2013 straddles monitors, it will return the value of one or the other in a platform-defined manner. 2014 2015 Please note this function may return zero if it doesn't know the answer! 2016 2017 2018 On Windows, it returns the dpi per monitor if the operating system supports it (Windows 10), 2019 or a system dpi value if not, which will live-update if the OS supports it (Windows 8 and up). 2020 2021 On X, it reads the xrandr extension to determine monitor positions and sizes. On some systems, 2022 this is not provided, meaning it will return 0. Otherwise, it will determine which monitor the 2023 window primarily resides on by checking the center point of the window against the monitor map. 2024 2025 Returns: 2026 0 if unknown. Otherwise, a rounded value of dots per inch reported by the monitor. It 2027 assumes the X and Y dpi are the same. 2028 2029 History: 2030 Added November 26, 2021 (dub v10.4) 2031 2032 It said "physical dpi" in the description prior to July 29, 2022, but the behavior was 2033 always a logical value on Windows and usually one on Linux too, so now the docs reflect 2034 that. 2035 2036 Bugs: 2037 Probably plenty. I haven't done a lot of tests on this. I know it doesn't automatically 2038 just work on linux; you need to set ARSD_SCALING_FACTOR as an environment variable to 2039 set it. Set ARSD_SCALING_FACTOR=1;1.5 for example to set it to 1x on the primary monitor 2040 and 1.5 on the secondary monitor. 2041 2042 The local dpi is not necessarily related to the physical dpi of the monitor. The name 2043 is a historical misnomer - the real thing of interest is the scale factor and due to 2044 compatibility concerns the scale would modify dpi values to trick applications. But since 2045 that's the terminology common out there, I used it too. 2046 2047 See_Also: 2048 [getDpi] gives the value provided for the default monitor. Not necessarily the same 2049 as this since the window many be on a different monitor, but it is a reasonable fallback 2050 to use if `actualDpi` returns 0. 2051 2052 [onDpiChanged] is changed when `actualDpi` has changed. 2053 +/ 2054 int actualDpi() { 2055 version(X11) bool useFallbackDpi = false; 2056 if(!actualDpiLoadAttempted) { 2057 // FIXME: do the actual monitor we are on 2058 // and on X this is a good chance to load the monitor map. 2059 version(Windows) { 2060 if(GetDpiForWindow) 2061 actualDpi_ = GetDpiForWindow(impl.hwnd); 2062 } else version(X11) { 2063 if(!xRandrInfoLoadAttemped) { 2064 xRandrInfoLoadAttemped = true; 2065 if(!XRandrLibrary.attempted) { 2066 XRandrLibrary.loadDynamicLibrary(); 2067 } 2068 2069 if(XRandrLibrary.loadSuccessful) { 2070 auto display = XDisplayConnection.get; 2071 int scratch; 2072 int major, minor; 2073 if(!XRRQueryExtension(display, &xrrEventBase, &scratch)) 2074 goto fallback; 2075 2076 XRRQueryVersion(display, &major, &minor); 2077 if(major <= 1 && minor < 5) 2078 goto fallback; 2079 2080 int count; 2081 XRRMonitorInfo *monitors = XRRGetMonitors(display, RootWindow(display, DefaultScreen(display)), true, &count); 2082 if(monitors is null) 2083 goto fallback; 2084 scope(exit) XRRFreeMonitors(monitors); 2085 2086 MonitorInfo.info = MonitorInfo.info[0 .. 0]; 2087 MonitorInfo.info.assumeSafeAppend(); 2088 foreach(idx, monitor; monitors[0 .. count]) { 2089 MonitorInfo.info ~= MonitorInfo( 2090 Rectangle(Point(monitor.x, monitor.y), Size(monitor.width, monitor.height)), 2091 Size(monitor.mwidth, monitor.mheight), 2092 cast(int) (customScalingFactorForMonitor(cast(int) idx) * getDpi()[0]) 2093 ); 2094 2095 /+ 2096 if(monitor.mwidth == 0 || monitor.mheight == 0) 2097 // unknown physical size, just guess 96 to avoid divide by zero 2098 MonitorInfo.info ~= MonitorInfo( 2099 Rectangle(Point(monitor.x, monitor.y), Size(monitor.width, monitor.height)), 2100 Size(monitor.mwidth, monitor.mheight), 2101 96 2102 ); 2103 else 2104 // and actual thing 2105 MonitorInfo.info ~= MonitorInfo( 2106 Rectangle(Point(monitor.x, monitor.y), Size(monitor.width, monitor.height)), 2107 Size(monitor.mwidth, monitor.mheight), 2108 minInternal( 2109 // millimeter to int then rounding up. 2110 cast(int)(monitor.width * 25.4 / monitor.mwidth + 0.5), 2111 cast(int)(monitor.height * 25.4 / monitor.mheight + 0.5) 2112 ) 2113 ); 2114 +/ 2115 } 2116 // writeln("Here", MonitorInfo.info); 2117 } 2118 } 2119 2120 if(XRandrLibrary.loadSuccessful) { 2121 updateActualDpi(true); 2122 // writeln("updated"); 2123 2124 if(!requestedInput) { 2125 // this is what requests live updates should the configuration change 2126 // each time you select input, it sends an initial event, so very important 2127 // to not get into a loop of selecting input, getting event, updating data, 2128 // and reselecting input... 2129 requestedInput = true; 2130 XRRSelectInput(display, impl.window, RRScreenChangeNotifyMask); 2131 // writeln("requested input"); 2132 } 2133 } else { 2134 fallback: 2135 // make sure we disable events that aren't coming 2136 xrrEventBase = -1; 2137 // best guess... respect the custom scaling user command to some extent at least though 2138 useFallbackDpi = true; 2139 } 2140 } else version(OSXCocoa) { 2141 actualDpi_ = cast(int)(96 * customScalingFactorForMonitor(0)); // FIXME 2142 } 2143 actualDpiLoadAttempted = true; 2144 } else version(X11) if(MonitorInfo.info.length == 0) { 2145 useFallbackDpi = true; 2146 } 2147 2148 version(X11) 2149 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... 2150 actualDpi_ = cast(int) (getDpi()[0] * customScalingFactorForMonitor(0)); 2151 return actualDpi_; 2152 } 2153 2154 private int actualDpi_; 2155 private bool actualDpiLoadAttempted; 2156 2157 version(X11) private { 2158 bool requestedInput; 2159 static bool xRandrInfoLoadAttemped; 2160 struct MonitorInfo { 2161 Rectangle position; 2162 Size size; 2163 int dpi; 2164 2165 static MonitorInfo[] info; 2166 } 2167 bool screenPositionKnown; 2168 int screenPositionX; 2169 int screenPositionY; 2170 void updateActualDpi(bool loadingNow = false) { 2171 if(!loadingNow && !actualDpiLoadAttempted) 2172 actualDpi(); // just to make it do the load 2173 foreach(idx, m; MonitorInfo.info) { 2174 if(m.position.contains(Point(screenPositionX + this.width / 2, screenPositionY + this.height / 2))) { 2175 bool changed = actualDpi_ && actualDpi_ != m.dpi; 2176 actualDpi_ = m.dpi; 2177 // writeln("monitor ", idx); 2178 if(changed && onDpiChanged) 2179 onDpiChanged(); 2180 break; 2181 } 2182 } 2183 } 2184 } 2185 2186 /++ 2187 Sent when the window is moved to a new DPI context, for example, when it is dragged between monitors 2188 or if the window is moved to a new remote connection or a monitor is hot-swapped. 2189 2190 History: 2191 Added November 26, 2021 (dub v10.4) 2192 2193 See_Also: 2194 [actualDpi] 2195 +/ 2196 void delegate() onDpiChanged; 2197 2198 version(X11) { 2199 void recreateAfterDisconnect() { 2200 if(!stateDiscarded) return; 2201 2202 if(_parent !is null && _parent.stateDiscarded) 2203 _parent.recreateAfterDisconnect(); 2204 2205 bool wasHidden = hidden; 2206 2207 activeScreenPainter = null; // should already be done but just to confirm 2208 2209 actualDpi_ = 0; 2210 actualDpiLoadAttempted = false; 2211 xRandrInfoLoadAttemped = false; 2212 2213 impl.createWindow(_width, _height, _title, openglMode, _parent); 2214 2215 if(auto dh = dropHandler) { 2216 dropHandler = null; 2217 enableDragAndDrop(this, dh); 2218 } 2219 2220 if(recreateAdditionalConnectionState) 2221 recreateAdditionalConnectionState(); 2222 2223 hidden = wasHidden; 2224 stateDiscarded = false; 2225 } 2226 2227 bool stateDiscarded; 2228 void discardConnectionState() { 2229 if(XDisplayConnection.display) 2230 impl.dispose(); // if display is already null, it is hopeless to try to destroy stuff on it anyway 2231 if(discardAdditionalConnectionState) 2232 discardAdditionalConnectionState(); 2233 stateDiscarded = true; 2234 } 2235 2236 void delegate() discardAdditionalConnectionState; 2237 void delegate() recreateAdditionalConnectionState; 2238 2239 } 2240 2241 private DropHandler dropHandler; 2242 2243 SimpleWindow _parent; 2244 bool beingOpenKeepsAppOpen = true; 2245 /++ 2246 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. 2247 2248 The constructor tries to have sane default arguments, so for many cases, you only need to provide a few of them. 2249 2250 Params: 2251 2252 width = the width of the window's client area, in pixels 2253 height = the height of the window's client area, in pixels 2254 title = the title of the window (seen in the title bar, taskbar, etc.). You can change it after construction with the [SimpleWindow.title] property. 2255 opengl = [OpenGlOptions] are yes and no. If yes, it creates an OpenGL context on the window. 2256 resizable = [Resizability] has three options: 2257 $(P `allowResizing`, which allows the window to be resized by the user. The `windowResized` delegate will be called when the size is changed.) 2258 $(P `fixedSize` will not allow the user to resize the window.) 2259 $(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.) 2260 windowType = The type of window you want to make. 2261 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. 2262 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". 2263 +/ 2264 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) { 2265 claimGuiThread(); 2266 version(sdpy_thread_checks) assert(thisIsGuiThread); 2267 this._width = this._virtualWidth = width; 2268 this._height = this._virtualHeight = height; 2269 this.openglMode = opengl; 2270 version(X11) { 2271 // auto scale not implemented except with opengl and even there it is kinda weird 2272 if(resizable == Resizability.automaticallyScaleIfPossible && opengl == OpenGlOptions.no) 2273 resizable = Resizability.fixedSize; 2274 } 2275 this.resizability = resizable; 2276 this.windowType = windowType; 2277 this.customizationFlags = customizationFlags; 2278 this._title = (title is null ? "D Application" : title); 2279 this._parent = parent; 2280 impl.createWindow(width, height, this._title, opengl, parent); 2281 2282 if(windowType == WindowTypes.dropdownMenu || windowType == WindowTypes.popupMenu || windowType == WindowTypes.nestedChild || (customizationFlags & WindowFlags.transient)) 2283 beingOpenKeepsAppOpen = false; 2284 } 2285 2286 /// ditto 2287 this(int width, int height, string title, Resizability resizable, OpenGlOptions opengl = OpenGlOptions.no, WindowTypes windowType = WindowTypes.normal, int customizationFlags = WindowFlags.normal, SimpleWindow parent = null) { 2288 this(width, height, title, opengl, resizable, windowType, customizationFlags, parent); 2289 } 2290 2291 /// Same as above, except using the `Size` struct instead of separate width and height. 2292 this(Size size, string title = null, OpenGlOptions opengl = OpenGlOptions.no, Resizability resizable = Resizability.automaticallyScaleIfPossible) { 2293 this(size.width, size.height, title, opengl, resizable); 2294 } 2295 2296 /// ditto 2297 this(Size size, string title, Resizability resizable, OpenGlOptions opengl = OpenGlOptions.no) { 2298 this(size, title, opengl, resizable); 2299 } 2300 2301 2302 /++ 2303 Creates a window based on the given [Image]. It's client area 2304 width and height is equal to the image. (A window's client area 2305 is the drawable space inside; it excludes the title bar, etc.) 2306 2307 Windows based on images will not be resizable and do not use OpenGL. 2308 2309 It will draw the image in upon creation, but this will be overwritten 2310 upon any draws, including the initial window visible event. 2311 2312 You probably do not want to use this and it may be removed from 2313 the library eventually, or I might change it to be a "permanent" 2314 background image; one that is automatically drawn on it before any 2315 other drawing event. idk. 2316 +/ 2317 this(Image image, string title = null) { 2318 this(image.width, image.height, title); 2319 this.image = image; 2320 } 2321 2322 /++ 2323 Wraps a native window handle with very little additional processing - notably no destruction 2324 this is incomplete so don't use it for much right now. The purpose of this is to make native 2325 windows created through the low level API (so you can use platform-specific options and 2326 other details SimpleWindow does not expose) available to the event loop wrappers. 2327 +/ 2328 this(NativeWindowHandle nativeWindow) { 2329 windowType = WindowTypes.minimallyWrapped; 2330 version(Windows) 2331 impl.hwnd = nativeWindow; 2332 else version(X11) { 2333 impl.window = nativeWindow; 2334 if(nativeWindow) 2335 display = XDisplayConnection.get(); // get initial display to not segfault 2336 } else version(Emscripten) { 2337 // FIXME 2338 } else version(OSXCocoa) { 2339 if(nativeWindow !is NullWindow) throw new NotYetImplementedException(); 2340 } else featureNotImplemented(); 2341 // FIXME: set the size correctly 2342 _width = 1; 2343 _height = 1; 2344 if(nativeWindow) 2345 nativeMapping[cast(void*) nativeWindow] = this; 2346 2347 beingOpenKeepsAppOpen = false; 2348 2349 if(nativeWindow) 2350 CapableOfHandlingNativeEvent.nativeHandleMapping[nativeWindow] = this; 2351 _suppressDestruction = true; // so it doesn't try to close 2352 } 2353 2354 /++ 2355 Used iff [WindowFlags.managesChildWindowFocus] is set when the window is created. 2356 The delegate will be called when the window manager asks you to take focus. 2357 2358 This is currently only used for the WM_TAKE_FOCUS protocol on X11 at this time. 2359 2360 History: 2361 Added April 1, 2022 (dub v10.8) 2362 +/ 2363 SimpleWindow delegate() setRequestedInputFocus; 2364 2365 /// Experimental, do not use yet 2366 /++ 2367 Grabs exclusive input from the user until you release it with 2368 [releaseInputGrab]. 2369 2370 2371 Note: it is extremely rude to do this without good reason. 2372 Reasons may include doing some kind of mouse drag operation 2373 or popping up a temporary menu that should get events and will 2374 be dismissed at ease by the user clicking away. 2375 2376 Params: 2377 keyboard = do you want to grab keyboard input? 2378 mouse = grab mouse input? 2379 confine = confine the mouse cursor to inside this window? 2380 2381 History: 2382 Prior to March 11, 2021, grabbing the keyboard would always also 2383 set the X input focus. Now, it only focuses if it is a non-transient 2384 window and otherwise manages the input direction internally. 2385 2386 This means spurious focus/blur events will no longer be sent and the 2387 application will not steal focus from other applications (which the 2388 window manager may have rejected anyway). 2389 +/ 2390 void grabInput(bool keyboard = true, bool mouse = true, bool confine = false) { 2391 static if(UsingSimpledisplayX11) { 2392 XSync(XDisplayConnection.get, 0); 2393 if(keyboard) { 2394 if(isTransient && _parent) { 2395 /* 2396 FIXME: 2397 setting the keyboard focus is not actually that helpful, what I more likely want 2398 is the events from the parent window to be sent over here if we're transient. 2399 */ 2400 2401 _parent.inputProxy = this; 2402 } else { 2403 2404 SimpleWindow setTo; 2405 if(setRequestedInputFocus !is null) 2406 setTo = setRequestedInputFocus(); 2407 if(setTo is null) 2408 setTo = this; 2409 2410 // sdpyPrintDebugString("grabInput() ", setTo.impl.window; 2411 XSetInputFocus(XDisplayConnection.get, setTo.impl.window, RevertToParent, CurrentTime); 2412 } 2413 } 2414 if(mouse) { 2415 if(auto res = XGrabPointer(XDisplayConnection.get, this.impl.window, false /* owner_events */, 2416 EventMask.PointerMotionMask // FIXME: not efficient 2417 | EventMask.ButtonPressMask 2418 | EventMask.ButtonReleaseMask 2419 /* event mask */, GrabMode.GrabModeAsync, GrabMode.GrabModeAsync, confine ? this.impl.window : None, None, CurrentTime) 2420 ) 2421 { 2422 XSync(XDisplayConnection.get, 0); 2423 import core.stdc.stdio; 2424 printf("Grab input failed %d\n", res); 2425 //throw new Exception("Grab input failed"); 2426 } else { 2427 // cool 2428 } 2429 } 2430 2431 } else version(Windows) { 2432 // FIXME: keyboard? 2433 SetCapture(impl.hwnd); 2434 if(confine) { 2435 RECT rcClip; 2436 //RECT rcOldClip; 2437 //GetClipCursor(&rcOldClip); 2438 GetWindowRect(hwnd, &rcClip); 2439 ClipCursor(&rcClip); 2440 } 2441 } else version(Emscripten) { 2442 // nothing necessary 2443 } else version(OSXCocoa) { 2444 // throw new NotYetImplementedException(); 2445 } else static assert(0); 2446 } 2447 2448 private Point imePopupLocation = Point(0, 0); 2449 2450 /++ 2451 Sets the location for the IME (input method editor) to pop up when the user activates it. 2452 2453 Bugs: 2454 Not implemented outside X11. 2455 +/ 2456 void setIMEPopupLocation(Point location) { 2457 static if(UsingSimpledisplayX11) { 2458 imePopupLocation = location; 2459 updateIMEPopupLocation(); 2460 } else { 2461 // this is non-fatal at this point... but still wanna find it when i search for NotYetImplementedException at least 2462 // throw new NotYetImplementedException(); 2463 } 2464 } 2465 2466 /// ditto 2467 void setIMEPopupLocation(int x, int y) { 2468 return setIMEPopupLocation(Point(x, y)); 2469 } 2470 2471 // we need to remind XIM of where we wanted to place the IME whenever the window moves 2472 // so this function gets called in setIMEPopupLocation as well as whenever the window 2473 // receives a ConfigureNotify event 2474 private void updateIMEPopupLocation() { 2475 static if(UsingSimpledisplayX11) { 2476 if (xic is null) { 2477 return; 2478 } 2479 2480 XPoint nspot; 2481 nspot.x = cast(short) imePopupLocation.x; 2482 nspot.y = cast(short) imePopupLocation.y; 2483 XVaNestedList preeditAttr = XVaCreateNestedList(0, /*XNSpotLocation*/"spotLocation".ptr, &nspot, null); 2484 XSetICValues(xic, /*XNPreeditAttributes*/"preeditAttributes".ptr, preeditAttr, null); 2485 XFree(preeditAttr); 2486 } 2487 } 2488 2489 private bool imeFocused = true; 2490 2491 /++ 2492 Tells the IME whether or not an input field is currently focused in the window. 2493 2494 Bugs: 2495 Not implemented outside X11. 2496 +/ 2497 void setIMEFocused(bool value) { 2498 imeFocused = value; 2499 updateIMEFocused(); 2500 } 2501 2502 // used to focus/unfocus the IC if necessary when the window gains/loses focus 2503 private void updateIMEFocused() { 2504 static if(UsingSimpledisplayX11) { 2505 if (xic is null) { 2506 return; 2507 } 2508 2509 if (focused && imeFocused) { 2510 XSetICFocus(xic); 2511 } else { 2512 XUnsetICFocus(xic); 2513 } 2514 } 2515 } 2516 2517 /++ 2518 Returns the native window. 2519 2520 History: 2521 Added November 5, 2021 (dub v10.4). Prior to that, you'd have 2522 to access it through the `impl` member (which is semi-supported 2523 but platform specific and here it is simple enough to offer an accessor). 2524 2525 Bugs: 2526 Not implemented outside Windows or X11. 2527 +/ 2528 NativeWindowHandle nativeWindowHandle() { 2529 version(X11) 2530 return impl.window; 2531 else version(Windows) 2532 return impl.hwnd; 2533 else 2534 throw new NotYetImplementedException(); 2535 } 2536 2537 private bool isTransient() { 2538 with(WindowTypes) 2539 final switch(windowType) { 2540 case normal, undecorated, eventOnly: 2541 case nestedChild, minimallyWrapped: 2542 return (customizationFlags & WindowFlags.transient) ? true : false; 2543 case dropdownMenu, popupMenu, notification, dialog: 2544 return true; 2545 } 2546 } 2547 2548 private SimpleWindow inputProxy; 2549 2550 /++ 2551 Releases the grab acquired by [grabInput]. 2552 +/ 2553 void releaseInputGrab() { 2554 static if(UsingSimpledisplayX11) { 2555 XUngrabPointer(XDisplayConnection.get, CurrentTime); 2556 if(_parent) 2557 _parent.inputProxy = null; 2558 } else version(Windows) { 2559 ReleaseCapture(); 2560 ClipCursor(null); 2561 } else version(OSXCocoa) { 2562 // throw new NotYetImplementedException(); 2563 } else version(Emscripten) { 2564 // nothing needed 2565 } else static assert(0); 2566 } 2567 2568 /++ 2569 Sets the input focus to this window. 2570 2571 You shouldn't call this very often - please let the user control the input focus. 2572 +/ 2573 void focus() { 2574 static if(UsingSimpledisplayX11) { 2575 SimpleWindow setTo; 2576 if(setRequestedInputFocus !is null) 2577 setTo = setRequestedInputFocus(); 2578 if(setTo is null) 2579 setTo = this; 2580 // sdpyPrintDebugString("sdpy.focus() ", setTo.impl.window); 2581 XSetInputFocus(XDisplayConnection.get, setTo.impl.window, RevertToParent, CurrentTime); 2582 } else version(Windows) { 2583 SetFocus(this.impl.hwnd); 2584 } else version(Emscripten) { 2585 throw new NotYetImplementedException(); 2586 } else version(OSXCocoa) { 2587 throw new NotYetImplementedException(); 2588 } else static assert(0); 2589 } 2590 2591 /++ 2592 Requests attention from the user for this window. 2593 2594 2595 The typical result of this function is to change the color 2596 of the taskbar icon, though it may be tweaked on specific 2597 platforms. 2598 2599 It is meant to unobtrusively tell the user that something 2600 relevant to them happened in the background and they should 2601 check the window when they get a chance. Upon receiving the 2602 keyboard focus, the window will automatically return to its 2603 natural state. 2604 2605 If the window already has the keyboard focus, this function 2606 may do nothing, because the user is presumed to already be 2607 giving the window attention. 2608 2609 Implementation_note: 2610 2611 `requestAttention` uses the _NET_WM_STATE_DEMANDS_ATTENTION 2612 atom on X11 and the FlashWindow function on Windows. 2613 +/ 2614 void requestAttention() { 2615 if(_focused) 2616 return; 2617 2618 version(Windows) { 2619 FLASHWINFO info; 2620 info.cbSize = info.sizeof; 2621 info.hwnd = impl.hwnd; 2622 info.dwFlags = FLASHW_TRAY; 2623 info.uCount = 1; 2624 2625 FlashWindowEx(&info); 2626 2627 } else version(X11) { 2628 demandingAttention = true; 2629 demandAttention(this, true); 2630 } else version(Emscripten) { 2631 throw new NotYetImplementedException(); 2632 } else version(OSXCocoa) { 2633 throw new NotYetImplementedException(); 2634 } else static assert(0); 2635 } 2636 2637 private bool _focused; 2638 2639 version(X11) private bool demandingAttention; 2640 2641 /// This will be called when WM wants to close your window (i.e. user clicked "close" icon, for example). 2642 /// You'll have to call `close()` manually if you set this delegate. 2643 void delegate () closeQuery; 2644 2645 /// This will be called when window visibility was changed. 2646 void delegate (bool becomesVisible) visibilityChanged; 2647 2648 /// This will be called when window becomes visible for the first time. 2649 /// You can do OpenGL initialization here. Note that in X11 you can't call 2650 /// [setAsCurrentOpenGlContext] right after window creation, or X11 may 2651 /// fail to send reparent and map events (hit that with proprietary NVidia drivers). 2652 /// So you need to wait until this is called and call setAsCurrentOpenGlContext in there, then do the OpenGL initialization. 2653 private bool _visibleForTheFirstTimeCalled; 2654 void delegate () visibleForTheFirstTime; 2655 2656 /// Returns true if the window has been closed. 2657 final @property bool closed() { return _closed; } 2658 2659 private final @property bool notClosed() { return !_closed; } 2660 2661 /// Returns true if the window is focused. 2662 final @property bool focused() { return _focused; } 2663 2664 private bool _visible; 2665 /// Returns true if the window is visible (mapped). 2666 final @property bool visible() { return _visible; } 2667 2668 /// Closes the window. If there are no more open windows, the event loop will terminate. 2669 void close() { 2670 if (!_closed) { 2671 runInGuiThread( { 2672 if(_closed) return; // another thread got to it first. this isn't a big deal, it just means our message was queued 2673 if (onClosing !is null) onClosing(); 2674 impl.closeWindow(); 2675 _closed = true; 2676 } ); 2677 } 2678 } 2679 2680 /++ 2681 `close` is one of the few methods that can be called from other threads. This `shared` overload reflects that. 2682 2683 History: 2684 Overload added on March 7, 2021. 2685 +/ 2686 void close() shared { 2687 (cast() this).close(); 2688 } 2689 2690 /++ 2691 2692 +/ 2693 void maximize() { 2694 version(Windows) 2695 ShowWindow(impl.hwnd, SW_MAXIMIZE); 2696 else version(X11) { 2697 setNetWmStateAtom(this.impl.window, GetAtom!("_NET_WM_STATE_MAXIMIZED_VERT", false)(XDisplayConnection.get), true, GetAtom!("_NET_WM_STATE_MAXIMIZED_HORZ", false)(XDisplayConnection.get)); 2698 2699 // also note _NET_WM_STATE_FULLSCREEN 2700 } 2701 2702 } 2703 2704 private bool _fullscreen; 2705 version(Windows) 2706 private WINDOWPLACEMENT g_wpPrev; 2707 2708 /// not fully implemented but planned for a future release 2709 void fullscreen(bool yes) { 2710 version(Windows) { 2711 g_wpPrev.length = WINDOWPLACEMENT.sizeof; 2712 DWORD dwStyle = GetWindowLong(hwnd, GWL_STYLE); 2713 if (dwStyle & WS_OVERLAPPEDWINDOW) { 2714 MONITORINFO mi; 2715 mi.cbSize = MONITORINFO.sizeof; 2716 if (GetWindowPlacement(hwnd, &g_wpPrev) && 2717 GetMonitorInfo(MonitorFromWindow(hwnd, 2718 MONITOR_DEFAULTTOPRIMARY), &mi)) { 2719 SetWindowLong(hwnd, GWL_STYLE, 2720 dwStyle & ~WS_OVERLAPPEDWINDOW); 2721 SetWindowPos(hwnd, HWND_TOP, 2722 mi.rcMonitor.left, mi.rcMonitor.top, 2723 mi.rcMonitor.right - mi.rcMonitor.left, 2724 mi.rcMonitor.bottom - mi.rcMonitor.top, 2725 SWP_NOOWNERZORDER | SWP_FRAMECHANGED); 2726 } 2727 } else { 2728 SetWindowLong(hwnd, GWL_STYLE, 2729 dwStyle | WS_OVERLAPPEDWINDOW); 2730 SetWindowPlacement(hwnd, &g_wpPrev); 2731 SetWindowPos(hwnd, null, 0, 0, 0, 0, 2732 SWP_NOMOVE | SWP_NOSIZE | SWP_NOZORDER | 2733 SWP_NOOWNERZORDER | SWP_FRAMECHANGED); 2734 } 2735 2736 } else version(X11) { 2737 setNetWmStateAtom(this.impl.window, GetAtom!("_NET_WM_STATE_FULLSCREEN", false)(XDisplayConnection.get), yes); 2738 } 2739 2740 _fullscreen = yes; 2741 2742 } 2743 2744 bool fullscreen() { 2745 return _fullscreen; 2746 } 2747 2748 /++ 2749 Note: only implemented on Windows. No-op on other platforms. You may want to use [hide] instead. 2750 2751 +/ 2752 void minimize() { 2753 version(Windows) 2754 ShowWindow(impl.hwnd, SW_MINIMIZE); 2755 //else version(X11) 2756 //setNetWmStateAtom(this, GetAtom!("_NET_WM_STATE_MINIMIZED", false)(XDisplayConnection.get), true); 2757 } 2758 2759 /// Alias for `hidden = false` 2760 void show() { 2761 hidden = false; 2762 } 2763 2764 /// Alias for `hidden = true` 2765 void hide() { 2766 hidden = true; 2767 } 2768 2769 /// Hide cursor when it enters the window. 2770 void hideCursor() { 2771 version(OSXCocoa) throw new NotYetImplementedException(); else 2772 if (!_closed) impl.hideCursor(); 2773 } 2774 2775 /// Don't hide cursor when it enters the window. 2776 void showCursor() { 2777 version(OSXCocoa) throw new NotYetImplementedException(); else 2778 if (!_closed) impl.showCursor(); 2779 } 2780 2781 /** "Warp" mouse pointer to coordinates relative to window top-left corner. Return "success" flag. 2782 * 2783 * Please remember that the cursor is a shared resource that should usually be left to the user's 2784 * control. Try to think for other approaches before using this function. 2785 * 2786 * Note: "warping" pointer will not send any synthesised mouse events, so you probably doesn't want 2787 * to use it to move mouse pointer to some active GUI area, for example, as your window won't 2788 * receive "mouse moved here" event. 2789 */ 2790 bool warpMouse (int x, int y) { 2791 version(X11) { 2792 if (!_closed) { impl.warpMouse(x, y); return true; } 2793 } else version(Windows) { 2794 if (!_closed) { 2795 POINT point; 2796 point.x = x; 2797 point.y = y; 2798 if(ClientToScreen(impl.hwnd, &point)) { 2799 SetCursorPos(point.x, point.y); 2800 return true; 2801 } 2802 } 2803 } 2804 return false; 2805 } 2806 2807 /// Send dummy window event to ping event loop. Required to process NotificationIcon on X11, for example. 2808 void sendDummyEvent () { 2809 version(X11) { 2810 if (!_closed) { impl.sendDummyEvent(); } 2811 } 2812 } 2813 2814 /// Set window minimal size. 2815 void setMinSize (int minwidth, int minheight) { 2816 version(OSXCocoa) throw new NotYetImplementedException(); else 2817 if (!_closed) impl.setMinSize(minwidth, minheight); 2818 } 2819 2820 /// Set window maximal size. 2821 void setMaxSize (int maxwidth, int maxheight) { 2822 version(OSXCocoa) throw new NotYetImplementedException(); else 2823 if (!_closed) impl.setMaxSize(maxwidth, maxheight); 2824 } 2825 2826 /// Set window resize step (window size will be changed with the given granularity on supported platforms). 2827 /// Currently only supported on X11. 2828 void setResizeGranularity (int granx, int grany) { 2829 version(OSXCocoa) throw new NotYetImplementedException(); else 2830 if (!_closed) impl.setResizeGranularity(granx, grany); 2831 } 2832 2833 /// Move window. 2834 void move(int x, int y) { 2835 version(OSXCocoa) throw new NotYetImplementedException(); else 2836 if (!_closed) impl.move(x, y); 2837 } 2838 2839 /// ditto 2840 void move(Point p) { 2841 version(OSXCocoa) throw new NotYetImplementedException(); else 2842 if (!_closed) impl.move(p.x, p.y); 2843 } 2844 2845 /++ 2846 Resize window. 2847 2848 Note that the width and height of the window are NOT instantly 2849 updated - it waits for the window manager to approve the resize 2850 request, which means you must return to the event loop before the 2851 width and height are actually changed. 2852 +/ 2853 void resize(int w, int h) { 2854 if(!_closed && _fullscreen) fullscreen = false; 2855 version(OSXCocoa) throw new NotYetImplementedException(); else 2856 if (!_closed) impl.resize(w, h); 2857 } 2858 2859 /// Move and resize window (this can be faster and more visually pleasant than doing it separately). 2860 void moveResize (int x, int y, int w, int h) { 2861 if(!_closed && _fullscreen) fullscreen = false; 2862 version(OSXCocoa) throw new NotYetImplementedException(); else 2863 if (!_closed) impl.moveResize(x, y, w, h); 2864 } 2865 2866 private bool _hidden; 2867 2868 /// Returns true if the window is hidden. 2869 final @property bool hidden() { 2870 return _hidden; 2871 } 2872 2873 /// Shows or hides the window based on the bool argument. 2874 final @property void hidden(bool b) { 2875 _hidden = b; 2876 version(Windows) { 2877 ShowWindow(impl.hwnd, b ? SW_HIDE : SW_SHOW); 2878 } else version(X11) { 2879 if(b) 2880 //XUnmapWindow(impl.display, impl.window); 2881 XWithdrawWindow(impl.display, impl.window, DefaultScreen(impl.display)); 2882 else 2883 XMapWindow(impl.display, impl.window); 2884 } else version(OSXCocoa) { 2885 // throw new NotYetImplementedException(); 2886 } else version(Emscripten) { 2887 } else static assert(0); 2888 } 2889 2890 /// Sets the window opacity. On X11 this requires a compositor to be running. On windows the WindowFlags.extraComposite must be set at window creation. 2891 void opacity(double opacity) @property 2892 in { 2893 assert(opacity >= 0 && opacity <= 1); 2894 } do { 2895 version (Windows) { 2896 impl.setOpacity(cast(ubyte)(255 * opacity)); 2897 } else version (X11) { 2898 impl.setOpacity(cast(uint)(uint.max * opacity)); 2899 } else throw new NotYetImplementedException(); 2900 } 2901 2902 /++ 2903 Sets your event handlers, without entering the event loop. Useful if you 2904 have multiple windows - set the handlers on each window, then only do 2905 [eventLoop] on your main window or call `EventLoop.get.run();`. 2906 2907 This assigns the given handlers to [handleKeyEvent], [handleCharEvent], 2908 [handlePulse], and [handleMouseEvent] automatically based on the provide 2909 delegate signatures. 2910 +/ 2911 void setEventHandlers(T...)(T eventHandlers) { 2912 // FIXME: add more events 2913 foreach(handler; eventHandlers) { 2914 static if(__traits(compiles, handleKeyEvent = handler)) { 2915 handleKeyEvent = handler; 2916 } else static if(__traits(compiles, handleCharEvent = handler)) { 2917 handleCharEvent = handler; 2918 } else static if(__traits(compiles, handlePulse = handler)) { 2919 handlePulse = handler; 2920 } else static if(__traits(compiles, handleMouseEvent = handler)) { 2921 handleMouseEvent = handler; 2922 } else static assert(0, "I can't use this event handler " ~ typeof(handler).stringof ~ "\nHave you tried using the delegate keyword?"); 2923 } 2924 } 2925 2926 /++ 2927 The event loop automatically returns when the window is closed 2928 pulseTimeout is given in milliseconds. If pulseTimeout == 0, no 2929 pulse timer is created. The event loop will block until an event 2930 arrives or the pulse timer goes off. 2931 2932 The given `eventHandlers` are passed to [setEventHandlers], which in turn 2933 assigns them to [handleKeyEvent], [handleCharEvent], [handlePulse], and 2934 [handleMouseEvent], based on the signature of delegates you provide. 2935 2936 Give one with no parameters to set a timer pulse handler. Give one that 2937 takes [KeyEvent] for a key handler, [MouseEvent], for a mouse handler, 2938 and one that takes `dchar` for a char event handler. You can use as many 2939 or as few handlers as you need for your application. 2940 2941 Bugs: 2942 2943 $(PITFALL 2944 You should always have one event loop live for your application. 2945 If you make two windows in sequence, the second call to eventLoop 2946 might fail: 2947 2948 --- 2949 // don't do this! 2950 auto window = new SimpleWindow(); 2951 window.eventLoop(0); 2952 2953 auto window2 = new SimpleWindow(); 2954 window2.eventLoop(0); // problematic! might crash 2955 --- 2956 2957 simpledisplay's current implementation assumes that final cleanup is 2958 done when the event loop refcount reaches zero. So after the first 2959 eventLoop returns, when there isn't already another one active, it assumes 2960 the program will exit soon and cleans up. 2961 2962 This is arguably a bug that it doesn't reinitialize, and I'll probably change 2963 it eventually, but in the mean time, there's an easy solution: 2964 2965 --- 2966 // do this 2967 EventLoop mainEventLoop = EventLoop.get; // just add this line 2968 2969 auto window = new SimpleWindow(); 2970 window.eventLoop(0); 2971 2972 auto window2 = new SimpleWindow(); 2973 window2.eventLoop(0); // perfectly fine since mainEventLoop still alive 2974 --- 2975 2976 By adding a top-level reference to the event loop, it ensures the final cleanup 2977 is not performed until it goes out of scope too, letting the individual window loops 2978 work without trouble despite the bug. 2979 ) 2980 2981 History: 2982 The overload without `pulseTimeout` was added on December 8, 2021. 2983 2984 On December 9, 2021, the default blocking mode (which is now configurable 2985 because [eventLoopWithBlockingMode] was added) switched from 2986 [BlockingMode.untilApplicationQuits] over to [BlockingMode.automatic]. This 2987 should almost never be noticeable to you since the typical simpledisplay 2988 paradigm has been (and I still recommend) to have one `eventLoop` call. 2989 2990 See_Also: 2991 [eventLoopWithBlockingMode] 2992 +/ 2993 final int eventLoop(T...)( 2994 long pulseTimeout, /// set to zero if you don't want a pulse. 2995 T eventHandlers) /// delegate list like std.concurrency.receive 2996 { 2997 return eventLoopWithBlockingMode(BlockingMode.automatic, pulseTimeout, eventHandlers); 2998 } 2999 3000 /// ditto 3001 final int eventLoop(T...)(T eventHandlers) if(T.length == 0 || is(T[0] == delegate)) 3002 { 3003 return eventLoopWithBlockingMode(BlockingMode.automatic, 0, eventHandlers); 3004 } 3005 3006 /++ 3007 This is the function [eventLoop] forwards to. It, in turn, forwards to `EventLoop.get.run`. 3008 3009 History: 3010 Added December 8, 2021 (dub v10.5) 3011 3012 Previously, this implementation was right inside [eventLoop], but when I wanted 3013 to add the new [BlockingMode] parameter, the compiler got in a trouble loop so I 3014 just renamed it instead of adding as an overload. Besides, the new name makes it 3015 easier to remember the order and avoids ambiguity between two int-like params anyway. 3016 3017 See_Also: 3018 [SimpleWindow.eventLoop], [EventLoop] 3019 3020 Bugs: 3021 The blocking mode is not implemented on OSX Cocoa nor on the (deprecated) arsd.eventloop. 3022 +/ 3023 final int eventLoopWithBlockingMode(T...)( 3024 BlockingMode blockingMode, /// when you want this function to block until 3025 long pulseTimeout, /// set to zero if you don't want a pulse. 3026 T eventHandlers) /// delegate list like std.concurrency.receive 3027 { 3028 setEventHandlers(eventHandlers); 3029 3030 version(with_eventloop) { 3031 // delegates event loop to my other module 3032 version(X11) 3033 XFlush(display); 3034 3035 import arsd.eventloop; 3036 auto handle = setInterval(handlePulse, cast(int) pulseTimeout); 3037 scope(exit) clearInterval(handle); 3038 3039 loop(); 3040 return 0; 3041 } else version(OSXCocoa) { 3042 // FIXME 3043 if (handlePulse !is null && pulseTimeout != 0) { 3044 timer = NSTimer.schedule(pulseTimeout*1e-3, 3045 cast(NSid) view, sel_registerName("simpledisplay_pulse:"), 3046 null, true); 3047 } 3048 3049 view.setNeedsDisplay(true); 3050 3051 NSApp.run(); 3052 return 0; 3053 } else { 3054 EventLoop el = EventLoop(pulseTimeout, handlePulse); 3055 3056 if((blockingMode & BlockingMode.onlyIfNotNested) && el.impl.refcount > 1) 3057 return 0; 3058 3059 return el.run( 3060 ((blockingMode & 0x0f) == BlockingMode.untilApplicationQuits) ? 3061 null : 3062 &this.notClosed 3063 ); 3064 } 3065 } 3066 3067 /++ 3068 This lets you draw on the window (or its backing buffer) using basic 3069 2D primitives. 3070 3071 Be sure to call this in a limited scope because your changes will not 3072 actually appear on the window until ScreenPainter's destructor runs. 3073 3074 Returns: an instance of [ScreenPainter], which has the drawing methods 3075 on it to draw on this window. 3076 3077 Params: 3078 manualInvalidations = if you set this to true, you will need to 3079 set the invalid rectangle on the painter yourself. If false, it 3080 assumes the whole window has been redrawn each time you draw. 3081 3082 Only invalidated rectangles are blitted back to the window when 3083 the destructor runs. Doing this yourself can reduce flickering 3084 of child windows. 3085 3086 History: 3087 The `manualInvalidations` parameter overload was added on 3088 December 30, 2021 (dub v10.5) 3089 +/ 3090 ScreenPainter draw() { 3091 return draw(false); 3092 } 3093 /// ditto 3094 ScreenPainter draw(bool manualInvalidations) { 3095 return impl.getPainter(manualInvalidations); 3096 } 3097 3098 // This is here to implement the interface we use for various native handlers. 3099 NativeEventHandler getNativeEventHandler() { return handleNativeEvent; } 3100 3101 // maps native window handles to SimpleWindow instances, if there are any 3102 // you shouldn't need this, but it is public in case you do in a native event handler or something 3103 // mac uses void* cuz NSObject opHash won't pick up in typeinfo 3104 version(OSXCocoa) 3105 public __gshared SimpleWindow[void*] nativeMapping; 3106 else 3107 public __gshared SimpleWindow[NativeWindowHandle] nativeMapping; 3108 3109 // the size the user requested in the constructor, in automatic scale modes it always pretends to be this size 3110 private int _virtualWidth; 3111 private int _virtualHeight; 3112 3113 /// Width of the window's drawable client area, in pixels. 3114 @scriptable 3115 final @property int width() const pure nothrow @safe @nogc { 3116 if(resizability == Resizability.automaticallyScaleIfPossible) 3117 return _virtualWidth; 3118 else 3119 return _width; 3120 } 3121 3122 /// Height of the window's drawable client area, in pixels. 3123 @scriptable 3124 final @property int height() const pure nothrow @safe @nogc { 3125 if(resizability == Resizability.automaticallyScaleIfPossible) 3126 return _virtualHeight; 3127 else 3128 return _height; 3129 } 3130 3131 /++ 3132 Returns the actual size of the window, bypassing the logical 3133 illusions of [Resizability.automaticallyScaleIfPossible]. 3134 3135 History: 3136 Added November 11, 2022 (dub v10.10) 3137 +/ 3138 final @property Size actualWindowSize() const pure nothrow @safe @nogc { 3139 return Size(_width, _height); 3140 } 3141 3142 3143 private int _width; 3144 private int _height; 3145 3146 // HACK: making the best of some copy constructor woes with refcounting 3147 private ScreenPainterImplementation* activeScreenPainter_; 3148 3149 protected ScreenPainterImplementation* activeScreenPainter() { return activeScreenPainter_; } 3150 protected void activeScreenPainter(ScreenPainterImplementation* i) { activeScreenPainter_ = i; } 3151 3152 private OpenGlOptions openglMode; 3153 private Resizability resizability; 3154 private WindowTypes windowType; 3155 private int customizationFlags; 3156 3157 /// `true` if OpenGL was initialized for this window. 3158 @property bool isOpenGL () const pure nothrow @safe @nogc { 3159 version(without_opengl) 3160 return false; 3161 else 3162 return (openglMode == OpenGlOptions.yes); 3163 } 3164 @property Resizability resizingMode () const pure nothrow @safe @nogc { return resizability; } /// Original resizability. 3165 @property WindowTypes type () const pure nothrow @safe @nogc { return windowType; } /// Original window type. 3166 @property int customFlags () const pure nothrow @safe @nogc { return customizationFlags; } /// Original customization flags. 3167 3168 /// "Lock" this window handle, to do multithreaded synchronization. You probably won't need 3169 /// to call this, as it's not recommended to share window between threads. 3170 void mtLock () { 3171 version(X11) { 3172 XLockDisplay(this.display); 3173 } 3174 } 3175 3176 /// "Unlock" this window handle, to do multithreaded synchronization. You probably won't need 3177 /// to call this, as it's not recommended to share window between threads. 3178 void mtUnlock () { 3179 version(X11) { 3180 XUnlockDisplay(this.display); 3181 } 3182 } 3183 3184 /// Emit a beep to get user's attention. 3185 void beep () { 3186 version(X11) { 3187 XBell(this.display, 100); 3188 } else version(Windows) { 3189 MessageBeep(0xFFFFFFFF); 3190 } 3191 } 3192 3193 3194 3195 version(without_opengl) {} else { 3196 3197 /// 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`. 3198 void delegate() redrawOpenGlScene; 3199 3200 /// This will allow you to change OpenGL vsync state. 3201 final @property void vsync (bool wait) { 3202 if (this._closed) return; // window may be closed, but timer is still firing; avoid GLXBadDrawable error 3203 version(X11) { 3204 setAsCurrentOpenGlContext(); 3205 glxSetVSync(display, impl.window, wait); 3206 } else version(Windows) { 3207 setAsCurrentOpenGlContext(); 3208 wglSetVSync(wait); 3209 } 3210 } 3211 3212 /// Set this to `false` if you don't need to do `glFinish()` after `swapOpenGlBuffers()`. 3213 /// Note that at least NVidia proprietary driver may segfault if you will modify texture fast 3214 /// enough without waiting 'em to finish their frame business. 3215 bool useGLFinish = true; 3216 3217 // FIXME: it should schedule it for the end of the current iteration of the event loop... 3218 /// 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. 3219 void redrawOpenGlSceneNow() { 3220 version(X11) if (!this._visible) return; // no need to do this if window is invisible 3221 if (this._closed) return; // window may be closed, but timer is still firing; avoid GLXBadDrawable error 3222 if(redrawOpenGlScene is null) 3223 return; 3224 3225 this.mtLock(); 3226 scope(exit) this.mtUnlock(); 3227 3228 this.setAsCurrentOpenGlContext(); 3229 3230 redrawOpenGlScene(); 3231 3232 this.swapOpenGlBuffers(); 3233 // 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. 3234 if (useGLFinish) glFinish(); 3235 } 3236 3237 private bool redrawOpenGlSceneSoonSet = false; 3238 private static class RedrawOpenGlSceneEvent { 3239 SimpleWindow w; 3240 this(SimpleWindow w) { this.w = w; } 3241 } 3242 private RedrawOpenGlSceneEvent redrawOpenGlSceneEvent; 3243 /++ 3244 Queues an opengl redraw as soon as the other pending events are cleared. 3245 +/ 3246 void redrawOpenGlSceneSoon() { 3247 if(redrawOpenGlScene is null) 3248 return; 3249 3250 if(!redrawOpenGlSceneSoonSet) { 3251 redrawOpenGlSceneEvent = new RedrawOpenGlSceneEvent(this); 3252 this.addEventListener((RedrawOpenGlSceneEvent e) { e.w.redrawOpenGlSceneNow(); }); 3253 redrawOpenGlSceneSoonSet = true; 3254 } 3255 this.postEvent(redrawOpenGlSceneEvent, true); 3256 } 3257 3258 3259 /// Makes all gl* functions target this window until changed. This is only valid if you passed `OpenGlOptions.yes` to the constructor. 3260 void setAsCurrentOpenGlContext() { 3261 assert(openglMode == OpenGlOptions.yes); 3262 version(X11) { 3263 if(glXMakeCurrent(display, impl.window, impl.glc) == 0) 3264 throw new Exception("glXMakeCurrent"); 3265 } else version(Windows) { 3266 static if (SdpyIsUsingIVGLBinds) import iv.glbinds; // override druntime windows imports 3267 if (!wglMakeCurrent(ghDC, ghRC)) 3268 throw new Exception("wglMakeCurrent " ~ toInternal!int(GetLastError())); // let windows users suffer too 3269 } 3270 } 3271 3272 /// Makes all gl* functions target this window until changed. This is only valid if you passed `OpenGlOptions.yes` to the constructor. 3273 /// This doesn't throw, returning success flag instead. 3274 bool setAsCurrentOpenGlContextNT() nothrow { 3275 assert(openglMode == OpenGlOptions.yes); 3276 version(X11) { 3277 return (glXMakeCurrent(display, impl.window, impl.glc) != 0); 3278 } else version(Windows) { 3279 static if (SdpyIsUsingIVGLBinds) import iv.glbinds; // override druntime windows imports 3280 return wglMakeCurrent(ghDC, ghRC) ? true : false; 3281 } 3282 } 3283 3284 /// 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. 3285 /// This doesn't throw, returning success flag instead. 3286 bool releaseCurrentOpenGlContext() nothrow { 3287 assert(openglMode == OpenGlOptions.yes); 3288 version(X11) { 3289 return (glXMakeCurrent(display, 0, null) != 0); 3290 } else version(Windows) { 3291 static if (SdpyIsUsingIVGLBinds) import iv.glbinds; // override druntime windows imports 3292 return wglMakeCurrent(ghDC, null) ? true : false; 3293 } 3294 } 3295 3296 /++ 3297 simpledisplay always uses double buffering, usually automatically. This 3298 manually swaps the OpenGL buffers. You should only use this if you are NOT 3299 using the [redrawOpenGlScene] delegate. 3300 3301 3302 You must not this yourself if you use [redrawOpenGlScene] because simpledisplay will do it 3303 for you after calling your `redrawOpenGlScene`. Please note that once you swap 3304 buffers, the contents become undefined - the implementation, in the OpenGL driver 3305 or the desktop compositor, may not actually just swap two buffers. The back buffer's 3306 contents are $(B undefined) after calling this function. 3307 3308 See: https://learn.microsoft.com/en-us/windows/win32/api/wingdi/nf-wingdi-swapbuffers 3309 and https://linux.die.net/man/3/glxswapbuffers 3310 3311 Remember that this may throw an exception, which you can catch in a multithreaded 3312 application to keep your thread from dying from an unhandled exception. 3313 +/ 3314 void swapOpenGlBuffers() { 3315 assert(openglMode == OpenGlOptions.yes); 3316 version(X11) { 3317 if (!this._visible) return; // no need to do this if window is invisible 3318 if (this._closed) return; // window may be closed, but timer is still firing; avoid GLXBadDrawable error 3319 glXSwapBuffers(display, impl.window); 3320 } else version(Windows) { 3321 SwapBuffers(ghDC); 3322 } 3323 } 3324 } 3325 3326 /++ 3327 Set the window title, which is visible on the window manager title bar, operating system taskbar, etc. 3328 3329 3330 --- 3331 auto window = new SimpleWindow(100, 100, "First title"); 3332 window.title = "A new title"; 3333 --- 3334 3335 You may call this function at any time. 3336 +/ 3337 @property void title(string title) { 3338 _title = title; 3339 version(OSXCocoa) throw new NotYetImplementedException(); else 3340 impl.setTitle(title); 3341 } 3342 3343 private string _title; 3344 3345 /// Gets the title 3346 @property string title() { 3347 if(_title is null) 3348 _title = getRealTitle(); 3349 return _title; 3350 } 3351 3352 /++ 3353 Get the title as set by the window manager. 3354 May not match what you attempted to set. 3355 +/ 3356 string getRealTitle() { 3357 static if(is(typeof(impl.getTitle()))) 3358 return impl.getTitle(); 3359 else 3360 return null; 3361 } 3362 3363 // don't use this generally it is not yet really released 3364 version(X11) 3365 @property Image secret_icon() { 3366 return secret_icon_inner; 3367 } 3368 private Image secret_icon_inner; 3369 3370 3371 /// Set the icon that is seen in the title bar or taskbar, etc., for the user. If passed `null`, does nothing. 3372 @property void icon(MemoryImage icon) { 3373 if(icon is null) 3374 return; 3375 auto tci = icon.getAsTrueColorImage(); 3376 version(Windows) { 3377 winIcon = new WindowsIcon(icon); 3378 SendMessageA(impl.hwnd, 0x0080 /*WM_SETICON*/, 0 /*ICON_SMALL*/, cast(LPARAM) winIcon.hIcon); // there is also 1 == ICON_BIG 3379 } else version(X11) { 3380 secret_icon_inner = Image.fromMemoryImage(icon); 3381 // FIXME: ensure this is correct 3382 auto display = XDisplayConnection.get; 3383 arch_ulong[] buffer; 3384 buffer ~= icon.width; 3385 buffer ~= icon.height; 3386 foreach(c; tci.imageData.colors) { 3387 arch_ulong b; 3388 b |= c.a << 24; 3389 b |= c.r << 16; 3390 b |= c.g << 8; 3391 b |= c.b; 3392 buffer ~= b; 3393 } 3394 3395 XChangeProperty( 3396 display, 3397 impl.window, 3398 GetAtom!("_NET_WM_ICON", true)(display), 3399 GetAtom!"CARDINAL"(display), 3400 32 /* bits */, 3401 0 /*PropModeReplace*/, 3402 buffer.ptr, 3403 cast(int) buffer.length); 3404 } else version(OSXCocoa) { 3405 throw new NotYetImplementedException(); 3406 } else version(Emscripten) { 3407 throw new NotYetImplementedException(); 3408 } else static assert(0); 3409 } 3410 3411 version(Windows) 3412 private WindowsIcon winIcon; 3413 3414 bool _suppressDestruction; 3415 3416 ~this() { 3417 if(!thisIsGuiThread) return; // FIXME: leaks if multithreaded gc 3418 if(_suppressDestruction) 3419 return; 3420 impl.dispose(); 3421 } 3422 3423 private bool _closed; 3424 3425 // the idea here is to draw something temporary on top of the main picture e.g. a blinking cursor 3426 /* 3427 ScreenPainter drawTransiently() { 3428 return impl.getPainter(); 3429 } 3430 */ 3431 3432 /// Draws an image on the window. This is meant to provide quick look 3433 /// of a static image generated elsewhere. 3434 @property void image(Image i) { 3435 /+ 3436 version(Windows) { 3437 BITMAP bm; 3438 HDC hdc = GetDC(hwnd); 3439 HDC hdcMem = CreateCompatibleDC(hdc); 3440 HBITMAP hbmOld = SelectObject(hdcMem, i.handle); 3441 3442 GetObject(i.handle, bm.sizeof, &bm); 3443 3444 BitBlt(hdc, 0, 0, bm.bmWidth, bm.bmHeight, hdcMem, 0, 0, SRCCOPY); 3445 3446 SelectObject(hdcMem, hbmOld); 3447 DeleteDC(hdcMem); 3448 ReleaseDC(hwnd, hdc); 3449 3450 /* 3451 RECT r; 3452 r.right = i.width; 3453 r.bottom = i.height; 3454 InvalidateRect(hwnd, &r, false); 3455 */ 3456 } else 3457 version(X11) { 3458 if(!destroyed) { 3459 if(i.usingXshm) 3460 XShmPutImage(display, cast(Drawable) window, gc, i.handle, 0, 0, 0, 0, i.width, i.height, false); 3461 else 3462 XPutImage(display, cast(Drawable) window, gc, i.handle, 0, 0, 0, 0, i.width, i.height); 3463 } 3464 } else 3465 version(OSXCocoa) { 3466 draw().drawImage(Point(0, 0), i); 3467 setNeedsDisplay(view, true); 3468 } else static assert(0); 3469 +/ 3470 auto painter = this.draw; 3471 painter.drawImage(Point(0, 0), i); 3472 } 3473 3474 /++ 3475 Changes the cursor for the window. If the cursor is hidden via [hideCursor], this has no effect. 3476 3477 --- 3478 window.cursor = GenericCursor.Help; 3479 // now the window mouse cursor is set to a generic help 3480 --- 3481 3482 +/ 3483 @property void cursor(MouseCursor cursor) { 3484 version(OSXCocoa) 3485 {} // featureNotImplemented(); 3486 else 3487 if(this.impl.curHidden <= 0) { 3488 static if(UsingSimpledisplayX11) { 3489 auto ch = cursor.cursorHandle; 3490 XDefineCursor(XDisplayConnection.get(), this.impl.window, ch); 3491 } else version(Windows) { 3492 auto ch = cursor.cursorHandle; 3493 impl.currentCursor = ch; 3494 SetCursor(ch); // redraw without waiting for mouse movement to update 3495 } else featureNotImplemented(); 3496 } 3497 3498 } 3499 3500 /// What follows are the event handlers. These are set automatically 3501 /// by the eventLoop function, but are still public so you can change 3502 /// them later. wasPressed == true means key down. false == key up. 3503 3504 /// Handles a low-level keyboard event. Settable through setEventHandlers. 3505 void delegate(KeyEvent ke) handleKeyEvent; 3506 3507 /// Handles a higher level keyboard event - c is the character just pressed. Settable through setEventHandlers. 3508 void delegate(dchar c) handleCharEvent; 3509 3510 /// Handles a timer pulse. Settable through setEventHandlers. 3511 void delegate() handlePulse; 3512 3513 /// Called when the focus changes, param is if we have it (true) or are losing it (false). 3514 void delegate(bool) onFocusChange; 3515 3516 /** Called inside `close()` method. Our window is still alive, and we can free various resources. 3517 * Sometimes it is easier to setup the delegate instead of subclassing. */ 3518 void delegate() onClosing; 3519 3520 /** Called when we received destroy notification. At this stage we cannot do much with our window 3521 * (as it is already dead, and it's native handle cannot be used), but we still can do some 3522 * last minute cleanup. */ 3523 void delegate() onDestroyed; 3524 3525 static if (UsingSimpledisplayX11) 3526 /** Called when Expose event comes. See Xlib manual to understand the arguments. 3527 * Return `false` if you want Simpledisplay to copy backbuffer, or `true` if you did it yourself. 3528 * You will probably never need to setup this handler, it is for very low-level stuff. 3529 * 3530 * WARNING! Xlib is multithread-locked when this handles is called! */ 3531 bool delegate(int x, int y, int width, int height, int eventsLeft) handleExpose; 3532 3533 //version(Windows) 3534 //bool delegate(WPARAM wParam, LPARAM lParam) handleWM_PAINT; 3535 3536 private { 3537 int lastMouseX = int.min; 3538 int lastMouseY = int.min; 3539 void mdx(ref MouseEvent ev) { 3540 if(lastMouseX == int.min || lastMouseY == int.min) { 3541 ev.dx = 0; 3542 ev.dy = 0; 3543 } else { 3544 ev.dx = ev.x - lastMouseX; 3545 ev.dy = ev.y - lastMouseY; 3546 } 3547 3548 lastMouseX = ev.x; 3549 lastMouseY = ev.y; 3550 } 3551 } 3552 3553 /// Mouse event handler. Settable through setEventHandlers. 3554 void delegate(MouseEvent) handleMouseEvent; 3555 3556 /// use to redraw child widgets if you use system apis to add stuff 3557 void delegate() paintingFinished; 3558 3559 void delegate() paintingFinishedDg() { 3560 return paintingFinished; 3561 } 3562 3563 /// handle a resize, after it happens. You must construct the window with Resizability.allowResizing 3564 /// for this to ever happen. 3565 void delegate(int width, int height) windowResized; 3566 3567 /++ 3568 Platform specific - handle any native message this window gets. 3569 3570 Note: this is called *in addition to* other event handlers, unless you either: 3571 3572 1) On X11, return 0 indicating that you handled it. Any other return value is simply discarded. 3573 3574 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. 3575 3576 On Windows, your delegate takes the form of `int delegate(HWND hwnd, UINT message, WPARAM wParam, LPARAM lParam, out int mustReturn)`. 3577 3578 On X, it takes the form of `int delegate(XEvent)`. 3579 3580 History: 3581 In ancient versions, this was `static`. If you want a global hook, use [handleNativeGlobalEvent] instead. 3582 3583 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. 3584 +/ 3585 NativeEventHandler handleNativeEvent_; 3586 3587 @property NativeEventHandler handleNativeEvent() nothrow pure @nogc const @safe { 3588 return handleNativeEvent_; 3589 } 3590 @property void handleNativeEvent(NativeEventHandler neh) nothrow pure @nogc @safe { 3591 handleNativeEvent_ = neh; 3592 } 3593 3594 version(Windows) 3595 // compatibility shim with the old deprecated way 3596 // in this one, if you return 0, it means you must return. otherwise the ret value is ignored. 3597 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) { 3598 handleNativeEvent_ = delegate int(HWND h, UINT m, WPARAM w, LPARAM l, out int r) { 3599 auto ret = dg(h, m, w, l); 3600 if(ret == 0) 3601 r = 1; 3602 return ret; 3603 }; 3604 } 3605 3606 /// This is the same as handleNativeEvent, but static so it can hook ALL events in the loop. 3607 /// If you used to use handleNativeEvent depending on it being static, just change it to use 3608 /// this instead and it will work the same way. 3609 __gshared NativeEventHandler handleNativeGlobalEvent; 3610 3611 // private: 3612 /// The native implementation is available, but you shouldn't use it unless you are 3613 /// familiar with the underlying operating system, don't mind depending on it, and 3614 /// know simpledisplay.d's internals too. It is virtually private; you can hopefully 3615 /// do what you need to do with handleNativeEvent instead. 3616 /// 3617 /// This is likely to eventually change to be just a struct holding platform-specific 3618 /// handles instead of a template mixin at some point because I'm not happy with the 3619 /// code duplication here (ironically). 3620 mixin NativeSimpleWindowImplementation!() impl; 3621 3622 /** 3623 This is in-process one-way (from anything to window) event sending mechanics. 3624 It is thread-safe, so it can be used in multi-threaded applications to send, 3625 for example, "wake up and repaint" events when thread completed some operation. 3626 This will allow to avoid using timer pulse to check events with synchronization, 3627 'cause event handler will be called in UI thread. You can stop guessing which 3628 pulse frequency will be enough for your app. 3629 Note that events handlers may be called in arbitrary order, i.e. last registered 3630 handler can be called first, and vice versa. 3631 */ 3632 public: 3633 /** Is our custom event queue empty? Can be used in simple cases to prevent 3634 * "spamming" window with events it can't cope with. 3635 * It is safe to call this from non-UI threads. 3636 */ 3637 @property bool eventQueueEmpty() () { 3638 synchronized(this) { 3639 foreach (const ref o; eventQueue[0..eventQueueUsed]) if (!o.doProcess) return false; 3640 } 3641 return true; 3642 } 3643 3644 /** Does our custom event queue contains at least one with the given type? 3645 * Can be used in simple cases to prevent "spamming" window with events 3646 * it can't cope with. 3647 * It is safe to call this from non-UI threads. 3648 */ 3649 @property bool eventQueued(ET:Object) () { 3650 synchronized(this) { 3651 foreach (const ref o; eventQueue[0..eventQueueUsed]) { 3652 if (!o.doProcess) { 3653 if (cast(ET)(o.evt)) return true; 3654 } 3655 } 3656 } 3657 return false; 3658 } 3659 3660 /++ 3661 Event listeners added with [addEventListener] have their exceptions swallowed by the event loop. This delegate can handle them again before it proceeds. 3662 3663 History: 3664 Added May 12, 2021 3665 +/ 3666 void delegate(Exception e) nothrow eventUncaughtException; 3667 3668 /** Add listener for custom event. Can be used like this: 3669 * 3670 * --------------------- 3671 * auto eid = win.addEventListener((MyStruct evt) { ... }); 3672 * ... 3673 * win.removeEventListener(eid); 3674 * --------------------- 3675 * 3676 * Returns: 0 on failure (should never happen, so ignore it) 3677 * 3678 * $(WARNING Don't use this method in object destructors!) 3679 * 3680 * $(WARNING It is better to register all event handlers and don't remove 'em, 3681 * 'cause if event handler id counter will overflow, you won't be able 3682 * to register any more events.) 3683 */ 3684 uint addEventListener(ET:Object) (void delegate (ET) dg) { 3685 if (dg is null) return 0; // ignore empty handlers 3686 synchronized(this) { 3687 //FIXME: abort on overflow? 3688 if (++lastUsedHandlerId == 0) { --lastUsedHandlerId; return 0; } // alas, can't register more events. at all. 3689 EventHandlerEntry e; 3690 e.dg = delegate (Object o) { 3691 if (auto co = cast(ET)o) { 3692 try { 3693 dg(co); 3694 } catch (Exception e) { 3695 // sorry! 3696 if(eventUncaughtException) 3697 eventUncaughtException(e); 3698 } 3699 return true; 3700 } 3701 return false; 3702 }; 3703 e.id = lastUsedHandlerId; 3704 auto optr = eventHandlers.ptr; 3705 eventHandlers ~= e; 3706 if (eventHandlers.ptr !is optr) { 3707 import core.memory : GC; 3708 if (eventHandlers.ptr is GC.addrOf(eventHandlers.ptr)) GC.setAttr(eventHandlers.ptr, GC.BlkAttr.NO_INTERIOR); 3709 } 3710 return lastUsedHandlerId; 3711 } 3712 } 3713 3714 /// Remove event listener. It is safe to pass invalid event id here. 3715 /// $(WARNING Don't use this method in object destructors!) 3716 void removeEventListener() (uint id) { 3717 if (id == 0 || id > lastUsedHandlerId) return; 3718 synchronized(this) { 3719 foreach (immutable idx; 0..eventHandlers.length) { 3720 if (eventHandlers[idx].id == id) { 3721 foreach (immutable c; idx+1..eventHandlers.length) eventHandlers[c-1] = eventHandlers[c]; 3722 eventHandlers[$-1].dg = null; 3723 eventHandlers.length -= 1; 3724 eventHandlers.assumeSafeAppend; 3725 return; 3726 } 3727 } 3728 } 3729 } 3730 3731 /// Post event to queue. It is safe to call this from non-UI threads. 3732 /// If `timeoutmsecs` is greater than zero, the event will be delayed for at least `timeoutmsecs` milliseconds. 3733 /// if `replace` is `true`, replace all existing events typed `ET` with the new one (if `evt` is empty, remove 'em all) 3734 /// Returns `true` if event was queued. Always returns `false` if `evt` is null. 3735 bool postTimeout(ET:Object) (ET evt, uint timeoutmsecs, bool replace=false) { 3736 if (this.closed) return false; // closed windows can't handle events 3737 3738 // remove all events of type `ET` 3739 void removeAllET () { 3740 uint eidx = 0, ec = eventQueueUsed; 3741 auto eptr = eventQueue.ptr; 3742 while (eidx < ec) { 3743 if (eptr.doProcess) { ++eidx; ++eptr; continue; } 3744 if (cast(ET)eptr.evt !is null) { 3745 // i found her! 3746 if (inCustomEventProcessor) { 3747 // if we're in custom event processing loop, processor will clear it for us 3748 eptr.evt = null; 3749 ++eidx; 3750 ++eptr; 3751 } else { 3752 foreach (immutable c; eidx+1..ec) eventQueue.ptr[c-1] = eventQueue.ptr[c]; 3753 ec = --eventQueueUsed; 3754 // clear last event (it is already copied) 3755 eventQueue.ptr[ec].evt = null; 3756 } 3757 } else { 3758 ++eidx; 3759 ++eptr; 3760 } 3761 } 3762 } 3763 3764 if (evt is null) { 3765 if (replace) { synchronized(this) removeAllET(); } 3766 // ignore empty events, they can't be handled anyway 3767 return false; 3768 } 3769 3770 // add events even if no event FD/event object created yet 3771 synchronized(this) { 3772 if (replace) removeAllET(); 3773 if (eventQueueUsed == uint.max) return false; // just in case 3774 if (eventQueueUsed < eventQueue.length) { 3775 eventQueue[eventQueueUsed++] = QueuedEvent(evt, timeoutmsecs); 3776 } else { 3777 if (eventQueue.capacity == eventQueue.length) { 3778 // need to reallocate; do a trick to ensure that old array is cleared 3779 auto oarr = eventQueue; 3780 eventQueue ~= QueuedEvent(evt, timeoutmsecs); 3781 // just in case, do yet another check 3782 if (oarr.length != 0 && oarr.ptr !is eventQueue.ptr) foreach (ref e; oarr[0..eventQueueUsed]) e.evt = null; 3783 import core.memory : GC; 3784 if (eventQueue.ptr is GC.addrOf(eventQueue.ptr)) GC.setAttr(eventQueue.ptr, GC.BlkAttr.NO_INTERIOR); 3785 } else { 3786 auto optr = eventQueue.ptr; 3787 eventQueue ~= QueuedEvent(evt, timeoutmsecs); 3788 assert(eventQueue.ptr is optr); 3789 } 3790 ++eventQueueUsed; 3791 assert(eventQueueUsed == eventQueue.length); 3792 } 3793 if (!eventWakeUp()) { 3794 // can't wake up event processor, so there is no reason to keep the event 3795 assert(eventQueueUsed > 0); 3796 eventQueue[--eventQueueUsed].evt = null; 3797 return false; 3798 } 3799 return true; 3800 } 3801 } 3802 3803 /// Post event to queue. It is safe to call this from non-UI threads. 3804 /// if `replace` is `true`, replace all existing events typed `ET` with the new one (if `evt` is empty, remove 'em all) 3805 /// Returns `true` if event was queued. Always returns `false` if `evt` is null. 3806 bool postEvent(ET:Object) (ET evt, bool replace=false) { 3807 return postTimeout!ET(evt, 0, replace); 3808 } 3809 3810 private: 3811 private import core.time : MonoTime; 3812 3813 version(Posix) { 3814 __gshared int customEventFDRead = -1; 3815 __gshared int customEventFDWrite = -1; 3816 __gshared int customSignalFD = -1; 3817 } else version(Windows) { 3818 __gshared HANDLE customEventH = null; 3819 } 3820 3821 // wake up event processor 3822 static bool eventWakeUp () { 3823 version(X11) { 3824 import core.sys.posix.unistd : write; 3825 ulong n = 1; 3826 if (customEventFDWrite >= 0) write(customEventFDWrite, &n, n.sizeof); 3827 return true; 3828 } else version(Windows) { 3829 if (customEventH !is null) SetEvent(customEventH); 3830 return true; 3831 } else version(OSXCocoa) { 3832 if(globalAppDelegate) 3833 globalAppDelegate.performSelectorOnMainThread(sel_registerName("sdpyCustomEventWakeup:"), null, false); 3834 return true; 3835 } else { 3836 // not implemented for other OSes 3837 return false; 3838 } 3839 } 3840 3841 static struct QueuedEvent { 3842 Object evt; 3843 bool timed = false; 3844 MonoTime hittime = MonoTime.zero; 3845 bool doProcess = false; // process event at the current iteration (internal flag) 3846 3847 this (Object aevt, uint toutmsecs) { 3848 evt = aevt; 3849 if (toutmsecs > 0) { 3850 import core.time : msecs; 3851 timed = true; 3852 hittime = MonoTime.currTime+toutmsecs.msecs; 3853 } 3854 } 3855 } 3856 3857 alias CustomEventHandler = bool delegate (Object o) nothrow; 3858 static struct EventHandlerEntry { 3859 CustomEventHandler dg; 3860 uint id; 3861 } 3862 3863 uint lastUsedHandlerId; 3864 EventHandlerEntry[] eventHandlers; 3865 QueuedEvent[] eventQueue = null; 3866 uint eventQueueUsed = 0; // to avoid `.assumeSafeAppend` and length changes 3867 bool inCustomEventProcessor = false; // required to properly remove events 3868 3869 // process queued events and call custom event handlers 3870 // this will not process events posted from called handlers (such events are postponed for the next iteration) 3871 void processCustomEvents () @system { 3872 bool hasSomethingToDo = false; 3873 uint ecount; 3874 bool ocep; 3875 synchronized(this) { 3876 ocep = inCustomEventProcessor; 3877 inCustomEventProcessor = true; 3878 ecount = eventQueueUsed; // user may want to post new events from an event handler; process 'em on next iteration 3879 auto ctt = MonoTime.currTime; 3880 bool hasEmpty = false; 3881 // mark events to process (this is required for `eventQueued()`) 3882 foreach (ref qe; eventQueue[0..ecount]) { 3883 if (qe.evt is null) { hasEmpty = true; continue; } 3884 if (qe.timed) { 3885 qe.doProcess = (qe.hittime <= ctt); 3886 } else { 3887 qe.doProcess = true; 3888 } 3889 hasSomethingToDo = (hasSomethingToDo || qe.doProcess); 3890 } 3891 if (!hasSomethingToDo) { 3892 // remove empty events 3893 if (hasEmpty) { 3894 uint eidx = 0, ec = eventQueueUsed; 3895 auto eptr = eventQueue.ptr; 3896 while (eidx < ec) { 3897 if (eptr.evt is null) { 3898 foreach (immutable c; eidx+1..ec) eventQueue.ptr[c-1] = eventQueue.ptr[c]; 3899 ec = --eventQueueUsed; 3900 eventQueue.ptr[ec].evt = null; // make GC life easier 3901 } else { 3902 ++eidx; 3903 ++eptr; 3904 } 3905 } 3906 } 3907 inCustomEventProcessor = ocep; 3908 return; 3909 } 3910 } 3911 // process marked events 3912 uint efree = 0; // non-processed events will be put at this index 3913 EventHandlerEntry[] eh; 3914 Object evt; 3915 foreach (immutable eidx; 0..ecount) { 3916 synchronized(this) { 3917 if (!eventQueue[eidx].doProcess) { 3918 // skip this event 3919 assert(efree <= eidx); 3920 if (efree != eidx) { 3921 // copy this event to queue start 3922 eventQueue[efree] = eventQueue[eidx]; 3923 eventQueue[eidx].evt = null; // just in case 3924 } 3925 ++efree; 3926 continue; 3927 } 3928 evt = eventQueue[eidx].evt; 3929 eventQueue[eidx].evt = null; // in case event handler will hit GC 3930 if (evt is null) continue; // just in case 3931 // try all handlers; this can be slow, but meh... 3932 eh = eventHandlers; 3933 } 3934 foreach (ref evhan; eh) if (evhan.dg !is null) evhan.dg(evt); 3935 evt = null; 3936 eh = null; 3937 } 3938 synchronized(this) { 3939 // move all unprocessed events to queue top; efree holds first "free index" 3940 foreach (immutable eidx; ecount..eventQueueUsed) { 3941 assert(efree <= eidx); 3942 if (efree != eidx) eventQueue[efree] = eventQueue[eidx]; 3943 ++efree; 3944 } 3945 eventQueueUsed = efree; 3946 // wake up event processor on next event loop iteration if we have more queued events 3947 // also, remove empty events 3948 bool awaken = false; 3949 uint eidx = 0, ec = eventQueueUsed; 3950 auto eptr = eventQueue.ptr; 3951 while (eidx < ec) { 3952 if (eptr.evt is null) { 3953 foreach (immutable c; eidx+1..ec) eventQueue.ptr[c-1] = eventQueue.ptr[c]; 3954 ec = --eventQueueUsed; 3955 eventQueue.ptr[ec].evt = null; // make GC life easier 3956 } else { 3957 if (!awaken && !eptr.timed) { eventWakeUp(); awaken = true; } 3958 ++eidx; 3959 ++eptr; 3960 } 3961 } 3962 inCustomEventProcessor = ocep; 3963 } 3964 } 3965 3966 // for all windows in nativeMapping 3967 package static void processAllCustomEvents () @system { 3968 3969 cleanupQueue.process(); 3970 3971 justCommunication.processCustomEvents(); 3972 3973 foreach (SimpleWindow sw; SimpleWindow.nativeMapping.byValue) { 3974 if (sw is null || sw.closed) continue; 3975 sw.processCustomEvents(); 3976 } 3977 3978 runPendingRunInGuiThreadDelegates(); 3979 } 3980 3981 // 0: infinite (i.e. no scheduled events in queue) 3982 uint eventQueueTimeoutMSecs () { 3983 synchronized(this) { 3984 if (eventQueueUsed == 0) return 0; 3985 if (inCustomEventProcessor) assert(0, "WUTAFUUUUUUU..."); // the thing that should not be. ABSOLUTELY! (c) 3986 uint res = int.max; 3987 auto ctt = MonoTime.currTime; 3988 foreach (const ref qe; eventQueue[0..eventQueueUsed]) { 3989 if (qe.evt is null) assert(0, "WUTAFUUUUUUU..."); // the thing that should not be. ABSOLUTELY! (c) 3990 if (qe.doProcess) continue; // just in case 3991 if (!qe.timed) return 1; // minimal 3992 if (qe.hittime <= ctt) return 1; // minimal 3993 auto tms = (qe.hittime-ctt).total!"msecs"; 3994 if (tms < 1) tms = 1; // safety net 3995 if (tms >= int.max) tms = int.max-1; // and another safety net 3996 if (res > tms) res = cast(uint)tms; 3997 } 3998 return (res >= int.max ? 0 : res); 3999 } 4000 } 4001 4002 // for all windows in nativeMapping 4003 static uint eventAllQueueTimeoutMSecs () { 4004 uint res = uint.max; 4005 foreach (SimpleWindow sw; SimpleWindow.nativeMapping.byValue) { 4006 if (sw is null || sw.closed) continue; 4007 uint to = sw.eventQueueTimeoutMSecs(); 4008 if (to && to < res) { 4009 res = to; 4010 if (to == 1) break; // can't have less than this 4011 } 4012 } 4013 return (res >= int.max ? 0 : res); 4014 } 4015 4016 version(X11) { 4017 ResizeEvent pendingResizeEvent; 4018 } 4019 4020 /++ 4021 When in opengl mode and automatically resizing, it will set the opengl viewport to stretch. 4022 4023 If you work with multiple opengl contexts and/or threads, this might be more trouble than it is 4024 worth so you can disable it by setting this to `true`. 4025 4026 History: 4027 Added November 13, 2022. 4028 +/ 4029 public bool suppressAutoOpenglViewport = false; 4030 private void updateOpenglViewportIfNeeded(int width, int height) { 4031 if(suppressAutoOpenglViewport) return; 4032 4033 version(without_opengl) {} else 4034 if(openglMode == OpenGlOptions.yes && resizability == Resizability.automaticallyScaleIfPossible) { 4035 // writeln(width, " ", height); 4036 setAsCurrentOpenGlContextNT(); 4037 glViewport(0, 0, width, height); 4038 } 4039 } 4040 4041 // TODO: Implement on non-Windows platforms (where available). 4042 private CornerStyle _fauxCornerStyle = CornerStyle.automatic; 4043 4044 /++ 4045 Style of the window's corners 4046 4047 $(WARNING 4048 Currently only implemented on Windows targets. 4049 Has no visual effect elsewhere. 4050 4051 Windows: Requires Windows 11 or later. 4052 ) 4053 4054 History: 4055 Added September 09, 2024. 4056 +/ 4057 public CornerStyle cornerStyle() @trusted { 4058 version(Windows) { 4059 DWM_WINDOW_CORNER_PREFERENCE dwmCorner; 4060 const apiResult = DwmGetWindowAttribute( 4061 this.hwnd, 4062 DWMWINDOWATTRIBUTE.DWMWA_WINDOW_CORNER_PREFERENCE, 4063 &dwmCorner, 4064 typeof(dwmCorner).sizeof 4065 ); 4066 4067 if (apiResult != S_OK) { 4068 // Unsupported? 4069 if (apiResult == E_INVALIDARG) { 4070 // Feature unsupported; Windows version probably too old. 4071 // Requires Windows 11 (build 22000) or later. 4072 return _fauxCornerStyle; 4073 } 4074 4075 throw new WindowsApiException("DwmGetWindowAttribute", apiResult); 4076 } 4077 4078 CornerStyle corner; 4079 if (!dwmCorner.fromDWM(corner)) { 4080 throw ArsdException!"DwmGetWindowAttribute unfamiliar corner preference"(dwmCorner); 4081 } 4082 return corner; 4083 } else { 4084 return _fauxCornerStyle; 4085 } 4086 } 4087 4088 /// ditto 4089 public void cornerStyle(const CornerStyle corner) @trusted { 4090 version(Windows) { 4091 DWM_WINDOW_CORNER_PREFERENCE dwmCorner; 4092 if (!corner.toDWM(dwmCorner)) { 4093 assert(false, "This should have been impossible because of a final switch."); 4094 } 4095 4096 const apiResult = DwmSetWindowAttribute( 4097 this.hwnd, 4098 DWMWINDOWATTRIBUTE.DWMWA_WINDOW_CORNER_PREFERENCE, 4099 &dwmCorner, 4100 typeof(dwmCorner).sizeof 4101 ); 4102 4103 if (apiResult != S_OK) { 4104 // Unsupported? 4105 if (apiResult == E_INVALIDARG) { 4106 // Feature unsupported; Windows version probably too old. 4107 // Requires Windows 11 (build 22000) or later. 4108 _fauxCornerStyle = corner; 4109 return; 4110 } 4111 4112 throw new WindowsApiException("DwmSetWindowAttribute", apiResult); 4113 } 4114 } else { 4115 _fauxCornerStyle = corner; 4116 } 4117 } 4118 } 4119 4120 version(OSXCocoa) 4121 enum NSWindow NullWindow = null; 4122 else 4123 enum NullWindow = NativeWindowHandle.init; 4124 4125 /++ 4126 Magic pseudo-window for just posting events to a global queue. 4127 4128 Not entirely supported, I might delete it at any time. 4129 4130 Added Nov 5, 2021. 4131 +/ 4132 __gshared SimpleWindow justCommunication = new SimpleWindow(NullWindow); 4133 4134 /* Drag and drop support { */ 4135 version(X11) { 4136 4137 } else version(Windows) { 4138 import core.sys.windows.uuid; 4139 import core.sys.windows.ole2; 4140 import core.sys.windows.oleidl; 4141 import core.sys.windows.objidl; 4142 import core.sys.windows.wtypes; 4143 4144 pragma(lib, "ole32"); 4145 void initDnd() { 4146 auto err = OleInitialize(null); 4147 if(err != S_OK && err != S_FALSE) 4148 throw new Exception("init");//err); 4149 } 4150 } 4151 /* } End drag and drop support */ 4152 4153 4154 /// Represents a mouse cursor (aka the mouse pointer, the image seen on screen that indicates where the mouse is pointing). 4155 /// See [GenericCursor]. 4156 class MouseCursor { 4157 int osId; 4158 bool isStockCursor; 4159 private this(int osId) { 4160 this.osId = osId; 4161 this.isStockCursor = true; 4162 } 4163 4164 // https://msdn.microsoft.com/en-us/library/windows/desktop/ms648385(v=vs.85).aspx 4165 this(int xHotSpot, int yHotSpot, ubyte[] andMask, ubyte[] xorMask) {} 4166 4167 version(Windows) { 4168 HCURSOR cursor_; 4169 HCURSOR cursorHandle() { 4170 if(cursor_ is null) 4171 cursor_ = LoadCursor(null, MAKEINTRESOURCE(osId)); 4172 return cursor_; 4173 } 4174 4175 } else static if(UsingSimpledisplayX11) { 4176 Cursor cursor_ = None; 4177 int xDisplaySequence; 4178 4179 Cursor cursorHandle() { 4180 if(this.osId == None) 4181 return None; 4182 4183 // we need to reload if we on a new X connection 4184 if(cursor_ == None || XDisplayConnection.connectionSequenceNumber != xDisplaySequence) { 4185 cursor_ = XCreateFontCursor(XDisplayConnection.get(), this.osId); 4186 xDisplaySequence = XDisplayConnection.connectionSequenceNumber; 4187 } 4188 return cursor_; 4189 } 4190 } 4191 } 4192 4193 // https://developer.mozilla.org/en-US/docs/Web/CSS/cursor 4194 // https://tronche.com/gui/x/xlib/appendix/b/ 4195 // https://msdn.microsoft.com/en-us/library/windows/desktop/ms648391(v=vs.85).aspx 4196 /// 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. 4197 enum GenericCursorType { 4198 Default, /// The default arrow pointer. 4199 Wait, /// A cursor indicating something is loading and the user must wait. 4200 Hand, /// A pointing finger, like the one used hovering over hyperlinks in a web browser. 4201 Help, /// A cursor indicating the user can get help about the pointer location. 4202 Cross, /// A crosshair. 4203 Text, /// An i-beam shape, typically used to indicate text selection is possible. 4204 Move, /// Pointer indicating movement is possible. May also be used as SizeAll. 4205 UpArrow, /// An arrow pointing straight up. 4206 Progress, /// The hourglass and arrow, indicating the computer is working but the user can still work. Not great results on X11. 4207 NotAllowed, /// Indicates the current operation is not allowed. Not great results on X11. 4208 SizeNesw, /// Arrow pointing northeast and southwest (lower-left corner resize indicator). 4209 SizeNs, /// Arrow pointing north and south (upper/lower edge resize indicator). 4210 SizeNwse, /// Arrow pointing northwest and southeast (upper-left corner resize indicator). 4211 SizeWe, /// Arrow pointing west and east (left/right edge resize indicator). 4212 4213 } 4214 4215 /* 4216 X_plus == css cell == Windows ? 4217 */ 4218 4219 /// You get one by `GenericCursor.SomeTime`. See [GenericCursorType] for a list of types. 4220 static struct GenericCursor { 4221 static: 4222 /// 4223 MouseCursor opDispatch(string str)() if(__traits(hasMember, GenericCursorType, str)) { 4224 static MouseCursor mc; 4225 4226 auto type = __traits(getMember, GenericCursorType, str); 4227 4228 if(mc is null) { 4229 4230 version(Windows) { 4231 int osId; 4232 final switch(type) { 4233 case GenericCursorType.Default: osId = IDC_ARROW; break; 4234 case GenericCursorType.Wait: osId = IDC_WAIT; break; 4235 case GenericCursorType.Hand: osId = IDC_HAND; break; 4236 case GenericCursorType.Help: osId = IDC_HELP; break; 4237 case GenericCursorType.Cross: osId = IDC_CROSS; break; 4238 case GenericCursorType.Text: osId = IDC_IBEAM; break; 4239 case GenericCursorType.Move: osId = IDC_SIZEALL; break; 4240 case GenericCursorType.UpArrow: osId = IDC_UPARROW; break; 4241 case GenericCursorType.Progress: osId = IDC_APPSTARTING; break; 4242 case GenericCursorType.NotAllowed: osId = IDC_NO; break; 4243 case GenericCursorType.SizeNesw: osId = IDC_SIZENESW; break; 4244 case GenericCursorType.SizeNs: osId = IDC_SIZENS; break; 4245 case GenericCursorType.SizeNwse: osId = IDC_SIZENWSE; break; 4246 case GenericCursorType.SizeWe: osId = IDC_SIZEWE; break; 4247 } 4248 } else static if(UsingSimpledisplayX11) { 4249 int osId; 4250 final switch(type) { 4251 case GenericCursorType.Default: osId = None; break; 4252 case GenericCursorType.Wait: osId = 150 /* XC_watch */; break; 4253 case GenericCursorType.Hand: osId = 60 /* XC_hand2 */; break; 4254 case GenericCursorType.Help: osId = 92 /* XC_question_arrow */; break; 4255 case GenericCursorType.Cross: osId = 34 /* XC_crosshair */; break; 4256 case GenericCursorType.Text: osId = 152 /* XC_xterm */; break; 4257 case GenericCursorType.Move: osId = 52 /* XC_fleur */; break; 4258 case GenericCursorType.UpArrow: osId = 22 /* XC_center_ptr */; break; 4259 case GenericCursorType.Progress: osId = 150 /* XC_watch, best i can do i think */; break; 4260 4261 case GenericCursorType.NotAllowed: osId = 24 /* XC_circle. not great */; break; 4262 case GenericCursorType.SizeNesw: osId = 12 /* XC_bottom_left_corner */ ; break; 4263 case GenericCursorType.SizeNs: osId = 116 /* XC_sb_v_double_arrow */; break; 4264 case GenericCursorType.SizeNwse: osId = 14 /* XC_bottom_right_corner */; break; 4265 case GenericCursorType.SizeWe: osId = 108 /* XC_sb_h_double_arrow */; break; 4266 } 4267 4268 } else { 4269 int osId; 4270 // featureNotImplemented(); 4271 } 4272 4273 mc = new MouseCursor(osId); 4274 } 4275 return mc; 4276 } 4277 } 4278 4279 4280 /++ 4281 If you want to get more control over the event loop, you can use this. 4282 4283 Typically though, you can just call [SimpleWindow.eventLoop] which forwards 4284 to `EventLoop.get.run`. 4285 +/ 4286 struct EventLoop { 4287 @disable this(); 4288 4289 /// Gets a reference to an existing event loop 4290 static EventLoop get() { 4291 return EventLoop(0, null); 4292 } 4293 4294 static void quitApplication() { 4295 version(use_arsd_core) { 4296 import arsd.core; 4297 ICoreEventLoop.exitApplication(); 4298 } 4299 EventLoop.get().exit(); 4300 } 4301 4302 private __gshared static Object monitor = new Object(); // deliberate CTFE usage here fyi 4303 4304 /// Construct an application-global event loop for yourself 4305 /// See_Also: [SimpleWindow.setEventHandlers] 4306 this(long pulseTimeout, void delegate() handlePulse) { 4307 synchronized(monitor) { 4308 if(impl is null) { 4309 claimGuiThread(); 4310 version(sdpy_thread_checks) assert(thisIsGuiThread); 4311 impl = new EventLoopImpl(pulseTimeout, handlePulse); 4312 } else { 4313 if(pulseTimeout) { 4314 impl.pulseTimeout = pulseTimeout; 4315 impl.handlePulse = handlePulse; 4316 } 4317 } 4318 impl.refcount++; 4319 } 4320 } 4321 4322 ~this() { 4323 if(impl is null) 4324 return; 4325 impl.refcount--; 4326 if(impl.refcount == 0) { 4327 impl.dispose(); 4328 if(thisIsGuiThread) 4329 guiThreadFinalize(); 4330 } 4331 4332 } 4333 4334 this(this) { 4335 if(impl is null) 4336 return; 4337 impl.refcount++; 4338 } 4339 4340 /// Runs the event loop until the whileCondition, if present, returns false 4341 int run(bool delegate() whileCondition = null) { 4342 assert(impl !is null); 4343 impl.notExited = true; 4344 return impl.run(whileCondition); 4345 } 4346 4347 /// Exits the event loop, but allows you to reenter it again later (in contrast with quitApplication, which tries to terminate the program) 4348 void exit() { 4349 assert(impl !is null); 4350 impl.notExited = false; 4351 4352 version(use_arsd_core) { 4353 import arsd.core; 4354 ICoreEventLoop.exitApplication(); 4355 } 4356 } 4357 4358 version(linux) 4359 ref void delegate(int) signalHandler() { 4360 assert(impl !is null); 4361 return impl.signalHandler; 4362 } 4363 4364 __gshared static EventLoopImpl* impl; 4365 } 4366 4367 version(linux) 4368 void delegate(int, int) globalHupHandler; 4369 4370 version(Posix) 4371 void makeNonBlocking(int fd) { 4372 import fcntl = core.sys.posix.fcntl; 4373 auto flags = fcntl.fcntl(fd, fcntl.F_GETFL, 0); 4374 if(flags == -1) 4375 throw new Exception("fcntl get"); 4376 flags |= fcntl.O_NONBLOCK; 4377 auto s = fcntl.fcntl(fd, fcntl.F_SETFL, flags); 4378 if(s == -1) 4379 throw new Exception("fcntl set"); 4380 } 4381 4382 struct EventLoopImpl { 4383 int refcount; 4384 4385 bool notExited = true; 4386 4387 version(Emscripten) { 4388 void delegate(int) signalHandler; 4389 static import unix = core.sys.posix.unistd; 4390 static import err = core.stdc.errno; 4391 } else 4392 version(linux) { 4393 static import ep = core.sys.linux.epoll; 4394 static import unix = core.sys.posix.unistd; 4395 static import err = core.stdc.errno; 4396 import core.sys.linux.timerfd; 4397 4398 void delegate(int) signalHandler; 4399 } 4400 4401 version(X11) { 4402 int pulseFd = -1; 4403 version(Emscripten) {} else 4404 version(linux) ep.epoll_event[16] events = void; 4405 } else version(Windows) { 4406 Timer pulser; 4407 HANDLE[] handles; 4408 } 4409 4410 4411 /// "Lock" this window handle, to do multithreaded synchronization. You probably won't need 4412 /// to call this, as it's not recommended to share window between threads. 4413 void mtLock () { 4414 version(X11) { 4415 XLockDisplay(this.display); 4416 } 4417 } 4418 4419 version(X11) 4420 auto display() { return XDisplayConnection.get; } 4421 4422 /// "Unlock" this window handle, to do multithreaded synchronization. You probably won't need 4423 /// to call this, as it's not recommended to share window between threads. 4424 void mtUnlock () { 4425 version(X11) { 4426 XUnlockDisplay(this.display); 4427 } 4428 } 4429 4430 version(with_eventloop) 4431 void initialize(long pulseTimeout) {} 4432 else 4433 void initialize(long pulseTimeout) @system { 4434 version(Windows) { 4435 if(pulseTimeout && handlePulse !is null) 4436 pulser = new Timer(cast(int) pulseTimeout, handlePulse); 4437 4438 if (customEventH is null) { 4439 customEventH = CreateEvent(null, FALSE/*autoreset*/, FALSE/*initial state*/, null); 4440 if (customEventH !is null) { 4441 handles ~= customEventH; 4442 } else { 4443 // this is something that should not be; better be safe than sorry 4444 throw new Exception("can't create eventfd for custom event processing"); 4445 } 4446 } 4447 4448 SimpleWindow.processAllCustomEvents(); // process events added before event object creation 4449 } 4450 4451 version(Emscripten) { 4452 4453 } else version(linux) { 4454 prepareEventLoop(); 4455 { 4456 auto display = XDisplayConnection.get; 4457 // adding Xlib file 4458 ep.epoll_event ev = void; 4459 { import core.stdc.string : memset; memset(&ev, 0, ev.sizeof); } // this makes valgrind happy 4460 ev.events = ep.EPOLLIN; 4461 ev.data.fd = display.fd; 4462 if(ep.epoll_ctl(epollFd, ep.EPOLL_CTL_ADD, display.fd, &ev) == -1) 4463 throw new Exception("add x fd");// ~ to!string(epollFd)); 4464 displayFd = display.fd; 4465 } 4466 4467 if(pulseTimeout && handlePulse !is null) { 4468 pulseFd = timerfd_create(CLOCK_MONOTONIC, 0); 4469 if(pulseFd == -1) 4470 throw new Exception("pulse timer create failed"); 4471 4472 itimerspec value; 4473 value.it_value.tv_sec = cast(int) (pulseTimeout / 1000); 4474 value.it_value.tv_nsec = (pulseTimeout % 1000) * 1000_000; 4475 4476 value.it_interval.tv_sec = cast(int) (pulseTimeout / 1000); 4477 value.it_interval.tv_nsec = (pulseTimeout % 1000) * 1000_000; 4478 4479 if(timerfd_settime(pulseFd, 0, &value, null) == -1) 4480 throw new Exception("couldn't make pulse timer"); 4481 4482 ep.epoll_event ev = void; 4483 { import core.stdc.string : memset; memset(&ev, 0, ev.sizeof); } // this makes valgrind happy 4484 ev.events = ep.EPOLLIN; 4485 ev.data.fd = pulseFd; 4486 ep.epoll_ctl(epollFd, ep.EPOLL_CTL_ADD, pulseFd, &ev); 4487 } 4488 4489 // eventfd for custom events 4490 if (customEventFDWrite == -1) { 4491 customEventFDWrite = eventfd(0, 0); 4492 customEventFDRead = customEventFDWrite; 4493 if (customEventFDRead >= 0) { 4494 ep.epoll_event ev = void; 4495 { import core.stdc.string : memset; memset(&ev, 0, ev.sizeof); } // this makes valgrind happy 4496 ev.events = ep.EPOLLIN; 4497 ev.data.fd = customEventFDRead; 4498 ep.epoll_ctl(epollFd, ep.EPOLL_CTL_ADD, customEventFDRead, &ev); 4499 } else { 4500 // this is something that should not be; better be safe than sorry 4501 throw new Exception("can't create eventfd for custom event processing"); 4502 } 4503 } 4504 4505 if (customSignalFD == -1) { 4506 import core.sys.linux.sys.signalfd; 4507 4508 sigset_t sigset; 4509 auto err = sigemptyset(&sigset); 4510 assert(!err); 4511 err = sigaddset(&sigset, SIGINT); 4512 assert(!err); 4513 err = sigaddset(&sigset, SIGHUP); 4514 assert(!err); 4515 err = sigprocmask(SIG_BLOCK, &sigset, null); 4516 assert(!err); 4517 4518 customSignalFD = signalfd(-1, &sigset, SFD_NONBLOCK); 4519 assert(customSignalFD != -1); 4520 4521 ep.epoll_event ev = void; 4522 { import core.stdc.string : memset; memset(&ev, 0, ev.sizeof); } // this makes valgrind happy 4523 ev.events = ep.EPOLLIN; 4524 ev.data.fd = customSignalFD; 4525 ep.epoll_ctl(epollFd, ep.EPOLL_CTL_ADD, customSignalFD, &ev); 4526 } 4527 } else version(Posix) { 4528 prepareEventLoop(); 4529 if (customEventFDRead == -1) { 4530 int[2] bfr; 4531 import core.sys.posix.unistd; 4532 auto ret = pipe(bfr); 4533 if(ret == -1) throw new Exception("pipe"); 4534 customEventFDRead = bfr[0]; 4535 customEventFDWrite = bfr[1]; 4536 } 4537 4538 } 4539 4540 SimpleWindow.processAllCustomEvents(); // process events added before event FD creation 4541 4542 version(linux) { 4543 this.mtLock(); 4544 scope(exit) this.mtUnlock(); 4545 version(X11) 4546 XPending(display); // no, really 4547 } 4548 4549 disposed = false; 4550 } 4551 4552 bool disposed = true; 4553 version(X11) 4554 int displayFd = -1; 4555 4556 version(with_eventloop) 4557 void dispose() {} 4558 else 4559 void dispose() @system { 4560 disposed = true; 4561 version(X11) { 4562 if(pulseFd != -1) { 4563 import unix = core.sys.posix.unistd; 4564 unix.close(pulseFd); 4565 pulseFd = -1; 4566 } 4567 4568 version(Emscripten) {} else 4569 version(linux) 4570 if(displayFd != -1) { 4571 // 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 4572 ep.epoll_event ev = void; 4573 { import core.stdc.string : memset; memset(&ev, 0, ev.sizeof); } // this makes valgrind happy 4574 ev.events = ep.EPOLLIN; 4575 ev.data.fd = displayFd; 4576 ep.epoll_ctl(epollFd, ep.EPOLL_CTL_DEL, displayFd, &ev); 4577 displayFd = -1; 4578 } 4579 4580 } else version(Windows) { 4581 if(pulser !is null) { 4582 pulser.destroy(); 4583 pulser = null; 4584 } 4585 if (customEventH !is null) { 4586 CloseHandle(customEventH); 4587 customEventH = null; 4588 } 4589 } 4590 } 4591 4592 this(long pulseTimeout, void delegate() handlePulse) { 4593 this.pulseTimeout = pulseTimeout; 4594 this.handlePulse = handlePulse; 4595 initialize(pulseTimeout); 4596 } 4597 4598 private long pulseTimeout; 4599 void delegate() handlePulse; 4600 4601 ~this() { 4602 dispose(); 4603 } 4604 4605 version(Posix) 4606 ref int customEventFDRead() { return SimpleWindow.customEventFDRead; } 4607 version(Posix) 4608 ref int customEventFDWrite() { return SimpleWindow.customEventFDWrite; } 4609 version(linux) 4610 ref int customSignalFD() { return SimpleWindow.customSignalFD; } 4611 version(Windows) 4612 ref auto customEventH() { return SimpleWindow.customEventH; } 4613 4614 version(X11) { 4615 bool doXPending() { 4616 bool done = false; 4617 4618 this.mtLock(); 4619 scope(exit) this.mtUnlock(); 4620 //{ import core.stdc.stdio; printf("*** queued: %d\n", XEventsQueued(this.display, QueueMode.QueuedAlready)); } 4621 while(!done && XPending(display)) { 4622 done = doXNextEvent(this.display); 4623 } 4624 4625 return done; 4626 } 4627 void doXNextEventVoid() { 4628 doXPending(); 4629 } 4630 } 4631 4632 version(with_eventloop) { 4633 int loopHelper(bool delegate() whileCondition) { 4634 // FIXME: whileCondition 4635 import arsd.eventloop; 4636 loop(); 4637 return 0; 4638 } 4639 } else 4640 int loopHelper(bool delegate() whileCondition) { 4641 version(X11) { 4642 bool done = false; 4643 4644 XFlush(display); 4645 insideXEventLoop = true; 4646 scope(exit) insideXEventLoop = false; 4647 4648 version(use_arsd_core) { 4649 import arsd.core; 4650 auto el = getThisThreadEventLoop(EventLoopType.Ui); 4651 4652 static bool loopInitialized = false; 4653 if(!loopInitialized) { 4654 el.addDelegateOnLoopIteration(&doXNextEventVoid, 0); 4655 el.addDelegateOnLoopIteration(&SimpleWindow.processAllCustomEvents, 0); 4656 4657 if(customSignalFD != -1) 4658 cast(void) el.addCallbackOnFdReadable(customSignalFD, new CallbackHelper(() { 4659 version(linux) { 4660 import core.sys.linux.sys.signalfd; 4661 import core.sys.posix.unistd : read; 4662 signalfd_siginfo info; 4663 read(customSignalFD, &info, info.sizeof); 4664 4665 auto sig = info.ssi_signo; 4666 4667 if(EventLoop.get.signalHandler !is null) { 4668 EventLoop.get.signalHandler()(sig); 4669 } else { 4670 EventLoop.get.exit(); 4671 } 4672 } 4673 })); 4674 4675 if(display.fd != -1) 4676 cast(void) el.addCallbackOnFdReadable(display.fd, new CallbackHelper(() { 4677 this.mtLock(); 4678 scope(exit) this.mtUnlock(); 4679 while(!done && XPending(display)) { 4680 done = doXNextEvent(this.display); 4681 } 4682 })); 4683 4684 if(pulseFd != -1) 4685 cast(void) el.addCallbackOnFdReadable(pulseFd, new CallbackHelper(() { 4686 long expirationCount; 4687 // 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... 4688 4689 handlePulse(); 4690 4691 // read just to clear the buffer so poll doesn't trigger again 4692 // BTW I read AFTER the pulse because if the pulse handler takes 4693 // a lot of time to execute, we don't want the app to get stuck 4694 // in a loop of timer hits without a chance to do anything else 4695 // 4696 // IOW handlePulse happens at most once per pulse interval. 4697 unix.read(pulseFd, &expirationCount, expirationCount.sizeof); 4698 })); 4699 4700 if(customEventFDRead != -1) 4701 cast(void) el.addCallbackOnFdReadable(customEventFDRead, new CallbackHelper(() { 4702 // we have some custom events; process 'em 4703 import core.sys.posix.unistd : read; 4704 ulong n; 4705 read(customEventFDRead, &n, n.sizeof); // reset counter value to zero again 4706 //{ import core.stdc.stdio; printf("custom event! count=%u\n", eventQueueUsed); } 4707 //SimpleWindow.processAllCustomEvents(); 4708 })); 4709 4710 // FIXME: posix fds 4711 // FIXME up? 4712 4713 4714 loopInitialized = true; 4715 } 4716 4717 el.run(() => !whileCondition()); 4718 } else version(linux) { 4719 while(!done && (whileCondition is null || whileCondition() == true) && notExited) { 4720 bool forceXPending = false; 4721 auto wto = SimpleWindow.eventAllQueueTimeoutMSecs(); 4722 // eh... some events may be queued for "squashing" (or "late delivery"), so we have to do the following magic 4723 { 4724 this.mtLock(); 4725 scope(exit) this.mtUnlock(); 4726 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 4727 } 4728 //{ import core.stdc.stdio; printf("*** wto=%d; force=%d\n", wto, (forceXPending ? 1 : 0)); } 4729 auto nfds = ep.epoll_wait(epollFd, events.ptr, events.length, (wto == 0 || wto >= int.max ? -1 : cast(int)wto)); 4730 if(nfds == -1) { 4731 if(err.errno == err.EINTR) { 4732 //if(forceXPending) goto xpending; 4733 continue; // interrupted by signal, just try again 4734 } 4735 throw new Exception("epoll wait failure"); 4736 } 4737 // writeln(nfds, " ", events[0].data.fd); 4738 4739 SimpleWindow.processAllCustomEvents(); // anyway 4740 //version(sdddd) { writeln("nfds=", nfds, "; [0]=", events[0].data.fd); } 4741 foreach(idx; 0 .. nfds) { 4742 if(done) break; 4743 auto fd = events[idx].data.fd; 4744 assert(fd != -1); // should never happen cuz the api doesn't do that but better to assert than assume. 4745 auto flags = events[idx].events; 4746 if(flags & ep.EPOLLIN) { 4747 if (fd == customSignalFD) { 4748 version(linux) { 4749 import core.sys.linux.sys.signalfd; 4750 import core.sys.posix.unistd : read; 4751 signalfd_siginfo info; 4752 read(customSignalFD, &info, info.sizeof); 4753 4754 auto sig = info.ssi_signo; 4755 4756 if(EventLoop.get.signalHandler !is null) { 4757 EventLoop.get.signalHandler()(sig); 4758 } else { 4759 EventLoop.get.exit(); 4760 } 4761 } 4762 } else if(fd == display.fd) { 4763 version(sdddd) { writeln("X EVENT PENDING!"); } 4764 this.mtLock(); 4765 scope(exit) this.mtUnlock(); 4766 while(!done && XPending(display)) { 4767 done = doXNextEvent(this.display); 4768 } 4769 forceXPending = false; 4770 } else if(fd == pulseFd) { 4771 long expirationCount; 4772 // 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... 4773 4774 handlePulse(); 4775 4776 // read just to clear the buffer so poll doesn't trigger again 4777 // BTW I read AFTER the pulse because if the pulse handler takes 4778 // a lot of time to execute, we don't want the app to get stuck 4779 // in a loop of timer hits without a chance to do anything else 4780 // 4781 // IOW handlePulse happens at most once per pulse interval. 4782 unix.read(pulseFd, &expirationCount, expirationCount.sizeof); 4783 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 4784 } else if (fd == customEventFDRead) { 4785 // we have some custom events; process 'em 4786 import core.sys.posix.unistd : read; 4787 ulong n; 4788 read(customEventFDRead, &n, n.sizeof); // reset counter value to zero again 4789 //{ import core.stdc.stdio; printf("custom event! count=%u\n", eventQueueUsed); } 4790 //SimpleWindow.processAllCustomEvents(); 4791 4792 forceXPending = true; 4793 } else { 4794 // some other timer 4795 version(sdddd) { writeln("unknown fd: ", fd); } 4796 4797 if(Timer* t = fd in Timer.mapping) 4798 (*t).trigger(); 4799 4800 if(PosixFdReader* pfr = fd in PosixFdReader.mapping) 4801 (*pfr).ready(flags); 4802 4803 // we don't know what the user did in this timer, so we need to assume that 4804 // there's X data to be flushed and potentially processed 4805 forceXPending = true; 4806 4807 // or i might add support for other FDs too 4808 // but for now it is just timer 4809 // (if you want other fds, use arsd.eventloop and compile with -version=with_eventloop), it offers a fuller api for arbitrary stuff. 4810 } 4811 } 4812 if(flags & ep.EPOLLHUP) { 4813 if(PosixFdReader* pfr = fd in PosixFdReader.mapping) 4814 (*pfr).hup(flags); 4815 if(globalHupHandler) 4816 globalHupHandler(fd, flags); 4817 } 4818 /+ 4819 } else { 4820 // not interested in OUT, we are just reading here. 4821 // 4822 // error or hup might also be reported 4823 // but it shouldn't here since we are only 4824 // using a few types of FD and Xlib will report 4825 // if it dies. 4826 // so instead of thoughtfully handling it, I'll 4827 // just throw. for now at least 4828 4829 throw new Exception("epoll did something else"); 4830 } 4831 +/ 4832 } 4833 // if we won't call `XPending()` here, libX may delay some internal event delivery. 4834 // i.e. we HAVE to repeatedly call `XPending()` even if libX fd wasn't signalled! 4835 xpending: 4836 if (!done && forceXPending) { 4837 done = doXPending(); 4838 } 4839 } 4840 } else { 4841 // Generic fallback: yes to simple pulse support, 4842 // but NO timer support! 4843 4844 // FIXME: we could probably support the POSIX timer_create 4845 // signal-based option, but I'm in no rush to write it since 4846 // I prefer the fd-based functions. 4847 while (!done && (whileCondition is null || whileCondition() == true) && notExited) { 4848 4849 import core.sys.posix.poll; 4850 4851 pollfd[] pfds; 4852 pollfd[32] pfdsBuffer; 4853 auto len = PosixFdReader.mapping.length + 2; 4854 // FIXME: i should just reuse the buffer 4855 if(len < pfdsBuffer.length) 4856 pfds = pfdsBuffer[0 .. len]; 4857 else 4858 pfds = new pollfd[](len); 4859 4860 pfds[0].fd = display.fd; 4861 pfds[0].events = POLLIN; 4862 pfds[0].revents = 0; 4863 4864 int slot = 1; 4865 4866 if(customEventFDRead != -1) { 4867 pfds[slot].fd = customEventFDRead; 4868 pfds[slot].events = POLLIN; 4869 pfds[slot].revents = 0; 4870 4871 slot++; 4872 } 4873 4874 foreach(fd, obj; PosixFdReader.mapping) { 4875 if(!obj.enabled) continue; 4876 pfds[slot].fd = fd; 4877 pfds[slot].events = POLLIN; 4878 pfds[slot].revents = 0; 4879 4880 slot++; 4881 } 4882 4883 auto ret = poll(pfds.ptr, slot, pulseTimeout > 0 ? cast(int) pulseTimeout : -1); 4884 if(ret == -1) throw new Exception("poll"); 4885 4886 if(ret == 0) { 4887 // FIXME it may not necessarily time out if events keep coming 4888 if(handlePulse !is null) 4889 handlePulse(); 4890 } else { 4891 foreach(s; 0 .. slot) { 4892 if(pfds[s].revents == 0) continue; 4893 4894 if(pfds[s].fd == display.fd) { 4895 while(!done && XPending(display)) { 4896 this.mtLock(); 4897 scope(exit) this.mtUnlock(); 4898 done = doXNextEvent(this.display); 4899 } 4900 } else if(customEventFDRead != -1 && pfds[s].fd == customEventFDRead) { 4901 4902 import core.sys.posix.unistd : read; 4903 ulong n; 4904 read(customEventFDRead, &n, n.sizeof); 4905 SimpleWindow.processAllCustomEvents(); 4906 } else { 4907 auto obj = PosixFdReader.mapping[pfds[s].fd]; 4908 if(pfds[s].revents & POLLNVAL) { 4909 obj.dispose(); 4910 } else { 4911 obj.ready(pfds[s].revents); 4912 } 4913 } 4914 4915 ret--; 4916 if(ret == 0) break; 4917 } 4918 } 4919 } 4920 } 4921 } 4922 4923 version(Windows) { 4924 4925 version(use_arsd_core) { 4926 import arsd.core; 4927 auto el = getThisThreadEventLoop(EventLoopType.Ui); 4928 static bool loopInitialized = false; 4929 if(!loopInitialized) { 4930 el.addDelegateOnLoopIteration(&SimpleWindow.processAllCustomEvents, 0); 4931 el.addDelegateOnLoopIteration(function() { eventLoopRound++; }, 0); 4932 loopInitialized = true; 4933 } 4934 el.run(() => !whileCondition()); 4935 } else { 4936 int ret = -1; 4937 MSG message; 4938 while(ret != 0 && (whileCondition is null || whileCondition() == true) && notExited) { 4939 eventLoopRound++; 4940 auto wto = SimpleWindow.eventAllQueueTimeoutMSecs(); 4941 auto waitResult = MsgWaitForMultipleObjectsEx( 4942 cast(int) handles.length, handles.ptr, 4943 (wto == 0 ? INFINITE : wto), /* timeout */ 4944 0x04FF, /* QS_ALLINPUT */ 4945 0x0002 /* MWMO_ALERTABLE */ | 0x0004 /* MWMO_INPUTAVAILABLE */); 4946 4947 SimpleWindow.processAllCustomEvents(); // anyway 4948 enum WAIT_OBJECT_0 = 0; 4949 if(waitResult >= WAIT_OBJECT_0 && waitResult < handles.length + WAIT_OBJECT_0) { 4950 auto h = handles[waitResult - WAIT_OBJECT_0]; 4951 if(auto e = h in WindowsHandleReader.mapping) { 4952 (*e).ready(); 4953 } 4954 } else if(waitResult == handles.length + WAIT_OBJECT_0) { 4955 // message ready 4956 int count; 4957 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 4958 ret = GetMessage(&message, null, 0, 0); 4959 if(ret == -1) 4960 throw new WindowsApiException("GetMessage", GetLastError()); 4961 TranslateMessage(&message); 4962 DispatchMessage(&message); 4963 4964 count++; 4965 if(count > 10) 4966 break; // take the opportunity to catch up on other events 4967 4968 if(ret == 0) { // WM_QUIT 4969 EventLoop.quitApplication(); 4970 break; 4971 } 4972 } 4973 } else if(waitResult == 0x000000C0L /* WAIT_IO_COMPLETION */) { 4974 SleepEx(0, true); // I call this to give it a chance to do stuff like async io 4975 } else if(waitResult == 258L /* WAIT_TIMEOUT */) { 4976 // timeout, should never happen since we aren't using it 4977 } else if(waitResult == 0xFFFFFFFF) { 4978 // failed 4979 throw new WindowsApiException("MsgWaitForMultipleObjectsEx", GetLastError()); 4980 } else { 4981 // idk.... 4982 } 4983 } 4984 } 4985 4986 // return message.wParam; 4987 return 0; 4988 } else { 4989 return 0; 4990 } 4991 } 4992 4993 int run(bool delegate() whileCondition = null) { 4994 if(disposed) 4995 initialize(this.pulseTimeout); 4996 4997 version(X11) { 4998 try { 4999 return loopHelper(whileCondition); 5000 } catch(XDisconnectException e) { 5001 if(e.userRequested) { 5002 foreach(item; CapableOfHandlingNativeEvent.nativeHandleMapping) 5003 item.discardConnectionState(); 5004 XCloseDisplay(XDisplayConnection.display); 5005 } 5006 5007 XDisplayConnection.display = null; 5008 5009 this.dispose(); 5010 5011 throw e; 5012 } 5013 } else { 5014 return loopHelper(whileCondition); 5015 } 5016 } 5017 } 5018 5019 5020 /++ 5021 Provides an icon on the system notification area (also known as the system tray). 5022 5023 5024 If a notification area is not available with the NotificationIcon object is created, 5025 it will silently succeed and simply attempt to create one when an area becomes available. 5026 5027 5028 NotificationAreaIcon on Windows assumes you are on Windows Vista or later. Support for 5029 Windows XP was dropped on October 31, 2023. On the other hand, support for 32 bit transparency 5030 with true color was added at that time. I was just too lazy to write the fallback. 5031 5032 If this is an issue, let me know, it'd take about an hour to get it back in there, but I suggest 5033 you use arsd 10.x when targeting Windows XP. 5034 +/ 5035 version(Emscripten) {} else 5036 version(OSXCocoa) {} else // NotYetImplementedException 5037 class NotificationAreaIcon : CapableOfHandlingNativeEvent { 5038 5039 version(X11) { 5040 void recreateAfterDisconnect() { 5041 stateDiscarded = false; 5042 clippixmap = None; 5043 throw new Exception("NOT IMPLEMENTED"); 5044 } 5045 5046 bool stateDiscarded; 5047 void discardConnectionState() { 5048 stateDiscarded = true; 5049 } 5050 } 5051 5052 5053 version(X11) { 5054 Image img; 5055 5056 NativeEventHandler getNativeEventHandler() { 5057 return delegate int(XEvent e) { 5058 switch(e.type) { 5059 case EventType.Expose: 5060 //case EventType.VisibilityNotify: 5061 redraw(); 5062 break; 5063 case EventType.ClientMessage: 5064 version(sddddd) { 5065 writeln("\t", e.xclient.message_type == GetAtom!("_XEMBED")(XDisplayConnection.get)); 5066 writeln("\t", e.xclient.format); 5067 writeln("\t", e.xclient.data.l); 5068 } 5069 break; 5070 case EventType.ButtonPress: 5071 auto event = e.xbutton; 5072 if (onClick !is null || onClickEx !is null) { 5073 MouseButton mb = cast(MouseButton)0; 5074 switch (event.button) { 5075 case 1: mb = MouseButton.left; break; // left 5076 case 2: mb = MouseButton.middle; break; // middle 5077 case 3: mb = MouseButton.right; break; // right 5078 case 4: mb = MouseButton.wheelUp; break; // scroll up 5079 case 5: mb = MouseButton.wheelDown; break; // scroll down 5080 case 6: break; // scroll left... 5081 case 7: break; // scroll right... 5082 case 8: mb = MouseButton.backButton; break; 5083 case 9: mb = MouseButton.forwardButton; break; 5084 default: 5085 } 5086 if (mb) { 5087 try { onClick()(mb); } catch (Exception) {} 5088 if (onClickEx !is null) try { onClickEx(event.x_root, event.y_root, mb, cast(ModifierState)event.state); } catch (Exception) {} 5089 } 5090 } 5091 break; 5092 case EventType.EnterNotify: 5093 if (onEnter !is null) { 5094 onEnter(e.xcrossing.x_root, e.xcrossing.y_root, cast(ModifierState)e.xcrossing.state); 5095 } 5096 break; 5097 case EventType.LeaveNotify: 5098 if (onLeave !is null) try { onLeave(); } catch (Exception) {} 5099 break; 5100 case EventType.DestroyNotify: 5101 active = false; 5102 CapableOfHandlingNativeEvent.nativeHandleMapping.remove(nativeHandle); 5103 break; 5104 case EventType.ConfigureNotify: 5105 auto event = e.xconfigure; 5106 this.width = event.width; 5107 this.height = event.height; 5108 // writeln(width, " x " , height, " @ ", event.x, " ", event.y); 5109 redraw(); 5110 break; 5111 default: return 1; 5112 } 5113 return 1; 5114 }; 5115 } 5116 5117 /* private */ void hideBalloon() { 5118 balloon.close(); 5119 version(with_timer) 5120 timer.destroy(); 5121 balloon = null; 5122 version(with_timer) 5123 timer = null; 5124 } 5125 5126 void redraw() { 5127 if (!active) return; 5128 5129 auto display = XDisplayConnection.get; 5130 GC gc; 5131 5132 // from https://stackoverflow.com/questions/10492275/how-to-upload-32-bit-image-to-server-side-pixmap 5133 5134 int gc_depth(int depth, Display *dpy, Window root, GC *gc) { 5135 Visual *visual; 5136 XVisualInfo vis_info; 5137 XSetWindowAttributes win_attr; 5138 c_ulong win_mask; 5139 5140 if(!XMatchVisualInfo(dpy, 0, depth, 4 /*TrueColor*/, &vis_info)) { 5141 assert(0); 5142 // return 1; 5143 } 5144 5145 visual = vis_info.visual; 5146 5147 win_attr.colormap = XCreateColormap(dpy, root, visual, AllocNone); 5148 win_attr.background_pixel = 0; 5149 win_attr.border_pixel = 0; 5150 5151 win_mask = CWBackPixel | CWColormap | CWBorderPixel; 5152 5153 *gc = XCreateGC(dpy, nativeHandle, 0, null); 5154 5155 return 0; 5156 } 5157 5158 if(useAlpha) 5159 gc_depth(32, display, RootWindow(display, DefaultScreen(display)), &gc); 5160 else 5161 gc = DefaultGC(display, DefaultScreen(display)); 5162 5163 XClearWindow(display, nativeHandle); 5164 5165 if(!useAlpha && img !is null) 5166 XSetClipMask(display, gc, clippixmap); 5167 5168 /+ 5169 XSetForeground(display, gc, 5170 cast(uint) 0 << 16 | 5171 cast(uint) 0 << 8 | 5172 cast(uint) 0); 5173 XFillRectangle(display, nativeHandle, gc, 0, 0, width, height); 5174 +/ 5175 5176 if (img is null) { 5177 XSetForeground(display, gc, 5178 cast(uint) 0 << 16 | 5179 cast(uint) 127 << 8 | 5180 cast(uint) 0); 5181 XFillArc(display, nativeHandle, 5182 gc, width / 4, height / 4, width * 2 / 4, height * 2 / 4, 0 * 64, 360 * 64); 5183 } else { 5184 int dx = 0; 5185 int dy = 0; 5186 if(width > img.width) 5187 dx = (width - img.width) / 2; 5188 if(height > img.height) 5189 dy = (height - img.height) / 2; 5190 // writeln(img.width, " ", img.height, " vs ", width, " ", height); 5191 XSetClipOrigin(display, gc, dx, dy); 5192 5193 int max(int a, int b) { 5194 if(a > b) return a; else return b; 5195 } 5196 5197 if (img.usingXshm) 5198 XShmPutImage(display, cast(Drawable)nativeHandle, gc, img.handle, 0, 0, dx, dy, max(img.width, width), max(img.height, height), false); 5199 else 5200 XPutImage(display, cast(Drawable)nativeHandle, gc, img.handle, 0, 0, dx, dy, max(img.width, width), max(img.height, height)); 5201 } 5202 XSetClipMask(display, gc, None); 5203 flushGui(); 5204 } 5205 5206 static Window getTrayOwner() { 5207 auto display = XDisplayConnection.get; 5208 auto i = cast(int) DefaultScreen(display); 5209 if(i < 10 && i >= 0) { 5210 static Atom atom; 5211 if(atom == None) 5212 atom = XInternAtom(display, cast(char*) ("_NET_SYSTEM_TRAY_S"~(cast(char) (i + '0')) ~ '\0').ptr, false); 5213 return XGetSelectionOwner(display, atom); 5214 } 5215 return None; 5216 } 5217 5218 static void sendTrayMessage(arch_long message, arch_long d1, arch_long d2, arch_long d3) { 5219 auto to = getTrayOwner(); 5220 auto display = XDisplayConnection.get; 5221 XEvent ev; 5222 ev.xclient.type = EventType.ClientMessage; 5223 ev.xclient.window = to; 5224 ev.xclient.message_type = GetAtom!("_NET_SYSTEM_TRAY_OPCODE", true)(display); 5225 ev.xclient.format = 32; 5226 ev.xclient.data.l[0] = CurrentTime; 5227 ev.xclient.data.l[1] = message; 5228 ev.xclient.data.l[2] = d1; 5229 ev.xclient.data.l[3] = d2; 5230 ev.xclient.data.l[4] = d3; 5231 5232 XSendEvent(XDisplayConnection.get, to, false, EventMask.NoEventMask, &ev); 5233 } 5234 5235 private static NotificationAreaIcon[] activeIcons; 5236 5237 // FIXME: possible leak with this stuff, should be able to clear it and stuff. 5238 private void newManager() { 5239 close(); 5240 createXWin(); 5241 5242 if(this.clippixmap) 5243 XFreePixmap(XDisplayConnection.get, clippixmap); 5244 if(this.originalMemoryImage) 5245 this.icon = this.originalMemoryImage; 5246 else if(this.img) 5247 this.icon = this.img; 5248 } 5249 5250 private bool useAlpha = false; 5251 5252 private void createXWin () { 5253 // create window 5254 auto display = XDisplayConnection.get; 5255 5256 // to check for MANAGER on root window to catch new/changed tray owners 5257 XDisplayConnection.addRootInput(EventMask.StructureNotifyMask); 5258 // so if a thing does appear, we can handle it 5259 foreach(ai; activeIcons) 5260 if(ai is this) 5261 goto alreadythere; 5262 activeIcons ~= this; 5263 alreadythere: 5264 5265 // and check for an existing tray 5266 auto trayOwner = getTrayOwner(); 5267 if(trayOwner == None) 5268 return; 5269 //throw new Exception("No notification area found"); 5270 5271 Visual* v = cast(Visual*) CopyFromParent; 5272 5273 // GNOME's default is 22x22 and KDE assumes all icons are going to match that then bitmap scales 5274 // from there. It is ugly and stupid but this gives the fewest artifacts. Good environments will send 5275 // a resize event later. 5276 width = 22; 5277 height = 22; 5278 5279 // if they system gave us a 32 bit visual we need to switch to it too 5280 int depth = 24; 5281 5282 auto visualProp = getX11PropertyData(trayOwner, GetAtom!("_NET_SYSTEM_TRAY_VISUAL", true)(display)); 5283 if(visualProp !is null) { 5284 c_ulong[] info = cast(c_ulong[]) visualProp; 5285 if(info.length == 1) { 5286 auto vid = info[0]; 5287 int returned; 5288 XVisualInfo t; 5289 t.visualid = vid; 5290 auto got = XGetVisualInfo(display, VisualIDMask, &t, &returned); 5291 if(got !is null) { 5292 if(returned == 1) { 5293 v = got.visual; 5294 depth = got.depth; 5295 // writeln("using special visual ", got.depth); 5296 // writeln(depth); 5297 } 5298 XFree(got); 5299 } 5300 } 5301 } 5302 5303 int CWFlags = CWBackPixel | CWBorderPixel | CWOverrideRedirect; 5304 XSetWindowAttributes attr; 5305 attr.background_pixel = 0; 5306 attr.border_pixel = 0; 5307 attr.override_redirect = 0; 5308 if(v !is cast(Visual*) CopyFromParent) { 5309 attr.colormap = XCreateColormap(display, RootWindow(display, DefaultScreen(display)), v, AllocNone); 5310 CWFlags |= CWColormap; 5311 if(depth == 32) 5312 useAlpha = true; 5313 else 5314 goto plain; 5315 } else { 5316 plain: 5317 attr.background_pixmap = 1 /* ParentRelative */; 5318 CWFlags |= CWBackPixmap; 5319 } 5320 5321 auto nativeWindow = XCreateWindow(display, RootWindow(display, DefaultScreen(display)), 0, 0, width, height, 0, depth, InputOutput, v, CWFlags, &attr); 5322 5323 assert(nativeWindow); 5324 5325 if(!useAlpha) 5326 XSetWindowBackgroundPixmap(display, nativeWindow, 1 /* ParentRelative */); 5327 5328 nativeHandle = nativeWindow; 5329 5330 ///+ 5331 arch_ulong[2] info; 5332 info[0] = 0; 5333 info[1] = 1; 5334 5335 string title = this.name is null ? "simpledisplay.d program" : this.name; 5336 auto XA_UTF8 = XInternAtom(display, "UTF8_STRING".ptr, false); 5337 auto XA_NETWM_NAME = XInternAtom(display, "_NET_WM_NAME".ptr, false); 5338 XChangeProperty(display, nativeWindow, XA_NETWM_NAME, XA_UTF8, 8, PropModeReplace, title.ptr, cast(uint)title.length); 5339 5340 XChangeProperty( 5341 display, 5342 nativeWindow, 5343 GetAtom!("_XEMBED_INFO", true)(display), 5344 GetAtom!("_XEMBED_INFO", true)(display), 5345 32 /* bits */, 5346 0 /*PropModeReplace*/, 5347 info.ptr, 5348 2); 5349 5350 import core.sys.posix.unistd; 5351 arch_ulong pid = getpid(); 5352 5353 XChangeProperty( 5354 display, 5355 nativeWindow, 5356 GetAtom!("_NET_WM_PID", true)(display), 5357 XA_CARDINAL, 5358 32 /* bits */, 5359 0 /*PropModeReplace*/, 5360 &pid, 5361 1); 5362 5363 updateNetWmIcon(); 5364 5365 if (sdpyWindowClassStr !is null && sdpyWindowClassStr[0]) { 5366 //{ import core.stdc.stdio; printf("winclass: [%s]\n", sdpyWindowClassStr); } 5367 XClassHint klass; 5368 XWMHints wh; 5369 XSizeHints size; 5370 klass.res_name = sdpyWindowClassStr; 5371 klass.res_class = sdpyWindowClassStr; 5372 XSetWMProperties(display, nativeWindow, null, null, null, 0, &size, &wh, &klass); 5373 } 5374 5375 // believe it or not, THIS is what xfce needed for the 9999 issue 5376 XSizeHints sh; 5377 c_long spr; 5378 XGetWMNormalHints(display, nativeWindow, &sh, &spr); 5379 sh.flags |= PMaxSize | PMinSize; 5380 // FIXME maybe nicer resizing 5381 sh.min_width = 16; 5382 sh.min_height = 16; 5383 sh.max_width = 22; 5384 sh.max_height = 22; 5385 XSetWMNormalHints(display, nativeWindow, &sh); 5386 5387 5388 //+/ 5389 5390 5391 XSelectInput(display, nativeWindow, 5392 EventMask.ButtonPressMask | EventMask.ExposureMask | EventMask.StructureNotifyMask | EventMask.VisibilityChangeMask | 5393 EventMask.EnterWindowMask | EventMask.LeaveWindowMask); 5394 5395 sendTrayMessage(SYSTEM_TRAY_REQUEST_DOCK, nativeWindow, 0, 0); 5396 // XMapWindow(display, nativeWindow); // to demo it w/o a tray 5397 5398 CapableOfHandlingNativeEvent.nativeHandleMapping[nativeWindow] = this; 5399 active = true; 5400 } 5401 5402 void updateNetWmIcon() { 5403 if(img is null) return; 5404 auto display = XDisplayConnection.get; 5405 // FIXME: ensure this is correct 5406 arch_ulong[] buffer; 5407 auto imgMi = img.toTrueColorImage; 5408 buffer ~= imgMi.width; 5409 buffer ~= imgMi.height; 5410 foreach(c; imgMi.imageData.colors) { 5411 arch_ulong b; 5412 b |= c.a << 24; 5413 b |= c.r << 16; 5414 b |= c.g << 8; 5415 b |= c.b; 5416 buffer ~= b; 5417 } 5418 5419 XChangeProperty( 5420 display, 5421 nativeHandle, 5422 GetAtom!"_NET_WM_ICON"(display), 5423 GetAtom!"CARDINAL"(display), 5424 32 /* bits */, 5425 0 /*PropModeReplace*/, 5426 buffer.ptr, 5427 cast(int) buffer.length); 5428 } 5429 5430 5431 5432 private SimpleWindow balloon; 5433 version(with_timer) 5434 private Timer timer; 5435 5436 private Window nativeHandle; 5437 private Pixmap clippixmap = None; 5438 private int width = 16; 5439 private int height = 16; 5440 private bool active = false; 5441 5442 void delegate (int x, int y, MouseButton button, ModifierState mods) onClickEx; /// x and y are globals (relative to root window). X11 only. 5443 void delegate (int x, int y, ModifierState mods) onEnter; /// x and y are global window coordinates. X11 only. 5444 void delegate () onLeave; /// X11 only. 5445 5446 @property bool closed () const pure nothrow @safe @nogc { return !active; } /// 5447 5448 /// X11 only. Get global window coordinates and size. This can be used to show various notifications. 5449 void getWindowRect (out int x, out int y, out int width, out int height) { 5450 if (!active) { width = 1; height = 1; return; } // 1: just in case 5451 Window dummyw; 5452 auto dpy = XDisplayConnection.get; 5453 //XWindowAttributes xwa; 5454 //XGetWindowAttributes(dpy, nativeHandle, &xwa); 5455 //XTranslateCoordinates(dpy, nativeHandle, RootWindow(dpy, DefaultScreen(dpy)), xwa.x, xwa.y, &x, &y, &dummyw); 5456 XTranslateCoordinates(dpy, nativeHandle, RootWindow(dpy, DefaultScreen(dpy)), x, y, &x, &y, &dummyw); 5457 width = this.width; 5458 height = this.height; 5459 } 5460 } 5461 5462 /+ 5463 What I actually want from this: 5464 5465 * set / change: icon, tooltip 5466 * handle: mouse click, right click 5467 * show: notification bubble. 5468 +/ 5469 5470 version(Windows) { 5471 WindowsIcon win32Icon; 5472 HWND hwnd; 5473 5474 NOTIFYICONDATAW data; 5475 5476 NativeEventHandler getNativeEventHandler() { 5477 return delegate int(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam, out int mustReturn) { 5478 if(msg == WM_USER) { 5479 auto event = LOWORD(lParam); 5480 auto iconId = HIWORD(lParam); 5481 //auto x = GET_X_LPARAM(wParam); 5482 //auto y = GET_Y_LPARAM(wParam); 5483 switch(event) { 5484 case WM_LBUTTONDOWN: 5485 onClick()(MouseButton.left); 5486 break; 5487 case WM_RBUTTONDOWN: 5488 onClick()(MouseButton.right); 5489 break; 5490 case WM_MBUTTONDOWN: 5491 onClick()(MouseButton.middle); 5492 break; 5493 case WM_MOUSEMOVE: 5494 // sent, we could use it. 5495 break; 5496 case WM_MOUSEWHEEL: 5497 // NOT SENT 5498 break; 5499 //case NIN_KEYSELECT: 5500 //case NIN_SELECT: 5501 //break; 5502 default: {} 5503 } 5504 } 5505 return 0; 5506 }; 5507 } 5508 5509 enum NIF_SHOWTIP = 0x00000080; 5510 5511 private static struct NOTIFYICONDATAW { 5512 DWORD cbSize; 5513 HWND hWnd; 5514 UINT uID; 5515 UINT uFlags; 5516 UINT uCallbackMessage; 5517 HICON hIcon; 5518 WCHAR[128] szTip; 5519 DWORD dwState; 5520 DWORD dwStateMask; 5521 WCHAR[256] szInfo; 5522 union { 5523 UINT uTimeout; 5524 UINT uVersion; 5525 } 5526 WCHAR[64] szInfoTitle; 5527 DWORD dwInfoFlags; 5528 GUID guidItem; 5529 HICON hBalloonIcon; 5530 } 5531 5532 } 5533 5534 /++ 5535 Note that on Windows, only left, right, and middle buttons are sent. 5536 Mouse wheel buttons are NOT set, so don't rely on those events if your 5537 program is meant to be used on Windows too. 5538 +/ 5539 this(string name, MemoryImage icon, void delegate(MouseButton button) onClick) { 5540 // The canonical constructor for Windows needs the MemoryImage, so it is here, 5541 // but on X, we need an Image, so its canonical ctor is there. They should 5542 // forward to each other though. 5543 version(X11) { 5544 this.name = name; 5545 this.onClick = onClick; 5546 createXWin(); 5547 this.icon = icon; 5548 } else version(Windows) { 5549 this.onClick = onClick; 5550 this.win32Icon = new WindowsIcon(icon); 5551 5552 HINSTANCE hInstance = cast(HINSTANCE) GetModuleHandle(null); 5553 5554 static bool registered = false; 5555 if(!registered) { 5556 WNDCLASSEX wc; 5557 wc.cbSize = wc.sizeof; 5558 wc.hInstance = hInstance; 5559 wc.lpfnWndProc = &WndProc; 5560 wc.lpszClassName = "arsd_simpledisplay_notification_icon"w.ptr; 5561 if(!RegisterClassExW(&wc)) 5562 throw new WindowsApiException("RegisterClass", GetLastError()); 5563 registered = true; 5564 } 5565 5566 this.hwnd = CreateWindowW("arsd_simpledisplay_notification_icon"w.ptr, "test"w.ptr /* name */, 0 /* dwStyle */, 0, 0, 0, 0, HWND_MESSAGE, null, hInstance, null); 5567 if(hwnd is null) 5568 throw new WindowsApiException("CreateWindow", GetLastError()); 5569 5570 data.cbSize = data.sizeof; 5571 data.hWnd = hwnd; 5572 data.uID = cast(uint) cast(void*) this; 5573 data.uFlags = NIF_MESSAGE | NIF_ICON | NIF_TIP | NIF_STATE | NIF_SHOWTIP /* use default tooltip, for now. */; 5574 // NIF_INFO means show balloon 5575 data.uCallbackMessage = WM_USER; 5576 data.hIcon = this.win32Icon.hIcon; 5577 data.szTip = ""; // FIXME 5578 data.dwState = 0; // NIS_HIDDEN; // windows vista 5579 data.dwStateMask = NIS_HIDDEN; // windows vista 5580 5581 data.uVersion = 4; // NOTIFYICON_VERSION_4; // Windows Vista and up 5582 5583 5584 Shell_NotifyIcon(NIM_ADD, cast(NOTIFYICONDATA*) &data); 5585 5586 CapableOfHandlingNativeEvent.nativeHandleMapping[this.hwnd] = this; 5587 } else version(OSXCocoa) { 5588 throw new NotYetImplementedException(); 5589 } else static assert(0); 5590 } 5591 5592 /// ditto 5593 this(string name, Image icon, void delegate(MouseButton button) onClick) { 5594 version(X11) { 5595 this.onClick = onClick; 5596 this.name = name; 5597 createXWin(); 5598 this.icon = icon; 5599 } else version(Windows) { 5600 this(name, icon is null ? null : icon.toTrueColorImage(), onClick); 5601 } else version(OSXCocoa) { 5602 throw new NotYetImplementedException(); 5603 } else static assert(0); 5604 } 5605 5606 version(X11) { 5607 /++ 5608 X-specific extension (for now at least) 5609 +/ 5610 this(string name, MemoryImage icon, void delegate(int x, int y, MouseButton button, ModifierState mods) onClickEx) { 5611 this.onClickEx = onClickEx; 5612 createXWin(); 5613 if (icon !is null) this.icon = icon; 5614 } 5615 5616 /// ditto 5617 this(string name, Image icon, void delegate(int x, int y, MouseButton button, ModifierState mods) onClickEx) { 5618 this.onClickEx = onClickEx; 5619 createXWin(); 5620 this.icon = icon; 5621 } 5622 } 5623 5624 private void delegate (MouseButton button) onClick_; 5625 5626 /// 5627 @property final void delegate(MouseButton) onClick() { 5628 if(onClick_ is null) 5629 onClick_ = delegate void(MouseButton) {}; 5630 return onClick_; 5631 } 5632 5633 /// ditto 5634 @property final void onClick(void delegate(MouseButton) handler) { 5635 // I made this a property setter so we can wrap smaller arg 5636 // delegates and just forward all to onClickEx or something. 5637 onClick_ = handler; 5638 } 5639 5640 5641 string name_; 5642 @property void name(string n) { 5643 name_ = n; 5644 } 5645 5646 @property string name() { 5647 return name_; 5648 } 5649 5650 private MemoryImage originalMemoryImage; 5651 5652 /// 5653 @property void icon(MemoryImage i) { 5654 version(X11) { 5655 this.originalMemoryImage = i; 5656 if (!active) return; 5657 if (i !is null) { 5658 this.img = Image.fromMemoryImage(i, useAlpha, false); 5659 if(!useAlpha) 5660 this.clippixmap = transparencyMaskFromMemoryImage(i, nativeHandle); 5661 // writeln("using pixmap ", clippixmap); 5662 updateNetWmIcon(); 5663 redraw(); 5664 } else { 5665 if (this.img !is null) { 5666 this.img = null; 5667 redraw(); 5668 } 5669 } 5670 } else version(Windows) { 5671 this.win32Icon = new WindowsIcon(i); 5672 5673 data.uFlags = NIF_ICON; 5674 data.hIcon = this.win32Icon.hIcon; 5675 5676 Shell_NotifyIcon(NIM_MODIFY, cast(NOTIFYICONDATA*) &data); 5677 } else version(OSXCocoa) { 5678 throw new NotYetImplementedException(); 5679 } else static assert(0); 5680 } 5681 5682 /// ditto 5683 @property void icon (Image i) { 5684 version(X11) { 5685 if (!active) return; 5686 if (i !is img) { 5687 originalMemoryImage = null; 5688 img = i; 5689 redraw(); 5690 } 5691 } else version(Windows) { 5692 this.icon(i is null ? null : i.toTrueColorImage()); 5693 } else version(OSXCocoa) { 5694 throw new NotYetImplementedException(); 5695 } else static assert(0); 5696 } 5697 5698 /++ 5699 Shows a balloon notification. You can only show one balloon at a time, if you call 5700 it twice while one is already up, the first balloon will be replaced. 5701 5702 5703 The user is free to block notifications and they will automatically disappear after 5704 a timeout period. 5705 5706 Params: 5707 title = Title of the notification. Must be 40 chars or less or the OS may truncate it. 5708 message = The message to pop up. Must be 220 chars or less or the OS may truncate it. 5709 icon = the icon to display with the notification. If null, it uses your existing icon. 5710 onclick = delegate called if the user clicks the balloon. (not yet implemented) 5711 timeout = your suggested timeout period. The operating system is free to ignore your suggestion. 5712 +/ 5713 void showBalloon(string title, string message, MemoryImage icon = null, void delegate() onclick = null, int timeout = 2_500) { 5714 bool useCustom = true; 5715 version(libnotify) { 5716 if(onclick is null) // libnotify impl doesn't support callbacks yet because it doesn't do a dbus message loop 5717 try { 5718 if(!active) return; 5719 5720 if(libnotify is null) { 5721 libnotify = new C_DynamicLibrary("libnotify.so"); 5722 libnotify.call!("notify_init", int, const char*)()((ApplicationName ~ "\0").ptr); 5723 } 5724 5725 auto n = libnotify.call!("notify_notification_new", void*, const char*, const char*, const char*)()((title~"\0").ptr, (message~"\0").ptr, null /* icon */); 5726 5727 libnotify.call!("notify_notification_set_timeout", void, void*, int)()(n, timeout); 5728 5729 if(onclick) { 5730 libnotify_action_delegates[libnotify_action_delegates_count] = onclick; 5731 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); 5732 libnotify_action_delegates_count++; 5733 } 5734 5735 // FIXME icon 5736 5737 // set hint image-data 5738 // set default action for onclick 5739 5740 void* error; 5741 libnotify.call!("notify_notification_show", bool, void*, void**)()(n, &error); 5742 5743 useCustom = false; 5744 } catch(Exception e) { 5745 5746 } 5747 } 5748 5749 version(X11) { 5750 if(useCustom) { 5751 if(!active) return; 5752 if(balloon) { 5753 hideBalloon(); 5754 } 5755 // I know there are two specs for this, but one is never 5756 // implemented by any window manager I have ever seen, and 5757 // the other is a bloated mess and too complicated for simpledisplay... 5758 // so doing my own little window instead. 5759 balloon = new SimpleWindow(380, 120, null, OpenGlOptions.no, Resizability.fixedSize, WindowTypes.notification, WindowFlags.dontAutoShow/*, window*/); 5760 5761 int x, y, width, height; 5762 getWindowRect(x, y, width, height); 5763 5764 int bx = x - balloon.width; 5765 int by = y - balloon.height; 5766 if(bx < 0) 5767 bx = x + width + balloon.width; 5768 if(by < 0) 5769 by = y + height; 5770 5771 // just in case, make sure it is actually on scren 5772 if(bx < 0) 5773 bx = 0; 5774 if(by < 0) 5775 by = 0; 5776 5777 balloon.move(bx, by); 5778 auto painter = balloon.draw(); 5779 painter.fillColor = Color(220, 220, 220); 5780 painter.outlineColor = Color.black; 5781 painter.drawRectangle(Point(0, 0), balloon.width, balloon.height); 5782 auto iconWidth = icon is null ? 0 : icon.width; 5783 if(icon) 5784 painter.drawImage(Point(4, 4), Image.fromMemoryImage(icon)); 5785 iconWidth += 6; // margin around the icon 5786 5787 // draw a close button 5788 painter.outlineColor = Color(44, 44, 44); 5789 painter.fillColor = Color(255, 255, 255); 5790 painter.drawRectangle(Point(balloon.width - 15, 3), 13, 13); 5791 painter.pen = Pen(Color.black, 3); 5792 painter.drawLine(Point(balloon.width - 14, 4), Point(balloon.width - 4, 14)); 5793 painter.drawLine(Point(balloon.width - 4, 4), Point(balloon.width - 14, 13)); 5794 painter.pen = Pen(Color.black, 1); 5795 painter.fillColor = Color(220, 220, 220); 5796 5797 // Draw the title and message 5798 painter.drawText(Point(4 + iconWidth, 4), title); 5799 painter.drawLine( 5800 Point(4 + iconWidth, 4 + painter.fontHeight + 1), 5801 Point(balloon.width - 4, 4 + painter.fontHeight + 1), 5802 ); 5803 painter.drawText(Point(4 + iconWidth, 4 + painter.fontHeight + 4), message); 5804 5805 balloon.setEventHandlers( 5806 (MouseEvent ev) { 5807 if(ev.type == MouseEventType.buttonPressed) { 5808 if(ev.x > balloon.width - 16 && ev.y < 16) 5809 hideBalloon(); 5810 else if(onclick) 5811 onclick(); 5812 } 5813 } 5814 ); 5815 balloon.show(); 5816 5817 version(with_timer) 5818 timer = new Timer(timeout, &hideBalloon); 5819 else {} // FIXME 5820 } 5821 } else version(Windows) { 5822 enum NIF_INFO = 0x00000010; 5823 5824 data.uFlags = NIF_INFO; 5825 5826 // FIXME: go back to the last valid unicode code point 5827 if(title.length > 40) 5828 title = title[0 .. 40]; 5829 if(message.length > 220) 5830 message = message[0 .. 220]; 5831 5832 enum NIIF_RESPECT_QUIET_TIME = 0x00000080; 5833 enum NIIF_LARGE_ICON = 0x00000020; 5834 enum NIIF_NOSOUND = 0x00000010; 5835 enum NIIF_USER = 0x00000004; 5836 enum NIIF_ERROR = 0x00000003; 5837 enum NIIF_WARNING = 0x00000002; 5838 enum NIIF_INFO = 0x00000001; 5839 enum NIIF_NONE = 0; 5840 5841 WCharzBuffer t = WCharzBuffer(title); 5842 WCharzBuffer m = WCharzBuffer(message); 5843 5844 t.copyInto(data.szInfoTitle); 5845 m.copyInto(data.szInfo); 5846 data.dwInfoFlags = NIIF_RESPECT_QUIET_TIME; 5847 5848 if(icon !is null) { 5849 auto i = new WindowsIcon(icon); 5850 data.hBalloonIcon = i.hIcon; 5851 data.dwInfoFlags |= NIIF_USER; 5852 } 5853 5854 Shell_NotifyIcon(NIM_MODIFY, cast(NOTIFYICONDATA*) &data); 5855 } else version(OSXCocoa) { 5856 throw new NotYetImplementedException(); 5857 } else static assert(0); 5858 } 5859 5860 /// 5861 //version(Windows) 5862 void show() { 5863 version(X11) { 5864 if(!hidden) 5865 return; 5866 sendTrayMessage(SYSTEM_TRAY_REQUEST_DOCK, nativeHandle, 0, 0); 5867 hidden = false; 5868 } else version(Windows) { 5869 data.uFlags = NIF_STATE; 5870 data.dwState = 0; // NIS_HIDDEN; // windows vista 5871 data.dwStateMask = NIS_HIDDEN; // windows vista 5872 Shell_NotifyIcon(NIM_MODIFY, cast(NOTIFYICONDATA*) &data); 5873 } else version(OSXCocoa) { 5874 throw new NotYetImplementedException(); 5875 } else static assert(0); 5876 } 5877 5878 version(X11) 5879 bool hidden = false; 5880 5881 /// 5882 //version(Windows) 5883 void hide() { 5884 version(X11) { 5885 if(hidden) 5886 return; 5887 hidden = true; 5888 XUnmapWindow(XDisplayConnection.get, nativeHandle); 5889 } else version(Windows) { 5890 data.uFlags = NIF_STATE; 5891 data.dwState = NIS_HIDDEN; // windows vista 5892 data.dwStateMask = NIS_HIDDEN; // windows vista 5893 Shell_NotifyIcon(NIM_MODIFY, cast(NOTIFYICONDATA*) &data); 5894 } else version(OSXCocoa) { 5895 throw new NotYetImplementedException(); 5896 } else static assert(0); 5897 } 5898 5899 /// 5900 void close () { 5901 version(X11) { 5902 if (active) { 5903 active = false; // event handler will set this too, but meh 5904 XUnmapWindow(XDisplayConnection.get, nativeHandle); // 'cause why not; let's be polite 5905 XDestroyWindow(XDisplayConnection.get, nativeHandle); 5906 flushGui(); 5907 } 5908 } else version(Windows) { 5909 Shell_NotifyIcon(NIM_DELETE, cast(NOTIFYICONDATA*) &data); 5910 } else version(OSXCocoa) { 5911 throw new NotYetImplementedException(); 5912 } else static assert(0); 5913 } 5914 5915 ~this() { 5916 if(!thisIsGuiThread) return; // FIXME: leaks if multithreaded gc 5917 version(X11) 5918 if(clippixmap != None) 5919 XFreePixmap(XDisplayConnection.get, clippixmap); 5920 close(); 5921 } 5922 } 5923 5924 version(X11) 5925 /// Call `XFreePixmap` on the return value. 5926 Pixmap transparencyMaskFromMemoryImage(MemoryImage i, Window window) { 5927 char[] data = new char[](i.width * i.height / 8 + 2); 5928 data[] = 0; 5929 5930 int bitOffset = 0; 5931 foreach(c; i.getAsTrueColorImage().imageData.colors) { // FIXME inefficient unnecessary conversion in palette cases 5932 ubyte v = c.a > 128 ? 1 : 0; 5933 data[bitOffset / 8] |= v << (bitOffset%8); 5934 bitOffset++; 5935 } 5936 auto handle = XCreateBitmapFromData(XDisplayConnection.get, cast(Drawable) window, data.ptr, i.width, i.height); 5937 return handle; 5938 } 5939 5940 5941 // basic functions to make timers 5942 /** 5943 A timer that will trigger your function on a given interval. 5944 5945 5946 You create a timer with an interval and a callback. It will continue 5947 to fire on the interval until it is destroyed. 5948 5949 There are currently no one-off timers (instead, just create one and 5950 destroy it when it is triggered) nor are there pause/resume functions - 5951 the timer must again be destroyed and recreated if you want to pause it. 5952 5953 --- 5954 auto timer = new Timer(50, { it happened!; }); 5955 timer.destroy(); 5956 --- 5957 5958 Timers can only be expected to fire when the event loop is running and only 5959 once per iteration through the event loop. 5960 5961 History: 5962 Prior to December 9, 2020, a timer pulse set too high with a handler too 5963 slow could lock up the event loop. It now guarantees other things will 5964 get a chance to run between timer calls, even if that means not keeping up 5965 with the requested interval. 5966 */ 5967 version(with_timer) { 5968 version(use_arsd_core) 5969 alias Timer = arsd.core.Timer; // FIXME should probably wrap it for a stable api 5970 else 5971 class Timer { 5972 // FIXME: needs pause and unpause 5973 // FIXME: I might add overloads for ones that take a count of 5974 // how many elapsed since last time (on Windows, it will divide 5975 // the ticks thing given, on Linux it is just available) and 5976 // maybe one that takes an instance of the Timer itself too 5977 /// Create a timer with a callback when it triggers. 5978 this(int intervalInMilliseconds, void delegate() onPulse) @trusted { 5979 assert(onPulse !is null); 5980 5981 this.intervalInMilliseconds = intervalInMilliseconds; 5982 this.onPulse = onPulse; 5983 5984 version(Windows) { 5985 /* 5986 handle = SetTimer(null, handle, intervalInMilliseconds, &timerCallback); 5987 if(handle == 0) 5988 throw new WindowsApiException("SetTimer", GetLastError()); 5989 */ 5990 5991 // thanks to Archival 998 for the WaitableTimer blocks 5992 handle = CreateWaitableTimer(null, false, null); 5993 long initialTime = -intervalInMilliseconds; 5994 if(handle is null || !SetWaitableTimer(handle, cast(LARGE_INTEGER*)&initialTime, intervalInMilliseconds, &timerCallback, handle, false)) 5995 throw new WindowsApiException("SetWaitableTimer", GetLastError()); 5996 5997 mapping[handle] = this; 5998 5999 } else version(Emscripten) { 6000 } else version(linux) { 6001 static import ep = core.sys.linux.epoll; 6002 6003 import core.sys.linux.timerfd; 6004 6005 fd = timerfd_create(CLOCK_MONOTONIC, 0); 6006 if(fd == -1) 6007 throw new Exception("timer create failed"); 6008 6009 mapping[fd] = this; 6010 6011 itimerspec value = makeItimerspec(intervalInMilliseconds); 6012 6013 if(timerfd_settime(fd, 0, &value, null) == -1) 6014 throw new Exception("couldn't make pulse timer"); 6015 6016 version(with_eventloop) { 6017 import arsd.eventloop; 6018 addFileEventListeners(fd, &trigger, null, null); 6019 } else { 6020 prepareEventLoop(); 6021 6022 ep.epoll_event ev = void; 6023 ev.events = ep.EPOLLIN; 6024 ev.data.fd = fd; 6025 ep.epoll_ctl(epollFd, ep.EPOLL_CTL_ADD, fd, &ev); 6026 } 6027 } else featureNotImplemented(); 6028 } 6029 6030 private int intervalInMilliseconds; 6031 6032 // just cuz I sometimes call it this. 6033 alias dispose = destroy; 6034 6035 /// Stop and destroy the timer object. 6036 void destroy() { 6037 version(Windows) { 6038 staticDestroy(handle); 6039 handle = null; 6040 } else version(linux) { 6041 staticDestroy(fd); 6042 fd = -1; 6043 } else featureNotImplemented(); 6044 } 6045 6046 version(Windows) 6047 static void staticDestroy(HANDLE handle) { 6048 if(handle) { 6049 // KillTimer(null, handle); 6050 CancelWaitableTimer(cast(void*)handle); 6051 mapping.remove(handle); 6052 CloseHandle(handle); 6053 } 6054 } 6055 else version(Emscripten) 6056 static void staticDestroy(int fd) @system { 6057 assert(0); 6058 } 6059 else version(linux) 6060 static void staticDestroy(int fd) @system { 6061 if(fd != -1) { 6062 import unix = core.sys.posix.unistd; 6063 static import ep = core.sys.linux.epoll; 6064 6065 version(with_eventloop) { 6066 import arsd.eventloop; 6067 removeFileEventListeners(fd); 6068 } else { 6069 ep.epoll_event ev = void; 6070 ev.events = ep.EPOLLIN; 6071 ev.data.fd = fd; 6072 6073 ep.epoll_ctl(epollFd, ep.EPOLL_CTL_DEL, fd, &ev); 6074 } 6075 unix.close(fd); 6076 mapping.remove(fd); 6077 } 6078 } 6079 6080 ~this() { 6081 version(Windows) { if(handle) 6082 cleanupQueue.queue!staticDestroy(handle); 6083 } else version(linux) { if(fd != -1) 6084 cleanupQueue.queue!staticDestroy(fd); 6085 } 6086 } 6087 6088 void changeTime(int intervalInMilliseconds) 6089 { 6090 this.intervalInMilliseconds = intervalInMilliseconds; 6091 version(Windows) 6092 { 6093 if(handle) 6094 { 6095 //handle = SetTimer(null, handle, intervalInMilliseconds, &timerCallback); 6096 long initialTime = -intervalInMilliseconds; 6097 if(handle is null || !SetWaitableTimer(handle, cast(LARGE_INTEGER*)&initialTime, intervalInMilliseconds, &timerCallback, handle, false)) 6098 throw new WindowsApiException("couldn't change pulse timer", GetLastError()); 6099 } 6100 } else version(linux) { 6101 import core.sys.linux.timerfd; 6102 6103 itimerspec value = makeItimerspec(intervalInMilliseconds); 6104 if(timerfd_settime(fd, 0, &value, null) == -1) { 6105 throw new Exception("couldn't change pulse timer"); 6106 } 6107 } else { 6108 assert(false, "Timer.changeTime(int) is not implemented for this platform"); 6109 } 6110 } 6111 6112 6113 private: 6114 6115 void delegate() onPulse; 6116 6117 int lastEventLoopRoundTriggered; 6118 6119 version(linux) { 6120 static auto makeItimerspec(int intervalInMilliseconds) { 6121 import core.sys.linux.timerfd; 6122 6123 itimerspec value; 6124 value.it_value.tv_sec = cast(int) (intervalInMilliseconds / 1000); 6125 value.it_value.tv_nsec = (intervalInMilliseconds % 1000) * 1000_000; 6126 6127 value.it_interval.tv_sec = cast(int) (intervalInMilliseconds / 1000); 6128 value.it_interval.tv_nsec = (intervalInMilliseconds % 1000) * 1000_000; 6129 6130 return value; 6131 } 6132 } 6133 6134 void trigger() { 6135 version(linux) { 6136 import unix = core.sys.posix.unistd; 6137 long val; 6138 unix.read(fd, &val, val.sizeof); // gotta clear the pipe 6139 } else version(Windows) { 6140 if(this.lastEventLoopRoundTriggered == eventLoopRound) 6141 return; // never try to actually run faster than the event loop 6142 lastEventLoopRoundTriggered = eventLoopRound; 6143 } else featureNotImplemented(); 6144 6145 onPulse(); 6146 } 6147 6148 version(Windows) 6149 void rearm() { 6150 6151 } 6152 6153 version(Windows) 6154 extern(Windows) 6155 //static void timerCallback(HWND, UINT, UINT_PTR timer, DWORD dwTime) nothrow { 6156 static void timerCallback(HANDLE timer, DWORD lowTime, DWORD hiTime) nothrow { 6157 if(Timer* t = timer in mapping) { 6158 try 6159 (*t).trigger(); 6160 catch(Exception e) { sdpy_abort(e); assert(0); } 6161 } 6162 } 6163 6164 version(Windows) { 6165 //UINT_PTR handle; 6166 //static Timer[UINT_PTR] mapping; 6167 HANDLE handle; 6168 __gshared Timer[HANDLE] mapping; 6169 } else version(linux) { 6170 int fd = -1; 6171 __gshared Timer[int] mapping; 6172 } else version(OSXCocoa) { 6173 } else static assert(0, "timer not supported"); 6174 } 6175 } 6176 6177 version(Windows) 6178 private int eventLoopRound; 6179 6180 version(Windows) 6181 /// 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 6182 class WindowsHandleReader { 6183 /// 6184 this(void delegate() onReady, HANDLE handle) { 6185 this.onReady = onReady; 6186 this.handle = handle; 6187 6188 mapping[handle] = this; 6189 6190 enable(); 6191 } 6192 6193 version(use_arsd_core) 6194 ICoreEventLoop.UnregisterToken unregisterToken; 6195 6196 /// 6197 void enable() { 6198 version(use_arsd_core) { 6199 unregisterToken = getThisThreadEventLoop(EventLoopType.Ui).addCallbackOnHandleReady(handle, new CallbackHelper(&ready)); 6200 } else { 6201 auto el = EventLoop.get().impl; 6202 el.handles ~= handle; 6203 } 6204 } 6205 6206 /// 6207 void disable() { 6208 version(use_arsd_core) { 6209 unregisterToken.unregister(); 6210 } else { 6211 auto el = EventLoop.get().impl; 6212 for(int i = 0; i < el.handles.length; i++) { 6213 if(el.handles[i] is handle) { 6214 el.handles[i] = el.handles[$-1]; 6215 el.handles = el.handles[0 .. $-1]; 6216 return; 6217 } 6218 } 6219 } 6220 } 6221 6222 void dispose() { 6223 disable(); 6224 if(handle) 6225 mapping.remove(handle); 6226 handle = null; 6227 } 6228 6229 void ready() { 6230 if(onReady) 6231 onReady(); 6232 } 6233 6234 HANDLE handle; 6235 void delegate() onReady; 6236 6237 __gshared WindowsHandleReader[HANDLE] mapping; 6238 } 6239 6240 version(Posix) 6241 /// Lets you add files to the event loop for reading. Use at your own risk. 6242 class PosixFdReader { 6243 /// 6244 this(void delegate() onReady, int fd, bool captureReads = true, bool captureWrites = false) { 6245 this((int, bool, bool) { onReady(); }, fd, captureReads, captureWrites); 6246 } 6247 6248 /// 6249 this(void delegate(int) onReady, int fd, bool captureReads = true, bool captureWrites = false) { 6250 this((int fd, bool, bool) { onReady(fd); }, fd, captureReads, captureWrites); 6251 } 6252 6253 /// 6254 this(void delegate(int fd, bool read, bool write) onReady, int fd, bool captureReads = true, bool captureWrites = false) { 6255 this.onReady = onReady; 6256 this.fd = fd; 6257 this.captureWrites = captureWrites; 6258 this.captureReads = captureReads; 6259 6260 mapping[fd] = this; 6261 6262 version(with_eventloop) { 6263 import arsd.eventloop; 6264 addFileEventListeners(fd, &readyel); 6265 } else { 6266 enable(); 6267 } 6268 } 6269 6270 bool captureReads; 6271 bool captureWrites; 6272 6273 version(use_arsd_core) { 6274 import arsd.core; 6275 ICoreEventLoop.UnregisterToken unregisterToken; 6276 } 6277 6278 version(with_eventloop) {} else 6279 /// 6280 void enable() @system { 6281 enabled = true; 6282 6283 version(use_arsd_core) { 6284 unregisterToken = getThisThreadEventLoop(EventLoopType.Ui).addCallbackOnFdReadable(fd, new CallbackHelper( 6285 () { onReady(fd, true, false); } 6286 )); 6287 // FIXME: what if it is writeable? 6288 6289 } else version(linux) { 6290 prepareEventLoop(); 6291 static import ep = core.sys.linux.epoll; 6292 ep.epoll_event ev = void; 6293 ev.events = (captureReads ? ep.EPOLLIN : 0) | (captureWrites ? ep.EPOLLOUT : 0); 6294 // writeln("enable ", fd, " ", captureReads, " ", captureWrites); 6295 ev.data.fd = fd; 6296 ep.epoll_ctl(epollFd, ep.EPOLL_CTL_ADD, fd, &ev); 6297 } else { 6298 6299 } 6300 } 6301 6302 version(with_eventloop) {} else 6303 /// 6304 void disable() @system { 6305 enabled = false; 6306 6307 version(use_arsd_core) { 6308 unregisterToken.unregister(); 6309 } else 6310 version(linux) { 6311 prepareEventLoop(); 6312 static import ep = core.sys.linux.epoll; 6313 ep.epoll_event ev = void; 6314 ev.events = (captureReads ? ep.EPOLLIN : 0) | (captureWrites ? ep.EPOLLOUT : 0); 6315 // writeln("disable ", fd, " ", captureReads, " ", captureWrites); 6316 ev.data.fd = fd; 6317 ep.epoll_ctl(epollFd, ep.EPOLL_CTL_DEL, fd, &ev); 6318 } 6319 } 6320 6321 version(with_eventloop) {} else 6322 /// 6323 void dispose() { 6324 if(enabled) 6325 disable(); 6326 if(fd != -1) 6327 mapping.remove(fd); 6328 fd = -1; 6329 } 6330 6331 void delegate(int, bool, bool) onReady; 6332 6333 version(with_eventloop) 6334 void readyel() { 6335 onReady(fd, true, true); 6336 } 6337 6338 void ready(uint flags) { 6339 version(Emscripten) { 6340 assert(0); 6341 } else version(linux) { 6342 static import ep = core.sys.linux.epoll; 6343 onReady(fd, (flags & ep.EPOLLIN) ? true : false, (flags & ep.EPOLLOUT) ? true : false); 6344 } else { 6345 import core.sys.posix.poll; 6346 onReady(fd, (flags & POLLIN) ? true : false, (flags & POLLOUT) ? true : false); 6347 } 6348 } 6349 6350 void hup(uint flags) { 6351 if(onHup) 6352 onHup(); 6353 } 6354 6355 void delegate() onHup; 6356 6357 int fd = -1; 6358 private bool enabled; 6359 __gshared PosixFdReader[int] mapping; 6360 } 6361 6362 // basic functions to access the clipboard 6363 /+ 6364 6365 6366 http://msdn.microsoft.com/en-us/library/windows/desktop/ff729168%28v=vs.85%29.aspx 6367 http://msdn.microsoft.com/en-us/library/windows/desktop/ms649039%28v=vs.85%29.aspx 6368 http://msdn.microsoft.com/en-us/library/windows/desktop/ms649035%28v=vs.85%29.aspx 6369 http://msdn.microsoft.com/en-us/library/windows/desktop/ms649051%28v=vs.85%29.aspx 6370 http://msdn.microsoft.com/en-us/library/windows/desktop/ms649037%28v=vs.85%29.aspx 6371 http://msdn.microsoft.com/en-us/library/windows/desktop/ms649035%28v=vs.85%29.aspx 6372 http://msdn.microsoft.com/en-us/library/windows/desktop/ms649016%28v=vs.85%29.aspx 6373 6374 +/ 6375 6376 /++ 6377 this does a delegate because it is actually an async call on X... 6378 the receiver may never be called if the clipboard is empty or unavailable 6379 gets plain text from the clipboard. 6380 +/ 6381 void getClipboardText(SimpleWindow clipboardOwner, void delegate(in char[]) receiver) @system { 6382 version(Windows) { 6383 HWND hwndOwner = clipboardOwner ? clipboardOwner.impl.hwnd : null; 6384 if(OpenClipboard(hwndOwner) == 0) 6385 throw new WindowsApiException("OpenClipboard", GetLastError()); 6386 scope(exit) 6387 CloseClipboard(); 6388 // see: https://docs.microsoft.com/en-us/windows/win32/api/winuser/nf-winuser-getpriorityclipboardformat 6389 if(auto dataHandle = GetClipboardData(CF_UNICODETEXT)) { 6390 6391 if(auto data = cast(wchar*) GlobalLock(dataHandle)) { 6392 scope(exit) 6393 GlobalUnlock(dataHandle); 6394 6395 // FIXME: CR/LF conversions 6396 // FIXME: I might not have to copy it now that the receiver is in char[] instead of string 6397 int len = 0; 6398 auto d = data; 6399 while(*d) { 6400 d++; 6401 len++; 6402 } 6403 string s; 6404 s.reserve(len); 6405 foreach(dchar ch; data[0 .. len]) { 6406 s ~= ch; 6407 } 6408 receiver(s); 6409 } 6410 } 6411 } else version(X11) { 6412 getX11Selection!"CLIPBOARD"(clipboardOwner, receiver); 6413 } else version(OSXCocoa) { 6414 throw new NotYetImplementedException(); 6415 } else version(Emscripten) { 6416 throw new NotYetImplementedException(); 6417 } else static assert(0); 6418 } 6419 6420 // FIXME: a clipboard listener might be cool btw 6421 6422 /++ 6423 this does a delegate because it is actually an async call on X... 6424 the receiver may never be called if the clipboard is empty or unavailable 6425 gets image from the clipboard. 6426 6427 templated because it introduces an optional dependency on arsd.bmp 6428 +/ 6429 void getClipboardImage()(SimpleWindow clipboardOwner, void delegate(MemoryImage) receiver) { 6430 version(Windows) { 6431 HWND hwndOwner = clipboardOwner ? clipboardOwner.impl.hwnd : null; 6432 if(OpenClipboard(hwndOwner) == 0) 6433 throw new WindowsApiException("OpenClipboard", GetLastError()); 6434 scope(exit) 6435 CloseClipboard(); 6436 if(auto dataHandle = GetClipboardData(CF_DIBV5)) { 6437 if(auto data = cast(ubyte*) GlobalLock(dataHandle)) { 6438 scope(exit) 6439 GlobalUnlock(dataHandle); 6440 6441 auto len = GlobalSize(dataHandle); 6442 6443 import arsd.bmp; 6444 auto img = readBmp(data[0 .. len], false); 6445 receiver(img); 6446 } 6447 } 6448 } else version(X11) { 6449 getX11Selection!"CLIPBOARD"(clipboardOwner, receiver); 6450 } else version(OSXCocoa) { 6451 throw new NotYetImplementedException(); 6452 } else version(Emscripten) { 6453 throw new NotYetImplementedException(); 6454 } else static assert(0); 6455 } 6456 6457 /// Copies some text to the clipboard. 6458 void setClipboardText(SimpleWindow clipboardOwner, string text) { 6459 assert(clipboardOwner !is null); 6460 version(Windows) { 6461 if(OpenClipboard(clipboardOwner.impl.hwnd) == 0) 6462 throw new WindowsApiException("OpenClipboard", GetLastError()); 6463 scope(exit) 6464 CloseClipboard(); 6465 EmptyClipboard(); 6466 auto sz = sizeOfConvertedWstring(text, WindowsStringConversionFlags.convertNewLines | WindowsStringConversionFlags.zeroTerminate); 6467 auto handle = GlobalAlloc(GMEM_MOVEABLE, sz * 2); // zero terminated wchars 6468 if(handle is null) throw new WindowsApiException("GlobalAlloc", GetLastError()); 6469 if(auto data = cast(wchar*) GlobalLock(handle)) { 6470 auto slice = data[0 .. sz]; 6471 scope(failure) 6472 GlobalUnlock(handle); 6473 6474 auto str = makeWindowsString(text, slice, WindowsStringConversionFlags.convertNewLines | WindowsStringConversionFlags.zeroTerminate); 6475 6476 GlobalUnlock(handle); 6477 SetClipboardData(CF_UNICODETEXT, handle); 6478 } 6479 } else version(X11) { 6480 setX11Selection!"CLIPBOARD"(clipboardOwner, text); 6481 } else version(OSXCocoa) { 6482 throw new NotYetImplementedException(); 6483 } else version(Emscripten) { 6484 throw new NotYetImplementedException(); 6485 } else static assert(0); 6486 } 6487 6488 void setClipboardImage()(SimpleWindow clipboardOwner, MemoryImage img) { 6489 assert(clipboardOwner !is null); 6490 version(Windows) { 6491 if(OpenClipboard(clipboardOwner.impl.hwnd) == 0) 6492 throw new WindowsApiException("OpenClipboard", GetLastError()); 6493 scope(exit) 6494 CloseClipboard(); 6495 EmptyClipboard(); 6496 6497 6498 import arsd.bmp; 6499 ubyte[] mdata; 6500 mdata.reserve(img.width * img.height); 6501 void sink(ubyte b) { 6502 mdata ~= b; 6503 } 6504 writeBmpIndirect(img, &sink, false); 6505 6506 auto handle = GlobalAlloc(GMEM_MOVEABLE, mdata.length); 6507 if(handle is null) throw new WindowsApiException("GlobalAlloc", GetLastError()); 6508 if(auto data = cast(ubyte*) GlobalLock(handle)) { 6509 auto slice = data[0 .. mdata.length]; 6510 scope(failure) 6511 GlobalUnlock(handle); 6512 6513 slice[] = mdata[]; 6514 6515 GlobalUnlock(handle); 6516 SetClipboardData(CF_DIB, handle); 6517 } 6518 } else version(X11) { 6519 static class X11SetSelectionHandler_Image : X11SetSelectionHandler { 6520 mixin X11SetSelectionHandler_Basics; 6521 private const(ubyte)[] mdata; 6522 private const(ubyte)[] mdata_original; 6523 this(MemoryImage img) { 6524 import arsd.bmp; 6525 6526 mdata.reserve(img.width * img.height); 6527 void sink(ubyte b) { 6528 mdata ~= b; 6529 } 6530 writeBmpIndirect(img, &sink, true); 6531 6532 mdata_original = mdata; 6533 } 6534 6535 Atom[] availableFormats() { 6536 auto display = XDisplayConnection.get; 6537 return [ 6538 GetAtom!"image/bmp"(display), 6539 GetAtom!"TARGETS"(display) 6540 ]; 6541 } 6542 6543 ubyte[] getData(Atom format, return scope ubyte[] data) { 6544 if(mdata.length < data.length) { 6545 data[0 .. mdata.length] = mdata[]; 6546 auto ret = data[0 .. mdata.length]; 6547 mdata = mdata[$..$]; 6548 return ret; 6549 } else { 6550 data[] = mdata[0 .. data.length]; 6551 mdata = mdata[data.length .. $]; 6552 return data[]; 6553 } 6554 } 6555 6556 void done() { 6557 mdata = mdata_original; 6558 } 6559 } 6560 6561 setX11Selection!"CLIPBOARD"(clipboardOwner, new X11SetSelectionHandler_Image(img)); 6562 } else version(OSXCocoa) { 6563 throw new NotYetImplementedException(); 6564 } else version(Emscripten) { 6565 throw new NotYetImplementedException(); 6566 } else static assert(0); 6567 } 6568 6569 6570 version(X11) { 6571 // and the PRIMARY on X, be sure to put these in static if(UsingSimpledisplayX11) 6572 6573 private __gshared Atom*[] interredAtoms; // for discardAndRecreate 6574 6575 // FIXME: do a GetAtomUpfront too that just queues all at CT and combines it all. 6576 /// Platform-specific for X11. 6577 /// History: On February 21, 2021, I changed the default value of `create` to be true. 6578 @property Atom GetAtom(string name, bool create = true)(Display* display) { 6579 __gshared static Atom a; 6580 if(!a) { 6581 a = XInternAtom(display, name, !create); 6582 // FIXME: might need to synchronize this and attach it to the actual object 6583 interredAtoms ~= &a; 6584 } 6585 if(a == None) 6586 throw new Exception("XInternAtom " ~ name ~ " " ~ (create ? "true":"false")); 6587 return a; 6588 } 6589 6590 /// Platform-specific for X11 - gets atom names as a string. 6591 string getAtomName(Atom atom, Display* display) { 6592 auto got = XGetAtomName(display, atom); 6593 scope(exit) XFree(got); 6594 import core.stdc.string; 6595 string s = got[0 .. strlen(got)].idup; 6596 return s; 6597 } 6598 6599 /// Asserts ownership of PRIMARY and copies the text into a buffer that clients can request later. 6600 void setPrimarySelection(SimpleWindow window, string text) { 6601 setX11Selection!"PRIMARY"(window, text); 6602 } 6603 6604 /// Asserts ownership of SECONDARY and copies the text into a buffer that clients can request later. 6605 void setSecondarySelection(SimpleWindow window, string text) { 6606 setX11Selection!"SECONDARY"(window, text); 6607 } 6608 6609 interface X11SetSelectionHandler { 6610 // should include TARGETS right now 6611 Atom[] availableFormats(); 6612 // Return the slice of data you filled, empty slice if done. 6613 // this is to support the incremental thing 6614 ubyte[] getData(Atom format, return scope ubyte[] data); 6615 6616 void done(); 6617 6618 void handleRequest(XEvent); 6619 6620 bool matchesIncr(Window, Atom); 6621 void sendMoreIncr(XPropertyEvent*); 6622 } 6623 6624 mixin template X11SetSelectionHandler_Basics() { 6625 Window incrWindow; 6626 Atom incrAtom; 6627 Atom selectionAtom; 6628 Atom formatAtom; 6629 ubyte[] toSend; 6630 bool matchesIncr(Window w, Atom a) { 6631 return incrAtom && incrAtom == a && w == incrWindow; 6632 } 6633 void sendMoreIncr(XPropertyEvent* event) { 6634 auto display = XDisplayConnection.get; 6635 6636 XChangeProperty (display, 6637 incrWindow, 6638 incrAtom, 6639 formatAtom, 6640 8 /* bits */, PropModeReplace, 6641 toSend.ptr, cast(int) toSend.length); 6642 6643 if(toSend.length != 0) { 6644 toSend = this.getData(formatAtom, toSend[]); 6645 } else { 6646 this.done(); 6647 incrWindow = None; 6648 incrAtom = None; 6649 selectionAtom = None; 6650 formatAtom = None; 6651 toSend = null; 6652 } 6653 } 6654 void handleRequest(XEvent ev) { 6655 6656 auto display = XDisplayConnection.get; 6657 6658 XSelectionRequestEvent* event = &ev.xselectionrequest; 6659 XSelectionEvent selectionEvent; 6660 selectionEvent.type = EventType.SelectionNotify; 6661 selectionEvent.display = event.display; 6662 selectionEvent.requestor = event.requestor; 6663 selectionEvent.selection = event.selection; 6664 selectionEvent.time = event.time; 6665 selectionEvent.target = event.target; 6666 6667 bool supportedType() { 6668 foreach(t; this.availableFormats()) 6669 if(t == event.target) 6670 return true; 6671 return false; 6672 } 6673 6674 if(event.property == None) { 6675 selectionEvent.property = event.target; 6676 6677 XSendEvent(display, selectionEvent.requestor, false, 0, cast(XEvent*) &selectionEvent); 6678 XFlush(display); 6679 } if(event.target == GetAtom!"TARGETS"(display)) { 6680 /* respond with the supported types */ 6681 auto tlist = this.availableFormats(); 6682 XChangeProperty(display, event.requestor, event.property, XA_ATOM, 32, PropModeReplace, cast(void*)tlist.ptr, cast(int) tlist.length); 6683 selectionEvent.property = event.property; 6684 6685 XSendEvent(display, selectionEvent.requestor, false, 0, cast(XEvent*) &selectionEvent); 6686 XFlush(display); 6687 } else if(supportedType()) { 6688 auto buffer = new ubyte[](1024 * 64); 6689 auto toSend = this.getData(event.target, buffer[]); 6690 6691 if(toSend.length < 32 * 1024) { 6692 // small enough to send directly... 6693 selectionEvent.property = event.property; 6694 XChangeProperty (display, 6695 selectionEvent.requestor, 6696 selectionEvent.property, 6697 event.target, 6698 8 /* bits */, 0 /* PropModeReplace */, 6699 toSend.ptr, cast(int) toSend.length); 6700 6701 XSendEvent(display, selectionEvent.requestor, false, 0, cast(XEvent*) &selectionEvent); 6702 XFlush(display); 6703 } else { 6704 // large, let's send incrementally 6705 arch_ulong l = toSend.length; 6706 6707 // if I wanted other events from this window don't want to clear that out.... 6708 XWindowAttributes xwa; 6709 XGetWindowAttributes(display, selectionEvent.requestor, &xwa); 6710 6711 XSelectInput(display, selectionEvent.requestor, cast(EventMask) (xwa.your_event_mask | EventMask.PropertyChangeMask)); 6712 6713 incrWindow = event.requestor; 6714 incrAtom = event.property; 6715 formatAtom = event.target; 6716 selectionAtom = event.selection; 6717 this.toSend = toSend; 6718 6719 selectionEvent.property = event.property; 6720 XChangeProperty (display, 6721 selectionEvent.requestor, 6722 selectionEvent.property, 6723 GetAtom!"INCR"(display), 6724 32 /* bits */, PropModeReplace, 6725 &l, 1); 6726 6727 XSendEvent(display, selectionEvent.requestor, false, 0, cast(XEvent*) &selectionEvent); 6728 XFlush(display); 6729 } 6730 //if(after) 6731 //after(); 6732 } else { 6733 debug(sdpy_clip) { 6734 writeln("Unsupported data ", getAtomName(event.target, display)); 6735 } 6736 selectionEvent.property = None; // I don't know how to handle this type... 6737 XSendEvent(display, selectionEvent.requestor, false, EventMask.NoEventMask, cast(XEvent*) &selectionEvent); 6738 XFlush(display); 6739 } 6740 } 6741 } 6742 6743 class X11SetSelectionHandler_Text : X11SetSelectionHandler { 6744 mixin X11SetSelectionHandler_Basics; 6745 private const(ubyte)[] text; 6746 private const(ubyte)[] text_original; 6747 this(string text) { 6748 this.text = cast(const ubyte[]) text; 6749 this.text_original = this.text; 6750 } 6751 Atom[] availableFormats() { 6752 auto display = XDisplayConnection.get; 6753 return [ 6754 GetAtom!"UTF8_STRING"(display), 6755 GetAtom!"text/plain"(display), 6756 XA_STRING, 6757 GetAtom!"TARGETS"(display) 6758 ]; 6759 } 6760 6761 ubyte[] getData(Atom format, return scope ubyte[] data) { 6762 if(text.length < data.length) { 6763 data[0 .. text.length] = text[]; 6764 return data[0 .. text.length]; 6765 } else { 6766 data[] = text[0 .. data.length]; 6767 text = text[data.length .. $]; 6768 return data[]; 6769 } 6770 } 6771 6772 void done() { 6773 text = text_original; 6774 } 6775 } 6776 6777 /// 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?!) 6778 void setX11Selection(string atomName)(SimpleWindow window, string text, void delegate() after = null) { 6779 setX11Selection!atomName(window, new X11SetSelectionHandler_Text(text), after); 6780 } 6781 6782 private __gshared bool mightShortCircuitClipboard; 6783 6784 void setX11Selection(string atomName)(SimpleWindow window, X11SetSelectionHandler data, void delegate() after = null) { 6785 assert(window !is null); 6786 6787 auto display = XDisplayConnection.get(); 6788 static if (atomName == "PRIMARY") Atom a = XA_PRIMARY; 6789 else static if (atomName == "SECONDARY") Atom a = XA_SECONDARY; 6790 else Atom a = GetAtom!atomName(display); 6791 6792 if(mightShortCircuitClipboard) 6793 if(auto ptr = a in window.impl.setSelectionHandlers) { 6794 // we already have it, don't even need to inform the X server 6795 // sdpyPrintDebugString("short circuit in set"); 6796 *ptr = data; 6797 return; 6798 } 6799 6800 // we don't have it, tell X we want it 6801 XSetSelectionOwner(display, a, window.impl.window, 0 /* CurrentTime */); 6802 window.impl.setSelectionHandlers[a] = data; 6803 mightShortCircuitClipboard = true; 6804 } 6805 6806 /+ 6807 /++ 6808 History: 6809 Added September 28, 2024 6810 +/ 6811 bool hasX11Selection(string atomName)(SimpleWindow window) { 6812 auto display = XDisplayConnection.get(); 6813 static if (atomName == "PRIMARY") Atom a = XA_PRIMARY; 6814 else static if (atomName == "SECONDARY") Atom a = XA_SECONDARY; 6815 else Atom a = GetAtom!atomName(display); 6816 6817 if(a in window.impl.setSelectionHandlers) 6818 return true; 6819 else 6820 return false; 6821 } 6822 +/ 6823 6824 /// 6825 void getPrimarySelection(SimpleWindow window, void delegate(in char[]) handler) { 6826 getX11Selection!"PRIMARY"(window, handler); 6827 } 6828 6829 // added July 28, 2020 6830 // undocumented as experimental tho 6831 interface X11GetSelectionHandler { 6832 void handleData(Atom target, in ubyte[] data); 6833 Atom findBestFormat(Atom[] answer); 6834 6835 void prepareIncremental(Window, Atom); 6836 bool matchesIncr(Window, Atom); 6837 void handleIncrData(Atom, in ubyte[] data); 6838 } 6839 6840 mixin template X11GetSelectionHandler_Basics() { 6841 Window incrWindow; 6842 Atom incrAtom; 6843 6844 void prepareIncremental(Window w, Atom a) { 6845 incrWindow = w; 6846 incrAtom = a; 6847 } 6848 bool matchesIncr(Window w, Atom a) { 6849 return incrWindow == w && incrAtom == a; 6850 } 6851 6852 Atom incrFormatAtom; 6853 ubyte[] incrData; 6854 void handleIncrData(Atom format, in ubyte[] data) { 6855 incrFormatAtom = format; 6856 6857 if(data.length) 6858 incrData ~= data; 6859 else 6860 handleData(incrFormatAtom, incrData); 6861 6862 } 6863 } 6864 6865 static class X11GetSelectionHandler_Text : X11GetSelectionHandler { 6866 this(void delegate(in char[]) handler) { 6867 this.handler = handler; 6868 } 6869 6870 mixin X11GetSelectionHandler_Basics; 6871 6872 void delegate(in char[]) handler; 6873 6874 void handleData(Atom target, in ubyte[] data) { 6875 // import std.stdio; writeln(target, " ", data); 6876 if(target == GetAtom!"UTF8_STRING"(XDisplayConnection.get) || target == XA_STRING || target == GetAtom!"text/plain"(XDisplayConnection.get)) 6877 handler(cast(const char[]) data); 6878 else if(target == None && data is null) 6879 handler(null); // no suitable selection exists 6880 } 6881 6882 Atom findBestFormat(Atom[] answer) { 6883 Atom best = None; 6884 foreach(option; answer) { 6885 if(option == GetAtom!"UTF8_STRING"(XDisplayConnection.get)) { 6886 best = option; 6887 break; 6888 } else if(option == XA_STRING) { 6889 best = option; 6890 } else if(option == GetAtom!"text/plain"(XDisplayConnection.get)) { 6891 best = option; 6892 } 6893 } 6894 return best; 6895 } 6896 } 6897 6898 /// 6899 void getX11Selection(string atomName)(SimpleWindow window, void delegate(in char[]) handler, Time timestamp = 0 /* CurrentTime */) { 6900 assert(window !is null); 6901 6902 auto display = XDisplayConnection.get(); 6903 6904 static if (atomName == "PRIMARY") Atom atom = XA_PRIMARY; 6905 else static if (atomName == "SECONDARY") Atom atom = XA_SECONDARY; 6906 else Atom atom = GetAtom!atomName(display); 6907 6908 if(mightShortCircuitClipboard) 6909 if(auto ptr = atom in window.impl.setSelectionHandlers) { 6910 if(auto txt = (cast(X11SetSelectionHandler_Text) *ptr)) { 6911 // we already have it! short circuit everything 6912 6913 // sdpyPrintDebugString("short circuit in get"); 6914 handler(cast(char[]) txt.text_original); 6915 return; 6916 } 6917 } 6918 6919 window.impl.getSelectionHandlers[atom] = new X11GetSelectionHandler_Text(handler); 6920 6921 auto target = GetAtom!"TARGETS"(display); 6922 6923 // SDD_DATA is "simpledisplay.d data" 6924 XConvertSelection(display, atom, target, GetAtom!("SDD_DATA", true)(display), window.impl.window, timestamp); 6925 } 6926 6927 /// Gets the image on the clipboard, if there is one. Added July 2020. 6928 /// only supports bmps. using this function will import arsd.bmp. 6929 void getX11Selection(string atomName)(SimpleWindow window, void delegate(MemoryImage) handler) { 6930 assert(window !is null); 6931 6932 auto display = XDisplayConnection.get(); 6933 auto atom = GetAtom!atomName(display); 6934 6935 static class X11GetSelectionHandler_Image : X11GetSelectionHandler { 6936 this(void delegate(MemoryImage) handler) { 6937 this.handler = handler; 6938 } 6939 6940 mixin X11GetSelectionHandler_Basics; 6941 6942 void delegate(MemoryImage) handler; 6943 6944 void handleData(Atom target, in ubyte[] data) { 6945 if(target == GetAtom!"image/bmp"(XDisplayConnection.get)) { 6946 import arsd.bmp; 6947 handler(readBmp(data)); 6948 } 6949 } 6950 6951 Atom findBestFormat(Atom[] answer) { 6952 Atom best = None; 6953 foreach(option; answer) { 6954 if(option == GetAtom!"image/bmp"(XDisplayConnection.get)) { 6955 best = option; 6956 } 6957 } 6958 return best; 6959 } 6960 6961 } 6962 6963 6964 window.impl.getSelectionHandlers[atom] = new X11GetSelectionHandler_Image(handler); 6965 6966 auto target = GetAtom!"TARGETS"(display); 6967 6968 // SDD_DATA is "simpledisplay.d data" 6969 XConvertSelection(display, atom, target, GetAtom!("SDD_DATA", true)(display), window.impl.window, 0 /*CurrentTime*/); 6970 } 6971 6972 6973 /// 6974 void[] getX11PropertyData(Window window, Atom property, Atom type = AnyPropertyType) { 6975 Atom actualType; 6976 int actualFormat; 6977 arch_ulong actualItems; 6978 arch_ulong bytesRemaining; 6979 void* data; 6980 6981 auto display = XDisplayConnection.get(); 6982 if(XGetWindowProperty(display, window, property, 0, 0x7fffffff, false, type, &actualType, &actualFormat, &actualItems, &bytesRemaining, &data) == Success) { 6983 if(actualFormat == 0) 6984 return null; 6985 else { 6986 int byteLength; 6987 if(actualFormat == 32) { 6988 // 32 means it is a C long... which is variable length 6989 actualFormat = cast(int) arch_long.sizeof * 8; 6990 } 6991 6992 // then it is just a bit count 6993 byteLength = cast(int) (actualItems * actualFormat / 8); 6994 6995 auto d = new ubyte[](byteLength); 6996 d[] = cast(ubyte[]) data[0 .. byteLength]; 6997 XFree(data); 6998 return d; 6999 } 7000 } 7001 return null; 7002 } 7003 7004 /* defined in the systray spec */ 7005 enum SYSTEM_TRAY_REQUEST_DOCK = 0; 7006 enum SYSTEM_TRAY_BEGIN_MESSAGE = 1; 7007 enum SYSTEM_TRAY_CANCEL_MESSAGE = 2; 7008 7009 7010 /** Global hotkey handler. Simpledisplay will usually create one for you, but if you want to use subclassing 7011 * instead of delegates, you can subclass this, and override `doHandle()` method. */ 7012 public class GlobalHotkey { 7013 KeyEvent key; 7014 void delegate () handler; 7015 7016 void doHandle () { if (handler !is null) handler(); } /// this will be called by hotkey manager 7017 7018 /// Create from initialzed KeyEvent object 7019 this (KeyEvent akey, void delegate () ahandler=null) { 7020 if (akey.key == 0 || !GlobalHotkeyManager.isGoodModifierMask(akey.modifierState)) throw new Exception("invalid global hotkey"); 7021 key = akey; 7022 handler = ahandler; 7023 } 7024 7025 /// Create from emacs-like key name ("C-M-Y", etc.) 7026 this (const(char)[] akey, void delegate () ahandler=null) { 7027 key = KeyEvent.parse(akey); 7028 if (key.key == 0 || !GlobalHotkeyManager.isGoodModifierMask(key.modifierState)) throw new Exception("invalid global hotkey"); 7029 handler = ahandler; 7030 } 7031 7032 } 7033 7034 private extern(C) int XGrabErrorHandler (Display* dpy, XErrorEvent* evt) nothrow @nogc { 7035 //conwriteln("failed to grab key"); 7036 GlobalHotkeyManager.ghfailed = true; 7037 return 0; 7038 } 7039 7040 private extern(C) int XShmErrorHandler (Display* dpy, XErrorEvent* evt) nothrow @nogc { 7041 Image.impl.xshmfailed = true; 7042 return 0; 7043 } 7044 7045 private __gshared int errorHappened; 7046 private extern(C) int adrlogger (Display* dpy, XErrorEvent* evt) nothrow @nogc { 7047 import core.stdc.stdio; 7048 char[265] buffer; 7049 XGetErrorText(dpy, evt.error_code, buffer.ptr, cast(int) buffer.length); 7050 debug printf("X Error %d: %s / Serial: %lld, Opcode: %d.%d, XID: 0x%llx\n", evt.error_code, buffer.ptr, evt.serial, evt.request_code, evt.minor_code, evt.resourceid); 7051 errorHappened = true; 7052 return 0; 7053 } 7054 7055 /++ 7056 Global hotkey manager. It contains static methods to manage global hotkeys. 7057 7058 --- 7059 try { 7060 GlobalHotkeyManager.register("M-H-A", delegate () { hideShowWindows(); }); 7061 } catch (Exception e) { 7062 conwriteln("ERROR registering hotkey!"); 7063 } 7064 EventLoop.get.run(); 7065 --- 7066 7067 The key strings are based on Emacs. In practical terms, 7068 `M` means `alt` and `H` means the Windows logo key. `C` 7069 is `ctrl`. 7070 7071 $(WARNING 7072 This is X-specific right now. If you are on 7073 Windows, try [registerHotKey] instead. 7074 7075 We will probably merge these into a single 7076 interface later. 7077 ) 7078 +/ 7079 public class GlobalHotkeyManager : CapableOfHandlingNativeEvent { 7080 version(X11) { 7081 void recreateAfterDisconnect() { 7082 throw new Exception("NOT IMPLEMENTED"); 7083 } 7084 void discardConnectionState() { 7085 throw new Exception("NOT IMPLEMENTED"); 7086 } 7087 } 7088 7089 private static immutable uint[8] masklist = [ 0, 7090 KeyOrButtonMask.LockMask, 7091 KeyOrButtonMask.Mod2Mask, 7092 KeyOrButtonMask.Mod3Mask, 7093 KeyOrButtonMask.LockMask|KeyOrButtonMask.Mod2Mask, 7094 KeyOrButtonMask.LockMask|KeyOrButtonMask.Mod3Mask, 7095 KeyOrButtonMask.LockMask|KeyOrButtonMask.Mod2Mask|KeyOrButtonMask.Mod3Mask, 7096 KeyOrButtonMask.Mod2Mask|KeyOrButtonMask.Mod3Mask, 7097 ]; 7098 private __gshared GlobalHotkeyManager ghmanager; 7099 private __gshared bool ghfailed = false; 7100 7101 private static bool isGoodModifierMask (uint modmask) pure nothrow @safe @nogc { 7102 if (modmask == 0) return false; 7103 if (modmask&(KeyOrButtonMask.LockMask|KeyOrButtonMask.Mod2Mask|KeyOrButtonMask.Mod3Mask)) return false; 7104 if (modmask&~(KeyOrButtonMask.Mod5Mask-1)) return false; 7105 return true; 7106 } 7107 7108 private static uint cleanupModifiers (uint modmask) pure nothrow @safe @nogc { 7109 modmask &= ~(KeyOrButtonMask.LockMask|KeyOrButtonMask.Mod2Mask|KeyOrButtonMask.Mod3Mask); // remove caps, num, scroll 7110 modmask &= (KeyOrButtonMask.Mod5Mask-1); // and other modifiers 7111 return modmask; 7112 } 7113 7114 private static uint keyEvent2KeyCode() (scope auto ref const KeyEvent ke) { 7115 uint keycode = cast(uint)ke.key; 7116 auto dpy = XDisplayConnection.get; 7117 return XKeysymToKeycode(dpy, keycode); 7118 } 7119 7120 private static ulong keyCode2Hash() (uint keycode, uint modstate) pure nothrow @safe @nogc { return ((cast(ulong)modstate)<<32)|keycode; } 7121 7122 private __gshared GlobalHotkey[ulong] globalHotkeyList; 7123 7124 NativeEventHandler getNativeEventHandler () { 7125 return delegate int (XEvent e) { 7126 if (e.type != EventType.KeyPress) return 1; 7127 auto kev = cast(const(XKeyEvent)*)&e; 7128 auto hash = keyCode2Hash(e.xkey.keycode, cleanupModifiers(e.xkey.state)); 7129 if (auto ghkp = hash in globalHotkeyList) { 7130 try { 7131 ghkp.doHandle(); 7132 } catch (Exception e) { 7133 import core.stdc.stdio : stderr, fprintf; 7134 stderr.fprintf("HOTKEY HANDLER EXCEPTION: %.*s", cast(uint)e.msg.length, e.msg.ptr); 7135 } 7136 } 7137 return 1; 7138 }; 7139 } 7140 7141 private this () { 7142 auto dpy = XDisplayConnection.get; 7143 auto root = RootWindow(dpy, DefaultScreen(dpy)); 7144 CapableOfHandlingNativeEvent.nativeHandleMapping[root] = this; 7145 XDisplayConnection.addRootInput(EventMask.KeyPressMask); 7146 } 7147 7148 /// Register new global hotkey with initialized `GlobalHotkey` object. 7149 /// This function will throw if it failed to register hotkey (i.e. hotkey is invalid or already taken). 7150 static void register (GlobalHotkey gh) { 7151 if (gh is null) return; 7152 if (gh.key.key == 0 || !isGoodModifierMask(gh.key.modifierState)) throw new Exception("invalid global hotkey"); 7153 7154 auto dpy = XDisplayConnection.get; 7155 immutable keycode = keyEvent2KeyCode(gh.key); 7156 7157 auto hash = keyCode2Hash(keycode, gh.key.modifierState); 7158 if (hash in globalHotkeyList) throw new Exception("duplicate global hotkey"); 7159 if (ghmanager is null) ghmanager = new GlobalHotkeyManager(); 7160 XSync(dpy, 0/*False*/); 7161 7162 Window root = RootWindow(dpy, DefaultScreen(dpy)); 7163 XErrorHandler savedErrorHandler = XSetErrorHandler(&XGrabErrorHandler); 7164 ghfailed = false; 7165 foreach (immutable uint ormask; masklist[]) { 7166 XGrabKey(dpy, keycode, gh.key.modifierState|ormask, /*grab_window*/root, /*owner_events*/0/*False*/, GrabMode.GrabModeAsync, GrabMode.GrabModeAsync); 7167 } 7168 XSync(dpy, 0/*False*/); 7169 XSetErrorHandler(savedErrorHandler); 7170 7171 if (ghfailed) { 7172 savedErrorHandler = XSetErrorHandler(&XGrabErrorHandler); 7173 foreach (immutable uint ormask; masklist[]) XUngrabKey(dpy, keycode, gh.key.modifierState|ormask, /*grab_window*/root); 7174 XSync(dpy, 0/*False*/); 7175 XSetErrorHandler(savedErrorHandler); 7176 throw new Exception("cannot register global hotkey"); 7177 } 7178 7179 globalHotkeyList[hash] = gh; 7180 } 7181 7182 /// Ditto 7183 static void register (const(char)[] akey, void delegate () ahandler) { 7184 register(new GlobalHotkey(akey, ahandler)); 7185 } 7186 7187 private static void removeByHash (ulong hash) { 7188 if (auto ghp = hash in globalHotkeyList) { 7189 auto dpy = XDisplayConnection.get; 7190 immutable keycode = keyEvent2KeyCode(ghp.key); 7191 Window root = RootWindow(dpy, DefaultScreen(dpy)); 7192 XSync(dpy, 0/*False*/); 7193 XErrorHandler savedErrorHandler = XSetErrorHandler(&XGrabErrorHandler); 7194 foreach (immutable uint ormask; masklist[]) XUngrabKey(dpy, keycode, ghp.key.modifierState|ormask, /*grab_window*/root); 7195 XSync(dpy, 0/*False*/); 7196 XSetErrorHandler(savedErrorHandler); 7197 globalHotkeyList.remove(hash); 7198 } 7199 } 7200 7201 /// Register new global hotkey with previously used `GlobalHotkey` object. 7202 /// It is safe to unregister unknown or invalid hotkey. 7203 static void unregister (GlobalHotkey gh) { 7204 //TODO: add second AA for faster search? prolly doesn't worth it. 7205 if (gh is null) return; 7206 foreach (const ref kv; globalHotkeyList.byKeyValue) { 7207 if (kv.value is gh) { 7208 removeByHash(kv.key); 7209 return; 7210 } 7211 } 7212 } 7213 7214 /// Ditto. 7215 static void unregister (const(char)[] key) { 7216 auto kev = KeyEvent.parse(key); 7217 immutable keycode = keyEvent2KeyCode(kev); 7218 removeByHash(keyCode2Hash(keycode, kev.modifierState)); 7219 } 7220 } 7221 } 7222 7223 version(Windows) { 7224 /++ 7225 See [SyntheticInput.sendSyntheticInput] instead for cross-platform applications. 7226 7227 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). 7228 +/ 7229 void sendSyntheticInput(wstring s) { 7230 INPUT[] inputs; 7231 inputs.reserve(s.length * 2); 7232 7233 foreach(wchar c; s) { 7234 INPUT input; 7235 input.type = INPUT_KEYBOARD; 7236 input.ki.wScan = c; 7237 input.ki.dwFlags = KEYEVENTF_UNICODE; 7238 inputs ~= input; 7239 7240 input.ki.dwFlags |= KEYEVENTF_KEYUP; 7241 inputs ~= input; 7242 } 7243 7244 if(SendInput(cast(int) inputs.length, inputs.ptr, INPUT.sizeof) != inputs.length) { 7245 throw new WindowsApiException("SendInput", GetLastError()); 7246 } 7247 7248 } 7249 7250 7251 // global hotkey helper function 7252 7253 /// Platform-specific for Windows. Registers a global hotkey. Returns a registration ID. See [GlobalHotkeyManager] for Linux. Maybe some day I will merge these. 7254 int registerHotKey(SimpleWindow window, UINT modifiers, UINT vk, void delegate() handler) @system { 7255 __gshared int hotkeyId = 0; 7256 int id = ++hotkeyId; 7257 if(!RegisterHotKey(window.impl.hwnd, id, modifiers, vk)) 7258 throw new Exception("RegisterHotKey"); 7259 7260 __gshared void delegate()[WPARAM][HWND] handlers; 7261 7262 handlers[window.impl.hwnd][id] = handler; 7263 7264 int delegate(HWND, UINT, WPARAM, LPARAM, out int) oldHandler; 7265 7266 auto nativeEventHandler = delegate int(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam, out int mustReturn) { 7267 switch(msg) { 7268 // http://msdn.microsoft.com/en-us/library/windows/desktop/ms646279%28v=vs.85%29.aspx 7269 case WM_HOTKEY: 7270 if(auto list = hwnd in handlers) { 7271 if(auto h = wParam in *list) { 7272 (*h)(); 7273 return 0; 7274 } 7275 } 7276 goto default; 7277 default: 7278 } 7279 if(oldHandler) 7280 return oldHandler(hwnd, msg, wParam, lParam, mustReturn); 7281 return 1; // pass it on 7282 }; 7283 7284 if(window.handleNativeEvent.funcptr !is nativeEventHandler.funcptr) { 7285 oldHandler = window.handleNativeEvent; 7286 window.handleNativeEvent = nativeEventHandler; 7287 } 7288 7289 return id; 7290 } 7291 7292 /// Platform-specific for Windows. Unregisters a key. The id is the value returned by [registerHotKey]. 7293 void unregisterHotKey(SimpleWindow window, int id) { 7294 if(!UnregisterHotKey(window.impl.hwnd, id)) 7295 throw new WindowsApiException("UnregisterHotKey", GetLastError()); 7296 } 7297 } 7298 7299 version (X11) { 7300 pragma(lib, "dl"); 7301 import core.sys.posix.dlfcn; 7302 } 7303 7304 /++ 7305 Allows for sending synthetic input to the X server via the Xtst 7306 extension or on Windows using SendInput. 7307 7308 Please remember user input is meant to be user - don't use this 7309 if you have some other alternative! 7310 7311 History: 7312 Added May 17, 2020 with the X implementation. 7313 7314 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.) 7315 Bugs: 7316 All methods on OSX Cocoa will throw not yet implemented exceptions. 7317 +/ 7318 struct SyntheticInput { 7319 @disable this(); 7320 7321 private int* refcount; 7322 7323 version(X11) { 7324 private void* lib; 7325 7326 private extern(C) { 7327 void function(Display*, uint keycode, bool press, arch_ulong delay) XTestFakeKeyEvent; 7328 void function(Display*, uint button, bool press, arch_ulong delay) XTestFakeButtonEvent; 7329 } 7330 } 7331 7332 /// The dummy param must be 0. 7333 this(int dummy) { 7334 version(X11) { 7335 lib = dlopen("libXtst.so", RTLD_NOW); 7336 if(lib is null) 7337 throw new Exception("cannot load xtest lib extension"); 7338 scope(failure) 7339 dlclose(lib); 7340 7341 XTestFakeButtonEvent = cast(typeof(XTestFakeButtonEvent)) dlsym(lib, "XTestFakeButtonEvent"); 7342 XTestFakeKeyEvent = cast(typeof(XTestFakeKeyEvent)) dlsym(lib, "XTestFakeKeyEvent"); 7343 7344 if(XTestFakeKeyEvent is null) 7345 throw new Exception("No XTestFakeKeyEvent"); 7346 if(XTestFakeButtonEvent is null) 7347 throw new Exception("No XTestFakeButtonEvent"); 7348 } 7349 7350 refcount = new int; 7351 *refcount = 1; 7352 } 7353 7354 this(this) { 7355 if(refcount) 7356 *refcount += 1; 7357 } 7358 7359 ~this() { 7360 if(refcount) { 7361 *refcount -= 1; 7362 if(*refcount == 0) 7363 // I commented this because if I close the lib before 7364 // XCloseDisplay, it is liable to segfault... so just 7365 // gonna keep it loaded if it is loaded, no big deal 7366 // anyway. 7367 {} // dlclose(lib); 7368 } 7369 } 7370 7371 /++ 7372 Simulates typing a string into the keyboard. 7373 7374 Bugs: 7375 On X11, this ONLY works with basic ascii! On Windows, it can handle more. 7376 7377 Not implemented except on Windows and X11. 7378 +/ 7379 void sendSyntheticInput(string s) { 7380 version(Windows) { 7381 INPUT[] inputs; 7382 inputs.reserve(s.length * 2); 7383 7384 auto ei = GetMessageExtraInfo(); 7385 7386 foreach(wchar c; s) { 7387 INPUT input; 7388 input.type = INPUT_KEYBOARD; 7389 input.ki.wScan = c; 7390 input.ki.dwFlags = KEYEVENTF_UNICODE; 7391 input.ki.dwExtraInfo = ei; 7392 inputs ~= input; 7393 7394 input.ki.dwFlags |= KEYEVENTF_KEYUP; 7395 inputs ~= input; 7396 } 7397 7398 if(SendInput(cast(int) inputs.length, inputs.ptr, INPUT.sizeof) != inputs.length) { 7399 throw new WindowsApiException("SendInput", GetLastError()); 7400 } 7401 } else version(X11) { 7402 int delay = 0; 7403 foreach(ch; s) { 7404 pressKey(cast(Key) ch, true, delay); 7405 pressKey(cast(Key) ch, false, delay); 7406 delay += 5; 7407 } 7408 } else throw new NotYetImplementedException(); 7409 } 7410 7411 /++ 7412 Sends a fake press or release key event. 7413 7414 Please note you need to call [flushGui] or return to the event loop for this to actually be sent on X11. 7415 7416 Bugs: 7417 The `delay` parameter is not implemented yet on Windows. 7418 7419 Not implemented except on Windows and X11. 7420 +/ 7421 void pressKey(Key key, bool pressed, int delay = 0) { 7422 version(Windows) { 7423 INPUT input; 7424 input.type = INPUT_KEYBOARD; 7425 input.ki.wVk = cast(ushort) key; 7426 7427 input.ki.dwFlags = pressed ? 0 : KEYEVENTF_KEYUP; 7428 input.ki.dwExtraInfo = GetMessageExtraInfo(); 7429 7430 if(SendInput(1, &input, INPUT.sizeof) != 1) { 7431 throw new WindowsApiException("SendInput", GetLastError()); 7432 } 7433 } else version(X11) { 7434 XTestFakeKeyEvent(XDisplayConnection.get, XKeysymToKeycode(XDisplayConnection.get, key), pressed, delay + pressed ? 0 : 5); 7435 } else throw new NotYetImplementedException(); 7436 } 7437 7438 /++ 7439 Sends a fake mouse button press or release event. 7440 7441 Please note you need to call [flushGui] or return to the event loop for this to actually be sent on X11. 7442 7443 `pressed` param must be `true` if button is `wheelUp` or `wheelDown`. 7444 7445 Bugs: 7446 The `delay` parameter is not implemented yet on Windows. 7447 7448 The backButton and forwardButton will throw NotYetImplementedException on Windows. 7449 7450 All arguments will throw NotYetImplementedException on OSX Cocoa. 7451 +/ 7452 void pressMouseButton(MouseButton button, bool pressed, int delay = 0) { 7453 version(Windows) { 7454 INPUT input; 7455 input.type = INPUT_MOUSE; 7456 input.mi.dwExtraInfo = GetMessageExtraInfo(); 7457 7458 // input.mi.mouseData for a wheel event 7459 7460 switch(button) { 7461 case MouseButton.left: input.mi.dwFlags = pressed ? MOUSEEVENTF_LEFTDOWN : MOUSEEVENTF_LEFTUP; break; 7462 case MouseButton.middle: input.mi.dwFlags = pressed ? MOUSEEVENTF_MIDDLEDOWN : MOUSEEVENTF_MIDDLEUP; break; 7463 case MouseButton.right: input.mi.dwFlags = pressed ? MOUSEEVENTF_RIGHTDOWN : MOUSEEVENTF_RIGHTUP; break; 7464 case MouseButton.wheelUp: 7465 case MouseButton.wheelDown: 7466 input.mi.dwFlags = MOUSEEVENTF_WHEEL; 7467 input.mi.mouseData = button == MouseButton.wheelUp ? 120 : -120; 7468 break; 7469 case MouseButton.backButton: throw new NotYetImplementedException(); 7470 case MouseButton.forwardButton: throw new NotYetImplementedException(); 7471 default: 7472 } 7473 7474 if(SendInput(1, &input, INPUT.sizeof) != 1) { 7475 throw new WindowsApiException("SendInput", GetLastError()); 7476 } 7477 } else version(X11) { 7478 int btn; 7479 7480 switch(button) { 7481 case MouseButton.left: btn = 1; break; 7482 case MouseButton.middle: btn = 2; break; 7483 case MouseButton.right: btn = 3; break; 7484 case MouseButton.wheelUp: btn = 4; break; 7485 case MouseButton.wheelDown: btn = 5; break; 7486 case MouseButton.backButton: btn = 8; break; 7487 case MouseButton.forwardButton: btn = 9; break; 7488 default: 7489 } 7490 7491 assert(btn); 7492 7493 XTestFakeButtonEvent(XDisplayConnection.get, btn, pressed, delay); 7494 } else throw new NotYetImplementedException(); 7495 } 7496 7497 /// 7498 static void moveMouseArrowBy(int dx, int dy) { 7499 version(Windows) { 7500 INPUT input; 7501 input.type = INPUT_MOUSE; 7502 input.mi.dwExtraInfo = GetMessageExtraInfo(); 7503 input.mi.dx = dx; 7504 input.mi.dy = dy; 7505 input.mi.dwFlags = MOUSEEVENTF_MOVE; 7506 7507 if(SendInput(1, &input, INPUT.sizeof) != 1) { 7508 throw new WindowsApiException("SendInput", GetLastError()); 7509 } 7510 } else version(X11) { 7511 auto disp = XDisplayConnection.get(); 7512 XWarpPointer(disp, None, None, 0, 0, 0, 0, dx, dy); 7513 XFlush(disp); 7514 } else throw new NotYetImplementedException(); 7515 } 7516 7517 /// 7518 static void moveMouseArrowTo(int x, int y) { 7519 version(Windows) { 7520 INPUT input; 7521 input.type = INPUT_MOUSE; 7522 input.mi.dwExtraInfo = GetMessageExtraInfo(); 7523 input.mi.dx = x; 7524 input.mi.dy = y; 7525 input.mi.dwFlags = MOUSEEVENTF_MOVE | MOUSEEVENTF_ABSOLUTE; 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 auto root = RootWindow(disp, DefaultScreen(disp)); 7533 XWarpPointer(disp, None, root, 0, 0, 0, 0, x, y); 7534 XFlush(disp); 7535 } else throw new NotYetImplementedException(); 7536 } 7537 } 7538 7539 7540 7541 /++ 7542 [ScreenPainter] operations can use different operations to combine the color with the color on screen. 7543 7544 See_Also: 7545 $(LIST 7546 *[ScreenPainter] 7547 *[ScreenPainter.rasterOp] 7548 ) 7549 +/ 7550 enum RasterOp { 7551 normal, /// Replaces the pixel. 7552 xor, /// Uses bitwise xor to draw. 7553 } 7554 7555 // being phobos-free keeps the size WAY down 7556 private const(char)* toStringz(string s) { return (s ~ '\0').ptr; } 7557 package(arsd) const(wchar)* toWStringz(wstring s) { return (s ~ '\0').ptr; } 7558 package(arsd) const(wchar)* toWStringz(string s) { 7559 wstring r; 7560 foreach(dchar c; s) 7561 r ~= c; 7562 r ~= '\0'; 7563 return r.ptr; 7564 } 7565 private string[] split(in void[] a, char c) { 7566 string[] ret; 7567 size_t previous = 0; 7568 foreach(i, char ch; cast(ubyte[]) a) { 7569 if(ch == c) { 7570 ret ~= cast(string) a[previous .. i]; 7571 previous = i + 1; 7572 } 7573 } 7574 if(previous != a.length) 7575 ret ~= cast(string) a[previous .. $]; 7576 return ret; 7577 } 7578 7579 version(without_opengl) { 7580 enum OpenGlOptions { 7581 no, 7582 } 7583 } else { 7584 /++ 7585 Determines if you want an OpenGL context created on the new window. 7586 7587 7588 See more: [#topics-3d|in the 3d topic]. 7589 7590 --- 7591 import arsd.simpledisplay; 7592 void main() { 7593 auto window = new SimpleWindow(500, 500, "OpenGL Test", OpenGlOptions.yes); 7594 7595 // Set up the matrix 7596 window.setAsCurrentOpenGlContext(); // make this window active 7597 7598 // This is called on each frame, we will draw our scene 7599 window.redrawOpenGlScene = delegate() { 7600 7601 }; 7602 7603 window.eventLoop(0); 7604 } 7605 --- 7606 +/ 7607 enum OpenGlOptions { 7608 no, /// No OpenGL context is created 7609 yes, /// Yes, create an OpenGL context 7610 } 7611 7612 version(X11) { 7613 static if (!SdpyIsUsingIVGLBinds) { 7614 7615 7616 struct __GLXFBConfigRec {} 7617 alias GLXFBConfig = __GLXFBConfigRec*; 7618 7619 //pragma(lib, "GL"); 7620 //pragma(lib, "GLU"); 7621 interface GLX { 7622 extern(C) nothrow @nogc { 7623 XVisualInfo* glXChooseVisual(Display *dpy, int screen, 7624 const int *attrib_list); 7625 7626 void glXCopyContext(Display *dpy, GLXContext src, 7627 GLXContext dst, arch_ulong mask); 7628 7629 GLXContext glXCreateContext(Display *dpy, XVisualInfo *vis, 7630 GLXContext share_list, Bool direct); 7631 7632 GLXPixmap glXCreateGLXPixmap(Display *dpy, XVisualInfo *vis, 7633 Pixmap pixmap); 7634 7635 void glXDestroyContext(Display *dpy, GLXContext ctx); 7636 7637 void glXDestroyGLXPixmap(Display *dpy, GLXPixmap pix); 7638 7639 int glXGetConfig(Display *dpy, XVisualInfo *vis, 7640 int attrib, int *value); 7641 7642 GLXContext glXGetCurrentContext(); 7643 7644 GLXDrawable glXGetCurrentDrawable(); 7645 7646 Bool glXIsDirect(Display *dpy, GLXContext ctx); 7647 7648 Bool glXMakeCurrent(Display *dpy, GLXDrawable drawable, 7649 GLXContext ctx); 7650 7651 Bool glXQueryExtension(Display *dpy, int *error_base, int *event_base); 7652 7653 Bool glXQueryVersion(Display *dpy, int *major, int *minor); 7654 7655 void glXSwapBuffers(Display *dpy, GLXDrawable drawable); 7656 7657 void glXUseXFont(Font font, int first, int count, int list_base); 7658 7659 void glXWaitGL(); 7660 7661 void glXWaitX(); 7662 7663 7664 GLXFBConfig* glXChooseFBConfig (Display*, int, int*, int*); 7665 int glXGetFBConfigAttrib (Display*, GLXFBConfig, int, int*); 7666 XVisualInfo* glXGetVisualFromFBConfig (Display*, GLXFBConfig); 7667 7668 char* glXQueryExtensionsString (Display*, int); 7669 void* glXGetProcAddress (const(char)*); 7670 7671 } 7672 } 7673 7674 version(OSX) 7675 mixin DynamicLoad!(GLX, "GL", 0, openGlLibrariesSuccessfullyLoaded) glx; 7676 else 7677 mixin DynamicLoad!(GLX, "GLX", 0, openGlLibrariesSuccessfullyLoaded) glx; 7678 shared static this() { 7679 glx.loadDynamicLibrary(); 7680 } 7681 7682 alias glbindGetProcAddress = glXGetProcAddress; 7683 } 7684 } else version(Windows) { 7685 /* it is done below by interface GL */ 7686 } else 7687 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."); 7688 } 7689 7690 deprecated("Sorry, I misspelled it in the first version! Use `Resizability` instead.") 7691 alias Resizablity = Resizability; 7692 7693 /// When you create a SimpleWindow, you can see its resizability to be one of these via the constructor... 7694 enum Resizability { 7695 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. 7696 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. 7697 /++ 7698 $(PITFALL 7699 Planned for the future but not implemented. 7700 ) 7701 7702 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. 7703 7704 History: 7705 Added November 11, 2022, but not yet implemented and may not be for some time. 7706 +/ 7707 /*@__future*/ allowResizingMaintainingAspectRatio, 7708 /++ 7709 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. 7710 7711 History: 7712 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. 7713 7714 Your programs should not be affected, as they will continue to function as if the user simply never resized the window at all. 7715 +/ 7716 automaticallyScaleIfPossible, 7717 } 7718 /// ditto 7719 alias Resizeability = Resizability; 7720 7721 7722 /++ 7723 Alignment for [ScreenPainter.drawText]. Left, Center, or Right may be combined with VerticalTop, VerticalCenter, or VerticalBottom via bitwise or. 7724 +/ 7725 enum TextAlignment : uint { 7726 Left = 0, /// 7727 Center = 1, /// 7728 Right = 2, /// 7729 7730 VerticalTop = 0, /// 7731 VerticalCenter = 4, /// 7732 VerticalBottom = 8, /// 7733 } 7734 7735 public import arsd.color; // no longer stand alone... :-( but i need a common type for this to work with images easily. 7736 alias Rectangle = arsd.color.Rectangle; 7737 7738 7739 /++ 7740 Keyboard press and release events. 7741 +/ 7742 struct KeyEvent { 7743 /// see table below. Always use the symbolic names, even for ASCII characters, since the actual numbers vary across platforms. See [Key] 7744 Key key; 7745 ubyte hardwareCode; /// A platform and hardware specific code for the key 7746 bool pressed; /// true if the key was just pressed, false if it was just released. note: released events aren't always sent... 7747 7748 deprecated("This never actually worked anyway, you should do a character event handler instead.") dchar character; 7749 7750 uint modifierState; /// see enum [ModifierState]. They are bitwise combined together. 7751 7752 SimpleWindow window; /// associated Window 7753 7754 /++ 7755 A view into the upcoming buffer holding coming character events that are sent if and only if neither 7756 the alt or super modifier keys are pressed (check this with `!(modifierState & (ModifierState.window | ModifierState.alt))` 7757 to predict if char events are actually coming.. 7758 7759 Only available on X systems since this information is not given ahead of time elsewhere. 7760 (Well, you COULD probably dig it up, but as far as I know right now, it isn't terribly pretty.) 7761 7762 I'm adding this because it is useful to the terminal emulator, but given its platform specificness 7763 and potential quirks I'd recommend avoiding it. 7764 7765 History: 7766 Added April 26, 2021 (dub v9.5) 7767 +/ 7768 version(X11) 7769 dchar[] charsPossible; 7770 7771 // convert key event to simplified string representation a-la emacs 7772 const(char)[] toStrBuf(bool growdest=false) (char[] dest) const nothrow @trusted { 7773 uint dpos = 0; 7774 void put (const(char)[] s...) nothrow @trusted { 7775 static if (growdest) { 7776 foreach (char ch; s) if (dpos < dest.length) dest.ptr[dpos++] = ch; else { dest ~= ch; ++dpos; } 7777 } else { 7778 foreach (char ch; s) if (dpos < dest.length) dest.ptr[dpos++] = ch; 7779 } 7780 } 7781 7782 void putMod (ModifierState mod, Key key, string text) nothrow @trusted { 7783 if ((this.modifierState&mod) != 0 && (this.pressed || this.key != key)) put(text); 7784 } 7785 7786 if (!this.key && !(this.modifierState&(ModifierState.ctrl|ModifierState.alt|ModifierState.shift|ModifierState.windows))) return null; 7787 7788 // put modifiers 7789 // releasing modifier keys can produce bizarre things like "Ctrl+Ctrl", so hack around it 7790 putMod(ModifierState.ctrl, Key.Ctrl, "Ctrl+"); 7791 putMod(ModifierState.alt, Key.Alt, "Alt+"); 7792 putMod(ModifierState.windows, Key.Shift, "Windows+"); 7793 putMod(ModifierState.shift, Key.Shift, "Shift+"); 7794 7795 if (this.key) { 7796 foreach (string kn; __traits(allMembers, Key)) { 7797 if (this.key == __traits(getMember, Key, kn)) { 7798 // HACK! 7799 static if (kn == "N0") put("0"); 7800 else static if (kn == "N1") put("1"); 7801 else static if (kn == "N2") put("2"); 7802 else static if (kn == "N3") put("3"); 7803 else static if (kn == "N4") put("4"); 7804 else static if (kn == "N5") put("5"); 7805 else static if (kn == "N6") put("6"); 7806 else static if (kn == "N7") put("7"); 7807 else static if (kn == "N8") put("8"); 7808 else static if (kn == "N9") put("9"); 7809 else put(kn); 7810 return dest[0..dpos]; 7811 } 7812 } 7813 put("Unknown"); 7814 } else { 7815 if (dpos && dest[dpos-1] == '+') --dpos; 7816 } 7817 return dest[0..dpos]; 7818 } 7819 7820 string toStr() () { return cast(string)toStrBuf!true(null); } // it is safe to cast here 7821 7822 /** Parse string into key name with modifiers. It accepts things like: 7823 * 7824 * C-H-1 -- emacs style (ctrl, and windows, and 1) 7825 * 7826 * Ctrl+Win+1 -- windows style 7827 * 7828 * Ctrl-Win-1 -- '-' is a valid delimiter too 7829 * 7830 * Ctrl Win 1 -- and space 7831 * 7832 * and even "Win + 1 + Ctrl". 7833 */ 7834 static KeyEvent parse (const(char)[] name, bool* ignoreModsOut=null, int* updown=null) nothrow @trusted @nogc { 7835 auto nanchor = name; // keep it anchored, 'cause `name` may have NO_INTERIOR set 7836 7837 // remove trailing spaces 7838 while (name.length && name[$-1] <= ' ') name = name[0..$-1]; 7839 7840 // tokens delimited by blank, '+', or '-' 7841 // null on eol 7842 const(char)[] getToken () nothrow @trusted @nogc { 7843 // remove leading spaces and delimiters 7844 while (name.length && (name[0] <= ' ' || name[0] == '+' || name[0] == '-')) name = name[1..$]; 7845 if (name.length == 0) return null; // oops, no more tokens 7846 // get token 7847 size_t epos = 0; 7848 while (epos < name.length && name[epos] > ' ' && name[epos] != '+' && name[epos] != '-') ++epos; 7849 assert(epos > 0 && epos <= name.length); 7850 auto res = name[0..epos]; 7851 name = name[epos..$]; 7852 return res; 7853 } 7854 7855 static bool strEquCI (const(char)[] s0, const(char)[] s1) pure nothrow @trusted @nogc { 7856 if (s0.length != s1.length) return false; 7857 foreach (immutable ci, char c0; s0) { 7858 if (c0 >= 'A' && c0 <= 'Z') c0 += 32; // poor man's tolower 7859 char c1 = s1[ci]; 7860 if (c1 >= 'A' && c1 <= 'Z') c1 += 32; // poor man's tolower 7861 if (c0 != c1) return false; 7862 } 7863 return true; 7864 } 7865 7866 if (ignoreModsOut !is null) *ignoreModsOut = false; 7867 if (updown !is null) *updown = -1; 7868 KeyEvent res; 7869 res.key = cast(Key)0; // just in case 7870 const(char)[] tk, tkn; // last token 7871 bool allowEmascStyle = true; 7872 bool ignoreModifiers = false; 7873 tokenloop: for (;;) { 7874 tk = tkn; 7875 tkn = getToken(); 7876 //k8: yay, i took "Bloody Mess" trait from Fallout! 7877 if (tkn.length != 0 && tk.length == 0) { tk = tkn; continue tokenloop; } 7878 if (tkn.length == 0 && tk.length == 0) break; // no more tokens 7879 if (allowEmascStyle && tkn.length != 0) { 7880 if (tk.length == 1) { 7881 char mdc = tk[0]; 7882 if (mdc >= 'a' && mdc <= 'z') mdc -= 32; // poor man's toupper() 7883 if (mdc == 'C' && (res.modifierState&ModifierState.ctrl) == 0) {res.modifierState |= ModifierState.ctrl; continue tokenloop; } 7884 if (mdc == 'M' && (res.modifierState&ModifierState.alt) == 0) { res.modifierState |= ModifierState.alt; continue tokenloop; } 7885 if (mdc == 'H' && (res.modifierState&ModifierState.windows) == 0) { res.modifierState |= ModifierState.windows; continue tokenloop; } 7886 if (mdc == 'S' && (res.modifierState&ModifierState.shift) == 0) { res.modifierState |= ModifierState.shift; continue tokenloop; } 7887 if (mdc == '*') { ignoreModifiers = true; continue tokenloop; } 7888 if (mdc == 'U' || mdc == 'R') { if (updown !is null) *updown = 0; continue tokenloop; } 7889 if (mdc == 'D' || mdc == 'P') { if (updown !is null) *updown = 1; continue tokenloop; } 7890 } 7891 } 7892 allowEmascStyle = false; 7893 if (strEquCI(tk, "Ctrl")) { res.modifierState |= ModifierState.ctrl; continue tokenloop; } 7894 if (strEquCI(tk, "Alt")) { res.modifierState |= ModifierState.alt; continue tokenloop; } 7895 if (strEquCI(tk, "Win") || strEquCI(tk, "Windows")) { res.modifierState |= ModifierState.windows; continue tokenloop; } 7896 if (strEquCI(tk, "Shift")) { res.modifierState |= ModifierState.shift; continue tokenloop; } 7897 if (strEquCI(tk, "Release")) { if (updown !is null) *updown = 0; continue tokenloop; } 7898 if (strEquCI(tk, "Press")) { if (updown !is null) *updown = 1; continue tokenloop; } 7899 if (tk == "*") { ignoreModifiers = true; continue tokenloop; } 7900 if (tk.length == 0) continue; 7901 // try key name 7902 if (res.key == 0) { 7903 // little hack 7904 if (tk.length == 1 && tk[0] >= '0' && tk[0] <= '9') { 7905 final switch (tk[0]) { 7906 case '0': tk = "N0"; break; 7907 case '1': tk = "N1"; break; 7908 case '2': tk = "N2"; break; 7909 case '3': tk = "N3"; break; 7910 case '4': tk = "N4"; break; 7911 case '5': tk = "N5"; break; 7912 case '6': tk = "N6"; break; 7913 case '7': tk = "N7"; break; 7914 case '8': tk = "N8"; break; 7915 case '9': tk = "N9"; break; 7916 } 7917 } 7918 foreach (string kn; __traits(allMembers, Key)) { 7919 if (strEquCI(tk, kn)) { res.key = __traits(getMember, Key, kn); continue tokenloop; } 7920 } 7921 } 7922 // unknown or duplicate key name, get out of here 7923 break; 7924 } 7925 if (ignoreModsOut !is null) *ignoreModsOut = ignoreModifiers; 7926 return res; // something 7927 } 7928 7929 bool opEquals() (const(char)[] name) const nothrow @trusted @nogc { 7930 enum modmask = (ModifierState.ctrl|ModifierState.alt|ModifierState.shift|ModifierState.windows); 7931 void doModKey (ref uint mask, ref Key kk, Key k, ModifierState mst) { 7932 if (kk == k) { mask |= mst; kk = cast(Key)0; } 7933 } 7934 bool ignoreMods; 7935 int updown; 7936 auto ke = KeyEvent.parse(name, &ignoreMods, &updown); 7937 if ((updown == 0 && this.pressed) || (updown == 1 && !this.pressed)) return false; 7938 if (this.key != ke.key) { 7939 // things like "ctrl+alt" are complicated 7940 uint tkm = this.modifierState&modmask; 7941 uint kkm = ke.modifierState&modmask; 7942 Key tk = this.key; 7943 // ke 7944 doModKey(kkm, ke.key, Key.Ctrl, ModifierState.ctrl); 7945 doModKey(kkm, ke.key, Key.Alt, ModifierState.alt); 7946 doModKey(kkm, ke.key, Key.Windows, ModifierState.windows); 7947 doModKey(kkm, ke.key, Key.Shift, ModifierState.shift); 7948 // this 7949 doModKey(tkm, tk, Key.Ctrl, ModifierState.ctrl); 7950 doModKey(tkm, tk, Key.Alt, ModifierState.alt); 7951 doModKey(tkm, tk, Key.Windows, ModifierState.windows); 7952 doModKey(tkm, tk, Key.Shift, ModifierState.shift); 7953 return (tk == ke.key && tkm == kkm); 7954 } 7955 return (ignoreMods || ((this.modifierState&modmask) == (ke.modifierState&modmask))); 7956 } 7957 } 7958 7959 /// Sets the application name. 7960 @property string ApplicationName(string name) { 7961 return _applicationName = name; 7962 } 7963 7964 string _applicationName; 7965 7966 /// ditto 7967 @property string ApplicationName() { 7968 if(_applicationName is null) { 7969 import core.runtime; 7970 return Runtime.args[0]; 7971 } 7972 return _applicationName; 7973 } 7974 7975 7976 /// Type of a [MouseEvent]. 7977 enum MouseEventType : int { 7978 motion = 0, /// The mouse moved inside the window 7979 buttonPressed = 1, /// A mouse button was pressed or the wheel was spun 7980 buttonReleased = 2, /// A mouse button was released 7981 } 7982 7983 // FIXME: mouse move should be distinct from presses+releases, so we can avoid subscribing to those events in X unnecessarily 7984 /++ 7985 Listen for this on your event listeners if you are interested in mouse action. 7986 7987 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. 7988 7989 Examples: 7990 7991 This will draw boxes on the window with the mouse as you hold the left button. 7992 --- 7993 import arsd.simpledisplay; 7994 7995 void main() { 7996 auto window = new SimpleWindow(); 7997 7998 window.eventLoop(0, 7999 (MouseEvent ev) { 8000 if(ev.modifierState & ModifierState.leftButtonDown) { 8001 auto painter = window.draw(); 8002 painter.fillColor = Color.red; 8003 painter.outlineColor = Color.black; 8004 painter.drawRectangle(Point(ev.x / 16 * 16, ev.y / 16 * 16), 16, 16); 8005 } 8006 } 8007 ); 8008 } 8009 --- 8010 +/ 8011 struct MouseEvent { 8012 MouseEventType type; /// movement, press, release, double click. See [MouseEventType] 8013 8014 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. 8015 int y; /// Current Y position of the cursor when the event fired. 8016 8017 int dx; /// Change in X position since last report 8018 int dy; /// Change in Y position since last report 8019 8020 MouseButton button; /// See [MouseButton] 8021 int modifierState; /// See [ModifierState] 8022 8023 version(X11) 8024 private Time timestamp; 8025 8026 /// Returns a linear representation of mouse button, 8027 /// for use with static arrays. Guaranteed to be >= 0 && <= 15 8028 /// 8029 /// Its implementation is based on range-limiting `core.bitop.bsf(button) + 1`. 8030 @property ubyte buttonLinear() const { 8031 import core.bitop; 8032 if(button == 0) 8033 return 0; 8034 return (bsf(button) + 1) & 0b1111; 8035 } 8036 8037 bool doubleClick; /// was it a double click? Only set on type == [MouseEventType.buttonPressed] 8038 8039 SimpleWindow window; /// The window in which the event happened. 8040 8041 Point globalCoordinates() { 8042 Point p; 8043 if(window is null) 8044 throw new Exception("wtf"); 8045 static if(UsingSimpledisplayX11) { 8046 Window child; 8047 XTranslateCoordinates( 8048 XDisplayConnection.get, 8049 window.impl.window, 8050 RootWindow(XDisplayConnection.get, DefaultScreen(XDisplayConnection.get)), 8051 x, y, &p.x, &p.y, &child); 8052 return p; 8053 } else version(Windows) { 8054 POINT[1] points; 8055 points[0].x = x; 8056 points[0].y = y; 8057 MapWindowPoints( 8058 window.impl.hwnd, 8059 null, 8060 points.ptr, 8061 points.length 8062 ); 8063 p.x = points[0].x; 8064 p.y = points[0].y; 8065 8066 return p; 8067 } else version(OSXCocoa) { 8068 throw new NotYetImplementedException(); 8069 } else version(Emscripten) { 8070 throw new NotYetImplementedException(); 8071 } else static assert(0); 8072 } 8073 8074 bool opEquals() (const(char)[] str) pure nothrow @trusted @nogc { return equStr(this, str); } 8075 8076 /** 8077 can contain emacs-like modifier prefix 8078 case-insensitive names: 8079 lmbX/leftX 8080 rmbX/rightX 8081 mmbX/middleX 8082 wheelX 8083 motion (no prefix allowed) 8084 'X' is either "up" or "down" (or "-up"/"-down"); if omited, means "down" 8085 */ 8086 static bool equStr() (scope auto ref const MouseEvent event, const(char)[] str) pure nothrow @trusted @nogc { 8087 if (str.length == 0) return false; // just in case 8088 debug(arsd_mevent_strcmp) { import iv.cmdcon; conwriteln("str=<", str, ">"); } 8089 enum Flag : uint { Up = 0x8000_0000U, Down = 0x4000_0000U, Any = 0x1000_0000U } 8090 auto anchor = str; 8091 uint mods = 0; // uint.max == any 8092 // interesting bits in kmod 8093 uint kmodmask = 8094 ModifierState.shift| 8095 ModifierState.ctrl| 8096 ModifierState.alt| 8097 ModifierState.windows| 8098 ModifierState.leftButtonDown| 8099 ModifierState.middleButtonDown| 8100 ModifierState.rightButtonDown| 8101 0; 8102 uint lastButt = uint.max; // otherwise, bit 31 means "down" 8103 bool wasButtons = false; 8104 while (str.length) { 8105 if (str.ptr[0] <= ' ') { 8106 while (str.length && str.ptr[0] <= ' ') str = str[1..$]; 8107 continue; 8108 } 8109 // one-letter modifier? 8110 if (str.length >= 2 && str.ptr[1] == '-') { 8111 switch (str.ptr[0]) { 8112 case '*': // "any" modifier (cannot be undone) 8113 mods = mods.max; 8114 break; 8115 case 'C': case 'c': // emacs "ctrl" 8116 if (mods != mods.max) mods |= ModifierState.ctrl; 8117 break; 8118 case 'M': case 'm': // emacs "meta" 8119 if (mods != mods.max) mods |= ModifierState.alt; 8120 break; 8121 case 'S': case 's': // emacs "shift" 8122 if (mods != mods.max) mods |= ModifierState.shift; 8123 break; 8124 case 'H': case 'h': // emacs "hyper" (aka winkey) 8125 if (mods != mods.max) mods |= ModifierState.windows; 8126 break; 8127 default: 8128 return false; // unknown modifier 8129 } 8130 str = str[2..$]; 8131 continue; 8132 } 8133 // word 8134 char[16] buf = void; // locased 8135 auto wep = 0; 8136 while (str.length) { 8137 immutable char ch = str.ptr[0]; 8138 if (ch <= ' ' || ch == '-') break; 8139 str = str[1..$]; 8140 if (wep > buf.length) return false; // too long 8141 if (ch >= 'A' && ch <= 'Z') buf.ptr[wep++] = cast(char)(ch+32); // poor man tolower 8142 else if (ch >= 'a' && ch <= 'z') buf.ptr[wep++] = ch; 8143 else return false; // invalid char 8144 } 8145 if (wep == 0) return false; // just in case 8146 uint bnum; 8147 enum UpDown { None = -1, Up, Down, Any } 8148 auto updown = UpDown.None; // 0: up; 1: down 8149 switch (buf[0..wep]) { 8150 // left button 8151 case "lmbup": case "leftup": updown = UpDown.Up; goto case "lmb"; 8152 case "lmbdown": case "leftdown": updown = UpDown.Down; goto case "lmb"; 8153 case "lmbany": case "leftany": updown = UpDown.Any; goto case "lmb"; 8154 case "lmb": case "left": bnum = 0; break; 8155 // middle button 8156 case "mmbup": case "middleup": updown = UpDown.Up; goto case "mmb"; 8157 case "mmbdown": case "middledown": updown = UpDown.Down; goto case "mmb"; 8158 case "mmbany": case "middleany": updown = UpDown.Any; goto case "mmb"; 8159 case "mmb": case "middle": bnum = 1; break; 8160 // right button 8161 case "rmbup": case "rightup": updown = UpDown.Up; goto case "rmb"; 8162 case "rmbdown": case "rightdown": updown = UpDown.Down; goto case "rmb"; 8163 case "rmbany": case "rightany": updown = UpDown.Any; goto case "rmb"; 8164 case "rmb": case "right": bnum = 2; break; 8165 // wheel 8166 case "wheelup": updown = UpDown.Up; goto case "wheel"; 8167 case "wheeldown": updown = UpDown.Down; goto case "wheel"; 8168 case "wheelany": updown = UpDown.Any; goto case "wheel"; 8169 case "wheel": bnum = 3; break; 8170 // motion 8171 case "motion": bnum = 7; break; 8172 // unknown 8173 default: return false; 8174 } 8175 debug(arsd_mevent_strcmp) { import iv.cmdcon; conprintfln(" 0: mods=0x%08x; bnum=%u; updown=%s [%s]", mods, bnum, updown, str); } 8176 // parse possible "-up" or "-down" 8177 if (updown == UpDown.None && bnum < 7 && str.length > 0 && str.ptr[0] == '-') { 8178 wep = 0; 8179 foreach (immutable idx, immutable char ch; str[1..$]) { 8180 if (ch <= ' ' || ch == '-') break; 8181 assert(idx == wep); // for now; trick 8182 if (wep > buf.length) { wep = 0; break; } // too long 8183 if (ch >= 'A' && ch <= 'Z') buf.ptr[wep++] = cast(char)(ch+32); // poor man tolower 8184 else if (ch >= 'a' && ch <= 'z') buf.ptr[wep++] = ch; 8185 else { wep = 0; break; } // invalid char 8186 } 8187 if (wep == 2 && buf[0..wep] == "up") updown = UpDown.Up; 8188 else if (wep == 4 && buf[0..wep] == "down") updown = UpDown.Down; 8189 else if (wep == 3 && buf[0..wep] == "any") updown = UpDown.Any; 8190 // remove parsed part 8191 if (updown != UpDown.None) str = str[wep+1..$]; 8192 } 8193 if (updown == UpDown.None) { 8194 updown = UpDown.Down; 8195 } 8196 wasButtons = wasButtons || (bnum <= 2); 8197 //assert(updown != UpDown.None); 8198 debug(arsd_mevent_strcmp) { import iv.cmdcon; conprintfln(" 1: mods=0x%08x; bnum=%u; updown=%s [%s]", mods, bnum, updown, str); } 8199 // if we have a previous button, it goes to modifiers (unless it is a wheel or motion) 8200 if (lastButt != lastButt.max) { 8201 if ((lastButt&0xff) >= 3) return false; // wheel or motion 8202 if (mods != mods.max) { 8203 uint butbit = 0; 8204 final switch (lastButt&0x03) { 8205 case 0: butbit = ModifierState.leftButtonDown; break; 8206 case 1: butbit = ModifierState.middleButtonDown; break; 8207 case 2: butbit = ModifierState.rightButtonDown; break; 8208 } 8209 if (lastButt&Flag.Down) mods |= butbit; 8210 else if (lastButt&Flag.Up) mods &= ~butbit; 8211 else if (lastButt&Flag.Any) kmodmask &= ~butbit; 8212 } 8213 } 8214 // remember last button 8215 lastButt = bnum|(updown == UpDown.Up ? Flag.Up : updown == UpDown.Any ? Flag.Any : Flag.Down); 8216 } 8217 // no button -- nothing to do 8218 if (lastButt == lastButt.max) return false; 8219 // done parsing, check if something's left 8220 foreach (immutable char ch; str) if (ch > ' ') return false; // oops 8221 // remove action button from mask 8222 if ((lastButt&0xff) < 3) { 8223 final switch (lastButt&0x03) { 8224 case 0: kmodmask &= ~cast(uint)ModifierState.leftButtonDown; break; 8225 case 1: kmodmask &= ~cast(uint)ModifierState.middleButtonDown; break; 8226 case 2: kmodmask &= ~cast(uint)ModifierState.rightButtonDown; break; 8227 } 8228 } 8229 // special case: "Motion" means "ignore buttons" 8230 if ((lastButt&0xff) == 7 && !wasButtons) { 8231 debug(arsd_mevent_strcmp) { import iv.cmdcon; conwriteln(" *: special motion"); } 8232 kmodmask &= ~cast(uint)(ModifierState.leftButtonDown|ModifierState.middleButtonDown|ModifierState.rightButtonDown); 8233 } 8234 uint kmod = event.modifierState&kmodmask; 8235 debug(arsd_mevent_strcmp) { import iv.cmdcon; conprintfln(" *: mods=0x%08x; lastButt=0x%08x; kmod=0x%08x; type=%s", mods, lastButt, kmod, event.type); } 8236 // check modifier state 8237 if (mods != mods.max) { 8238 if (kmod != mods) return false; 8239 } 8240 // now check type 8241 if ((lastButt&0xff) == 7) { 8242 // motion 8243 if (event.type != MouseEventType.motion) return false; 8244 } else if ((lastButt&0xff) == 3) { 8245 // wheel 8246 if (lastButt&Flag.Up) return (event.type == MouseEventType.buttonPressed && event.button == MouseButton.wheelUp); 8247 if (lastButt&Flag.Down) return (event.type == MouseEventType.buttonPressed && event.button == MouseButton.wheelDown); 8248 if (lastButt&Flag.Any) return (event.type == MouseEventType.buttonPressed && (event.button == MouseButton.wheelUp || event.button == MouseButton.wheelUp)); 8249 return false; 8250 } else { 8251 // buttons 8252 if (((lastButt&Flag.Down) != 0 && event.type != MouseEventType.buttonPressed) || 8253 ((lastButt&Flag.Up) != 0 && event.type != MouseEventType.buttonReleased)) 8254 { 8255 return false; 8256 } 8257 // button number 8258 switch (lastButt&0x03) { 8259 case 0: if (event.button != MouseButton.left) return false; break; 8260 case 1: if (event.button != MouseButton.middle) return false; break; 8261 case 2: if (event.button != MouseButton.right) return false; break; 8262 default: return false; 8263 } 8264 } 8265 return true; 8266 } 8267 } 8268 8269 version(arsd_mevent_strcmp_test) unittest { 8270 MouseEvent event; 8271 event.type = MouseEventType.buttonPressed; 8272 event.button = MouseButton.left; 8273 event.modifierState = ModifierState.ctrl; 8274 assert(event == "C-LMB"); 8275 assert(event != "C-LMBUP"); 8276 assert(event != "C-LMB-UP"); 8277 assert(event != "C-S-LMB"); 8278 assert(event == "*-LMB"); 8279 assert(event != "*-LMB-UP"); 8280 8281 event.type = MouseEventType.buttonReleased; 8282 assert(event != "C-LMB"); 8283 assert(event == "C-LMBUP"); 8284 assert(event == "C-LMB-UP"); 8285 assert(event != "C-S-LMB"); 8286 assert(event != "*-LMB"); 8287 assert(event == "*-LMB-UP"); 8288 8289 event.button = MouseButton.right; 8290 event.modifierState |= ModifierState.shift; 8291 event.type = MouseEventType.buttonPressed; 8292 assert(event != "C-LMB"); 8293 assert(event != "C-LMBUP"); 8294 assert(event != "C-LMB-UP"); 8295 assert(event != "C-S-LMB"); 8296 assert(event != "*-LMB"); 8297 assert(event != "*-LMB-UP"); 8298 8299 assert(event != "C-RMB"); 8300 assert(event != "C-RMBUP"); 8301 assert(event != "C-RMB-UP"); 8302 assert(event == "C-S-RMB"); 8303 assert(event == "*-RMB"); 8304 assert(event != "*-RMB-UP"); 8305 } 8306 8307 /// This gives a few more options to drawing lines and such 8308 struct Pen { 8309 Color color; /// the foreground color 8310 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. 8311 Style style; /// See [Style] 8312 /+ 8313 // From X.h 8314 8315 #define LineSolid 0 8316 #define LineOnOffDash 1 8317 #define LineDoubleDash 2 8318 LineDou- The full path of the line is drawn, but the 8319 bleDash even dashes are filled differently from the 8320 odd dashes (see fill-style) with CapButt 8321 style used where even and odd dashes meet. 8322 8323 8324 8325 /* capStyle */ 8326 8327 #define CapNotLast 0 8328 #define CapButt 1 8329 #define CapRound 2 8330 #define CapProjecting 3 8331 8332 /* joinStyle */ 8333 8334 #define JoinMiter 0 8335 #define JoinRound 1 8336 #define JoinBevel 2 8337 8338 /* fillStyle */ 8339 8340 #define FillSolid 0 8341 #define FillTiled 1 8342 #define FillStippled 2 8343 #define FillOpaqueStippled 3 8344 8345 8346 +/ 8347 /// Style of lines drawn 8348 enum Style { 8349 Solid, /// a solid line 8350 Dashed, /// a dashed line 8351 Dotted, /// a dotted line 8352 } 8353 } 8354 8355 8356 /++ 8357 Represents an in-memory image in the format that the GUI expects, but with its raw data available to your program. 8358 8359 8360 On Windows, this means a device-independent bitmap. On X11, it is an XImage. 8361 8362 $(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.) 8363 8364 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. 8365 8366 If you intend to draw an image to screen several times, you will want to convert it into a [Sprite]. 8367 8368 $(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. 8369 8370 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! 8371 8372 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!) 8373 8374 Please call `destroy(image);` when you are done with it. The easiest way to do this is with scope: 8375 8376 --- 8377 auto image = new Image(256, 256); 8378 scope(exit) destroy(image); 8379 --- 8380 8381 As long as you don't hold on to it outside the scope. 8382 8383 I might change it to be an owned pointer at some point in the future. 8384 8385 ) 8386 8387 Drawing pixels on the image may be simple, using the `opIndexAssign` function, but 8388 you can also often get a fair amount of speedup by getting the raw data format and 8389 writing some custom code. 8390 8391 FIXME INSERT EXAMPLES HERE 8392 8393 8394 +/ 8395 final class Image { 8396 /// 8397 this(int width, int height, bool forcexshm=false, bool enableAlpha = false) { 8398 this.width = width; 8399 this.height = height; 8400 this.enableAlpha = enableAlpha; 8401 8402 impl.createImage(width, height, forcexshm, enableAlpha); 8403 } 8404 8405 /// 8406 this(Size size, bool forcexshm=false, bool enableAlpha = false) { 8407 this(size.width, size.height, forcexshm, enableAlpha); 8408 } 8409 8410 private bool suppressDestruction; 8411 8412 version(X11) 8413 this(XImage* handle) { 8414 this.handle = handle; 8415 this.rawData = cast(ubyte*) handle.data; 8416 this.width = handle.width; 8417 this.height = handle.height; 8418 this.enableAlpha = handle.depth == 32; 8419 suppressDestruction = true; 8420 } 8421 8422 ~this() { 8423 if(suppressDestruction) return; 8424 impl.dispose(); 8425 } 8426 8427 // these numbers are used for working with rawData itself, skipping putPixel and getPixel 8428 /// if you do the math yourself you might be able to optimize it. Call these functions only once and cache the value. 8429 pure const @system nothrow { 8430 /* 8431 To use these to draw a blue rectangle with size WxH at position X,Y... 8432 8433 // make certain that it will fit before we proceed 8434 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! 8435 8436 // gather all the values you'll need up front. These can be kept until the image changes size if you want 8437 // (though calculating them isn't really that expensive). 8438 auto nextLineAdjustment = img.adjustmentForNextLine(); 8439 auto offR = img.redByteOffset(); 8440 auto offB = img.blueByteOffset(); 8441 auto offG = img.greenByteOffset(); 8442 auto bpp = img.bytesPerPixel(); 8443 8444 auto data = img.getDataPointer(); 8445 8446 // figure out the starting byte offset 8447 auto offset = img.offsetForTopLeftPixel() + nextLineAdjustment*Y + bpp * X; 8448 8449 auto startOfLine = data + offset; // get our pointer lined up on the first pixel 8450 8451 // and now our drawing loop for the rectangle 8452 foreach(y; 0 .. H) { 8453 auto data = startOfLine; // we keep the start of line separately so moving to the next line is simple and portable 8454 foreach(x; 0 .. W) { 8455 // write our color 8456 data[offR] = 0; 8457 data[offG] = 0; 8458 data[offB] = 255; 8459 8460 data += bpp; // moving to the next pixel is just an addition... 8461 } 8462 startOfLine += nextLineAdjustment; 8463 } 8464 8465 8466 As you can see, the loop itself was very simple thanks to the calculations being moved outside. 8467 8468 FIXME: I wonder if I can make the pixel formats consistently 32 bit across platforms, so the color offsets 8469 can be made into a bitmask or something so we can write them as *uint... 8470 */ 8471 8472 /// 8473 int offsetForTopLeftPixel() { 8474 version(X11) { 8475 return 0; 8476 } else version(Windows) { 8477 if(enableAlpha) { 8478 return (width * 4) * (height - 1); 8479 } else { 8480 return (((cast(int) width * 3 + 3) / 4) * 4) * (height - 1); 8481 } 8482 } else version(OSXCocoa) { 8483 return 0 ; //throw new NotYetImplementedException(); 8484 } else version(Emscripten) { 8485 return 0; 8486 } else static assert(0, "fill in this info for other OSes"); 8487 } 8488 8489 /// 8490 int offsetForPixel(int x, int y) { 8491 version(X11) { 8492 auto offset = (y * width + x) * 4; 8493 return offset; 8494 } else version(Windows) { 8495 if(enableAlpha) { 8496 auto itemsPerLine = width * 4; 8497 // remember, bmps are upside down 8498 auto offset = itemsPerLine * (height - y - 1) + x * 4; 8499 return offset; 8500 } else { 8501 auto itemsPerLine = ((cast(int) width * 3 + 3) / 4) * 4; 8502 // remember, bmps are upside down 8503 auto offset = itemsPerLine * (height - y - 1) + x * 3; 8504 return offset; 8505 } 8506 } else version(OSXCocoa) { 8507 return (y * width + x) * 4 ; //throw new NotYetImplementedException(); 8508 } else version(Emscripten) { 8509 return (y * width + x) * 4 ; //throw new NotYetImplementedException(); 8510 } else static assert(0, "fill in this info for other OSes"); 8511 } 8512 8513 /// 8514 int adjustmentForNextLine() { 8515 version(X11) { 8516 return width * 4; 8517 } else version(Windows) { 8518 // windows bmps are upside down, so the adjustment is actually negative 8519 if(enableAlpha) 8520 return - (cast(int) width * 4); 8521 else 8522 return -((cast(int) width * 3 + 3) / 4) * 4; 8523 } else version(OSXCocoa) { 8524 return width * 4 ; //throw new NotYetImplementedException(); 8525 } else version(Emscripten) { 8526 return width * 4 ; //throw new NotYetImplementedException(); 8527 } else static assert(0, "fill in this info for other OSes"); 8528 } 8529 8530 /// once you have the position of a pixel, use these to get to the proper color 8531 int redByteOffset() { 8532 version(X11) { 8533 return 2; 8534 } else version(Windows) { 8535 return 2; 8536 } else version(OSXCocoa) { 8537 return 2 ; //throw new NotYetImplementedException(); 8538 } else version(Emscripten) { 8539 return 2 ; //throw new NotYetImplementedException(); 8540 } else static assert(0, "fill in this info for other OSes"); 8541 } 8542 8543 /// 8544 int greenByteOffset() { 8545 version(X11) { 8546 return 1; 8547 } else version(Windows) { 8548 return 1; 8549 } else version(OSXCocoa) { 8550 return 1 ; //throw new NotYetImplementedException(); 8551 } else version(Emscripten) { 8552 return 1 ; //throw new NotYetImplementedException(); 8553 } else static assert(0, "fill in this info for other OSes"); 8554 } 8555 8556 /// 8557 int blueByteOffset() { 8558 version(X11) { 8559 return 0; 8560 } else version(Windows) { 8561 return 0; 8562 } else version(OSXCocoa) { 8563 return 0 ; //throw new NotYetImplementedException(); 8564 } else version(Emscripten) { 8565 return 0 ; //throw new NotYetImplementedException(); 8566 } else static assert(0, "fill in this info for other OSes"); 8567 } 8568 8569 /// Only valid if [enableAlpha] is true 8570 int alphaByteOffset() { 8571 version(X11) { 8572 return 3; 8573 } else version(Windows) { 8574 return 3; 8575 } else version(OSXCocoa) { 8576 return 3; //throw new NotYetImplementedException(); 8577 } else version(Emscripten) { 8578 return 3 ; //throw new NotYetImplementedException(); 8579 } else static assert(0, "fill in this info for other OSes"); 8580 } 8581 } 8582 8583 /// 8584 final void putPixel(int x, int y, Color c) { 8585 if(x < 0 || x >= width) 8586 return; 8587 if(y < 0 || y >= height) 8588 return; 8589 8590 impl.setPixel(x, y, c); 8591 } 8592 8593 /// 8594 final Color getPixel(int x, int y) { 8595 if(x < 0 || x >= width) 8596 return Color.transparent; 8597 if(y < 0 || y >= height) 8598 return Color.transparent; 8599 8600 version(OSXCocoa) throw new NotYetImplementedException(); else 8601 return impl.getPixel(x, y); 8602 } 8603 8604 /// 8605 final void opIndexAssign(Color c, int x, int y) { 8606 putPixel(x, y, c); 8607 } 8608 8609 /// 8610 TrueColorImage toTrueColorImage() { 8611 auto tci = new TrueColorImage(width, height); 8612 convertToRgbaBytes(tci.imageData.bytes); 8613 return tci; 8614 } 8615 8616 /// 8617 static Image fromMemoryImage(MemoryImage i, bool enableAlpha = false, bool premultiply = true) { 8618 auto tci = i.getAsTrueColorImage(); 8619 auto img = new Image(tci.width, tci.height, false, enableAlpha); 8620 static if(UsingSimpledisplayX11) 8621 img.premultiply = premultiply; 8622 img.setRgbaBytes(tci.imageData.bytes); 8623 return img; 8624 } 8625 8626 /// this is here for interop with arsd.image. where can be a TrueColorImage's data member 8627 /// if you pass in a buffer, it will put it right there. length must be width*height*4 already 8628 /// if you pass null, it will allocate a new one. 8629 ubyte[] getRgbaBytes(ubyte[] where = null) { 8630 if(where is null) 8631 where = new ubyte[this.width*this.height*4]; 8632 convertToRgbaBytes(where); 8633 return where; 8634 } 8635 8636 /// this is here for interop with arsd.image. from can be a TrueColorImage's data member 8637 void setRgbaBytes(in ubyte[] from ) { 8638 assert(from.length == this.width * this.height * 4); 8639 setFromRgbaBytes(from); 8640 } 8641 8642 // FIXME: make properly cross platform by getting rgba right 8643 8644 /// warning: this is not portable across platforms because the data format can change 8645 ubyte* getDataPointer() { 8646 return impl.rawData; 8647 } 8648 8649 /// for use with getDataPointer 8650 final int bytesPerLine() const pure @safe nothrow { 8651 version(Windows) 8652 return enableAlpha ? (width * 4) : (((cast(int) width * 3 + 3) / 4) * 4); 8653 else version(X11) 8654 return 4 * width; 8655 else version(OSXCocoa) 8656 return 4 * width; 8657 else static assert(0); 8658 } 8659 8660 /// for use with getDataPointer 8661 final int bytesPerPixel() const pure @safe nothrow { 8662 version(Windows) 8663 return enableAlpha ? 4 : 3; 8664 else version(X11) 8665 return 4; 8666 else version(OSXCocoa) 8667 return 4; 8668 else static assert(0); 8669 } 8670 8671 /// 8672 immutable int width; 8673 8674 /// 8675 immutable int height; 8676 8677 /// 8678 immutable bool enableAlpha; 8679 //private: 8680 mixin NativeImageImplementation!() impl; 8681 } 8682 8683 /++ 8684 A convenience function to pop up a window displaying the image. 8685 If you pass a win, it will draw the image in it. Otherwise, it will 8686 create a window with the size of the image and run its event loop, closing 8687 when a key is pressed. 8688 8689 History: 8690 `BlockingMode` parameter added on December 8, 2021. Previously, it would 8691 always block until the application quit which could cause bizarre behavior 8692 inside a more complex application. Now, the default is to block until 8693 this window closes if it is the only event loop running, and otherwise, 8694 not to block at all and just pop up the display window asynchronously. 8695 +/ 8696 void displayImage(Image image, SimpleWindow win = null, BlockingMode bm = BlockingMode.untilWindowCloses | BlockingMode.onlyIfNotNested) { 8697 if(win is null) { 8698 win = new SimpleWindow(image); 8699 { 8700 auto p = win.draw; 8701 p.drawImage(Point(0, 0), image); 8702 } 8703 win.eventLoopWithBlockingMode( 8704 bm, 0, 8705 (KeyEvent ev) { 8706 if (ev.pressed && (ev.key == Key.Escape || ev.key == Key.Space)) win.close(); 8707 } ); 8708 } else { 8709 win.image = image; 8710 } 8711 } 8712 8713 enum FontWeight : int { 8714 dontcare = 0, 8715 thin = 100, 8716 extralight = 200, 8717 light = 300, 8718 regular = 400, 8719 medium = 500, 8720 semibold = 600, 8721 bold = 700, 8722 extrabold = 800, 8723 heavy = 900 8724 } 8725 8726 /++ 8727 Interface with the common functionality for font measurements between [OperatingSystemFont] and [DrawableFont]. 8728 8729 History: 8730 Added October 24, 2022. The methods were already on [OperatingSystemFont] before that. 8731 +/ 8732 interface MeasurableFont { 8733 /++ 8734 Returns true if it is a monospace font, meaning each of the 8735 glyphs (at least the ascii characters) have matching width 8736 and no kerning, so you can determine the display width of some 8737 strings by simply multiplying the string width by [averageWidth]. 8738 8739 (Please note that multiply doesn't $(I actually) work in general, 8740 consider characters like tab and newline, but it does sometimes.) 8741 +/ 8742 bool isMonospace(); 8743 8744 /++ 8745 The average width of glyphs in the font, traditionally equal to the 8746 width of the lowercase x. Can be used to estimate bounding boxes, 8747 especially if the font [isMonospace]. 8748 8749 Given in pixels. 8750 +/ 8751 int averageWidth(); 8752 /++ 8753 The height of the bounding box of a line. 8754 +/ 8755 int height(); 8756 /++ 8757 The maximum ascent of a glyph above the baseline. 8758 8759 Given in pixels. 8760 +/ 8761 int ascent(); 8762 /++ 8763 The maximum descent of a glyph below the baseline. For example, how low the g might go. 8764 8765 Given in pixels. 8766 +/ 8767 int descent(); 8768 /++ 8769 The display width of the given string, and if you provide a window, it will use it to 8770 make the pixel count on screen more accurate too, but this shouldn't generally be necessary. 8771 8772 Given in pixels. 8773 +/ 8774 int stringWidth(scope const(char)[] s, SimpleWindow window = null); 8775 8776 } 8777 8778 // FIXME: i need a font cache and it needs to handle disconnects. 8779 8780 /++ 8781 Represents a font loaded off the operating system or the X server. 8782 8783 8784 While the api here is unified cross platform, the fonts are not necessarily 8785 available, even across machines of the same platform, so be sure to always check 8786 for null (using [isNull]) and have a fallback plan. 8787 8788 When you have a font you like, use [ScreenPainter.setFont] to load it for drawing. 8789 8790 Worst case, a null font will automatically fall back to the default font loaded 8791 for your system. 8792 +/ 8793 class OperatingSystemFont : MeasurableFont { 8794 // FIXME: when the X Connection is lost, these need to be invalidated! 8795 // that means I need to store the original stuff again to reconstruct it too. 8796 8797 version(Emscripten) { 8798 void* font; 8799 } else version(X11) { 8800 XFontStruct* font; 8801 XFontSet fontset; 8802 8803 version(with_xft) { 8804 XftFont* xftFont; 8805 bool isXft; 8806 } 8807 } else version(Windows) { 8808 HFONT font; 8809 int width_; 8810 int height_; 8811 } else version(OSXCocoa) { 8812 NSFont font; 8813 } else static assert(0); 8814 8815 /++ 8816 Constructs the class and immediately calls [load]. 8817 +/ 8818 this(string name, int size = 0, FontWeight weight = FontWeight.dontcare, bool italic = false) { 8819 load(name, size, weight, italic); 8820 } 8821 8822 /++ 8823 Constructs the object, but does nothing. Call one of [load] or [loadDefault] to populate the object. 8824 8825 You can also call the platform-specific [loadXft], [loadCoreX], or [loadWin32] functions if appropriate for you. 8826 8827 History: 8828 Added January 24, 2021. 8829 +/ 8830 this() { 8831 // this space intentionally left blank 8832 } 8833 8834 /++ 8835 Constructs a copy of the given font object. 8836 8837 History: 8838 Added January 7, 2023. 8839 +/ 8840 this(OperatingSystemFont font) { 8841 if(font is null || font.loadedInfo is LoadedInfo.init) 8842 loadDefault(); 8843 else 8844 load(font.loadedInfo.tupleof); 8845 } 8846 8847 /++ 8848 Loads specifically with the Xft library - a freetype font from a fontconfig string. 8849 8850 History: 8851 Added November 13, 2020. 8852 +/ 8853 version(with_xft) 8854 bool loadXft(string name, int size = 0, FontWeight weight = FontWeight.dontcare, bool italic = false) { 8855 unload(); 8856 8857 if(!XftLibrary.attempted) { 8858 XftLibrary.loadDynamicLibrary(); 8859 } 8860 8861 if(!XftLibrary.loadSuccessful) 8862 return false; 8863 8864 auto display = XDisplayConnection.get; 8865 8866 char[256] nameBuffer = void; 8867 int nbp = 0; 8868 8869 void add(in char[] a) { 8870 nameBuffer[nbp .. nbp + a.length] = a[]; 8871 nbp += a.length; 8872 } 8873 add(name); 8874 8875 if(size) { 8876 add(":size="); 8877 add(toInternal!string(size)); 8878 } 8879 if(weight != FontWeight.dontcare && weight != 400) { 8880 if(weight < 400) 8881 add(":style=Light"); 8882 else 8883 add(":style=Bold"); 8884 add(":weight="); 8885 add(weightToString(weight)); 8886 } 8887 if(italic) { 8888 if(weight == FontWeight.dontcare) 8889 add(":style=Italic"); 8890 add(":slant=100"); 8891 } 8892 8893 nameBuffer[nbp] = 0; 8894 8895 this.xftFont = XftFontOpenName( 8896 display, 8897 DefaultScreen(display), 8898 nameBuffer.ptr 8899 ); 8900 8901 this.isXft = true; 8902 8903 if(xftFont !is null) { 8904 isMonospace_ = stringWidth("x") == stringWidth("M"); 8905 ascent_ = xftFont.ascent; 8906 descent_ = xftFont.descent; 8907 } 8908 8909 return !isNull(); 8910 } 8911 8912 /++ 8913 Lists available fonts from the system that match the given pattern, finding names that are suitable for passing to [OperatingSystemFont]'s constructor. 8914 8915 8916 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. 8917 8918 If `pattern` is null, it returns all available font families. 8919 8920 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. 8921 8922 The format of the pattern is platform-specific. 8923 8924 History: 8925 Added May 1, 2021 (dub v9.5) 8926 +/ 8927 static void listFonts(string pattern, bool delegate(in char[] name) handler) { 8928 version(Windows) { 8929 auto hdc = GetDC(null); 8930 scope(exit) ReleaseDC(null, hdc); 8931 LOGFONT logfont; 8932 static extern(Windows) int proc(const LOGFONT* lf, const TEXTMETRIC* tm, DWORD type, LPARAM p) { 8933 auto localHandler = *(cast(typeof(handler)*) p); 8934 return localHandler(lf.lfFaceName[].sliceCString) ? 1 : 0; 8935 } 8936 EnumFontFamiliesEx(hdc, &logfont, &proc, cast(LPARAM) &handler, 0); 8937 } else version(X11) { 8938 //import core.stdc.stdio; 8939 bool done = false; 8940 version(with_xft) { 8941 if(!XftLibrary.attempted) { 8942 XftLibrary.loadDynamicLibrary(); 8943 } 8944 8945 if(!XftLibrary.loadSuccessful) 8946 goto skipXft; 8947 8948 if(!FontConfigLibrary.attempted) 8949 FontConfigLibrary.loadDynamicLibrary(); 8950 if(!FontConfigLibrary.loadSuccessful) 8951 goto skipXft; 8952 8953 { 8954 auto got = XftListFonts(XDisplayConnection.get, 0, null, "family".ptr, "style".ptr, null); 8955 if(got is null) 8956 goto skipXft; 8957 scope(exit) FcFontSetDestroy(got); 8958 8959 auto fontPatterns = got.fonts[0 .. got.nfont]; 8960 foreach(candidate; fontPatterns) { 8961 char* where, whereStyle; 8962 8963 char* pmg = FcNameUnparse(candidate); 8964 8965 //FcPatternGetString(candidate, "family", 0, &where); 8966 //FcPatternGetString(candidate, "style", 0, &whereStyle); 8967 //if(where && whereStyle) { 8968 if(pmg) { 8969 if(!handler(pmg.sliceCString)) 8970 return; 8971 //printf("%s || %s %s\n", pmg, where, whereStyle); 8972 } 8973 } 8974 } 8975 } 8976 8977 skipXft: 8978 8979 if(pattern is null) 8980 pattern = "*"; 8981 8982 int count; 8983 auto coreFontsRaw = XListFonts(XDisplayConnection.get, pattern.toStringz, 10000 /* max return */, &count); 8984 scope(exit) XFreeFontNames(coreFontsRaw); 8985 8986 auto coreFonts = coreFontsRaw[0 .. count]; 8987 8988 foreach(font; coreFonts) { 8989 char[128] tmp; 8990 tmp[0 ..5] = "core:"; 8991 auto cf = font.sliceCString; 8992 if(5 + cf.length > tmp.length) 8993 assert(0, "a font name was too long, sorry i didn't bother implementing a fallback"); 8994 tmp[5 .. 5 + cf.length] = cf; 8995 if(!handler(tmp[0 .. 5 + cf.length])) 8996 return; 8997 } 8998 } 8999 } 9000 9001 /++ 9002 Returns the raw content of the ttf file, if possible. This allows you to use OperatingSystemFont 9003 to look up fonts that you then pass to things like [arsd.ttf.OpenGlLimitedFont] or [arsd.nanovega]. 9004 9005 Returns null if impossible. It is impossible if the loaded font is not a local TTF file or if the 9006 underlying system doesn't support returning the raw bytes. 9007 9008 History: 9009 Added September 10, 2021 (dub v10.3) 9010 +/ 9011 ubyte[] getTtfBytes() { 9012 if(isNull) 9013 return null; 9014 9015 version(Windows) { 9016 auto dc = GetDC(null); 9017 auto orig = SelectObject(dc, font); 9018 9019 scope(exit) { 9020 SelectObject(dc, orig); 9021 ReleaseDC(null, dc); 9022 } 9023 9024 auto res = GetFontData(dc, 0 /* whole file */, 0 /* offset */, null, 0); 9025 if(res == GDI_ERROR) 9026 return null; 9027 9028 ubyte[] buffer = new ubyte[](res); 9029 res = GetFontData(dc, 0 /* whole file */, 0 /* offset */, buffer.ptr, cast(DWORD) buffer.length); 9030 if(res == GDI_ERROR) 9031 return null; // wtf really tbh 9032 9033 return buffer; 9034 } else version(with_xft) { 9035 if(isXft && xftFont) { 9036 if(!FontConfigLibrary.attempted) 9037 FontConfigLibrary.loadDynamicLibrary(); 9038 if(!FontConfigLibrary.loadSuccessful) 9039 return null; 9040 9041 char* file; 9042 if (FcPatternGetString(xftFont.pattern, "file", 0, &file) == 0 /*FcResultMatch*/) { 9043 if (file !is null && file[0]) { 9044 import core.stdc.stdio; 9045 auto fp = fopen(file, "rb"); 9046 if(fp is null) 9047 return null; 9048 scope(exit) 9049 fclose(fp); 9050 fseek(fp, 0, SEEK_END); 9051 ubyte[] buffer = new ubyte[](ftell(fp)); 9052 fseek(fp, 0, SEEK_SET); 9053 9054 auto got = fread(buffer.ptr, 1, buffer.length, fp); 9055 if(got != buffer.length) 9056 return null; 9057 9058 return buffer; 9059 } 9060 } 9061 } 9062 return null; 9063 } else throw new NotYetImplementedException(); 9064 } 9065 9066 // see also: XftLockFace(font) which gives a FT_Face. from /usr/include/X11/Xft/Xft.h line 352 9067 9068 private string weightToString(FontWeight weight) { 9069 with(FontWeight) 9070 final switch(weight) { 9071 case dontcare: return "*"; 9072 case thin: return "extralight"; 9073 case extralight: return "extralight"; 9074 case light: return "light"; 9075 case regular: return "regular"; 9076 case medium: return "medium"; 9077 case semibold: return "demibold"; 9078 case bold: return "bold"; 9079 case extrabold: return "demibold"; 9080 case heavy: return "black"; 9081 } 9082 } 9083 9084 /++ 9085 Loads specifically a Core X font - rendered on the X server without antialiasing. Best performance. 9086 9087 History: 9088 Added November 13, 2020. Before then, this code was integrated in the [load] function. 9089 +/ 9090 version(X11) 9091 bool loadCoreX(string name, int size = 0, FontWeight weight = FontWeight.dontcare, bool italic = false) { 9092 unload(); 9093 9094 string xfontstr; 9095 9096 if(name.length > 3 && name[0 .. 3] == "-*-") { 9097 // this is kinda a disgusting hack but if the user sends an exact 9098 // string I'd like to honor it... 9099 xfontstr = name; 9100 } else { 9101 string weightstr = weightToString(weight); 9102 string sizestr; 9103 if(size == 0) 9104 sizestr = "*"; 9105 else 9106 sizestr = toInternal!string(size); 9107 xfontstr = "-*-"~name~"-"~weightstr~"-"~(italic ? "i" : "r")~"-*-*-"~sizestr~"-*-*-*-*-*-*-*\0"; 9108 } 9109 9110 // writeln(xfontstr); 9111 9112 auto display = XDisplayConnection.get; 9113 9114 font = XLoadQueryFont(display, xfontstr.ptr); 9115 if(font is null) 9116 return false; 9117 9118 char** lol; 9119 int lol2; 9120 char* lol3; 9121 fontset = XCreateFontSet(display, xfontstr.ptr, &lol, &lol2, &lol3); 9122 9123 prepareFontInfo(); 9124 9125 return !isNull(); 9126 } 9127 9128 version(X11) 9129 private void prepareFontInfo() { 9130 if(font !is null) { 9131 isMonospace_ = stringWidth("l") == stringWidth("M"); 9132 ascent_ = font.max_bounds.ascent; 9133 descent_ = font.max_bounds.descent; 9134 } 9135 } 9136 9137 version(OSXCocoa) 9138 private void prepareFontInfo() { 9139 if(font !is null) { 9140 isMonospace_ = font.isFixedPitch; 9141 ascent_ = cast(int) font.ascender; 9142 descent_ = cast(int) - font.descender; 9143 } 9144 } 9145 9146 9147 /++ 9148 Loads a Windows font. You probably want to use [load] instead to be more generic. 9149 9150 History: 9151 Added November 13, 2020. Before then, this code was integrated in the [load] function. 9152 +/ 9153 version(Windows) 9154 bool loadWin32(string name, int size = 0, FontWeight weight = FontWeight.dontcare, bool italic = false, HDC hdc = null) { 9155 unload(); 9156 9157 WCharzBuffer buffer = WCharzBuffer(name); 9158 font = CreateFont(size, 0, 0, 0, cast(int) weight, italic, 0, 0, 0, 0, 0, 0, 0, buffer.ptr); 9159 9160 prepareFontInfo(hdc); 9161 9162 return !isNull(); 9163 } 9164 9165 version(Windows) 9166 void prepareFontInfo(HDC hdc = null) { 9167 if(font is null) 9168 return; 9169 9170 TEXTMETRIC tm; 9171 auto dc = hdc ? hdc : GetDC(null); 9172 auto orig = SelectObject(dc, font); 9173 GetTextMetrics(dc, &tm); 9174 SelectObject(dc, orig); 9175 if(hdc is null) 9176 ReleaseDC(null, dc); 9177 9178 width_ = tm.tmAveCharWidth; 9179 height_ = tm.tmHeight; 9180 ascent_ = tm.tmAscent; 9181 descent_ = tm.tmDescent; 9182 // 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. 9183 isMonospace_ = (tm.tmPitchAndFamily & TMPF_FIXED_PITCH) == 0; 9184 } 9185 9186 9187 /++ 9188 `name` is a font name, but it can also be a more complicated string parsed in an OS-specific way. 9189 9190 On X, you may prefix a name with `core:` to bypass the freetype engine causing this function to forward to [loadCoreX]. Otherwise, 9191 it calls [loadXft] if the library is available. If the library or font is not available on Xft, it falls back on [loadCoreX]. 9192 9193 On Windows, it forwards directly to [loadWin32]. 9194 9195 Params: 9196 name = font name. This is looked up by the operating system and may be interpreted differently across platforms or user machines and their preferences. 9197 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. 9198 weight = approximate boldness, results may vary. 9199 italic = try to get a slanted version of the given font. 9200 9201 History: 9202 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. 9203 +/ 9204 bool load(string name, int size = 0, FontWeight weight = FontWeight.dontcare, bool italic = false) { 9205 this.loadedInfo = LoadedInfo(name, size, weight, italic); 9206 version(X11) { 9207 version(with_xft) { 9208 if(name.length > 5 && name[0 .. 5] == "core:") { 9209 goto core; 9210 } 9211 9212 if(loadXft(name, size, weight, italic)) 9213 return true; 9214 // if xft fails, fallback to core to avoid breaking 9215 // code that already depended on this. 9216 } 9217 9218 core: 9219 9220 if(name.length > 5 && name[0 .. 5] == "core:") { 9221 name = name[5 .. $]; 9222 } 9223 9224 return loadCoreX(name, size, weight, italic); 9225 } else version(Windows) { 9226 return loadWin32(name, size, weight, italic); 9227 } else version(OSXCocoa) { 9228 return loadCocoa(name, size, weight, italic); 9229 } else static assert(0); 9230 } 9231 9232 version(OSXCocoa) 9233 bool loadCocoa(string name, int size, FontWeight weight, bool italic) { 9234 unload(); 9235 9236 font = NSFont.fontWithName(MacString(name).borrow, size); // FIXME: weight and italic? 9237 prepareFontInfo(); 9238 9239 return !isNull(); 9240 } 9241 9242 private struct LoadedInfo { 9243 string name; 9244 int size; 9245 FontWeight weight; 9246 bool italic; 9247 } 9248 private LoadedInfo loadedInfo; 9249 9250 /// 9251 void unload() { 9252 if(isNull()) 9253 return; 9254 9255 version(X11) { 9256 auto display = XDisplayConnection.display; 9257 9258 if(display is null) 9259 return; 9260 9261 version(with_xft) { 9262 if(isXft) { 9263 if(xftFont) 9264 XftFontClose(display, xftFont); 9265 isXft = false; 9266 xftFont = null; 9267 return; 9268 } 9269 } 9270 9271 if(font && font !is ScreenPainterImplementation.defaultfont) 9272 XFreeFont(display, font); 9273 if(fontset && fontset !is ScreenPainterImplementation.defaultfontset) 9274 XFreeFontSet(display, fontset); 9275 9276 font = null; 9277 fontset = null; 9278 } else version(Windows) { 9279 DeleteObject(font); 9280 font = null; 9281 } else version(OSXCocoa) { 9282 font.release(); 9283 font = null; 9284 } else static assert(0); 9285 } 9286 9287 private bool isMonospace_; 9288 9289 /++ 9290 History: 9291 Added January 16, 2021 9292 +/ 9293 bool isMonospace() { 9294 return isMonospace_; 9295 } 9296 9297 /++ 9298 Returns the average width of the font, conventionally defined as the width of the lowercase 'x' character. 9299 9300 History: 9301 Added March 26, 2020 9302 Documented January 16, 2021 9303 +/ 9304 int averageWidth() { 9305 version(X11) { 9306 return stringWidth("x"); 9307 } version(OSXCocoa) { 9308 return stringWidth("x"); 9309 } else version(Windows) 9310 return width_; 9311 else assert(0); 9312 } 9313 9314 /++ 9315 Returns the width of the string as drawn on the specified window, or the default screen if the window is null. 9316 9317 History: 9318 Added January 16, 2021 9319 +/ 9320 int stringWidth(scope const(char)[] s, SimpleWindow window = null) { 9321 // FIXME: what about tab? 9322 if(isNull) 9323 return 0; 9324 9325 version(X11) { 9326 version(with_xft) 9327 if(isXft && xftFont !is null) { 9328 //return xftFont.max_advance_width; 9329 XGlyphInfo extents; 9330 XftTextExtentsUtf8(XDisplayConnection.get, xftFont, s.ptr, cast(int) s.length, &extents); 9331 // writeln(extents); 9332 return extents.xOff; 9333 } 9334 if(font is null) 9335 return 0; 9336 else if(fontset) { 9337 XRectangle rect; 9338 Xutf8TextExtents(fontset, s.ptr, cast(int) s.length, null, &rect); 9339 9340 return rect.width; 9341 } else { 9342 return XTextWidth(font, s.ptr, cast(int) s.length); 9343 } 9344 } else version(Windows) { 9345 WCharzBuffer buffer = WCharzBuffer(s); 9346 9347 return stringWidth(buffer.slice, window); 9348 } else version(OSXCocoa) { 9349 /+ 9350 int charCount = [string length]; 9351 CGGlyph glyphs[charCount]; 9352 CGRect rects[charCount]; 9353 9354 CTFontGetGlyphsForCharacters(theCTFont, (const unichar*)[string cStringUsingEncoding:NSUnicodeStringEncoding], glyphs, charCount); 9355 CTFontGetBoundingRectsForGlyphs(theCTFont, kCTFontDefaultOrientation, glyphs, rects, charCount); 9356 9357 int totalwidth = 0, maxheight = 0; 9358 for (int i=0; i < charCount; i++) 9359 { 9360 totalwidth += rects[i].size.width; 9361 maxheight = maxheight < rects[i].size.height ? rects[i].size.height : maxheight; 9362 } 9363 9364 dim = CGSizeMake(totalwidth, maxheight); 9365 +/ 9366 9367 return 16; // FIXME 9368 } 9369 else assert(0); 9370 } 9371 9372 version(Windows) 9373 /// ditto 9374 int stringWidth(scope const(wchar)[] s, SimpleWindow window = null) { 9375 if(isNull) 9376 return 0; 9377 version(Windows) { 9378 SIZE size; 9379 9380 prepareContext(window); 9381 scope(exit) releaseContext(); 9382 9383 GetTextExtentPoint32W(dc, s.ptr, cast(int) s.length, &size); 9384 9385 return size.cx; 9386 } else { 9387 // std.conv can do this easily but it is slow to import and i don't think it is worth it 9388 static assert(0, "not implemented yet"); 9389 //return stringWidth(s, window); 9390 } 9391 } 9392 9393 private { 9394 int prepRefcount; 9395 9396 version(Windows) { 9397 HDC dc; 9398 HANDLE orig; 9399 HWND hwnd; 9400 } 9401 } 9402 /++ 9403 [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. 9404 9405 History: 9406 Added January 23, 2021 9407 +/ 9408 void prepareContext(SimpleWindow window = null) { 9409 prepRefcount++; 9410 if(prepRefcount == 1) { 9411 version(Windows) { 9412 hwnd = window is null ? null : window.impl.hwnd; 9413 dc = GetDC(hwnd); 9414 orig = SelectObject(dc, font); 9415 } 9416 } 9417 } 9418 /// ditto 9419 void releaseContext() { 9420 prepRefcount--; 9421 if(prepRefcount == 0) { 9422 version(Windows) { 9423 SelectObject(dc, orig); 9424 ReleaseDC(hwnd, dc); 9425 hwnd = null; 9426 dc = null; 9427 orig = null; 9428 } 9429 } 9430 } 9431 9432 /+ 9433 FIXME: I think I need advance and kerning pair 9434 9435 int advance(dchar from, dchar to) { } // use dchar.init for first item in string 9436 +/ 9437 9438 /++ 9439 Returns the height of the font. 9440 9441 History: 9442 Added March 26, 2020 9443 Documented January 16, 2021 9444 +/ 9445 int height() { 9446 version(X11) { 9447 version(with_xft) 9448 if(isXft && xftFont !is null) { 9449 return xftFont.ascent + xftFont.descent; // i don't use height here because it doesn't include the baseline pixel 9450 } 9451 if(font is null) 9452 return 0; 9453 return font.max_bounds.ascent + font.max_bounds.descent; 9454 } else version(Windows) { 9455 return height_; 9456 } else version(OSXCocoa) { 9457 if(font is null) 9458 return 0; 9459 return cast(int) (font.ascender + font.descender + 0.9 /* to round up */); // font.capHeight 9460 } 9461 else assert(0); 9462 } 9463 9464 private int ascent_; 9465 private int descent_; 9466 9467 /++ 9468 Max ascent above the baseline. 9469 9470 History: 9471 Added January 22, 2021 9472 +/ 9473 int ascent() { 9474 return ascent_; 9475 } 9476 9477 /++ 9478 Max descent below the baseline. 9479 9480 History: 9481 Added January 22, 2021 9482 +/ 9483 int descent() { 9484 return descent_; 9485 } 9486 9487 /++ 9488 Loads the default font used by [ScreenPainter] if none others are loaded. 9489 9490 Returns: 9491 This method mutates the `this` object, but then returns `this` for 9492 easy chaining like: 9493 9494 --- 9495 auto font = foo.isNull ? foo : foo.loadDefault 9496 --- 9497 9498 History: 9499 Added previously, but left unimplemented until January 24, 2021. 9500 +/ 9501 OperatingSystemFont loadDefault() { 9502 unload(); 9503 9504 loadedInfo = LoadedInfo.init; 9505 9506 version(X11) { 9507 // another option would be https://tronche.com/gui/x/xlib/graphics/font-metrics/XQueryFont.html 9508 // but meh since sdpy does its own thing, this should be ok too 9509 9510 ScreenPainterImplementation.ensureDefaultFontLoaded(); 9511 this.font = ScreenPainterImplementation.defaultfont; 9512 this.fontset = ScreenPainterImplementation.defaultfontset; 9513 9514 prepareFontInfo(); 9515 return this; 9516 } else version(Windows) { 9517 ScreenPainterImplementation.ensureDefaultFontLoaded(); 9518 this.font = ScreenPainterImplementation.defaultGuiFont; 9519 9520 prepareFontInfo(); 9521 return this; 9522 } else version(OSXCocoa) { 9523 this.font = NSFont.systemFontOfSize(15); 9524 9525 prepareFontInfo(); 9526 9527 // import std.stdio; writeln("Load default: ", this.height()); 9528 return this; 9529 } else throw new NotYetImplementedException(); 9530 } 9531 9532 /// 9533 bool isNull() { 9534 version(with_xft) 9535 if(isXft) 9536 return xftFont is null; 9537 return font is null; 9538 } 9539 9540 /* Metrics */ 9541 /+ 9542 GetABCWidth 9543 GetKerningPairs 9544 9545 if I do it right, I can size it all here, and match 9546 what happens when I draw the full string with the OS functions. 9547 9548 subclasses might do the same thing while getting the glyphs on images 9549 struct GlyphInfo { 9550 int glyph; 9551 9552 size_t stringIdxStart; 9553 size_t stringIdxEnd; 9554 9555 Rectangle boundingBox; 9556 } 9557 GlyphInfo[] getCharBoxes() { 9558 // XftTextExtentsUtf8 9559 return null; 9560 9561 } 9562 +/ 9563 9564 ~this() { 9565 if(!thisIsGuiThread) return; // FIXME: leaks if multithreaded gc 9566 unload(); 9567 } 9568 } 9569 9570 version(Windows) 9571 private string sliceCString(const(wchar)[] w) { 9572 return makeUtf8StringFromWindowsString(cast(wchar*) w.ptr); 9573 } 9574 9575 private inout(char)[] sliceCString(inout(char)* s) { 9576 import core.stdc.string; 9577 auto len = strlen(s); 9578 return s[0 .. len]; 9579 } 9580 9581 version(OSXCocoa) 9582 alias PaintingHandle = NSObject; 9583 else 9584 alias PaintingHandle = NativeWindowHandle; 9585 9586 /** 9587 The 2D drawing proxy. You acquire one of these with [SimpleWindow.draw] rather 9588 than constructing it directly. Then, it is reference counted so you can pass it 9589 at around and when the last ref goes out of scope, the buffered drawing activities 9590 are all carried out. 9591 9592 9593 Most functions use the outlineColor instead of taking a color themselves. 9594 ScreenPainter is reference counted and draws its buffer to the screen when its 9595 final reference goes out of scope. 9596 */ 9597 struct ScreenPainter { 9598 CapableOfBeingDrawnUpon window; 9599 this(CapableOfBeingDrawnUpon window, PaintingHandle handle, bool manualInvalidations) { 9600 this.window = window; 9601 if(window.closed) 9602 return; // null painter is now allowed so no need to throw anymore, this likely happens at the end of a program anyway 9603 //currentClipRectangle = arsd.color.Rectangle(0, 0, window.width, window.height); 9604 currentClipRectangle = arsd.color.Rectangle(short.min, short.min, short.max, short.max); 9605 if(window.activeScreenPainter !is null) { 9606 impl = window.activeScreenPainter; 9607 if(impl.referenceCount == 0) { 9608 impl.window = window; 9609 impl.create(handle); 9610 } 9611 impl.manualInvalidations = manualInvalidations; 9612 impl.referenceCount++; 9613 // writeln("refcount ++ ", impl.referenceCount); 9614 } else { 9615 impl = new ScreenPainterImplementation; 9616 impl.window = window; 9617 impl.create(handle); 9618 impl.referenceCount = 1; 9619 impl.manualInvalidations = manualInvalidations; 9620 window.activeScreenPainter = impl; 9621 // writeln("constructed"); 9622 } 9623 9624 copyActiveOriginals(); 9625 } 9626 9627 /++ 9628 EXPERIMENTAL. subject to change. 9629 9630 When you draw a cursor, you can draw this to notify your window of where it is, 9631 for IME systems to use. 9632 +/ 9633 void notifyCursorPosition(int x, int y, int width, int height) { 9634 if(auto w = cast(SimpleWindow) window) { 9635 w.setIMEPopupLocation(x + _originX + width, y + _originY + height); 9636 } 9637 } 9638 9639 /++ 9640 If you are using manual invalidations, this informs the 9641 window system that a section needs to be redrawn. 9642 9643 If you didn't opt into manual invalidation, you don't 9644 have to call this. 9645 9646 History: 9647 Added December 30, 2021 (dub v10.5) 9648 +/ 9649 void invalidateRect(Rectangle rect) { 9650 if(impl is null) return; 9651 9652 // transform(rect) 9653 rect.left += _originX; 9654 rect.right += _originX; 9655 rect.top += _originY; 9656 rect.bottom += _originY; 9657 9658 impl.invalidateRect(rect); 9659 } 9660 9661 private Pen originalPen; 9662 private Color originalFillColor; 9663 private arsd.color.Rectangle originalClipRectangle; 9664 private OperatingSystemFont originalFont; 9665 void copyActiveOriginals() { 9666 if(impl is null) return; 9667 originalPen = impl._activePen; 9668 originalFillColor = impl._fillColor; 9669 originalClipRectangle = impl._clipRectangle; 9670 version(OSXCocoa) {} else 9671 originalFont = impl._activeFont; 9672 } 9673 9674 ~this() { 9675 if(impl is null) return; 9676 impl.referenceCount--; 9677 //writeln("refcount -- ", impl.referenceCount); 9678 if(impl.referenceCount == 0) { 9679 // writeln("destructed"); 9680 impl.dispose(); 9681 *window.activeScreenPainter = ScreenPainterImplementation.init; 9682 // writeln("paint finished"); 9683 } else { 9684 // there is still an active reference, reset stuff so the 9685 // next user doesn't get weirdness via the reference 9686 this.rasterOp = RasterOp.normal; 9687 pen = originalPen; 9688 fillColor = originalFillColor; 9689 if(originalFont) 9690 setFont(originalFont); 9691 impl.setClipRectangle(originalClipRectangle.left, originalClipRectangle.top, originalClipRectangle.width, originalClipRectangle.height); 9692 } 9693 } 9694 9695 this(this) { 9696 if(impl is null) return; 9697 impl.referenceCount++; 9698 //writeln("refcount ++ ", impl.referenceCount); 9699 9700 copyActiveOriginals(); 9701 } 9702 9703 private int _originX; 9704 private int _originY; 9705 @property int originX() { return _originX; } 9706 @property int originY() { return _originY; } 9707 @property int originX(int a) { 9708 _originX = a; 9709 return _originX; 9710 } 9711 @property int originY(int a) { 9712 _originY = a; 9713 return _originY; 9714 } 9715 arsd.color.Rectangle currentClipRectangle; // set BEFORE doing any transformations 9716 private void transform(ref Point p) { 9717 if(impl is null) return; 9718 p.x += _originX; 9719 p.y += _originY; 9720 } 9721 9722 // this needs to be checked BEFORE the originX/Y transformation 9723 private bool isClipped(Point p) { 9724 return !currentClipRectangle.contains(p); 9725 } 9726 private bool isClipped(Point p, int width, int height) { 9727 return !currentClipRectangle.overlaps(arsd.color.Rectangle(p, Size(width + 1, height + 1))); 9728 } 9729 private bool isClipped(Point p, Size s) { 9730 return !currentClipRectangle.overlaps(arsd.color.Rectangle(p, Size(s.width + 1, s.height + 1))); 9731 } 9732 private bool isClipped(Point p, Point p2) { 9733 // need to ensure the end points are actually included inside, so the +1 does that 9734 return !currentClipRectangle.overlaps(arsd.color.Rectangle(p, p2 + Point(1, 1))); 9735 } 9736 9737 9738 /++ 9739 Sets the clipping region for drawing. If width == 0 && height == 0, disabled clipping. 9740 9741 Returns: 9742 The old clip rectangle. 9743 9744 History: 9745 Return value was `void` prior to May 10, 2021. 9746 9747 +/ 9748 arsd.color.Rectangle setClipRectangle(Point pt, int width, int height) { 9749 if(impl is null) return currentClipRectangle; 9750 if(pt == currentClipRectangle.upperLeft && width == currentClipRectangle.width && height == currentClipRectangle.height) 9751 return currentClipRectangle; // no need to do anything 9752 auto old = currentClipRectangle; 9753 currentClipRectangle = arsd.color.Rectangle(pt, Size(width, height)); 9754 transform(pt); 9755 9756 impl.setClipRectangle(pt.x, pt.y, width, height); 9757 9758 return old; 9759 } 9760 9761 /// ditto 9762 arsd.color.Rectangle setClipRectangle(arsd.color.Rectangle rect) { 9763 if(impl is null) return currentClipRectangle; 9764 return setClipRectangle(rect.upperLeft, rect.width, rect.height); 9765 } 9766 9767 /// 9768 void setFont(OperatingSystemFont font) { 9769 if(impl is null) return; 9770 impl.setFont(font); 9771 } 9772 9773 /// 9774 int fontHeight() { 9775 if(impl is null) return 0; 9776 return impl.fontHeight(); 9777 } 9778 9779 private Pen activePen; 9780 9781 /// 9782 @property void pen(Pen p) { 9783 if(impl is null) return; 9784 activePen = p; 9785 impl.pen(p); 9786 } 9787 9788 /// 9789 @scriptable 9790 @property void outlineColor(Color c) { 9791 if(impl is null) return; 9792 if(activePen.color == c) 9793 return; 9794 activePen.color = c; 9795 impl.pen(activePen); 9796 } 9797 9798 /// 9799 @scriptable 9800 @property void fillColor(Color c) { 9801 if(impl is null) return; 9802 impl.fillColor(c); 9803 } 9804 9805 /// 9806 @property void rasterOp(RasterOp op) { 9807 if(impl is null) return; 9808 impl.rasterOp(op); 9809 } 9810 9811 9812 void updateDisplay() { 9813 // FIXME this should do what the dtor does 9814 } 9815 9816 /// 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) 9817 void scrollArea(Point upperLeft, int width, int height, int dx, int dy) { 9818 if(impl is null) return; 9819 if(isClipped(upperLeft, width, height)) return; 9820 transform(upperLeft); 9821 version(Windows) { 9822 // http://msdn.microsoft.com/en-us/library/windows/desktop/bb787589%28v=vs.85%29.aspx 9823 RECT scroll = RECT(upperLeft.x, upperLeft.y, upperLeft.x + width, upperLeft.y + height); 9824 RECT clip = scroll; 9825 RECT uncovered; 9826 HRGN hrgn; 9827 if(!ScrollDC(impl.hdc, -dx, -dy, &scroll, &clip, hrgn, &uncovered)) 9828 throw new WindowsApiException("ScrollDC", GetLastError()); 9829 9830 } else version(X11) { 9831 // FIXME: clip stuff outside this rectangle 9832 XCopyArea(impl.display, impl.d, impl.d, impl.gc, upperLeft.x, upperLeft.y, width, height, upperLeft.x - dx, upperLeft.y - dy); 9833 } else version(OSXCocoa) { 9834 throw new NotYetImplementedException(); 9835 } else static assert(0); 9836 } 9837 9838 /// 9839 void clear(Color color = Color.white()) { 9840 if(impl is null) return; 9841 fillColor = color; 9842 outlineColor = color; 9843 drawRectangle(Point(0, 0), window.width, window.height); 9844 } 9845 9846 /++ 9847 Draws a pixmap (represented by the [Sprite] class) on the drawable. 9848 9849 Params: 9850 upperLeft = point on the window where the upper left corner of the image will be drawn 9851 imageUpperLeft = point on the image to start the slice to draw 9852 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. 9853 History: 9854 The `imageUpperLeft` and `sliceSize` parameters were added on March 11, 2021 (dub v9.3.0) 9855 +/ 9856 void drawPixmap(Sprite s, Point upperLeft, Point imageUpperLeft = Point(0, 0), Size sliceSize = Size(0, 0)) { 9857 if(impl is null) return; 9858 if(isClipped(upperLeft, s.width, s.height)) return; 9859 transform(upperLeft); 9860 impl.drawPixmap(s, upperLeft.x, upperLeft.y, imageUpperLeft.x, imageUpperLeft.y, sliceSize.width, sliceSize.height); 9861 } 9862 9863 /// 9864 void drawImage(Point upperLeft, Image i, Point upperLeftOfImage = Point(0, 0), int w = 0, int h = 0) { 9865 if(impl is null) return; 9866 //if(isClipped(upperLeft, w, h)) return; // FIXME 9867 transform(upperLeft); 9868 if(w == 0 || w > i.width) 9869 w = i.width; 9870 if(h == 0 || h > i.height) 9871 h = i.height; 9872 if(upperLeftOfImage.x < 0) 9873 upperLeftOfImage.x = 0; 9874 if(upperLeftOfImage.y < 0) 9875 upperLeftOfImage.y = 0; 9876 9877 impl.drawImage(upperLeft.x, upperLeft.y, i, upperLeftOfImage.x, upperLeftOfImage.y, w, h); 9878 } 9879 9880 /// 9881 Size textSize(in char[] text) { 9882 if(impl is null) return Size(0, 0); 9883 return impl.textSize(text); 9884 } 9885 9886 /++ 9887 Draws a string in the window with the set font (see [setFont] to change it). 9888 9889 Params: 9890 upperLeft = the upper left point of the bounding box of the text 9891 text = the string to draw 9892 lowerRight = the lower right point of the bounding box of the text. If 0, 0, there is no lower right bound. 9893 alignment = A [arsd.docs.general_concepts#bitflags|combination] of [TextAlignment] flags 9894 +/ 9895 @scriptable 9896 void drawText(Point upperLeft, in char[] text, Point lowerRight = Point(0, 0), uint alignment = 0) { 9897 if(impl is null) return; 9898 if(lowerRight.x != 0 || lowerRight.y != 0) { 9899 if(isClipped(upperLeft, lowerRight)) return; 9900 transform(lowerRight); 9901 } else { 9902 if(isClipped(upperLeft, textSize(text))) return; 9903 } 9904 transform(upperLeft); 9905 impl.drawText(upperLeft.x, upperLeft.y, lowerRight.x, lowerRight.y, text, alignment); 9906 } 9907 9908 /++ 9909 Draws text using a custom font. 9910 9911 This is still MAJOR work in progress. 9912 9913 Creating a [DrawableFont] can be tricky and require additional dependencies. 9914 +/ 9915 void drawText(DrawableFont font, Point upperLeft, in char[] text) { 9916 if(impl is null) return; 9917 if(isClipped(upperLeft, Point(int.max, int.max))) return; 9918 transform(upperLeft); 9919 font.drawString(this, upperLeft, text); 9920 } 9921 9922 version(Windows) 9923 void drawText(Point upperLeft, scope const(wchar)[] text) { 9924 if(impl is null) return; 9925 if(isClipped(upperLeft, Point(int.max, int.max))) return; 9926 transform(upperLeft); 9927 9928 if(text.length && text[$-1] == '\n') 9929 text = text[0 .. $-1]; // tailing newlines are weird on windows... 9930 9931 TextOutW(impl.hdc, upperLeft.x, upperLeft.y, text.ptr, cast(int) text.length); 9932 } 9933 9934 static struct TextDrawingContext { 9935 Point boundingBoxUpperLeft; 9936 Point boundingBoxLowerRight; 9937 9938 Point currentLocation; 9939 9940 Point lastDrewUpperLeft; 9941 Point lastDrewLowerRight; 9942 9943 // how do i do right aligned rich text? 9944 // i kinda want to do a pre-made drawing then right align 9945 // draw the whole block. 9946 // 9947 // That's exactly the diff: inline vs block stuff. 9948 9949 // I need to get coordinates of an inline section out too, 9950 // not just a bounding box, but a series of bounding boxes 9951 // should be ok. Consider what's needed to detect a click 9952 // on a link in the middle of a paragraph breaking a line. 9953 // 9954 // Generally, we should be able to get the rectangles of 9955 // any portion we draw. 9956 // 9957 // It also needs to tell what text is left if it overflows 9958 // out of the box, so we can do stuff like float images around 9959 // it. It should not attempt to draw a letter that would be 9960 // clipped. 9961 // 9962 // I might also turn off word wrap stuff. 9963 } 9964 9965 void drawText(TextDrawingContext context, in char[] text, uint alignment = 0) { 9966 if(impl is null) return; 9967 // FIXME 9968 } 9969 9970 /// Drawing an individual pixel is slow. Avoid it if possible. 9971 void drawPixel(Point where) { 9972 if(impl is null) return; 9973 if(isClipped(where)) return; 9974 transform(where); 9975 impl.drawPixel(where.x, where.y); 9976 } 9977 9978 9979 /// Draws a pen using the current pen / outlineColor 9980 @scriptable 9981 void drawLine(Point starting, Point ending) { 9982 if(impl is null) return; 9983 if(isClipped(starting, ending)) return; 9984 transform(starting); 9985 transform(ending); 9986 impl.drawLine(starting.x, starting.y, ending.x, ending.y); 9987 } 9988 9989 /// Draws a rectangle using the current pen/outline color for the border and brush/fill color for the insides 9990 /// The outer lines, inclusive of x = 0, y = 0, x = width - 1, and y = height - 1 are drawn with the outlineColor 9991 /// The rest of the pixels are drawn with the fillColor. If fillColor is transparent, those pixels are not drawn. 9992 @scriptable 9993 void drawRectangle(Point upperLeft, int width, int height) { 9994 if(impl is null) return; 9995 if(isClipped(upperLeft, width, height)) return; 9996 transform(upperLeft); 9997 impl.drawRectangle(upperLeft.x, upperLeft.y, width, height); 9998 } 9999 10000 /// ditto 10001 void drawRectangle(Point upperLeft, Size size) { 10002 if(impl is null) return; 10003 if(isClipped(upperLeft, size.width, size.height)) return; 10004 transform(upperLeft); 10005 impl.drawRectangle(upperLeft.x, upperLeft.y, size.width, size.height); 10006 } 10007 10008 /// ditto 10009 void drawRectangle(Point upperLeft, Point lowerRightInclusive) { 10010 if(impl is null) return; 10011 if(isClipped(upperLeft, lowerRightInclusive + Point(1, 1))) return; 10012 transform(upperLeft); 10013 transform(lowerRightInclusive); 10014 impl.drawRectangle(upperLeft.x, upperLeft.y, 10015 lowerRightInclusive.x - upperLeft.x + 1, lowerRightInclusive.y - upperLeft.y + 1); 10016 } 10017 10018 // overload added on May 12, 2021 10019 /// ditto 10020 void drawRectangle(Rectangle rect) { 10021 drawRectangle(rect.upperLeft, rect.size); 10022 } 10023 10024 /// Arguments are the points of the bounding rectangle 10025 void drawEllipse(Point upperLeft, Point lowerRight) { 10026 if(impl is null) return; 10027 if(isClipped(upperLeft, lowerRight)) return; 10028 transform(upperLeft); 10029 transform(lowerRight); 10030 impl.drawEllipse(upperLeft.x, upperLeft.y, lowerRight.x, lowerRight.y); 10031 } 10032 10033 /++ 10034 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. 10035 10036 10037 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. 10038 10039 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. 10040 10041 Bugs: 10042 They still don't exactly match in outlining the arc with straight lines (Windows does, Linux doesn't for now). 10043 10044 The arc outline on Linux sometimes goes over the target. 10045 10046 The fill on Windows sometimes stops short. 10047 10048 History: 10049 This function was broken af, totally inconsistent on platforms until September 24, 2021. 10050 10051 The interpretation of the final argument was incorrectly documented and implemented until August 2, 2024. 10052 +/ 10053 void drawArc(Point upperLeft, int width, int height, int start, int length) { 10054 if(impl is null) return; 10055 // FIXME: not actually implemented 10056 if(isClipped(upperLeft, width, height)) return; 10057 transform(upperLeft); 10058 impl.drawArc(upperLeft.x, upperLeft.y, width, height, start, length); 10059 } 10060 10061 /// this function draws a circle with the drawEllipse() function above, it requires the upper left point and the radius 10062 void drawCircle(Point upperLeft, int diameter) { 10063 drawEllipse(upperLeft, Point(upperLeft.x + diameter, upperLeft.y + diameter)); 10064 } 10065 10066 /++ 10067 Draws a rectangle with rounded corners. It is outlined with the current foreground pen and filled with the current background brush. 10068 10069 10070 Bugs: 10071 Not implemented on Mac; it will instead draw a non-rounded rectangle for now. 10072 10073 History: 10074 Added August 3, 2024 10075 +/ 10076 void drawRectangleRounded(Rectangle rect, int borderRadius) { 10077 drawRectangleRounded(rect.upperLeft, rect.lowerRight, borderRadius); 10078 } 10079 10080 /// ditto 10081 void drawRectangleRounded(Point upperLeft, Size size, int borderRadius) { 10082 drawRectangleRounded(upperLeft, upperLeft + Point(size.width, size.height), borderRadius); 10083 } 10084 10085 /// ditto 10086 void drawRectangleRounded(Point upperLeft, Point lowerRight, int borderRadius) { 10087 if(borderRadius <= 0) { 10088 drawRectangle(upperLeft, lowerRight); 10089 return; 10090 } 10091 10092 transform(upperLeft); 10093 transform(lowerRight); 10094 10095 impl.drawRectangleRounded(upperLeft, lowerRight, borderRadius); 10096 } 10097 10098 /// . 10099 void drawPolygon(Point[] vertexes) { 10100 if(impl is null) return; 10101 assert(vertexes.length); 10102 int minX = int.max, minY = int.max, maxX = int.min, maxY = int.min; 10103 foreach(ref vertex; vertexes) { 10104 if(vertex.x < minX) 10105 minX = vertex.x; 10106 if(vertex.y < minY) 10107 minY = vertex.y; 10108 if(vertex.x > maxX) 10109 maxX = vertex.x; 10110 if(vertex.y > maxY) 10111 maxY = vertex.y; 10112 transform(vertex); 10113 } 10114 if(isClipped(Point(minX, maxY), Point(maxX + 1, maxY + 1))) return; 10115 impl.drawPolygon(vertexes); 10116 } 10117 10118 /// ditto 10119 void drawPolygon(Point[] vertexes...) { 10120 if(impl is null) return; 10121 drawPolygon(vertexes); 10122 } 10123 10124 10125 // and do a draw/fill in a single call maybe. Windows can do it... but X can't, though it could do two calls. 10126 10127 //mixin NativeScreenPainterImplementation!() impl; 10128 10129 10130 // HACK: if I mixin the impl directly, it won't let me override the copy 10131 // constructor! The linker complains about there being multiple definitions. 10132 // I'll make the best of it and reference count it though. 10133 ScreenPainterImplementation* impl; 10134 } 10135 10136 // HACK: I need a pointer to the implementation so it's separate 10137 struct ScreenPainterImplementation { 10138 CapableOfBeingDrawnUpon window; 10139 int referenceCount; 10140 mixin NativeScreenPainterImplementation!(); 10141 } 10142 10143 // FIXME: i haven't actually tested the sprite class on MS Windows 10144 10145 /** 10146 Sprites are optimized for fast drawing on the screen, but slow for direct pixel 10147 access. They are best for drawing a relatively unchanging image repeatedly on the screen. 10148 10149 10150 On X11, this corresponds to an `XPixmap`. On Windows, it still uses a bitmap, 10151 though I'm not sure that's ideal and the implementation might change. 10152 10153 You create one by giving a window and an image. It optimizes for that window, 10154 and copies the image into it to use as the initial picture. Creating a sprite 10155 can be quite slow (especially over a network connection) so you should do it 10156 as little as possible and just hold on to your sprite handles after making them. 10157 simpledisplay does try to do its best though, using the XSHM extension if available, 10158 but you should still write your code as if it will always be slow. 10159 10160 Then you can use `sprite.drawAt(painter, point);` to draw it, which should be 10161 a fast operation - much faster than drawing the Image itself every time. 10162 10163 `Sprite` represents a scarce resource which should be freed when you 10164 are done with it. Use the `dispose` method to do this. Do not use a `Sprite` 10165 after it has been disposed. If you are unsure about this, don't take chances, 10166 just let the garbage collector do it for you. But ideally, you can manage its 10167 lifetime more efficiently. 10168 10169 $(NOTE `Sprite`, like the rest of simpledisplay's `ScreenPainter`, does not 10170 support alpha blending in its drawing at this time. That might change in the 10171 future, but if you need alpha blending right now, use OpenGL instead. See 10172 `gamehelpers.d` for a similar class to `Sprite` that uses OpenGL: `OpenGlTexture`.) 10173 10174 Update: on April 23, 2021, I finally added alpha blending support. You must opt 10175 in by setting the enableAlpha = true in the constructor. 10176 */ 10177 class Sprite : CapableOfBeingDrawnUpon { 10178 10179 /// 10180 ScreenPainter draw() { 10181 return ScreenPainter(this, handle, false); 10182 } 10183 10184 /++ 10185 Copies the sprite's current state into a [TrueColorImage]. 10186 10187 Be warned: this can be a very slow operation 10188 10189 History: 10190 Actually implemented on March 14, 2021 10191 +/ 10192 TrueColorImage takeScreenshot() { 10193 return trueColorImageFromNativeHandle(handle, width, height); 10194 } 10195 10196 void delegate() paintingFinishedDg() { return null; } 10197 bool closed() { return false; } 10198 ScreenPainterImplementation* activeScreenPainter_; 10199 protected ScreenPainterImplementation* activeScreenPainter() { return activeScreenPainter_; } 10200 protected void activeScreenPainter(ScreenPainterImplementation* i) { activeScreenPainter_ = i; } 10201 10202 version(Windows) 10203 private ubyte* rawData; 10204 // FIXME: sprites are lost when disconnecting from X! We need some way to invalidate them... 10205 // ditto on the XPicture stuff 10206 10207 version(X11) { 10208 private static XRenderPictFormat* RGB24; 10209 private static XRenderPictFormat* ARGB32; 10210 10211 private Picture xrenderPicture; 10212 } 10213 10214 version(X11) 10215 private static void requireXRender() { 10216 if(!XRenderLibrary.loadAttempted) { 10217 XRenderLibrary.loadDynamicLibrary(); 10218 } 10219 10220 if(!XRenderLibrary.loadSuccessful) 10221 throw new Exception("XRender library load failure"); 10222 10223 auto display = XDisplayConnection.get; 10224 10225 // FIXME: if we migrate X displays, these need to be changed 10226 if(RGB24 is null) 10227 RGB24 = XRenderFindStandardFormat(display, PictStandardRGB24); 10228 if(ARGB32 is null) 10229 ARGB32 = XRenderFindStandardFormat(display, PictStandardARGB32); 10230 } 10231 10232 protected this() {} 10233 10234 this(SimpleWindow win, int width, int height, bool enableAlpha = false) { 10235 this._width = width; 10236 this._height = height; 10237 this.enableAlpha = enableAlpha; 10238 10239 version(X11) { 10240 auto display = XDisplayConnection.get(); 10241 10242 if(enableAlpha) { 10243 requireXRender(); 10244 } 10245 10246 handle = XCreatePixmap(display, cast(Drawable) win.window, width, height, enableAlpha ? 32 : DefaultDepthOfDisplay(display)); 10247 10248 if(enableAlpha) { 10249 XRenderPictureAttributes attrs; 10250 xrenderPicture = XRenderCreatePicture(display, handle, ARGB32, 0, &attrs); 10251 } 10252 } else version(Windows) { 10253 version(CRuntime_DigitalMars) { 10254 //if(enableAlpha) 10255 //throw new Exception("Alpha support not available, try recompiling with -m32mscoff"); 10256 } 10257 10258 BITMAPINFO infoheader; 10259 infoheader.bmiHeader.biSize = infoheader.bmiHeader.sizeof; 10260 infoheader.bmiHeader.biWidth = width; 10261 infoheader.bmiHeader.biHeight = height; 10262 infoheader.bmiHeader.biPlanes = 1; 10263 infoheader.bmiHeader.biBitCount = enableAlpha ? 32 : 24; 10264 infoheader.bmiHeader.biCompression = BI_RGB; 10265 10266 // FIXME: this should prolly be a device dependent bitmap... 10267 handle = CreateDIBSection( 10268 null, 10269 &infoheader, 10270 DIB_RGB_COLORS, 10271 cast(void**) &rawData, 10272 null, 10273 0); 10274 10275 if(handle is null) 10276 throw new WindowsApiException("couldn't create pixmap", GetLastError()); 10277 } 10278 } 10279 10280 /// Makes a sprite based on the image with the initial contents from the Image 10281 this(SimpleWindow win, Image i) { 10282 this(win, i.width, i.height, i.enableAlpha); 10283 10284 version(X11) { 10285 auto display = XDisplayConnection.get(); 10286 auto gc = XCreateGC(display, this.handle, 0, null); 10287 scope(exit) XFreeGC(display, gc); 10288 if(i.usingXshm) 10289 XShmPutImage(display, cast(Drawable) handle, gc, i.handle, 0, 0, 0, 0, i.width, i.height, false); 10290 else 10291 XPutImage(display, cast(Drawable) handle, gc, i.handle, 0, 0, 0, 0, i.width, i.height); 10292 } else version(Windows) { 10293 auto itemsPerLine = enableAlpha ? (4 * width) : (((cast(int) width * 3 + 3) / 4) * 4); 10294 auto arrLength = itemsPerLine * height; 10295 rawData[0..arrLength] = i.rawData[0..arrLength]; 10296 } else version(OSXCocoa) { 10297 // FIXME: I have no idea if this is even any good 10298 auto colorSpace = CGColorSpaceCreateDeviceRGB(); 10299 handle = CGBitmapContextCreate(null, width, height, 8, 4*width, 10300 colorSpace, 10301 kCGImageAlphaPremultipliedLast 10302 |kCGBitmapByteOrder32Big); 10303 CGColorSpaceRelease(colorSpace); 10304 auto rawData = CGBitmapContextGetData(handle); 10305 10306 auto rdl = (width * height * 4); 10307 rawData[0 .. rdl] = i.rawData[0 .. rdl]; 10308 } else static assert(0); 10309 } 10310 10311 /++ 10312 Draws the image on the specified painter at the specified point. The point is the upper-left point where the image will be drawn. 10313 10314 Params: 10315 where = point on the window where the upper left corner of the image will be drawn 10316 imageUpperLeft = point on the image to start the slice to draw 10317 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. 10318 History: 10319 The `imageUpperLeft` and `sliceSize` parameters were added on March 11, 2021 (dub v9.3.0) 10320 +/ 10321 void drawAt(ScreenPainter painter, Point where, Point imageUpperLeft = Point(0, 0), Size sliceSize = Size(0, 0)) { 10322 painter.drawPixmap(this, where, imageUpperLeft, sliceSize); 10323 } 10324 10325 /// Call this when you're ready to get rid of it 10326 void dispose() { 10327 version(X11) { 10328 staticDispose(xrenderPicture, handle); 10329 xrenderPicture = None; 10330 handle = None; 10331 } else version(Windows) { 10332 staticDispose(handle); 10333 handle = null; 10334 } else version(OSXCocoa) { 10335 staticDispose(handle); 10336 handle = null; 10337 } else static assert(0); 10338 10339 } 10340 10341 version(X11) 10342 static void staticDispose(Picture xrenderPicture, Pixmap handle) { 10343 if(xrenderPicture) 10344 XRenderFreePicture(XDisplayConnection.get, xrenderPicture); 10345 if(handle) 10346 XFreePixmap(XDisplayConnection.get(), handle); 10347 } 10348 else version(Windows) 10349 static void staticDispose(HBITMAP handle) { 10350 if(handle) 10351 DeleteObject(handle); 10352 } 10353 else version(OSXCocoa) 10354 static void staticDispose(CGContextRef context) { 10355 if(context) 10356 CGContextRelease(context); 10357 } 10358 10359 ~this() { 10360 version(X11) { if(xrenderPicture || handle) 10361 cleanupQueue.queue!staticDispose(xrenderPicture, handle); 10362 } else version(Windows) { if(handle) 10363 cleanupQueue.queue!staticDispose(handle); 10364 } else version(OSXCocoa) { if(handle) 10365 cleanupQueue.queue!staticDispose(handle); 10366 } else static assert(0); 10367 } 10368 10369 /// 10370 final @property int width() { return _width; } 10371 10372 /// 10373 final @property int height() { return _height; } 10374 10375 /// 10376 static Sprite fromMemoryImage(SimpleWindow win, MemoryImage img, bool enableAlpha = false) { 10377 return new Sprite(win, Image.fromMemoryImage(img, enableAlpha)); 10378 } 10379 10380 auto nativeHandle() { 10381 return handle; 10382 } 10383 10384 private: 10385 10386 int _width; 10387 int _height; 10388 bool enableAlpha; 10389 version(X11) 10390 Pixmap handle; 10391 else version(Windows) 10392 HBITMAP handle; 10393 else version(OSXCocoa) 10394 CGContextRef handle; 10395 else version(Emscripten) 10396 void* handle; 10397 else static assert(0); 10398 } 10399 10400 /++ 10401 Represents a display-side gradient pseudo-image. Actually construct it with [LinearGradient], [RadialGradient], or [ConicalGradient]. 10402 10403 History: 10404 Added November 20, 2021 (dub v10.4) 10405 +/ 10406 version(OSXCocoa) {} else // NotYetImplementedException 10407 abstract class Gradient : Sprite { 10408 protected this(int w, int h) { 10409 version(X11) { 10410 Sprite.requireXRender(); 10411 10412 super(); 10413 enableAlpha = true; 10414 _width = w; 10415 _height = h; 10416 } else version(Windows) { 10417 super(null, w, h, true); // on Windows i'm just making a bitmap myself 10418 } 10419 } 10420 10421 version(Windows) 10422 final void forEachPixel(scope Color delegate(int x, int y) dg) @system { 10423 auto ptr = rawData; 10424 foreach(j; 0 .. _height) 10425 foreach(i; 0 .. _width) { 10426 auto color = dg(i, _height - j - 1); // cuz of upside down bitmap 10427 *rawData = (color.a * color.b) / 255; rawData++; 10428 *rawData = (color.a * color.g) / 255; rawData++; 10429 *rawData = (color.a * color.r) / 255; rawData++; 10430 *rawData = color.a; rawData++; 10431 } 10432 } 10433 10434 version(X11) 10435 protected void helper(scope Stop[] stops, scope Picture delegate(scope XFixed[] stopsPositions, scope XRenderColor[] colors) dg) { 10436 assert(stops.length > 0); 10437 assert(stops.length <= 16, "I got lazy with buffers"); 10438 10439 XFixed[16] stopsPositions = void; 10440 XRenderColor[16] colors = void; 10441 10442 foreach(idx, stop; stops) { 10443 stopsPositions[idx] = cast(int)(stop.percentage * ushort.max); 10444 auto c = stop.c; 10445 colors[idx] = XRenderColor( 10446 cast(ushort)(c.r * ushort.max / 255), 10447 cast(ushort)(c.g * ushort.max / 255), 10448 cast(ushort)(c.b * ushort.max / 255), 10449 cast(ushort)(c.a * ubyte.max) // max value here is fractional 10450 ); 10451 } 10452 10453 xrenderPicture = dg(stopsPositions, colors); 10454 } 10455 10456 /// 10457 static struct Stop { 10458 float percentage; /// between 0 and 1.0 10459 Color c; 10460 } 10461 } 10462 10463 /++ 10464 Creates a linear gradient between p1 and p2. 10465 10466 X ONLY RIGHT NOW 10467 10468 History: 10469 Added November 20, 2021 (dub v10.4) 10470 10471 Bugs: 10472 Not yet implemented on Windows. 10473 +/ 10474 version(OSXCocoa) {} else // NotYetImplementedException 10475 class LinearGradient : Gradient { 10476 /++ 10477 10478 +/ 10479 this(Point p1, Point p2, Stop[] stops...) { 10480 super(p2.x, p2.y); 10481 10482 version(X11) { 10483 XLinearGradient gradient; 10484 gradient.p1 = XPointFixed(p1.x * ushort.max, p1.y * ushort.max); 10485 gradient.p2 = XPointFixed(p2.x * ushort.max, p2.y * ushort.max); 10486 10487 helper(stops, (scope XFixed[] stopsPositions, scope XRenderColor[] colors) { 10488 return XRenderCreateLinearGradient( 10489 XDisplayConnection.get, 10490 &gradient, 10491 stopsPositions.ptr, 10492 colors.ptr, 10493 cast(int) stops.length); 10494 }); 10495 } else version(Windows) { 10496 // FIXME 10497 forEachPixel((int x, int y) { 10498 import core.stdc.math; 10499 10500 //sqrtf( 10501 10502 return Color.transparent; 10503 // return Color(x * 2, y * 2, 0, 128); // this result is so beautiful 10504 }); 10505 } 10506 } 10507 } 10508 10509 /++ 10510 A conical gradient goes from color to color around a circumference from a center point. 10511 10512 X ONLY RIGHT NOW 10513 10514 History: 10515 Added November 20, 2021 (dub v10.4) 10516 10517 Bugs: 10518 Not yet implemented on Windows. 10519 +/ 10520 version(OSXCocoa) {} else // NotYetImplementedException 10521 class ConicalGradient : Gradient { 10522 /++ 10523 10524 +/ 10525 this(Point center, float angleInDegrees, Stop[] stops...) { 10526 super(center.x * 2, center.y * 2); 10527 10528 version(X11) { 10529 XConicalGradient gradient; 10530 gradient.center = XPointFixed(center.x * ushort.max, center.y * ushort.max); 10531 gradient.angle = cast(int)(angleInDegrees * ushort.max); 10532 10533 helper(stops, (scope XFixed[] stopsPositions, scope XRenderColor[] colors) { 10534 return XRenderCreateConicalGradient( 10535 XDisplayConnection.get, 10536 &gradient, 10537 stopsPositions.ptr, 10538 colors.ptr, 10539 cast(int) stops.length); 10540 }); 10541 } else version(Windows) { 10542 // FIXME 10543 forEachPixel((int x, int y) { 10544 import core.stdc.math; 10545 10546 //sqrtf( 10547 10548 return Color.transparent; 10549 // return Color(x * 2, y * 2, 0, 128); // this result is so beautiful 10550 }); 10551 10552 } 10553 } 10554 } 10555 10556 /++ 10557 A radial gradient goes from color to color based on distance from the center. 10558 It is like rings of color. 10559 10560 X ONLY RIGHT NOW 10561 10562 10563 More specifically, you create two circles: an inner circle and an outer circle. 10564 The gradient is only drawn in the area outside the inner circle but inside the outer 10565 circle. The closest line between those two circles forms the line for the gradient 10566 and the stops are calculated the same as the [LinearGradient]. Then it just sweeps around. 10567 10568 History: 10569 Added November 20, 2021 (dub v10.4) 10570 10571 Bugs: 10572 Not yet implemented on Windows. 10573 +/ 10574 version(OSXCocoa) {} else // NotYetImplementedException 10575 class RadialGradient : Gradient { 10576 /++ 10577 10578 +/ 10579 this(Point innerCenter, float innerRadius, Point outerCenter, float outerRadius, Stop[] stops...) { 10580 super(cast(int)(outerCenter.x + outerRadius + 0.5), cast(int)(outerCenter.y + outerRadius + 0.5)); 10581 10582 version(X11) { 10583 XRadialGradient gradient; 10584 gradient.inner = XCircle(innerCenter.x * ushort.max, innerCenter.y * ushort.max, cast(int) (innerRadius * ushort.max)); 10585 gradient.outer = XCircle(outerCenter.x * ushort.max, outerCenter.y * ushort.max, cast(int) (outerRadius * ushort.max)); 10586 10587 helper(stops, (scope XFixed[] stopsPositions, scope XRenderColor[] colors) { 10588 return XRenderCreateRadialGradient( 10589 XDisplayConnection.get, 10590 &gradient, 10591 stopsPositions.ptr, 10592 colors.ptr, 10593 cast(int) stops.length); 10594 }); 10595 } else version(Windows) { 10596 // FIXME 10597 forEachPixel((int x, int y) { 10598 import core.stdc.math; 10599 10600 //sqrtf( 10601 10602 return Color.transparent; 10603 // return Color(x * 2, y * 2, 0, 128); // this result is so beautiful 10604 }); 10605 } 10606 } 10607 } 10608 10609 10610 10611 /+ 10612 NOT IMPLEMENTED 10613 10614 A display-stored image optimized for relatively quick drawing, like 10615 [Sprite], but this one supports alpha channel blending and does NOT 10616 support direct drawing upon it with a [ScreenPainter]. 10617 10618 You can think of it as an [arsd.game.OpenGlTexture] for usage with a 10619 plain [ScreenPainter]... sort of. 10620 10621 On X11, it requires the Xrender extension and library. This is available 10622 almost everywhere though. 10623 10624 History: 10625 Added November 14, 2020 but NOT ACTUALLY IMPLEMENTED 10626 +/ 10627 version(none) 10628 class AlphaSprite { 10629 /++ 10630 Copies the given image into it. 10631 +/ 10632 this(MemoryImage img) { 10633 10634 if(!XRenderLibrary.loadAttempted) { 10635 XRenderLibrary.loadDynamicLibrary(); 10636 10637 // FIXME: this needs to be reconstructed when the X server changes 10638 repopulateX(); 10639 } 10640 if(!XRenderLibrary.loadSuccessful) 10641 throw new Exception("XRender library load failure"); 10642 10643 // I probably need to put the alpha mask in a separate Picture 10644 // ugh 10645 // maybe the Sprite itself can have an alpha bitmask anyway 10646 10647 10648 auto display = XDisplayConnection.get(); 10649 pixmap = XCreatePixmap(display, cast(Drawable) win.window, width, height, DefaultDepthOfDisplay(display)); 10650 10651 10652 XRenderPictureAttributes attrs; 10653 10654 handle = XRenderCreatePicture( 10655 XDisplayConnection.get, 10656 pixmap, 10657 RGBA, 10658 0, 10659 &attrs 10660 ); 10661 10662 } 10663 10664 // maybe i'll use the create gradient functions too with static factories.. 10665 10666 void drawAt(ScreenPainter painter, Point where) { 10667 //painter.drawPixmap(this, where); 10668 10669 XRenderPictureAttributes attrs; 10670 10671 auto pic = XRenderCreatePicture( 10672 XDisplayConnection.get, 10673 painter.impl.d, 10674 RGB, 10675 0, 10676 &attrs 10677 ); 10678 10679 XRenderComposite( 10680 XDisplayConnection.get, 10681 3, // PictOpOver 10682 handle, 10683 None, 10684 pic, 10685 0, // src 10686 0, 10687 0, // mask 10688 0, 10689 10, // dest 10690 10, 10691 100, // width 10692 100 10693 ); 10694 10695 /+ 10696 XRenderFreePicture( 10697 XDisplayConnection.get, 10698 pic 10699 ); 10700 10701 XRenderFreePicture( 10702 XDisplayConnection.get, 10703 fill 10704 ); 10705 +/ 10706 // on Windows you can stretch but Xrender still can't :( 10707 } 10708 10709 static XRenderPictFormat* RGB; 10710 static XRenderPictFormat* RGBA; 10711 static void repopulateX() { 10712 auto display = XDisplayConnection.get; 10713 RGB = XRenderFindStandardFormat(display, PictStandardRGB24); 10714 RGBA = XRenderFindStandardFormat(display, PictStandardARGB32); 10715 } 10716 10717 XPixmap pixmap; 10718 Picture handle; 10719 } 10720 10721 /// 10722 interface CapableOfBeingDrawnUpon { 10723 /// 10724 ScreenPainter draw(); 10725 /// 10726 int width(); 10727 /// 10728 int height(); 10729 protected ScreenPainterImplementation* activeScreenPainter(); 10730 protected void activeScreenPainter(ScreenPainterImplementation*); 10731 bool closed(); 10732 10733 void delegate() paintingFinishedDg(); 10734 10735 /// Be warned: this can be a very slow operation 10736 TrueColorImage takeScreenshot(); 10737 } 10738 10739 /// 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]. 10740 void flushGui() { 10741 version(X11) { 10742 auto dpy = XDisplayConnection.get(); 10743 XLockDisplay(dpy); 10744 scope(exit) XUnlockDisplay(dpy); 10745 XFlush(dpy); 10746 } 10747 } 10748 10749 /++ 10750 Runs the given code in the GUI thread when its event loop 10751 is available, blocking until it completes. This allows you 10752 to create and manipulate windows from another thread without 10753 invoking undefined behavior. 10754 10755 If this is the gui thread, it runs the code immediately. 10756 10757 If no gui thread exists yet, the current thread is assumed 10758 to be it. Attempting to create windows or run the event loop 10759 in any other thread will cause an assertion failure. 10760 10761 10762 $(TIP 10763 Did you know you can use UFCS on delegate literals? 10764 10765 () { 10766 // code here 10767 }.runInGuiThread; 10768 ) 10769 10770 Returns: 10771 `true` if the function was called, `false` if it was not. 10772 The function may not be called because the gui thread had 10773 already terminated by the time you called this. 10774 10775 History: 10776 Added April 10, 2020 (v7.2.0) 10777 10778 Return value added and implementation tweaked to avoid locking 10779 at program termination on February 24, 2021 (v9.2.1). 10780 +/ 10781 bool runInGuiThread(scope void delegate() dg) @trusted { 10782 claimGuiThread(); 10783 10784 if(thisIsGuiThread) { 10785 dg(); 10786 return true; 10787 } 10788 10789 if(guiThreadTerminating) 10790 return false; 10791 10792 import core.sync.semaphore; 10793 static Semaphore sc; 10794 if(sc is null) 10795 sc = new Semaphore(); 10796 10797 static RunQueueMember* rqm; 10798 if(rqm is null) 10799 rqm = new RunQueueMember; 10800 rqm.dg = cast(typeof(rqm.dg)) dg; 10801 rqm.signal = sc; 10802 rqm.thrown = null; 10803 10804 synchronized(runInGuiThreadLock) { 10805 runInGuiThreadQueue ~= rqm; 10806 } 10807 10808 if(!SimpleWindow.eventWakeUp()) 10809 throw new Error("runInGuiThread impossible; eventWakeUp failed"); 10810 10811 rqm.signal.wait(); 10812 auto t = rqm.thrown; 10813 10814 if(t) 10815 throw t; 10816 10817 return true; 10818 } 10819 10820 // note it runs sync if this is the gui thread.... 10821 void runInGuiThreadAsync(void delegate() dg, void delegate(Exception) nothrow handleError = null) nothrow { 10822 claimGuiThread(); 10823 10824 try { 10825 10826 if(thisIsGuiThread) { 10827 dg(); 10828 return; 10829 } 10830 10831 if(guiThreadTerminating) 10832 return; 10833 10834 RunQueueMember* rqm = new RunQueueMember; 10835 rqm.dg = cast(typeof(rqm.dg)) dg; 10836 rqm.signal = null; 10837 rqm.thrown = null; 10838 10839 synchronized(runInGuiThreadLock) { 10840 runInGuiThreadQueue ~= rqm; 10841 } 10842 10843 if(!SimpleWindow.eventWakeUp()) 10844 throw new Error("runInGuiThread impossible; eventWakeUp failed"); 10845 } catch(Exception e) { 10846 if(handleError) 10847 handleError(e); 10848 } 10849 } 10850 10851 private void runPendingRunInGuiThreadDelegates() { 10852 more: 10853 RunQueueMember* next; 10854 synchronized(runInGuiThreadLock) { 10855 if(runInGuiThreadQueue.length) { 10856 next = runInGuiThreadQueue[0]; 10857 runInGuiThreadQueue = runInGuiThreadQueue[1 .. $]; 10858 } else { 10859 next = null; 10860 } 10861 } 10862 10863 if(next) { 10864 try { 10865 next.dg(); 10866 next.thrown = null; 10867 } catch(Throwable t) { 10868 next.thrown = t; 10869 } 10870 10871 if(next.signal) 10872 next.signal.notify(); 10873 10874 goto more; 10875 } 10876 } 10877 10878 private void claimGuiThread() nothrow { 10879 import core.atomic; 10880 if(cas(&guiThreadExists_, false, true)) 10881 thisIsGuiThread = true; 10882 } 10883 10884 private struct RunQueueMember { 10885 void delegate() dg; 10886 import core.sync.semaphore; 10887 Semaphore signal; 10888 Throwable thrown; 10889 } 10890 10891 private __gshared RunQueueMember*[] runInGuiThreadQueue; 10892 private __gshared Object runInGuiThreadLock = new Object; // intentional CTFE 10893 private bool thisIsGuiThread = false; 10894 private shared bool guiThreadExists_ = false; 10895 private shared bool guiThreadTerminating = false; 10896 10897 /++ 10898 Returns `true` if a gui thread exists, that is, a thread running the simpledisplay.d 10899 event loop. All windows must be exclusively created and managed by a single thread. 10900 10901 If no gui thread exists, simpledisplay.d will automatically adopt the current thread 10902 when you call one of its constructors. 10903 10904 If a gui thread exists, you should check [thisThreadRunningGui] to see if it is this 10905 one. If so, you can run gui functions on it. If not, don't. The helper functions 10906 [runInGuiThread] and [runInGuiThreadAsync] can be used to help you with this automatically. 10907 10908 The reason this function is available is in case you want to message pass between a gui 10909 thread and your current thread. If no gui thread exists or if this is the gui thread, 10910 you're liable to deadlock when trying to communicate since you'd end up talking to yourself. 10911 10912 History: 10913 Added December 3, 2021 (dub v10.5) 10914 +/ 10915 public bool guiThreadExists() { 10916 return guiThreadExists_; 10917 } 10918 10919 /++ 10920 Returns `true` if this thread is either running or set to be running the 10921 simpledisplay.d gui core event loop because it owns windows. 10922 10923 It is important to keep gui-related functionality in the right thread, so you will 10924 want to `runInGuiThread` when you call them (with some specific exceptions called 10925 out in those specific functions' documentation). Notably, all windows must be 10926 created and managed only from the gui thread. 10927 10928 Will return false if simpledisplay's other functions haven't been called 10929 yet; check [guiThreadExists] in addition to this. 10930 10931 History: 10932 Added December 3, 2021 (dub v10.5) 10933 +/ 10934 public bool thisThreadRunningGui() { 10935 return thisIsGuiThread; 10936 } 10937 10938 /++ 10939 Function to help temporarily print debugging info. It will bypass any stdout/err redirection 10940 and go to the controlling tty or console (attaching to the parent and/or allocating one as 10941 needed on Windows. Please note it may overwrite output from other programs in the parent and the 10942 allocated one will not survive if your program crashes. Use the `fileOverride` to print to a log 10943 file instead if you are in one of those situations). 10944 10945 It does not support outputting very many types; just strings and ints are likely to actually work. 10946 10947 It will perform very slowly and swallows any errors that may occur. Moreover, the specific output 10948 is unspecified meaning I can change it at any time. The only point of this function is to help 10949 in temporary use for printf-style debugging. It is NOT nogc, but you can use the `debug` keyword 10950 and the compiler will cheat for you. It is, however, formally nothrow and trusted to ease its use 10951 in those contexts. 10952 10953 $(WARNING 10954 I reserve the right to change this function at any time. You can use it if it helps you 10955 but do not rely on it for anything permanent. 10956 ) 10957 10958 History: 10959 Added December 3, 2021. Not formally supported under any stable tag. 10960 +/ 10961 void sdpyPrintDebugString(string fileOverride = null, T...)(T t) nothrow @trusted { 10962 try { 10963 version(Windows) { 10964 import core.sys.windows.wincon; 10965 if(!AttachConsole(ATTACH_PARENT_PROCESS)) 10966 AllocConsole(); 10967 const(char)* fn = "CONOUT$"; 10968 } else version(Posix) { 10969 const(char)* fn = "/dev/tty"; 10970 } else static assert(0, "Function not implemented for your system"); 10971 10972 if(fileOverride.length) 10973 fn = fileOverride.ptr; 10974 10975 import core.stdc.stdio; 10976 auto fp = fopen(fn, "wt"); 10977 if(fp is null) return; 10978 scope(exit) fclose(fp); 10979 10980 string str; 10981 foreach(item; t) { 10982 static if(is(typeof(item) : const(char)[])) 10983 str ~= item; 10984 else 10985 str ~= toInternal!string(item); 10986 str ~= " "; 10987 } 10988 str ~= "\n"; 10989 10990 fwrite(str.ptr, 1, str.length, fp); 10991 fflush(fp); 10992 } catch(Exception e) { 10993 // sorry no hope 10994 } 10995 } 10996 10997 private void guiThreadFinalize() { 10998 assert(thisIsGuiThread); 10999 11000 guiThreadTerminating = true; // don't add any more from this point on 11001 runPendingRunInGuiThreadDelegates(); 11002 } 11003 11004 /+ 11005 interface IPromise { 11006 void reportProgress(int current, int max, string message); 11007 11008 /+ // not formally in cuz of templates but still 11009 IPromise Then(); 11010 IPromise Catch(); 11011 IPromise Finally(); 11012 +/ 11013 } 11014 11015 /+ 11016 auto promise = async({ ... }); 11017 promise.Then(whatever). 11018 Then(whateverelse). 11019 Catch((exception) { }); 11020 11021 11022 A promise is run inside a fiber and it looks something like: 11023 11024 try { 11025 auto res = whatever(); 11026 auto res2 = whateverelse(res); 11027 } catch(Exception e) { 11028 { }(e); 11029 } 11030 11031 When a thing succeeds, it is passed as an arg to the next 11032 +/ 11033 class Promise(T) : IPromise { 11034 auto Then() { return null; } 11035 auto Catch() { return null; } 11036 auto Finally() { return null; } 11037 11038 // wait for it to resolve and return the value, or rethrow the error if that occurred. 11039 // cannot be called from the gui thread, but this is caught at runtime instead of compile time. 11040 T await(); 11041 } 11042 11043 interface Task { 11044 } 11045 11046 interface Resolvable(T) : Task { 11047 void run(); 11048 11049 void resolve(T); 11050 11051 Resolvable!T then(void delegate(T)); // returns a new promise 11052 Resolvable!T error(Throwable); // js catch 11053 Resolvable!T completed(); // js finally 11054 11055 } 11056 11057 /++ 11058 Runs `work` in a helper thread and sends its return value back to the main gui 11059 thread as the argument to `uponCompletion`. If `work` throws, the exception is 11060 sent to the `uponThrown` if given, or if null, rethrown from the event loop to 11061 kill the program. 11062 11063 You can call reportProgress(position, max, message) to update your parent window 11064 on your progress. 11065 11066 I should also use `shared` methods. FIXME 11067 11068 History: 11069 Added March 6, 2021 (dub version 9.3). 11070 +/ 11071 void runInWorkerThread(T)(T delegate(Task) work, void delegate(T) uponCompletion) { 11072 uponCompletion(work(null)); 11073 } 11074 11075 +/ 11076 11077 /// Used internal to dispatch events to various classes. 11078 interface CapableOfHandlingNativeEvent { 11079 NativeEventHandler getNativeEventHandler(); 11080 11081 /*private*//*protected*/ __gshared CapableOfHandlingNativeEvent[NativeWindowHandle] nativeHandleMapping; 11082 11083 version(X11) { 11084 // if this is impossible, you are allowed to just throw from it 11085 // Note: if you call it from another object, set a flag cuz the manger will call you again 11086 void recreateAfterDisconnect(); 11087 // discard any *connection specific* state, but keep enough that you 11088 // can be recreated if possible. discardConnectionState() is always called immediately 11089 // before recreateAfterDisconnect(), so you can set a flag there to decide if 11090 // you need initialization order 11091 void discardConnectionState(); 11092 } 11093 } 11094 11095 version(X11) 11096 /++ 11097 State of keys on mouse events, especially motion. 11098 11099 Do not trust the actual integer values in this, they are platform-specific. Always use the names. 11100 +/ 11101 enum ModifierState : uint { 11102 shift = 1, /// 11103 capsLock = 2, /// 11104 ctrl = 4, /// 11105 alt = 8, /// Not always available on Windows 11106 windows = 64, /// ditto 11107 numLock = 16, /// 11108 11109 leftButtonDown = 256, /// these aren't available on Windows for key events, so don't use them for that unless your app is X only. 11110 middleButtonDown = 512, /// ditto 11111 rightButtonDown = 1024, /// ditto 11112 } 11113 else version(Emscripten) 11114 enum ModifierState : uint { 11115 shift = 1, /// 11116 capsLock = 2, /// 11117 ctrl = 4, /// 11118 alt = 8, /// Not always available on Windows 11119 windows = 64, /// ditto 11120 numLock = 16, /// 11121 11122 leftButtonDown = 256, /// these aren't available on Windows for key events, so don't use them for that unless your app is X only. 11123 middleButtonDown = 512, /// ditto 11124 rightButtonDown = 1024, /// ditto 11125 } 11126 else version(Windows) 11127 /// ditto 11128 enum ModifierState : uint { 11129 shift = 4, /// 11130 ctrl = 8, /// 11131 11132 // i'm not sure if the next two are available 11133 alt = 256, /// not always available on Windows 11134 windows = 512, /// ditto 11135 11136 capsLock = 1024, /// 11137 numLock = 2048, /// 11138 11139 leftButtonDown = 1, /// not available on key events 11140 middleButtonDown = 16, /// ditto 11141 rightButtonDown = 2, /// ditto 11142 11143 backButtonDown = 0x20, /// not available on X 11144 forwardButtonDown = 0x40, /// ditto 11145 } 11146 else version(OSXCocoa) 11147 // FIXME FIXME NotYetImplementedException 11148 enum ModifierState : uint { 11149 shift = 1, /// 11150 capsLock = 2, /// 11151 ctrl = 4, /// 11152 alt = 8, /// Not always available on Windows 11153 windows = 64, /// ditto 11154 numLock = 16, /// 11155 11156 leftButtonDown = 256, /// these aren't available on Windows for key events, so don't use them for that unless your app is X only. 11157 middleButtonDown = 512, /// ditto 11158 rightButtonDown = 1024, /// ditto 11159 } 11160 11161 /// The names assume a right-handed mouse. These are bitwise combined on the events that use them. 11162 enum MouseButton : int { 11163 none = 0, 11164 left = 1, /// 11165 right = 2, /// 11166 middle = 4, /// 11167 wheelUp = 8, /// 11168 wheelDown = 16, /// 11169 backButton = 32, /// often found on the thumb and used for back in browsers 11170 forwardButton = 64, /// often found on the thumb and used for forward in browsers 11171 } 11172 11173 /// Corresponds to the values found in MouseEvent.buttonLinear, being equal to `core.bitop.bsf(button) + 1` 11174 enum MouseButtonLinear : ubyte { 11175 left = 1, /// 11176 right, /// 11177 middle, /// 11178 wheelUp, /// 11179 wheelDown, /// 11180 backButton, /// often found on the thumb and used for back in browsers 11181 forwardButton, /// often found on the thumb and used for forward in browsers 11182 } 11183 11184 version(WebAssembly) { 11185 /// Do not trust the numeric values as they are platform-specific. Always use the symbolic name. 11186 enum Key { 11187 Escape = 0xff1b, /// 11188 F1 = 0xffbe, /// 11189 F2 = 0xffbf, /// 11190 F3 = 0xffc0, /// 11191 F4 = 0xffc1, /// 11192 F5 = 0xffc2, /// 11193 F6 = 0xffc3, /// 11194 F7 = 0xffc4, /// 11195 F8 = 0xffc5, /// 11196 F9 = 0xffc6, /// 11197 F10 = 0xffc7, /// 11198 F11 = 0xffc8, /// 11199 F12 = 0xffc9, /// 11200 PrintScreen = 0xff61, /// 11201 ScrollLock = 0xff14, /// 11202 Pause = 0xff13, /// 11203 Grave = 0x60, /// The $(BACKTICK) ~ key 11204 // number keys across the top of the keyboard 11205 N1 = 0x31, /// Number key atop the keyboard 11206 N2 = 0x32, /// 11207 N3 = 0x33, /// 11208 N4 = 0x34, /// 11209 N5 = 0x35, /// 11210 N6 = 0x36, /// 11211 N7 = 0x37, /// 11212 N8 = 0x38, /// 11213 N9 = 0x39, /// 11214 N0 = 0x30, /// 11215 Dash = 0x2d, /// 11216 Equals = 0x3d, /// 11217 Backslash = 0x5c, /// The \ | key 11218 Backspace = 0xff08, /// 11219 Insert = 0xff63, /// 11220 Home = 0xff50, /// 11221 PageUp = 0xff55, /// 11222 Delete = 0xffff, /// 11223 End = 0xff57, /// 11224 PageDown = 0xff56, /// 11225 Up = 0xff52, /// 11226 Down = 0xff54, /// 11227 Left = 0xff51, /// 11228 Right = 0xff53, /// 11229 11230 Tab = 0xff09, /// 11231 Q = 0x71, /// 11232 W = 0x77, /// 11233 E = 0x65, /// 11234 R = 0x72, /// 11235 T = 0x74, /// 11236 Y = 0x79, /// 11237 U = 0x75, /// 11238 I = 0x69, /// 11239 O = 0x6f, /// 11240 P = 0x70, /// 11241 LeftBracket = 0x5b, /// the [ { key 11242 RightBracket = 0x5d, /// the ] } key 11243 CapsLock = 0xffe5, /// 11244 A = 0x61, /// 11245 S = 0x73, /// 11246 D = 0x64, /// 11247 F = 0x66, /// 11248 G = 0x67, /// 11249 H = 0x68, /// 11250 J = 0x6a, /// 11251 K = 0x6b, /// 11252 L = 0x6c, /// 11253 Semicolon = 0x3b, /// 11254 Apostrophe = 0x27, /// 11255 Enter = 0xff0d, /// 11256 Shift = 0xffe1, /// 11257 Z = 0x7a, /// 11258 X = 0x78, /// 11259 C = 0x63, /// 11260 V = 0x76, /// 11261 B = 0x62, /// 11262 N = 0x6e, /// 11263 M = 0x6d, /// 11264 Comma = 0x2c, /// 11265 Period = 0x2e, /// 11266 Slash = 0x2f, /// the / ? key 11267 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 11268 Ctrl = 0xffe3, /// 11269 Windows = 0xffeb, /// 11270 Alt = 0xffe9, /// 11271 Space = 0x20, /// 11272 Alt_r = 0xffea, /// ditto of shift_r 11273 Windows_r = 0xffec, /// 11274 Menu = 0xff67, /// 11275 Ctrl_r = 0xffe4, /// 11276 11277 NumLock = 0xff7f, /// 11278 Divide = 0xffaf, /// The / key on the number pad 11279 Multiply = 0xffaa, /// The * key on the number pad 11280 Minus = 0xffad, /// The - key on the number pad 11281 Plus = 0xffab, /// The + key on the number pad 11282 PadEnter = 0xff8d, /// Numberpad enter key 11283 Pad1 = 0xff9c, /// Numberpad keys 11284 Pad2 = 0xff99, /// 11285 Pad3 = 0xff9b, /// 11286 Pad4 = 0xff96, /// 11287 Pad5 = 0xff9d, /// 11288 Pad6 = 0xff98, /// 11289 Pad7 = 0xff95, /// 11290 Pad8 = 0xff97, /// 11291 Pad9 = 0xff9a, /// 11292 Pad0 = 0xff9e, /// 11293 PadDot = 0xff9f, /// 11294 } 11295 } version(X11) { 11296 // FIXME: match ASCII whenever we can. Most of it is already there, 11297 // but there's a few exceptions and mismatches with Windows 11298 11299 /// Do not trust the numeric values as they are platform-specific. Always use the symbolic name. 11300 enum Key { 11301 Escape = 0xff1b, /// 11302 F1 = 0xffbe, /// 11303 F2 = 0xffbf, /// 11304 F3 = 0xffc0, /// 11305 F4 = 0xffc1, /// 11306 F5 = 0xffc2, /// 11307 F6 = 0xffc3, /// 11308 F7 = 0xffc4, /// 11309 F8 = 0xffc5, /// 11310 F9 = 0xffc6, /// 11311 F10 = 0xffc7, /// 11312 F11 = 0xffc8, /// 11313 F12 = 0xffc9, /// 11314 PrintScreen = 0xff61, /// 11315 ScrollLock = 0xff14, /// 11316 Pause = 0xff13, /// 11317 Grave = 0x60, /// The $(BACKTICK) ~ key 11318 // number keys across the top of the keyboard 11319 N1 = 0x31, /// Number key atop the keyboard 11320 N2 = 0x32, /// 11321 N3 = 0x33, /// 11322 N4 = 0x34, /// 11323 N5 = 0x35, /// 11324 N6 = 0x36, /// 11325 N7 = 0x37, /// 11326 N8 = 0x38, /// 11327 N9 = 0x39, /// 11328 N0 = 0x30, /// 11329 Dash = 0x2d, /// 11330 Equals = 0x3d, /// 11331 Backslash = 0x5c, /// The \ | key 11332 Backspace = 0xff08, /// 11333 Insert = 0xff63, /// 11334 Home = 0xff50, /// 11335 PageUp = 0xff55, /// 11336 Delete = 0xffff, /// 11337 End = 0xff57, /// 11338 PageDown = 0xff56, /// 11339 Up = 0xff52, /// 11340 Down = 0xff54, /// 11341 Left = 0xff51, /// 11342 Right = 0xff53, /// 11343 11344 Tab = 0xff09, /// 11345 Q = 0x71, /// 11346 W = 0x77, /// 11347 E = 0x65, /// 11348 R = 0x72, /// 11349 T = 0x74, /// 11350 Y = 0x79, /// 11351 U = 0x75, /// 11352 I = 0x69, /// 11353 O = 0x6f, /// 11354 P = 0x70, /// 11355 LeftBracket = 0x5b, /// the [ { key 11356 RightBracket = 0x5d, /// the ] } key 11357 CapsLock = 0xffe5, /// 11358 A = 0x61, /// 11359 S = 0x73, /// 11360 D = 0x64, /// 11361 F = 0x66, /// 11362 G = 0x67, /// 11363 H = 0x68, /// 11364 J = 0x6a, /// 11365 K = 0x6b, /// 11366 L = 0x6c, /// 11367 Semicolon = 0x3b, /// 11368 Apostrophe = 0x27, /// 11369 Enter = 0xff0d, /// 11370 Shift = 0xffe1, /// 11371 Z = 0x7a, /// 11372 X = 0x78, /// 11373 C = 0x63, /// 11374 V = 0x76, /// 11375 B = 0x62, /// 11376 N = 0x6e, /// 11377 M = 0x6d, /// 11378 Comma = 0x2c, /// 11379 Period = 0x2e, /// 11380 Slash = 0x2f, /// the / ? key 11381 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 11382 Ctrl = 0xffe3, /// 11383 Windows = 0xffeb, /// 11384 Alt = 0xffe9, /// 11385 Space = 0x20, /// 11386 Alt_r = 0xffea, /// ditto of shift_r 11387 Windows_r = 0xffec, /// 11388 Menu = 0xff67, /// 11389 Ctrl_r = 0xffe4, /// 11390 11391 NumLock = 0xff7f, /// 11392 Divide = 0xffaf, /// The / key on the number pad 11393 Multiply = 0xffaa, /// The * key on the number pad 11394 Minus = 0xffad, /// The - key on the number pad 11395 Plus = 0xffab, /// The + key on the number pad 11396 PadEnter = 0xff8d, /// Numberpad enter key 11397 Pad1 = 0xff9c, /// Numberpad keys 11398 Pad2 = 0xff99, /// 11399 Pad3 = 0xff9b, /// 11400 Pad4 = 0xff96, /// 11401 Pad5 = 0xff9d, /// 11402 Pad6 = 0xff98, /// 11403 Pad7 = 0xff95, /// 11404 Pad8 = 0xff97, /// 11405 Pad9 = 0xff9a, /// 11406 Pad0 = 0xff9e, /// 11407 PadDot = 0xff9f, /// 11408 } 11409 } else version(Windows) { 11410 // the character here is for en-us layouts and for illustration only 11411 // if you actually want to get characters, wait for character events 11412 // (the argument to your event handler is simply a dchar) 11413 // those will be converted by the OS for the right locale. 11414 11415 enum Key { 11416 Escape = 0x1b, 11417 F1 = 0x70, 11418 F2 = 0x71, 11419 F3 = 0x72, 11420 F4 = 0x73, 11421 F5 = 0x74, 11422 F6 = 0x75, 11423 F7 = 0x76, 11424 F8 = 0x77, 11425 F9 = 0x78, 11426 F10 = 0x79, 11427 F11 = 0x7a, 11428 F12 = 0x7b, 11429 PrintScreen = 0x2c, 11430 ScrollLock = 0x91, 11431 Pause = 0x13, 11432 Grave = 0xc0, 11433 // number keys across the top of the keyboard 11434 N1 = 0x31, 11435 N2 = 0x32, 11436 N3 = 0x33, 11437 N4 = 0x34, 11438 N5 = 0x35, 11439 N6 = 0x36, 11440 N7 = 0x37, 11441 N8 = 0x38, 11442 N9 = 0x39, 11443 N0 = 0x30, 11444 Dash = 0xbd, 11445 Equals = 0xbb, 11446 Backslash = 0xdc, 11447 Backspace = 0x08, 11448 Insert = 0x2d, 11449 Home = 0x24, 11450 PageUp = 0x21, 11451 Delete = 0x2e, 11452 End = 0x23, 11453 PageDown = 0x22, 11454 Up = 0x26, 11455 Down = 0x28, 11456 Left = 0x25, 11457 Right = 0x27, 11458 11459 Tab = 0x09, 11460 Q = 0x51, 11461 W = 0x57, 11462 E = 0x45, 11463 R = 0x52, 11464 T = 0x54, 11465 Y = 0x59, 11466 U = 0x55, 11467 I = 0x49, 11468 O = 0x4f, 11469 P = 0x50, 11470 LeftBracket = 0xdb, 11471 RightBracket = 0xdd, 11472 CapsLock = 0x14, 11473 A = 0x41, 11474 S = 0x53, 11475 D = 0x44, 11476 F = 0x46, 11477 G = 0x47, 11478 H = 0x48, 11479 J = 0x4a, 11480 K = 0x4b, 11481 L = 0x4c, 11482 Semicolon = 0xba, 11483 Apostrophe = 0xde, 11484 Enter = 0x0d, 11485 Shift = 0x10, 11486 Z = 0x5a, 11487 X = 0x58, 11488 C = 0x43, 11489 V = 0x56, 11490 B = 0x42, 11491 N = 0x4e, 11492 M = 0x4d, 11493 Comma = 0xbc, 11494 Period = 0xbe, 11495 Slash = 0xbf, 11496 Shift_r = 0xa1, // FIXME Note: this isn't sent on all computers, sometimes it just sends Shift, so don't rely on it 11497 Ctrl = 0x11, 11498 Windows = 0x5b, 11499 Alt = -5, // FIXME 11500 Space = 0x20, 11501 Alt_r = 0xffea, // ditto of shift_r 11502 Windows_r = 0x5c, // ditto of shift_r 11503 Menu = 0x5d, 11504 Ctrl_r = 0xa3, // ditto of shift_r 11505 11506 NumLock = 0x90, 11507 Divide = 0x6f, 11508 Multiply = 0x6a, 11509 Minus = 0x6d, 11510 Plus = 0x6b, 11511 PadEnter = -8, // FIXME 11512 Pad1 = 0x61, 11513 Pad2 = 0x62, 11514 Pad3 = 0x63, 11515 Pad4 = 0x64, 11516 Pad5 = 0x65, 11517 Pad6 = 0x66, 11518 Pad7 = 0x67, 11519 Pad8 = 0x68, 11520 Pad9 = 0x69, 11521 Pad0 = 0x60, 11522 PadDot = 0x6e, 11523 } 11524 11525 // I'm keeping this around for reference purposes 11526 // ideally all these buttons will be listed for all platforms, 11527 // but now now I'm just focusing on my US keyboard 11528 version(none) 11529 enum Key { 11530 LBUTTON = 0x01, 11531 RBUTTON = 0x02, 11532 CANCEL = 0x03, 11533 MBUTTON = 0x04, 11534 //static if (_WIN32_WINNT > = 0x500) { 11535 XBUTTON1 = 0x05, 11536 XBUTTON2 = 0x06, 11537 //} 11538 BACK = 0x08, 11539 TAB = 0x09, 11540 CLEAR = 0x0C, 11541 RETURN = 0x0D, 11542 SHIFT = 0x10, 11543 CONTROL = 0x11, 11544 MENU = 0x12, 11545 PAUSE = 0x13, 11546 CAPITAL = 0x14, 11547 KANA = 0x15, 11548 HANGEUL = 0x15, 11549 HANGUL = 0x15, 11550 JUNJA = 0x17, 11551 FINAL = 0x18, 11552 HANJA = 0x19, 11553 KANJI = 0x19, 11554 ESCAPE = 0x1B, 11555 CONVERT = 0x1C, 11556 NONCONVERT = 0x1D, 11557 ACCEPT = 0x1E, 11558 MODECHANGE = 0x1F, 11559 SPACE = 0x20, 11560 PRIOR = 0x21, 11561 NEXT = 0x22, 11562 END = 0x23, 11563 HOME = 0x24, 11564 LEFT = 0x25, 11565 UP = 0x26, 11566 RIGHT = 0x27, 11567 DOWN = 0x28, 11568 SELECT = 0x29, 11569 PRINT = 0x2A, 11570 EXECUTE = 0x2B, 11571 SNAPSHOT = 0x2C, 11572 INSERT = 0x2D, 11573 DELETE = 0x2E, 11574 HELP = 0x2F, 11575 LWIN = 0x5B, 11576 RWIN = 0x5C, 11577 APPS = 0x5D, 11578 SLEEP = 0x5F, 11579 NUMPAD0 = 0x60, 11580 NUMPAD1 = 0x61, 11581 NUMPAD2 = 0x62, 11582 NUMPAD3 = 0x63, 11583 NUMPAD4 = 0x64, 11584 NUMPAD5 = 0x65, 11585 NUMPAD6 = 0x66, 11586 NUMPAD7 = 0x67, 11587 NUMPAD8 = 0x68, 11588 NUMPAD9 = 0x69, 11589 MULTIPLY = 0x6A, 11590 ADD = 0x6B, 11591 SEPARATOR = 0x6C, 11592 SUBTRACT = 0x6D, 11593 DECIMAL = 0x6E, 11594 DIVIDE = 0x6F, 11595 F1 = 0x70, 11596 F2 = 0x71, 11597 F3 = 0x72, 11598 F4 = 0x73, 11599 F5 = 0x74, 11600 F6 = 0x75, 11601 F7 = 0x76, 11602 F8 = 0x77, 11603 F9 = 0x78, 11604 F10 = 0x79, 11605 F11 = 0x7A, 11606 F12 = 0x7B, 11607 F13 = 0x7C, 11608 F14 = 0x7D, 11609 F15 = 0x7E, 11610 F16 = 0x7F, 11611 F17 = 0x80, 11612 F18 = 0x81, 11613 F19 = 0x82, 11614 F20 = 0x83, 11615 F21 = 0x84, 11616 F22 = 0x85, 11617 F23 = 0x86, 11618 F24 = 0x87, 11619 NUMLOCK = 0x90, 11620 SCROLL = 0x91, 11621 LSHIFT = 0xA0, 11622 RSHIFT = 0xA1, 11623 LCONTROL = 0xA2, 11624 RCONTROL = 0xA3, 11625 LMENU = 0xA4, 11626 RMENU = 0xA5, 11627 //static if (_WIN32_WINNT > = 0x500) { 11628 BROWSER_BACK = 0xA6, 11629 BROWSER_FORWARD = 0xA7, 11630 BROWSER_REFRESH = 0xA8, 11631 BROWSER_STOP = 0xA9, 11632 BROWSER_SEARCH = 0xAA, 11633 BROWSER_FAVORITES = 0xAB, 11634 BROWSER_HOME = 0xAC, 11635 VOLUME_MUTE = 0xAD, 11636 VOLUME_DOWN = 0xAE, 11637 VOLUME_UP = 0xAF, 11638 MEDIA_NEXT_TRACK = 0xB0, 11639 MEDIA_PREV_TRACK = 0xB1, 11640 MEDIA_STOP = 0xB2, 11641 MEDIA_PLAY_PAUSE = 0xB3, 11642 LAUNCH_MAIL = 0xB4, 11643 LAUNCH_MEDIA_SELECT = 0xB5, 11644 LAUNCH_APP1 = 0xB6, 11645 LAUNCH_APP2 = 0xB7, 11646 //} 11647 OEM_1 = 0xBA, 11648 //static if (_WIN32_WINNT > = 0x500) { 11649 OEM_PLUS = 0xBB, 11650 OEM_COMMA = 0xBC, 11651 OEM_MINUS = 0xBD, 11652 OEM_PERIOD = 0xBE, 11653 //} 11654 OEM_2 = 0xBF, 11655 OEM_3 = 0xC0, 11656 OEM_4 = 0xDB, 11657 OEM_5 = 0xDC, 11658 OEM_6 = 0xDD, 11659 OEM_7 = 0xDE, 11660 OEM_8 = 0xDF, 11661 //static if (_WIN32_WINNT > = 0x500) { 11662 OEM_102 = 0xE2, 11663 //} 11664 PROCESSKEY = 0xE5, 11665 //static if (_WIN32_WINNT > = 0x500) { 11666 PACKET = 0xE7, 11667 //} 11668 ATTN = 0xF6, 11669 CRSEL = 0xF7, 11670 EXSEL = 0xF8, 11671 EREOF = 0xF9, 11672 PLAY = 0xFA, 11673 ZOOM = 0xFB, 11674 NONAME = 0xFC, 11675 PA1 = 0xFD, 11676 OEM_CLEAR = 0xFE, 11677 } 11678 11679 } else version(OSXCocoa) { 11680 enum Key { 11681 Escape = 53, 11682 F1 = 122, 11683 F2 = 120, 11684 F3 = 99, 11685 F4 = 118, 11686 F5 = 96, 11687 F6 = 97, 11688 F7 = 98, 11689 F8 = 100, 11690 F9 = 101, 11691 F10 = 109, 11692 F11 = 103, 11693 F12 = 111, 11694 PrintScreen = 105, 11695 ScrollLock = 107, 11696 Pause = 113, 11697 Grave = 50, 11698 // number keys across the top of the keyboard 11699 N1 = 18, 11700 N2 = 19, 11701 N3 = 20, 11702 N4 = 21, 11703 N5 = 23, 11704 N6 = 22, 11705 N7 = 26, 11706 N8 = 28, 11707 N9 = 25, 11708 N0 = 29, 11709 Dash = 27, 11710 Equals = 24, 11711 Backslash = 42, 11712 Backspace = 51, 11713 Insert = 114, 11714 Home = 115, 11715 PageUp = 116, 11716 Delete = 117, 11717 End = 119, 11718 PageDown = 121, 11719 Up = 126, 11720 Down = 125, 11721 Left = 123, 11722 Right = 124, 11723 11724 Tab = 48, 11725 Q = 12, 11726 W = 13, 11727 E = 14, 11728 R = 15, 11729 T = 17, 11730 Y = 16, 11731 U = 32, 11732 I = 34, 11733 O = 31, 11734 P = 35, 11735 LeftBracket = 33, 11736 RightBracket = 30, 11737 CapsLock = 57, 11738 A = 0, 11739 S = 1, 11740 D = 2, 11741 F = 3, 11742 G = 5, 11743 H = 4, 11744 J = 38, 11745 K = 40, 11746 L = 37, 11747 Semicolon = 41, 11748 Apostrophe = 39, 11749 Enter = 36, 11750 Shift = 56, 11751 Z = 6, 11752 X = 7, 11753 C = 8, 11754 V = 9, 11755 B = 11, 11756 N = 45, 11757 M = 46, 11758 Comma = 43, 11759 Period = 47, 11760 Slash = 44, 11761 Shift_r = -4, // FIXME Note: this isn't sent on all computers, sometimes it just sends Shift, so don't rely on it 11762 Ctrl = 59, 11763 Windows = 55, 11764 Alt = 58, 11765 Space = 49, 11766 Alt_r = -3, // ditto of shift_r 11767 Windows_r = -2, 11768 Menu = 110, 11769 Ctrl_r = -1, 11770 11771 NumLock = 1, 11772 Divide = 75, 11773 Multiply = 67, 11774 Minus = 78, 11775 Plus = 69, 11776 PadEnter = 76, 11777 Pad1 = 83, 11778 Pad2 = 84, 11779 Pad3 = 85, 11780 Pad4 = 86, 11781 Pad5 = 87, 11782 Pad6 = 88, 11783 Pad7 = 89, 11784 Pad8 = 91, 11785 Pad9 = 92, 11786 Pad0 = 82, 11787 PadDot = 65, 11788 } 11789 11790 } 11791 11792 char keyToLetterCharAssumingLotsOfThingsThatYouMightBetterNotAssume(Key key) { 11793 version(OSXCocoa) { 11794 return char.init; // FIXME 11795 } else { 11796 return cast(char)(key - Key.A + 'a'); 11797 } 11798 } 11799 11800 /* Additional utilities */ 11801 11802 11803 Color fromHsl(real h, real s, real l) { 11804 return arsd.color.fromHsl([h,s,l]); 11805 } 11806 11807 11808 11809 /* ********** What follows is the system-specific implementations *********/ 11810 version(Windows) { 11811 11812 11813 // helpers for making HICONs from MemoryImages 11814 class WindowsIcon { 11815 struct Win32Icon { 11816 align(1): 11817 uint biSize; 11818 int biWidth; 11819 int biHeight; 11820 ushort biPlanes; 11821 ushort biBitCount; 11822 uint biCompression; 11823 uint biSizeImage; 11824 int biXPelsPerMeter; 11825 int biYPelsPerMeter; 11826 uint biClrUsed; 11827 uint biClrImportant; 11828 // RGBQUAD[colorCount] biColors; 11829 /* Pixels: 11830 Uint8 pixels[] 11831 */ 11832 /* Mask: 11833 Uint8 mask[] 11834 */ 11835 } 11836 11837 ubyte[] fromMemoryImage(MemoryImage mi, out int icon_len, out int width, out int height) { 11838 11839 assert(mi.width <= 256, "image too wide"); 11840 assert(mi.height <= 256, "image too tall"); 11841 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 11842 assert(mi.height % 4 == 0, "image not multiple of 4 height"); 11843 11844 int icon_plen = mi.width * mi.height * 4; 11845 int icon_mlen = mi.width * mi.height / 8; 11846 11847 int colorCount = 0; 11848 icon_len = 40 + icon_plen + icon_mlen + cast(int) RGBQUAD.sizeof * colorCount; 11849 11850 ubyte[] memory = new ubyte[](Win32Icon.sizeof + icon_plen + icon_mlen); 11851 Win32Icon* icon_win32 = cast(Win32Icon*) memory.ptr; 11852 11853 auto data = memory[Win32Icon.sizeof .. $]; 11854 11855 width = mi.width; 11856 height = mi.height; 11857 11858 auto trueColorImage = mi.getAsTrueColorImage(); 11859 11860 icon_win32.biSize = 40; 11861 icon_win32.biWidth = mi.width; 11862 icon_win32.biHeight = mi.height*2; 11863 icon_win32.biPlanes = 1; 11864 icon_win32.biBitCount = 32; 11865 icon_win32.biSizeImage = icon_plen + icon_mlen; 11866 11867 int offset = 0; 11868 int andOff = icon_plen * 8; // the and offset is in bits 11869 11870 // leaving the and mask as the default 0 so the rgba alpha blend 11871 // does its thing instead 11872 for(int y = height - 1; y >= 0; y--) { 11873 int off2 = y * width * 4; 11874 foreach(x; 0 .. width) { 11875 data[offset + 2] = trueColorImage.imageData.bytes[off2 + 0]; 11876 data[offset + 1] = trueColorImage.imageData.bytes[off2 + 1]; 11877 data[offset + 0] = trueColorImage.imageData.bytes[off2 + 2]; 11878 data[offset + 3] = trueColorImage.imageData.bytes[off2 + 3]; 11879 11880 offset += 4; 11881 off2 += 4; 11882 } 11883 } 11884 11885 return memory; 11886 } 11887 11888 this(MemoryImage mi) { 11889 int icon_len, width, height; 11890 11891 auto icon_win32 = fromMemoryImage(mi, icon_len, width, height); 11892 11893 /* 11894 PNG* png = readPnpngData); 11895 PNGHeader pngh = getHeader(png); 11896 void* icon_win32; 11897 if(pngh.depth == 4) { 11898 auto i = new Win32Icon!(16); 11899 i.fromPNG(png, pngh, icon_len, width, height); 11900 icon_win32 = i; 11901 } 11902 else if(pngh.depth == 8) { 11903 auto i = new Win32Icon!(256); 11904 i.fromPNG(png, pngh, icon_len, width, height); 11905 icon_win32 = i; 11906 } else assert(0); 11907 */ 11908 11909 hIcon = CreateIconFromResourceEx(icon_win32.ptr, icon_len, true, 0x00030000, width, height, 0); 11910 11911 if(hIcon is null) throw new WindowsApiException("CreateIconFromResourceEx", GetLastError()); 11912 } 11913 11914 ~this() { 11915 if(!thisIsGuiThread) return; // FIXME: leaks if multithreaded gc 11916 DestroyIcon(hIcon); 11917 } 11918 11919 HICON hIcon; 11920 } 11921 11922 11923 11924 11925 11926 11927 alias int delegate(HWND, UINT, WPARAM, LPARAM, out int) NativeEventHandler; 11928 alias HWND NativeWindowHandle; 11929 11930 extern(Windows) 11931 LRESULT WndProc(HWND hWnd, UINT iMessage, WPARAM wParam, LPARAM lParam) nothrow { 11932 try { 11933 if(SimpleWindow.handleNativeGlobalEvent !is null) { 11934 // it returns zero if the message is handled, so we won't do anything more there 11935 // do I like that though? 11936 int mustReturn; 11937 auto ret = SimpleWindow.handleNativeGlobalEvent(hWnd, iMessage, wParam, lParam, mustReturn); 11938 if(mustReturn) 11939 return ret; 11940 } 11941 11942 if(auto window = hWnd in CapableOfHandlingNativeEvent.nativeHandleMapping) { 11943 if(window.getNativeEventHandler !is null) { 11944 int mustReturn; 11945 auto ret = window.getNativeEventHandler()(hWnd, iMessage, wParam, lParam, mustReturn); 11946 if(mustReturn) 11947 return ret; 11948 } 11949 if(auto w = cast(SimpleWindow) (*window)) 11950 return w.windowProcedure(hWnd, iMessage, wParam, lParam); 11951 else 11952 return DefWindowProc(hWnd, iMessage, wParam, lParam); 11953 } else { 11954 return DefWindowProc(hWnd, iMessage, wParam, lParam); 11955 } 11956 } catch (Exception e) { 11957 try { 11958 sdpy_abort(e); 11959 return 0; 11960 } catch(Exception e) { assert(0); } 11961 } 11962 } 11963 11964 void sdpy_abort(Throwable e) nothrow { 11965 try 11966 MessageBoxA(null, (e.toString() ~ "\0").ptr, "Exception caught in WndProc", 0); 11967 catch(Exception e) 11968 MessageBoxA(null, "Exception.toString threw too!", "Exception caught in WndProc", 0); 11969 ExitProcess(1); 11970 } 11971 11972 mixin template NativeScreenPainterImplementation() { 11973 HDC hdc; 11974 HWND hwnd; 11975 //HDC windowHdc; 11976 HBITMAP oldBmp; 11977 11978 void create(PaintingHandle window) { 11979 hwnd = window; 11980 11981 if(auto sw = cast(SimpleWindow) this.window) { 11982 // drawing on a window, double buffer 11983 auto windowHdc = GetDC(hwnd); 11984 11985 auto buffer = sw.impl.buffer; 11986 if(buffer is null) { 11987 hdc = windowHdc; 11988 windowDc = true; 11989 } else { 11990 hdc = CreateCompatibleDC(windowHdc); 11991 11992 ReleaseDC(hwnd, windowHdc); 11993 11994 oldBmp = SelectObject(hdc, buffer); 11995 } 11996 } else { 11997 // drawing on something else, draw directly 11998 hdc = CreateCompatibleDC(null); 11999 SelectObject(hdc, window); 12000 } 12001 12002 // X doesn't draw a text background, so neither should we 12003 SetBkMode(hdc, TRANSPARENT); 12004 12005 ensureDefaultFontLoaded(); 12006 12007 if(defaultGuiFont) { 12008 SelectObject(hdc, defaultGuiFont); 12009 // DeleteObject(defaultGuiFont); 12010 } 12011 } 12012 12013 static HFONT defaultGuiFont; 12014 static void ensureDefaultFontLoaded() { 12015 static bool triedDefaultGuiFont = false; 12016 if(!triedDefaultGuiFont) { 12017 NONCLIENTMETRICS params; 12018 params.cbSize = params.sizeof; 12019 if(SystemParametersInfo(SPI_GETNONCLIENTMETRICS, params.sizeof, ¶ms, 0)) { 12020 defaultGuiFont = CreateFontIndirect(¶ms.lfMessageFont); 12021 } 12022 triedDefaultGuiFont = true; 12023 } 12024 } 12025 12026 private OperatingSystemFont _activeFont; 12027 12028 void setFont(OperatingSystemFont font) { 12029 _activeFont = font; 12030 if(font && font.font) { 12031 if(SelectObject(hdc, font.font) == HGDI_ERROR) { 12032 // error... how to handle tho? 12033 } else { 12034 12035 } 12036 } 12037 else if(defaultGuiFont) 12038 SelectObject(hdc, defaultGuiFont); 12039 } 12040 12041 arsd.color.Rectangle _clipRectangle; 12042 12043 void setClipRectangle(int x, int y, int width, int height) { 12044 auto old = _clipRectangle; 12045 _clipRectangle = arsd.color.Rectangle(Point(x, y), Size(width, height)); 12046 if(old == _clipRectangle) 12047 return; 12048 12049 if(width == 0 || height == 0) { 12050 SelectClipRgn(hdc, null); 12051 } else { 12052 auto region = CreateRectRgn(x, y, x + width, y + height); 12053 SelectClipRgn(hdc, region); 12054 DeleteObject(region); 12055 } 12056 } 12057 12058 12059 // just because we can on Windows... 12060 //void create(Image image); 12061 12062 void invalidateRect(Rectangle invalidRect) { 12063 RECT rect; 12064 rect.left = invalidRect.left; 12065 rect.right = invalidRect.right; 12066 rect.top = invalidRect.top; 12067 rect.bottom = invalidRect.bottom; 12068 InvalidateRect(hwnd, &rect, false); 12069 } 12070 bool manualInvalidations; 12071 12072 void dispose() { 12073 // FIXME: this.window.width/height is probably wrong 12074 // BitBlt(windowHdc, 0, 0, this.window.width, this.window.height, hdc, 0, 0, SRCCOPY); 12075 // ReleaseDC(hwnd, windowHdc); 12076 12077 // FIXME: it shouldn't invalidate the whole thing in all cases... it would be ideal to do this right 12078 if(cast(SimpleWindow) this.window) { 12079 if(!manualInvalidations) 12080 InvalidateRect(hwnd, cast(RECT*)null, false); // no need to erase bg as the whole thing gets bitblt'd ove 12081 } 12082 12083 if(originalPen !is null) 12084 SelectObject(hdc, originalPen); 12085 if(currentPen !is null) 12086 DeleteObject(currentPen); 12087 if(originalBrush !is null) 12088 SelectObject(hdc, originalBrush); 12089 if(currentBrush !is null) 12090 DeleteObject(currentBrush); 12091 12092 SelectObject(hdc, oldBmp); 12093 12094 if(windowDc) 12095 ReleaseDC(hwnd, hdc); 12096 else 12097 DeleteDC(hdc); 12098 12099 if(window.paintingFinishedDg !is null) 12100 window.paintingFinishedDg()(); 12101 } 12102 12103 bool windowDc; 12104 HPEN originalPen; 12105 HPEN currentPen; 12106 12107 Pen _activePen; 12108 12109 Color _outlineColor; 12110 12111 @property void pen(Pen p) { 12112 _activePen = p; 12113 _outlineColor = p.color; 12114 12115 HPEN pen; 12116 if(p.color.a == 0) { 12117 pen = GetStockObject(NULL_PEN); 12118 } else { 12119 int style = PS_SOLID; 12120 final switch(p.style) { 12121 case Pen.Style.Solid: 12122 style = PS_SOLID; 12123 break; 12124 case Pen.Style.Dashed: 12125 style = PS_DASH; 12126 break; 12127 case Pen.Style.Dotted: 12128 style = PS_DOT; 12129 break; 12130 } 12131 pen = CreatePen(style, p.width, RGB(p.color.r, p.color.g, p.color.b)); 12132 } 12133 auto orig = SelectObject(hdc, pen); 12134 if(originalPen is null) 12135 originalPen = orig; 12136 12137 if(currentPen !is null) 12138 DeleteObject(currentPen); 12139 12140 currentPen = pen; 12141 12142 // the outline is like a foreground since it's done that way on X 12143 SetTextColor(hdc, RGB(p.color.r, p.color.g, p.color.b)); 12144 12145 } 12146 12147 @property void rasterOp(RasterOp op) { 12148 int mode; 12149 final switch(op) { 12150 case RasterOp.normal: 12151 mode = R2_COPYPEN; 12152 break; 12153 case RasterOp.xor: 12154 mode = R2_XORPEN; 12155 break; 12156 } 12157 SetROP2(hdc, mode); 12158 } 12159 12160 HBRUSH originalBrush; 12161 HBRUSH currentBrush; 12162 Color _fillColor = Color(1, 1, 1, 1); // what are the odds that they'd set this?? 12163 @property void fillColor(Color c) { 12164 if(c == _fillColor) 12165 return; 12166 _fillColor = c; 12167 HBRUSH brush; 12168 if(c.a == 0) { 12169 brush = GetStockObject(HOLLOW_BRUSH); 12170 } else { 12171 brush = CreateSolidBrush(RGB(c.r, c.g, c.b)); 12172 } 12173 auto orig = SelectObject(hdc, brush); 12174 if(originalBrush is null) 12175 originalBrush = orig; 12176 12177 if(currentBrush !is null) 12178 DeleteObject(currentBrush); 12179 12180 currentBrush = brush; 12181 12182 // background color is NOT set because X doesn't draw text backgrounds 12183 // SetBkColor(hdc, RGB(255, 255, 255)); 12184 } 12185 12186 void drawImage(int x, int y, Image i, int ix, int iy, int w, int h) { 12187 BITMAP bm; 12188 12189 HDC hdcMem = CreateCompatibleDC(hdc); 12190 HBITMAP hbmOld = SelectObject(hdcMem, i.handle); 12191 12192 GetObject(i.handle, bm.sizeof, &bm); 12193 12194 // or should I AlphaBlend!??!?! 12195 BitBlt(hdc, x, y, w /* bm.bmWidth */, /*bm.bmHeight*/ h, hdcMem, ix, iy, SRCCOPY); 12196 12197 SelectObject(hdcMem, hbmOld); 12198 DeleteDC(hdcMem); 12199 } 12200 12201 void drawPixmap(Sprite s, int x, int y, int ix, int iy, int w, int h) { 12202 BITMAP bm; 12203 12204 HDC hdcMem = CreateCompatibleDC(hdc); 12205 HBITMAP hbmOld = SelectObject(hdcMem, s.handle); 12206 12207 GetObject(s.handle, bm.sizeof, &bm); 12208 12209 version(CRuntime_DigitalMars) goto noalpha; 12210 12211 // or should I AlphaBlend!??!?! note it is supposed to be premultiplied http://www.fengyuan.com/article/alphablend.html 12212 if(s.enableAlpha) { 12213 auto dw = w ? w : bm.bmWidth; 12214 auto dh = h ? h : bm.bmHeight; 12215 BLENDFUNCTION bf; 12216 bf.BlendOp = AC_SRC_OVER; 12217 bf.SourceConstantAlpha = 255; 12218 bf.AlphaFormat = AC_SRC_ALPHA; 12219 AlphaBlend(hdc, x, y, dw, dh, hdcMem, ix, iy, dw, dh, bf); 12220 } else { 12221 noalpha: 12222 BitBlt(hdc, x, y, w ? w : bm.bmWidth, h ? h : bm.bmHeight, hdcMem, ix, iy, SRCCOPY); 12223 } 12224 12225 SelectObject(hdcMem, hbmOld); 12226 DeleteDC(hdcMem); 12227 } 12228 12229 Size textSize(scope const(char)[] text) { 12230 bool dummyX; 12231 if(text.length == 0) { 12232 text = " "; 12233 dummyX = true; 12234 } 12235 RECT rect; 12236 WCharzBuffer buffer = WCharzBuffer(text); 12237 DrawTextW(hdc, buffer.ptr, cast(int) buffer.length, &rect, DT_CALCRECT | DT_NOPREFIX); 12238 return Size(dummyX ? 0 : rect.right, rect.bottom); 12239 } 12240 12241 void drawText(int x, int y, int x2, int y2, scope const(char)[] text, uint alignment) { 12242 if(text.length && text[$-1] == '\n') 12243 text = text[0 .. $-1]; // tailing newlines are weird on windows... 12244 if(text.length && text[$-1] == '\r') 12245 text = text[0 .. $-1]; 12246 12247 WCharzBuffer buffer = WCharzBuffer(text, WindowsStringConversionFlags.convertNewLines); 12248 if(x2 == 0 && y2 == 0) { 12249 TextOutW(hdc, x, y, buffer.ptr, cast(int) buffer.length); 12250 } else { 12251 RECT rect; 12252 rect.left = x; 12253 rect.top = y; 12254 rect.right = x2; 12255 rect.bottom = y2; 12256 12257 uint mode = DT_LEFT; 12258 if(alignment & TextAlignment.Right) 12259 mode = DT_RIGHT; 12260 else if(alignment & TextAlignment.Center) 12261 mode = DT_CENTER; 12262 12263 // FIXME: vcenter on windows only works with single line, but I want it to work in all cases 12264 if(alignment & TextAlignment.VerticalCenter) 12265 mode |= DT_VCENTER | DT_SINGLELINE; 12266 12267 DrawTextW(hdc, buffer.ptr, cast(int) buffer.length, &rect, mode | DT_NOPREFIX); 12268 } 12269 12270 /* 12271 uint mode; 12272 12273 if(alignment & TextAlignment.Center) 12274 mode = TA_CENTER; 12275 12276 SetTextAlign(hdc, mode); 12277 */ 12278 } 12279 12280 int fontHeight() { 12281 TEXTMETRIC metric; 12282 if(GetTextMetricsW(hdc, &metric)) { 12283 return metric.tmHeight; 12284 } 12285 12286 return 16; // idk just guessing here, maybe we should throw 12287 } 12288 12289 void drawPixel(int x, int y) { 12290 SetPixel(hdc, x, y, RGB(_activePen.color.r, _activePen.color.g, _activePen.color.b)); 12291 } 12292 12293 // The basic shapes, outlined 12294 12295 void drawLine(int x1, int y1, int x2, int y2) { 12296 MoveToEx(hdc, x1, y1, null); 12297 LineTo(hdc, x2, y2); 12298 } 12299 12300 void drawRectangle(int x, int y, int width, int height) { 12301 // FIXME: with a wider pen this might not draw quite right. im not sure. 12302 gdi.Rectangle(hdc, x, y, x + width, y + height); 12303 } 12304 12305 void drawRectangleRounded(Point upperLeft, Point lowerRight, int borderRadius) { 12306 RoundRect( 12307 hdc, 12308 upperLeft.x, upperLeft.y, 12309 lowerRight.x, lowerRight.y, 12310 borderRadius, borderRadius 12311 ); 12312 } 12313 12314 /// Arguments are the points of the bounding rectangle 12315 void drawEllipse(int x1, int y1, int x2, int y2) { 12316 Ellipse(hdc, x1, y1, x2, y2); 12317 } 12318 12319 void drawArc(int x1, int y1, int width, int height, int start, int length) { 12320 //if(length > 360*64) 12321 //length = 360*64; 12322 12323 if((start == 0 && length == 360*64)) { 12324 drawEllipse(x1, y1, x1 + width, y1 + height); 12325 } else { 12326 import core.stdc.math; 12327 12328 bool clockwise = false; 12329 if(length < 0) { 12330 clockwise = true; 12331 length = -length; 12332 } 12333 12334 double startAngle = cast(double) start / 64.0 / 180.0 * 3.14159265358979323; 12335 double endAngle = cast(double) (start + length) / 64.0 / 180.0 * 3.14159265358979323; 12336 12337 auto c1 = cast(int) (cos(startAngle) * width / 2.0 + double(x1) + double(width) / 2.0); 12338 auto c2 = cast(int) (-sin(startAngle) * height / 2.0 + double(y1) + double(height) / 2.0); 12339 auto c3 = cast(int) (cos(endAngle) * width / 2.0 + double(x1) + double(width) / 2.0); 12340 auto c4 = cast(int) (-sin(endAngle) * height / 2.0 + double(y1) + double(height) / 2.0); 12341 12342 if(clockwise) { 12343 auto t1 = c1; 12344 auto t2 = c2; 12345 c1 = c3; 12346 c2 = c4; 12347 c3 = t1; 12348 c4 = t2; 12349 } 12350 12351 //if(_activePen.color.a) 12352 //Arc(hdc, x1, y1, x1 + width + 0, y1 + height + 0, c1, c2, c3, c4); 12353 //if(_fillColor.a) 12354 12355 Pie(hdc, x1, y1, x1 + width + 0, y1 + height + 0, c1, c2, c3, c4); 12356 } 12357 } 12358 12359 void drawPolygon(Point[] vertexes) { 12360 POINT[] points; 12361 points.length = vertexes.length; 12362 12363 foreach(i, p; vertexes) { 12364 points[i].x = p.x; 12365 points[i].y = p.y; 12366 } 12367 12368 Polygon(hdc, points.ptr, cast(int) points.length); 12369 } 12370 } 12371 12372 12373 // Mix this into the SimpleWindow class 12374 mixin template NativeSimpleWindowImplementation() { 12375 int curHidden = 0; // counter 12376 __gshared static bool[string] knownWinClasses; 12377 static bool altPressed = false; 12378 12379 HANDLE oldCursor; 12380 12381 void hideCursor () { 12382 if(curHidden == 0) 12383 oldCursor = SetCursor(null); 12384 ++curHidden; 12385 } 12386 12387 void showCursor () { 12388 --curHidden; 12389 if(curHidden == 0) { 12390 SetCursor(currentCursor is null ? oldCursor : currentCursor); // show it immediately without waiting for mouse movement 12391 } 12392 } 12393 12394 12395 int minWidth = 0, minHeight = 0, maxWidth = int.max, maxHeight = int.max; 12396 12397 void setMinSize (int minwidth, int minheight) { 12398 minWidth = minwidth; 12399 minHeight = minheight; 12400 } 12401 void setMaxSize (int maxwidth, int maxheight) { 12402 maxWidth = maxwidth; 12403 maxHeight = maxheight; 12404 } 12405 12406 // FIXME i'm not sure that Windows has this functionality 12407 // though it is nonessential anyway. 12408 void setResizeGranularity (int granx, int grany) {} 12409 12410 ScreenPainter getPainter(bool manualInvalidations) { 12411 return ScreenPainter(this, hwnd, manualInvalidations); 12412 } 12413 12414 HBITMAP buffer; 12415 12416 void setTitle(string title) { 12417 WCharzBuffer bfr = WCharzBuffer(title); 12418 SetWindowTextW(hwnd, bfr.ptr); 12419 } 12420 12421 string getTitle() { 12422 auto len = GetWindowTextLengthW(hwnd); 12423 if (!len) 12424 return null; 12425 wchar[256] tmpBuffer; 12426 wchar[] buffer = (len <= tmpBuffer.length) ? tmpBuffer[] : new wchar[len]; 12427 auto len2 = GetWindowTextW(hwnd, buffer.ptr, cast(int) buffer.length); 12428 auto str = buffer[0 .. len2]; 12429 return makeUtf8StringFromWindowsString(str); 12430 } 12431 12432 void move(int x, int y) { 12433 RECT rect; 12434 GetWindowRect(hwnd, &rect); 12435 // move it while maintaining the same size... 12436 MoveWindow(hwnd, x, y, rect.right - rect.left, rect.bottom - rect.top, true); 12437 } 12438 12439 void resize(int w, int h) { 12440 RECT rect; 12441 GetWindowRect(hwnd, &rect); 12442 12443 RECT client; 12444 GetClientRect(hwnd, &client); 12445 12446 rect.right = rect.right - client.right + w; 12447 rect.bottom = rect.bottom - client.bottom + h; 12448 12449 // same position, new size for the client rectangle 12450 MoveWindow(hwnd, rect.left, rect.top, rect.right, rect.bottom, true); 12451 12452 updateOpenglViewportIfNeeded(w, h); 12453 } 12454 12455 void moveResize (int x, int y, int w, int h) { 12456 // what's given is the client rectangle, we need to adjust 12457 12458 RECT rect; 12459 rect.left = x; 12460 rect.top = y; 12461 rect.right = w + x; 12462 rect.bottom = h + y; 12463 if(!AdjustWindowRect(&rect, GetWindowLong(hwnd, GWL_STYLE), GetMenu(hwnd) !is null)) 12464 throw new WindowsApiException("AdjustWindowRect", GetLastError()); 12465 12466 MoveWindow(hwnd, rect.left, rect.top, rect.right - rect.left, rect.bottom - rect.top, true); 12467 updateOpenglViewportIfNeeded(w, h); 12468 if (windowResized !is null) windowResized(w, h); 12469 } 12470 12471 version(without_opengl) {} else { 12472 HGLRC ghRC; 12473 HDC ghDC; 12474 } 12475 12476 void createWindow(int width, int height, string title, OpenGlOptions opengl, SimpleWindow parent) { 12477 string cnamec; 12478 if (sdpyWindowClassStr is null) loadBinNameToWindowClassName(); 12479 if (sdpyWindowClassStr is null || sdpyWindowClassStr[0] == 0) { 12480 cnamec = "DSimpleWindow"; 12481 } else { 12482 cnamec = sdpyWindowClass; 12483 } 12484 12485 WCharzBuffer cn = WCharzBuffer(cnamec); 12486 12487 HINSTANCE hInstance = cast(HINSTANCE) GetModuleHandle(null); 12488 12489 if(cnamec !in knownWinClasses) { 12490 WNDCLASSEX wc; 12491 12492 // FIXME: I might be able to use cbWndExtra to hold the pointer back 12493 // to the object. Maybe. 12494 wc.cbSize = wc.sizeof; 12495 wc.cbClsExtra = 0; 12496 wc.cbWndExtra = 0; 12497 wc.hbrBackground = cast(HBRUSH) (COLOR_WINDOW+1); // GetStockObject(WHITE_BRUSH); 12498 wc.hCursor = LoadCursorW(null, IDC_ARROW); 12499 wc.hIcon = LoadIcon(hInstance, null); 12500 wc.hInstance = hInstance; 12501 wc.lpfnWndProc = &WndProc; 12502 wc.lpszClassName = cn.ptr; 12503 wc.hIconSm = null; 12504 wc.style = CS_HREDRAW | CS_VREDRAW | CS_DBLCLKS; 12505 if(!RegisterClassExW(&wc)) 12506 throw new WindowsApiException("RegisterClassExW", GetLastError()); 12507 knownWinClasses[cnamec] = true; 12508 } 12509 12510 int style; 12511 uint flags = WS_EX_ACCEPTFILES; // accept drag-drop files 12512 12513 // FIXME: windowType and customizationFlags 12514 final switch(windowType) { 12515 case WindowTypes.normal: 12516 if(resizability == Resizability.fixedSize) { 12517 style = WS_SYSMENU | WS_OVERLAPPED | WS_CAPTION; 12518 } else { 12519 style = WS_OVERLAPPEDWINDOW; 12520 } 12521 break; 12522 case WindowTypes.undecorated: 12523 style = WS_POPUP | WS_SYSMENU; 12524 break; 12525 case WindowTypes.eventOnly: 12526 _hidden = true; 12527 break; 12528 case WindowTypes.dropdownMenu: 12529 case WindowTypes.popupMenu: 12530 case WindowTypes.notification: 12531 style = WS_POPUP; 12532 flags |= WS_EX_NOACTIVATE; 12533 break; 12534 case WindowTypes.dialog: 12535 style = WS_OVERLAPPEDWINDOW; 12536 break; 12537 case WindowTypes.nestedChild: 12538 style = WS_CHILD; 12539 break; 12540 case WindowTypes.minimallyWrapped: 12541 assert(0, "construct minimally wrapped through the other ctor overlad"); 12542 } 12543 12544 if ((customizationFlags & WindowFlags.extraComposite) != 0) 12545 flags |= WS_EX_LAYERED; // composite window for better performance and effects support 12546 12547 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 12548 CW_USEDEFAULT, CW_USEDEFAULT, width, height, 12549 parent is null ? null : parent.impl.hwnd, null, hInstance, null); 12550 12551 if(!hwnd) 12552 throw new WindowsApiException("CreateWindowEx", GetLastError()); 12553 12554 if ((customizationFlags & WindowFlags.extraComposite) != 0) 12555 setOpacity(255); 12556 12557 SimpleWindow.nativeMapping[hwnd] = this; 12558 CapableOfHandlingNativeEvent.nativeHandleMapping[hwnd] = this; 12559 12560 if(windowType == WindowTypes.eventOnly) 12561 return; 12562 12563 HDC hdc = GetDC(hwnd); 12564 12565 if(!hdc) 12566 throw new WindowsApiException("GetDC", GetLastError()); 12567 12568 version(without_opengl) {} 12569 else { 12570 if(opengl == OpenGlOptions.yes) { 12571 if(!openGlLibrariesSuccessfullyLoaded) throw new Exception("OpenGL libraries did not load"); 12572 static if (SdpyIsUsingIVGLBinds) {if (glbindGetProcAddress("glHint") is null) assert(0, "GL: error loading OpenGL"); } // loads all necessary functions 12573 static if (SdpyIsUsingIVGLBinds) import iv.glbinds; // override druntime windows imports 12574 ghDC = hdc; 12575 PIXELFORMATDESCRIPTOR pfd; 12576 12577 pfd.nSize = PIXELFORMATDESCRIPTOR.sizeof; 12578 pfd.nVersion = 1; 12579 pfd.dwFlags = PFD_DRAW_TO_WINDOW | PFD_SUPPORT_OPENGL | PFD_DOUBLEBUFFER; 12580 pfd.dwLayerMask = PFD_MAIN_PLANE; 12581 pfd.iPixelType = PFD_TYPE_RGBA; 12582 pfd.cColorBits = 24; 12583 pfd.cDepthBits = 24; 12584 pfd.cAccumBits = 0; 12585 pfd.cStencilBits = 8; // any reasonable OpenGL implementation should support this anyway 12586 12587 auto pixelformat = ChoosePixelFormat(hdc, &pfd); 12588 12589 if (pixelformat == 0) 12590 throw new WindowsApiException("ChoosePixelFormat", GetLastError()); 12591 12592 if (SetPixelFormat(hdc, pixelformat, &pfd) == 0) 12593 throw new WindowsApiException("SetPixelFormat", GetLastError()); 12594 12595 if (sdpyOpenGLContextVersion && wglCreateContextAttribsARB is null) { 12596 // windoze is idiotic: we have to have OpenGL context to get function addresses 12597 // so we will create fake context to get that stupid address 12598 auto tmpcc = wglCreateContext(ghDC); 12599 if (tmpcc !is null) { 12600 scope(exit) { wglMakeCurrent(ghDC, null); wglDeleteContext(tmpcc); } 12601 wglMakeCurrent(ghDC, tmpcc); 12602 wglInitOtherFunctions(); 12603 } 12604 } 12605 12606 if (wglCreateContextAttribsARB !is null && sdpyOpenGLContextVersion) { 12607 int[9] contextAttribs = [ 12608 WGL_CONTEXT_MAJOR_VERSION_ARB, (sdpyOpenGLContextVersion>>8), 12609 WGL_CONTEXT_MINOR_VERSION_ARB, (sdpyOpenGLContextVersion&0xff), 12610 WGL_CONTEXT_PROFILE_MASK_ARB, (sdpyOpenGLContextCompatible ? WGL_CONTEXT_COMPATIBILITY_PROFILE_BIT_ARB : WGL_CONTEXT_CORE_PROFILE_BIT_ARB), 12611 // for modern context, set "forward compatibility" flag too 12612 (sdpyOpenGLContextCompatible ? 0/*None*/ : WGL_CONTEXT_FLAGS_ARB), WGL_CONTEXT_FORWARD_COMPATIBLE_BIT_ARB, 12613 0/*None*/, 12614 ]; 12615 ghRC = wglCreateContextAttribsARB(ghDC, null, contextAttribs.ptr); 12616 if (ghRC is null && sdpyOpenGLContextAllowFallback) { 12617 // activate fallback mode 12618 // 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; 12619 ghRC = wglCreateContext(ghDC); 12620 } 12621 if (ghRC is null) 12622 throw new WindowsApiException("wglCreateContextAttribsARB", GetLastError()); 12623 } else { 12624 // try to do at least something 12625 if (sdpyOpenGLContextAllowFallback || sdpyOpenGLContextVersion == 0) { 12626 sdpyOpenGLContextVersion = 0; 12627 ghRC = wglCreateContext(ghDC); 12628 } 12629 if (ghRC is null) 12630 throw new WindowsApiException("wglCreateContext", GetLastError()); 12631 } 12632 } 12633 } 12634 12635 if(opengl == OpenGlOptions.no) { 12636 buffer = CreateCompatibleBitmap(hdc, width, height); 12637 12638 auto hdcBmp = CreateCompatibleDC(hdc); 12639 // make sure it's filled with a blank slate 12640 auto oldBmp = SelectObject(hdcBmp, buffer); 12641 auto oldBrush = SelectObject(hdcBmp, GetStockObject(WHITE_BRUSH)); 12642 auto oldPen = SelectObject(hdcBmp, GetStockObject(WHITE_PEN)); 12643 gdi.Rectangle(hdcBmp, 0, 0, width, height); 12644 SelectObject(hdcBmp, oldBmp); 12645 SelectObject(hdcBmp, oldBrush); 12646 SelectObject(hdcBmp, oldPen); 12647 DeleteDC(hdcBmp); 12648 12649 bmpWidth = width; 12650 bmpHeight = height; 12651 12652 ReleaseDC(hwnd, hdc); // we keep this in opengl mode since it is a class member now 12653 } 12654 12655 // We want the window's client area to match the image size 12656 RECT rcClient, rcWindow; 12657 POINT ptDiff; 12658 GetClientRect(hwnd, &rcClient); 12659 GetWindowRect(hwnd, &rcWindow); 12660 ptDiff.x = (rcWindow.right - rcWindow.left) - rcClient.right; 12661 ptDiff.y = (rcWindow.bottom - rcWindow.top) - rcClient.bottom; 12662 MoveWindow(hwnd,rcWindow.left, rcWindow.top, width + ptDiff.x, height + ptDiff.y, true); 12663 12664 if ((customizationFlags&WindowFlags.dontAutoShow) == 0) { 12665 ShowWindow(hwnd, SW_SHOWNORMAL); 12666 } else { 12667 _hidden = true; 12668 } 12669 this._visibleForTheFirstTimeCalled = false; // hack! 12670 } 12671 12672 12673 void dispose() { 12674 if(buffer) 12675 DeleteObject(buffer); 12676 } 12677 12678 void closeWindow() { 12679 if(ghRC) { 12680 wglDeleteContext(ghRC); 12681 ghRC = null; 12682 } 12683 DestroyWindow(hwnd); 12684 } 12685 12686 bool setOpacity(ubyte alpha) { 12687 return SetLayeredWindowAttributes(hwnd, 0, alpha, LWA_ALPHA) == TRUE; 12688 } 12689 12690 HANDLE currentCursor; 12691 12692 // returns zero if it recognized the event 12693 static int triggerEvents(HWND hwnd, uint msg, WPARAM wParam, LPARAM lParam, int offsetX, int offsetY, SimpleWindow wind) { 12694 MouseEvent mouse; 12695 12696 void mouseEvent(bool isScreen, ulong mods) { 12697 auto x = LOWORD(lParam); 12698 auto y = HIWORD(lParam); 12699 if(isScreen) { 12700 POINT p; 12701 p.x = x; 12702 p.y = y; 12703 ScreenToClient(hwnd, &p); 12704 x = cast(ushort) p.x; 12705 y = cast(ushort) p.y; 12706 } 12707 12708 if(wind.resizability == Resizability.automaticallyScaleIfPossible) { 12709 x = cast(ushort)( x * wind._virtualWidth / wind._width ); 12710 y = cast(ushort)( y * wind._virtualHeight / wind._height ); 12711 } 12712 12713 mouse.x = x + offsetX; 12714 mouse.y = y + offsetY; 12715 12716 wind.mdx(mouse); 12717 mouse.modifierState = cast(int) mods; 12718 mouse.window = wind; 12719 12720 if(wind.handleMouseEvent) 12721 wind.handleMouseEvent(mouse); 12722 } 12723 12724 switch(msg) { 12725 case WM_GETMINMAXINFO: 12726 MINMAXINFO* mmi = cast(MINMAXINFO*) lParam; 12727 12728 if(wind.minWidth > 0) { 12729 RECT rect; 12730 rect.left = 100; 12731 rect.top = 100; 12732 rect.right = wind.minWidth + 100; 12733 rect.bottom = wind.minHeight + 100; 12734 if(!AdjustWindowRect(&rect, GetWindowLong(wind.hwnd, GWL_STYLE), GetMenu(wind.hwnd) !is null)) 12735 throw new WindowsApiException("AdjustWindowRect", GetLastError()); 12736 12737 mmi.ptMinTrackSize.x = rect.right - rect.left; 12738 mmi.ptMinTrackSize.y = rect.bottom - rect.top; 12739 } 12740 12741 if(wind.maxWidth < int.max) { 12742 RECT rect; 12743 rect.left = 100; 12744 rect.top = 100; 12745 rect.right = wind.maxWidth + 100; 12746 rect.bottom = wind.maxHeight + 100; 12747 if(!AdjustWindowRect(&rect, GetWindowLong(wind.hwnd, GWL_STYLE), GetMenu(wind.hwnd) !is null)) 12748 throw new WindowsApiException("AdjustWindowRect", GetLastError()); 12749 12750 mmi.ptMaxTrackSize.x = rect.right - rect.left; 12751 mmi.ptMaxTrackSize.y = rect.bottom - rect.top; 12752 } 12753 break; 12754 case WM_CHAR: 12755 wchar c = cast(wchar) wParam; 12756 if(wind.handleCharEvent) 12757 wind.handleCharEvent(cast(dchar) c); 12758 break; 12759 case WM_SETFOCUS: 12760 case WM_KILLFOCUS: 12761 wind._focused = (msg == WM_SETFOCUS); 12762 if (msg == WM_SETFOCUS) altPressed = false; //k8: reset alt state on defocus (it is better than nothing...) 12763 if(wind.onFocusChange) 12764 wind.onFocusChange(msg == WM_SETFOCUS); 12765 break; 12766 12767 case WM_SYSKEYDOWN: 12768 goto case; 12769 case WM_SYSKEYUP: 12770 if(lParam & (1 << 29)) { 12771 goto case; 12772 } else { 12773 // no window has keyboard focus 12774 goto default; 12775 } 12776 case WM_KEYDOWN: 12777 case WM_KEYUP: 12778 KeyEvent ev; 12779 ev.key = cast(Key) wParam; 12780 ev.pressed = (msg == WM_KEYDOWN || msg == WM_SYSKEYDOWN); 12781 if (wParam == 0x12) ev.key = Key.Alt; // windows does it this way 12782 12783 ev.hardwareCode = (lParam & 0xff0000) >> 16; 12784 12785 if(GetKeyState(Key.Shift)&0x8000 || GetKeyState(Key.Shift_r)&0x8000) 12786 ev.modifierState |= ModifierState.shift; 12787 //k8: this doesn't work; thanks for nothing, windows 12788 /*if(GetKeyState(Key.Alt)&0x8000 || GetKeyState(Key.Alt_r)&0x8000) 12789 ev.modifierState |= ModifierState.alt;*/ 12790 // this never seems to actually be set 12791 // if (lParam & 0x2000 /* KF_ALTDOWN */) ev.modifierState |= ModifierState.alt; else ev.modifierState &= ~ModifierState.alt; 12792 12793 if (wParam == 0x12) { 12794 altPressed = (msg == WM_SYSKEYDOWN); 12795 } 12796 12797 if(msg == WM_KEYDOWN || msg == WM_KEYUP) { 12798 altPressed = false; 12799 } 12800 // sdpyPrintDebugString(altPressed ? "alt down" : " up "); 12801 12802 if (altPressed) ev.modifierState |= ModifierState.alt; else ev.modifierState &= ~ModifierState.alt; 12803 if(GetKeyState(Key.Ctrl)&0x8000 || GetKeyState(Key.Ctrl_r)&0x8000) 12804 ev.modifierState |= ModifierState.ctrl; 12805 if(GetKeyState(Key.Windows)&0x8000 || GetKeyState(Key.Windows_r)&0x8000) 12806 ev.modifierState |= ModifierState.windows; 12807 if(GetKeyState(Key.NumLock)) 12808 ev.modifierState |= ModifierState.numLock; 12809 if(GetKeyState(Key.CapsLock)) 12810 ev.modifierState |= ModifierState.capsLock; 12811 12812 /+ 12813 // we always want to send the character too, so let's convert it 12814 ubyte[256] state; 12815 wchar[16] buffer; 12816 GetKeyboardState(state.ptr); 12817 ToUnicodeEx(wParam, lParam, state.ptr, buffer.ptr, buffer.length, 0, null); 12818 12819 foreach(dchar d; buffer) { 12820 ev.character = d; 12821 break; 12822 } 12823 +/ 12824 12825 ev.window = wind; 12826 if(wind.handleKeyEvent) 12827 wind.handleKeyEvent(ev); 12828 break; 12829 case 0x020a /*WM_MOUSEWHEEL*/: 12830 // send click 12831 mouse.type = cast(MouseEventType) 1; 12832 mouse.button = ((GET_WHEEL_DELTA_WPARAM(wParam) > 0) ? MouseButton.wheelUp : MouseButton.wheelDown); 12833 mouseEvent(true, LOWORD(wParam)); 12834 12835 // also send release 12836 mouse.type = cast(MouseEventType) 2; 12837 mouse.button = ((GET_WHEEL_DELTA_WPARAM(wParam) > 0) ? MouseButton.wheelUp : MouseButton.wheelDown); 12838 mouseEvent(true, LOWORD(wParam)); 12839 break; 12840 case WM_MOUSEMOVE: 12841 mouse.type = cast(MouseEventType) 0; 12842 mouseEvent(false, wParam); 12843 break; 12844 case WM_LBUTTONDOWN: 12845 case WM_LBUTTONDBLCLK: 12846 mouse.type = cast(MouseEventType) 1; 12847 mouse.button = MouseButton.left; 12848 mouse.doubleClick = msg == WM_LBUTTONDBLCLK; 12849 mouseEvent(false, wParam); 12850 break; 12851 case WM_LBUTTONUP: 12852 mouse.type = cast(MouseEventType) 2; 12853 mouse.button = MouseButton.left; 12854 mouseEvent(false, wParam); 12855 break; 12856 case WM_RBUTTONDOWN: 12857 case WM_RBUTTONDBLCLK: 12858 mouse.type = cast(MouseEventType) 1; 12859 mouse.button = MouseButton.right; 12860 mouse.doubleClick = msg == WM_RBUTTONDBLCLK; 12861 mouseEvent(false, wParam); 12862 break; 12863 case WM_RBUTTONUP: 12864 mouse.type = cast(MouseEventType) 2; 12865 mouse.button = MouseButton.right; 12866 mouseEvent(false, wParam); 12867 break; 12868 case WM_MBUTTONDOWN: 12869 case WM_MBUTTONDBLCLK: 12870 mouse.type = cast(MouseEventType) 1; 12871 mouse.button = MouseButton.middle; 12872 mouse.doubleClick = msg == WM_MBUTTONDBLCLK; 12873 mouseEvent(false, wParam); 12874 break; 12875 case WM_MBUTTONUP: 12876 mouse.type = cast(MouseEventType) 2; 12877 mouse.button = MouseButton.middle; 12878 mouseEvent(false, wParam); 12879 break; 12880 case WM_XBUTTONDOWN: 12881 case WM_XBUTTONDBLCLK: 12882 mouse.type = cast(MouseEventType) 1; 12883 mouse.button = HIWORD(wParam) == 1 ? MouseButton.backButton : MouseButton.forwardButton; 12884 mouse.doubleClick = msg == WM_XBUTTONDBLCLK; 12885 mouseEvent(false, wParam); 12886 return 1; // MSDN says special treatment here, return TRUE to bypass simulation programs 12887 case WM_XBUTTONUP: 12888 mouse.type = cast(MouseEventType) 2; 12889 mouse.button = HIWORD(wParam) == 1 ? MouseButton.backButton : MouseButton.forwardButton; 12890 mouseEvent(false, wParam); 12891 return 1; // see: https://msdn.microsoft.com/en-us/library/windows/desktop/ms646246(v=vs.85).aspx 12892 12893 default: return 1; 12894 } 12895 return 0; 12896 } 12897 12898 HWND hwnd; 12899 private int oldWidth; 12900 private int oldHeight; 12901 private bool inSizeMove; 12902 12903 /++ 12904 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. 12905 12906 History: 12907 Added November 23, 2021 12908 12909 Not fully stable, may be moved out of the impl struct. 12910 12911 Default value changed to `true` on February 15, 2021 12912 +/ 12913 bool doLiveResizing = true; 12914 12915 package int bmpWidth; 12916 package int bmpHeight; 12917 12918 // the extern(Windows) wndproc should just forward to this 12919 LRESULT windowProcedure(HWND hwnd, uint msg, WPARAM wParam, LPARAM lParam) { 12920 try { 12921 assert(hwnd is this.hwnd); 12922 12923 if(triggerEvents(hwnd, msg, wParam, lParam, 0, 0, this)) 12924 switch(msg) { 12925 case WM_MENUCHAR: // menu active but key not associated with a thing. 12926 // you would ideally use this for like a search function but sdpy not that ideally designed. alas. 12927 // The main things we can do are select, execute, close, or ignore 12928 // the default is ignore, but it doesn't *just* ignore it - it also dings an audio alert to 12929 // the user. This can be a bit annoying for sdpy things so instead im overriding and setting it 12930 // to close, which can be really annoying when you hit the wrong button. but meh i think for sdpy 12931 // that's the lesser bad choice rn. Can always override by returning true in triggerEvents.... 12932 12933 // returns the value in the *high order word* of the return value 12934 // hence the << 16 12935 return 1 << 16; // MNC_CLOSE, close the menu without dinging at the user 12936 case WM_SETCURSOR: 12937 if(cast(HWND) wParam !is hwnd) 12938 return 0; // further processing elsewhere 12939 12940 if(LOWORD(lParam) == HTCLIENT && (this.curHidden > 0 || currentCursor !is null)) { 12941 SetCursor(this.curHidden > 0 ? null : currentCursor); 12942 return 1; 12943 } else { 12944 return DefWindowProc(hwnd, msg, wParam, lParam); 12945 } 12946 //break; 12947 12948 case WM_CLOSE: 12949 if (this.closeQuery !is null) this.closeQuery(); else this.close(); 12950 break; 12951 case WM_DESTROY: 12952 if (this.visibilityChanged !is null && this._visible) this.visibilityChanged(false); 12953 12954 if (this.onDestroyed !is null) try { this.onDestroyed(); } catch (Exception e) {} // sorry 12955 SimpleWindow.nativeMapping.remove(hwnd); 12956 CapableOfHandlingNativeEvent.nativeHandleMapping.remove(hwnd); 12957 12958 bool anyImportant = false; 12959 foreach(SimpleWindow w; SimpleWindow.nativeMapping) 12960 if(w.beingOpenKeepsAppOpen) { 12961 anyImportant = true; 12962 break; 12963 } 12964 if(!anyImportant) { 12965 PostQuitMessage(0); 12966 } 12967 break; 12968 case 0x02E0 /*WM_DPICHANGED*/: 12969 this.actualDpi_ = LOWORD(wParam); // hiword is the y param but it is the same per docs 12970 12971 RECT* prcNewWindow = cast(RECT*)lParam; 12972 // docs say this is the recommended position and we should honor it 12973 SetWindowPos(hwnd, 12974 null, 12975 prcNewWindow.left, 12976 prcNewWindow.top, 12977 prcNewWindow.right - prcNewWindow.left, 12978 prcNewWindow.bottom - prcNewWindow.top, 12979 SWP_NOZORDER | SWP_NOACTIVATE); 12980 12981 // doing this because of https://github.com/microsoft/Windows-classic-samples/blob/main/Samples/DPIAwarenessPerWindow/client/DpiAwarenessContext.cpp 12982 // im not sure it is completely correct 12983 // but without it the tabs and such do look weird as things change. 12984 if(SystemParametersInfoForDpi) { 12985 LOGFONT lfText; 12986 SystemParametersInfoForDpi(SPI_GETICONTITLELOGFONT, lfText.sizeof, &lfText, FALSE, this.actualDpi_); 12987 HFONT hFontNew = CreateFontIndirect(&lfText); 12988 if (hFontNew) 12989 { 12990 //DeleteObject(hFontOld); 12991 static extern(Windows) BOOL helper(HWND hWnd, LPARAM lParam) { 12992 SendMessage(hWnd, WM_SETFONT, cast(WPARAM)lParam, MAKELPARAM(TRUE, 0)); 12993 return TRUE; 12994 } 12995 EnumChildWindows(hwnd, &helper, cast(LPARAM) hFontNew); 12996 } 12997 } 12998 12999 if(this.onDpiChanged) 13000 this.onDpiChanged(); 13001 break; 13002 case WM_ENTERIDLE: 13003 // when a menu is up, it stops normal event processing (modal message loop) 13004 // but this at least gives us a chance to SOMETIMES catch up 13005 // FIXME: I can use SetTimer while idle to keep working i think... but idk when i'd destroy it. 13006 SimpleWindow.processAllCustomEvents; 13007 SimpleWindow.processAllCustomEvents; 13008 SleepEx(0, true); 13009 break; 13010 case WM_SIZE: 13011 if(wParam == 1 /* SIZE_MINIMIZED */) 13012 break; 13013 _width = LOWORD(lParam); 13014 _height = HIWORD(lParam); 13015 13016 // I want to avoid tearing in the windows (my code is inefficient 13017 // so this is a hack around that) so while sizing, we don't trigger, 13018 // but we do want to trigger on events like mazimize. 13019 if(!inSizeMove || doLiveResizing) 13020 goto size_changed; 13021 break; 13022 /+ 13023 case WM_SIZING: 13024 writeln("size"); 13025 break; 13026 +/ 13027 // I don't like the tearing I get when redrawing on WM_SIZE 13028 // (I know there's other ways to fix that but I don't like that behavior anyway) 13029 // so instead it is going to redraw only at the end of a size. 13030 case 0x0231: /* WM_ENTERSIZEMOVE */ 13031 inSizeMove = true; 13032 break; 13033 case 0x0232: /* WM_EXITSIZEMOVE */ 13034 inSizeMove = false; 13035 13036 size_changed: 13037 13038 // nothing relevant changed, don't bother redrawing 13039 if(oldWidth == _width && oldHeight == _height) { 13040 if(msg == 0x0232) 13041 goto finalize_resize; 13042 break; 13043 } 13044 13045 // note: OpenGL windows don't use a backing bmp, so no need to change them 13046 // if resizability is anything other than allowResizing, it is meant to either stretch the one image or just do nothing 13047 if(openglMode == OpenGlOptions.no) { // && resizability == Resizability.allowResizing) { 13048 // gotta get the double buffer bmp to match the window 13049 // FIXME: could this be more efficient? it never relinquishes a large bitmap 13050 13051 // if it is auto-scaled, we keep the backing bitmap the same size all the time 13052 if(resizability != Resizability.automaticallyScaleIfPossible) 13053 if(_width > bmpWidth || _height > bmpHeight) { 13054 auto hdc = GetDC(hwnd); 13055 auto oldBuffer = buffer; 13056 buffer = CreateCompatibleBitmap(hdc, _width, _height); 13057 13058 auto hdcBmp = CreateCompatibleDC(hdc); 13059 auto oldBmp = SelectObject(hdcBmp, buffer); 13060 13061 auto hdcOldBmp = CreateCompatibleDC(hdc); 13062 auto oldOldBmp = SelectObject(hdcOldBmp, oldBuffer); 13063 13064 /+ 13065 RECT r; 13066 r.left = 0; 13067 r.top = 0; 13068 r.right = width; 13069 r.bottom = height; 13070 auto c = Color.green; 13071 auto brush = CreateSolidBrush(RGB(c.r, c.g, c.b)); 13072 FillRect(hdcBmp, &r, brush); 13073 DeleteObject(brush); 13074 +/ 13075 13076 BitBlt(hdcBmp, 0, 0, bmpWidth, bmpHeight, hdcOldBmp, 0, 0, SRCCOPY); 13077 13078 bmpWidth = _width; 13079 bmpHeight = _height; 13080 13081 SelectObject(hdcOldBmp, oldOldBmp); 13082 DeleteDC(hdcOldBmp); 13083 13084 SelectObject(hdcBmp, oldBmp); 13085 DeleteDC(hdcBmp); 13086 13087 ReleaseDC(hwnd, hdc); 13088 13089 DeleteObject(oldBuffer); 13090 } 13091 } 13092 13093 updateOpenglViewportIfNeeded(_width, _height); 13094 13095 if(resizability != Resizability.automaticallyScaleIfPossible) 13096 if(windowResized !is null) 13097 windowResized(_width, _height); 13098 13099 /+ 13100 if(inSizeMove) { 13101 // SimpleWindow.processAllCustomEvents(); 13102 // SimpleWindow.processAllCustomEvents(); 13103 13104 //RedrawWindow(hwnd, null, null, RDW_ERASE | RDW_INVALIDATE | RDW_ALLCHILDREN); 13105 //sdpyPrintDebugString("redraw b"); 13106 } else { 13107 +/ { 13108 finalize_resize: 13109 // when it is all done, make sure everything is freshly drawn or there might be 13110 // weird bugs left. 13111 SimpleWindow.processAllCustomEvents(); 13112 SimpleWindow.processAllCustomEvents(); 13113 13114 RedrawWindow(hwnd, null, null, RDW_ERASE | RDW_INVALIDATE | RDW_ALLCHILDREN); 13115 // sdpyPrintDebugString("redraw"); 13116 } 13117 13118 oldWidth = this._width; 13119 oldHeight = this._height; 13120 break; 13121 case WM_ERASEBKGND: 13122 // call `visibleForTheFirstTime` here, so we can do initialization as early as possible 13123 if (!this._visibleForTheFirstTimeCalled) { 13124 this._visibleForTheFirstTimeCalled = true; 13125 if (this.visibleForTheFirstTime !is null) { 13126 this.visibleForTheFirstTime(); 13127 } 13128 } 13129 // block it in OpenGL mode, 'cause no sane person will (or should) draw windows controls over OpenGL scene 13130 version(without_opengl) {} else { 13131 if (openglMode == OpenGlOptions.yes) return 1; 13132 } 13133 // call windows default handler, so it can paint standard controls 13134 goto default; 13135 case WM_CTLCOLORBTN: 13136 case WM_CTLCOLORSTATIC: 13137 SetBkMode(cast(HDC) wParam, TRANSPARENT); 13138 return cast(typeof(return)) //GetStockObject(NULL_BRUSH); 13139 GetSysColorBrush(COLOR_3DFACE); 13140 //break; 13141 case WM_SHOWWINDOW: 13142 auto before = this._visible; 13143 this._visible = (wParam != 0); 13144 if (!this._visibleForTheFirstTimeCalled && this._visible) { 13145 this._visibleForTheFirstTimeCalled = true; 13146 if (this.visibleForTheFirstTime !is null) { 13147 this.visibleForTheFirstTime(); 13148 } 13149 } 13150 if (this.visibilityChanged !is null && this._visible != before) this.visibilityChanged(this._visible); 13151 break; 13152 case WM_PAINT: { 13153 if (!this._visibleForTheFirstTimeCalled) { 13154 this._visibleForTheFirstTimeCalled = true; 13155 if (this.visibleForTheFirstTime !is null) { 13156 this.visibleForTheFirstTime(); 13157 } 13158 } 13159 13160 BITMAP bm; 13161 PAINTSTRUCT ps; 13162 13163 HDC hdc = BeginPaint(hwnd, &ps); 13164 13165 if(openglMode == OpenGlOptions.no) { 13166 13167 HDC hdcMem = CreateCompatibleDC(hdc); 13168 HBITMAP hbmOld = SelectObject(hdcMem, buffer); 13169 13170 GetObject(buffer, bm.sizeof, &bm); 13171 13172 // FIXME: only BitBlt the invalidated rectangle, not the whole thing 13173 if(resizability == Resizability.automaticallyScaleIfPossible) 13174 StretchBlt(hdc, 0, 0, this._width, this._height, hdcMem, 0, 0, bm.bmWidth, bm.bmHeight, SRCCOPY); 13175 else 13176 BitBlt(hdc, 0, 0, bm.bmWidth, bm.bmHeight, hdcMem, 0, 0, SRCCOPY); 13177 //BitBlt(hdc, ps.rcPaint.left, ps.rcPaint.top, ps.rcPaint.right - ps.rcPaint.left, ps.rcPaint.top - ps.rcPaint.bottom, hdcMem, 0, 0, SRCCOPY); 13178 13179 SelectObject(hdcMem, hbmOld); 13180 DeleteDC(hdcMem); 13181 EndPaint(hwnd, &ps); 13182 } else { 13183 EndPaint(hwnd, &ps); 13184 version(without_opengl) {} else 13185 redrawOpenGlSceneSoon(); 13186 } 13187 } break; 13188 default: 13189 return DefWindowProc(hwnd, msg, wParam, lParam); 13190 } 13191 return 0; 13192 13193 } 13194 catch(Throwable t) { 13195 sdpyPrintDebugString(t.toString); 13196 return 0; 13197 } 13198 } 13199 } 13200 13201 mixin template NativeImageImplementation() { 13202 HBITMAP handle; 13203 ubyte* rawData; 13204 13205 final: 13206 13207 Color getPixel(int x, int y) @system { 13208 auto itemsPerLine = ((cast(int) width * 3 + 3) / 4) * 4; 13209 // remember, bmps are upside down 13210 auto offset = itemsPerLine * (height - y - 1) + x * 3; 13211 13212 Color c; 13213 if(enableAlpha) 13214 c.a = rawData[offset + 3]; 13215 else 13216 c.a = 255; 13217 c.b = rawData[offset + 0]; 13218 c.g = rawData[offset + 1]; 13219 c.r = rawData[offset + 2]; 13220 c.unPremultiply(); 13221 return c; 13222 } 13223 13224 void setPixel(int x, int y, Color c) @system { 13225 auto itemsPerLine = ((cast(int) width * 3 + 3) / 4) * 4; 13226 // remember, bmps are upside down 13227 auto offset = itemsPerLine * (height - y - 1) + x * 3; 13228 13229 if(enableAlpha) 13230 c.premultiply(); 13231 13232 rawData[offset + 0] = c.b; 13233 rawData[offset + 1] = c.g; 13234 rawData[offset + 2] = c.r; 13235 if(enableAlpha) 13236 rawData[offset + 3] = c.a; 13237 } 13238 13239 void convertToRgbaBytes(ubyte[] where) @system { 13240 assert(where.length == this.width * this.height * 4); 13241 13242 auto itemsPerLine = ((cast(int) width * 3 + 3) / 4) * 4; 13243 int idx = 0; 13244 int offset = itemsPerLine * (height - 1); 13245 // remember, bmps are upside down 13246 for(int y = height - 1; y >= 0; y--) { 13247 auto offsetStart = offset; 13248 for(int x = 0; x < width; x++) { 13249 where[idx + 0] = rawData[offset + 2]; // r 13250 where[idx + 1] = rawData[offset + 1]; // g 13251 where[idx + 2] = rawData[offset + 0]; // b 13252 if(enableAlpha) { 13253 where[idx + 3] = rawData[offset + 3]; // a 13254 unPremultiplyRgba(where[idx .. idx + 4]); 13255 offset++; 13256 } else 13257 where[idx + 3] = 255; // a 13258 idx += 4; 13259 offset += 3; 13260 } 13261 13262 offset = offsetStart - itemsPerLine; 13263 } 13264 } 13265 13266 void setFromRgbaBytes(in ubyte[] what) @system { 13267 assert(what.length == this.width * this.height * 4); 13268 13269 auto itemsPerLine = enableAlpha ? (width * 4) : (((cast(int) width * 3 + 3) / 4) * 4); 13270 int idx = 0; 13271 int offset = itemsPerLine * (height - 1); 13272 // remember, bmps are upside down 13273 for(int y = height - 1; y >= 0; y--) { 13274 auto offsetStart = offset; 13275 for(int x = 0; x < width; x++) { 13276 if(enableAlpha) { 13277 auto a = what[idx + 3]; 13278 13279 rawData[offset + 2] = (a * what[idx + 0]) / 255; // r 13280 rawData[offset + 1] = (a * what[idx + 1]) / 255; // g 13281 rawData[offset + 0] = (a * what[idx + 2]) / 255; // b 13282 rawData[offset + 3] = a; // a 13283 //premultiplyBgra(rawData[offset .. offset + 4]); 13284 offset++; 13285 } else { 13286 rawData[offset + 2] = what[idx + 0]; // r 13287 rawData[offset + 1] = what[idx + 1]; // g 13288 rawData[offset + 0] = what[idx + 2]; // b 13289 } 13290 idx += 4; 13291 offset += 3; 13292 } 13293 13294 offset = offsetStart - itemsPerLine; 13295 } 13296 } 13297 13298 13299 void createImage(int width, int height, bool forcexshm=false, bool enableAlpha = false) { 13300 BITMAPINFO infoheader; 13301 infoheader.bmiHeader.biSize = infoheader.bmiHeader.sizeof; 13302 infoheader.bmiHeader.biWidth = width; 13303 infoheader.bmiHeader.biHeight = height; 13304 infoheader.bmiHeader.biPlanes = 1; 13305 infoheader.bmiHeader.biBitCount = enableAlpha ? 32: 24; 13306 infoheader.bmiHeader.biCompression = BI_RGB; 13307 13308 handle = CreateDIBSection( 13309 null, 13310 &infoheader, 13311 DIB_RGB_COLORS, 13312 cast(void**) &rawData, 13313 null, 13314 0); 13315 if(handle is null) 13316 throw new WindowsApiException("create image failed", GetLastError()); 13317 13318 } 13319 13320 void dispose() { 13321 DeleteObject(handle); 13322 } 13323 } 13324 13325 enum KEY_ESCAPE = 27; 13326 } 13327 13328 version(Emscripten) { 13329 alias int delegate(void*) NativeEventHandler; 13330 alias void* NativeWindowHandle; 13331 13332 mixin template NativeSimpleWindowImplementation() { } 13333 mixin template NativeScreenPainterImplementation() { } 13334 mixin template NativeImageImplementation() { } 13335 } 13336 13337 version(X11) { 13338 /// This is the default font used. You might change this before doing anything else with 13339 /// the library if you want to try something else. Surround that in `static if(UsingSimpledisplayX11)` 13340 /// for cross-platform compatibility. 13341 //__gshared string xfontstr = "-*-dejavu sans-medium-r-*-*-12-*-*-*-*-*-*-*"; 13342 //__gshared string xfontstr = "-*-dejavu sans-medium-r-*-*-12-*-*-*-*-*-*-*"; 13343 __gshared string xfontstr = "-*-lucida-medium-r-normal-sans-12-*-*-*-*-*-*-*"; 13344 //__gshared string xfontstr = "-*-fixed-medium-r-*-*-14-*-*-*-*-*-*-*"; 13345 13346 alias int delegate(XEvent) NativeEventHandler; 13347 alias Window NativeWindowHandle; 13348 13349 enum KEY_ESCAPE = 9; 13350 13351 mixin template NativeScreenPainterImplementation() { 13352 Display* display; 13353 Drawable d; 13354 Drawable destiny; 13355 13356 // FIXME: should the gc be static too so it isn't recreated every time draw is called? 13357 GC gc; 13358 13359 __gshared bool fontAttempted; 13360 13361 __gshared XFontStruct* defaultfont; 13362 __gshared XFontSet defaultfontset; 13363 13364 XFontStruct* font; 13365 XFontSet fontset; 13366 13367 void create(PaintingHandle window) { 13368 this.display = XDisplayConnection.get(); 13369 13370 Drawable buffer = None; 13371 if(auto sw = cast(SimpleWindow) this.window) { 13372 buffer = sw.impl.buffer; 13373 this.destiny = cast(Drawable) window; 13374 } else { 13375 buffer = cast(Drawable) window; 13376 this.destiny = None; 13377 } 13378 13379 this.d = cast(Drawable) buffer; 13380 13381 auto dgc = DefaultGC(display, DefaultScreen(display)); 13382 13383 this.gc = XCreateGC(display, d, 0, null); 13384 13385 XCopyGC(display, dgc, 0xffffffff, this.gc); 13386 13387 ensureDefaultFontLoaded(); 13388 13389 font = defaultfont; 13390 fontset = defaultfontset; 13391 13392 if(font) { 13393 XSetFont(display, gc, font.fid); 13394 } 13395 } 13396 13397 static void ensureDefaultFontLoaded() { 13398 if(!fontAttempted) { 13399 auto display = XDisplayConnection.get; 13400 auto font = XLoadQueryFont(display, xfontstr.ptr); 13401 // if the user font choice fails, fixed is pretty reliable (required by X to start!) and not bad either 13402 if(font is null) { 13403 xfontstr = "-*-fixed-medium-r-*-*-13-*-*-*-*-*-*-*"; 13404 font = XLoadQueryFont(display, xfontstr.ptr); 13405 } 13406 13407 char** lol; 13408 int lol2; 13409 char* lol3; 13410 auto fontset = XCreateFontSet(display, xfontstr.ptr, &lol, &lol2, &lol3); 13411 13412 fontAttempted = true; 13413 13414 defaultfont = font; 13415 defaultfontset = fontset; 13416 } 13417 } 13418 13419 arsd.color.Rectangle _clipRectangle; 13420 void setClipRectangle(int x, int y, int width, int height) { 13421 auto old = _clipRectangle; 13422 _clipRectangle = arsd.color.Rectangle(Point(x, y), Size(width, height)); 13423 if(old == _clipRectangle) 13424 return; 13425 13426 if(width == 0 || height == 0) { 13427 XSetClipMask(display, gc, None); 13428 13429 if(xrenderPicturePainter) { 13430 13431 XRectangle[1] rects; 13432 rects[0] = XRectangle(short.min, short.min, short.max, short.max); 13433 XRenderSetPictureClipRectangles(display, xrenderPicturePainter, 0, 0, rects.ptr, cast(int) rects.length); 13434 } 13435 13436 version(with_xft) { 13437 if(xftFont is null || xftDraw is null) 13438 return; 13439 XftDrawSetClip(xftDraw, null); 13440 } 13441 } else { 13442 XRectangle[1] rects; 13443 rects[0] = XRectangle(cast(short)(x), cast(short)(y), cast(short) width, cast(short) height); 13444 XSetClipRectangles(XDisplayConnection.get, gc, 0, 0, rects.ptr, 1, 0); 13445 13446 if(xrenderPicturePainter) 13447 XRenderSetPictureClipRectangles(display, xrenderPicturePainter, 0, 0, rects.ptr, cast(int) rects.length); 13448 13449 version(with_xft) { 13450 if(xftFont is null || xftDraw is null) 13451 return; 13452 XftDrawSetClipRectangles(xftDraw, 0, 0, rects.ptr, 1); 13453 } 13454 } 13455 } 13456 13457 version(with_xft) { 13458 XftFont* xftFont; 13459 XftDraw* xftDraw; 13460 13461 XftColor xftColor; 13462 13463 void updateXftColor() { 13464 if(xftFont is null) 13465 return; 13466 13467 // not bothering with XftColorFree since p sure i don't need it on 24 bit displays.... 13468 XRenderColor colorIn = XRenderColor(_outlineColor.r * 255, _outlineColor.g * 255, _outlineColor.b * 255, _outlineColor.a * 255); 13469 13470 XftColorAllocValue( 13471 display, 13472 DefaultVisual(display, DefaultScreen(display)), 13473 DefaultColormap(display, 0), 13474 &colorIn, 13475 &xftColor 13476 ); 13477 } 13478 } 13479 13480 private OperatingSystemFont _activeFont; 13481 void setFont(OperatingSystemFont font) { 13482 _activeFont = font; 13483 version(with_xft) { 13484 if(font && font.isXft && font.xftFont) 13485 this.xftFont = font.xftFont; 13486 else 13487 this.xftFont = null; 13488 13489 if(this.xftFont) { 13490 if(xftDraw is null) { 13491 xftDraw = XftDrawCreate( 13492 display, 13493 d, 13494 DefaultVisual(display, DefaultScreen(display)), 13495 DefaultColormap(display, 0) 13496 ); 13497 13498 updateXftColor(); 13499 } 13500 13501 return; 13502 } 13503 } 13504 13505 if(font && font.font) { 13506 this.font = font.font; 13507 this.fontset = font.fontset; 13508 XSetFont(display, gc, font.font.fid); 13509 } else { 13510 this.font = defaultfont; 13511 this.fontset = defaultfontset; 13512 } 13513 13514 } 13515 13516 private Picture xrenderPicturePainter; 13517 13518 bool manualInvalidations; 13519 void invalidateRect(Rectangle invalidRect) { 13520 // FIXME if manualInvalidations 13521 } 13522 13523 void dispose() { 13524 this.rasterOp = RasterOp.normal; 13525 13526 if(xrenderPicturePainter) { 13527 XRenderFreePicture(display, xrenderPicturePainter); 13528 xrenderPicturePainter = None; 13529 } 13530 13531 // FIXME: this.window.width/height is probably wrong 13532 13533 // src x,y then dest x, y 13534 if(destiny != None) { 13535 // FIXME: if manual invalidations we can actually only copy some of the area. 13536 // if(manualInvalidations) 13537 XSetClipMask(display, gc, None); 13538 XCopyArea(display, d, destiny, gc, 0, 0, this.window.width, this.window.height, 0, 0); 13539 } 13540 13541 XFreeGC(display, gc); 13542 13543 version(with_xft) 13544 if(xftDraw) { 13545 XftDrawDestroy(xftDraw); 13546 xftDraw = null; 13547 } 13548 13549 /+ 13550 // this should prolly legit never be used since if it destroys the font handle from a OperatingSystemFont, it also ruins a reusable resource. 13551 if(font && font !is defaultfont) { 13552 XFreeFont(display, font); 13553 font = null; 13554 } 13555 if(fontset && fontset !is defaultfontset) { 13556 XFreeFontSet(display, fontset); 13557 fontset = null; 13558 } 13559 +/ 13560 XFlush(display); 13561 13562 if(window.paintingFinishedDg !is null) 13563 window.paintingFinishedDg()(); 13564 } 13565 13566 bool backgroundIsNotTransparent = true; 13567 bool foregroundIsNotTransparent = true; 13568 13569 bool _penInitialized = false; 13570 Pen _activePen; 13571 13572 Color _outlineColor; 13573 Color _fillColor; 13574 13575 @property void pen(Pen p) { 13576 if(_penInitialized && p == _activePen) { 13577 return; 13578 } 13579 _penInitialized = true; 13580 _activePen = p; 13581 _outlineColor = p.color; 13582 13583 int style; 13584 13585 byte dashLength; 13586 13587 final switch(p.style) { 13588 case Pen.Style.Solid: 13589 style = 0 /*LineSolid*/; 13590 break; 13591 case Pen.Style.Dashed: 13592 style = 1 /*LineOnOffDash*/; 13593 dashLength = 4; 13594 break; 13595 case Pen.Style.Dotted: 13596 style = 1 /*LineOnOffDash*/; 13597 dashLength = 1; 13598 break; 13599 } 13600 13601 XSetLineAttributes(display, gc, p.width, style, style == 0 ? 3 : 0, 0); 13602 if(dashLength) 13603 XSetDashes(display, gc, 0, &dashLength, 1); 13604 13605 if(p.color.a == 0) { 13606 foregroundIsNotTransparent = false; 13607 return; 13608 } 13609 13610 foregroundIsNotTransparent = true; 13611 13612 XSetForeground(display, gc, colorToX(p.color, display)); 13613 13614 version(with_xft) 13615 updateXftColor(); 13616 } 13617 13618 RasterOp _currentRasterOp; 13619 bool _currentRasterOpInitialized = false; 13620 @property void rasterOp(RasterOp op) { 13621 if(_currentRasterOpInitialized && _currentRasterOp == op) 13622 return; 13623 _currentRasterOp = op; 13624 _currentRasterOpInitialized = true; 13625 int mode; 13626 final switch(op) { 13627 case RasterOp.normal: 13628 mode = GXcopy; 13629 break; 13630 case RasterOp.xor: 13631 mode = GXxor; 13632 break; 13633 } 13634 XSetFunction(display, gc, mode); 13635 } 13636 13637 13638 bool _fillColorInitialized = false; 13639 13640 @property void fillColor(Color c) { 13641 if(_fillColorInitialized && _fillColor == c) 13642 return; // already good, no need to waste time calling it 13643 _fillColor = c; 13644 _fillColorInitialized = true; 13645 if(c.a == 0) { 13646 backgroundIsNotTransparent = false; 13647 return; 13648 } 13649 13650 backgroundIsNotTransparent = true; 13651 13652 XSetBackground(display, gc, colorToX(c, display)); 13653 13654 } 13655 13656 void swapColors() { 13657 auto tmp = _fillColor; 13658 fillColor = _outlineColor; 13659 auto newPen = _activePen; 13660 newPen.color = tmp; 13661 pen(newPen); 13662 } 13663 13664 uint colorToX(Color c, Display* display) { 13665 auto visual = DefaultVisual(display, DefaultScreen(display)); 13666 import core.bitop; 13667 uint color = 0; 13668 { 13669 auto startBit = bsf(visual.red_mask); 13670 auto lastBit = bsr(visual.red_mask); 13671 auto r = cast(uint) c.r; 13672 r >>= 7 - (lastBit - startBit); 13673 r <<= startBit; 13674 color |= r; 13675 } 13676 { 13677 auto startBit = bsf(visual.green_mask); 13678 auto lastBit = bsr(visual.green_mask); 13679 auto g = cast(uint) c.g; 13680 g >>= 7 - (lastBit - startBit); 13681 g <<= startBit; 13682 color |= g; 13683 } 13684 { 13685 auto startBit = bsf(visual.blue_mask); 13686 auto lastBit = bsr(visual.blue_mask); 13687 auto b = cast(uint) c.b; 13688 b >>= 7 - (lastBit - startBit); 13689 b <<= startBit; 13690 color |= b; 13691 } 13692 13693 13694 13695 return color; 13696 } 13697 13698 void drawImage(int x, int y, Image i, int ix, int iy, int w, int h) { 13699 // source x, source y 13700 if(ix >= i.width) return; 13701 if(iy >= i.height) return; 13702 if(ix + w > i.width) w = i.width - ix; 13703 if(iy + h > i.height) h = i.height - iy; 13704 if(i.usingXshm) 13705 XShmPutImage(display, d, gc, i.handle, ix, iy, x, y, w, h, false); 13706 else 13707 XPutImage(display, d, gc, i.handle, ix, iy, x, y, w, h); 13708 } 13709 13710 void drawPixmap(Sprite s, int x, int y, int ix, int iy, int w, int h) { 13711 if(s.enableAlpha) { 13712 // the Sprite must be created first, meaning if we're here, XRender is already loaded 13713 if(this.xrenderPicturePainter == None) { 13714 XRenderPictureAttributes attrs; 13715 // FIXME: I can prolly reuse this as long as the pixmap itself is valid. 13716 xrenderPicturePainter = XRenderCreatePicture(display, d, Sprite.RGB24, 0, &attrs); 13717 13718 // need to initialize the clip 13719 XRectangle[1] rects; 13720 rects[0] = XRectangle(cast(short)(_clipRectangle.left), cast(short)(_clipRectangle.top), cast(short) _clipRectangle.width, cast(short) _clipRectangle.height); 13721 13722 if(_clipRectangle != Rectangle.init) 13723 XRenderSetPictureClipRectangles(display, xrenderPicturePainter, 0, 0, rects.ptr, cast(int) rects.length); 13724 } 13725 13726 XRenderComposite( 13727 display, 13728 3, // PicOpOver 13729 s.xrenderPicture, 13730 None, 13731 this.xrenderPicturePainter, 13732 ix, 13733 iy, 13734 0, 13735 0, 13736 x, 13737 y, 13738 w ? w : s.width, 13739 h ? h : s.height 13740 ); 13741 } else { 13742 XCopyArea(display, s.handle, d, gc, ix, iy, w ? w : s.width, h ? h : s.height, x, y); 13743 } 13744 } 13745 13746 int fontHeight() { 13747 version(with_xft) 13748 if(xftFont !is null) 13749 return xftFont.height; 13750 if(font) 13751 return font.max_bounds.ascent + font.max_bounds.descent; 13752 return 12; // pretty common default... 13753 } 13754 13755 int textWidth(in char[] line) { 13756 version(with_xft) 13757 if(xftFont) { 13758 if(line.length == 0) 13759 return 0; 13760 XGlyphInfo extents; 13761 XftTextExtentsUtf8(display, xftFont, line.ptr, cast(int) line.length, &extents); 13762 return extents.width; 13763 } 13764 13765 if(fontset) { 13766 if(line.length == 0) 13767 return 0; 13768 XRectangle rect; 13769 Xutf8TextExtents(fontset, line.ptr, cast(int) line.length, null, &rect); 13770 13771 return rect.width; 13772 } 13773 13774 if(font) 13775 // FIXME: unicode 13776 return XTextWidth( font, line.ptr, cast(int) line.length); 13777 else 13778 return fontHeight / 2 * cast(int) line.length; // if no font is loaded, it is prolly Fixed, which is a 2:1 ratio 13779 } 13780 13781 Size textSize(in char[] text) { 13782 auto maxWidth = 0; 13783 auto lineHeight = fontHeight; 13784 int h = text.length ? 0 : lineHeight + 4; // if text is empty, it still gives the line height 13785 foreach(line; text.split('\n')) { 13786 int textWidth = this.textWidth(line); 13787 if(textWidth > maxWidth) 13788 maxWidth = textWidth; 13789 h += lineHeight + 4; 13790 } 13791 return Size(maxWidth, h); 13792 } 13793 13794 void drawText(in int x, in int y, in int x2, in int y2, in char[] originalText, in uint alignment) { 13795 const(char)[] text; 13796 version(with_xft) 13797 if(xftFont) { 13798 text = originalText; 13799 goto loaded; 13800 } 13801 13802 if(fontset) 13803 text = originalText; 13804 else { 13805 text.reserve(originalText.length); 13806 // the first 256 unicode codepoints are the same as ascii and latin-1, which is what X expects, so we can keep all those 13807 // then strip the rest so there isn't garbage 13808 foreach(dchar ch; originalText) 13809 if(ch < 256) 13810 text ~= cast(ubyte) ch; 13811 else 13812 text ~= 191; // FIXME: using a random character (upside down question mark) to fill the space 13813 } 13814 loaded: 13815 if(text.length == 0) 13816 return; 13817 13818 // FIXME: should we clip it to the bounding box? 13819 int textHeight = fontHeight; 13820 13821 auto lines = text.split('\n'); 13822 13823 const lineHeight = textHeight; 13824 textHeight *= lines.length; 13825 13826 int cy = y; 13827 13828 if(alignment & TextAlignment.VerticalBottom) { 13829 if(y2 <= 0) 13830 return; 13831 auto h = y2 - y; 13832 if(h > textHeight) { 13833 cy += h - textHeight; 13834 cy -= lineHeight / 2; 13835 } 13836 } else if(alignment & TextAlignment.VerticalCenter) { 13837 if(y2 <= 0) 13838 return; 13839 auto h = y2 - y; 13840 if(textHeight < h) { 13841 cy += (h - textHeight) / 2; 13842 //cy -= lineHeight / 4; 13843 } 13844 } 13845 13846 foreach(line; text.split('\n')) { 13847 int textWidth = this.textWidth(line); 13848 13849 int px = x, py = cy; 13850 13851 if(alignment & TextAlignment.Center) { 13852 if(x2 <= 0) 13853 return; 13854 auto w = x2 - x; 13855 if(w > textWidth) 13856 px += (w - textWidth) / 2; 13857 } else if(alignment & TextAlignment.Right) { 13858 if(x2 <= 0) 13859 return; 13860 auto pos = x2 - textWidth; 13861 if(pos > x) 13862 px = pos; 13863 } 13864 13865 version(with_xft) 13866 if(xftFont) { 13867 XftDrawStringUtf8(xftDraw, &xftColor, xftFont, px, py + xftFont.ascent, line.ptr, cast(int) line.length); 13868 13869 goto carry_on; 13870 } 13871 13872 if(fontset) 13873 Xutf8DrawString(display, d, fontset, gc, px, py + (font ? font.max_bounds.ascent : lineHeight), line.ptr, cast(int) line.length); 13874 else 13875 XDrawString(display, d, gc, px, py + (font ? font.max_bounds.ascent : lineHeight), line.ptr, cast(int) line.length); 13876 carry_on: 13877 cy += lineHeight + 4; 13878 } 13879 } 13880 13881 void drawPixel(int x, int y) { 13882 XDrawPoint(display, d, gc, x, y); 13883 } 13884 13885 // The basic shapes, outlined 13886 13887 void drawLine(int x1, int y1, int x2, int y2) { 13888 if(foregroundIsNotTransparent) 13889 XDrawLine(display, d, gc, x1, y1, x2, y2); 13890 } 13891 13892 void drawRectangle(int x, int y, int width, int height) { 13893 if(backgroundIsNotTransparent) { 13894 swapColors(); 13895 XFillRectangle(display, d, gc, x+1, y+1, width-2, height-2); // Need to ensure pixels are only drawn once... 13896 swapColors(); 13897 } 13898 // 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 13899 if(foregroundIsNotTransparent) 13900 XDrawRectangle(display, d, gc, x + _activePen.width / 2, y + _activePen.width / 2, width - 1 - _activePen.width / 2, height - 1 - _activePen.width / 2); 13901 } 13902 13903 void drawRectangleRounded(Point upperLeft, Point lowerRight, int borderRadius) { 13904 int[4] radii = borderRadius; 13905 auto r = Rectangle(upperLeft, lowerRight); 13906 13907 if(backgroundIsNotTransparent) { 13908 swapColors(); 13909 // FIXME these overlap and thus draw the pixels multiple times 13910 XFillRectangle(display, d, gc, r.left, r.top + borderRadius/2, r.width, r.height - borderRadius); 13911 XFillRectangle(display, d, gc, r.left + borderRadius/2, r.top, r.width - borderRadius, r.height); 13912 swapColors(); 13913 } 13914 13915 drawLine(r.left + borderRadius / 2, r.top, r.right - borderRadius / 2, r.top); 13916 drawLine(r.left + borderRadius / 2, r.bottom-1, r.right - borderRadius / 2, r.bottom-1); 13917 drawLine(r.left, r.top + borderRadius / 2, r.left, r.bottom - borderRadius / 2); 13918 drawLine(r.right - 1, r.top + borderRadius / 2, r.right - 1, r.bottom - borderRadius / 2); 13919 13920 //drawRectangle(r.left + borderRadius/2, r.top, r.width - borderRadius, r.height); 13921 13922 drawArc(r.upperLeft.x, r.upperLeft.y, radii[0], radii[0], 90*64, 90*64); 13923 drawArc(r.upperRight.x - radii[1], r.upperRight.y, radii[1] - 1, radii[1], 0*64, 90*64); 13924 drawArc(r.lowerLeft.x, r.lowerLeft.y - radii[2], radii[2], radii[2] - 1, 180*64, 90*64); 13925 drawArc(r.lowerRight.x - radii[3], r.lowerRight.y - radii[3], radii[3] - 1, radii[3] - 1, 270*64, 90*64); 13926 } 13927 13928 13929 /// Arguments are the points of the bounding rectangle 13930 void drawEllipse(int x1, int y1, int x2, int y2) { 13931 drawArc(x1, y1, x2 - x1, y2 - y1, 0, 360 * 64); 13932 } 13933 13934 // NOTE: start and finish are in units of degrees * 64 13935 void drawArc(int x1, int y1, int width, int height, int start, int length) { 13936 if(backgroundIsNotTransparent) { 13937 swapColors(); 13938 XFillArc(display, d, gc, x1, y1, width, height, start, length); 13939 swapColors(); 13940 } 13941 if(foregroundIsNotTransparent) { 13942 XDrawArc(display, d, gc, x1, y1, width, height, start, length); 13943 13944 // Windows draws the straight lines on the edges too so FIXME sort of 13945 } 13946 } 13947 13948 void drawPolygon(Point[] vertexes) { 13949 XPoint[16] pointsBuffer; 13950 XPoint[] points; 13951 if(vertexes.length <= pointsBuffer.length) 13952 points = pointsBuffer[0 .. vertexes.length]; 13953 else 13954 points.length = vertexes.length; 13955 13956 foreach(i, p; vertexes) { 13957 points[i].x = cast(short) p.x; 13958 points[i].y = cast(short) p.y; 13959 } 13960 13961 if(backgroundIsNotTransparent) { 13962 swapColors(); 13963 XFillPolygon(display, d, gc, points.ptr, cast(int) points.length, PolygonShape.Complex, CoordMode.CoordModeOrigin); 13964 swapColors(); 13965 } 13966 if(foregroundIsNotTransparent) { 13967 XDrawLines(display, d, gc, points.ptr, cast(int) points.length, CoordMode.CoordModeOrigin); 13968 } 13969 } 13970 } 13971 13972 /* XRender { */ 13973 13974 struct XRenderColor { 13975 ushort red; 13976 ushort green; 13977 ushort blue; 13978 ushort alpha; 13979 } 13980 13981 alias Picture = XID; 13982 alias PictFormat = XID; 13983 13984 struct XGlyphInfo { 13985 ushort width; 13986 ushort height; 13987 short x; 13988 short y; 13989 short xOff; 13990 short yOff; 13991 } 13992 13993 struct XRenderDirectFormat { 13994 short red; 13995 short redMask; 13996 short green; 13997 short greenMask; 13998 short blue; 13999 short blueMask; 14000 short alpha; 14001 short alphaMask; 14002 } 14003 14004 struct XRenderPictFormat { 14005 PictFormat id; 14006 int type; 14007 int depth; 14008 XRenderDirectFormat direct; 14009 Colormap colormap; 14010 } 14011 14012 enum PictFormatID = (1 << 0); 14013 enum PictFormatType = (1 << 1); 14014 enum PictFormatDepth = (1 << 2); 14015 enum PictFormatRed = (1 << 3); 14016 enum PictFormatRedMask =(1 << 4); 14017 enum PictFormatGreen = (1 << 5); 14018 enum PictFormatGreenMask=(1 << 6); 14019 enum PictFormatBlue = (1 << 7); 14020 enum PictFormatBlueMask =(1 << 8); 14021 enum PictFormatAlpha = (1 << 9); 14022 enum PictFormatAlphaMask=(1 << 10); 14023 enum PictFormatColormap =(1 << 11); 14024 14025 struct XRenderPictureAttributes { 14026 int repeat; 14027 Picture alpha_map; 14028 int alpha_x_origin; 14029 int alpha_y_origin; 14030 int clip_x_origin; 14031 int clip_y_origin; 14032 Pixmap clip_mask; 14033 Bool graphics_exposures; 14034 int subwindow_mode; 14035 int poly_edge; 14036 int poly_mode; 14037 Atom dither; 14038 Bool component_alpha; 14039 } 14040 14041 alias int XFixed; 14042 14043 struct XPointFixed { 14044 XFixed x, y; 14045 } 14046 14047 struct XCircle { 14048 XFixed x; 14049 XFixed y; 14050 XFixed radius; 14051 } 14052 14053 struct XTransform { 14054 XFixed[3][3] matrix; 14055 } 14056 14057 struct XFilters { 14058 int nfilter; 14059 char **filter; 14060 int nalias; 14061 short *alias_; 14062 } 14063 14064 struct XIndexValue { 14065 c_ulong pixel; 14066 ushort red, green, blue, alpha; 14067 } 14068 14069 struct XAnimCursor { 14070 Cursor cursor; 14071 c_ulong delay; 14072 } 14073 14074 struct XLinearGradient { 14075 XPointFixed p1; 14076 XPointFixed p2; 14077 } 14078 14079 struct XRadialGradient { 14080 XCircle inner; 14081 XCircle outer; 14082 } 14083 14084 struct XConicalGradient { 14085 XPointFixed center; 14086 XFixed angle; /* in degrees */ 14087 } 14088 14089 enum PictStandardARGB32 = 0; 14090 enum PictStandardRGB24 = 1; 14091 enum PictStandardA8 = 2; 14092 enum PictStandardA4 = 3; 14093 enum PictStandardA1 = 4; 14094 enum PictStandardNUM = 5; 14095 14096 interface XRender { 14097 extern(C) @nogc: 14098 14099 Bool XRenderQueryExtension (Display *dpy, int *event_basep, int *error_basep); 14100 14101 Status XRenderQueryVersion (Display *dpy, 14102 int *major_versionp, 14103 int *minor_versionp); 14104 14105 Status XRenderQueryFormats (Display *dpy); 14106 14107 int XRenderQuerySubpixelOrder (Display *dpy, int screen); 14108 14109 Bool XRenderSetSubpixelOrder (Display *dpy, int screen, int subpixel); 14110 14111 XRenderPictFormat * 14112 XRenderFindVisualFormat (Display *dpy, const Visual *visual); 14113 14114 XRenderPictFormat * 14115 XRenderFindFormat (Display *dpy, 14116 c_ulong mask, 14117 const XRenderPictFormat *templ, 14118 int count); 14119 XRenderPictFormat * 14120 XRenderFindStandardFormat (Display *dpy, 14121 int format); 14122 14123 XIndexValue * 14124 XRenderQueryPictIndexValues(Display *dpy, 14125 const XRenderPictFormat *format, 14126 int *num); 14127 14128 Picture XRenderCreatePicture( 14129 Display *dpy, 14130 Drawable drawable, 14131 const XRenderPictFormat *format, 14132 c_ulong valuemask, 14133 const XRenderPictureAttributes *attributes); 14134 14135 void XRenderChangePicture (Display *dpy, 14136 Picture picture, 14137 c_ulong valuemask, 14138 const XRenderPictureAttributes *attributes); 14139 14140 void 14141 XRenderSetPictureClipRectangles (Display *dpy, 14142 Picture picture, 14143 int xOrigin, 14144 int yOrigin, 14145 const XRectangle *rects, 14146 int n); 14147 14148 void 14149 XRenderSetPictureClipRegion (Display *dpy, 14150 Picture picture, 14151 Region r); 14152 14153 void 14154 XRenderSetPictureTransform (Display *dpy, 14155 Picture picture, 14156 XTransform *transform); 14157 14158 void 14159 XRenderFreePicture (Display *dpy, 14160 Picture picture); 14161 14162 void 14163 XRenderComposite (Display *dpy, 14164 int op, 14165 Picture src, 14166 Picture mask, 14167 Picture dst, 14168 int src_x, 14169 int src_y, 14170 int mask_x, 14171 int mask_y, 14172 int dst_x, 14173 int dst_y, 14174 uint width, 14175 uint height); 14176 14177 14178 Picture XRenderCreateSolidFill (Display *dpy, 14179 const XRenderColor *color); 14180 14181 Picture XRenderCreateLinearGradient (Display *dpy, 14182 const XLinearGradient *gradient, 14183 const XFixed *stops, 14184 const XRenderColor *colors, 14185 int nstops); 14186 14187 Picture XRenderCreateRadialGradient (Display *dpy, 14188 const XRadialGradient *gradient, 14189 const XFixed *stops, 14190 const XRenderColor *colors, 14191 int nstops); 14192 14193 Picture XRenderCreateConicalGradient (Display *dpy, 14194 const XConicalGradient *gradient, 14195 const XFixed *stops, 14196 const XRenderColor *colors, 14197 int nstops); 14198 14199 14200 14201 Cursor 14202 XRenderCreateCursor (Display *dpy, 14203 Picture source, 14204 uint x, 14205 uint y); 14206 14207 XFilters * 14208 XRenderQueryFilters (Display *dpy, Drawable drawable); 14209 14210 void 14211 XRenderSetPictureFilter (Display *dpy, 14212 Picture picture, 14213 const char *filter, 14214 XFixed *params, 14215 int nparams); 14216 14217 Cursor 14218 XRenderCreateAnimCursor (Display *dpy, 14219 int ncursor, 14220 XAnimCursor *cursors); 14221 } 14222 14223 __gshared bool XRenderLibrarySuccessfullyLoaded = true; 14224 mixin DynamicLoad!(XRender, "Xrender", 1, XRenderLibrarySuccessfullyLoaded) XRenderLibrary; 14225 14226 /* XRender } */ 14227 14228 /* Xrandr { */ 14229 14230 struct XRRMonitorInfo { 14231 Atom name; 14232 Bool primary; 14233 Bool automatic; 14234 int noutput; 14235 int x; 14236 int y; 14237 int width; 14238 int height; 14239 int mwidth; 14240 int mheight; 14241 /*RROutput*/ void *outputs; 14242 } 14243 14244 struct XRRScreenChangeNotifyEvent { 14245 int type; /* event base */ 14246 c_ulong serial; /* # of last request processed by server */ 14247 Bool send_event; /* true if this came from a SendEvent request */ 14248 Display *display; /* Display the event was read from */ 14249 Window window; /* window which selected for this event */ 14250 Window root; /* Root window for changed screen */ 14251 Time timestamp; /* when the screen change occurred */ 14252 Time config_timestamp; /* when the last configuration change */ 14253 ushort/*SizeID*/ size_index; 14254 ushort/*SubpixelOrder*/ subpixel_order; 14255 ushort/*Rotation*/ rotation; 14256 int width; 14257 int height; 14258 int mwidth; 14259 int mheight; 14260 } 14261 14262 enum RRScreenChangeNotify = 0; 14263 14264 enum RRScreenChangeNotifyMask = 1; 14265 14266 __gshared int xrrEventBase = -1; 14267 14268 14269 interface XRandr { 14270 extern(C) @nogc: 14271 Bool XRRQueryExtension (Display *dpy, int *event_base_return, int *error_base_return); 14272 Status XRRQueryVersion (Display *dpy, int *major_version_return, int *minor_version_return); 14273 14274 XRRMonitorInfo * XRRGetMonitors(Display *dpy, Window window, Bool get_active, int *nmonitors); 14275 void XRRFreeMonitors(XRRMonitorInfo *monitors); 14276 14277 void XRRSelectInput(Display *dpy, Window window, int mask); 14278 } 14279 14280 __gshared bool XRandrLibrarySuccessfullyLoaded = true; 14281 mixin DynamicLoad!(XRandr, "Xrandr", 2, XRandrLibrarySuccessfullyLoaded) XRandrLibrary; 14282 /* Xrandr } */ 14283 14284 /* Xft { */ 14285 14286 // actually freetype 14287 alias void FT_Face; 14288 14289 // actually fontconfig 14290 private alias FcBool = int; 14291 alias void FcCharSet; 14292 alias void FcPattern; 14293 alias void FcResult; 14294 enum FcEndian { FcEndianBig, FcEndianLittle } 14295 struct FcFontSet { 14296 int nfont; 14297 int sfont; 14298 FcPattern** fonts; 14299 } 14300 14301 // actually XRegion 14302 struct BOX { 14303 short x1, x2, y1, y2; 14304 } 14305 struct _XRegion { 14306 c_long size; 14307 c_long numRects; 14308 BOX* rects; 14309 BOX extents; 14310 } 14311 14312 alias Region = _XRegion*; 14313 14314 // ok actually Xft 14315 14316 struct XftFontInfo; 14317 14318 struct XftFont { 14319 int ascent; 14320 int descent; 14321 int height; 14322 int max_advance_width; 14323 FcCharSet* charset; 14324 FcPattern* pattern; 14325 } 14326 14327 struct XftDraw; 14328 14329 struct XftColor { 14330 c_ulong pixel; 14331 XRenderColor color; 14332 } 14333 14334 struct XftCharSpec { 14335 dchar ucs4; 14336 short x; 14337 short y; 14338 } 14339 14340 struct XftCharFontSpec { 14341 XftFont *font; 14342 dchar ucs4; 14343 short x; 14344 short y; 14345 } 14346 14347 struct XftGlyphSpec { 14348 uint glyph; 14349 short x; 14350 short y; 14351 } 14352 14353 struct XftGlyphFontSpec { 14354 XftFont *font; 14355 uint glyph; 14356 short x; 14357 short y; 14358 } 14359 14360 interface Xft { 14361 extern(C) @nogc pure: 14362 14363 Bool XftColorAllocName (Display *dpy, 14364 const Visual *visual, 14365 Colormap cmap, 14366 const char *name, 14367 XftColor *result); 14368 14369 Bool XftColorAllocValue (Display *dpy, 14370 Visual *visual, 14371 Colormap cmap, 14372 const XRenderColor *color, 14373 XftColor *result); 14374 14375 void XftColorFree (Display *dpy, 14376 Visual *visual, 14377 Colormap cmap, 14378 XftColor *color); 14379 14380 Bool XftDefaultHasRender (Display *dpy); 14381 14382 Bool XftDefaultSet (Display *dpy, FcPattern *defaults); 14383 14384 void XftDefaultSubstitute (Display *dpy, int screen, FcPattern *pattern); 14385 14386 XftDraw * XftDrawCreate (Display *dpy, 14387 Drawable drawable, 14388 Visual *visual, 14389 Colormap colormap); 14390 14391 XftDraw * XftDrawCreateBitmap (Display *dpy, 14392 Pixmap bitmap); 14393 14394 XftDraw * XftDrawCreateAlpha (Display *dpy, 14395 Pixmap pixmap, 14396 int depth); 14397 14398 void XftDrawChange (XftDraw *draw, 14399 Drawable drawable); 14400 14401 Display * XftDrawDisplay (XftDraw *draw); 14402 14403 Drawable XftDrawDrawable (XftDraw *draw); 14404 14405 Colormap XftDrawColormap (XftDraw *draw); 14406 14407 Visual * XftDrawVisual (XftDraw *draw); 14408 14409 void XftDrawDestroy (XftDraw *draw); 14410 14411 Picture XftDrawPicture (XftDraw *draw); 14412 14413 Picture XftDrawSrcPicture (XftDraw *draw, const XftColor *color); 14414 14415 void XftDrawGlyphs (XftDraw *draw, 14416 const XftColor *color, 14417 XftFont *pub, 14418 int x, 14419 int y, 14420 const uint *glyphs, 14421 int nglyphs); 14422 14423 void XftDrawString8 (XftDraw *draw, 14424 const XftColor *color, 14425 XftFont *pub, 14426 int x, 14427 int y, 14428 const char *string, 14429 int len); 14430 14431 void XftDrawString16 (XftDraw *draw, 14432 const XftColor *color, 14433 XftFont *pub, 14434 int x, 14435 int y, 14436 const wchar *string, 14437 int len); 14438 14439 void XftDrawString32 (XftDraw *draw, 14440 const XftColor *color, 14441 XftFont *pub, 14442 int x, 14443 int y, 14444 const dchar *string, 14445 int len); 14446 14447 void XftDrawStringUtf8 (XftDraw *draw, 14448 const XftColor *color, 14449 XftFont *pub, 14450 int x, 14451 int y, 14452 const char *string, 14453 int len); 14454 void XftDrawStringUtf16 (XftDraw *draw, 14455 const XftColor *color, 14456 XftFont *pub, 14457 int x, 14458 int y, 14459 const char *string, 14460 FcEndian endian, 14461 int len); 14462 14463 void XftDrawCharSpec (XftDraw *draw, 14464 const XftColor *color, 14465 XftFont *pub, 14466 const XftCharSpec *chars, 14467 int len); 14468 14469 void XftDrawCharFontSpec (XftDraw *draw, 14470 const XftColor *color, 14471 const XftCharFontSpec *chars, 14472 int len); 14473 14474 void XftDrawGlyphSpec (XftDraw *draw, 14475 const XftColor *color, 14476 XftFont *pub, 14477 const XftGlyphSpec *glyphs, 14478 int len); 14479 14480 void XftDrawGlyphFontSpec (XftDraw *draw, 14481 const XftColor *color, 14482 const XftGlyphFontSpec *glyphs, 14483 int len); 14484 14485 void XftDrawRect (XftDraw *draw, 14486 const XftColor *color, 14487 int x, 14488 int y, 14489 uint width, 14490 uint height); 14491 14492 Bool XftDrawSetClip (XftDraw *draw, 14493 Region r); 14494 14495 14496 Bool XftDrawSetClipRectangles (XftDraw *draw, 14497 int xOrigin, 14498 int yOrigin, 14499 const XRectangle *rects, 14500 int n); 14501 14502 void XftDrawSetSubwindowMode (XftDraw *draw, 14503 int mode); 14504 14505 void XftGlyphExtents (Display *dpy, 14506 XftFont *pub, 14507 const uint *glyphs, 14508 int nglyphs, 14509 XGlyphInfo *extents); 14510 14511 void XftTextExtents8 (Display *dpy, 14512 XftFont *pub, 14513 const char *string, 14514 int len, 14515 XGlyphInfo *extents); 14516 14517 void XftTextExtents16 (Display *dpy, 14518 XftFont *pub, 14519 const wchar *string, 14520 int len, 14521 XGlyphInfo *extents); 14522 14523 void XftTextExtents32 (Display *dpy, 14524 XftFont *pub, 14525 const dchar *string, 14526 int len, 14527 XGlyphInfo *extents); 14528 14529 void XftTextExtentsUtf8 (Display *dpy, 14530 XftFont *pub, 14531 const char *string, 14532 int len, 14533 XGlyphInfo *extents); 14534 14535 void XftTextExtentsUtf16 (Display *dpy, 14536 XftFont *pub, 14537 const char *string, 14538 FcEndian endian, 14539 int len, 14540 XGlyphInfo *extents); 14541 14542 FcPattern * XftFontMatch (Display *dpy, 14543 int screen, 14544 const FcPattern *pattern, 14545 FcResult *result); 14546 14547 XftFont * XftFontOpen (Display *dpy, int screen, ...); 14548 14549 XftFont * XftFontOpenName (Display *dpy, int screen, const char *name); 14550 14551 XftFont * XftFontOpenXlfd (Display *dpy, int screen, const char *xlfd); 14552 14553 FT_Face XftLockFace (XftFont *pub); 14554 14555 void XftUnlockFace (XftFont *pub); 14556 14557 XftFontInfo * XftFontInfoCreate (Display *dpy, const FcPattern *pattern); 14558 14559 void XftFontInfoDestroy (Display *dpy, XftFontInfo *fi); 14560 14561 dchar XftFontInfoHash (const XftFontInfo *fi); 14562 14563 FcBool XftFontInfoEqual (const XftFontInfo *a, const XftFontInfo *b); 14564 14565 XftFont * XftFontOpenInfo (Display *dpy, 14566 FcPattern *pattern, 14567 XftFontInfo *fi); 14568 14569 XftFont * XftFontOpenPattern (Display *dpy, FcPattern *pattern); 14570 14571 XftFont * XftFontCopy (Display *dpy, XftFont *pub); 14572 14573 void XftFontClose (Display *dpy, XftFont *pub); 14574 14575 FcBool XftInitFtLibrary(); 14576 void XftFontLoadGlyphs (Display *dpy, 14577 XftFont *pub, 14578 FcBool need_bitmaps, 14579 const uint *glyphs, 14580 int nglyph); 14581 14582 void XftFontUnloadGlyphs (Display *dpy, 14583 XftFont *pub, 14584 const uint *glyphs, 14585 int nglyph); 14586 14587 FcBool XftFontCheckGlyph (Display *dpy, 14588 XftFont *pub, 14589 FcBool need_bitmaps, 14590 uint glyph, 14591 uint *missing, 14592 int *nmissing); 14593 14594 FcBool XftCharExists (Display *dpy, 14595 XftFont *pub, 14596 dchar ucs4); 14597 14598 uint XftCharIndex (Display *dpy, 14599 XftFont *pub, 14600 dchar ucs4); 14601 FcBool XftInit (const char *config); 14602 14603 int XftGetVersion (); 14604 14605 FcFontSet * XftListFonts (Display *dpy, 14606 int screen, 14607 ...); 14608 14609 FcPattern *XftNameParse (const char *name); 14610 14611 void XftGlyphRender (Display *dpy, 14612 int op, 14613 Picture src, 14614 XftFont *pub, 14615 Picture dst, 14616 int srcx, 14617 int srcy, 14618 int x, 14619 int y, 14620 const uint *glyphs, 14621 int nglyphs); 14622 14623 void XftGlyphSpecRender (Display *dpy, 14624 int op, 14625 Picture src, 14626 XftFont *pub, 14627 Picture dst, 14628 int srcx, 14629 int srcy, 14630 const XftGlyphSpec *glyphs, 14631 int nglyphs); 14632 14633 void XftCharSpecRender (Display *dpy, 14634 int op, 14635 Picture src, 14636 XftFont *pub, 14637 Picture dst, 14638 int srcx, 14639 int srcy, 14640 const XftCharSpec *chars, 14641 int len); 14642 void XftGlyphFontSpecRender (Display *dpy, 14643 int op, 14644 Picture src, 14645 Picture dst, 14646 int srcx, 14647 int srcy, 14648 const XftGlyphFontSpec *glyphs, 14649 int nglyphs); 14650 14651 void XftCharFontSpecRender (Display *dpy, 14652 int op, 14653 Picture src, 14654 Picture dst, 14655 int srcx, 14656 int srcy, 14657 const XftCharFontSpec *chars, 14658 int len); 14659 14660 void XftTextRender8 (Display *dpy, 14661 int op, 14662 Picture src, 14663 XftFont *pub, 14664 Picture dst, 14665 int srcx, 14666 int srcy, 14667 int x, 14668 int y, 14669 const char *string, 14670 int len); 14671 void XftTextRender16 (Display *dpy, 14672 int op, 14673 Picture src, 14674 XftFont *pub, 14675 Picture dst, 14676 int srcx, 14677 int srcy, 14678 int x, 14679 int y, 14680 const wchar *string, 14681 int len); 14682 14683 void XftTextRender16BE (Display *dpy, 14684 int op, 14685 Picture src, 14686 XftFont *pub, 14687 Picture dst, 14688 int srcx, 14689 int srcy, 14690 int x, 14691 int y, 14692 const char *string, 14693 int len); 14694 14695 void XftTextRender16LE (Display *dpy, 14696 int op, 14697 Picture src, 14698 XftFont *pub, 14699 Picture dst, 14700 int srcx, 14701 int srcy, 14702 int x, 14703 int y, 14704 const char *string, 14705 int len); 14706 14707 void XftTextRender32 (Display *dpy, 14708 int op, 14709 Picture src, 14710 XftFont *pub, 14711 Picture dst, 14712 int srcx, 14713 int srcy, 14714 int x, 14715 int y, 14716 const dchar *string, 14717 int len); 14718 14719 void XftTextRender32BE (Display *dpy, 14720 int op, 14721 Picture src, 14722 XftFont *pub, 14723 Picture dst, 14724 int srcx, 14725 int srcy, 14726 int x, 14727 int y, 14728 const char *string, 14729 int len); 14730 14731 void XftTextRender32LE (Display *dpy, 14732 int op, 14733 Picture src, 14734 XftFont *pub, 14735 Picture dst, 14736 int srcx, 14737 int srcy, 14738 int x, 14739 int y, 14740 const char *string, 14741 int len); 14742 14743 void XftTextRenderUtf8 (Display *dpy, 14744 int op, 14745 Picture src, 14746 XftFont *pub, 14747 Picture dst, 14748 int srcx, 14749 int srcy, 14750 int x, 14751 int y, 14752 const char *string, 14753 int len); 14754 14755 void XftTextRenderUtf16 (Display *dpy, 14756 int op, 14757 Picture src, 14758 XftFont *pub, 14759 Picture dst, 14760 int srcx, 14761 int srcy, 14762 int x, 14763 int y, 14764 const char *string, 14765 FcEndian endian, 14766 int len); 14767 FcPattern * XftXlfdParse (const char *xlfd_orig, Bool ignore_scalable, Bool complete); 14768 14769 } 14770 14771 interface FontConfig { 14772 extern(C) @nogc pure: 14773 int FcPatternGetString(const FcPattern *p, const char *object, int n, char ** s); 14774 void FcFontSetDestroy(FcFontSet*); 14775 char* FcNameUnparse(const FcPattern *); 14776 } 14777 14778 mixin DynamicLoad!(Xft, "Xft", 2, librariesSuccessfullyLoaded) XftLibrary; 14779 mixin DynamicLoad!(FontConfig, "fontconfig", 1, librariesSuccessfullyLoaded) FontConfigLibrary; 14780 14781 14782 /* Xft } */ 14783 14784 class XDisconnectException : Exception { 14785 bool userRequested; 14786 this(bool userRequested = true) { 14787 this.userRequested = userRequested; 14788 super("X disconnected"); 14789 } 14790 } 14791 14792 /++ 14793 Platform-specific for X11. Traps errors for the duration of `dg`. Avoid calling this from inside a call to this. 14794 14795 Please note that it returns 14796 +/ 14797 XErrorEvent[] trapXErrors(scope void delegate() dg) { 14798 14799 static XErrorEvent[] errorBuffer; 14800 14801 static extern(C) int handler (Display* dpy, XErrorEvent* evt) nothrow { 14802 errorBuffer ~= *evt; 14803 return 0; 14804 } 14805 14806 auto savedErrorHandler = XSetErrorHandler(&handler); 14807 14808 try { 14809 dg(); 14810 } finally { 14811 XSync(XDisplayConnection.get, 0/*False*/); 14812 XSetErrorHandler(savedErrorHandler); 14813 } 14814 14815 auto bfr = errorBuffer; 14816 errorBuffer = null; 14817 14818 return bfr; 14819 } 14820 14821 /// Platform-specific for X11. A singleton class (well, all its methods are actually static... so more like a namespace) wrapping a `Display*`. 14822 class XDisplayConnection { 14823 private __gshared Display* display; 14824 private __gshared XIM xim; 14825 private __gshared char* displayName; 14826 14827 private __gshared int connectionSequence_; 14828 private __gshared bool isLocal_; 14829 14830 /// use this for lazy caching when reconnection 14831 static int connectionSequenceNumber() { return connectionSequence_; } 14832 14833 /++ 14834 Guesses if the connection appears to be local. 14835 14836 History: 14837 Added June 3, 2021 14838 +/ 14839 static @property bool isLocal() nothrow @trusted @nogc { 14840 return isLocal_; 14841 } 14842 14843 /// Attempts recreation of state, may require application assistance 14844 /// You MUST call this OUTSIDE the event loop. Let the exception kill the loop, 14845 /// then call this, and if successful, reenter the loop. 14846 static void discardAndRecreate(string newDisplayString = null) { 14847 if(insideXEventLoop) 14848 throw new Error("You MUST call discardAndRecreate from OUTSIDE the event loop"); 14849 14850 // 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 14851 auto chnenhm = CapableOfHandlingNativeEvent.nativeHandleMapping.dup; 14852 14853 foreach(handle; chnenhm) { 14854 handle.discardConnectionState(); 14855 } 14856 14857 discardState(); 14858 14859 if(newDisplayString !is null) 14860 setDisplayName(newDisplayString); 14861 14862 auto display = get(); 14863 14864 foreach(handle; chnenhm) { 14865 handle.recreateAfterDisconnect(); 14866 } 14867 } 14868 14869 private __gshared EventMask rootEventMask; 14870 14871 /++ 14872 Requests the specified input from the root window on the connection, in addition to any other request. 14873 14874 14875 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. 14876 14877 $(WARNING it calls XSelectInput itself, which will override any other root window input you have!) 14878 +/ 14879 static void addRootInput(EventMask mask) { 14880 auto old = rootEventMask; 14881 rootEventMask |= mask; 14882 get(); // to ensure display connected 14883 if(display !is null && rootEventMask != old) 14884 XSelectInput(display, RootWindow(display, DefaultScreen(display)), rootEventMask); 14885 } 14886 14887 static void discardState() { 14888 freeImages(); 14889 14890 foreach(atomPtr; interredAtoms) 14891 *atomPtr = 0; 14892 interredAtoms = null; 14893 interredAtoms.assumeSafeAppend(); 14894 14895 ScreenPainterImplementation.fontAttempted = false; 14896 ScreenPainterImplementation.defaultfont = null; 14897 ScreenPainterImplementation.defaultfontset = null; 14898 14899 Image.impl.xshmQueryCompleted = false; 14900 Image.impl._xshmAvailable = false; 14901 14902 SimpleWindow.nativeMapping = null; 14903 CapableOfHandlingNativeEvent.nativeHandleMapping = null; 14904 // GlobalHotkeyManager 14905 14906 display = null; 14907 xim = null; 14908 } 14909 14910 // Do you want to know why do we need all this horrible-looking code? See comment at the bottom. 14911 private static void createXIM () { 14912 import core.stdc.locale : setlocale, LC_ALL; 14913 import core.stdc.stdio : stderr, fprintf; 14914 import core.stdc.stdlib : free; 14915 import core.stdc.string : strdup; 14916 14917 static immutable string[3] mtry = [ "", "@im=local", "@im=" ]; 14918 14919 auto olocale = strdup(setlocale(LC_ALL, null)); 14920 setlocale(LC_ALL, (sdx_isUTF8Locale ? "" : "en_US.UTF-8")); 14921 scope(exit) { setlocale(LC_ALL, olocale); free(olocale); } 14922 14923 //fprintf(stderr, "opening IM...\n"); 14924 foreach (string s; mtry) { 14925 XSetLocaleModifiers(s.ptr); // it's safe, as `s` is string literal 14926 if ((xim = XOpenIM(display, null, null, null)) !is null) return; 14927 } 14928 fprintf(stderr, "createXIM: XOpenIM failed!\n"); 14929 } 14930 14931 // for X11 we will keep all XShm-allocated images in this list, so we can free 'em on connection closing. 14932 // we'll use glibc malloc()/free(), 'cause `unregisterImage()` can be called from object dtor. 14933 static struct ImgList { 14934 size_t img; // class; hide it from GC 14935 ImgList* next; 14936 } 14937 14938 static __gshared ImgList* imglist = null; 14939 static __gshared bool imglistLocked = false; // true: don't register and unregister images 14940 14941 static void registerImage (Image img) { 14942 if (!imglistLocked && img !is null) { 14943 import core.stdc.stdlib : malloc; 14944 auto it = cast(ImgList*)malloc(ImgList.sizeof); 14945 assert(it !is null); // do proper checks 14946 it.img = cast(size_t)cast(void*)img; 14947 it.next = imglist; 14948 imglist = it; 14949 version(sdpy_debug_xshm) { import core.stdc.stdio : printf; printf("registering image %p\n", cast(void*)img); } 14950 } 14951 } 14952 14953 static void unregisterImage (Image img) { 14954 if (!imglistLocked && img !is null) { 14955 import core.stdc.stdlib : free; 14956 ImgList* prev = null; 14957 ImgList* cur = imglist; 14958 while (cur !is null) { 14959 if (cur.img == cast(size_t)cast(void*)img) break; // i found her! 14960 prev = cur; 14961 cur = cur.next; 14962 } 14963 if (cur !is null) { 14964 if (prev is null) imglist = cur.next; else prev.next = cur.next; 14965 free(cur); 14966 version(sdpy_debug_xshm) { import core.stdc.stdio : printf; printf("unregistering image %p\n", cast(void*)img); } 14967 } else { 14968 version(sdpy_debug_xshm) { import core.stdc.stdio : printf; printf("trying to unregister unknown image %p\n", cast(void*)img); } 14969 } 14970 } 14971 } 14972 14973 static void freeImages () { // needed for discardAndRecreate 14974 imglistLocked = true; 14975 scope(exit) imglistLocked = false; 14976 ImgList* cur = imglist; 14977 ImgList* next = null; 14978 while (cur !is null) { 14979 import core.stdc.stdlib : free; 14980 next = cur.next; 14981 version(sdpy_debug_xshm) { import core.stdc.stdio : printf; printf("disposing image %p\n", cast(void*)cur.img); } 14982 (cast(Image)cast(void*)cur.img).dispose(); 14983 free(cur); 14984 cur = next; 14985 } 14986 imglist = null; 14987 } 14988 14989 /// can be used to override normal handling of display name 14990 /// from environment and/or command line 14991 static setDisplayName(string newDisplayName) { 14992 displayName = cast(char*) (newDisplayName ~ '\0'); 14993 } 14994 14995 /// resets to the default display string 14996 static resetDisplayName() { 14997 displayName = null; 14998 } 14999 15000 /// 15001 static Display* get() { 15002 if(display is null) { 15003 if(!librariesSuccessfullyLoaded) 15004 throw new Exception("Unable to load X11 client libraries"); 15005 display = XOpenDisplay(displayName); 15006 15007 isLocal_ = false; 15008 15009 connectionSequence_++; 15010 if(display is null) 15011 throw new Exception("Unable to open X display"); 15012 15013 auto str = display.display_name; 15014 // this is a bit of a hack but like if it looks like a unix socket we assume it is local 15015 // and otherwise it probably isn't 15016 if(str is null || (str[0] != ':' && str[0] != '/')) 15017 isLocal_ = false; 15018 else 15019 isLocal_ = true; 15020 15021 XSetErrorHandler(&adrlogger); 15022 15023 debug(sdpy_x_errors) { 15024 XSynchronize(display, true); 15025 15026 extern(C) int wtf() { 15027 if(errorHappened) { 15028 asm { int 3; } 15029 errorHappened = false; 15030 } 15031 return 0; 15032 } 15033 XSetAfterFunction(display, &wtf); 15034 } 15035 15036 15037 XSetIOErrorHandler(&x11ioerrCB); 15038 Bool sup; 15039 XkbSetDetectableAutoRepeat(display, 1, &sup); // so we will not receive KeyRelease until key is really released 15040 createXIM(); 15041 version(with_eventloop) { 15042 import arsd.eventloop; 15043 addFileEventListeners(display.fd, &eventListener, null, null); 15044 } 15045 } 15046 15047 return display; 15048 } 15049 15050 extern(C) 15051 static int x11ioerrCB(Display* dpy) { 15052 throw new XDisconnectException(false); 15053 } 15054 15055 version(with_eventloop) { 15056 import arsd.eventloop; 15057 static void eventListener(OsFileHandle fd) { 15058 //this.mtLock(); 15059 //scope(exit) this.mtUnlock(); 15060 while(XPending(display)) 15061 doXNextEvent(display); 15062 } 15063 } 15064 15065 // close connection on program exit -- we need this to properly free all images 15066 static ~this () { 15067 // the gui thread must clean up after itself or else Xlib might deadlock 15068 // using this flag on any thread destruction is the easiest way i know of 15069 // (shared static this is run by the LAST thread to exit, which may not be 15070 // the gui thread, and normal static this run by ALL threads, so we gotta check.) 15071 if(thisIsGuiThread) 15072 close(); 15073 } 15074 15075 /// 15076 static void close() { 15077 if(display is null) 15078 return; 15079 15080 version(with_eventloop) { 15081 import arsd.eventloop; 15082 removeFileEventListeners(display.fd); 15083 } 15084 15085 // now remove all registered images to prevent shared memory leaks 15086 freeImages(); 15087 15088 // tbh I don't know why it is doing this but like if this happens to run 15089 // from the other thread there's frequent hanging inside here. 15090 if(thisIsGuiThread) 15091 XCloseDisplay(display); 15092 display = null; 15093 } 15094 } 15095 15096 mixin template NativeImageImplementation() { 15097 XImage* handle; 15098 ubyte* rawData; 15099 15100 XShmSegmentInfo shminfo; 15101 bool premultiply = true; 15102 15103 __gshared bool xshmQueryCompleted; 15104 __gshared bool _xshmAvailable; 15105 public static @property bool xshmAvailable() { 15106 if(!xshmQueryCompleted) { 15107 int i1, i2, i3; 15108 xshmQueryCompleted = true; 15109 15110 if(!XDisplayConnection.isLocal) 15111 _xshmAvailable = false; 15112 else 15113 _xshmAvailable = XQueryExtension(XDisplayConnection.get(), "MIT-SHM", &i1, &i2, &i3) != 0; 15114 } 15115 return _xshmAvailable; 15116 } 15117 15118 bool usingXshm; 15119 final: 15120 15121 private __gshared bool xshmfailed; 15122 15123 void createImage(int width, int height, bool forcexshm=false, bool enableAlpha = false) { 15124 auto display = XDisplayConnection.get(); 15125 assert(display !is null); 15126 auto screen = DefaultScreen(display); 15127 15128 // it will only use shared memory for somewhat largish images, 15129 // since otherwise we risk wasting shared memory handles on a lot of little ones 15130 if (xshmAvailable && (forcexshm || (width > 100 && height > 100))) { 15131 15132 15133 // it is possible for the query extension to return true, the DISPLAY check to pass, yet 15134 // the actual use still fails. For example, if the program is in a container and permission denied 15135 // on shared memory, or if it is a local thing forwarded to a remote server, etc. 15136 // 15137 // If it does fail, we need to detect it now, abort the xshm and fall back to core protocol. 15138 15139 15140 // synchronize so preexisting buffers are clear 15141 XSync(display, false); 15142 xshmfailed = false; 15143 15144 auto oldErrorHandler = XSetErrorHandler(&XShmErrorHandler); 15145 15146 15147 usingXshm = true; 15148 handle = XShmCreateImage( 15149 display, 15150 DefaultVisual(display, screen), 15151 enableAlpha ? 32: 24, 15152 ImageFormat.ZPixmap, 15153 null, 15154 &shminfo, 15155 width, height); 15156 if(handle is null) 15157 goto abortXshm1; 15158 15159 if(handle.bytes_per_line != 4 * width) 15160 goto abortXshm2; 15161 15162 shminfo.shmid = shmget(IPC_PRIVATE, handle.bytes_per_line * height, IPC_CREAT | 511 /* 0777 */); 15163 if(shminfo.shmid < 0) 15164 goto abortXshm3; 15165 handle.data = shminfo.shmaddr = rawData = cast(ubyte*) shmat(shminfo.shmid, null, 0); 15166 if(rawData == cast(ubyte*) -1) 15167 goto abortXshm4; 15168 shminfo.readOnly = 0; 15169 XShmAttach(display, &shminfo); 15170 15171 // and now to the final error check to ensure it actually worked. 15172 XSync(display, false); 15173 if(xshmfailed) 15174 goto abortXshm5; 15175 15176 XSetErrorHandler(oldErrorHandler); 15177 15178 XDisplayConnection.registerImage(this); 15179 // if I don't flush here there's a chance the dtor will run before the 15180 // ctor and lead to a bad value X error. While this hurts the efficiency 15181 // it is local anyway so prolly better to keep it simple 15182 XFlush(display); 15183 15184 return; 15185 15186 abortXshm5: 15187 shmdt(shminfo.shmaddr); 15188 rawData = null; 15189 15190 abortXshm4: 15191 shmctl(shminfo.shmid, IPC_RMID, null); 15192 15193 abortXshm3: 15194 // nothing needed, the shmget failed so there's nothing to free 15195 15196 abortXshm2: 15197 XDestroyImage(handle); 15198 handle = null; 15199 15200 abortXshm1: 15201 XSetErrorHandler(oldErrorHandler); 15202 usingXshm = false; 15203 handle = null; 15204 15205 shminfo = typeof(shminfo).init; 15206 15207 _xshmAvailable = false; // don't try again in the future 15208 15209 // writeln("fallingback"); 15210 15211 goto fallback; 15212 15213 } else { 15214 fallback: 15215 15216 if (forcexshm) throw new Exception("can't create XShm Image"); 15217 // This actually needs to be malloc to avoid a double free error when XDestroyImage is called 15218 import core.stdc.stdlib : malloc; 15219 rawData = cast(ubyte*) malloc(width * height * 4); 15220 15221 handle = XCreateImage( 15222 display, 15223 DefaultVisual(display, screen), 15224 enableAlpha ? 32 : 24, // bpp 15225 ImageFormat.ZPixmap, 15226 0, // offset 15227 rawData, 15228 width, height, 15229 enableAlpha ? 32 : 8 /* FIXME */, 4 * width); // padding, bytes per line 15230 } 15231 } 15232 15233 void dispose() { 15234 // note: this calls free(rawData) for us 15235 if(handle) { 15236 if (usingXshm) { 15237 XDisplayConnection.unregisterImage(this); 15238 if (XDisplayConnection.get()) XShmDetach(XDisplayConnection.get(), &shminfo); 15239 } 15240 XDestroyImage(handle); 15241 if(usingXshm) { 15242 shmdt(shminfo.shmaddr); 15243 shmctl(shminfo.shmid, IPC_RMID, null); 15244 } 15245 handle = null; 15246 } 15247 } 15248 15249 Color getPixel(int x, int y) @system { 15250 auto offset = (y * width + x) * 4; 15251 Color c; 15252 c.a = enableAlpha ? rawData[offset + 3] : 255; 15253 c.b = rawData[offset + 0]; 15254 c.g = rawData[offset + 1]; 15255 c.r = rawData[offset + 2]; 15256 if(enableAlpha && premultiply) 15257 c.unPremultiply; 15258 return c; 15259 } 15260 15261 void setPixel(int x, int y, Color c) @system { 15262 if(enableAlpha && premultiply) 15263 c.premultiply(); 15264 auto offset = (y * width + x) * 4; 15265 rawData[offset + 0] = c.b; 15266 rawData[offset + 1] = c.g; 15267 rawData[offset + 2] = c.r; 15268 if(enableAlpha) 15269 rawData[offset + 3] = c.a; 15270 } 15271 15272 void convertToRgbaBytes(ubyte[] where) @system { 15273 assert(where.length == this.width * this.height * 4); 15274 15275 // if rawData had a length.... 15276 //assert(rawData.length == where.length); 15277 for(int idx = 0; idx < where.length; idx += 4) { 15278 where[idx + 0] = rawData[idx + 2]; // r 15279 where[idx + 1] = rawData[idx + 1]; // g 15280 where[idx + 2] = rawData[idx + 0]; // b 15281 where[idx + 3] = enableAlpha ? rawData[idx + 3] : 255; // a 15282 15283 if(enableAlpha && premultiply) 15284 unPremultiplyRgba(where[idx .. idx + 4]); 15285 } 15286 } 15287 15288 void setFromRgbaBytes(in ubyte[] where) @system { 15289 assert(where.length == this.width * this.height * 4); 15290 15291 // if rawData had a length.... 15292 //assert(rawData.length == where.length); 15293 for(int idx = 0; idx < where.length; idx += 4) { 15294 rawData[idx + 2] = where[idx + 0]; // r 15295 rawData[idx + 1] = where[idx + 1]; // g 15296 rawData[idx + 0] = where[idx + 2]; // b 15297 if(enableAlpha) { 15298 rawData[idx + 3] = where[idx + 3]; // a 15299 if(premultiply) 15300 premultiplyBgra(rawData[idx .. idx + 4]); 15301 } 15302 } 15303 } 15304 15305 } 15306 15307 mixin template NativeSimpleWindowImplementation() { 15308 GC gc; 15309 Window window; 15310 Display* display; 15311 15312 Pixmap buffer; 15313 int bufferw, bufferh; // size of the buffer; can be bigger than window 15314 XIC xic; // input context 15315 int curHidden = 0; // counter 15316 Cursor blankCurPtr = 0; 15317 int cursorSequenceNumber = 0; 15318 int warpEventCount = 0; // number of mouse movement events to eat 15319 15320 __gshared X11SetSelectionHandler[Atom] setSelectionHandlers; // FIXME: make sure this is not accessed from other threads. it might be ok to make it TLS 15321 X11GetSelectionHandler[Atom] getSelectionHandlers; 15322 15323 version(without_opengl) {} else 15324 GLXContext glc; 15325 15326 private void fixFixedSize(bool forced=false) (int width, int height) { 15327 if (forced || this.resizability == Resizability.fixedSize) { 15328 //{ import core.stdc.stdio; printf("fixing size to: %dx%d\n", width, height); } 15329 XSizeHints sh; 15330 static if (!forced) { 15331 c_long spr; 15332 XGetWMNormalHints(display, window, &sh, &spr); 15333 sh.flags |= PMaxSize | PMinSize; 15334 } else { 15335 sh.flags = PMaxSize | PMinSize; 15336 } 15337 sh.min_width = width; 15338 sh.min_height = height; 15339 sh.max_width = width; 15340 sh.max_height = height; 15341 XSetWMNormalHints(display, window, &sh); 15342 //XFlush(display); 15343 } 15344 } 15345 15346 ScreenPainter getPainter(bool manualInvalidations) { 15347 return ScreenPainter(this, window, manualInvalidations); 15348 } 15349 15350 void move(int x, int y) { 15351 XMoveWindow(display, window, x, y); 15352 } 15353 15354 void resize(int w, int h) { 15355 if (w < 1) w = 1; 15356 if (h < 1) h = 1; 15357 XResizeWindow(display, window, w, h); 15358 15359 // calling this now to avoid waiting for the server to 15360 // acknowledge the resize; draws without returning to the 15361 // event loop will thus actually work. the server's event 15362 // btw might overrule this and resize it again 15363 recordX11Resize(display, this, w, h); 15364 15365 updateOpenglViewportIfNeeded(w, h); 15366 } 15367 15368 void moveResize (int x, int y, int w, int h) { 15369 if (w < 1) w = 1; 15370 if (h < 1) h = 1; 15371 XMoveResizeWindow(display, window, x, y, w, h); 15372 updateOpenglViewportIfNeeded(w, h); 15373 } 15374 15375 void hideCursor () { 15376 if (curHidden++ == 0) { 15377 if (!blankCurPtr || cursorSequenceNumber != XDisplayConnection.connectionSequenceNumber) { 15378 static const(char)[1] cmbmp = 0; 15379 XColor blackcolor = { 0, 0, 0, 0, 0, 0 }; 15380 Pixmap pm = XCreateBitmapFromData(display, window, cmbmp.ptr, 1, 1); 15381 blankCurPtr = XCreatePixmapCursor(display, pm, pm, &blackcolor, &blackcolor, 0, 0); 15382 cursorSequenceNumber = XDisplayConnection.connectionSequenceNumber; 15383 XFreePixmap(display, pm); 15384 } 15385 XDefineCursor(display, window, blankCurPtr); 15386 } 15387 } 15388 15389 void showCursor () { 15390 if (--curHidden == 0) XUndefineCursor(display, window); 15391 } 15392 15393 void warpMouse (int x, int y) { 15394 // here i will send dummy "ignore next mouse motion" event, 15395 // 'cause `XWarpPointer()` sends synthesised mouse motion, 15396 // and we don't need to report it to the user (as warping is 15397 // used when the user needs movement deltas). 15398 //XClientMessageEvent xclient; 15399 XEvent e; 15400 e.xclient.type = EventType.ClientMessage; 15401 e.xclient.window = window; 15402 e.xclient.message_type = GetAtom!("_X11SDPY_INSMME_FLAG_EVENT_", true)(display); // let's hope nobody else will use such stupid name ;-) 15403 e.xclient.format = 32; 15404 e.xclient.data.l[0] = 0; 15405 debug(x11sdpy_warp_debug) { import core.stdc.stdio : printf; printf("X11: sending \"INSMME\"...\n"); } 15406 //{ 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]); } 15407 XSendEvent(display, window, false, EventMask.NoEventMask, /*cast(XEvent*)&xclient*/&e); 15408 // now warp pointer... 15409 debug(x11sdpy_warp_debug) { import core.stdc.stdio : printf; printf("X11: sending \"warp\"...\n"); } 15410 XWarpPointer(display, None, window, 0, 0, 0, 0, x, y); 15411 // ...and flush 15412 debug(x11sdpy_warp_debug) { import core.stdc.stdio : printf; printf("X11: flushing...\n"); } 15413 XFlush(display); 15414 } 15415 15416 void sendDummyEvent () { 15417 // here i will send dummy event to ping event queue 15418 XEvent e; 15419 e.xclient.type = EventType.ClientMessage; 15420 e.xclient.window = window; 15421 e.xclient.message_type = GetAtom!("_X11SDPY_DUMMY_EVENT_", true)(display); // let's hope nobody else will use such stupid name ;-) 15422 e.xclient.format = 32; 15423 e.xclient.data.l[0] = 0; 15424 XSendEvent(display, window, false, EventMask.NoEventMask, /*cast(XEvent*)&xclient*/&e); 15425 XFlush(display); 15426 } 15427 15428 void setTitle(string title) { 15429 if (title.ptr is null) title = ""; 15430 auto XA_UTF8 = XInternAtom(display, "UTF8_STRING".ptr, false); 15431 auto XA_NETWM_NAME = XInternAtom(display, "_NET_WM_NAME".ptr, false); 15432 XTextProperty windowName; 15433 windowName.value = title.ptr; 15434 windowName.encoding = XA_UTF8; //XA_STRING; 15435 windowName.format = 8; 15436 windowName.nitems = cast(uint)title.length; 15437 XSetWMName(display, window, &windowName); 15438 char[1024] namebuf = 0; 15439 auto maxlen = namebuf.length-1; 15440 if (maxlen > title.length) maxlen = title.length; 15441 namebuf[0..maxlen] = title[0..maxlen]; 15442 XStoreName(display, window, namebuf.ptr); 15443 XChangeProperty(display, window, XA_NETWM_NAME, XA_UTF8, 8, PropModeReplace, title.ptr, cast(uint)title.length); 15444 flushGui(); // without this OpenGL windows has a LONG delay before changing title 15445 } 15446 15447 string[] getTitles() { 15448 auto XA_UTF8 = XInternAtom(display, "UTF8_STRING".ptr, false); 15449 auto XA_NETWM_NAME = XInternAtom(display, "_NET_WM_NAME".ptr, false); 15450 XTextProperty textProp; 15451 if (XGetTextProperty(display, window, &textProp, XA_NETWM_NAME) != 0 || XGetWMName(display, window, &textProp) != 0) { 15452 if ((textProp.encoding == XA_UTF8 || textProp.encoding == XA_STRING) && textProp.format == 8) { 15453 return textProp.value[0 .. textProp.nitems].idup.split('\0'); 15454 } else 15455 return []; 15456 } else 15457 return null; 15458 } 15459 15460 string getTitle() { 15461 auto titles = getTitles(); 15462 return titles.length ? titles[0] : null; 15463 } 15464 15465 void setMinSize (int minwidth, int minheight) { 15466 import core.stdc.config : c_long; 15467 if (minwidth < 1) minwidth = 1; 15468 if (minheight < 1) minheight = 1; 15469 XSizeHints sh; 15470 c_long spr; 15471 XGetWMNormalHints(display, window, &sh, &spr); 15472 sh.min_width = minwidth; 15473 sh.min_height = minheight; 15474 sh.flags |= PMinSize; 15475 XSetWMNormalHints(display, window, &sh); 15476 flushGui(); 15477 } 15478 15479 void setMaxSize (int maxwidth, int maxheight) { 15480 import core.stdc.config : c_long; 15481 if (maxwidth < 1) maxwidth = 1; 15482 if (maxheight < 1) maxheight = 1; 15483 XSizeHints sh; 15484 c_long spr; 15485 XGetWMNormalHints(display, window, &sh, &spr); 15486 sh.max_width = maxwidth; 15487 sh.max_height = maxheight; 15488 sh.flags |= PMaxSize; 15489 XSetWMNormalHints(display, window, &sh); 15490 flushGui(); 15491 } 15492 15493 void setResizeGranularity (int granx, int grany) { 15494 import core.stdc.config : c_long; 15495 if (granx < 1) granx = 1; 15496 if (grany < 1) grany = 1; 15497 XSizeHints sh; 15498 c_long spr; 15499 XGetWMNormalHints(display, window, &sh, &spr); 15500 sh.width_inc = granx; 15501 sh.height_inc = grany; 15502 sh.flags |= PResizeInc; 15503 XSetWMNormalHints(display, window, &sh); 15504 flushGui(); 15505 } 15506 15507 void setOpacity (uint opacity) { 15508 arch_ulong o = opacity; 15509 if (opacity == uint.max) 15510 XDeleteProperty(display, window, XInternAtom(display, "_NET_WM_WINDOW_OPACITY".ptr, false)); 15511 else 15512 XChangeProperty(display, window, XInternAtom(display, "_NET_WM_WINDOW_OPACITY".ptr, false), 15513 XA_CARDINAL, 32, PropModeReplace, &o, 1); 15514 } 15515 15516 void createWindow(int width, int height, string title, in OpenGlOptions opengl, SimpleWindow parent) @trusted { 15517 version(without_opengl) {} else if(opengl == OpenGlOptions.yes && !openGlLibrariesSuccessfullyLoaded) throw new Exception("OpenGL libraries did not load"); 15518 display = XDisplayConnection.get(); 15519 auto screen = DefaultScreen(display); 15520 15521 bool overrideRedirect = false; 15522 if(windowType == WindowTypes.dropdownMenu || windowType == WindowTypes.popupMenu || windowType == WindowTypes.notification)// || windowType == WindowTypes.nestedChild) 15523 overrideRedirect = true; 15524 15525 version(without_opengl) {} 15526 else { 15527 if(opengl == OpenGlOptions.yes) { 15528 GLXFBConfig fbconf = null; 15529 XVisualInfo* vi = null; 15530 bool useLegacy = false; 15531 static if (SdpyIsUsingIVGLBinds) {if (glbindGetProcAddress("glHint") is null) assert(0, "GL: error loading OpenGL"); } // loads all necessary functions 15532 if (sdpyOpenGLContextVersion != 0 && glXCreateContextAttribsARB_present()) { 15533 int[23] visualAttribs = [ 15534 GLX_X_RENDERABLE , 1/*True*/, 15535 GLX_DRAWABLE_TYPE, GLX_WINDOW_BIT, 15536 GLX_RENDER_TYPE , GLX_RGBA_BIT, 15537 GLX_X_VISUAL_TYPE, GLX_TRUE_COLOR, 15538 GLX_RED_SIZE , 8, 15539 GLX_GREEN_SIZE , 8, 15540 GLX_BLUE_SIZE , 8, 15541 GLX_ALPHA_SIZE , 8, 15542 GLX_DEPTH_SIZE , 24, 15543 GLX_STENCIL_SIZE , 8, 15544 GLX_DOUBLEBUFFER , 1/*True*/, 15545 0/*None*/, 15546 ]; 15547 int fbcount; 15548 GLXFBConfig* fbc = glXChooseFBConfig(display, screen, visualAttribs.ptr, &fbcount); 15549 if (fbcount == 0) { 15550 useLegacy = true; // try to do at least something 15551 } else { 15552 // pick the FB config/visual with the most samples per pixel 15553 int bestidx = -1, bestns = -1; 15554 foreach (int fbi; 0..fbcount) { 15555 int sb, samples; 15556 glXGetFBConfigAttrib(display, fbc[fbi], GLX_SAMPLE_BUFFERS, &sb); 15557 glXGetFBConfigAttrib(display, fbc[fbi], GLX_SAMPLES, &samples); 15558 if (bestidx < 0 || sb && samples > bestns) { bestidx = fbi; bestns = samples; } 15559 } 15560 //{ import core.stdc.stdio; printf("found gl visual with %d samples\n", bestns); } 15561 fbconf = fbc[bestidx]; 15562 // Be sure to free the FBConfig list allocated by glXChooseFBConfig() 15563 XFree(fbc); 15564 vi = cast(XVisualInfo*)glXGetVisualFromFBConfig(display, fbconf); 15565 } 15566 } 15567 if (vi is null || useLegacy) { 15568 static immutable GLint[5] attrs = [ GLX_RGBA, GLX_DEPTH_SIZE, 24, GLX_DOUBLEBUFFER, None ]; 15569 vi = cast(XVisualInfo*)glXChooseVisual(display, 0, attrs.ptr); 15570 useLegacy = true; 15571 } 15572 if (vi is null) throw new Exception("no open gl visual found"); 15573 15574 XSetWindowAttributes swa; 15575 auto root = RootWindow(display, screen); 15576 swa.colormap = XCreateColormap(display, root, vi.visual, AllocNone); 15577 15578 swa.override_redirect = overrideRedirect; 15579 15580 window = XCreateWindow(display, (windowType != WindowTypes.nestedChild || parent is null) ? root : parent.impl.window, 15581 0, 0, width, height, 15582 0, vi.depth, 1 /* InputOutput */, vi.visual, CWColormap | CWOverrideRedirect, &swa); 15583 15584 // now try to use `glXCreateContextAttribsARB()` if it's here 15585 if (!useLegacy) { 15586 // request fairly advanced context, even with stencil buffer! 15587 int[9] contextAttribs = [ 15588 GLX_CONTEXT_MAJOR_VERSION_ARB, (sdpyOpenGLContextVersion>>8), 15589 GLX_CONTEXT_MINOR_VERSION_ARB, (sdpyOpenGLContextVersion&0xff), 15590 /*GLX_CONTEXT_PROFILE_MASK_ARB*/0x9126, (sdpyOpenGLContextCompatible ? /*GLX_CONTEXT_COMPATIBILITY_PROFILE_BIT_ARB*/0x02 : /*GLX_CONTEXT_CORE_PROFILE_BIT_ARB*/ 0x01), 15591 // for modern context, set "forward compatibility" flag too 15592 (sdpyOpenGLContextCompatible ? None : /*GLX_CONTEXT_FLAGS_ARB*/ 0x2094), /*GLX_CONTEXT_FORWARD_COMPATIBLE_BIT_ARB*/ 0x02, 15593 0/*None*/, 15594 ]; 15595 glc = glXCreateContextAttribsARB(display, fbconf, null, 1/*True*/, contextAttribs.ptr); 15596 if (glc is null && sdpyOpenGLContextAllowFallback) { 15597 sdpyOpenGLContextVersion = 0; 15598 glc = glXCreateContext(display, vi, null, /*GL_TRUE*/1); 15599 } 15600 //{ import core.stdc.stdio; printf("using modern ogl v%d.%d\n", contextAttribs[1], contextAttribs[3]); } 15601 } else { 15602 // fallback to old GLX call 15603 if (sdpyOpenGLContextAllowFallback || sdpyOpenGLContextVersion == 0) { 15604 sdpyOpenGLContextVersion = 0; 15605 glc = glXCreateContext(display, vi, null, /*GL_TRUE*/1); 15606 } 15607 } 15608 // sync to ensure any errors generated are processed 15609 XSync(display, 0/*False*/); 15610 //{ import core.stdc.stdio; printf("ogl is here\n"); } 15611 if(glc is null) 15612 throw new Exception("glc"); 15613 } 15614 } 15615 15616 if(opengl == OpenGlOptions.no) { 15617 15618 XSetWindowAttributes swa; 15619 swa.background_pixel = WhitePixel(display, screen); 15620 swa.border_pixel = BlackPixel(display, screen); 15621 swa.override_redirect = overrideRedirect; 15622 auto root = RootWindow(display, screen); 15623 swa.colormap = XCreateColormap(display, root, DefaultVisual(display, screen), AllocNone); 15624 15625 window = XCreateWindow(display, (windowType != WindowTypes.nestedChild || parent is null) ? root : parent.impl.window, 15626 0, 0, width, height, 15627 // I'm commenting that CWBackPixel thing just because it actually causes flicker for no apparent benefit. 15628 0, CopyFromParent, 1 /* InputOutput */, cast(Visual*) CopyFromParent, CWColormap /*| CWBackPixel*/ | CWBorderPixel | CWOverrideRedirect, &swa); 15629 15630 15631 15632 /* 15633 window = XCreateSimpleWindow( 15634 display, 15635 parent is null ? RootWindow(display, screen) : parent.impl.window, 15636 0, 0, // x, y 15637 width, height, 15638 1, // border width 15639 BlackPixel(display, screen), // border 15640 WhitePixel(display, screen)); // background 15641 */ 15642 15643 buffer = XCreatePixmap(display, cast(Drawable) window, width, height, DefaultDepthOfDisplay(display)); 15644 bufferw = width; 15645 bufferh = height; 15646 15647 gc = DefaultGC(display, screen); 15648 15649 // clear out the buffer to get us started... 15650 XSetForeground(display, gc, WhitePixel(display, screen)); 15651 XFillRectangle(display, cast(Drawable) buffer, gc, 0, 0, width, height); 15652 XSetForeground(display, gc, BlackPixel(display, screen)); 15653 } 15654 15655 // input context 15656 //TODO: create this only for top-level windows, and reuse that? 15657 populateXic(); 15658 15659 if (sdpyWindowClassStr is null) loadBinNameToWindowClassName(); 15660 if (sdpyWindowClassStr is null) sdpyWindowClass = "DSimpleWindow"; 15661 // window class 15662 if (sdpyWindowClassStr !is null && sdpyWindowClassStr[0]) { 15663 //{ import core.stdc.stdio; printf("winclass: [%s]\n", sdpyWindowClassStr); } 15664 XClassHint klass; 15665 XWMHints wh; 15666 if(this.customizationFlags & WindowFlags.managesChildWindowFocus) { 15667 wh.input = true; 15668 wh.flags |= InputHint; 15669 } 15670 XSizeHints size; 15671 klass.res_name = sdpyWindowClassStr; 15672 klass.res_class = sdpyWindowClassStr; 15673 XSetWMProperties(display, window, null, null, null, 0, &size, &wh, &klass); 15674 } 15675 15676 setTitle(title); 15677 SimpleWindow.nativeMapping[window] = this; 15678 CapableOfHandlingNativeEvent.nativeHandleMapping[window] = this; 15679 15680 // This gives our window a close button 15681 if (windowType != WindowTypes.eventOnly) { 15682 Atom[2] atoms = [GetAtom!"WM_DELETE_WINDOW"(display), GetAtom!"WM_TAKE_FOCUS"(display)]; 15683 int useAtoms; 15684 if(this.customizationFlags & WindowFlags.managesChildWindowFocus) { 15685 useAtoms = 2; 15686 } else { 15687 useAtoms = 1; 15688 } 15689 assert(useAtoms <= atoms.length); 15690 XSetWMProtocols(display, window, atoms.ptr, useAtoms); 15691 } 15692 15693 // FIXME: windowType and customizationFlags 15694 Atom[8] wsatoms; // here, due to goto 15695 int wmsacount = 0; // here, due to goto 15696 15697 try 15698 final switch(windowType) { 15699 case WindowTypes.normal: 15700 setNetWMWindowType(GetAtom!"_NET_WM_WINDOW_TYPE_NORMAL"(display)); 15701 break; 15702 case WindowTypes.undecorated: 15703 motifHideDecorations(); 15704 setNetWMWindowType(GetAtom!"_NET_WM_WINDOW_TYPE_NORMAL"(display)); 15705 break; 15706 case WindowTypes.eventOnly: 15707 _hidden = true; 15708 XSelectInput(display, window, EventMask.StructureNotifyMask); // without this, we won't get destroy notification 15709 goto hiddenWindow; 15710 //break; 15711 case WindowTypes.nestedChild: 15712 // handled in XCreateWindow calls 15713 break; 15714 15715 case WindowTypes.dropdownMenu: 15716 motifHideDecorations(); 15717 setNetWMWindowType(GetAtom!"_NET_WM_WINDOW_TYPE_DROPDOWN_MENU"(display)); 15718 customizationFlags |= WindowFlags.skipTaskbar | WindowFlags.alwaysOnTop; 15719 break; 15720 case WindowTypes.popupMenu: 15721 motifHideDecorations(); 15722 setNetWMWindowType(GetAtom!"_NET_WM_WINDOW_TYPE_POPUP_MENU"(display)); 15723 customizationFlags |= WindowFlags.skipTaskbar | WindowFlags.alwaysOnTop; 15724 break; 15725 case WindowTypes.notification: 15726 motifHideDecorations(); 15727 setNetWMWindowType(GetAtom!"_NET_WM_WINDOW_TYPE_NOTIFICATION"(display)); 15728 customizationFlags |= WindowFlags.skipTaskbar | WindowFlags.alwaysOnTop; 15729 break; 15730 case WindowTypes.minimallyWrapped: 15731 assert(0, "don't create a minimallyWrapped thing explicitly!"); 15732 15733 case WindowTypes.dialog: 15734 setNetWMWindowType(GetAtom!"_NET_WM_WINDOW_TYPE_DIALOG"(display)); 15735 break; 15736 /+ 15737 case WindowTypes.menu: 15738 atoms[0] = GetAtom!"_NET_WM_WINDOW_TYPE_MENU"(display); 15739 motifHideDecorations(); 15740 break; 15741 case WindowTypes.desktop: 15742 atoms[0] = GetAtom!"_NET_WM_WINDOW_TYPE_DESKTOP"(display); 15743 break; 15744 case WindowTypes.dock: 15745 atoms[0] = GetAtom!"_NET_WM_WINDOW_TYPE_DOCK"(display); 15746 break; 15747 case WindowTypes.toolbar: 15748 atoms[0] = GetAtom!"_NET_WM_WINDOW_TYPE_TOOLBAR"(display); 15749 break; 15750 case WindowTypes.menu: 15751 atoms[0] = GetAtom!"_NET_WM_WINDOW_TYPE_MENU"(display); 15752 break; 15753 case WindowTypes.utility: 15754 atoms[0] = GetAtom!"_NET_WM_WINDOW_TYPE_UTILITY"(display); 15755 break; 15756 case WindowTypes.splash: 15757 atoms[0] = GetAtom!"_NET_WM_WINDOW_TYPE_SPLASH"(display); 15758 break; 15759 case WindowTypes.tooltip: 15760 atoms[0] = GetAtom!"_NET_WM_WINDOW_TYPE_TOOLTIP"(display); 15761 break; 15762 case WindowTypes.notification: 15763 atoms[0] = GetAtom!"_NET_WM_WINDOW_TYPE_NOTIFICATION"(display); 15764 break; 15765 case WindowTypes.combo: 15766 atoms[0] = GetAtom!"_NET_WM_WINDOW_TYPE_COMBO"(display); 15767 break; 15768 case WindowTypes.dnd: 15769 atoms[0] = GetAtom!"_NET_WM_WINDOW_TYPE_DND"(display); 15770 break; 15771 +/ 15772 } 15773 catch(Exception e) { 15774 // XInternAtom failed, prolly a WM 15775 // that doesn't support these things 15776 } 15777 15778 if (customizationFlags&WindowFlags.skipTaskbar) wsatoms[wmsacount++] = GetAtom!("_NET_WM_STATE_SKIP_TASKBAR", true)(display); 15779 // the two following flags may be ignored by WM 15780 if (customizationFlags&WindowFlags.alwaysOnTop) wsatoms[wmsacount++] = GetAtom!("_NET_WM_STATE_ABOVE", true)(display); 15781 if (customizationFlags&WindowFlags.alwaysOnBottom) wsatoms[wmsacount++] = GetAtom!("_NET_WM_STATE_BELOW", true)(display); 15782 15783 if (wmsacount != 0) XChangeProperty(display, window, GetAtom!("_NET_WM_STATE", true)(display), XA_ATOM, 32 /* bits */,0 /*PropModeReplace*/, wsatoms.ptr, wmsacount); 15784 15785 if (this.resizability == Resizability.fixedSize || (opengl == OpenGlOptions.no && this.resizability != Resizability.allowResizing)) fixFixedSize!true(width, height); 15786 15787 // What would be ideal here is if they only were 15788 // selected if there was actually an event handler 15789 // for them... 15790 15791 selectDefaultInput((customizationFlags & WindowFlags.alwaysRequestMouseMotionEvents)?true:false); 15792 15793 hiddenWindow: 15794 15795 // set the pid property for lookup later by window managers 15796 // a standard convenience 15797 import core.sys.posix.unistd; 15798 arch_ulong pid = getpid(); 15799 15800 XChangeProperty( 15801 display, 15802 impl.window, 15803 GetAtom!("_NET_WM_PID", true)(display), 15804 XA_CARDINAL, 15805 32 /* bits */, 15806 0 /*PropModeReplace*/, 15807 &pid, 15808 1); 15809 15810 if(isTransient && parent) { // customizationFlags & WindowFlags.transient) { 15811 if(parent is null) assert(0); 15812 // sdpyPrintDebugString("transient"); 15813 XChangeProperty( 15814 display, 15815 impl.window, 15816 GetAtom!("WM_TRANSIENT_FOR", true)(display), 15817 XA_WINDOW, 15818 32 /* bits */, 15819 0 /*PropModeReplace*/, 15820 &parent.impl.window, 15821 1); 15822 15823 } 15824 15825 if(windowType != WindowTypes.eventOnly && (customizationFlags&WindowFlags.dontAutoShow) == 0) { 15826 XMapWindow(display, window); 15827 } else { 15828 _hidden = true; 15829 } 15830 } 15831 15832 void populateXic() { 15833 if (XDisplayConnection.xim !is null) { 15834 xic = XCreateIC(XDisplayConnection.xim, 15835 /*XNInputStyle*/"inputStyle".ptr, XIMPreeditNothing|XIMStatusNothing, 15836 /*XNClientWindow*/"clientWindow".ptr, window, 15837 /*XNFocusWindow*/"focusWindow".ptr, window, 15838 null); 15839 if (xic is null) { 15840 import core.stdc.stdio : stderr, fprintf; 15841 fprintf(stderr, "XCreateIC failed for window %u\n", cast(uint)window); 15842 } 15843 } 15844 } 15845 15846 void selectDefaultInput(bool forceIncludeMouseMotion) { 15847 auto mask = EventMask.ExposureMask | 15848 EventMask.KeyPressMask | 15849 EventMask.KeyReleaseMask | 15850 EventMask.PropertyChangeMask | 15851 EventMask.FocusChangeMask | 15852 EventMask.StructureNotifyMask | 15853 EventMask.SubstructureNotifyMask | 15854 EventMask.VisibilityChangeMask 15855 | EventMask.ButtonPressMask 15856 | EventMask.ButtonReleaseMask 15857 ; 15858 15859 // xshm is our shortcut for local connections 15860 if(XDisplayConnection.isLocal || forceIncludeMouseMotion) 15861 mask |= EventMask.PointerMotionMask; 15862 else 15863 mask |= EventMask.ButtonMotionMask; 15864 15865 XSelectInput(display, window, mask); 15866 } 15867 15868 15869 void setNetWMWindowType(Atom type) { 15870 Atom[2] atoms; 15871 15872 atoms[0] = type; 15873 // generic fallback 15874 atoms[1] = GetAtom!"_NET_WM_WINDOW_TYPE_NORMAL"(display); 15875 15876 XChangeProperty( 15877 display, 15878 impl.window, 15879 GetAtom!"_NET_WM_WINDOW_TYPE"(display), 15880 XA_ATOM, 15881 32 /* bits */, 15882 0 /*PropModeReplace*/, 15883 atoms.ptr, 15884 cast(int) atoms.length); 15885 } 15886 15887 void motifHideDecorations(bool hide = true) { 15888 MwmHints hints; 15889 hints.flags = MWM_HINTS_DECORATIONS; 15890 hints.decorations = hide ? 0 : 1; 15891 15892 XChangeProperty( 15893 display, 15894 impl.window, 15895 GetAtom!"_MOTIF_WM_HINTS"(display), 15896 GetAtom!"_MOTIF_WM_HINTS"(display), 15897 32 /* bits */, 15898 0 /*PropModeReplace*/, 15899 &hints, 15900 hints.sizeof / 4); 15901 } 15902 15903 /*k8: unused 15904 void createOpenGlContext() { 15905 15906 } 15907 */ 15908 15909 void closeWindow() { 15910 // I can't close this or a child window closing will 15911 // break events for everyone. So I'm just leaking it right 15912 // now and that is probably perfectly fine... 15913 version(none) 15914 if (customEventFDRead != -1) { 15915 import core.sys.posix.unistd : close; 15916 auto same = customEventFDRead == customEventFDWrite; 15917 15918 close(customEventFDRead); 15919 if(!same) 15920 close(customEventFDWrite); 15921 customEventFDRead = -1; 15922 customEventFDWrite = -1; 15923 } 15924 15925 version(without_opengl) {} else 15926 if(glc !is null) { 15927 glXDestroyContext(display, glc); 15928 glc = null; 15929 } 15930 15931 if(buffer) 15932 XFreePixmap(display, buffer); 15933 bufferw = bufferh = 0; 15934 if (blankCurPtr && cursorSequenceNumber == XDisplayConnection.connectionSequenceNumber) XFreeCursor(display, blankCurPtr); 15935 XDestroyWindow(display, window); 15936 XFlush(display); 15937 } 15938 15939 void dispose() { 15940 } 15941 15942 bool destroyed = false; 15943 } 15944 15945 bool insideXEventLoop; 15946 } 15947 15948 version(X11) { 15949 15950 int mouseDoubleClickTimeout = 350; /// Double click timeout. X only, you probably shouldn't change this. 15951 15952 private class ResizeEvent { 15953 int width, height; 15954 } 15955 15956 void recordX11ResizeAsync(Display* display, SimpleWindow win, int width, int height) { 15957 if(win.windowType == WindowTypes.minimallyWrapped) 15958 return; 15959 15960 if(win.pendingResizeEvent is null) { 15961 win.pendingResizeEvent = new ResizeEvent(); 15962 win.addEventListener((ResizeEvent re) { 15963 recordX11Resize(XDisplayConnection.get, win, re.width, re.height); 15964 }); 15965 } 15966 win.pendingResizeEvent.width = width; 15967 win.pendingResizeEvent.height = height; 15968 if(!win.eventQueued!ResizeEvent) { 15969 win.postEvent(win.pendingResizeEvent); 15970 } 15971 } 15972 15973 void recordX11Resize(Display* display, SimpleWindow win, int width, int height) { 15974 if(win.windowType == WindowTypes.minimallyWrapped) 15975 return; 15976 if(win.closed) 15977 return; 15978 15979 if(width != win.width || height != win.height) { 15980 15981 // writeln("RESIZE: ", width, "x", height, " was ", win._width, "x", win._height, " window: ", win.windowType, "-", win.title, " ", win.window); 15982 win._width = width; 15983 win._height = height; 15984 15985 if(win.openglMode == OpenGlOptions.no) { 15986 // FIXME: could this be more efficient? 15987 15988 if (win.bufferw < width || win.bufferh < height) { 15989 //{ 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); } 15990 // grow the internal buffer to match the window... 15991 auto newPixmap = XCreatePixmap(display, cast(Drawable) win.window, width, height, DefaultDepthOfDisplay(display)); 15992 { 15993 GC xgc = XCreateGC(win.display, cast(Drawable)win.window, 0, null); 15994 XCopyGC(win.display, win.gc, 0xffffffff, xgc); 15995 scope(exit) XFreeGC(win.display, xgc); 15996 XSetClipMask(win.display, xgc, None); 15997 XSetForeground(win.display, xgc, 0); 15998 XFillRectangle(display, cast(Drawable)newPixmap, xgc, 0, 0, width, height); 15999 } 16000 XCopyArea(display, 16001 cast(Drawable) win.buffer, 16002 cast(Drawable) newPixmap, 16003 win.gc, 0, 0, 16004 win.bufferw < width ? win.bufferw : win.width, 16005 win.bufferh < height ? win.bufferh : win.height, 16006 0, 0); 16007 16008 XFreePixmap(display, win.buffer); 16009 win.buffer = newPixmap; 16010 win.bufferw = width; 16011 win.bufferh = height; 16012 } 16013 16014 // clear unused parts of the buffer 16015 if (win.bufferw > width || win.bufferh > height) { 16016 GC xgc = XCreateGC(win.display, cast(Drawable)win.window, 0, null); 16017 XCopyGC(win.display, win.gc, 0xffffffff, xgc); 16018 scope(exit) XFreeGC(win.display, xgc); 16019 XSetClipMask(win.display, xgc, None); 16020 XSetForeground(win.display, xgc, 0); 16021 immutable int maxw = (win.bufferw > width ? win.bufferw : width); 16022 immutable int maxh = (win.bufferh > height ? win.bufferh : height); 16023 XFillRectangle(win.display, cast(Drawable)win.buffer, xgc, width, 0, maxw, maxh); // let X11 do clipping 16024 XFillRectangle(win.display, cast(Drawable)win.buffer, xgc, 0, height, maxw, maxh); // let X11 do clipping 16025 } 16026 16027 } 16028 16029 win.updateOpenglViewportIfNeeded(width, height); 16030 16031 win.fixFixedSize(width, height); //k8: this does nothing on my FluxBox; wtf?! 16032 16033 if(win.resizability != Resizability.automaticallyScaleIfPossible) 16034 if(win.windowResized !is null) { 16035 XUnlockDisplay(display); 16036 scope(exit) XLockDisplay(display); 16037 win.windowResized(width, height); 16038 } 16039 } 16040 } 16041 16042 16043 /// Platform-specific, you might use it when doing a custom event loop. 16044 bool doXNextEvent(Display* display) { 16045 bool done; 16046 XEvent e; 16047 XNextEvent(display, &e); 16048 version(sddddd) { 16049 if(auto win = e.xany.window in CapableOfHandlingNativeEvent.nativeHandleMapping) { 16050 if(typeid(cast(Object) *win) == NotificationAreaIcon.classinfo) 16051 writeln("event for: ", e.xany.window, "; type is ", to!string(cast(EventType)e.type)); 16052 } 16053 } 16054 16055 // filter out compose events 16056 if (XFilterEvent(&e, None)) { 16057 //{ import core.stdc.stdio : printf; printf("XFilterEvent filtered!\n"); } 16058 //NOTE: we should ungrab keyboard here, but simpledisplay doesn't use keyboard grabbing (yet) 16059 return false; 16060 } 16061 // process keyboard mapping changes 16062 if (e.type == EventType.KeymapNotify) { 16063 //{ import core.stdc.stdio : printf; printf("KeymapNotify processed!\n"); } 16064 XRefreshKeyboardMapping(&e.xmapping); 16065 return false; 16066 } 16067 16068 version(with_eventloop) 16069 import arsd.eventloop; 16070 16071 if(SimpleWindow.handleNativeGlobalEvent !is null) { 16072 // see windows impl's comments 16073 XUnlockDisplay(display); 16074 scope(exit) XLockDisplay(display); 16075 auto ret = SimpleWindow.handleNativeGlobalEvent(e); 16076 if(ret == 0) 16077 return done; 16078 } 16079 16080 16081 if(auto win = e.xany.window in CapableOfHandlingNativeEvent.nativeHandleMapping) { 16082 if(win.getNativeEventHandler !is null) { 16083 XUnlockDisplay(display); 16084 scope(exit) XLockDisplay(display); 16085 auto ret = win.getNativeEventHandler()(e); 16086 if(ret == 0) 16087 return done; 16088 } 16089 } 16090 16091 if(xrrEventBase != -1 && e.type == xrrEventBase + RRScreenChangeNotify) { 16092 if(auto win = e.xany.window in SimpleWindow.nativeMapping) { 16093 // we get this because of the RRScreenChangeNotifyMask 16094 16095 // this isn't actually an ideal way to do it since it wastes time 16096 // but meh it is simple and it works. 16097 win.actualDpiLoadAttempted = false; 16098 SimpleWindow.xRandrInfoLoadAttemped = false; 16099 win.updateActualDpi(); // trigger a reload 16100 } 16101 } 16102 16103 switch(e.type) { 16104 case EventType.SelectionClear: 16105 // writeln("SelectionClear"); 16106 if(auto win = e.xselectionclear.window in SimpleWindow.nativeMapping) { 16107 // FIXME so it is supposed to finish any in progress transfers... but idk... 16108 // writeln("SelectionClear"); 16109 } 16110 SimpleWindow.impl.setSelectionHandlers.remove(e.xselectionclear.selection); 16111 mightShortCircuitClipboard = false; 16112 break; 16113 case EventType.SelectionRequest: 16114 if(auto win = e.xselectionrequest.owner in SimpleWindow.nativeMapping) 16115 if(auto ssh = e.xselectionrequest.selection in SimpleWindow.impl.setSelectionHandlers) { 16116 // printf("SelectionRequest %s\n", XGetAtomName(e.xselectionrequest.display, e.xselectionrequest.target)); 16117 XUnlockDisplay(display); 16118 scope(exit) XLockDisplay(display); 16119 (*ssh).handleRequest(e); 16120 } 16121 break; 16122 case EventType.PropertyNotify: 16123 // import core.stdc.stdio; printf("PropertyNotify %s %d\n", XGetAtomName(e.xproperty.display, e.xproperty.atom), e.xproperty.state); 16124 16125 foreach(ssh; SimpleWindow.impl.setSelectionHandlers) { 16126 if(ssh.matchesIncr(e.xproperty.window, e.xproperty.atom) && e.xproperty.state == PropertyNotification.PropertyDelete) 16127 ssh.sendMoreIncr(&e.xproperty); 16128 } 16129 16130 16131 if(auto win = e.xproperty.window in SimpleWindow.nativeMapping) 16132 if(auto handler = e.xproperty.atom in win.getSelectionHandlers) { 16133 if(handler.matchesIncr(e.xproperty.window, e.xproperty.atom) && e.xproperty.state == PropertyNotification.PropertyNewValue) { 16134 Atom target; 16135 int format; 16136 arch_ulong bytesafter, length; 16137 void* value; 16138 16139 ubyte[] s; 16140 Atom targetToKeep; 16141 16142 XGetWindowProperty( 16143 e.xproperty.display, 16144 e.xproperty.window, 16145 e.xproperty.atom, 16146 0, 16147 100000 /* length */, 16148 true, /* erase it to signal we got it and want more */ 16149 0 /*AnyPropertyType*/, 16150 &target, &format, &length, &bytesafter, &value); 16151 16152 if(!targetToKeep) 16153 targetToKeep = target; 16154 16155 auto id = (cast(ubyte*) value)[0 .. length]; 16156 16157 handler.handleIncrData(targetToKeep, id); 16158 if(length == 0) { 16159 win.getSelectionHandlers.remove(e.xproperty.atom); 16160 } 16161 16162 XFree(value); 16163 } 16164 } 16165 break; 16166 case EventType.SelectionNotify: 16167 // import std.stdio; writefln("SelectionNotify %06x %06x", e.xselection.requestor, e.xproperty.atom); 16168 if(auto win = e.xselection.requestor in SimpleWindow.nativeMapping) 16169 if(auto handler = e.xproperty.atom in win.getSelectionHandlers) { 16170 if(e.xselection.property == None) { // || e.xselection.property == GetAtom!("NULL", true)(e.xselection.display)) { 16171 XUnlockDisplay(display); 16172 scope(exit) XLockDisplay(display); 16173 handler.handleData(None, null); 16174 win.getSelectionHandlers.remove(e.xproperty.atom); 16175 } else { 16176 Atom target; 16177 int format; 16178 arch_ulong bytesafter, length; 16179 void* value; 16180 XGetWindowProperty( 16181 e.xselection.display, 16182 e.xselection.requestor, 16183 e.xselection.property, 16184 0, 16185 100000 /* length */, 16186 //false, /* don't erase it */ 16187 true, /* do erase it lol */ 16188 0 /*AnyPropertyType*/, 16189 &target, &format, &length, &bytesafter, &value); 16190 16191 // FIXME: I don't have to copy it now since it is in char[] instead of string 16192 16193 { 16194 XUnlockDisplay(display); 16195 scope(exit) XLockDisplay(display); 16196 16197 if(target == XA_ATOM) { 16198 // initial request, see what they are able to work with and request the best one 16199 // we can handle, if available 16200 16201 Atom[] answer = (cast(Atom*) value)[0 .. length]; 16202 Atom best = handler.findBestFormat(answer); 16203 16204 /+ 16205 writeln("got ", answer); 16206 foreach(a; answer) 16207 writeln(XGetAtomName(display, a).stringz); 16208 writeln("best ", best); 16209 +/ 16210 16211 if(best != None) { 16212 // actually request the best format 16213 XConvertSelection(e.xselection.display, e.xselection.selection, best, GetAtom!("SDD_DATA", true)(display), e.xselection.requestor, 0 /*CurrentTime*/); 16214 } 16215 } else if(target == GetAtom!"INCR"(display)) { 16216 // incremental 16217 16218 handler.prepareIncremental(e.xselection.requestor, e.xselection.property); 16219 16220 // signal the sending program that we see 16221 // the incr and are ready to receive more. 16222 XDeleteProperty( 16223 e.xselection.display, 16224 e.xselection.requestor, 16225 e.xselection.property); 16226 } else { 16227 // unsupported type... maybe, forward, then we done with it 16228 if(target != None) { 16229 handler.handleData(target, cast(ubyte[]) value[0 .. length]); 16230 win.getSelectionHandlers.remove(e.xproperty.atom); 16231 } 16232 } 16233 } 16234 XFree(value); 16235 /* 16236 XDeleteProperty( 16237 e.xselection.display, 16238 e.xselection.requestor, 16239 e.xselection.property); 16240 */ 16241 } 16242 } 16243 break; 16244 case EventType.ConfigureNotify: 16245 auto event = e.xconfigure; 16246 if(auto win = event.window in SimpleWindow.nativeMapping) { 16247 if(win.windowType == WindowTypes.minimallyWrapped) 16248 break; 16249 //version(sdddd) { writeln(" w=", event.width, "; h=", event.height); } 16250 16251 /+ 16252 The ICCCM says window managers must send a synthetic event when the window 16253 is moved but NOT when it is resized. In the resize case, an event is sent 16254 with position (0, 0) which can be wrong and break the dpi calculations. 16255 16256 So we only consider the synthetic events from the WM and otherwise 16257 need to wait for some other event to get the position which... sucks. 16258 16259 I'd rather not have windows changing their layout on mouse motion after 16260 switching monitors... might be forced to but for now just ignoring it. 16261 16262 Easiest way to switch monitors without sending a size position is by 16263 maximize or fullscreen in a setup like mine, but on most setups those 16264 work on the monitor it is already living on, so it should be ok most the 16265 time. 16266 +/ 16267 if(event.send_event) { 16268 win.screenPositionKnown = true; 16269 win.screenPositionX = event.x; 16270 win.screenPositionY = event.y; 16271 win.updateActualDpi(); 16272 } 16273 16274 win.updateIMEPopupLocation(); 16275 recordX11ResizeAsync(display, *win, event.width, event.height); 16276 } 16277 break; 16278 case EventType.Expose: 16279 if(auto win = e.xexpose.window in SimpleWindow.nativeMapping) { 16280 if(win.windowType == WindowTypes.minimallyWrapped) 16281 break; 16282 // if it is closing from a popup menu, it can get 16283 // an Expose event right by the end and trigger a 16284 // BadDrawable error ... we'll just check 16285 // closed to handle that. 16286 if((*win).closed) break; 16287 if((*win).openglMode == OpenGlOptions.no) { 16288 bool doCopy = true;// e.xexpose.count == 0; // the count is better if we copy all area but meh 16289 if (win.handleExpose !is null) doCopy = !win.handleExpose(e.xexpose.x, e.xexpose.y, e.xexpose.width, e.xexpose.height, e.xexpose.count); 16290 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); 16291 } else { 16292 // need to redraw the scene somehow 16293 if(e.xexpose.count == 0) { // only do the last one since redrawOpenGlSceneNow always does it all 16294 XUnlockDisplay(display); 16295 scope(exit) XLockDisplay(display); 16296 version(without_opengl) {} else 16297 win.redrawOpenGlSceneSoon(); 16298 } 16299 } 16300 } 16301 break; 16302 case EventType.FocusIn: 16303 case EventType.FocusOut: 16304 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 16305 16306 if(auto win = e.xfocus.window in SimpleWindow.nativeMapping) { 16307 /+ 16308 16309 void info(string detail) { 16310 string s; 16311 // import std.conv; 16312 // import std.datetime; 16313 s ~= to!string(Clock.currTime); 16314 s ~= " "; 16315 s ~= e.type == EventType.FocusIn ? "in " : "out"; 16316 s ~= " "; 16317 s ~= win.windowType == WindowTypes.nestedChild ? "child " : "main "; 16318 s ~= e.xfocus.mode == NotifyModes.NotifyNormal ? " normal ": " grabbed "; 16319 s ~= detail; 16320 s ~= " "; 16321 16322 sdpyPrintDebugString(s); 16323 16324 } 16325 16326 switch(e.xfocus.detail) { 16327 case NotifyDetail.NotifyAncestor: info("Ancestor"); break; 16328 case NotifyDetail.NotifyVirtual: info("Virtual"); break; 16329 case NotifyDetail.NotifyInferior: info("Inferior"); break; 16330 case NotifyDetail.NotifyNonlinear: info("Nonlinear"); break; 16331 case NotifyDetail.NotifyNonlinearVirtual: info("nlinearvirtual"); break; 16332 case NotifyDetail.NotifyPointer: info("pointer"); break; 16333 case NotifyDetail.NotifyPointerRoot: info("pointerroot"); break; 16334 case NotifyDetail.NotifyDetailNone: info("none"); break; 16335 default: 16336 16337 } 16338 +/ 16339 16340 16341 if(e.xfocus.detail == NotifyDetail.NotifyPointer) 16342 break; // just ignore these they seem irrelevant 16343 16344 auto old = win._focused; 16345 win._focused = e.type == EventType.FocusIn; 16346 16347 // yes, we are losing the focus, but to our own child. that's actually kinda keeping it. 16348 if(e.type == EventType.FocusOut && e.xfocus.detail == NotifyDetail.NotifyInferior) 16349 win._focused = true; 16350 16351 if(win.demandingAttention) 16352 demandAttention(*win, false); 16353 16354 win.updateIMEFocused(); 16355 16356 if(old != win._focused && win.onFocusChange) { 16357 XUnlockDisplay(display); 16358 scope(exit) XLockDisplay(display); 16359 win.onFocusChange(win._focused); 16360 } 16361 } 16362 break; 16363 case EventType.VisibilityNotify: 16364 if(auto win = e.xfocus.window in SimpleWindow.nativeMapping) { 16365 auto before = (*win)._visible; 16366 (*win)._visible = (e.xvisibility.state != VisibilityNotify.VisibilityFullyObscured); 16367 if (e.xvisibility.state == VisibilityNotify.VisibilityFullyObscured) { 16368 if (win.visibilityChanged !is null && before == true) { 16369 XUnlockDisplay(display); 16370 scope(exit) XLockDisplay(display); 16371 win.visibilityChanged(false); 16372 } 16373 } else { 16374 if (win.visibilityChanged !is null && before == false) { 16375 XUnlockDisplay(display); 16376 scope(exit) XLockDisplay(display); 16377 win.visibilityChanged(true); 16378 } 16379 } 16380 } 16381 break; 16382 case EventType.ClientMessage: 16383 if (e.xclient.message_type == GetAtom!("_X11SDPY_INSMME_FLAG_EVENT_", true)(e.xany.display)) { 16384 // "ignore next mouse motion" event, increment ignore counter for teh window 16385 if (auto win = e.xclient.window in SimpleWindow.nativeMapping) { 16386 ++(*win).warpEventCount; 16387 debug(x11sdpy_warp_debug) { import core.stdc.stdio : printf; printf("X11: got \"INSMME\" message, new count=%d\n", (*win).warpEventCount); } 16388 } else { 16389 debug(x11sdpy_warp_debug) { import core.stdc.stdio : printf; printf("X11: got \"INSMME\" WTF?!!\n"); } 16390 } 16391 } else if(e.xclient.data.l[0] == GetAtom!"WM_DELETE_WINDOW"(e.xany.display)) { 16392 // user clicked the close button on the window manager 16393 if(auto win = e.xclient.window in SimpleWindow.nativeMapping) { 16394 XUnlockDisplay(display); 16395 scope(exit) XLockDisplay(display); 16396 if ((*win).closeQuery !is null) (*win).closeQuery(); else (*win).close(); 16397 } 16398 16399 } else if(e.xclient.data.l[0] == GetAtom!"WM_TAKE_FOCUS"(e.xany.display)) { 16400 // writeln("HAPPENED"); 16401 // user clicked the close button on the window manager 16402 if(auto win = e.xclient.window in SimpleWindow.nativeMapping) { 16403 XUnlockDisplay(display); 16404 scope(exit) XLockDisplay(display); 16405 16406 auto setTo = *win; 16407 16408 if(win.setRequestedInputFocus !is null) { 16409 auto s = win.setRequestedInputFocus(); 16410 if(s !is null) { 16411 setTo = s; 16412 } 16413 } 16414 16415 assert(setTo !is null); 16416 16417 // FIXME: so this is actually supposed to focus to a relevant child window if appropriate 16418 16419 // sdpyPrintDebugString("WM_TAKE_FOCUS ", setTo.impl.window); 16420 XSetInputFocus(display, setTo.impl.window, RevertToParent, e.xclient.data.l[1]); 16421 } 16422 } else if(e.xclient.message_type == GetAtom!"MANAGER"(e.xany.display)) { 16423 foreach(nai; NotificationAreaIcon.activeIcons) 16424 nai.newManager(); 16425 } else if(auto win = e.xclient.window in SimpleWindow.nativeMapping) { 16426 16427 bool xDragWindow = true; 16428 if(xDragWindow && e.xclient.message_type == GetAtom!"XdndStatus"(e.xany.display)) { 16429 //XDefineCursor(display, xDragWindow.impl.window, 16430 //writeln("XdndStatus ", e.xclient.data.l); 16431 } 16432 if(auto dh = win.dropHandler) { 16433 16434 static Atom[3] xFormatsBuffer; 16435 static Atom[] xFormats; 16436 16437 void resetXFormats() { 16438 xFormatsBuffer[] = 0; 16439 xFormats = xFormatsBuffer[]; 16440 } 16441 16442 if(e.xclient.message_type == GetAtom!"XdndEnter"(e.xany.display)) { 16443 // on Windows it is supposed to return the effect you actually do FIXME 16444 16445 auto sourceWindow = e.xclient.data.l[0]; 16446 16447 xFormatsBuffer[0] = e.xclient.data.l[2]; 16448 xFormatsBuffer[1] = e.xclient.data.l[3]; 16449 xFormatsBuffer[2] = e.xclient.data.l[4]; 16450 16451 if(e.xclient.data.l[1] & 1) { 16452 // can just grab it all but like we don't necessarily need them... 16453 xFormats = cast(Atom[]) getX11PropertyData(sourceWindow, GetAtom!"XdndTypeList"(display), XA_ATOM); 16454 } else { 16455 int len; 16456 foreach(fmt; xFormatsBuffer) 16457 if(fmt) len++; 16458 xFormats = xFormatsBuffer[0 .. len]; 16459 } 16460 16461 auto pkg = DropPackage(*win, e.xclient.data.l[0], 0, xFormats); 16462 16463 dh.dragEnter(&pkg); 16464 } else if(e.xclient.message_type == GetAtom!"XdndPosition"(e.xany.display)) { 16465 16466 auto pack = e.xclient.data.l[2]; 16467 16468 auto result = dh.dragOver(Point((pack & 0xffff0000) >> 16, pack & 0xffff)); // FIXME: translate screen coordinates back to window coords 16469 16470 16471 XClientMessageEvent xclient; 16472 16473 xclient.type = EventType.ClientMessage; 16474 xclient.window = e.xclient.data.l[0]; 16475 xclient.message_type = GetAtom!"XdndStatus"(display); 16476 xclient.format = 32; 16477 xclient.data.l[0] = win.impl.window; 16478 xclient.data.l[1] = (result.action != DragAndDropAction.none) ? 1 : 0; // will accept 16479 auto r = result.consistentWithin; 16480 xclient.data.l[2] = ((cast(short) r.left) << 16) | (cast(short) r.top); 16481 xclient.data.l[3] = ((cast(short) r.width) << 16) | (cast(short) r.height); 16482 xclient.data.l[4] = dndActionAtom(e.xany.display, result.action); 16483 16484 XSendEvent( 16485 display, 16486 e.xclient.data.l[0], 16487 false, 16488 EventMask.NoEventMask, 16489 cast(XEvent*) &xclient 16490 ); 16491 16492 16493 } else if(e.xclient.message_type == GetAtom!"XdndLeave"(e.xany.display)) { 16494 //writeln("XdndLeave"); 16495 // drop cancelled. 16496 // data.l[0] is the source window 16497 dh.dragLeave(); 16498 16499 resetXFormats(); 16500 } else if(e.xclient.message_type == GetAtom!"XdndDrop"(e.xany.display)) { 16501 // drop happening, should fetch data, then send finished 16502 // writeln("XdndDrop"); 16503 16504 auto pkg = DropPackage(*win, e.xclient.data.l[0], e.xclient.data.l[2], xFormats); 16505 16506 dh.drop(&pkg); 16507 16508 resetXFormats(); 16509 } else if(e.xclient.message_type == GetAtom!"XdndFinished"(e.xany.display)) { 16510 // writeln("XdndFinished"); 16511 16512 dh.finish(); 16513 } 16514 16515 } 16516 } 16517 break; 16518 case EventType.MapNotify: 16519 if(auto win = e.xmap.window in SimpleWindow.nativeMapping) { 16520 auto before = (*win)._visible; 16521 (*win)._visible = true; 16522 if (!(*win)._visibleForTheFirstTimeCalled) { 16523 (*win)._visibleForTheFirstTimeCalled = true; 16524 if ((*win).visibleForTheFirstTime !is null) { 16525 XUnlockDisplay(display); 16526 scope(exit) XLockDisplay(display); 16527 (*win).visibleForTheFirstTime(); 16528 } 16529 } 16530 if ((*win).visibilityChanged !is null && before == false) { 16531 XUnlockDisplay(display); 16532 scope(exit) XLockDisplay(display); 16533 (*win).visibilityChanged(true); 16534 } 16535 } 16536 break; 16537 case EventType.UnmapNotify: 16538 if(auto win = e.xunmap.window in SimpleWindow.nativeMapping) { 16539 auto before = (*win)._visible; 16540 win._visible = false; 16541 if (win.visibilityChanged !is null && before == true) { 16542 XUnlockDisplay(display); 16543 scope(exit) XLockDisplay(display); 16544 win.visibilityChanged(false); 16545 } 16546 } 16547 break; 16548 case EventType.DestroyNotify: 16549 if(auto win = e.xdestroywindow.window in SimpleWindow.nativeMapping) { 16550 if(win.destroyed) 16551 break; // might get a notification both for itself and from its parent 16552 if (win.onDestroyed !is null) try { win.onDestroyed(); } catch (Exception e) {} // sorry 16553 win._closed = true; // just in case 16554 win.destroyed = true; 16555 if (win.xic !is null) { 16556 XDestroyIC(win.xic); 16557 win.xic = null; // just in case 16558 } 16559 SimpleWindow.nativeMapping.remove(e.xdestroywindow.window); 16560 bool anyImportant = false; 16561 foreach(SimpleWindow w; SimpleWindow.nativeMapping) 16562 if(w.beingOpenKeepsAppOpen) { 16563 anyImportant = true; 16564 break; 16565 } 16566 if(!anyImportant) { 16567 EventLoop.quitApplication(); 16568 done = true; 16569 } 16570 } 16571 auto window = e.xdestroywindow.window; 16572 if(window in CapableOfHandlingNativeEvent.nativeHandleMapping) 16573 CapableOfHandlingNativeEvent.nativeHandleMapping.remove(window); 16574 16575 version(with_eventloop) { 16576 if(done) exit(); 16577 } 16578 break; 16579 16580 case EventType.MotionNotify: 16581 MouseEvent mouse; 16582 auto event = e.xmotion; 16583 16584 mouse.type = MouseEventType.motion; 16585 mouse.x = event.x; 16586 mouse.y = event.y; 16587 mouse.modifierState = event.state; 16588 16589 mouse.timestamp = event.time; 16590 16591 if(auto win = e.xmotion.window in SimpleWindow.nativeMapping) { 16592 mouse.window = *win; 16593 if (win.warpEventCount > 0) { 16594 debug(x11sdpy_warp_debug) { import core.stdc.stdio : printf; printf("X11: got \"warp motion\" message, current count=%d\n", (*win).warpEventCount); } 16595 --(*win).warpEventCount; 16596 (*win).mdx(mouse); // so deltas will be correctly updated 16597 } else { 16598 win.warpEventCount = 0; // just in case 16599 (*win).mdx(mouse); 16600 if((*win).handleMouseEvent) { 16601 XUnlockDisplay(display); 16602 scope(exit) XLockDisplay(display); 16603 (*win).handleMouseEvent(mouse); 16604 } 16605 } 16606 } 16607 16608 version(with_eventloop) 16609 send(mouse); 16610 break; 16611 case EventType.ButtonPress: 16612 case EventType.ButtonRelease: 16613 MouseEvent mouse; 16614 auto event = e.xbutton; 16615 16616 mouse.timestamp = event.time; 16617 16618 mouse.type = cast(MouseEventType) (e.type == EventType.ButtonPress ? 1 : 2); 16619 mouse.x = event.x; 16620 mouse.y = event.y; 16621 16622 static Time lastMouseDownTime = 0; 16623 static int lastMouseDownButton = -1; 16624 16625 mouse.doubleClick = e.type == EventType.ButtonPress && event.button == lastMouseDownButton && (event.time - lastMouseDownTime) < mouseDoubleClickTimeout; 16626 if(e.type == EventType.ButtonPress) { 16627 lastMouseDownTime = event.time; 16628 lastMouseDownButton = event.button; 16629 } 16630 16631 switch(event.button) { 16632 case 1: mouse.button = MouseButton.left; break; // left 16633 case 2: mouse.button = MouseButton.middle; break; // middle 16634 case 3: mouse.button = MouseButton.right; break; // right 16635 case 4: mouse.button = MouseButton.wheelUp; break; // scroll up 16636 case 5: mouse.button = MouseButton.wheelDown; break; // scroll down 16637 case 6: break; // idk 16638 case 7: break; // idk 16639 case 8: mouse.button = MouseButton.backButton; break; 16640 case 9: mouse.button = MouseButton.forwardButton; break; 16641 default: 16642 } 16643 16644 // FIXME: double check this 16645 mouse.modifierState = event.state; 16646 16647 //mouse.modifierState = event.detail; 16648 16649 if(auto win = e.xbutton.window in SimpleWindow.nativeMapping) { 16650 mouse.window = *win; 16651 (*win).mdx(mouse); 16652 if((*win).handleMouseEvent) { 16653 XUnlockDisplay(display); 16654 scope(exit) XLockDisplay(display); 16655 (*win).handleMouseEvent(mouse); 16656 } 16657 } 16658 version(with_eventloop) 16659 send(mouse); 16660 break; 16661 16662 case EventType.KeyPress: 16663 case EventType.KeyRelease: 16664 //if (e.type == EventType.KeyPress) { import core.stdc.stdio : stderr, fprintf; fprintf(stderr, "X11 keyboard event!\n"); } 16665 KeyEvent ke; 16666 ke.pressed = e.type == EventType.KeyPress; 16667 ke.hardwareCode = cast(ubyte) e.xkey.keycode; 16668 16669 auto sym = XKeycodeToKeysym( 16670 XDisplayConnection.get(), 16671 e.xkey.keycode, 16672 0); 16673 16674 ke.key = cast(Key) sym;//e.xkey.keycode; 16675 16676 ke.modifierState = e.xkey.state; 16677 16678 // writefln("%x", sym); 16679 wchar_t[128] charbuf = void; // buffer for XwcLookupString; composed value can consist of many chars! 16680 int charbuflen = 0; // return value of XwcLookupString 16681 if (ke.pressed) { 16682 auto win = e.xkey.window in SimpleWindow.nativeMapping; 16683 if (win !is null && win.xic !is null) { 16684 //{ import core.stdc.stdio : printf; printf("using xic!\n"); } 16685 Status status; 16686 charbuflen = XwcLookupString(win.xic, &e.xkey, charbuf.ptr, cast(int)charbuf.length, &sym, &status); 16687 //{ import core.stdc.stdio : printf; printf("charbuflen=%d\n", charbuflen); } 16688 } else { 16689 //{ import core.stdc.stdio : printf; printf("NOT using xic!\n"); } 16690 // If XIM initialization failed, don't process intl chars. Sorry, boys and girls. 16691 char[16] buffer; 16692 auto res = XLookupString(&e.xkey, buffer.ptr, buffer.length, null, null); 16693 if (res && buffer[0] < 128) charbuf[charbuflen++] = cast(wchar_t)buffer[0]; 16694 } 16695 } 16696 16697 // if there's no char, subst one 16698 if (charbuflen == 0) { 16699 switch (sym) { 16700 case 0xff09: charbuf[charbuflen++] = '\t'; break; 16701 case 0xff8d: // keypad enter 16702 case 0xff0d: charbuf[charbuflen++] = '\n'; break; 16703 default : // ignore 16704 } 16705 } 16706 16707 if (auto win = e.xkey.window in SimpleWindow.nativeMapping) { 16708 ke.window = *win; 16709 16710 16711 if(win.inputProxy) 16712 win = &win.inputProxy; 16713 16714 // char events are separate since they are on Windows too 16715 // also, xcompose can generate long char sequences 16716 // don't send char events if Meta and/or Hyper is pressed 16717 // TODO: ctrl+char should only send control chars; not yet 16718 if ((e.xkey.state&ModifierState.ctrl) != 0) { 16719 if (charbuflen > 1 || charbuf[0] >= ' ') charbuflen = 0; 16720 } 16721 16722 dchar[32] charsComingBuffer; 16723 int charsComingPosition; 16724 dchar[] charsComing = charsComingBuffer[]; 16725 16726 if (ke.pressed && charbuflen > 0) { 16727 // FIXME: I think Windows sends these on releases... we should try to match that, but idk about repeats. 16728 foreach (immutable dchar ch; charbuf[0..charbuflen]) { 16729 if(charsComingPosition >= charsComing.length) 16730 charsComing.length = charsComingPosition + 8; 16731 16732 charsComing[charsComingPosition++] = ch; 16733 } 16734 16735 charsComing = charsComing[0 .. charsComingPosition]; 16736 } else { 16737 charsComing = null; 16738 } 16739 16740 ke.charsPossible = charsComing; 16741 16742 if (win.handleKeyEvent) { 16743 XUnlockDisplay(display); 16744 scope(exit) XLockDisplay(display); 16745 win.handleKeyEvent(ke); 16746 } 16747 16748 // Super and alt modifier keys never actually send the chars, they are assumed to be special. 16749 if ((e.xkey.state&(ModifierState.alt|ModifierState.windows)) == 0 && win.handleCharEvent) { 16750 XUnlockDisplay(display); 16751 scope(exit) XLockDisplay(display); 16752 foreach(ch; charsComing) 16753 win.handleCharEvent(ch); 16754 } 16755 } 16756 16757 version(with_eventloop) 16758 send(ke); 16759 break; 16760 default: 16761 } 16762 16763 return done; 16764 } 16765 } 16766 16767 /* *************************************** */ 16768 /* Done with simpledisplay stuff */ 16769 /* *************************************** */ 16770 16771 // Necessary C library bindings follow 16772 version(Windows) {} else 16773 version(Emscripten) {} else 16774 version(X11) { 16775 16776 extern(C) int eventfd (uint initval, int flags) nothrow @trusted @nogc; 16777 16778 // X11 bindings needed here 16779 /* 16780 A little of this is from the bindings project on 16781 D Source and some of it is copy/paste from the C 16782 header. 16783 16784 The DSource listing consistently used D's long 16785 where C used long. That's wrong - C long is 32 bit, so 16786 it should be int in D. I changed that here. 16787 16788 Note: 16789 This isn't complete, just took what I needed for myself. 16790 */ 16791 16792 import core.stdc.stddef : wchar_t; 16793 16794 interface XLib { 16795 extern(C) nothrow @nogc { 16796 char* XResourceManagerString(Display*); 16797 void XrmInitialize(); 16798 XrmDatabase XrmGetStringDatabase(char* data); 16799 bool XrmGetResource(XrmDatabase, const char*, const char*, char**, XrmValue*); 16800 16801 Cursor XCreateFontCursor(Display*, uint shape); 16802 int XDefineCursor(Display* display, Window w, Cursor cursor); 16803 int XUndefineCursor(Display* display, Window w); 16804 16805 Pixmap XCreateBitmapFromData(Display* display, Drawable d, const(char)* data, uint width, uint height); 16806 Cursor XCreatePixmapCursor(Display* display, Pixmap source, Pixmap mask, XColor* foreground_color, XColor* background_color, uint x, uint y); 16807 int XFreeCursor(Display* display, Cursor cursor); 16808 16809 int XLookupString(XKeyEvent *event_struct, char *buffer_return, int bytes_buffer, KeySym *keysym_return, void *status_in_out); 16810 16811 int XwcLookupString(XIC ic, XKeyPressedEvent* event, wchar_t* buffer_return, int wchars_buffer, KeySym* keysym_return, Status* status_return); 16812 16813 XVaNestedList XVaCreateNestedList(int unused, ...); 16814 16815 char *XKeysymToString(KeySym keysym); 16816 KeySym XKeycodeToKeysym( 16817 Display* /* display */, 16818 KeyCode /* keycode */, 16819 int /* index */ 16820 ); 16821 16822 int XConvertSelection(Display *display, Atom selection, Atom target, Atom property, Window requestor, Time time); 16823 16824 int XFree(void*); 16825 int XDeleteProperty(Display *display, Window w, Atom property); 16826 16827 int XChangeProperty(Display *display, Window w, Atom property, Atom type, int format, int mode, scope const void *data, int nelements); 16828 16829 int XGetWindowProperty(Display *display, Window w, Atom property, arch_long 16830 long_offset, arch_long long_length, Bool del, Atom req_type, Atom 16831 *actual_type_return, int *actual_format_return, arch_ulong 16832 *nitems_return, arch_ulong *bytes_after_return, void** prop_return); 16833 Atom* XListProperties(Display *display, Window w, int *num_prop_return); 16834 Status XGetTextProperty(Display *display, Window w, XTextProperty *text_prop_return, Atom property); 16835 Status XQueryTree(Display *display, Window w, Window *root_return, Window *parent_return, Window **children_return, uint *nchildren_return); 16836 16837 int XSetSelectionOwner(Display *display, Atom selection, Window owner, Time time); 16838 16839 Window XGetSelectionOwner(Display *display, Atom selection); 16840 16841 XVisualInfo* XGetVisualInfo(Display*, c_long, XVisualInfo*, int*); 16842 16843 char** XListFonts(Display*, const char*, int, int*); 16844 void XFreeFontNames(char**); 16845 16846 Display* XOpenDisplay(const char*); 16847 int XCloseDisplay(Display*); 16848 16849 int function() XSynchronize(Display*, bool); 16850 int function() XSetAfterFunction(Display*, int function() proc); 16851 16852 Bool XQueryExtension(Display*, const char*, int*, int*, int*); 16853 16854 Bool XSupportsLocale(); 16855 char* XSetLocaleModifiers(const(char)* modifier_list); 16856 XOM XOpenOM(Display* display, _XrmHashBucketRec* rdb, const(char)* res_name, const(char)* res_class); 16857 Status XCloseOM(XOM om); 16858 16859 XIM XOpenIM(Display* dpy, _XrmHashBucketRec* rdb, const(char)* res_name, const(char)* res_class); 16860 Status XCloseIM(XIM im); 16861 16862 char* XGetIMValues(XIM im, ...) /*_X_SENTINEL(0)*/; 16863 char* XSetIMValues(XIM im, ...) /*_X_SENTINEL(0)*/; 16864 Display* XDisplayOfIM(XIM im); 16865 char* XLocaleOfIM(XIM im); 16866 XIC XCreateIC(XIM im, ...) /*_X_SENTINEL(0)*/; 16867 void XDestroyIC(XIC ic); 16868 void XSetICFocus(XIC ic); 16869 void XUnsetICFocus(XIC ic); 16870 //wchar_t* XwcResetIC(XIC ic); 16871 char* XmbResetIC(XIC ic); 16872 char* Xutf8ResetIC(XIC ic); 16873 char* XSetICValues(XIC ic, ...) /*_X_SENTINEL(0)*/; 16874 char* XGetICValues(XIC ic, ...) /*_X_SENTINEL(0)*/; 16875 XIM XIMOfIC(XIC ic); 16876 16877 uint XSendEvent(Display* display, Window w, Bool propagate, arch_long event_mask, XEvent* event_send); 16878 16879 16880 XFontStruct *XLoadQueryFont(Display *display, scope const char *name); 16881 int XFreeFont(Display *display, XFontStruct *font_struct); 16882 int XSetFont(Display* display, GC gc, Font font); 16883 int XTextWidth(XFontStruct*, scope const char*, int); 16884 16885 int XSetLineAttributes(Display *display, GC gc, uint line_width, int line_style, int cap_style, int join_style); 16886 int XSetDashes(Display *display, GC gc, int dash_offset, scope const byte* dash_list, int n); 16887 16888 Window XCreateSimpleWindow( 16889 Display* /* display */, 16890 Window /* parent */, 16891 int /* x */, 16892 int /* y */, 16893 uint /* width */, 16894 uint /* height */, 16895 uint /* border_width */, 16896 uint /* border */, 16897 uint /* background */ 16898 ); 16899 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); 16900 16901 int XReparentWindow(Display*, Window, Window, int, int); 16902 int XClearWindow(Display*, Window); 16903 int XMoveResizeWindow(Display*, Window, int, int, uint, uint); 16904 int XMoveWindow(Display*, Window, int, int); 16905 int XResizeWindow(Display *display, Window w, uint width, uint height); 16906 16907 Colormap XCreateColormap(Display *display, Window w, Visual *visual, int alloc); 16908 16909 Status XMatchVisualInfo(Display *display, int screen, int depth, int class_, XVisualInfo *vinfo_return); 16910 16911 Status XGetWindowAttributes(Display*, Window, XWindowAttributes*); 16912 16913 XImage *XCreateImage( 16914 Display* /* display */, 16915 Visual* /* visual */, 16916 uint /* depth */, 16917 int /* format */, 16918 int /* offset */, 16919 ubyte* /* data */, 16920 uint /* width */, 16921 uint /* height */, 16922 int /* bitmap_pad */, 16923 int /* bytes_per_line */ 16924 ); 16925 16926 Status XInitImage (XImage* image); 16927 16928 Atom XInternAtom( 16929 Display* /* display */, 16930 const char* /* atom_name */, 16931 Bool /* only_if_exists */ 16932 ); 16933 16934 Status XInternAtoms(Display*, const char**, int, Bool, Atom*); 16935 char* XGetAtomName(Display*, Atom); 16936 Status XGetAtomNames(Display*, Atom*, int count, char**); 16937 16938 int XPutImage( 16939 Display* /* display */, 16940 Drawable /* d */, 16941 GC /* gc */, 16942 XImage* /* image */, 16943 int /* src_x */, 16944 int /* src_y */, 16945 int /* dest_x */, 16946 int /* dest_y */, 16947 uint /* width */, 16948 uint /* height */ 16949 ); 16950 16951 XImage *XGetImage(Display *display, Drawable d, int x, int y, uint width, uint height, c_ulong plane_mask, int format); 16952 16953 16954 int XDestroyWindow( 16955 Display* /* display */, 16956 Window /* w */ 16957 ); 16958 16959 int XDestroyImage(XImage*); 16960 16961 int XSelectInput( 16962 Display* /* display */, 16963 Window /* w */, 16964 EventMask /* event_mask */ 16965 ); 16966 16967 int XMapWindow( 16968 Display* /* display */, 16969 Window /* w */ 16970 ); 16971 16972 Status XIconifyWindow(Display*, Window, int); 16973 int XMapRaised(Display*, Window); 16974 int XMapSubwindows(Display*, Window); 16975 16976 int XNextEvent( 16977 Display* /* display */, 16978 XEvent* /* event_return */ 16979 ); 16980 16981 int XMaskEvent(Display*, arch_long, XEvent*); 16982 16983 Bool XFilterEvent(XEvent *event, Window window); 16984 int XRefreshKeyboardMapping(XMappingEvent *event_map); 16985 16986 Status XSetWMProtocols( 16987 Display* /* display */, 16988 Window /* w */, 16989 Atom* /* protocols */, 16990 int /* count */ 16991 ); 16992 16993 void XSetWMNormalHints(Display *display, Window w, XSizeHints *hints); 16994 Status XGetWMNormalHints(Display *display, Window w, XSizeHints *hints, c_long* supplied_return); 16995 16996 16997 Status XInitThreads(); 16998 void XLockDisplay (Display* display); 16999 void XUnlockDisplay (Display* display); 17000 17001 void XSetWMProperties(Display*, Window, XTextProperty*, XTextProperty*, char**, int, XSizeHints*, XWMHints*, XClassHint*); 17002 17003 int XSetWindowBackground (Display* display, Window w, c_ulong background_pixel); 17004 int XSetWindowBackgroundPixmap (Display* display, Window w, Pixmap background_pixmap); 17005 //int XSetWindowBorder (Display* display, Window w, c_ulong border_pixel); 17006 //int XSetWindowBorderPixmap (Display* display, Window w, Pixmap border_pixmap); 17007 //int XSetWindowBorderWidth (Display* display, Window w, uint width); 17008 17009 17010 // check out Xft too: http://www.keithp.com/~keithp/render/Xft.tutorial 17011 int XDrawString(Display*, Drawable, GC, int, int, scope const char*, int); 17012 int XDrawLine(Display*, Drawable, GC, int, int, int, int); 17013 int XDrawRectangle(Display*, Drawable, GC, int, int, uint, uint); 17014 int XDrawArc(Display*, Drawable, GC, int, int, uint, uint, int, int); 17015 int XFillRectangle(Display*, Drawable, GC, int, int, uint, uint); 17016 int XFillArc(Display*, Drawable, GC, int, int, uint, uint, int, int); 17017 int XDrawPoint(Display*, Drawable, GC, int, int); 17018 int XSetForeground(Display*, GC, uint); 17019 int XSetBackground(Display*, GC, uint); 17020 17021 XFontSet XCreateFontSet(Display*, const char*, char***, int*, char**); 17022 void XFreeFontSet(Display*, XFontSet); 17023 void Xutf8DrawString(Display*, Drawable, XFontSet, GC, int, int, scope const char*, int); 17024 void Xutf8DrawText(Display*, Drawable, GC, int, int, XmbTextItem*, int); 17025 17026 int Xutf8TextExtents(XFontSet font_set, const char *, int num_bytes, XRectangle *overall_ink_return, XRectangle *overall_logical_return); 17027 17028 17029 //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); 17030 17031 void XDrawText(Display*, Drawable, GC, int, int, XTextItem*, int); 17032 int XSetFunction(Display*, GC, int); 17033 17034 GC XCreateGC(Display*, Drawable, uint, void*); 17035 int XCopyGC(Display*, GC, uint, GC); 17036 int XFreeGC(Display*, GC); 17037 17038 bool XCheckWindowEvent(Display*, Window, int, XEvent*); 17039 bool XCheckMaskEvent(Display*, int, XEvent*); 17040 17041 int XPending(Display*); 17042 int XEventsQueued(Display* display, int mode); 17043 17044 Pixmap XCreatePixmap(Display*, Drawable, uint, uint, uint); 17045 int XFreePixmap(Display*, Pixmap); 17046 int XCopyArea(Display*, Drawable, Drawable, GC, int, int, uint, uint, int, int); 17047 int XFlush(Display*); 17048 int XBell(Display*, int); 17049 int XSync(Display*, bool); 17050 17051 int XGrabKey (Display* display, int keycode, uint modifiers, Window grab_window, Bool owner_events, int pointer_mode, int keyboard_mode); 17052 int XUngrabKey (Display* display, int keycode, uint modifiers, Window grab_window); 17053 17054 int XGrabKeyboard(Display*, Window, Bool, int, int, Time); 17055 int XUngrabKeyboard(Display*, Time); 17056 17057 KeyCode XKeysymToKeycode (Display* display, KeySym keysym); 17058 17059 KeySym XStringToKeysym(const char *string); 17060 17061 Bool XCheckTypedEvent(Display *display, int event_type, XEvent *event_return); 17062 17063 Window XDefaultRootWindow(Display*); 17064 17065 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); 17066 17067 int XUngrabButton(Display *display, uint button, uint modifiers, Window grab_window); 17068 17069 int XDrawLines(Display*, Drawable, GC, XPoint*, int, CoordMode); 17070 int XFillPolygon(Display*, Drawable, GC, XPoint*, int, PolygonShape, CoordMode); 17071 17072 Status XAllocColor(Display*, Colormap, XColor*); 17073 17074 int XWithdrawWindow(Display*, Window, int); 17075 int XUnmapWindow(Display*, Window); 17076 int XLowerWindow(Display*, Window); 17077 int XRaiseWindow(Display*, Window); 17078 17079 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); 17080 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); 17081 17082 int XGetInputFocus(Display*, Window*, int*); 17083 int XSetInputFocus(Display*, Window, int, Time); 17084 17085 XErrorHandler XSetErrorHandler(XErrorHandler); 17086 17087 int XGetErrorText(Display*, int, char*, int); 17088 17089 Bool XkbSetDetectableAutoRepeat(Display* dpy, Bool detectable, Bool* supported); 17090 17091 17092 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); 17093 int XUngrabPointer(Display *display, Time time); 17094 int XChangeActivePointerGrab(Display *display, uint event_mask, Cursor cursor, Time time); 17095 17096 int XCopyPlane(Display*, Drawable, Drawable, GC, int, int, uint, uint, int, int, arch_ulong); 17097 17098 Status XGetGeometry(Display*, Drawable, Window*, int*, int*, uint*, uint*, uint*, uint*); 17099 int XSetClipMask(Display*, GC, Pixmap); 17100 int XSetClipOrigin(Display*, GC, int, int); 17101 17102 void XSetClipRectangles(Display*, GC, int, int, XRectangle*, int, int); 17103 17104 void XSetWMName(Display*, Window, XTextProperty*); 17105 Status XGetWMName(Display*, Window, XTextProperty*); 17106 int XStoreName(Display* display, Window w, const(char)* window_name); 17107 17108 XIOErrorHandler XSetIOErrorHandler (XIOErrorHandler handler); 17109 17110 } 17111 } 17112 17113 interface Xext { 17114 extern(C) nothrow @nogc { 17115 Status XShmAttach(Display*, XShmSegmentInfo*); 17116 Status XShmDetach(Display*, XShmSegmentInfo*); 17117 Status XShmPutImage( 17118 Display* /* dpy */, 17119 Drawable /* d */, 17120 GC /* gc */, 17121 XImage* /* image */, 17122 int /* src_x */, 17123 int /* src_y */, 17124 int /* dst_x */, 17125 int /* dst_y */, 17126 uint /* src_width */, 17127 uint /* src_height */, 17128 Bool /* send_event */ 17129 ); 17130 17131 Status XShmQueryExtension(Display*); 17132 17133 XImage *XShmCreateImage( 17134 Display* /* dpy */, 17135 Visual* /* visual */, 17136 uint /* depth */, 17137 int /* format */, 17138 char* /* data */, 17139 XShmSegmentInfo* /* shminfo */, 17140 uint /* width */, 17141 uint /* height */ 17142 ); 17143 17144 Pixmap XShmCreatePixmap( 17145 Display* /* dpy */, 17146 Drawable /* d */, 17147 char* /* data */, 17148 XShmSegmentInfo* /* shminfo */, 17149 uint /* width */, 17150 uint /* height */, 17151 uint /* depth */ 17152 ); 17153 17154 } 17155 } 17156 17157 // this requires -lXpm 17158 //int XpmCreatePixmapFromData(Display*, Drawable, scope const char**, Pixmap*, Pixmap*, void*); // FIXME: void* should be XpmAttributes 17159 17160 17161 mixin DynamicLoad!(XLib, "X11", 6, librariesSuccessfullyLoaded) xlib; 17162 mixin DynamicLoad!(Xext, "Xext", 6, librariesSuccessfullyLoaded) xext; 17163 shared static this() { 17164 xlib.loadDynamicLibrary(); 17165 xext.loadDynamicLibrary(); 17166 } 17167 17168 17169 extern(C) nothrow @nogc { 17170 17171 alias XrmDatabase = void*; 17172 struct XrmValue { 17173 uint size; 17174 void* addr; 17175 } 17176 17177 struct XVisualInfo { 17178 Visual* visual; 17179 VisualID visualid; 17180 int screen; 17181 uint depth; 17182 int c_class; 17183 c_ulong red_mask; 17184 c_ulong green_mask; 17185 c_ulong blue_mask; 17186 int colormap_size; 17187 int bits_per_rgb; 17188 } 17189 17190 enum VisualNoMask= 0x0; 17191 enum VisualIDMask= 0x1; 17192 enum VisualScreenMask=0x2; 17193 enum VisualDepthMask= 0x4; 17194 enum VisualClassMask= 0x8; 17195 enum VisualRedMaskMask=0x10; 17196 enum VisualGreenMaskMask=0x20; 17197 enum VisualBlueMaskMask=0x40; 17198 enum VisualColormapSizeMask=0x80; 17199 enum VisualBitsPerRGBMask=0x100; 17200 enum VisualAllMask= 0x1FF; 17201 17202 enum AnyKey = 0; 17203 enum AnyModifier = 1 << 15; 17204 17205 // XIM and other crap 17206 struct _XOM {} 17207 struct _XIM {} 17208 struct _XIC {} 17209 alias XOM = _XOM*; 17210 alias XIM = _XIM*; 17211 alias XIC = _XIC*; 17212 17213 alias XVaNestedList = void*; 17214 17215 alias XIMStyle = arch_ulong; 17216 enum : arch_ulong { 17217 XIMPreeditArea = 0x0001, 17218 XIMPreeditCallbacks = 0x0002, 17219 XIMPreeditPosition = 0x0004, 17220 XIMPreeditNothing = 0x0008, 17221 XIMPreeditNone = 0x0010, 17222 XIMStatusArea = 0x0100, 17223 XIMStatusCallbacks = 0x0200, 17224 XIMStatusNothing = 0x0400, 17225 XIMStatusNone = 0x0800, 17226 } 17227 17228 17229 /* X Shared Memory Extension functions */ 17230 //pragma(lib, "Xshm"); 17231 alias arch_ulong ShmSeg; 17232 struct XShmSegmentInfo { 17233 ShmSeg shmseg; 17234 int shmid; 17235 ubyte* shmaddr; 17236 Bool readOnly; 17237 } 17238 17239 // and the necessary OS functions 17240 int shmget(int, size_t, int); 17241 void* shmat(int, scope const void*, int); 17242 int shmdt(scope const void*); 17243 int shmctl (int shmid, int cmd, void* ptr /*struct shmid_ds *buf*/); 17244 17245 enum IPC_PRIVATE = 0; 17246 enum IPC_CREAT = 512; 17247 enum IPC_RMID = 0; 17248 17249 /* MIT-SHM end */ 17250 17251 17252 enum MappingType:int { 17253 MappingModifier =0, 17254 MappingKeyboard =1, 17255 MappingPointer =2 17256 } 17257 17258 /* ImageFormat -- PutImage, GetImage */ 17259 enum ImageFormat:int { 17260 XYBitmap =0, /* depth 1, XYFormat */ 17261 XYPixmap =1, /* depth == drawable depth */ 17262 ZPixmap =2 /* depth == drawable depth */ 17263 } 17264 17265 enum ModifierName:int { 17266 ShiftMapIndex =0, 17267 LockMapIndex =1, 17268 ControlMapIndex =2, 17269 Mod1MapIndex =3, 17270 Mod2MapIndex =4, 17271 Mod3MapIndex =5, 17272 Mod4MapIndex =6, 17273 Mod5MapIndex =7 17274 } 17275 17276 enum ButtonMask:int { 17277 Button1Mask =1<<8, 17278 Button2Mask =1<<9, 17279 Button3Mask =1<<10, 17280 Button4Mask =1<<11, 17281 Button5Mask =1<<12, 17282 AnyModifier =1<<15/* used in GrabButton, GrabKey */ 17283 } 17284 17285 enum KeyOrButtonMask:uint { 17286 ShiftMask =1<<0, 17287 LockMask =1<<1, 17288 ControlMask =1<<2, 17289 Mod1Mask =1<<3, 17290 Mod2Mask =1<<4, 17291 Mod3Mask =1<<5, 17292 Mod4Mask =1<<6, 17293 Mod5Mask =1<<7, 17294 Button1Mask =1<<8, 17295 Button2Mask =1<<9, 17296 Button3Mask =1<<10, 17297 Button4Mask =1<<11, 17298 Button5Mask =1<<12, 17299 AnyModifier =1<<15/* used in GrabButton, GrabKey */ 17300 } 17301 17302 enum ButtonName:int { 17303 Button1 =1, 17304 Button2 =2, 17305 Button3 =3, 17306 Button4 =4, 17307 Button5 =5 17308 } 17309 17310 /* Notify modes */ 17311 enum NotifyModes:int 17312 { 17313 NotifyNormal =0, 17314 NotifyGrab =1, 17315 NotifyUngrab =2, 17316 NotifyWhileGrabbed =3 17317 } 17318 enum NotifyHint = 1; /* for MotionNotify events */ 17319 17320 /* Notify detail */ 17321 enum NotifyDetail:int 17322 { 17323 NotifyAncestor =0, 17324 NotifyVirtual =1, 17325 NotifyInferior =2, 17326 NotifyNonlinear =3, 17327 NotifyNonlinearVirtual =4, 17328 NotifyPointer =5, 17329 NotifyPointerRoot =6, 17330 NotifyDetailNone =7 17331 } 17332 17333 /* Visibility notify */ 17334 17335 enum VisibilityNotify:int 17336 { 17337 VisibilityUnobscured =0, 17338 VisibilityPartiallyObscured =1, 17339 VisibilityFullyObscured =2 17340 } 17341 17342 17343 enum WindowStackingMethod:int 17344 { 17345 Above =0, 17346 Below =1, 17347 TopIf =2, 17348 BottomIf =3, 17349 Opposite =4 17350 } 17351 17352 /* Circulation request */ 17353 enum CirculationRequest:int 17354 { 17355 PlaceOnTop =0, 17356 PlaceOnBottom =1 17357 } 17358 17359 enum PropertyNotification:int 17360 { 17361 PropertyNewValue =0, 17362 PropertyDelete =1 17363 } 17364 17365 enum ColorMapNotification:int 17366 { 17367 ColormapUninstalled =0, 17368 ColormapInstalled =1 17369 } 17370 17371 17372 struct _XPrivate {} 17373 struct _XrmHashBucketRec {} 17374 17375 alias void* XPointer; 17376 alias void* XExtData; 17377 17378 version( X86_64 ) { 17379 alias ulong XID; 17380 alias ulong arch_ulong; 17381 alias long arch_long; 17382 } else version (AArch64) { 17383 alias ulong XID; 17384 alias ulong arch_ulong; 17385 alias long arch_long; 17386 } else { 17387 alias uint XID; 17388 alias uint arch_ulong; 17389 alias int arch_long; 17390 } 17391 17392 alias XID Window; 17393 alias XID Drawable; 17394 alias XID Pixmap; 17395 17396 alias arch_ulong Atom; 17397 alias int Bool; 17398 alias Display XDisplay; 17399 17400 alias int ByteOrder; 17401 alias arch_ulong Time; 17402 alias void ScreenFormat; 17403 17404 struct XImage { 17405 int width, height; /* size of image */ 17406 int xoffset; /* number of pixels offset in X direction */ 17407 ImageFormat format; /* XYBitmap, XYPixmap, ZPixmap */ 17408 void *data; /* pointer to image data */ 17409 ByteOrder byte_order; /* data byte order, LSBFirst, MSBFirst */ 17410 int bitmap_unit; /* quant. of scanline 8, 16, 32 */ 17411 int bitmap_bit_order; /* LSBFirst, MSBFirst */ 17412 int bitmap_pad; /* 8, 16, 32 either XY or ZPixmap */ 17413 int depth; /* depth of image */ 17414 int bytes_per_line; /* accelarator to next line */ 17415 int bits_per_pixel; /* bits per pixel (ZPixmap) */ 17416 arch_ulong red_mask; /* bits in z arrangment */ 17417 arch_ulong green_mask; 17418 arch_ulong blue_mask; 17419 XPointer obdata; /* hook for the object routines to hang on */ 17420 static struct F { /* image manipulation routines */ 17421 XImage* function( 17422 XDisplay* /* display */, 17423 Visual* /* visual */, 17424 uint /* depth */, 17425 int /* format */, 17426 int /* offset */, 17427 ubyte* /* data */, 17428 uint /* width */, 17429 uint /* height */, 17430 int /* bitmap_pad */, 17431 int /* bytes_per_line */) create_image; 17432 int function(XImage *) destroy_image; 17433 arch_ulong function(XImage *, int, int) get_pixel; 17434 int function(XImage *, int, int, arch_ulong) put_pixel; 17435 XImage* function(XImage *, int, int, uint, uint) sub_image; 17436 int function(XImage *, arch_long) add_pixel; 17437 } 17438 F f; 17439 } 17440 version(X86_64) static assert(XImage.sizeof == 136); 17441 else version(X86) static assert(XImage.sizeof == 88); 17442 17443 struct XCharStruct { 17444 short lbearing; /* origin to left edge of raster */ 17445 short rbearing; /* origin to right edge of raster */ 17446 short width; /* advance to next char's origin */ 17447 short ascent; /* baseline to top edge of raster */ 17448 short descent; /* baseline to bottom edge of raster */ 17449 ushort attributes; /* per char flags (not predefined) */ 17450 } 17451 17452 /* 17453 * To allow arbitrary information with fonts, there are additional properties 17454 * returned. 17455 */ 17456 struct XFontProp { 17457 Atom name; 17458 arch_ulong card32; 17459 } 17460 17461 alias Atom Font; 17462 17463 struct XFontStruct { 17464 XExtData *ext_data; /* Hook for extension to hang data */ 17465 Font fid; /* Font ID for this font */ 17466 uint direction; /* Direction the font is painted */ 17467 uint min_char_or_byte2; /* First character */ 17468 uint max_char_or_byte2; /* Last character */ 17469 uint min_byte1; /* First row that exists (for two-byte fonts) */ 17470 uint max_byte1; /* Last row that exists (for two-byte fonts) */ 17471 Bool all_chars_exist; /* Flag if all characters have nonzero size */ 17472 uint default_char; /* Char to print for undefined character */ 17473 int n_properties; /* How many properties there are */ 17474 XFontProp *properties; /* Pointer to array of additional properties*/ 17475 XCharStruct min_bounds; /* Minimum bounds over all existing char*/ 17476 XCharStruct max_bounds; /* Maximum bounds over all existing char*/ 17477 XCharStruct *per_char; /* first_char to last_char information */ 17478 int ascent; /* Max extent above baseline for spacing */ 17479 int descent; /* Max descent below baseline for spacing */ 17480 } 17481 17482 17483 /* 17484 * Definitions of specific events. 17485 */ 17486 struct XKeyEvent 17487 { 17488 int type; /* of event */ 17489 arch_ulong serial; /* # of last request processed by server */ 17490 Bool send_event; /* true if this came from a SendEvent request */ 17491 Display *display; /* Display the event was read from */ 17492 Window window; /* "event" window it is reported relative to */ 17493 Window root; /* root window that the event occurred on */ 17494 Window subwindow; /* child window */ 17495 Time time; /* milliseconds */ 17496 int x, y; /* pointer x, y coordinates in event window */ 17497 int x_root, y_root; /* coordinates relative to root */ 17498 KeyOrButtonMask state; /* key or button mask */ 17499 uint keycode; /* detail */ 17500 Bool same_screen; /* same screen flag */ 17501 } 17502 version(X86_64) static assert(XKeyEvent.sizeof == 96); 17503 alias XKeyEvent XKeyPressedEvent; 17504 alias XKeyEvent XKeyReleasedEvent; 17505 17506 struct XButtonEvent 17507 { 17508 int type; /* of event */ 17509 arch_ulong serial; /* # of last request processed by server */ 17510 Bool send_event; /* true if this came from a SendEvent request */ 17511 Display *display; /* Display the event was read from */ 17512 Window window; /* "event" window it is reported relative to */ 17513 Window root; /* root window that the event occurred on */ 17514 Window subwindow; /* child window */ 17515 Time time; /* milliseconds */ 17516 int x, y; /* pointer x, y coordinates in event window */ 17517 int x_root, y_root; /* coordinates relative to root */ 17518 KeyOrButtonMask state; /* key or button mask */ 17519 uint button; /* detail */ 17520 Bool same_screen; /* same screen flag */ 17521 } 17522 alias XButtonEvent XButtonPressedEvent; 17523 alias XButtonEvent XButtonReleasedEvent; 17524 17525 struct XMotionEvent{ 17526 int type; /* of event */ 17527 arch_ulong serial; /* # of last request processed by server */ 17528 Bool send_event; /* true if this came from a SendEvent request */ 17529 Display *display; /* Display the event was read from */ 17530 Window window; /* "event" window reported relative to */ 17531 Window root; /* root window that the event occurred on */ 17532 Window subwindow; /* child window */ 17533 Time time; /* milliseconds */ 17534 int x, y; /* pointer x, y coordinates in event window */ 17535 int x_root, y_root; /* coordinates relative to root */ 17536 KeyOrButtonMask state; /* key or button mask */ 17537 byte is_hint; /* detail */ 17538 Bool same_screen; /* same screen flag */ 17539 } 17540 alias XMotionEvent XPointerMovedEvent; 17541 17542 struct XCrossingEvent{ 17543 int type; /* of event */ 17544 arch_ulong serial; /* # of last request processed by server */ 17545 Bool send_event; /* true if this came from a SendEvent request */ 17546 Display *display; /* Display the event was read from */ 17547 Window window; /* "event" window reported relative to */ 17548 Window root; /* root window that the event occurred on */ 17549 Window subwindow; /* child window */ 17550 Time time; /* milliseconds */ 17551 int x, y; /* pointer x, y coordinates in event window */ 17552 int x_root, y_root; /* coordinates relative to root */ 17553 NotifyModes mode; /* NotifyNormal, NotifyGrab, NotifyUngrab */ 17554 NotifyDetail detail; 17555 /* 17556 * NotifyAncestor, NotifyVirtual, NotifyInferior, 17557 * NotifyNonlinear,NotifyNonlinearVirtual 17558 */ 17559 Bool same_screen; /* same screen flag */ 17560 Bool focus; /* Boolean focus */ 17561 KeyOrButtonMask state; /* key or button mask */ 17562 } 17563 alias XCrossingEvent XEnterWindowEvent; 17564 alias XCrossingEvent XLeaveWindowEvent; 17565 17566 struct XFocusChangeEvent{ 17567 int type; /* FocusIn or FocusOut */ 17568 arch_ulong serial; /* # of last request processed by server */ 17569 Bool send_event; /* true if this came from a SendEvent request */ 17570 Display *display; /* Display the event was read from */ 17571 Window window; /* window of event */ 17572 NotifyModes mode; /* NotifyNormal, NotifyWhileGrabbed, 17573 NotifyGrab, NotifyUngrab */ 17574 NotifyDetail detail; 17575 /* 17576 * NotifyAncestor, NotifyVirtual, NotifyInferior, 17577 * NotifyNonlinear,NotifyNonlinearVirtual, NotifyPointer, 17578 * NotifyPointerRoot, NotifyDetailNone 17579 */ 17580 } 17581 alias XFocusChangeEvent XFocusInEvent; 17582 alias XFocusChangeEvent XFocusOutEvent; 17583 17584 enum CWBackPixmap = (1L<<0); 17585 enum CWBackPixel = (1L<<1); 17586 enum CWBorderPixmap = (1L<<2); 17587 enum CWBorderPixel = (1L<<3); 17588 enum CWBitGravity = (1L<<4); 17589 enum CWWinGravity = (1L<<5); 17590 enum CWBackingStore = (1L<<6); 17591 enum CWBackingPlanes = (1L<<7); 17592 enum CWBackingPixel = (1L<<8); 17593 enum CWOverrideRedirect = (1L<<9); 17594 enum CWSaveUnder = (1L<<10); 17595 enum CWEventMask = (1L<<11); 17596 enum CWDontPropagate = (1L<<12); 17597 enum CWColormap = (1L<<13); 17598 enum CWCursor = (1L<<14); 17599 17600 struct XWindowAttributes { 17601 int x, y; /* location of window */ 17602 int width, height; /* width and height of window */ 17603 int border_width; /* border width of window */ 17604 int depth; /* depth of window */ 17605 Visual *visual; /* the associated visual structure */ 17606 Window root; /* root of screen containing window */ 17607 int class_; /* InputOutput, InputOnly*/ 17608 int bit_gravity; /* one of the bit gravity values */ 17609 int win_gravity; /* one of the window gravity values */ 17610 int backing_store; /* NotUseful, WhenMapped, Always */ 17611 arch_ulong backing_planes; /* planes to be preserved if possible */ 17612 arch_ulong backing_pixel; /* value to be used when restoring planes */ 17613 Bool save_under; /* boolean, should bits under be saved? */ 17614 Colormap colormap; /* color map to be associated with window */ 17615 Bool map_installed; /* boolean, is color map currently installed*/ 17616 int map_state; /* IsUnmapped, IsUnviewable, IsViewable */ 17617 arch_long all_event_masks; /* set of events all people have interest in*/ 17618 arch_long your_event_mask; /* my event mask */ 17619 arch_long do_not_propagate_mask; /* set of events that should not propagate */ 17620 Bool override_redirect; /* boolean value for override-redirect */ 17621 Screen *screen; /* back pointer to correct screen */ 17622 } 17623 17624 enum IsUnmapped = 0; 17625 enum IsUnviewable = 1; 17626 enum IsViewable = 2; 17627 17628 struct XSetWindowAttributes { 17629 Pixmap background_pixmap;/* background, None, or ParentRelative */ 17630 arch_ulong background_pixel;/* background pixel */ 17631 Pixmap border_pixmap; /* border of the window or CopyFromParent */ 17632 arch_ulong border_pixel;/* border pixel value */ 17633 int bit_gravity; /* one of bit gravity values */ 17634 int win_gravity; /* one of the window gravity values */ 17635 int backing_store; /* NotUseful, WhenMapped, Always */ 17636 arch_ulong backing_planes;/* planes to be preserved if possible */ 17637 arch_ulong backing_pixel;/* value to use in restoring planes */ 17638 Bool save_under; /* should bits under be saved? (popups) */ 17639 arch_long event_mask; /* set of events that should be saved */ 17640 arch_long do_not_propagate_mask;/* set of events that should not propagate */ 17641 Bool override_redirect; /* boolean value for override_redirect */ 17642 Colormap colormap; /* color map to be associated with window */ 17643 Cursor cursor; /* cursor to be displayed (or None) */ 17644 } 17645 17646 17647 alias int Status; 17648 17649 17650 enum EventMask:int 17651 { 17652 NoEventMask =0, 17653 KeyPressMask =1<<0, 17654 KeyReleaseMask =1<<1, 17655 ButtonPressMask =1<<2, 17656 ButtonReleaseMask =1<<3, 17657 EnterWindowMask =1<<4, 17658 LeaveWindowMask =1<<5, 17659 PointerMotionMask =1<<6, 17660 PointerMotionHintMask =1<<7, 17661 Button1MotionMask =1<<8, 17662 Button2MotionMask =1<<9, 17663 Button3MotionMask =1<<10, 17664 Button4MotionMask =1<<11, 17665 Button5MotionMask =1<<12, 17666 ButtonMotionMask =1<<13, 17667 KeymapStateMask =1<<14, 17668 ExposureMask =1<<15, 17669 VisibilityChangeMask =1<<16, 17670 StructureNotifyMask =1<<17, 17671 ResizeRedirectMask =1<<18, 17672 SubstructureNotifyMask =1<<19, 17673 SubstructureRedirectMask=1<<20, 17674 FocusChangeMask =1<<21, 17675 PropertyChangeMask =1<<22, 17676 ColormapChangeMask =1<<23, 17677 OwnerGrabButtonMask =1<<24 17678 } 17679 17680 struct MwmHints { 17681 c_ulong flags; 17682 c_ulong functions; 17683 c_ulong decorations; 17684 c_long input_mode; 17685 c_ulong status; 17686 } 17687 17688 enum { 17689 MWM_HINTS_FUNCTIONS = (1L << 0), 17690 MWM_HINTS_DECORATIONS = (1L << 1), 17691 17692 MWM_FUNC_ALL = (1L << 0), 17693 MWM_FUNC_RESIZE = (1L << 1), 17694 MWM_FUNC_MOVE = (1L << 2), 17695 MWM_FUNC_MINIMIZE = (1L << 3), 17696 MWM_FUNC_MAXIMIZE = (1L << 4), 17697 MWM_FUNC_CLOSE = (1L << 5), 17698 17699 MWM_DECOR_ALL = (1L << 0), 17700 MWM_DECOR_BORDER = (1L << 1), 17701 MWM_DECOR_RESIZEH = (1L << 2), 17702 MWM_DECOR_TITLE = (1L << 3), 17703 MWM_DECOR_MENU = (1L << 4), 17704 MWM_DECOR_MINIMIZE = (1L << 5), 17705 MWM_DECOR_MAXIMIZE = (1L << 6), 17706 } 17707 17708 import core.stdc.config : c_long, c_ulong; 17709 17710 /* Size hints mask bits */ 17711 17712 enum USPosition = (1L << 0) /* user specified x, y */; 17713 enum USSize = (1L << 1) /* user specified width, height */; 17714 enum PPosition = (1L << 2) /* program specified position */; 17715 enum PSize = (1L << 3) /* program specified size */; 17716 enum PMinSize = (1L << 4) /* program specified minimum size */; 17717 enum PMaxSize = (1L << 5) /* program specified maximum size */; 17718 enum PResizeInc = (1L << 6) /* program specified resize increments */; 17719 enum PAspect = (1L << 7) /* program specified min and max aspect ratios */; 17720 enum PBaseSize = (1L << 8); 17721 enum PWinGravity = (1L << 9); 17722 enum PAllHints = (PPosition|PSize| PMinSize|PMaxSize| PResizeInc|PAspect); 17723 struct XSizeHints { 17724 arch_long flags; /* marks which fields in this structure are defined */ 17725 int x, y; /* Obsolete */ 17726 int width, height; /* Obsolete */ 17727 int min_width, min_height; 17728 int max_width, max_height; 17729 int width_inc, height_inc; 17730 struct Aspect { 17731 int x; /* numerator */ 17732 int y; /* denominator */ 17733 } 17734 17735 Aspect min_aspect; 17736 Aspect max_aspect; 17737 int base_width, base_height; 17738 int win_gravity; 17739 /* this structure may be extended in the future */ 17740 } 17741 17742 17743 17744 enum EventType:int 17745 { 17746 KeyPress =2, 17747 KeyRelease =3, 17748 ButtonPress =4, 17749 ButtonRelease =5, 17750 MotionNotify =6, 17751 EnterNotify =7, 17752 LeaveNotify =8, 17753 FocusIn =9, 17754 FocusOut =10, 17755 KeymapNotify =11, 17756 Expose =12, 17757 GraphicsExpose =13, 17758 NoExpose =14, 17759 VisibilityNotify =15, 17760 CreateNotify =16, 17761 DestroyNotify =17, 17762 UnmapNotify =18, 17763 MapNotify =19, 17764 MapRequest =20, 17765 ReparentNotify =21, 17766 ConfigureNotify =22, 17767 ConfigureRequest =23, 17768 GravityNotify =24, 17769 ResizeRequest =25, 17770 CirculateNotify =26, 17771 CirculateRequest =27, 17772 PropertyNotify =28, 17773 SelectionClear =29, 17774 SelectionRequest =30, 17775 SelectionNotify =31, 17776 ColormapNotify =32, 17777 ClientMessage =33, 17778 MappingNotify =34, 17779 LASTEvent =35 /* must be bigger than any event # */ 17780 } 17781 /* generated on EnterWindow and FocusIn when KeyMapState selected */ 17782 struct XKeymapEvent 17783 { 17784 int type; 17785 arch_ulong serial; /* # of last request processed by server */ 17786 Bool send_event; /* true if this came from a SendEvent request */ 17787 Display *display; /* Display the event was read from */ 17788 Window window; 17789 byte[32] key_vector; 17790 } 17791 17792 struct XExposeEvent 17793 { 17794 int type; 17795 arch_ulong serial; /* # of last request processed by server */ 17796 Bool send_event; /* true if this came from a SendEvent request */ 17797 Display *display; /* Display the event was read from */ 17798 Window window; 17799 int x, y; 17800 int width, height; 17801 int count; /* if non-zero, at least this many more */ 17802 } 17803 17804 struct XGraphicsExposeEvent{ 17805 int type; 17806 arch_ulong serial; /* # of last request processed by server */ 17807 Bool send_event; /* true if this came from a SendEvent request */ 17808 Display *display; /* Display the event was read from */ 17809 Drawable drawable; 17810 int x, y; 17811 int width, height; 17812 int count; /* if non-zero, at least this many more */ 17813 int major_code; /* core is CopyArea or CopyPlane */ 17814 int minor_code; /* not defined in the core */ 17815 } 17816 17817 struct XNoExposeEvent{ 17818 int type; 17819 arch_ulong serial; /* # of last request processed by server */ 17820 Bool send_event; /* true if this came from a SendEvent request */ 17821 Display *display; /* Display the event was read from */ 17822 Drawable drawable; 17823 int major_code; /* core is CopyArea or CopyPlane */ 17824 int minor_code; /* not defined in the core */ 17825 } 17826 17827 struct XVisibilityEvent{ 17828 int type; 17829 arch_ulong serial; /* # of last request processed by server */ 17830 Bool send_event; /* true if this came from a SendEvent request */ 17831 Display *display; /* Display the event was read from */ 17832 Window window; 17833 VisibilityNotify state; /* Visibility state */ 17834 } 17835 17836 struct XCreateWindowEvent{ 17837 int type; 17838 arch_ulong serial; /* # of last request processed by server */ 17839 Bool send_event; /* true if this came from a SendEvent request */ 17840 Display *display; /* Display the event was read from */ 17841 Window parent; /* parent of the window */ 17842 Window window; /* window id of window created */ 17843 int x, y; /* window location */ 17844 int width, height; /* size of window */ 17845 int border_width; /* border width */ 17846 Bool override_redirect; /* creation should be overridden */ 17847 } 17848 17849 struct XDestroyWindowEvent 17850 { 17851 int type; 17852 arch_ulong serial; /* # of last request processed by server */ 17853 Bool send_event; /* true if this came from a SendEvent request */ 17854 Display *display; /* Display the event was read from */ 17855 Window event; 17856 Window window; 17857 } 17858 17859 struct XUnmapEvent 17860 { 17861 int type; 17862 arch_ulong serial; /* # of last request processed by server */ 17863 Bool send_event; /* true if this came from a SendEvent request */ 17864 Display *display; /* Display the event was read from */ 17865 Window event; 17866 Window window; 17867 Bool from_configure; 17868 } 17869 17870 struct XMapEvent 17871 { 17872 int type; 17873 arch_ulong serial; /* # of last request processed by server */ 17874 Bool send_event; /* true if this came from a SendEvent request */ 17875 Display *display; /* Display the event was read from */ 17876 Window event; 17877 Window window; 17878 Bool override_redirect; /* Boolean, is override set... */ 17879 } 17880 17881 struct XMapRequestEvent 17882 { 17883 int type; 17884 arch_ulong serial; /* # of last request processed by server */ 17885 Bool send_event; /* true if this came from a SendEvent request */ 17886 Display *display; /* Display the event was read from */ 17887 Window parent; 17888 Window window; 17889 } 17890 17891 struct XReparentEvent 17892 { 17893 int type; 17894 arch_ulong serial; /* # of last request processed by server */ 17895 Bool send_event; /* true if this came from a SendEvent request */ 17896 Display *display; /* Display the event was read from */ 17897 Window event; 17898 Window window; 17899 Window parent; 17900 int x, y; 17901 Bool override_redirect; 17902 } 17903 17904 struct XConfigureEvent 17905 { 17906 int type; 17907 arch_ulong serial; /* # of last request processed by server */ 17908 Bool send_event; /* true if this came from a SendEvent request */ 17909 Display *display; /* Display the event was read from */ 17910 Window event; 17911 Window window; 17912 int x, y; 17913 int width, height; 17914 int border_width; 17915 Window above; 17916 Bool override_redirect; 17917 } 17918 17919 struct XGravityEvent 17920 { 17921 int type; 17922 arch_ulong serial; /* # of last request processed by server */ 17923 Bool send_event; /* true if this came from a SendEvent request */ 17924 Display *display; /* Display the event was read from */ 17925 Window event; 17926 Window window; 17927 int x, y; 17928 } 17929 17930 struct XResizeRequestEvent 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 window; 17937 int width, height; 17938 } 17939 17940 struct XConfigureRequestEvent 17941 { 17942 int type; 17943 arch_ulong serial; /* # of last request processed by server */ 17944 Bool send_event; /* true if this came from a SendEvent request */ 17945 Display *display; /* Display the event was read from */ 17946 Window parent; 17947 Window window; 17948 int x, y; 17949 int width, height; 17950 int border_width; 17951 Window above; 17952 WindowStackingMethod detail; /* Above, Below, TopIf, BottomIf, Opposite */ 17953 arch_ulong value_mask; 17954 } 17955 17956 struct XCirculateEvent 17957 { 17958 int type; 17959 arch_ulong serial; /* # of last request processed by server */ 17960 Bool send_event; /* true if this came from a SendEvent request */ 17961 Display *display; /* Display the event was read from */ 17962 Window event; 17963 Window window; 17964 CirculationRequest place; /* PlaceOnTop, PlaceOnBottom */ 17965 } 17966 17967 struct XCirculateRequestEvent 17968 { 17969 int type; 17970 arch_ulong serial; /* # of last request processed by server */ 17971 Bool send_event; /* true if this came from a SendEvent request */ 17972 Display *display; /* Display the event was read from */ 17973 Window parent; 17974 Window window; 17975 CirculationRequest place; /* PlaceOnTop, PlaceOnBottom */ 17976 } 17977 17978 struct XPropertyEvent 17979 { 17980 int type; 17981 arch_ulong serial; /* # of last request processed by server */ 17982 Bool send_event; /* true if this came from a SendEvent request */ 17983 Display *display; /* Display the event was read from */ 17984 Window window; 17985 Atom atom; 17986 Time time; 17987 PropertyNotification state; /* NewValue, Deleted */ 17988 } 17989 17990 struct XSelectionClearEvent 17991 { 17992 int type; 17993 arch_ulong serial; /* # of last request processed by server */ 17994 Bool send_event; /* true if this came from a SendEvent request */ 17995 Display *display; /* Display the event was read from */ 17996 Window window; 17997 Atom selection; 17998 Time time; 17999 } 18000 18001 struct XSelectionRequestEvent 18002 { 18003 int type; 18004 arch_ulong serial; /* # of last request processed by server */ 18005 Bool send_event; /* true if this came from a SendEvent request */ 18006 Display *display; /* Display the event was read from */ 18007 Window owner; 18008 Window requestor; 18009 Atom selection; 18010 Atom target; 18011 Atom property; 18012 Time time; 18013 } 18014 18015 struct XSelectionEvent 18016 { 18017 int type; 18018 arch_ulong serial; /* # of last request processed by server */ 18019 Bool send_event; /* true if this came from a SendEvent request */ 18020 Display *display; /* Display the event was read from */ 18021 Window requestor; 18022 Atom selection; 18023 Atom target; 18024 Atom property; /* ATOM or None */ 18025 Time time; 18026 } 18027 version(X86_64) static assert(XSelectionClearEvent.sizeof == 56); 18028 18029 struct XColormapEvent 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 Colormap colormap; /* COLORMAP or None */ 18037 Bool new_; /* C++ */ 18038 ColorMapNotification state; /* ColormapInstalled, ColormapUninstalled */ 18039 } 18040 version(X86_64) static assert(XColormapEvent.sizeof == 56); 18041 18042 struct XClientMessageEvent 18043 { 18044 int type; 18045 arch_ulong serial; /* # of last request processed by server */ 18046 Bool send_event; /* true if this came from a SendEvent request */ 18047 Display *display; /* Display the event was read from */ 18048 Window window; 18049 Atom message_type; 18050 int format; 18051 union Data{ 18052 byte[20] b; 18053 short[10] s; 18054 arch_ulong[5] l; 18055 } 18056 Data data; 18057 18058 } 18059 version(X86_64) static assert(XClientMessageEvent.sizeof == 96); 18060 18061 struct XMappingEvent 18062 { 18063 int type; 18064 arch_ulong serial; /* # of last request processed by server */ 18065 Bool send_event; /* true if this came from a SendEvent request */ 18066 Display *display; /* Display the event was read from */ 18067 Window window; /* unused */ 18068 MappingType request; /* one of MappingModifier, MappingKeyboard, 18069 MappingPointer */ 18070 int first_keycode; /* first keycode */ 18071 int count; /* defines range of change w. first_keycode*/ 18072 } 18073 18074 struct XErrorEvent 18075 { 18076 int type; 18077 Display *display; /* Display the event was read from */ 18078 XID resourceid; /* resource id */ 18079 arch_ulong serial; /* serial number of failed request */ 18080 ubyte error_code; /* error code of failed request */ 18081 ubyte request_code; /* Major op-code of failed request */ 18082 ubyte minor_code; /* Minor op-code of failed request */ 18083 } 18084 18085 struct XAnyEvent 18086 { 18087 int type; 18088 arch_ulong serial; /* # of last request processed by server */ 18089 Bool send_event; /* true if this came from a SendEvent request */ 18090 Display *display;/* Display the event was read from */ 18091 Window window; /* window on which event was requested in event mask */ 18092 } 18093 18094 union XEvent{ 18095 int type; /* must not be changed; first element */ 18096 XAnyEvent xany; 18097 XKeyEvent xkey; 18098 XButtonEvent xbutton; 18099 XMotionEvent xmotion; 18100 XCrossingEvent xcrossing; 18101 XFocusChangeEvent xfocus; 18102 XExposeEvent xexpose; 18103 XGraphicsExposeEvent xgraphicsexpose; 18104 XNoExposeEvent xnoexpose; 18105 XVisibilityEvent xvisibility; 18106 XCreateWindowEvent xcreatewindow; 18107 XDestroyWindowEvent xdestroywindow; 18108 XUnmapEvent xunmap; 18109 XMapEvent xmap; 18110 XMapRequestEvent xmaprequest; 18111 XReparentEvent xreparent; 18112 XConfigureEvent xconfigure; 18113 XGravityEvent xgravity; 18114 XResizeRequestEvent xresizerequest; 18115 XConfigureRequestEvent xconfigurerequest; 18116 XCirculateEvent xcirculate; 18117 XCirculateRequestEvent xcirculaterequest; 18118 XPropertyEvent xproperty; 18119 XSelectionClearEvent xselectionclear; 18120 XSelectionRequestEvent xselectionrequest; 18121 XSelectionEvent xselection; 18122 XColormapEvent xcolormap; 18123 XClientMessageEvent xclient; 18124 XMappingEvent xmapping; 18125 XErrorEvent xerror; 18126 XKeymapEvent xkeymap; 18127 arch_ulong[24] pad; 18128 } 18129 18130 18131 struct Display { 18132 XExtData *ext_data; /* hook for extension to hang data */ 18133 _XPrivate *private1; 18134 int fd; /* Network socket. */ 18135 int private2; 18136 int proto_major_version;/* major version of server's X protocol */ 18137 int proto_minor_version;/* minor version of servers X protocol */ 18138 char *vendor; /* vendor of the server hardware */ 18139 XID private3; 18140 XID private4; 18141 XID private5; 18142 int private6; 18143 XID function(Display*)resource_alloc;/* allocator function */ 18144 ByteOrder byte_order; /* screen byte order, LSBFirst, MSBFirst */ 18145 int bitmap_unit; /* padding and data requirements */ 18146 int bitmap_pad; /* padding requirements on bitmaps */ 18147 ByteOrder bitmap_bit_order; /* LeastSignificant or MostSignificant */ 18148 int nformats; /* number of pixmap formats in list */ 18149 ScreenFormat *pixmap_format; /* pixmap format list */ 18150 int private8; 18151 int release; /* release of the server */ 18152 _XPrivate *private9; 18153 _XPrivate *private10; 18154 int qlen; /* Length of input event queue */ 18155 arch_ulong last_request_read; /* seq number of last event read */ 18156 arch_ulong request; /* sequence number of last request. */ 18157 XPointer private11; 18158 XPointer private12; 18159 XPointer private13; 18160 XPointer private14; 18161 uint max_request_size; /* maximum number 32 bit words in request*/ 18162 _XrmHashBucketRec *db; 18163 int function (Display*)private15; 18164 char *display_name; /* "host:display" string used on this connect*/ 18165 int default_screen; /* default screen for operations */ 18166 int nscreens; /* number of screens on this server*/ 18167 Screen *screens; /* pointer to list of screens */ 18168 arch_ulong motion_buffer; /* size of motion buffer */ 18169 arch_ulong private16; 18170 int min_keycode; /* minimum defined keycode */ 18171 int max_keycode; /* maximum defined keycode */ 18172 XPointer private17; 18173 XPointer private18; 18174 int private19; 18175 byte *xdefaults; /* contents of defaults from server */ 18176 /* there is more to this structure, but it is private to Xlib */ 18177 } 18178 18179 // I got these numbers from a C program as a sanity test 18180 version(X86_64) { 18181 static assert(Display.sizeof == 296); 18182 static assert(XPointer.sizeof == 8); 18183 static assert(XErrorEvent.sizeof == 40); 18184 static assert(XAnyEvent.sizeof == 40); 18185 static assert(XMappingEvent.sizeof == 56); 18186 static assert(XEvent.sizeof == 192); 18187 } else version (AArch64) { 18188 // omit check for aarch64 18189 } else { 18190 static assert(Display.sizeof == 176); 18191 static assert(XPointer.sizeof == 4); 18192 static assert(XEvent.sizeof == 96); 18193 } 18194 18195 struct Depth 18196 { 18197 int depth; /* this depth (Z) of the depth */ 18198 int nvisuals; /* number of Visual types at this depth */ 18199 Visual *visuals; /* list of visuals possible at this depth */ 18200 } 18201 18202 alias void* GC; 18203 alias c_ulong VisualID; 18204 alias XID Colormap; 18205 alias XID Cursor; 18206 alias XID KeySym; 18207 alias uint KeyCode; 18208 enum None = 0; 18209 } 18210 18211 version(without_opengl) {} 18212 else { 18213 extern(C) nothrow @nogc { 18214 18215 18216 static if(!SdpyIsUsingIVGLBinds) { 18217 enum GLX_USE_GL= 1; /* support GLX rendering */ 18218 enum GLX_BUFFER_SIZE= 2; /* depth of the color buffer */ 18219 enum GLX_LEVEL= 3; /* level in plane stacking */ 18220 enum GLX_RGBA= 4; /* true if RGBA mode */ 18221 enum GLX_DOUBLEBUFFER= 5; /* double buffering supported */ 18222 enum GLX_STEREO= 6; /* stereo buffering supported */ 18223 enum GLX_AUX_BUFFERS= 7; /* number of aux buffers */ 18224 enum GLX_RED_SIZE= 8; /* number of red component bits */ 18225 enum GLX_GREEN_SIZE= 9; /* number of green component bits */ 18226 enum GLX_BLUE_SIZE= 10; /* number of blue component bits */ 18227 enum GLX_ALPHA_SIZE= 11; /* number of alpha component bits */ 18228 enum GLX_DEPTH_SIZE= 12; /* number of depth bits */ 18229 enum GLX_STENCIL_SIZE= 13; /* number of stencil bits */ 18230 enum GLX_ACCUM_RED_SIZE= 14; /* number of red accum bits */ 18231 enum GLX_ACCUM_GREEN_SIZE= 15; /* number of green accum bits */ 18232 enum GLX_ACCUM_BLUE_SIZE= 16; /* number of blue accum bits */ 18233 enum GLX_ACCUM_ALPHA_SIZE= 17; /* number of alpha accum bits */ 18234 18235 18236 //XVisualInfo* glXChooseVisual(Display *dpy, int screen, in int *attrib_list); 18237 18238 18239 18240 enum GL_TRUE = 1; 18241 enum GL_FALSE = 0; 18242 } 18243 18244 alias XID GLXContextID; 18245 alias XID GLXPixmap; 18246 alias XID GLXDrawable; 18247 alias XID GLXPbuffer; 18248 alias XID GLXWindow; 18249 alias XID GLXFBConfigID; 18250 alias void* GLXContext; 18251 18252 } 18253 } 18254 18255 enum AllocNone = 0; 18256 18257 extern(C) { 18258 /* WARNING, this type not in Xlib spec */ 18259 extern(C) alias XIOErrorHandler = int function (Display* display); 18260 } 18261 18262 extern(C) nothrow 18263 alias XErrorHandler = int function(Display*, XErrorEvent*); 18264 18265 extern(C) nothrow @nogc { 18266 struct Screen{ 18267 XExtData *ext_data; /* hook for extension to hang data */ 18268 Display *display; /* back pointer to display structure */ 18269 Window root; /* Root window id. */ 18270 int width, height; /* width and height of screen */ 18271 int mwidth, mheight; /* width and height of in millimeters */ 18272 int ndepths; /* number of depths possible */ 18273 Depth *depths; /* list of allowable depths on the screen */ 18274 int root_depth; /* bits per pixel */ 18275 Visual *root_visual; /* root visual */ 18276 GC default_gc; /* GC for the root root visual */ 18277 Colormap cmap; /* default color map */ 18278 uint white_pixel; 18279 uint black_pixel; /* White and Black pixel values */ 18280 int max_maps, min_maps; /* max and min color maps */ 18281 int backing_store; /* Never, WhenMapped, Always */ 18282 bool save_unders; 18283 int root_input_mask; /* initial root input mask */ 18284 } 18285 18286 struct Visual 18287 { 18288 XExtData *ext_data; /* hook for extension to hang data */ 18289 VisualID visualid; /* visual id of this visual */ 18290 int class_; /* class of screen (monochrome, etc.) */ 18291 c_ulong red_mask, green_mask, blue_mask; /* mask values */ 18292 int bits_per_rgb; /* log base 2 of distinct color values */ 18293 int map_entries; /* color map entries */ 18294 } 18295 18296 alias Display* _XPrivDisplay; 18297 18298 extern(D) Screen* ScreenOfDisplay(Display* dpy, int scr) @system { 18299 assert(dpy !is null); 18300 return &dpy.screens[scr]; 18301 } 18302 18303 extern(D) Window RootWindow(Display *dpy,int scr) { 18304 return ScreenOfDisplay(dpy,scr).root; 18305 } 18306 18307 struct XWMHints { 18308 arch_long flags; 18309 Bool input; 18310 int initial_state; 18311 Pixmap icon_pixmap; 18312 Window icon_window; 18313 int icon_x, icon_y; 18314 Pixmap icon_mask; 18315 XID window_group; 18316 } 18317 18318 struct XClassHint { 18319 char* res_name; 18320 char* res_class; 18321 } 18322 18323 extern(D) int DefaultScreen(Display *dpy) { 18324 return dpy.default_screen; 18325 } 18326 18327 extern(D) int DefaultDepth(Display* dpy, int scr) { return ScreenOfDisplay(dpy, scr).root_depth; } 18328 extern(D) int DisplayWidth(Display* dpy, int scr) { return ScreenOfDisplay(dpy, scr).width; } 18329 extern(D) int DisplayHeight(Display* dpy, int scr) { return ScreenOfDisplay(dpy, scr).height; } 18330 extern(D) int DisplayWidthMM(Display* dpy, int scr) { return ScreenOfDisplay(dpy, scr).mwidth; } 18331 extern(D) int DisplayHeightMM(Display* dpy, int scr) { return ScreenOfDisplay(dpy, scr).mheight; } 18332 extern(D) auto DefaultColormap(Display* dpy, int scr) { return ScreenOfDisplay(dpy, scr).cmap; } 18333 18334 extern(D) int ConnectionNumber(Display* dpy) { return dpy.fd; } 18335 18336 enum int AnyPropertyType = 0; 18337 enum int Success = 0; 18338 18339 enum int RevertToNone = None; 18340 enum int PointerRoot = 1; 18341 enum Time CurrentTime = 0; 18342 enum int RevertToPointerRoot = PointerRoot; 18343 enum int RevertToParent = 2; 18344 18345 extern(D) int DefaultDepthOfDisplay(Display* dpy) { 18346 return ScreenOfDisplay(dpy, DefaultScreen(dpy)).root_depth; 18347 } 18348 18349 extern(D) Visual* DefaultVisual(Display *dpy,int scr) { 18350 return ScreenOfDisplay(dpy,scr).root_visual; 18351 } 18352 18353 extern(D) GC DefaultGC(Display *dpy,int scr) { 18354 return ScreenOfDisplay(dpy,scr).default_gc; 18355 } 18356 18357 extern(D) uint BlackPixel(Display *dpy,int scr) { 18358 return ScreenOfDisplay(dpy,scr).black_pixel; 18359 } 18360 18361 extern(D) uint WhitePixel(Display *dpy,int scr) { 18362 return ScreenOfDisplay(dpy,scr).white_pixel; 18363 } 18364 18365 alias void* XFontSet; // i think 18366 struct XmbTextItem { 18367 char* chars; 18368 int nchars; 18369 int delta; 18370 XFontSet font_set; 18371 } 18372 18373 struct XTextItem { 18374 char* chars; 18375 int nchars; 18376 int delta; 18377 Font font; 18378 } 18379 18380 enum { 18381 GXclear = 0x0, /* 0 */ 18382 GXand = 0x1, /* src AND dst */ 18383 GXandReverse = 0x2, /* src AND NOT dst */ 18384 GXcopy = 0x3, /* src */ 18385 GXandInverted = 0x4, /* NOT src AND dst */ 18386 GXnoop = 0x5, /* dst */ 18387 GXxor = 0x6, /* src XOR dst */ 18388 GXor = 0x7, /* src OR dst */ 18389 GXnor = 0x8, /* NOT src AND NOT dst */ 18390 GXequiv = 0x9, /* NOT src XOR dst */ 18391 GXinvert = 0xa, /* NOT dst */ 18392 GXorReverse = 0xb, /* src OR NOT dst */ 18393 GXcopyInverted = 0xc, /* NOT src */ 18394 GXorInverted = 0xd, /* NOT src OR dst */ 18395 GXnand = 0xe, /* NOT src OR NOT dst */ 18396 GXset = 0xf, /* 1 */ 18397 } 18398 enum QueueMode : int { 18399 QueuedAlready, 18400 QueuedAfterReading, 18401 QueuedAfterFlush 18402 } 18403 18404 enum GrabMode { GrabModeSync = 0, GrabModeAsync = 1 } 18405 18406 struct XPoint { 18407 short x; 18408 short y; 18409 } 18410 18411 enum CoordMode:int { 18412 CoordModeOrigin = 0, 18413 CoordModePrevious = 1 18414 } 18415 18416 enum PolygonShape:int { 18417 Complex = 0, 18418 Nonconvex = 1, 18419 Convex = 2 18420 } 18421 18422 struct XTextProperty { 18423 const(char)* value; /* same as Property routines */ 18424 Atom encoding; /* prop type */ 18425 int format; /* prop data format: 8, 16, or 32 */ 18426 arch_ulong nitems; /* number of data items in value */ 18427 } 18428 18429 version( X86_64 ) { 18430 static assert(XTextProperty.sizeof == 32); 18431 } 18432 18433 18434 struct XGCValues { 18435 int function_; /* logical operation */ 18436 arch_ulong plane_mask;/* plane mask */ 18437 arch_ulong foreground;/* foreground pixel */ 18438 arch_ulong background;/* background pixel */ 18439 int line_width; /* line width */ 18440 int line_style; /* LineSolid, LineOnOffDash, LineDoubleDash */ 18441 int cap_style; /* CapNotLast, CapButt, 18442 CapRound, CapProjecting */ 18443 int join_style; /* JoinMiter, JoinRound, JoinBevel */ 18444 int fill_style; /* FillSolid, FillTiled, 18445 FillStippled, FillOpaeueStippled */ 18446 int fill_rule; /* EvenOddRule, WindingRule */ 18447 int arc_mode; /* ArcChord, ArcPieSlice */ 18448 Pixmap tile; /* tile pixmap for tiling operations */ 18449 Pixmap stipple; /* stipple 1 plane pixmap for stipping */ 18450 int ts_x_origin; /* offset for tile or stipple operations */ 18451 int ts_y_origin; 18452 Font font; /* default text font for text operations */ 18453 int subwindow_mode; /* ClipByChildren, IncludeInferiors */ 18454 Bool graphics_exposures;/* boolean, should exposures be generated */ 18455 int clip_x_origin; /* origin for clipping */ 18456 int clip_y_origin; 18457 Pixmap clip_mask; /* bitmap clipping; other calls for rects */ 18458 int dash_offset; /* patterned/dashed line information */ 18459 char dashes; 18460 } 18461 18462 struct XColor { 18463 arch_ulong pixel; 18464 ushort red, green, blue; 18465 byte flags; 18466 byte pad; 18467 } 18468 18469 struct XRectangle { 18470 short x; 18471 short y; 18472 ushort width; 18473 ushort height; 18474 } 18475 18476 enum ClipByChildren = 0; 18477 enum IncludeInferiors = 1; 18478 18479 enum Atom XA_PRIMARY = 1; 18480 enum Atom XA_SECONDARY = 2; 18481 enum Atom XA_STRING = 31; 18482 enum Atom XA_CARDINAL = 6; 18483 enum Atom XA_WM_NAME = 39; 18484 enum Atom XA_ATOM = 4; 18485 enum Atom XA_WINDOW = 33; 18486 enum Atom XA_WM_HINTS = 35; 18487 enum int PropModeAppend = 2; 18488 enum int PropModeReplace = 0; 18489 enum int PropModePrepend = 1; 18490 18491 enum int CopyFromParent = 0; 18492 enum int InputOutput = 1; 18493 18494 // XWMHints 18495 enum InputHint = 1 << 0; 18496 enum StateHint = 1 << 1; 18497 enum IconPixmapHint = (1L << 2); 18498 enum IconWindowHint = (1L << 3); 18499 enum IconPositionHint = (1L << 4); 18500 enum IconMaskHint = (1L << 5); 18501 enum WindowGroupHint = (1L << 6); 18502 enum AllHints = (InputHint|StateHint|IconPixmapHint|IconWindowHint|IconPositionHint|IconMaskHint|WindowGroupHint); 18503 enum XUrgencyHint = (1L << 8); 18504 18505 // GC Components 18506 enum GCFunction = (1L<<0); 18507 enum GCPlaneMask = (1L<<1); 18508 enum GCForeground = (1L<<2); 18509 enum GCBackground = (1L<<3); 18510 enum GCLineWidth = (1L<<4); 18511 enum GCLineStyle = (1L<<5); 18512 enum GCCapStyle = (1L<<6); 18513 enum GCJoinStyle = (1L<<7); 18514 enum GCFillStyle = (1L<<8); 18515 enum GCFillRule = (1L<<9); 18516 enum GCTile = (1L<<10); 18517 enum GCStipple = (1L<<11); 18518 enum GCTileStipXOrigin = (1L<<12); 18519 enum GCTileStipYOrigin = (1L<<13); 18520 enum GCFont = (1L<<14); 18521 enum GCSubwindowMode = (1L<<15); 18522 enum GCGraphicsExposures= (1L<<16); 18523 enum GCClipXOrigin = (1L<<17); 18524 enum GCClipYOrigin = (1L<<18); 18525 enum GCClipMask = (1L<<19); 18526 enum GCDashOffset = (1L<<20); 18527 enum GCDashList = (1L<<21); 18528 enum GCArcMode = (1L<<22); 18529 enum GCLastBit = 22; 18530 18531 18532 enum int WithdrawnState = 0; 18533 enum int NormalState = 1; 18534 enum int IconicState = 3; 18535 18536 } 18537 } else version (OSXCocoa) { 18538 18539 /+ 18540 DON'T FORGET TO MARK THE CLASSES `extern`!! can cause "unrecognized selector sent to class" errors if you do. 18541 +/ 18542 18543 private __gshared AppDelegate globalAppDelegate; 18544 18545 extern(Objective-C) 18546 class AppDelegate : NSObject, NSApplicationDelegate { 18547 override static AppDelegate alloc() @selector("alloc"); 18548 18549 18550 void sdpyCustomEventWakeup(NSid arg) @selector("sdpyCustomEventWakeup:") { 18551 SimpleWindow.processAllCustomEvents(); 18552 } 18553 18554 override void applicationWillFinishLaunching(NSNotification notification) @selector("applicationWillFinishLaunching:") { 18555 immutable style = NSWindowStyleMask.resizable | 18556 NSWindowStyleMask.closable | 18557 NSWindowStyleMask.miniaturizable | 18558 NSWindowStyleMask.titled; 18559 18560 NSMenu mainMenu = NSMenu.alloc.init(MacString("Main").borrow); 18561 18562 { 18563 auto item = mainMenu.addItem(MacString("Test").borrow, null, MacString("").borrow); 18564 auto menu = NSMenu.alloc.init(MacString("Test2").borrow); 18565 mainMenu.setSubmenu(menu, item); 18566 18567 auto newItem = menu.addItem(MacString("Quit").borrow, sel_registerName("terminate:"), MacString("q").borrow); 18568 newItem.target = NSApp; 18569 auto newItem2 = menu.addItem(MacString("Disabled").borrow, sel_registerName("doesnotexist:"), MacString("x").borrow); 18570 newItem2.target = NSApp; 18571 } 18572 18573 { 18574 auto item = mainMenu.addItem(MacString("Test3").borrow, null, MacString("").borrow); 18575 auto menu = NSMenu.alloc.init(MacString("Test4").borrow); // this is the title actually used 18576 mainMenu.setSubmenu(menu, item); 18577 18578 auto newItem = menu.addItem(MacString("Quit2").borrow, sel_registerName("stop:"), MacString("s").borrow); 18579 menu.addItem(MacString("Pulse").borrow, sel_registerName("simpledisplay_pulse:"), MacString("p").borrow); 18580 } 18581 18582 18583 NSApp.menu = mainMenu; 18584 18585 18586 // auto controller = ViewController.alloc.init; 18587 18588 // auto timer = NSTimer.schedule(1.0, cast(NSid) view, sel_registerName("simpledisplay_pulse:"), null, true); 18589 18590 /+ 18591 this.window = window; 18592 this.controller = controller; 18593 +/ 18594 } 18595 18596 override void applicationDidFinishLaunching(NSNotification notification) @selector("applicationDidFinishLaunching:") { 18597 NSApplication.shared_.activateIgnoringOtherApps(true); 18598 } 18599 override bool applicationShouldTerminateAfterLastWindowClosed(NSNotification notification) @selector("applicationShouldTerminateAfterLastWindowClosed:") { 18600 return true; 18601 } 18602 } 18603 18604 extern(Objective-C) 18605 class SDWindowDelegate : NSObject, NSWindowDelegate { 18606 override static SDWindowDelegate alloc() @selector("alloc"); 18607 override SDWindowDelegate init() @selector("init"); 18608 18609 SimpleWindow simpleWindow; 18610 18611 override void windowWillClose(NSNotification notification) @selector("windowWillClose:") { 18612 auto window = cast(void*) notification.object; 18613 18614 // FIXME: do i need to release it? 18615 SimpleWindow.nativeMapping.remove(window); 18616 } 18617 18618 override NSSize windowWillResize(NSWindow sender, NSSize frameSize) @selector("windowWillResize:toSize:") { 18619 if(simpleWindow.windowResized) { 18620 // FIXME: automaticallyScaleIfPossible behaviors 18621 18622 simpleWindow._width = cast(int) frameSize.width; 18623 simpleWindow._height = cast(int) frameSize.height; 18624 18625 simpleWindow.view.setFrameSize(frameSize); 18626 18627 /+ 18628 auto size = simpleWindow.view.frame.size; 18629 writeln(cast(int) size.width, "x", cast(int) size.height); 18630 +/ 18631 18632 simpleWindow.createNewDrawingContext(simpleWindow._width, simpleWindow._height); 18633 18634 simpleWindow.windowResized(simpleWindow._width, simpleWindow._height); 18635 18636 // simpleWindow.view.setNeedsDisplay(true); 18637 } 18638 18639 return frameSize; 18640 } 18641 18642 /+ 18643 override void windowDidResize(NSNotification notification) @selector("windowDidResize:") { 18644 if(simpleWindow.windowResized) { 18645 auto window = simpleWindow.window; 18646 auto rect = window.contentRectForFrameRect(window.frame); 18647 import std.stdio; writeln(window.frame.size); 18648 simpleWindow.windowResized(cast(int) rect.size.width, cast(int) rect.size.height); 18649 } 18650 } 18651 +/ 18652 } 18653 18654 extern(Objective-C) 18655 class SDGraphicsView : NSView { 18656 SimpleWindow simpleWindow; 18657 18658 override static SDGraphicsView alloc() @selector("alloc"); 18659 override SDGraphicsView init() @selector("init") { 18660 super.init(); 18661 return this; 18662 } 18663 18664 override void drawRect(NSRect rect) @selector("drawRect:") { 18665 auto curCtx = NSGraphicsContext.currentContext.graphicsPort; 18666 auto cgImage = CGBitmapContextCreateImage(simpleWindow.drawingContext); 18667 auto size = CGSize(CGBitmapContextGetWidth(simpleWindow.drawingContext), CGBitmapContextGetHeight(simpleWindow.drawingContext)); 18668 CGContextDrawImage(curCtx, CGRect(CGPoint(0, 0), size), cgImage); 18669 CGImageRelease(cgImage); 18670 } 18671 18672 extern(D) 18673 private void mouseHelper(NSEvent event, MouseEventType type, MouseButton button) { 18674 MouseEvent me; 18675 me.type = type; 18676 18677 auto pos = event.locationInWindow; 18678 18679 me.x = cast(int) pos.x; 18680 me.y = cast(int) (simpleWindow.height - pos.y); 18681 18682 me.dx = 0; // FIXME 18683 me.dy = 0; // FIXME 18684 18685 me.button = button; 18686 me.modifierState = cast(uint) event.modifierFlags; 18687 me.window = simpleWindow; 18688 18689 me.doubleClick = false; 18690 18691 if(simpleWindow && simpleWindow.handleMouseEvent) 18692 simpleWindow.handleMouseEvent(me); 18693 } 18694 18695 override void mouseDown(NSEvent event) @selector("mouseDown:") { 18696 // writeln(event.pressedMouseButtons); 18697 18698 mouseHelper(event, MouseEventType.buttonPressed, MouseButton.left); 18699 } 18700 override void mouseDragged(NSEvent event) @selector("mouseDragged:") { 18701 mouseHelper(event, MouseEventType.motion, MouseButton.left); 18702 } 18703 override void mouseUp(NSEvent event) @selector("mouseUp:") { 18704 mouseHelper(event, MouseEventType.buttonReleased, MouseButton.left); 18705 } 18706 override void mouseMoved(NSEvent event) @selector("mouseMoved:") { 18707 mouseHelper(event, MouseEventType.motion, MouseButton.left); // button wrong prolly 18708 } 18709 /+ 18710 // FIXME 18711 override void mouseEntered(NSEvent event) @selector("mouseEntered:") { 18712 mouseHelper(event, MouseEventType.buttonPressed, MouseButton.left); 18713 } 18714 override void mouseExited(NSEvent event) @selector("mouseExited:") { 18715 mouseHelper(event, MouseEventType.buttonPressed, MouseButton.left); 18716 } 18717 +/ 18718 18719 override void rightMouseDown(NSEvent event) @selector("rightMouseDown:") { 18720 mouseHelper(event, MouseEventType.buttonPressed, MouseButton.right); 18721 } 18722 override void rightMouseDragged(NSEvent event) @selector("rightMouseDragged:") { 18723 mouseHelper(event, MouseEventType.motion, MouseButton.right); 18724 } 18725 override void rightMouseUp(NSEvent event) @selector("rightMouseUp:") { 18726 mouseHelper(event, MouseEventType.buttonReleased, MouseButton.right); 18727 } 18728 18729 override void otherMouseDown(NSEvent event) @selector("otherMouseDown:") { 18730 mouseHelper(event, MouseEventType.buttonPressed, MouseButton.middle); 18731 } 18732 override void otherMouseDragged(NSEvent event) @selector("otherMouseDragged:") { 18733 mouseHelper(event, MouseEventType.motion, MouseButton.middle); 18734 } 18735 override void otherMouseUp(NSEvent event) @selector("otherMouseUp:") { 18736 mouseHelper(event, MouseEventType.buttonReleased, MouseButton.middle); 18737 } 18738 18739 override void scrollWheel(NSEvent event) @selector("scrollWheel:") { 18740 // import std.stdio; writeln(event.deltaY); 18741 } 18742 18743 override void keyDown(NSEvent event) @selector("keyDown:") { 18744 // the event may have multiple characters, and we send them all at once. 18745 if (simpleWindow.handleCharEvent) { 18746 auto chars = DeifiedNSString(event.characters); 18747 foreach (dchar dc; chars.str) 18748 simpleWindow.handleCharEvent(dc); 18749 } 18750 18751 keyHelper(event, true); 18752 } 18753 18754 override void keyUp(NSEvent event) @selector("keyUp:") { 18755 keyHelper(event, false); 18756 } 18757 18758 extern(D) 18759 private void keyHelper(NSEvent event, bool pressed) { 18760 if(simpleWindow.handleKeyEvent) { 18761 KeyEvent ev; 18762 ev.key = cast(Key) event.keyCode;// (event.specialKey ? event.specialKey : event.keyCode); 18763 ev.pressed = pressed; 18764 ev.hardwareCode = cast(ubyte) event.keyCode; 18765 ev.modifierState = cast(uint) event.modifierFlags; 18766 ev.window = simpleWindow; 18767 18768 simpleWindow.handleKeyEvent(ev); 18769 } 18770 } 18771 18772 override bool isFlipped() @selector("isFlipped") { 18773 return true; 18774 } 18775 override bool acceptsFirstResponder() @selector("acceptsFirstResponder") { 18776 return true; 18777 } 18778 18779 void simpledisplay_pulse(NSTimer timer) @selector("simpledisplay_pulse:") { 18780 if(simpleWindow && simpleWindow.handlePulse) 18781 simpleWindow.handlePulse(); 18782 /+ 18783 setNeedsDisplay = true; 18784 +/ 18785 } 18786 } 18787 18788 private: 18789 alias const(void)* CFStringRef; 18790 alias const(void)* CFAllocatorRef; 18791 alias const(void)* CFTypeRef; 18792 alias const(void)* CGColorSpaceRef; 18793 alias const(void)* CGImageRef; 18794 alias ulong CGBitmapInfo; 18795 alias NSGraphicsContext CGContextRef; 18796 18797 alias NSPoint CGPoint; 18798 alias NSSize CGSize; 18799 alias NSRect CGRect; 18800 18801 struct CGAffineTransform { 18802 double a, b, c, d, tx, ty; 18803 } 18804 18805 enum NSApplicationActivationPolicyRegular = 0; 18806 enum NSBackingStoreBuffered = 2; 18807 enum kCFStringEncodingUTF8 = 0x08000100; 18808 18809 enum : size_t { 18810 NSBorderlessWindowMask = 0, 18811 NSTitledWindowMask = 1 << 0, 18812 NSClosableWindowMask = 1 << 1, 18813 NSMiniaturizableWindowMask = 1 << 2, 18814 NSResizableWindowMask = 1 << 3, 18815 NSTexturedBackgroundWindowMask = 1 << 8 18816 } 18817 18818 enum : ulong { 18819 kCGImageAlphaNone, 18820 kCGImageAlphaPremultipliedLast, 18821 kCGImageAlphaPremultipliedFirst, 18822 kCGImageAlphaLast, 18823 kCGImageAlphaFirst, 18824 kCGImageAlphaNoneSkipLast, 18825 kCGImageAlphaNoneSkipFirst 18826 } 18827 enum : ulong { 18828 kCGBitmapAlphaInfoMask = 0x1F, 18829 kCGBitmapFloatComponents = (1 << 8), 18830 kCGBitmapByteOrderMask = 0x7000, 18831 kCGBitmapByteOrderDefault = (0 << 12), 18832 kCGBitmapByteOrder16Little = (1 << 12), 18833 kCGBitmapByteOrder32Little = (2 << 12), 18834 kCGBitmapByteOrder16Big = (3 << 12), 18835 kCGBitmapByteOrder32Big = (4 << 12) 18836 } 18837 enum CGPathDrawingMode { 18838 kCGPathFill, 18839 kCGPathEOFill, 18840 kCGPathStroke, 18841 kCGPathFillStroke, 18842 kCGPathEOFillStroke 18843 } 18844 enum objc_AssociationPolicy : size_t { 18845 OBJC_ASSOCIATION_ASSIGN = 0, 18846 OBJC_ASSOCIATION_RETAIN_NONATOMIC = 1, 18847 OBJC_ASSOCIATION_COPY_NONATOMIC = 3, 18848 OBJC_ASSOCIATION_RETAIN = 0x301, //01401, 18849 OBJC_ASSOCIATION_COPY = 0x303 //01403 18850 } 18851 18852 extern(C) { 18853 CGContextRef CGBitmapContextCreate(void* data, size_t width, size_t height, size_t bitsPerComponent, size_t bytesPerRow, CGColorSpaceRef colorspace, CGBitmapInfo bitmapInfo); 18854 void CGContextRelease(CGContextRef c); 18855 ubyte* CGBitmapContextGetData(CGContextRef c); 18856 CGImageRef CGBitmapContextCreateImage(CGContextRef c); 18857 size_t CGBitmapContextGetWidth(CGContextRef c); 18858 size_t CGBitmapContextGetHeight(CGContextRef c); 18859 18860 CGColorSpaceRef CGColorSpaceCreateDeviceRGB(); 18861 void CGColorSpaceRelease(CGColorSpaceRef cs); 18862 18863 void CGContextSetRGBStrokeColor(CGContextRef c, double red, double green, double blue, double alpha); 18864 void CGContextSetRGBFillColor(CGContextRef c, double red, double green, double blue, double alpha); 18865 void CGContextDrawImage(CGContextRef c, CGRect rect, CGImageRef image); 18866 void CGContextShowTextAtPoint(CGContextRef c, double x, double y, const(char)* str, size_t length); 18867 void CGContextStrokeLineSegments(CGContextRef c, const(CGPoint)* points, size_t count); 18868 void CGContextSetLineDash(CGContextRef c, CGFloat phase, const CGFloat *lengths, size_t count); 18869 18870 void CGContextBeginPath(CGContextRef c); 18871 void CGContextDrawPath(CGContextRef c, CGPathDrawingMode mode); 18872 void CGContextAddEllipseInRect(CGContextRef c, CGRect rect); 18873 void CGContextAddArc(CGContextRef c, double x, double y, double radius, double startAngle, double endAngle, long clockwise); 18874 void CGContextAddRect(CGContextRef c, CGRect rect); 18875 void CGContextAddLines(CGContextRef c, const(CGPoint)* points, size_t count); 18876 void CGContextSaveGState(CGContextRef c); 18877 void CGContextRestoreGState(CGContextRef c); 18878 void CGContextSelectFont(CGContextRef c, const(char)* name, double size, ulong textEncoding); 18879 CGAffineTransform CGContextGetTextMatrix(CGContextRef c); 18880 void CGContextSetTextMatrix(CGContextRef c, CGAffineTransform t); 18881 18882 void CGImageRelease(CGImageRef image); 18883 } 18884 } else static assert(0, "Unsupported operating system"); 18885 18886 18887 version(OSXCocoa) { 18888 // I don't know anything about the Mac, but a couple years ago, KennyTM on the newsgroup wrote this for me 18889 // 18890 // http://forum.dlang.org/thread/innr0v$1deh$1@digitalmars.com?page=4#post-int88l:24uaf:241:40digitalmars.com 18891 // https://github.com/kennytm/simpledisplay.d/blob/osx/simpledisplay.d 18892 // 18893 // and it is about time I merged it in here. It is available with -version=OSXCocoa until someone tests it for me! 18894 // Probably won't even fully compile right now 18895 18896 private enum double PI = 3.14159265358979323; 18897 18898 alias NSWindow NativeWindowHandle; 18899 alias void delegate(NSid) NativeEventHandler; 18900 18901 enum KEY_ESCAPE = 27; 18902 18903 mixin template NativeImageImplementation() { 18904 CGContextRef context; 18905 ubyte* rawData; 18906 18907 final: 18908 18909 void convertToRgbaBytes(ubyte[] where) @system { 18910 assert(where.length == this.width * this.height * 4); 18911 18912 // if rawData had a length.... 18913 //assert(rawData.length == where.length); 18914 for(long idx = 0; idx < where.length; idx += 4) { 18915 auto alpha = rawData[idx + 3]; 18916 if(alpha == 255) { 18917 where[idx + 0] = rawData[idx + 0]; // r 18918 where[idx + 1] = rawData[idx + 1]; // g 18919 where[idx + 2] = rawData[idx + 2]; // b 18920 where[idx + 3] = rawData[idx + 3]; // a 18921 } else { 18922 where[idx + 0] = cast(ubyte)(rawData[idx + 0] * 255 / alpha); // r 18923 where[idx + 1] = cast(ubyte)(rawData[idx + 1] * 255 / alpha); // g 18924 where[idx + 2] = cast(ubyte)(rawData[idx + 2] * 255 / alpha); // b 18925 where[idx + 3] = rawData[idx + 3]; // a 18926 18927 } 18928 } 18929 } 18930 18931 void setFromRgbaBytes(in ubyte[] where) @system { 18932 // FIXME: this is probably wrong 18933 assert(where.length == this.width * this.height * 4); 18934 18935 // if rawData had a length.... 18936 //assert(rawData.length == where.length); 18937 for(long idx = 0; idx < where.length; idx += 4) { 18938 auto alpha = where[idx + 3]; 18939 if(alpha == 255) { 18940 rawData[idx + 0] = where[idx + 0]; // r 18941 rawData[idx + 1] = where[idx + 1]; // g 18942 rawData[idx + 2] = where[idx + 2]; // b 18943 rawData[idx + 3] = where[idx + 3]; // a 18944 } else if(alpha == 0) { 18945 rawData[idx + 0] = 0; 18946 rawData[idx + 1] = 0; 18947 rawData[idx + 2] = 0; 18948 rawData[idx + 3] = 0; 18949 } else { 18950 rawData[idx + 0] = cast(ubyte)(where[idx + 0] * 255 / alpha); // r 18951 rawData[idx + 1] = cast(ubyte)(where[idx + 1] * 255 / alpha); // g 18952 rawData[idx + 2] = cast(ubyte)(where[idx + 2] * 255 / alpha); // b 18953 rawData[idx + 3] = where[idx + 3]; // a 18954 } 18955 } 18956 } 18957 18958 18959 void createImage(int width, int height, bool forcexshm=false, bool ignored = false) { 18960 auto colorSpace = CGColorSpaceCreateDeviceRGB(); 18961 context = CGBitmapContextCreate(null, width, height, 8, 4*width, colorSpace, kCGImageAlphaPremultipliedLast |kCGBitmapByteOrder32Big); 18962 CGColorSpaceRelease(colorSpace); 18963 rawData = CGBitmapContextGetData(context); 18964 } 18965 void dispose() { 18966 CGContextRelease(context); 18967 } 18968 18969 void setPixel(int x, int y, Color c) @system { 18970 auto offset = (y * width + x) * 4; 18971 if (c.a == 255) { 18972 rawData[offset + 0] = c.r; 18973 rawData[offset + 1] = c.g; 18974 rawData[offset + 2] = c.b; 18975 rawData[offset + 3] = c.a; 18976 } else { 18977 rawData[offset + 0] = cast(ubyte)(c.r*c.a/255); 18978 rawData[offset + 1] = cast(ubyte)(c.g*c.a/255); 18979 rawData[offset + 2] = cast(ubyte)(c.b*c.a/255); 18980 rawData[offset + 3] = c.a; 18981 } 18982 } 18983 } 18984 18985 mixin template NativeScreenPainterImplementation() { 18986 CGContextRef context; 18987 ubyte[4] _outlineComponents; 18988 NSView view; 18989 18990 Pen _activePen; 18991 Color _fillColor; 18992 Rectangle _clipRectangle; 18993 OperatingSystemFont _font; 18994 18995 OperatingSystemFont getFont() { 18996 if(_font is null) { 18997 static OperatingSystemFont _defaultFont; 18998 if(_defaultFont is null) { 18999 _defaultFont = new OperatingSystemFont(); 19000 _defaultFont.loadDefault(); 19001 } 19002 _font = _defaultFont; 19003 } 19004 19005 return _font; 19006 } 19007 19008 void create(PaintingHandle window) { 19009 // this.destiny = window; 19010 if(auto sw = cast(SimpleWindow) this.window) { 19011 context = sw.drawingContext; 19012 view = sw.view; 19013 } else { 19014 throw new NotYetImplementedException(); 19015 } 19016 } 19017 19018 void dispose() { 19019 view.setNeedsDisplay(true); 19020 } 19021 19022 bool manualInvalidations; 19023 void invalidateRect(Rectangle invalidRect) { } 19024 19025 // NotYetImplementedException 19026 void rasterOp(RasterOp op) { 19027 } 19028 void setClipRectangle(int, int, int, int) { 19029 } 19030 Size textSize(in char[] txt) { 19031 auto font = getFont(); 19032 return Size(font.stringWidth(txt), font.height()); 19033 } 19034 19035 void setFont(OperatingSystemFont font) { 19036 _font = font; 19037 //font.font.setInContext(context); 19038 } 19039 int fontHeight() { 19040 auto font = getFont(); 19041 return font.height; 19042 } 19043 19044 // end 19045 19046 void pen(Pen pen) { 19047 _activePen = pen; 19048 auto color = pen.color; // FIXME 19049 double alphaComponent = color.a/255.0f; 19050 CGContextSetRGBStrokeColor(context, color.r/255.0f, color.g/255.0f, color.b/255.0f, alphaComponent); 19051 19052 double[2] patternBuffer; 19053 double[] pattern; 19054 final switch(pen.style) { 19055 case Pen.Style.Solid: 19056 pattern = null; 19057 break; 19058 case Pen.Style.Dashed: 19059 patternBuffer[0] = 4; 19060 patternBuffer[1] = 1; 19061 pattern = patternBuffer[]; 19062 break; 19063 case Pen.Style.Dotted: 19064 patternBuffer[0] = 1; 19065 patternBuffer[1] = 1; 19066 pattern = patternBuffer[]; 19067 break; 19068 } 19069 19070 CGContextSetLineDash(context, 0, pattern.ptr, pattern.length); 19071 19072 if (color.a != 255) { 19073 _outlineComponents[0] = cast(ubyte)(color.r*color.a/255); 19074 _outlineComponents[1] = cast(ubyte)(color.g*color.a/255); 19075 _outlineComponents[2] = cast(ubyte)(color.b*color.a/255); 19076 _outlineComponents[3] = color.a; 19077 } else { 19078 _outlineComponents[0] = color.r; 19079 _outlineComponents[1] = color.g; 19080 _outlineComponents[2] = color.b; 19081 _outlineComponents[3] = color.a; 19082 } 19083 } 19084 19085 @property void fillColor(Color color) { 19086 CGContextSetRGBFillColor(context, color.r/255.0f, color.g/255.0f, color.b/255.0f, color.a/255.0f); 19087 } 19088 19089 void drawImage(int x, int y, Image image, int ulx, int upy, int width, int height) { 19090 // NotYetImplementedException for upper left/width/height 19091 auto cgImage = CGBitmapContextCreateImage(image.context); 19092 auto size = CGSize(CGBitmapContextGetWidth(image.context), CGBitmapContextGetHeight(image.context)); 19093 CGContextDrawImage(context, CGRect(CGPoint(x, y), size), cgImage); 19094 CGImageRelease(cgImage); 19095 } 19096 19097 void drawPixmap(Sprite s, int x, int y, int ix, int iy, int w, int h) { 19098 // FIXME: is this efficient? 19099 auto cgImage = CGBitmapContextCreateImage(s.handle); 19100 auto size = CGSize(CGBitmapContextGetWidth(s.handle), CGBitmapContextGetHeight(s.handle)); 19101 CGContextDrawImage(context, CGRect(CGPoint(x, y), size), cgImage); 19102 CGImageRelease(cgImage); 19103 } 19104 19105 19106 void drawText(int x, int y, int x2, int y2, in char[] text, uint alignment) { 19107 // FIXME: alignment 19108 if (_outlineComponents[3] != 0) { 19109 CGContextSaveGState(context); 19110 auto invAlpha = 1.0f/_outlineComponents[3]; 19111 CGContextSetRGBFillColor(context, _outlineComponents[0]*invAlpha, 19112 _outlineComponents[1]*invAlpha, 19113 _outlineComponents[2]*invAlpha, 19114 _outlineComponents[3]/255.0f); 19115 19116 19117 19118 // FIXME: should we clip it to the bounding box? 19119 int textHeight = fontHeight; 19120 19121 auto lines = text.split('\n'); 19122 19123 const lineHeight = textHeight; 19124 textHeight *= lines.length; 19125 19126 int cy = y; 19127 19128 if(alignment & TextAlignment.VerticalBottom) { 19129 if(y2 <= 0) 19130 return; 19131 auto h = y2 - y; 19132 if(h > textHeight) { 19133 cy += h - textHeight; 19134 cy -= lineHeight / 2; 19135 } 19136 } else if(alignment & TextAlignment.VerticalCenter) { 19137 if(y2 <= 0) 19138 return; 19139 auto h = y2 - y; 19140 if(textHeight < h) { 19141 cy += (h - textHeight) / 2; 19142 //cy -= lineHeight / 4; 19143 } 19144 } 19145 19146 foreach(line; text.split('\n')) { 19147 int textWidth = this.textSize(line).width; 19148 19149 int px = x, py = cy; 19150 19151 if(alignment & TextAlignment.Center) { 19152 if(x2 <= 0) 19153 return; 19154 auto w = x2 - x; 19155 if(w > textWidth) 19156 px += (w - textWidth) / 2; 19157 } else if(alignment & TextAlignment.Right) { 19158 if(x2 <= 0) 19159 return; 19160 auto pos = x2 - textWidth; 19161 if(pos > x) 19162 px = pos; 19163 } 19164 19165 CGContextShowTextAtPoint(context, px, py + getFont.ascent /* this is cuz this picks baseline but i want bounding box */, line.ptr, line.length); 19166 19167 carry_on: 19168 cy += lineHeight + 4; 19169 } 19170 19171 // auto cfstr = cast(NSid)createCFString(text); 19172 // objc_msgSend(cfstr, sel_registerName("drawAtPoint:withAttributes:"), 19173 // NSPoint(x, y), null); 19174 // CFRelease(cfstr); 19175 CGContextRestoreGState(context); 19176 } 19177 } 19178 19179 void drawPixel(int x, int y) { 19180 auto rawData = CGBitmapContextGetData(context); 19181 auto width = CGBitmapContextGetWidth(context); 19182 auto height = CGBitmapContextGetHeight(context); 19183 auto offset = ((height - y - 1) * width + x) * 4; 19184 rawData[offset .. offset+4] = _outlineComponents; 19185 } 19186 19187 void drawLine(int x1, int y1, int x2, int y2) { 19188 CGPoint[2] linePoints; 19189 linePoints[0] = CGPoint(x1, y1); 19190 linePoints[1] = CGPoint(x2, y2); 19191 CGContextStrokeLineSegments(context, linePoints.ptr, linePoints.length); 19192 } 19193 19194 void drawRectangleRounded(Point upperLeft, Point lowerRight, int borderRadius) { 19195 drawRectangle(upperLeft.x, upperLeft.y, lowerRight.x - upperLeft.x, lowerRight.y - upperLeft.y); // FIXME not rounded 19196 } 19197 19198 void drawRectangle(int x, int y, int width, int height) { 19199 CGContextBeginPath(context); 19200 auto rect = CGRect(CGPoint(x, y), CGSize(width, height)); 19201 CGContextAddRect(context, rect); 19202 CGContextDrawPath(context, CGPathDrawingMode.kCGPathFillStroke); 19203 } 19204 19205 void drawEllipse(int x1, int y1, int x2, int y2) { 19206 CGContextBeginPath(context); 19207 auto rect = CGRect(CGPoint(x1, y1), CGSize(x2-x1, y2-y1)); 19208 CGContextAddEllipseInRect(context, rect); 19209 CGContextDrawPath(context, CGPathDrawingMode.kCGPathFillStroke); 19210 } 19211 19212 void drawArc(int x1, int y1, int width, int height, int start, int length) { 19213 // @@@BUG@@@ Does not support elliptic arc (width != height). 19214 CGContextBeginPath(context); 19215 int clockwise = 0; 19216 if(length < 0) { 19217 clockwise = 1; 19218 length = -length; 19219 } 19220 CGContextAddArc(context, x1+width*0.5f, y1+height*0.5f, width, 19221 start*PI/(180*64), (start+length)*PI/(180*64), clockwise); 19222 CGContextDrawPath(context, CGPathDrawingMode.kCGPathFillStroke); 19223 } 19224 19225 void drawPolygon(Point[] intPoints) { 19226 CGContextBeginPath(context); 19227 CGPoint[16] pointsBuffer; 19228 CGPoint[] points; 19229 if(intPoints.length <= pointsBuffer.length) 19230 points = pointsBuffer[0 .. intPoints.length]; 19231 else 19232 points = new CGPoint[](intPoints.length); 19233 19234 foreach(idx, pt; intPoints) 19235 points[idx] = CGPoint(pt.x, pt.y); 19236 19237 CGContextAddLines(context, points.ptr, points.length); 19238 CGContextDrawPath(context, CGPathDrawingMode.kCGPathFillStroke); 19239 } 19240 } 19241 19242 private bool appInitialized = false; 19243 void initializeApp() { 19244 if(appInitialized) 19245 return; 19246 synchronized { 19247 if(appInitialized) 19248 return; 19249 19250 auto app = NSApp(); // ensure the is initialized 19251 19252 auto dg = AppDelegate.alloc; 19253 globalAppDelegate = dg; 19254 NSApp.delegate_ = dg; 19255 19256 NSApp.setActivationPolicy(NSApplicationActivationPolicy.regular); 19257 19258 appInitialized = true; 19259 } 19260 } 19261 19262 mixin template NativeSimpleWindowImplementation() { 19263 void createWindow(int width, int height, string title, OpenGlOptions opengl, SimpleWindow parent) { 19264 initializeApp(); 19265 19266 auto contentRect = NSRect(NSPoint(0, 0), NSSize(width, height)); 19267 19268 auto window = NSWindow.alloc.initWithContentRect( 19269 contentRect, 19270 NSWindowStyleMask.resizable | NSWindowStyleMask.closable | NSWindowStyleMask.miniaturizable | NSWindowStyleMask.titled, 19271 NSBackingStoreType.buffered, 19272 true 19273 ); 19274 19275 SimpleWindow.nativeMapping[cast(void*) window] = this; 19276 19277 window.title = MacString(title).borrow; 19278 19279 auto dg = SDWindowDelegate.alloc.init; 19280 dg.simpleWindow = this; 19281 window.delegate_ = dg; 19282 19283 auto view = SDGraphicsView.alloc.init; 19284 assert(view !is null); 19285 window.contentView = view; 19286 this.view = view; 19287 view.simpleWindow = this; 19288 19289 window.center(); 19290 19291 window.makeKeyAndOrderFront(null); 19292 19293 // no need to make a bitmap on mac since everything is double buffered already 19294 19295 // create area to draw on. 19296 createNewDrawingContext(width, height); 19297 19298 window.setBackgroundColor(NSColor.whiteColor); 19299 } 19300 19301 void createNewDrawingContext(int width, int height) { 19302 // FIXME need to preserve info from the old context too i think... maybe. or at least setNeedsDisplay 19303 if(this.drawingContext) 19304 CGContextRelease(this.drawingContext); 19305 auto colorSpace = CGColorSpaceCreateDeviceRGB(); 19306 this.drawingContext = CGBitmapContextCreate(null, width, height, 8, 4*width, colorSpace, kCGImageAlphaPremultipliedLast |kCGBitmapByteOrder32Big); 19307 CGColorSpaceRelease(colorSpace); 19308 CGContextSelectFont(drawingContext, "Lucida Grande", 12.0f, 1); 19309 auto matrix = CGContextGetTextMatrix(drawingContext); 19310 matrix.c = -matrix.c; 19311 matrix.d = -matrix.d; 19312 CGContextSetTextMatrix(drawingContext, matrix); 19313 19314 } 19315 19316 void dispose() { 19317 closeWindow(); 19318 // window.release(); // closing the window does this automatically i think 19319 } 19320 void closeWindow() { 19321 if(timer) 19322 timer.invalidate(); 19323 window.close(); 19324 } 19325 19326 ScreenPainter getPainter(bool manualInvalidations) { 19327 return ScreenPainter(this, this.window, manualInvalidations); 19328 } 19329 19330 NSWindow window; 19331 NSTimer timer; 19332 NSView view; 19333 CGContextRef drawingContext; 19334 } 19335 } 19336 19337 version(without_opengl) {} else 19338 extern(System) nothrow @nogc { 19339 //enum uint GL_VERSION = 0x1F02; 19340 //const(char)* glGetString (/*GLenum*/uint); 19341 version(X11) { 19342 static if (!SdpyIsUsingIVGLBinds) { 19343 19344 enum GLX_X_RENDERABLE = 0x8012; 19345 enum GLX_DRAWABLE_TYPE = 0x8010; 19346 enum GLX_RENDER_TYPE = 0x8011; 19347 enum GLX_X_VISUAL_TYPE = 0x22; 19348 enum GLX_TRUE_COLOR = 0x8002; 19349 enum GLX_WINDOW_BIT = 0x00000001; 19350 enum GLX_RGBA_BIT = 0x00000001; 19351 enum GLX_COLOR_INDEX_BIT = 0x00000002; 19352 enum GLX_SAMPLE_BUFFERS = 0x186a0; 19353 enum GLX_SAMPLES = 0x186a1; 19354 enum GLX_CONTEXT_MAJOR_VERSION_ARB = 0x2091; 19355 enum GLX_CONTEXT_MINOR_VERSION_ARB = 0x2092; 19356 } 19357 19358 // GLX_EXT_swap_control 19359 alias glXSwapIntervalEXT = void function (Display* dpy, /*GLXDrawable*/Drawable drawable, int interval); 19360 private __gshared glXSwapIntervalEXT _glx_swapInterval_fn = null; 19361 19362 //k8: ugly code to prevent warnings when sdpy is compiled into .a 19363 extern(System) { 19364 alias glXCreateContextAttribsARB_fna = GLXContext function (Display *dpy, GLXFBConfig config, GLXContext share_context, /*Bool*/int direct, const(int)* attrib_list); 19365 } 19366 private __gshared /*glXCreateContextAttribsARB_fna*/void* glXCreateContextAttribsARBFn = cast(void*)1; //HACK! 19367 19368 // this made public so we don't have to get it again and again 19369 public bool glXCreateContextAttribsARB_present () @system { 19370 if (glXCreateContextAttribsARBFn is cast(void*)1) { 19371 // get it 19372 glXCreateContextAttribsARBFn = cast(void*)glbindGetProcAddress("glXCreateContextAttribsARB"); 19373 //{ import core.stdc.stdio; printf("checking glXCreateContextAttribsARB: %shere\n", (glXCreateContextAttribsARBFn !is null ? "".ptr : "not ".ptr)); } 19374 } 19375 return (glXCreateContextAttribsARBFn !is null); 19376 } 19377 19378 // this made public so we don't have to get it again and again 19379 public GLXContext glXCreateContextAttribsARB (Display *dpy, GLXFBConfig config, GLXContext share_context, /*Bool*/int direct, const(int)* attrib_list) @system { 19380 if (!glXCreateContextAttribsARB_present()) assert(0, "glXCreateContextAttribsARB is not present"); 19381 return (cast(glXCreateContextAttribsARB_fna)glXCreateContextAttribsARBFn)(dpy, config, share_context, direct, attrib_list); 19382 } 19383 19384 // extern(C) private __gshared int function(int) glXSwapIntervalSGI; // seems totally redundant to the tohers 19385 extern(C) private __gshared int function(int) glXSwapIntervalMESA; 19386 19387 void glxSetVSync (Display* dpy, /*GLXDrawable*/Drawable drawable, bool wait) { 19388 if (cast(void*)_glx_swapInterval_fn is cast(void*)1) return; 19389 if (_glx_swapInterval_fn is null) { 19390 _glx_swapInterval_fn = cast(glXSwapIntervalEXT)glXGetProcAddress("glXSwapIntervalEXT"); 19391 if (_glx_swapInterval_fn is null) { 19392 _glx_swapInterval_fn = cast(glXSwapIntervalEXT)1; 19393 return; 19394 } 19395 version(sdddd) { debug writeln("glXSwapIntervalEXT found!"); } 19396 } 19397 19398 if(glXSwapIntervalMESA is null) { 19399 // it seems to require both to actually take effect on many computers 19400 // idk why 19401 glXSwapIntervalMESA = cast(typeof(glXSwapIntervalMESA)) glXGetProcAddress("glXSwapIntervalMESA"); 19402 if(glXSwapIntervalMESA is null) 19403 glXSwapIntervalMESA = cast(typeof(glXSwapIntervalMESA)) 1; 19404 } 19405 19406 if(cast(void*) glXSwapIntervalMESA > cast(void*) 1) 19407 glXSwapIntervalMESA(wait ? 1 : 0); 19408 19409 _glx_swapInterval_fn(dpy, drawable, (wait ? 1 : 0)); 19410 } 19411 } else version(Windows) { 19412 static if (!SdpyIsUsingIVGLBinds) { 19413 enum GL_TRUE = 1; 19414 enum GL_FALSE = 0; 19415 19416 public void* glbindGetProcAddress (const(char)* name) { 19417 void* res = wglGetProcAddress(name); 19418 if (res is null) { 19419 /+ 19420 //{ import core.stdc.stdio; printf("GL: '%s' not found (0)\n", name); } 19421 import core.sys.windows.windef, core.sys.windows.winbase; 19422 __gshared HINSTANCE dll = null; 19423 if (dll is null) { 19424 dll = LoadLibraryA("opengl32.dll"); 19425 if (dll is null) return null; // <32, but idc 19426 } 19427 res = GetProcAddress(dll, name); 19428 +/ 19429 res = GetProcAddress(gl.libHandle, name); 19430 } 19431 //{ import core.stdc.stdio; printf(" GL: '%s' is 0x%08x\n", name, cast(uint)res); } 19432 return res; 19433 } 19434 } 19435 19436 19437 private __gshared extern(System) BOOL function(int) wglSwapIntervalEXT; 19438 void wglSetVSync(bool wait) { 19439 if(wglSwapIntervalEXT is null) { 19440 wglSwapIntervalEXT = cast(typeof(wglSwapIntervalEXT)) wglGetProcAddress("wglSwapIntervalEXT"); 19441 if(wglSwapIntervalEXT is null) 19442 wglSwapIntervalEXT = cast(typeof(wglSwapIntervalEXT)) 1; 19443 } 19444 if(cast(void*) wglSwapIntervalEXT is cast(void*) 1) 19445 return; 19446 19447 wglSwapIntervalEXT(wait ? 1 : 0); 19448 } 19449 19450 enum WGL_CONTEXT_MAJOR_VERSION_ARB = 0x2091; 19451 enum WGL_CONTEXT_MINOR_VERSION_ARB = 0x2092; 19452 enum WGL_CONTEXT_LAYER_PLANE_ARB = 0x2093; 19453 enum WGL_CONTEXT_FLAGS_ARB = 0x2094; 19454 enum WGL_CONTEXT_PROFILE_MASK_ARB = 0x9126; 19455 19456 enum WGL_CONTEXT_DEBUG_BIT_ARB = 0x0001; 19457 enum WGL_CONTEXT_FORWARD_COMPATIBLE_BIT_ARB = 0x0002; 19458 19459 enum WGL_CONTEXT_CORE_PROFILE_BIT_ARB = 0x00000001; 19460 enum WGL_CONTEXT_COMPATIBILITY_PROFILE_BIT_ARB = 0x00000002; 19461 19462 alias wglCreateContextAttribsARB_fna = HGLRC function (HDC hDC, HGLRC hShareContext, const(int)* attribList); 19463 __gshared wglCreateContextAttribsARB_fna wglCreateContextAttribsARB = null; 19464 19465 void wglInitOtherFunctions () { 19466 if (wglCreateContextAttribsARB is null) { 19467 wglCreateContextAttribsARB = cast(wglCreateContextAttribsARB_fna)glbindGetProcAddress("wglCreateContextAttribsARB"); 19468 } 19469 } 19470 } 19471 19472 static if (!SdpyIsUsingIVGLBinds) { 19473 19474 interface GL { 19475 extern(System) @nogc nothrow: 19476 19477 void glGetIntegerv(int, void*); 19478 void glMatrixMode(int); 19479 void glPushMatrix(); 19480 void glLoadIdentity(); 19481 void glOrtho(double, double, double, double, double, double); 19482 void glFrustum(double, double, double, double, double, double); 19483 19484 void glPopMatrix(); 19485 void glEnable(int); 19486 void glDisable(int); 19487 void glClear(int); 19488 void glBegin(int); 19489 void glVertex2f(float, float); 19490 void glVertex3f(float, float, float); 19491 void glEnd(); 19492 void glColor3b(byte, byte, byte); 19493 void glColor3ub(ubyte, ubyte, ubyte); 19494 void glColor4b(byte, byte, byte, byte); 19495 void glColor4ub(ubyte, ubyte, ubyte, ubyte); 19496 void glColor3i(int, int, int); 19497 void glColor3ui(uint, uint, uint); 19498 void glColor4i(int, int, int, int); 19499 void glColor4ui(uint, uint, uint, uint); 19500 void glColor3f(float, float, float); 19501 void glColor4f(float, float, float, float); 19502 void glTranslatef(float, float, float); 19503 void glScalef(float, float, float); 19504 version(X11) { 19505 void glSecondaryColor3b(byte, byte, byte); 19506 void glSecondaryColor3ub(ubyte, ubyte, ubyte); 19507 void glSecondaryColor3i(int, int, int); 19508 void glSecondaryColor3ui(uint, uint, uint); 19509 void glSecondaryColor3f(float, float, float); 19510 } 19511 19512 void glDrawElements(int, int, int, void*); 19513 19514 void glRotatef(float, float, float, float); 19515 19516 uint glGetError(); 19517 19518 void glDeleteTextures(int, uint*); 19519 19520 19521 void glRasterPos2i(int, int); 19522 void glDrawPixels(int, int, uint, uint, void*); 19523 void glClearColor(float, float, float, float); 19524 19525 19526 void glPixelStorei(uint, int); 19527 19528 void glGenTextures(uint, uint*); 19529 void glBindTexture(int, int); 19530 void glTexParameteri(uint, uint, int); 19531 void glTexParameterf(uint/*GLenum*/ target, uint/*GLenum*/ pname, float param); 19532 void glTexImage2D(int, int, int, int, int, int, int, int, scope const void*); 19533 void glTexSubImage2D(uint/*GLenum*/ target, int level, int xoffset, int yoffset, 19534 /*GLsizei*/int width, /*GLsizei*/int height, 19535 uint/*GLenum*/ format, uint/*GLenum*/ type, scope const void* pixels); 19536 void glTexEnvf(uint/*GLenum*/ target, uint/*GLenum*/ pname, float param); 19537 19538 void glLineWidth(int); 19539 19540 19541 void glTexCoord2f(float, float); 19542 void glVertex2i(int, int); 19543 void glBlendFunc (int, int); 19544 void glDepthFunc (int); 19545 void glViewport(int, int, int, int); 19546 19547 void glClearDepth(double); 19548 19549 void glReadBuffer(uint); 19550 void glReadPixels(int, int, int, int, int, int, void*); 19551 19552 void glScissor(GLint x, GLint y, GLsizei width, GLsizei height); 19553 19554 void glFlush(); 19555 void glFinish(); 19556 19557 version(Windows) { 19558 BOOL wglCopyContext(HGLRC, HGLRC, UINT); 19559 HGLRC wglCreateContext(HDC); 19560 HGLRC wglCreateLayerContext(HDC, int); 19561 BOOL wglDeleteContext(HGLRC); 19562 BOOL wglDescribeLayerPlane(HDC, int, int, UINT, LPLAYERPLANEDESCRIPTOR); 19563 HGLRC wglGetCurrentContext(); 19564 HDC wglGetCurrentDC(); 19565 int wglGetLayerPaletteEntries(HDC, int, int, int, COLORREF*); 19566 PROC wglGetProcAddress(LPCSTR); 19567 BOOL wglMakeCurrent(HDC, HGLRC); 19568 BOOL wglRealizeLayerPalette(HDC, int, BOOL); 19569 int wglSetLayerPaletteEntries(HDC, int, int, int, const(COLORREF)*); 19570 BOOL wglShareLists(HGLRC, HGLRC); 19571 BOOL wglSwapLayerBuffers(HDC, UINT); 19572 BOOL wglUseFontBitmapsA(HDC, DWORD, DWORD, DWORD); 19573 BOOL wglUseFontBitmapsW(HDC, DWORD, DWORD, DWORD); 19574 BOOL wglUseFontOutlinesA(HDC, DWORD, DWORD, DWORD, FLOAT, FLOAT, int, LPGLYPHMETRICSFLOAT); 19575 BOOL wglUseFontOutlinesW(HDC, DWORD, DWORD, DWORD, FLOAT, FLOAT, int, LPGLYPHMETRICSFLOAT); 19576 } 19577 19578 } 19579 19580 interface GL3 { 19581 extern(System) @nogc nothrow: 19582 19583 void glGenVertexArrays(GLsizei, GLuint*); 19584 void glBindVertexArray(GLuint); 19585 void glDeleteVertexArrays(GLsizei, const(GLuint)*); 19586 void glGenerateMipmap(GLenum); 19587 void glBufferSubData(GLenum, GLintptr, GLsizeiptr, const(GLvoid)*); 19588 void glStencilMask(GLuint); 19589 void glStencilFunc(GLenum, GLint, GLuint); 19590 void glGetShaderInfoLog(GLuint, GLsizei, GLsizei*, GLchar*); 19591 void glGetProgramInfoLog(GLuint, GLsizei, GLsizei*, GLchar*); 19592 GLuint glCreateProgram(); 19593 GLuint glCreateShader(GLenum); 19594 void glShaderSource(GLuint, GLsizei, const(GLchar*)*, const(GLint)*); 19595 void glCompileShader(GLuint); 19596 void glGetShaderiv(GLuint, GLenum, GLint*); 19597 void glAttachShader(GLuint, GLuint); 19598 void glBindAttribLocation(GLuint, GLuint, const(GLchar)*); 19599 void glLinkProgram(GLuint); 19600 void glGetProgramiv(GLuint, GLenum, GLint*); 19601 void glDeleteProgram(GLuint); 19602 void glDeleteShader(GLuint); 19603 GLint glGetUniformLocation(GLuint, const(GLchar)*); 19604 void glGenBuffers(GLsizei, GLuint*); 19605 19606 void glUniform1f(GLint location, GLfloat v0); 19607 void glUniform2f(GLint location, GLfloat v0, GLfloat v1); 19608 void glUniform3f(GLint location, GLfloat v0, GLfloat v1, GLfloat v2); 19609 void glUniform4f(GLint location, GLfloat v0, GLfloat v1, GLfloat v2, GLfloat v3); 19610 void glUniform1i(GLint location, GLint v0); 19611 void glUniform2i(GLint location, GLint v0, GLint v1); 19612 void glUniform3i(GLint location, GLint v0, GLint v1, GLint v2); 19613 void glUniform4i(GLint location, GLint v0, GLint v1, GLint v2, GLint v3); 19614 void glUniform1ui(GLint location, GLuint v0); 19615 void glUniform2ui(GLint location, GLuint v0, GLuint v1); 19616 void glUniform3ui(GLint location, GLuint v0, GLuint v1, GLuint v2); 19617 void glUniform4ui(GLint location, GLuint v0, GLuint v1, GLuint v2, GLuint v3); 19618 void glUniform1fv(GLint location, GLsizei count, const GLfloat *value); 19619 void glUniform2fv(GLint location, GLsizei count, const GLfloat *value); 19620 void glUniform3fv(GLint location, GLsizei count, const GLfloat *value); 19621 void glUniform4fv(GLint location, GLsizei count, const GLfloat *value); 19622 void glUniform1iv(GLint location, GLsizei count, const GLint *value); 19623 void glUniform2iv(GLint location, GLsizei count, const GLint *value); 19624 void glUniform3iv(GLint location, GLsizei count, const GLint *value); 19625 void glUniform4iv(GLint location, GLsizei count, const GLint *value); 19626 void glUniform1uiv(GLint location, GLsizei count, const GLuint *value); 19627 void glUniform2uiv(GLint location, GLsizei count, const GLuint *value); 19628 void glUniform3uiv(GLint location, GLsizei count, const GLuint *value); 19629 void glUniform4uiv(GLint location, GLsizei count, const GLuint *value); 19630 void glUniformMatrix2fv(GLint location, GLsizei count, GLboolean transpose, const GLfloat *value); 19631 void glUniformMatrix3fv(GLint location, GLsizei count, GLboolean transpose, const GLfloat *value); 19632 void glUniformMatrix4fv(GLint location, GLsizei count, GLboolean transpose, const GLfloat *value); 19633 void glUniformMatrix2x3fv(GLint location, GLsizei count, GLboolean transpose, const GLfloat *value); 19634 void glUniformMatrix3x2fv(GLint location, GLsizei count, GLboolean transpose, const GLfloat *value); 19635 void glUniformMatrix2x4fv(GLint location, GLsizei count, GLboolean transpose, const GLfloat *value); 19636 void glUniformMatrix4x2fv(GLint location, GLsizei count, GLboolean transpose, const GLfloat *value); 19637 void glUniformMatrix3x4fv(GLint location, GLsizei count, GLboolean transpose, const GLfloat *value); 19638 void glUniformMatrix4x3fv(GLint location, GLsizei count, GLboolean transpose, const GLfloat *value); 19639 19640 void glColorMask(GLboolean, GLboolean, GLboolean, GLboolean); 19641 void glStencilOpSeparate(GLenum, GLenum, GLenum, GLenum); 19642 void glDrawArrays(GLenum, GLint, GLsizei); 19643 void glStencilOp(GLenum, GLenum, GLenum); 19644 void glUseProgram(GLuint); 19645 void glCullFace(GLenum); 19646 void glFrontFace(GLenum); 19647 void glActiveTexture(GLenum); 19648 void glBindBuffer(GLenum, GLuint); 19649 void glBufferData(GLenum, GLsizeiptr, const(void)*, GLenum); 19650 void glEnableVertexAttribArray(GLuint); 19651 void glVertexAttribPointer(GLuint, GLint, GLenum, GLboolean, GLsizei, const(void)*); 19652 void glUniform1i(GLint, GLint); 19653 void glUniform2fv(GLint, GLsizei, const(GLfloat)*); 19654 void glDisableVertexAttribArray(GLuint); 19655 void glDeleteBuffers(GLsizei, const(GLuint)*); 19656 void glBlendFuncSeparate(GLenum, GLenum, GLenum, GLenum); 19657 void glLogicOp (GLenum opcode); 19658 void glFramebufferTexture2D (GLenum target, GLenum attachment, GLenum textarget, GLuint texture, GLint level); 19659 void glDeleteFramebuffers (GLsizei n, const(GLuint)* framebuffers); 19660 void glGenFramebuffers (GLsizei n, GLuint* framebuffers); 19661 GLenum glCheckFramebufferStatus (GLenum target); 19662 void glBindFramebuffer (GLenum target, GLuint framebuffer); 19663 } 19664 19665 interface GL4 { 19666 extern(System) @nogc nothrow: 19667 19668 void glTextureSubImage2D(uint texture, int level, int xoffset, int yoffset, 19669 /*GLsizei*/int width, /*GLsizei*/int height, 19670 uint/*GLenum*/ format, uint/*GLenum*/ type, scope const void* pixels); 19671 } 19672 19673 interface GLU { 19674 extern(System) @nogc nothrow: 19675 19676 void gluLookAt(double, double, double, double, double, double, double, double, double); 19677 void gluPerspective(double, double, double, double); 19678 19679 char* gluErrorString(uint); 19680 } 19681 19682 19683 enum GL_RED = 0x1903; 19684 enum GL_ALPHA = 0x1906; 19685 19686 enum uint GL_FRONT = 0x0404; 19687 19688 enum uint GL_BLEND = 0x0be2; 19689 enum uint GL_LEQUAL = 0x0203; 19690 19691 19692 enum uint GL_RGB = 0x1907; 19693 enum uint GL_BGRA = 0x80e1; 19694 enum uint GL_RGBA = 0x1908; 19695 enum uint GL_RGBA8 = 0x8058; 19696 enum uint GL_TEXTURE_2D = 0x0DE1; 19697 enum uint GL_TEXTURE_MIN_FILTER = 0x2801; 19698 enum uint GL_NEAREST = 0x2600; 19699 enum uint GL_LINEAR = 0x2601; 19700 enum uint GL_TEXTURE_MAG_FILTER = 0x2800; 19701 enum uint GL_TEXTURE_WRAP_S = 0x2802; 19702 enum uint GL_TEXTURE_WRAP_T = 0x2803; 19703 enum uint GL_REPEAT = 0x2901; 19704 enum uint GL_CLAMP = 0x2900; 19705 enum uint GL_CLAMP_TO_EDGE = 0x812F; 19706 enum uint GL_CLAMP_TO_BORDER = 0x812D; 19707 enum uint GL_DECAL = 0x2101; 19708 enum uint GL_MODULATE = 0x2100; 19709 enum uint GL_TEXTURE_ENV = 0x2300; 19710 enum uint GL_TEXTURE_ENV_MODE = 0x2200; 19711 enum uint GL_REPLACE = 0x1E01; 19712 enum uint GL_LIGHTING = 0x0B50; 19713 enum uint GL_DITHER = 0x0BD0; 19714 19715 enum uint GL_NO_ERROR = 0; 19716 19717 19718 19719 enum int GL_VIEWPORT = 0x0BA2; 19720 enum int GL_MODELVIEW = 0x1700; 19721 enum int GL_TEXTURE = 0x1702; 19722 enum int GL_PROJECTION = 0x1701; 19723 enum int GL_DEPTH_TEST = 0x0B71; 19724 19725 enum int GL_COLOR_BUFFER_BIT = 0x00004000; 19726 enum int GL_ACCUM_BUFFER_BIT = 0x00000200; 19727 enum int GL_DEPTH_BUFFER_BIT = 0x00000100; 19728 enum uint GL_STENCIL_BUFFER_BIT = 0x00000400; 19729 19730 enum int GL_POINTS = 0x0000; 19731 enum int GL_LINES = 0x0001; 19732 enum int GL_LINE_LOOP = 0x0002; 19733 enum int GL_LINE_STRIP = 0x0003; 19734 enum int GL_TRIANGLES = 0x0004; 19735 enum int GL_TRIANGLE_STRIP = 5; 19736 enum int GL_TRIANGLE_FAN = 6; 19737 enum int GL_QUADS = 7; 19738 enum int GL_QUAD_STRIP = 8; 19739 enum int GL_POLYGON = 9; 19740 19741 alias GLvoid = void; 19742 alias GLboolean = ubyte; 19743 alias GLint = int; 19744 alias GLuint = uint; 19745 alias GLenum = uint; 19746 alias GLchar = char; 19747 alias GLsizei = int; 19748 alias GLfloat = float; 19749 alias GLintptr = size_t; 19750 alias GLsizeiptr = ptrdiff_t; 19751 19752 19753 enum uint GL_INVALID_ENUM = 0x0500; 19754 19755 enum uint GL_ZERO = 0; 19756 enum uint GL_ONE = 1; 19757 19758 enum uint GL_BYTE = 0x1400; 19759 enum uint GL_UNSIGNED_BYTE = 0x1401; 19760 enum uint GL_SHORT = 0x1402; 19761 enum uint GL_UNSIGNED_SHORT = 0x1403; 19762 enum uint GL_INT = 0x1404; 19763 enum uint GL_UNSIGNED_INT = 0x1405; 19764 enum uint GL_FLOAT = 0x1406; 19765 enum uint GL_2_BYTES = 0x1407; 19766 enum uint GL_3_BYTES = 0x1408; 19767 enum uint GL_4_BYTES = 0x1409; 19768 enum uint GL_DOUBLE = 0x140A; 19769 19770 enum uint GL_STREAM_DRAW = 0x88E0; 19771 19772 enum uint GL_CCW = 0x0901; 19773 19774 enum uint GL_STENCIL_TEST = 0x0B90; 19775 enum uint GL_SCISSOR_TEST = 0x0C11; 19776 19777 enum uint GL_EQUAL = 0x0202; 19778 enum uint GL_NOTEQUAL = 0x0205; 19779 19780 enum uint GL_ALWAYS = 0x0207; 19781 enum uint GL_KEEP = 0x1E00; 19782 19783 enum uint GL_INCR = 0x1E02; 19784 19785 enum uint GL_INCR_WRAP = 0x8507; 19786 enum uint GL_DECR_WRAP = 0x8508; 19787 19788 enum uint GL_CULL_FACE = 0x0B44; 19789 enum uint GL_BACK = 0x0405; 19790 19791 enum uint GL_FRAGMENT_SHADER = 0x8B30; 19792 enum uint GL_VERTEX_SHADER = 0x8B31; 19793 19794 enum uint GL_COMPILE_STATUS = 0x8B81; 19795 enum uint GL_LINK_STATUS = 0x8B82; 19796 19797 enum uint GL_ELEMENT_ARRAY_BUFFER = 0x8893; 19798 19799 enum uint GL_STATIC_DRAW = 0x88E4; 19800 19801 enum uint GL_UNPACK_ALIGNMENT = 0x0CF5; 19802 enum uint GL_UNPACK_ROW_LENGTH = 0x0CF2; 19803 enum uint GL_UNPACK_SKIP_PIXELS = 0x0CF4; 19804 enum uint GL_UNPACK_SKIP_ROWS = 0x0CF3; 19805 19806 enum uint GL_GENERATE_MIPMAP = 0x8191; 19807 enum uint GL_LINEAR_MIPMAP_LINEAR = 0x2703; 19808 19809 enum uint GL_TEXTURE0 = 0x84C0U; 19810 enum uint GL_TEXTURE1 = 0x84C1U; 19811 19812 enum uint GL_ARRAY_BUFFER = 0x8892; 19813 19814 enum uint GL_SRC_COLOR = 0x0300; 19815 enum uint GL_ONE_MINUS_SRC_COLOR = 0x0301; 19816 enum uint GL_SRC_ALPHA = 0x0302; 19817 enum uint GL_ONE_MINUS_SRC_ALPHA = 0x0303; 19818 enum uint GL_DST_ALPHA = 0x0304; 19819 enum uint GL_ONE_MINUS_DST_ALPHA = 0x0305; 19820 enum uint GL_DST_COLOR = 0x0306; 19821 enum uint GL_ONE_MINUS_DST_COLOR = 0x0307; 19822 enum uint GL_SRC_ALPHA_SATURATE = 0x0308; 19823 19824 enum uint GL_INVERT = 0x150AU; 19825 19826 enum uint GL_DEPTH_STENCIL = 0x84F9U; 19827 enum uint GL_UNSIGNED_INT_24_8 = 0x84FAU; 19828 19829 enum uint GL_FRAMEBUFFER = 0x8D40U; 19830 enum uint GL_COLOR_ATTACHMENT0 = 0x8CE0U; 19831 enum uint GL_DEPTH_STENCIL_ATTACHMENT = 0x821AU; 19832 19833 enum uint GL_FRAMEBUFFER_COMPLETE = 0x8CD5U; 19834 enum uint GL_FRAMEBUFFER_INCOMPLETE_ATTACHMENT = 0x8CD6U; 19835 enum uint GL_FRAMEBUFFER_INCOMPLETE_MISSING_ATTACHMENT = 0x8CD7U; 19836 enum uint GL_FRAMEBUFFER_INCOMPLETE_DIMENSIONS = 0x8CD9U; 19837 enum uint GL_FRAMEBUFFER_UNSUPPORTED = 0x8CDDU; 19838 19839 enum uint GL_COLOR_LOGIC_OP = 0x0BF2U; 19840 enum uint GL_CLEAR = 0x1500U; 19841 enum uint GL_COPY = 0x1503U; 19842 enum uint GL_XOR = 0x1506U; 19843 19844 enum uint GL_FRAMEBUFFER_BINDING = 0x8CA6U; 19845 19846 enum uint GL_TEXTURE_LOD_BIAS = 0x8501; 19847 19848 } 19849 } 19850 19851 /++ 19852 History: 19853 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. 19854 +/ 19855 __gshared bool gluSuccessfullyLoaded = true; 19856 19857 version(without_opengl) {} else { 19858 static if(!SdpyIsUsingIVGLBinds) { 19859 version(Windows) { 19860 mixin DynamicLoad!(GL, "opengl32", 1, openGlLibrariesSuccessfullyLoaded) gl; 19861 mixin DynamicLoad!(GLU, "glu32", 1, gluSuccessfullyLoaded) glu; 19862 } else { 19863 mixin DynamicLoad!(GL, "GL", 1, openGlLibrariesSuccessfullyLoaded) gl; 19864 mixin DynamicLoad!(GLU, "GLU", 3, gluSuccessfullyLoaded) glu; 19865 } 19866 mixin DynamicLoadSupplementalOpenGL!(GL3) gl3; 19867 19868 19869 shared static this() { 19870 gl.loadDynamicLibrary(); 19871 19872 // FIXME: this is NOT actually required and should NOT fail if it is not loaded 19873 // unless those functions are actually used 19874 // go to mark b openGlLibrariesSuccessfullyLoaded = false; 19875 glu.loadDynamicLibrary(); 19876 } 19877 } 19878 } 19879 19880 /++ 19881 Convenience method for converting D arrays to opengl buffer data 19882 19883 I would LOVE to overload it with the original glBufferData, but D won't 19884 let me since glBufferData is a function pointer :( 19885 19886 Added: August 25, 2020 (version 8.5) 19887 +/ 19888 version(without_opengl) {} else 19889 void glBufferDataSlice(GLenum target, const(void[]) data, GLenum usage) { 19890 glBufferData(target, data.length, data.ptr, usage); 19891 } 19892 19893 /++ 19894 History: 19895 Added September 1, 2024 19896 +/ 19897 version(without_opengl) {} else 19898 void glBufferSubDataSlice(GLenum target, size_t offset, const(void[]) data, GLenum usage) { 19899 glBufferSubData(target, offset, data.length, data.ptr); 19900 } 19901 19902 /++ 19903 Convenience class for using opengl shaders. 19904 19905 Ensure that you've loaded opengl 3+ and set your active 19906 context before trying to use this. 19907 19908 Added: August 25, 2020 (version 8.5) 19909 +/ 19910 version(without_opengl) {} else 19911 final class OpenGlShader { 19912 private int shaderProgram_; 19913 private @property void shaderProgram(int a) { 19914 shaderProgram_ = a; 19915 } 19916 /// Get the program ID for use in OpenGL functions. 19917 public @property int shaderProgram() { 19918 return shaderProgram_; 19919 } 19920 19921 /++ 19922 19923 +/ 19924 static struct Source { 19925 uint type; /// GL_FRAGMENT_SHADER, GL_VERTEX_SHADER, etc. 19926 string code; /// 19927 } 19928 19929 /++ 19930 Helper method to just compile some shader code and check for errors 19931 while you do glCreateShader, etc. on the outside yourself. 19932 19933 This just does `glShaderSource` and `glCompileShader` for the given code. 19934 19935 If you the OpenGlShader class constructor, you never need to call this yourself. 19936 +/ 19937 static void compile(int sid, Source code) { 19938 const(char)*[1] buffer; 19939 int[1] lengthBuffer; 19940 19941 buffer[0] = code.code.ptr; 19942 lengthBuffer[0] = cast(int) code.code.length; 19943 19944 glShaderSource(sid, cast(int) buffer.length, buffer.ptr, lengthBuffer.ptr); 19945 glCompileShader(sid); 19946 19947 int success; 19948 glGetShaderiv(sid, GL_COMPILE_STATUS, &success); 19949 if(!success) { 19950 char[512] info; 19951 int len; 19952 glGetShaderInfoLog(sid, info.length, &len, info.ptr); 19953 19954 throw new Exception("Shader compile failure: " ~ cast(immutable) info[0 .. len]); 19955 } 19956 } 19957 19958 /++ 19959 Calls `glLinkProgram` and throws if error a occurs. 19960 19961 If you the OpenGlShader class constructor, you never need to call this yourself. 19962 +/ 19963 static void link(int shaderProgram) { 19964 glLinkProgram(shaderProgram); 19965 int success; 19966 glGetProgramiv(shaderProgram, GL_LINK_STATUS, &success); 19967 if(!success) { 19968 char[512] info; 19969 int len; 19970 glGetProgramInfoLog(shaderProgram, info.length, &len, info.ptr); 19971 19972 throw new Exception("Shader link failure: " ~ cast(immutable) info[0 .. len]); 19973 } 19974 } 19975 19976 /++ 19977 Constructs the shader object by calling `glCreateProgram`, then 19978 compiling each given [Source], and finally, linking them together. 19979 19980 Throws: on compile or link failure. 19981 +/ 19982 this(Source[] codes...) { 19983 shaderProgram = glCreateProgram(); 19984 19985 int[16] shadersBufferStack; 19986 19987 int[] shadersBuffer = codes.length <= shadersBufferStack.length ? 19988 shadersBufferStack[0 .. codes.length] : 19989 new int[](codes.length); 19990 19991 foreach(idx, code; codes) { 19992 shadersBuffer[idx] = glCreateShader(code.type); 19993 19994 compile(shadersBuffer[idx], code); 19995 19996 glAttachShader(shaderProgram, shadersBuffer[idx]); 19997 } 19998 19999 link(shaderProgram); 20000 20001 foreach(s; shadersBuffer) 20002 glDeleteShader(s); 20003 } 20004 20005 /// Calls `glUseProgram(this.shaderProgram)` 20006 void use() { 20007 glUseProgram(this.shaderProgram); 20008 } 20009 20010 /// Deletes the program. 20011 void delete_() { 20012 glDeleteProgram(shaderProgram); 20013 shaderProgram = 0; 20014 } 20015 20016 /++ 20017 [OpenGlShader.uniforms].name gives you one of these. 20018 20019 You can get the id out of it or just assign 20020 +/ 20021 static struct Uniform { 20022 /// the id passed to glUniform* 20023 int id; 20024 20025 /// Assigns the 4 floats. You will probably have to call this via the .opAssign name 20026 void opAssign(float x, float y, float z, float w) { 20027 if(id != -1) 20028 glUniform4f(id, x, y, z, w); 20029 } 20030 20031 void opAssign(float x) { 20032 if(id != -1) 20033 glUniform1f(id, x); 20034 } 20035 20036 void opAssign(float x, float y) { 20037 if(id != -1) 20038 glUniform2f(id, x, y); 20039 } 20040 20041 void opAssign(T)(T t) { 20042 t.glUniform(id); 20043 } 20044 } 20045 20046 static struct UniformsHelper { 20047 OpenGlShader _shader; 20048 20049 @property Uniform opDispatch(string name)() { 20050 auto i = glGetUniformLocation(_shader.shaderProgram, name.ptr); 20051 // FIXME: decide what to do here; the exception is liable to be swallowed by the event syste 20052 //if(i == -1) 20053 //throw new Exception("Could not find uniform " ~ name); 20054 return Uniform(i); 20055 } 20056 20057 @property void opDispatch(string name, T)(T t) { 20058 Uniform f = this.opDispatch!name; 20059 t.glUniform(f); 20060 } 20061 } 20062 20063 /++ 20064 Gives access to the uniforms through dot access. 20065 `OpenGlShader.Uniform = shader.uniforms.foo; // calls glGetUniformLocation(this, "foo"); 20066 +/ 20067 @property UniformsHelper uniforms() { return UniformsHelper(this); } 20068 } 20069 20070 version(without_opengl) {} else { 20071 /++ 20072 A static container of experimental types and value constructors for opengl 3+ shaders. 20073 20074 20075 You can declare variables like: 20076 20077 ``` 20078 OGL.vec3f something; 20079 ``` 20080 20081 But generally it would be used with [OpenGlShader]'s uniform helpers like 20082 20083 ``` 20084 shader.uniforms.mouse = OGL.vec(mouseX, mouseY); // or OGL.vec2f if you want to be more specific 20085 ``` 20086 20087 This is still extremely experimental, not very useful at this point, and thus subject to change at random. 20088 20089 20090 History: 20091 Added December 7, 2021. Not yet stable. 20092 +/ 20093 final class OGL { 20094 static: 20095 20096 private template typeFromSpecifier(string specifier) { 20097 static if(specifier == "f") 20098 alias typeFromSpecifier = GLfloat; 20099 else static if(specifier == "i") 20100 alias typeFromSpecifier = GLint; 20101 else static if(specifier == "ui") 20102 alias typeFromSpecifier = GLuint; 20103 else static assert(0, "I don't know this ogl type suffix " ~ specifier); 20104 } 20105 20106 private template CommonType(T...) { 20107 static if(T.length == 1) 20108 alias CommonType = T[0]; 20109 else static if(is(typeof(true ? T[0].init : T[1].init) C)) 20110 alias CommonType = CommonType!(C, T[2 .. $]); 20111 } 20112 20113 private template typesToSpecifier(T...) { 20114 static if(is(CommonType!T == float)) 20115 enum typesToSpecifier = "f"; 20116 else static if(is(CommonType!T == int)) 20117 enum typesToSpecifier = "i"; 20118 else static if(is(CommonType!T == uint)) 20119 enum typesToSpecifier = "ui"; 20120 else static assert(0, "I can't find a gl type suffix for common type " ~ CommonType!T.stringof); 20121 } 20122 20123 private template genNames(size_t dim, size_t dim2 = 0) { 20124 string helper() { 20125 string s; 20126 if(dim2) { 20127 static if(__VERSION__ < 2102) 20128 s ~= "type["~(dim + '0')~"]["~(dim2 + '0')~"] matrix = void;"; // stupid compiler bug 20129 else 20130 s ~= "type["~(dim + '0')~"]["~(dim2 + '0')~"] matrix = 0;"; 20131 } else { 20132 if(dim > 0) s ~= "type x = 0;"; 20133 if(dim > 1) s ~= "type y = 0;"; 20134 if(dim > 2) s ~= "type z = 0;"; 20135 if(dim > 3) s ~= "type w = 0;"; 20136 } 20137 20138 s ~= "this(typeof(this.tupleof) args) { this.tupleof = args; }"; 20139 if(dim2) 20140 s ~= "this(type["~(dim*dim2).stringof~"] t) { (cast(typeof(t)) this.matrix)[] = t[]; }"; 20141 20142 return s; 20143 } 20144 20145 enum genNames = helper(); 20146 } 20147 20148 // there's vec, arrays of vec, mat, and arrays of mat 20149 template opDispatch(string name) 20150 if(name.length > 4 && (name[0 .. 3] == "vec" || name[0 .. 3] == "mat")) 20151 { 20152 static if(name[4] == 'x') { 20153 enum dimX = cast(int) (name[3] - '0'); 20154 static assert(dimX > 0 && dimX <= 4, "Bad dimension for OGL X type " ~ name[3]); 20155 20156 enum dimY = cast(int) (name[5] - '0'); 20157 static assert(dimY > 0 && dimY <= 4, "Bad dimension for OGL Y type " ~ name[5]); 20158 20159 enum isArray = name[$ - 1] == 'v'; 20160 enum typeSpecifier = isArray ? name[6 .. $ - 1] : name[6 .. $]; 20161 alias type = typeFromSpecifier!typeSpecifier; 20162 } else { 20163 enum dim = cast(int) (name[3] - '0'); 20164 static assert(dim > 0 && dim <= 4, "Bad dimension for OGL type " ~ name[3]); 20165 enum isArray = name[$ - 1] == 'v'; 20166 enum typeSpecifier = isArray ? name[4 .. $ - 1] : name[4 .. $]; 20167 alias type = typeFromSpecifier!typeSpecifier; 20168 } 20169 20170 align(1) 20171 struct opDispatch { 20172 align(1): 20173 static if(name[4] == 'x') 20174 mixin(genNames!(dimX, dimY)); 20175 else 20176 mixin(genNames!dim); 20177 20178 private void glUniform(OpenGlShader.Uniform assignTo) { 20179 glUniform(assignTo.id); 20180 } 20181 private void glUniform(int assignTo) { 20182 static if(name[4] == 'x') { 20183 static if(name[3] == name[5]) { 20184 // import std.stdio; writeln(name, " ", this.matrix, dimX, " ", dimY); 20185 mixin("glUniformMatrix" ~ name[5 .. $] ~ "v")(assignTo, 1, true, &this.matrix[0][0]); 20186 } else { 20187 mixin("glUniformMatrix" ~ name[3 .. $] ~ "v")(assignTo, 1, false, this.matrix.ptr); 20188 } 20189 } else 20190 mixin("glUniform" ~ name[3 .. $])(assignTo, this.tupleof); 20191 } 20192 } 20193 } 20194 20195 auto vec(T...)(T members) { 20196 return typeof(this).opDispatch!("vec" ~ toInternal!string(cast(int) T.length)~ typesToSpecifier!T)(members); 20197 } 20198 } 20199 20200 void checkGlError() { 20201 auto error = glGetError(); 20202 int[] errors; 20203 string[] errorStrings; 20204 while(error != GL_NO_ERROR) { 20205 errors ~= error; 20206 switch(error) { 20207 case 0x0500: errorStrings ~= "GL_INVALID_ENUM"; break; 20208 case 0x0501: errorStrings ~= "GL_INVALID_VALUE"; break; 20209 case 0x0502: errorStrings ~= "GL_INVALID_OPERATION"; break; 20210 case 0x0503: errorStrings ~= "GL_STACK_OVERFLOW"; break; 20211 case 0x0504: errorStrings ~= "GL_STACK_UNDERFLOW"; break; 20212 case 0x0505: errorStrings ~= "GL_OUT_OF_MEMORY"; break; 20213 default: errorStrings ~= "idk"; 20214 } 20215 error = glGetError(); 20216 } 20217 if(errors.length) 20218 throw ArsdException!"glGetError"(errors, errorStrings); 20219 } 20220 20221 /++ 20222 A matrix for simple uses that easily integrates with [OpenGlShader]. 20223 20224 Might not be useful to you since it only as some simple functions and 20225 probably isn't that fast. 20226 20227 Note it uses an inline static array for its storage, so copying it 20228 may be expensive. 20229 +/ 20230 struct BasicMatrix(int columns, int rows, T = float) { 20231 static import core.stdc.math; 20232 static if(is(T == float)) { 20233 alias cos = core.stdc.math.cosf; 20234 alias sin = core.stdc.math.sinf; 20235 } else { 20236 alias cos = core.stdc.math.cos; 20237 alias sin = core.stdc.math.sin; 20238 } 20239 20240 T[columns * rows] data = 0.0; 20241 20242 /++ 20243 20244 +/ 20245 this(T[columns * rows] data) { 20246 this.data = data; 20247 } 20248 20249 /++ 20250 Basic operations that operate *in place*. 20251 +/ 20252 static if(columns == 4 && rows == 4) 20253 void translate(T x, T y, T z) { 20254 BasicMatrix m = [ 20255 1, 0, 0, x, 20256 0, 1, 0, y, 20257 0, 0, 1, z, 20258 0, 0, 0, 1 20259 ]; 20260 20261 this *= m; 20262 } 20263 20264 /// ditto 20265 static if(columns == 4 && rows == 4) 20266 void scale(T x, T y, T z) { 20267 BasicMatrix m = [ 20268 x, 0, 0, 0, 20269 0, y, 0, 0, 20270 0, 0, z, 0, 20271 0, 0, 0, 1 20272 ]; 20273 20274 this *= m; 20275 } 20276 20277 /// ditto 20278 static if(columns == 4 && rows == 4) 20279 void rotateX(T theta) { 20280 BasicMatrix m = [ 20281 1, 0, 0, 0, 20282 0, cos(theta), -sin(theta), 0, 20283 0, sin(theta), cos(theta), 0, 20284 0, 0, 0, 1 20285 ]; 20286 20287 this *= m; 20288 } 20289 20290 /// ditto 20291 static if(columns == 4 && rows == 4) 20292 void rotateY(T theta) { 20293 BasicMatrix m = [ 20294 cos(theta), 0, sin(theta), 0, 20295 0, 1, 0, 0, 20296 -sin(theta), 0, cos(theta), 0, 20297 0, 0, 0, 1 20298 ]; 20299 20300 this *= m; 20301 } 20302 20303 /// ditto 20304 static if(columns == 4 && rows == 4) 20305 void rotateZ(T theta) { 20306 BasicMatrix m = [ 20307 cos(theta), -sin(theta), 0, 0, 20308 sin(theta), cos(theta), 0, 0, 20309 0, 0, 1, 0, 20310 0, 0, 0, 1 20311 ]; 20312 20313 this *= m; 20314 } 20315 20316 /++ 20317 20318 +/ 20319 static if(columns == rows) 20320 static BasicMatrix identity() { 20321 BasicMatrix m; 20322 foreach(i; 0 .. columns) 20323 m.data[0 + i + i * columns] = 1.0; 20324 return m; 20325 } 20326 20327 static if(columns == rows) 20328 void loadIdentity() { 20329 this = identity(); 20330 } 20331 20332 static if(columns == 4 && rows == 4) 20333 static BasicMatrix ortho(T l, T r, T b, T t, T n, T f) { 20334 return BasicMatrix([ 20335 2/(r-l), 0, 0, -(r+l)/(r-l), 20336 0, 2/(t-b), 0, -(t+b)/(t-b), 20337 0, 0, -2/(f-n), -(f+n)/(f-n), 20338 0, 0, 0, 1 20339 ]); 20340 } 20341 20342 static if(columns == 4 && rows == 4) 20343 void loadOrtho(T l, T r, T b, T t, T n, T f) { 20344 this = ortho(l, r, b, t, n, f); 20345 } 20346 20347 void opOpAssign(string op : "+")(const BasicMatrix rhs) { 20348 this.data[] += rhs.data; 20349 } 20350 void opOpAssign(string op : "-")(const BasicMatrix rhs) { 20351 this.data[] -= rhs.data; 20352 } 20353 void opOpAssign(string op : "*")(const T rhs) { 20354 this.data[] *= rhs; 20355 } 20356 void opOpAssign(string op : "/")(const T rhs) { 20357 this.data[] /= rhs; 20358 } 20359 void opOpAssign(string op : "*", BM : BasicMatrix!(rhsColumns, rhsRows, rhsT), int rhsColumns, int rhsRows, rhsT)(const BM rhs) { 20360 static assert(columns == rhsRows); 20361 auto multiplySize = columns; 20362 20363 auto tmp = this.data; // copy cuz it is a value type 20364 20365 int idx = 0; 20366 foreach(r; 0 .. rows) 20367 foreach(c; 0 .. columns) { 20368 T sum = 0.0; 20369 20370 foreach(i; 0 .. multiplySize) 20371 sum += this.data[r * columns + i] * rhs.data[i * rhsColumns + c]; 20372 20373 tmp[idx++] = sum; 20374 } 20375 20376 this.data = tmp; 20377 } 20378 } 20379 20380 unittest { 20381 auto m = BasicMatrix!(2, 2)([ 20382 1, 2, 20383 3, 4 20384 ]); 20385 20386 auto m2 = BasicMatrix!(2, 2)([ 20387 5, 6, 20388 7, 8 20389 ]); 20390 20391 //import std.conv; 20392 m *= m2; 20393 assert(m.data == [ 20394 19, 22, 20395 43, 50 20396 ]);//, to!string(m.data)); 20397 } 20398 20399 20400 20401 class GlObjectBase { 20402 protected uint _vao; 20403 protected uint _elementsCount; 20404 20405 protected uint element_buffer; 20406 20407 void gen() { 20408 glGenVertexArrays(1, &_vao); 20409 } 20410 20411 void bind() { 20412 glBindVertexArray(_vao); 20413 } 20414 20415 void dispose() { 20416 glDeleteVertexArrays(1, &_vao); 20417 } 20418 20419 void draw() { 20420 bind(); 20421 glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, element_buffer); 20422 glDrawElements(GL_TRIANGLES, _elementsCount, GL_UNSIGNED_INT, null); 20423 } 20424 } 20425 20426 /++ 20427 20428 +/ 20429 class GlObject(T) : GlObjectBase { 20430 protected uint VBO; 20431 20432 this(T[] arr, uint[] indices) { 20433 gen(); 20434 bind(); 20435 20436 glGenBuffers(1, &VBO); 20437 glGenBuffers(1, &element_buffer); 20438 20439 glBindBuffer(GL_ARRAY_BUFFER, VBO); 20440 glBufferDataSlice(GL_ARRAY_BUFFER, arr, GL_STATIC_DRAW); 20441 20442 glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, element_buffer); 20443 glBufferDataSlice(GL_ELEMENT_ARRAY_BUFFER, indices, GL_STATIC_DRAW); 20444 _elementsCount = cast(int) indices.length; 20445 20446 foreach(int idx, memberName; __traits(allMembers, T)) { 20447 static if(memberName != "__ctor") { 20448 static if(is(typeof(__traits(getMember, T, memberName)) == float[N], size_t N)) { 20449 glVertexAttribPointer(idx, N, GL_FLOAT, GL_FALSE, T.sizeof, cast(void*) __traits(getMember, T, memberName).offsetof); 20450 glEnableVertexAttribArray(idx); 20451 } else static assert(0); } 20452 } 20453 } 20454 20455 static string generateShaderDefinitions() { 20456 string code; 20457 20458 foreach(idx, memberName; __traits(allMembers, T)) { 20459 // never use stringof ladies and gents it has a LU thing at the end of it 20460 static if(memberName != "__ctor") 20461 code ~= "layout (location = " ~ idx.stringof[0..$-2] ~ ") in " ~ typeToGl!(typeof(__traits(getMember, T, memberName))) ~ " " ~ memberName ~ ";\n"; 20462 } 20463 20464 return code; 20465 } 20466 } 20467 20468 private string typeToGl(T)() { 20469 static if(is(T == float[4])) 20470 return "vec4"; 20471 else static if(is(T == float[3])) 20472 return "vec3"; 20473 else static if(is(T == float[2])) 20474 return "vec2"; 20475 else static assert(0, T.stringof); 20476 } 20477 20478 20479 } 20480 20481 version(Emscripten) { 20482 20483 } else version(linux) { 20484 version(with_eventloop) {} else { 20485 private int epollFd = -1; 20486 void prepareEventLoop() { 20487 if(epollFd != -1) 20488 return; // already initialized, no need to do it again 20489 import ep = core.sys.linux.epoll; 20490 20491 epollFd = ep.epoll_create1(ep.EPOLL_CLOEXEC); 20492 if(epollFd == -1) 20493 throw new Exception("epoll create failure"); 20494 } 20495 } 20496 } else version(Posix) { 20497 void prepareEventLoop() {} 20498 } 20499 20500 version(X11) { 20501 import core.stdc.locale : LC_ALL; // rdmd fix 20502 __gshared bool sdx_isUTF8Locale; 20503 20504 // This whole crap is used to initialize X11 locale, so that you can use XIM methods later. 20505 // Yes, there are people with non-utf locale (it's me, Ketmar!), but XIM (composing) will 20506 // not work right if app/X11 locale is not utf. This sux. That's why all that "utf detection" 20507 // anal magic is here. I (Ketmar) hope you like it. 20508 // We will use `sdx_isUTF8Locale` on XIM creation to enforce UTF-8 locale, so XCompose will 20509 // always return correct unicode symbols. The detection is here 'cause user can change locale 20510 // later. 20511 20512 // NOTE: IT IS VERY IMPORTANT THAT THIS BE THE LAST STATIC CTOR OF THE FILE since it tests librariesSuccessfullyLoaded 20513 shared static this () @system { 20514 if(!librariesSuccessfullyLoaded) 20515 return; 20516 20517 import core.stdc.locale : setlocale, LC_ALL, LC_CTYPE; 20518 20519 // this doesn't hurt; it may add some locking, but the speed is still 20520 // allows doing 60 FPS videogames; also, ignore the result, as most 20521 // users will probably won't do mulththreaded X11 anyway (and I (ketmar) 20522 // never seen this failing). 20523 if (XInitThreads() == 0) { import core.stdc.stdio; fprintf(stderr, "XInitThreads() failed!\n"); } 20524 20525 setlocale(LC_ALL, ""); 20526 // check if out locale is UTF-8 20527 auto lct = setlocale(LC_CTYPE, null); 20528 if (lct is null) { 20529 sdx_isUTF8Locale = false; 20530 } else { 20531 for (size_t idx = 0; lct[idx] && lct[idx+1] && lct[idx+2]; ++idx) { 20532 if ((lct[idx+0] == 'u' || lct[idx+0] == 'U') && 20533 (lct[idx+1] == 't' || lct[idx+1] == 'T') && 20534 (lct[idx+2] == 'f' || lct[idx+2] == 'F')) 20535 { 20536 sdx_isUTF8Locale = true; 20537 break; 20538 } 20539 } 20540 } 20541 //{ import core.stdc.stdio : stderr, fprintf; fprintf(stderr, "UTF8: %s\n", sdx_isUTF8Locale ? "tan".ptr : "ona".ptr); } 20542 } 20543 } 20544 20545 class ExperimentalTextComponent2 { 20546 /+ 20547 Stage 1: get it working monospace 20548 Stage 2: use proportional font 20549 Stage 3: allow changes in inline style 20550 Stage 4: allow new fonts and sizes in the middle 20551 Stage 5: optimize gap buffer 20552 Stage 6: optimize layout 20553 Stage 7: word wrap 20554 Stage 8: justification 20555 Stage 9: editing, selection, etc. 20556 20557 Operations: 20558 insert text 20559 overstrike text 20560 select 20561 cut 20562 modify 20563 +/ 20564 20565 /++ 20566 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. 20567 +/ 20568 this(SimpleWindow window) { 20569 this.window = window; 20570 } 20571 20572 private SimpleWindow window; 20573 20574 20575 /++ 20576 When you render a [ComponentInFlow], it returns an arbitrary number of these interfaces 20577 representing the internal parts. The first pass is focused on the x parameter, then the 20578 renderer is responsible for going back to the parts in the current line and calling 20579 adjustDownForAscent to change the y params. 20580 +/ 20581 static interface ComponentRenderHelper { 20582 20583 /+ 20584 When you do an edit, possibly stuff on the same line previously need to move (to adjust 20585 the baseline), stuff subsequent needs to move (adjust x) and possibly stuff below needs 20586 to move (adjust y to make room for new line) until you get back to the same position, 20587 then you can stop - if one thing is unchanged, nothing after it is changed too. 20588 20589 Word wrap might change this as if can rewrap tons of stuff, but the same idea applies, 20590 once you reach something that is unchanged, you can stop. 20591 +/ 20592 20593 void adjustDownForAscent(int amount); // at the end of the line it needs to do these 20594 20595 int ascent() const; 20596 int descent() const; 20597 20598 int advance() const; 20599 20600 bool endsWithExplititLineBreak() const; 20601 } 20602 20603 static interface RenderResult { 20604 /++ 20605 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. 20606 +/ 20607 void popFront(); 20608 @property bool empty() const; 20609 @property ComponentRenderHelper front() const; 20610 20611 void repositionForNextLine(Point baseline, int availableWidth); 20612 } 20613 20614 static interface ComponentInFlow { 20615 void draw(ScreenPainter painter); 20616 //RenderResult render(Point baseline, int availableWidth); // FIXME: it needs to be able to say "my cache is good, nothing different" 20617 20618 bool startsWithExplicitLineBreak() const; 20619 } 20620 20621 static class TextFlowComponent : ComponentInFlow { 20622 bool startsWithExplicitLineBreak() const { return false; } // FIXME: if it is block this can return true 20623 20624 Color foreground; 20625 Color background; 20626 20627 OperatingSystemFont font; // should NEVER be null 20628 20629 ubyte attributes; // underline, strike through, display on new block 20630 20631 version(Windows) 20632 const(wchar)[] content; 20633 else 20634 const(char)[] content; // this should NEVER have a newline, except at the end 20635 20636 RenderedComponent[] rendered; // entirely controlled by [rerender] 20637 20638 // could prolly put some spacing around it too like margin / padding 20639 20640 this(Color f, Color b, OperatingSystemFont font, ubyte attr, const(char)[] c) 20641 in { assert(font !is null); 20642 assert(!font.isNull); } 20643 do 20644 { 20645 this.foreground = f; 20646 this.background = b; 20647 this.font = font; 20648 20649 this.attributes = attr; 20650 version(Windows) { 20651 auto conversionFlags = 0;//WindowsStringConversionFlags.convertNewLines; 20652 auto sz = sizeOfConvertedWstring(c, conversionFlags); 20653 auto buffer = new wchar[](sz); 20654 this.content = makeWindowsString(c, buffer, conversionFlags); 20655 } else { 20656 this.content = c.dup; 20657 } 20658 } 20659 20660 void draw(ScreenPainter painter) { 20661 painter.setFont(this.font); 20662 painter.outlineColor = this.foreground; 20663 painter.fillColor = Color.transparent; 20664 foreach(rendered; this.rendered) { 20665 // the component works in term of baseline, 20666 // but the painter works in term of upper left bounding box 20667 // so need to translate that 20668 20669 if(this.background.a) { 20670 painter.fillColor = this.background; 20671 painter.outlineColor = this.background; 20672 20673 painter.drawRectangle(Point(rendered.startX, rendered.startY - this.font.ascent), Size(rendered.width, this.font.height)); 20674 20675 painter.outlineColor = this.foreground; 20676 painter.fillColor = Color.transparent; 20677 } 20678 20679 painter.drawText(Point(rendered.startX, rendered.startY - this.font.ascent), rendered.slice); 20680 20681 // FIXME: strike through, underline, highlight selection, etc. 20682 } 20683 } 20684 } 20685 20686 // I could split the parts into words on render 20687 // for easier word-wrap, each one being an unbreakable "inline-block" 20688 private TextFlowComponent[] parts; 20689 private int needsRerenderFrom; 20690 20691 void addPart(Color f, Color b, OperatingSystemFont font, ubyte attr, const(char)[] c) { 20692 // FIXME: needsRerenderFrom. Basically if the bounding box and baseline is the same as the previous thing, it can prolly just stop. 20693 parts ~= new TextFlowComponent(f, b, font, attr, c); 20694 } 20695 20696 static struct RenderedComponent { 20697 int startX; 20698 int startY; 20699 short width; 20700 // 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! 20701 // for individual chars in here you've gotta process on demand 20702 version(Windows) 20703 const(wchar)[] slice; 20704 else 20705 const(char)[] slice; 20706 } 20707 20708 20709 void rerender(Rectangle boundingBox) { 20710 Point baseline = boundingBox.upperLeft; 20711 20712 this.boundingBox.left = boundingBox.left; 20713 this.boundingBox.top = boundingBox.top; 20714 20715 auto remainingParts = parts; 20716 20717 int largestX; 20718 20719 20720 foreach(part; parts) 20721 part.font.prepareContext(window); 20722 scope(exit) 20723 foreach(part; parts) 20724 part.font.releaseContext(); 20725 20726 calculateNextLine: 20727 20728 int nextLineHeight = 0; 20729 int nextBiggestDescent = 0; 20730 20731 foreach(part; remainingParts) { 20732 auto height = part.font.ascent; 20733 if(height > nextLineHeight) 20734 nextLineHeight = height; 20735 if(part.font.descent > nextBiggestDescent) 20736 nextBiggestDescent = part.font.descent; 20737 if(part.content.length && part.content[$-1] == '\n') 20738 break; 20739 } 20740 20741 baseline.y += nextLineHeight; 20742 auto lineStart = baseline; 20743 20744 while(remainingParts.length) { 20745 remainingParts[0].rendered = null; 20746 20747 bool eol; 20748 if(remainingParts[0].content.length && remainingParts[0].content[$-1] == '\n') 20749 eol = true; 20750 20751 // FIXME: word wrap 20752 auto font = remainingParts[0].font; 20753 auto slice = remainingParts[0].content[0 .. $ - (eol ? 1 : 0)]; 20754 auto width = font.stringWidth(slice, window); 20755 remainingParts[0].rendered ~= RenderedComponent(baseline.x, baseline.y, cast(short) width, slice); 20756 20757 remainingParts = remainingParts[1 .. $]; 20758 baseline.x += width; 20759 20760 if(eol) { 20761 baseline.y += nextBiggestDescent; 20762 if(baseline.x > largestX) 20763 largestX = baseline.x; 20764 baseline.x = lineStart.x; 20765 goto calculateNextLine; 20766 } 20767 } 20768 20769 if(baseline.x > largestX) 20770 largestX = baseline.x; 20771 20772 this.boundingBox.right = largestX; 20773 this.boundingBox.bottom = baseline.y; 20774 } 20775 20776 // you must call rerender first! 20777 void draw(ScreenPainter painter) { 20778 foreach(part; parts) { 20779 part.draw(painter); 20780 } 20781 } 20782 20783 struct IdentifyResult { 20784 TextFlowComponent part; 20785 int charIndexInPart; 20786 int totalCharIndex = -1; // if this is -1, it just means the end 20787 20788 Rectangle boundingBox; 20789 } 20790 20791 IdentifyResult identify(Point pt, bool exact = false) { 20792 if(parts.length == 0) 20793 return IdentifyResult(null, 0); 20794 20795 if(pt.y < boundingBox.top) { 20796 if(exact) 20797 return IdentifyResult(null, 1); 20798 return IdentifyResult(parts[0], 0); 20799 } 20800 if(pt.y > boundingBox.bottom) { 20801 if(exact) 20802 return IdentifyResult(null, 2); 20803 return IdentifyResult(parts[$-1], cast(int) parts[$-1].content.length); 20804 } 20805 20806 int tci = 0; 20807 20808 // I should probably like binary search this or something... 20809 foreach(ref part; parts) { 20810 foreach(rendered; part.rendered) { 20811 auto rect = Rectangle(rendered.startX, rendered.startY - part.font.ascent, rendered.startX + rendered.width, rendered.startY + part.font.descent); 20812 if(rect.contains(pt)) { 20813 auto x = pt.x - rendered.startX; 20814 auto estimatedIdx = x / part.font.averageWidth; 20815 20816 if(estimatedIdx < 0) 20817 estimatedIdx = 0; 20818 20819 if(estimatedIdx > rendered.slice.length) 20820 estimatedIdx = cast(int) rendered.slice.length; 20821 20822 int idx; 20823 int x1, x2; 20824 if(part.font.isMonospace) { 20825 auto w = part.font.averageWidth; 20826 if(!exact && x > (estimatedIdx + 1) * w) 20827 return IdentifyResult(null, 4); 20828 idx = estimatedIdx; 20829 x1 = idx * w; 20830 x2 = (idx + 1) * w; 20831 } else { 20832 idx = estimatedIdx; 20833 20834 part.font.prepareContext(window); 20835 scope(exit) part.font.releaseContext(); 20836 20837 // int iterations; 20838 20839 while(true) { 20840 // iterations++; 20841 x1 = idx ? part.font.stringWidth(rendered.slice[0 .. idx - 1]) : 0; 20842 x2 = part.font.stringWidth(rendered.slice[0 .. idx]); // should be the maximum since `averageWidth` kinda lies. 20843 20844 x1 += rendered.startX; 20845 x2 += rendered.startX; 20846 20847 if(pt.x < x1) { 20848 if(idx == 0) { 20849 if(exact) 20850 return IdentifyResult(null, 6); 20851 else 20852 break; 20853 } 20854 idx--; 20855 } else if(pt.x > x2) { 20856 idx++; 20857 if(idx > rendered.slice.length) { 20858 if(exact) 20859 return IdentifyResult(null, 5); 20860 else 20861 break; 20862 } 20863 } else if(pt.x >= x1 && pt.x <= x2) { 20864 if(idx) 20865 idx--; // point it at the original index 20866 break; // we fit 20867 } 20868 } 20869 20870 // writeln(iterations) 20871 } 20872 20873 20874 return IdentifyResult(part, idx, tci + idx, Rectangle(x1, rect.top, x2, rect.bottom)); // FIXME: utf-8? 20875 } 20876 } 20877 tci += cast(int) part.content.length; // FIXME: utf-8? 20878 } 20879 return IdentifyResult(null, 3); 20880 } 20881 20882 Rectangle boundingBox; // only set after [rerender] 20883 20884 // text will be positioned around the exclusion zone 20885 static struct ExclusionZone { 20886 20887 } 20888 20889 ExclusionZone[] exclusionZones; 20890 } 20891 20892 20893 // Don't use this yet. When I'm happy with it, I will move it to the 20894 // regular module namespace. 20895 mixin template ExperimentalTextComponent() { 20896 20897 static: 20898 20899 alias Rectangle = arsd.color.Rectangle; 20900 20901 struct ForegroundColor { 20902 Color color; 20903 alias color this; 20904 20905 this(Color c) { 20906 color = c; 20907 } 20908 20909 this(int r, int g, int b, int a = 255) { 20910 color = Color(r, g, b, a); 20911 } 20912 20913 static ForegroundColor opDispatch(string s)() if(__traits(compiles, ForegroundColor(mixin("Color." ~ s)))) { 20914 return ForegroundColor(mixin("Color." ~ s)); 20915 } 20916 } 20917 20918 struct BackgroundColor { 20919 Color color; 20920 alias color this; 20921 20922 this(Color c) { 20923 color = c; 20924 } 20925 20926 this(int r, int g, int b, int a = 255) { 20927 color = Color(r, g, b, a); 20928 } 20929 20930 static BackgroundColor opDispatch(string s)() if(__traits(compiles, BackgroundColor(mixin("Color." ~ s)))) { 20931 return BackgroundColor(mixin("Color." ~ s)); 20932 } 20933 } 20934 20935 static class InlineElement { 20936 string text; 20937 20938 BlockElement containingBlock; 20939 20940 Color color = Color.black; 20941 Color backgroundColor = Color.transparent; 20942 ushort styles; 20943 20944 string font; 20945 int fontSize; 20946 20947 int lineHeight; 20948 20949 void* identifier; 20950 20951 Rectangle boundingBox; 20952 int[] letterXs; // FIXME: maybe i should do bounding boxes for every character 20953 20954 bool isMergeCompatible(InlineElement other) { 20955 return 20956 containingBlock is other.containingBlock && 20957 color == other.color && 20958 backgroundColor == other.backgroundColor && 20959 styles == other.styles && 20960 font == other.font && 20961 fontSize == other.fontSize && 20962 lineHeight == other.lineHeight && 20963 true; 20964 } 20965 20966 int xOfIndex(size_t index) { 20967 if(index < letterXs.length) 20968 return letterXs[index]; 20969 else 20970 return boundingBox.right; 20971 } 20972 20973 InlineElement clone() { 20974 auto ie = new InlineElement(); 20975 ie.tupleof = this.tupleof; 20976 return ie; 20977 } 20978 20979 InlineElement getPreviousInlineElement() { 20980 InlineElement prev = null; 20981 foreach(ie; this.containingBlock.parts) { 20982 if(ie is this) 20983 break; 20984 prev = ie; 20985 } 20986 if(prev is null) { 20987 BlockElement pb; 20988 BlockElement cb = this.containingBlock; 20989 moar: 20990 foreach(ie; this.containingBlock.containingLayout.blocks) { 20991 if(ie is cb) 20992 break; 20993 pb = ie; 20994 } 20995 if(pb is null) 20996 return null; 20997 if(pb.parts.length == 0) { 20998 cb = pb; 20999 goto moar; 21000 } 21001 21002 prev = pb.parts[$-1]; 21003 21004 } 21005 return prev; 21006 } 21007 21008 InlineElement getNextInlineElement() { 21009 InlineElement next = null; 21010 foreach(idx, ie; this.containingBlock.parts) { 21011 if(ie is this) { 21012 if(idx + 1 < this.containingBlock.parts.length) 21013 next = this.containingBlock.parts[idx + 1]; 21014 break; 21015 } 21016 } 21017 if(next is null) { 21018 BlockElement n; 21019 foreach(idx, ie; this.containingBlock.containingLayout.blocks) { 21020 if(ie is this.containingBlock) { 21021 if(idx + 1 < this.containingBlock.containingLayout.blocks.length) 21022 n = this.containingBlock.containingLayout.blocks[idx + 1]; 21023 break; 21024 } 21025 } 21026 if(n is null) 21027 return null; 21028 21029 if(n.parts.length) 21030 next = n.parts[0]; 21031 else {} // FIXME 21032 21033 } 21034 return next; 21035 } 21036 21037 } 21038 21039 // Block elements are used entirely for positioning inline elements, 21040 // which are the things that are actually drawn. 21041 class BlockElement { 21042 InlineElement[] parts; 21043 uint alignment; 21044 21045 int whiteSpace; // pre, pre-wrap, wrap 21046 21047 TextLayout containingLayout; 21048 21049 // inputs 21050 Point where; 21051 Size minimumSize; 21052 Size maximumSize; 21053 Rectangle[] excludedBoxes; // like if you want it to write around a floated image or something. Coordinates are relative to the bounding box. 21054 void* identifier; 21055 21056 Rectangle margin; 21057 Rectangle padding; 21058 21059 // outputs 21060 Rectangle[] boundingBoxes; 21061 } 21062 21063 struct TextIdentifyResult { 21064 InlineElement element; 21065 int offset; 21066 21067 private TextIdentifyResult fixupNewline() { 21068 if(element !is null && offset < element.text.length && element.text[offset] == '\n') { 21069 offset--; 21070 } else if(element !is null && offset == element.text.length && element.text.length > 1 && element.text[$-1] == '\n') { 21071 offset--; 21072 } 21073 return this; 21074 } 21075 } 21076 21077 class TextLayout { 21078 BlockElement[] blocks; 21079 Rectangle boundingBox_; 21080 Rectangle boundingBox() { return boundingBox_; } 21081 void boundingBox(Rectangle r) { 21082 if(r != boundingBox_) { 21083 boundingBox_ = r; 21084 layoutInvalidated = true; 21085 } 21086 } 21087 21088 Rectangle contentBoundingBox() { 21089 Rectangle r; 21090 foreach(block; blocks) 21091 foreach(ie; block.parts) { 21092 if(ie.boundingBox.right > r.right) 21093 r.right = ie.boundingBox.right; 21094 if(ie.boundingBox.bottom > r.bottom) 21095 r.bottom = ie.boundingBox.bottom; 21096 } 21097 return r; 21098 } 21099 21100 BlockElement[] getBlocks() { 21101 return blocks; 21102 } 21103 21104 InlineElement[] getTexts() { 21105 InlineElement[] elements; 21106 foreach(block; blocks) 21107 elements ~= block.parts; 21108 return elements; 21109 } 21110 21111 string getPlainText() { 21112 string text; 21113 foreach(block; blocks) 21114 foreach(part; block.parts) 21115 text ~= part.text; 21116 return text; 21117 } 21118 21119 string getHtml() { 21120 return null; // FIXME 21121 } 21122 21123 this(Rectangle boundingBox) { 21124 this.boundingBox = boundingBox; 21125 } 21126 21127 BlockElement addBlock(InlineElement after = null, Rectangle margin = Rectangle(0, 0, 0, 0), Rectangle padding = Rectangle(0, 0, 0, 0)) { 21128 auto be = new BlockElement(); 21129 be.containingLayout = this; 21130 if(after is null) 21131 blocks ~= be; 21132 else { 21133 foreach(idx, b; blocks) { 21134 if(b is after.containingBlock) { 21135 blocks = blocks[0 .. idx + 1] ~ be ~ blocks[idx + 1 .. $]; 21136 break; 21137 } 21138 } 21139 } 21140 return be; 21141 } 21142 21143 void clear() { 21144 blocks = null; 21145 selectionStart = selectionEnd = caret = Caret.init; 21146 } 21147 21148 void addText(Args...)(Args args) { 21149 if(blocks.length == 0) 21150 addBlock(); 21151 21152 InlineElement ie = new InlineElement(); 21153 foreach(idx, arg; args) { 21154 static if(is(typeof(arg) == ForegroundColor)) 21155 ie.color = arg; 21156 else static if(is(typeof(arg) == TextFormat)) { 21157 if(arg & 0x8000) // ~TextFormat.something turns it off 21158 ie.styles &= arg; 21159 else 21160 ie.styles |= arg; 21161 } else static if(is(typeof(arg) == string)) { 21162 static if(idx == 0 && args.length > 1) 21163 static assert(0, "Put styles before the string."); 21164 size_t lastLineIndex; 21165 foreach(cidx, char a; arg) { 21166 if(a == '\n') { 21167 ie.text = arg[lastLineIndex .. cidx + 1]; 21168 lastLineIndex = cidx + 1; 21169 ie.containingBlock = blocks[$-1]; 21170 blocks[$-1].parts ~= ie.clone; 21171 ie.text = null; 21172 } else { 21173 21174 } 21175 } 21176 21177 ie.text = arg[lastLineIndex .. $]; 21178 ie.containingBlock = blocks[$-1]; 21179 blocks[$-1].parts ~= ie.clone; 21180 caret = Caret(this, blocks[$-1].parts[$-1], cast(int) blocks[$-1].parts[$-1].text.length); 21181 } 21182 } 21183 21184 invalidateLayout(); 21185 } 21186 21187 void tryMerge(InlineElement into, InlineElement what) { 21188 if(!into.isMergeCompatible(what)) { 21189 return; // cannot merge, different configs 21190 } 21191 21192 // cool, can merge, bring text together... 21193 into.text ~= what.text; 21194 21195 // and remove what 21196 for(size_t a = 0; a < what.containingBlock.parts.length; a++) { 21197 if(what.containingBlock.parts[a] is what) { 21198 for(size_t i = a; i < what.containingBlock.parts.length - 1; i++) 21199 what.containingBlock.parts[i] = what.containingBlock.parts[i + 1]; 21200 what.containingBlock.parts = what.containingBlock.parts[0 .. $-1]; 21201 21202 } 21203 } 21204 21205 // FIXME: ensure no other carets have a reference to it 21206 } 21207 21208 /// exact = true means return null if no match. otherwise, get the closest one that makes sense for a mouse click. 21209 TextIdentifyResult identify(int x, int y, bool exact = false) { 21210 TextIdentifyResult inexactMatch; 21211 foreach(block; blocks) { 21212 foreach(part; block.parts) { 21213 if(x >= part.boundingBox.left && x < part.boundingBox.right && y >= part.boundingBox.top && y < part.boundingBox.bottom) { 21214 21215 // FIXME binary search 21216 int tidx; 21217 int lastX; 21218 foreach_reverse(idxo, lx; part.letterXs) { 21219 int idx = cast(int) idxo; 21220 if(lx <= x) { 21221 if(lastX && lastX - x < x - lx) 21222 tidx = idx + 1; 21223 else 21224 tidx = idx; 21225 break; 21226 } 21227 lastX = lx; 21228 } 21229 21230 return TextIdentifyResult(part, tidx).fixupNewline; 21231 } else if(!exact) { 21232 // we're not in the box, but are we on the same line? 21233 if(y >= part.boundingBox.top && y < part.boundingBox.bottom) 21234 inexactMatch = TextIdentifyResult(part, x == 0 ? 0 : cast(int) part.text.length); 21235 } 21236 } 21237 } 21238 21239 if(!exact && inexactMatch is TextIdentifyResult.init && blocks.length && blocks[$-1].parts.length) 21240 return TextIdentifyResult(blocks[$-1].parts[$-1], cast(int) blocks[$-1].parts[$-1].text.length).fixupNewline; 21241 21242 return exact ? TextIdentifyResult.init : inexactMatch.fixupNewline; 21243 } 21244 21245 void moveCaretToPixelCoordinates(int x, int y) { 21246 auto result = identify(x, y); 21247 caret.inlineElement = result.element; 21248 caret.offset = result.offset; 21249 } 21250 21251 void selectToPixelCoordinates(int x, int y) { 21252 auto result = identify(x, y); 21253 21254 if(y < caretLastDrawnY1) { 21255 // on a previous line, carat is selectionEnd 21256 selectionEnd = caret; 21257 21258 selectionStart = Caret(this, result.element, result.offset); 21259 } else if(y > caretLastDrawnY2) { 21260 // on a later line 21261 selectionStart = caret; 21262 21263 selectionEnd = Caret(this, result.element, result.offset); 21264 } else { 21265 // on the same line... 21266 if(x <= caretLastDrawnX) { 21267 selectionEnd = caret; 21268 selectionStart = Caret(this, result.element, result.offset); 21269 } else { 21270 selectionStart = caret; 21271 selectionEnd = Caret(this, result.element, result.offset); 21272 } 21273 21274 } 21275 } 21276 21277 21278 /// Call this if the inputs change. It will reflow everything 21279 void redoLayout(ScreenPainter painter) { 21280 //painter.setClipRectangle(boundingBox); 21281 auto pos = Point(boundingBox.left, boundingBox.top); 21282 21283 int lastHeight; 21284 void nl() { 21285 pos.x = boundingBox.left; 21286 pos.y += lastHeight; 21287 } 21288 foreach(block; blocks) { 21289 nl(); 21290 foreach(part; block.parts) { 21291 part.letterXs = null; 21292 21293 auto size = painter.textSize(part.text); 21294 version(Windows) 21295 if(part.text.length && part.text[$-1] == '\n') 21296 size.height /= 2; // windows counts the new line at the end, but we don't want that 21297 21298 part.boundingBox = Rectangle(pos.x, pos.y, pos.x + size.width, pos.y + size.height); 21299 21300 foreach(idx, char c; part.text) { 21301 // FIXME: unicode 21302 part.letterXs ~= painter.textSize(part.text[0 .. idx]).width + pos.x; 21303 } 21304 21305 pos.x += size.width; 21306 if(pos.x >= boundingBox.right) { 21307 pos.y += size.height; 21308 pos.x = boundingBox.left; 21309 lastHeight = 0; 21310 } else { 21311 lastHeight = size.height; 21312 } 21313 21314 if(part.text.length && part.text[$-1] == '\n') 21315 nl(); 21316 } 21317 } 21318 21319 layoutInvalidated = false; 21320 } 21321 21322 bool layoutInvalidated = true; 21323 void invalidateLayout() { 21324 layoutInvalidated = true; 21325 } 21326 21327 // FIXME: caret can remain sometimes when inserting 21328 // FIXME: inserting at the beginning once you already have something can eff it up. 21329 void drawInto(ScreenPainter painter, bool focused = false) { 21330 if(layoutInvalidated) 21331 redoLayout(painter); 21332 foreach(block; blocks) { 21333 foreach(part; block.parts) { 21334 painter.outlineColor = part.color; 21335 painter.fillColor = part.backgroundColor; 21336 21337 auto pos = part.boundingBox.upperLeft; 21338 auto size = part.boundingBox.size; 21339 21340 painter.drawText(pos, part.text); 21341 if(part.styles & TextFormat.underline) 21342 painter.drawLine(Point(pos.x, pos.y + size.height - 4), Point(pos.x + size.width, pos.y + size.height - 4)); 21343 if(part.styles & TextFormat.strikethrough) 21344 painter.drawLine(Point(pos.x, pos.y + size.height/2), Point(pos.x + size.width, pos.y + size.height/2)); 21345 } 21346 } 21347 21348 // on every redraw, I will force the caret to be 21349 // redrawn too, in order to eliminate perceived lag 21350 // when moving around with the mouse. 21351 eraseCaret(painter); 21352 21353 if(focused) { 21354 highlightSelection(painter); 21355 drawCaret(painter); 21356 } 21357 } 21358 21359 Color selectionXorColor = Color(255, 255, 127); 21360 21361 void highlightSelection(ScreenPainter painter) { 21362 if(selectionStart is selectionEnd) 21363 return; // no selection 21364 21365 if(selectionStart.inlineElement is null) return; 21366 if(selectionEnd.inlineElement is null) return; 21367 21368 assert(selectionStart.inlineElement !is null); 21369 assert(selectionEnd.inlineElement !is null); 21370 21371 painter.rasterOp = RasterOp.xor; 21372 painter.outlineColor = Color.transparent; 21373 painter.fillColor = selectionXorColor; 21374 21375 auto at = selectionStart.inlineElement; 21376 auto atOffset = selectionStart.offset; 21377 bool done; 21378 while(at) { 21379 auto box = at.boundingBox; 21380 if(atOffset < at.letterXs.length) 21381 box.left = at.letterXs[atOffset]; 21382 21383 if(at is selectionEnd.inlineElement) { 21384 if(selectionEnd.offset < at.letterXs.length) 21385 box.right = at.letterXs[selectionEnd.offset]; 21386 done = true; 21387 } 21388 21389 painter.drawRectangle(box.upperLeft, box.width, box.height); 21390 21391 if(done) 21392 break; 21393 21394 at = at.getNextInlineElement(); 21395 atOffset = 0; 21396 } 21397 } 21398 21399 int caretLastDrawnX, caretLastDrawnY1, caretLastDrawnY2; 21400 bool caretShowingOnScreen = false; 21401 void drawCaret(ScreenPainter painter) { 21402 //painter.setClipRectangle(boundingBox); 21403 int x, y1, y2; 21404 if(caret.inlineElement is null) { 21405 x = boundingBox.left; 21406 y1 = boundingBox.top + 2; 21407 y2 = boundingBox.top + painter.fontHeight; 21408 } else { 21409 x = caret.inlineElement.xOfIndex(caret.offset); 21410 y1 = caret.inlineElement.boundingBox.top + 2; 21411 y2 = caret.inlineElement.boundingBox.bottom - 2; 21412 } 21413 21414 if(caretShowingOnScreen && (x != caretLastDrawnX || y1 != caretLastDrawnY1 || y2 != caretLastDrawnY2)) 21415 eraseCaret(painter); 21416 21417 painter.pen = Pen(Color.white, 1); 21418 painter.rasterOp = RasterOp.xor; 21419 painter.drawLine( 21420 Point(x, y1), 21421 Point(x, y2) 21422 ); 21423 painter.rasterOp = RasterOp.normal; 21424 caretShowingOnScreen = !caretShowingOnScreen; 21425 21426 if(caretShowingOnScreen) { 21427 caretLastDrawnX = x; 21428 caretLastDrawnY1 = y1; 21429 caretLastDrawnY2 = y2; 21430 } 21431 } 21432 21433 Rectangle caretBoundingBox() { 21434 int x, y1, y2; 21435 if(caret.inlineElement is null) { 21436 x = boundingBox.left; 21437 y1 = boundingBox.top + 2; 21438 y2 = boundingBox.top + 16; 21439 } else { 21440 x = caret.inlineElement.xOfIndex(caret.offset); 21441 y1 = caret.inlineElement.boundingBox.top + 2; 21442 y2 = caret.inlineElement.boundingBox.bottom - 2; 21443 } 21444 21445 return Rectangle(x, y1, x + 1, y2); 21446 } 21447 21448 void eraseCaret(ScreenPainter painter) { 21449 //painter.setClipRectangle(boundingBox); 21450 if(!caretShowingOnScreen) return; 21451 painter.pen = Pen(Color.white, 1); 21452 painter.rasterOp = RasterOp.xor; 21453 painter.drawLine( 21454 Point(caretLastDrawnX, caretLastDrawnY1), 21455 Point(caretLastDrawnX, caretLastDrawnY2) 21456 ); 21457 21458 caretShowingOnScreen = false; 21459 painter.rasterOp = RasterOp.normal; 21460 } 21461 21462 /// Caret movement api 21463 /// These should give the user a logical result based on what they see on screen... 21464 /// thus they locate predominately by *pixels* not char index. (These will generally coincide with monospace fonts tho!) 21465 void moveUp() { 21466 if(caret.inlineElement is null) return; 21467 auto x = caret.inlineElement.xOfIndex(caret.offset); 21468 auto y = caret.inlineElement.boundingBox.top + 2; 21469 21470 y -= caret.inlineElement.boundingBox.bottom - caret.inlineElement.boundingBox.top; 21471 if(y < 0) 21472 return; 21473 21474 auto i = identify(x, y); 21475 21476 if(i.element) { 21477 caret.inlineElement = i.element; 21478 caret.offset = i.offset; 21479 } 21480 } 21481 void moveDown() { 21482 if(caret.inlineElement is null) return; 21483 auto x = caret.inlineElement.xOfIndex(caret.offset); 21484 auto y = caret.inlineElement.boundingBox.bottom - 2; 21485 21486 y += caret.inlineElement.boundingBox.bottom - caret.inlineElement.boundingBox.top; 21487 21488 auto i = identify(x, y); 21489 if(i.element) { 21490 caret.inlineElement = i.element; 21491 caret.offset = i.offset; 21492 } 21493 } 21494 void moveLeft() { 21495 if(caret.inlineElement is null) return; 21496 if(caret.offset) 21497 caret.offset--; 21498 else { 21499 auto p = caret.inlineElement.getPreviousInlineElement(); 21500 if(p) { 21501 caret.inlineElement = p; 21502 if(p.text.length && p.text[$-1] == '\n') 21503 caret.offset = cast(int) p.text.length - 1; 21504 else 21505 caret.offset = cast(int) p.text.length; 21506 } 21507 } 21508 } 21509 void moveRight() { 21510 if(caret.inlineElement is null) return; 21511 if(caret.offset < caret.inlineElement.text.length && caret.inlineElement.text[caret.offset] != '\n') { 21512 caret.offset++; 21513 } else { 21514 auto p = caret.inlineElement.getNextInlineElement(); 21515 if(p) { 21516 caret.inlineElement = p; 21517 caret.offset = 0; 21518 } 21519 } 21520 } 21521 void moveHome() { 21522 if(caret.inlineElement is null) return; 21523 auto x = 0; 21524 auto y = caret.inlineElement.boundingBox.top + 2; 21525 21526 auto i = identify(x, y); 21527 21528 if(i.element) { 21529 caret.inlineElement = i.element; 21530 caret.offset = i.offset; 21531 } 21532 } 21533 void moveEnd() { 21534 if(caret.inlineElement is null) return; 21535 auto x = int.max; 21536 auto y = caret.inlineElement.boundingBox.top + 2; 21537 21538 auto i = identify(x, y); 21539 21540 if(i.element) { 21541 caret.inlineElement = i.element; 21542 caret.offset = i.offset; 21543 } 21544 21545 } 21546 void movePageUp(ref Caret caret) {} 21547 void movePageDown(ref Caret caret) {} 21548 21549 void moveDocumentStart(ref Caret caret) { 21550 if(blocks.length && blocks[0].parts.length) 21551 caret = Caret(this, blocks[0].parts[0], 0); 21552 else 21553 caret = Caret.init; 21554 } 21555 21556 void moveDocumentEnd(ref Caret caret) { 21557 if(blocks.length) { 21558 auto parts = blocks[$-1].parts; 21559 if(parts.length) { 21560 caret = Caret(this, parts[$-1], cast(int) parts[$-1].text.length); 21561 } else { 21562 caret = Caret.init; 21563 } 21564 } else 21565 caret = Caret.init; 21566 } 21567 21568 void deleteSelection() { 21569 if(selectionStart is selectionEnd) 21570 return; 21571 21572 if(selectionStart.inlineElement is null) return; 21573 if(selectionEnd.inlineElement is null) return; 21574 21575 assert(selectionStart.inlineElement !is null); 21576 assert(selectionEnd.inlineElement !is null); 21577 21578 auto at = selectionStart.inlineElement; 21579 21580 if(selectionEnd.inlineElement is at) { 21581 // same element, need to chop out 21582 at.text = at.text[0 .. selectionStart.offset] ~ at.text[selectionEnd.offset .. $]; 21583 at.letterXs = at.letterXs[0 .. selectionStart.offset] ~ at.letterXs[selectionEnd.offset .. $]; 21584 selectionEnd.offset -= selectionEnd.offset - selectionStart.offset; 21585 } else { 21586 // different elements, we can do it with slicing 21587 at.text = at.text[0 .. selectionStart.offset]; 21588 if(selectionStart.offset < at.letterXs.length) 21589 at.letterXs = at.letterXs[0 .. selectionStart.offset]; 21590 21591 at = at.getNextInlineElement(); 21592 21593 while(at) { 21594 if(at is selectionEnd.inlineElement) { 21595 at.text = at.text[selectionEnd.offset .. $]; 21596 if(selectionEnd.offset < at.letterXs.length) 21597 at.letterXs = at.letterXs[selectionEnd.offset .. $]; 21598 selectionEnd.offset = 0; 21599 break; 21600 } else { 21601 auto cfd = at; 21602 cfd.text = null; // delete the whole thing 21603 21604 at = at.getNextInlineElement(); 21605 21606 if(cfd.text.length == 0) { 21607 // and remove cfd 21608 for(size_t a = 0; a < cfd.containingBlock.parts.length; a++) { 21609 if(cfd.containingBlock.parts[a] is cfd) { 21610 for(size_t i = a; i < cfd.containingBlock.parts.length - 1; i++) 21611 cfd.containingBlock.parts[i] = cfd.containingBlock.parts[i + 1]; 21612 cfd.containingBlock.parts = cfd.containingBlock.parts[0 .. $-1]; 21613 21614 } 21615 } 21616 } 21617 } 21618 } 21619 } 21620 21621 caret = selectionEnd; 21622 selectNone(); 21623 21624 invalidateLayout(); 21625 21626 } 21627 21628 /// Plain text editing api. These work at the current caret inside the selected inline element. 21629 void insert(in char[] text) { 21630 foreach(dchar ch; text) 21631 insert(ch); 21632 } 21633 /// ditto 21634 void insert(dchar ch) { 21635 21636 bool selectionDeleted = false; 21637 if(selectionStart !is selectionEnd) { 21638 deleteSelection(); 21639 selectionDeleted = true; 21640 } 21641 21642 if(ch == 127) { 21643 delete_(); 21644 return; 21645 } 21646 if(ch == 8) { 21647 if(!selectionDeleted) 21648 backspace(); 21649 return; 21650 } 21651 21652 invalidateLayout(); 21653 21654 if(ch == 13) ch = 10; 21655 auto e = caret.inlineElement; 21656 if(e is null) { 21657 addText("" ~ cast(char) ch) ; // FIXME 21658 return; 21659 } 21660 21661 if(caret.offset == e.text.length) { 21662 e.text ~= cast(char) ch; // FIXME 21663 caret.offset++; 21664 if(ch == 10) { 21665 auto c = caret.inlineElement.clone; 21666 c.text = null; 21667 c.letterXs = null; 21668 insertPartAfter(c,e); 21669 caret = Caret(this, c, 0); 21670 } 21671 } else { 21672 // FIXME cast char sucks 21673 if(ch == 10) { 21674 auto c = caret.inlineElement.clone; 21675 c.text = e.text[caret.offset .. $]; 21676 if(caret.offset < c.letterXs.length) 21677 c.letterXs = e.letterXs[caret.offset .. $]; // FIXME boundingBox 21678 e.text = e.text[0 .. caret.offset] ~ cast(char) ch; 21679 if(caret.offset <= e.letterXs.length) { 21680 e.letterXs = e.letterXs[0 .. caret.offset] ~ 0; // FIXME bounding box 21681 } 21682 insertPartAfter(c,e); 21683 caret = Caret(this, c, 0); 21684 } else { 21685 e.text = e.text[0 .. caret.offset] ~ cast(char) ch ~ e.text[caret.offset .. $]; 21686 caret.offset++; 21687 } 21688 } 21689 } 21690 21691 void insertPartAfter(InlineElement what, InlineElement where) { 21692 foreach(idx, p; where.containingBlock.parts) { 21693 if(p is where) { 21694 if(idx + 1 == where.containingBlock.parts.length) 21695 where.containingBlock.parts ~= what; 21696 else 21697 where.containingBlock.parts = where.containingBlock.parts[0 .. idx + 1] ~ what ~ where.containingBlock.parts[idx + 1 .. $]; 21698 return; 21699 } 21700 } 21701 } 21702 21703 void cleanupStructures() { 21704 for(size_t i = 0; i < blocks.length; i++) { 21705 auto block = blocks[i]; 21706 for(size_t a = 0; a < block.parts.length; a++) { 21707 auto part = block.parts[a]; 21708 if(part.text.length == 0) { 21709 for(size_t b = a; b < block.parts.length - 1; b++) 21710 block.parts[b] = block.parts[b+1]; 21711 block.parts = block.parts[0 .. $-1]; 21712 } 21713 } 21714 if(block.parts.length == 0) { 21715 for(size_t a = i; a < blocks.length - 1; a++) 21716 blocks[a] = blocks[a+1]; 21717 blocks = blocks[0 .. $-1]; 21718 } 21719 } 21720 } 21721 21722 void backspace() { 21723 try_again: 21724 auto e = caret.inlineElement; 21725 if(e is null) 21726 return; 21727 if(caret.offset == 0) { 21728 auto prev = e.getPreviousInlineElement(); 21729 if(prev is null) 21730 return; 21731 auto newOffset = cast(int) prev.text.length; 21732 tryMerge(prev, e); 21733 caret.inlineElement = prev; 21734 caret.offset = prev is null ? 0 : newOffset; 21735 21736 goto try_again; 21737 } else if(caret.offset == e.text.length) { 21738 e.text = e.text[0 .. $-1]; 21739 caret.offset--; 21740 } else { 21741 e.text = e.text[0 .. caret.offset - 1] ~ e.text[caret.offset .. $]; 21742 caret.offset--; 21743 } 21744 //cleanupStructures(); 21745 21746 invalidateLayout(); 21747 } 21748 void delete_() { 21749 if(selectionStart !is selectionEnd) 21750 deleteSelection(); 21751 else { 21752 auto before = caret; 21753 moveRight(); 21754 if(caret != before) { 21755 backspace(); 21756 } 21757 } 21758 21759 invalidateLayout(); 21760 } 21761 void overstrike() {} 21762 21763 /// Selection API. See also: caret movement. 21764 void selectAll() { 21765 moveDocumentStart(selectionStart); 21766 moveDocumentEnd(selectionEnd); 21767 } 21768 bool selectNone() { 21769 if(selectionStart != selectionEnd) { 21770 selectionStart = selectionEnd = Caret.init; 21771 return true; 21772 } 21773 return false; 21774 } 21775 21776 /// Rich text editing api. These allow you to manipulate the meta data of the current element and add new elements. 21777 /// They will modify the current selection if there is one and will splice one in if needed. 21778 void changeAttributes() {} 21779 21780 21781 /// Text search api. They manipulate the selection and/or caret. 21782 void findText(string text) {} 21783 void findIndex(size_t textIndex) {} 21784 21785 // sample event handlers 21786 21787 void handleEvent(KeyEvent event) { 21788 //if(event.type == KeyEvent.Type.KeyPressed) { 21789 21790 //} 21791 } 21792 21793 void handleEvent(dchar ch) { 21794 21795 } 21796 21797 void handleEvent(MouseEvent event) { 21798 21799 } 21800 21801 bool contentEditable; // can it be edited? 21802 bool contentCaretable; // is there a caret/cursor that moves around in there? 21803 bool contentSelectable; // selectable? 21804 21805 Caret caret; 21806 Caret selectionStart; 21807 Caret selectionEnd; 21808 21809 bool insertMode; 21810 } 21811 21812 struct Caret { 21813 TextLayout layout; 21814 InlineElement inlineElement; 21815 int offset; 21816 } 21817 21818 enum TextFormat : ushort { 21819 // decorations 21820 underline = 1, 21821 strikethrough = 2, 21822 21823 // font selectors 21824 21825 bold = 0x4000 | 1, // weight 700 21826 light = 0x4000 | 2, // weight 300 21827 veryBoldOrLight = 0x4000 | 4, // weight 100 with light, weight 900 with bold 21828 // bold | light is really invalid but should give weight 500 21829 // veryBoldOrLight without one of the others should just give the default for the font; it should be ignored. 21830 21831 italic = 0x4000 | 8, 21832 smallcaps = 0x4000 | 16, 21833 } 21834 21835 void* findFont(string family, int weight, TextFormat formats) { 21836 return null; 21837 } 21838 21839 } 21840 21841 /++ 21842 $(PITFALL This is not yet stable and may break in future versions without notice.) 21843 21844 History: 21845 Added February 19, 2021 21846 +/ 21847 /// Group: drag_and_drop 21848 interface DropHandler { 21849 /++ 21850 Called when the drag enters the handler's area. 21851 +/ 21852 DragAndDropAction dragEnter(DropPackage*); 21853 /++ 21854 Called when the drag leaves the handler's area or is 21855 cancelled. You should free your resources when this is called. 21856 +/ 21857 void dragLeave(); 21858 /++ 21859 Called continually as the drag moves over the handler's area. 21860 21861 Returns: feedback to the dragger 21862 +/ 21863 DropParameters dragOver(Point pt); 21864 /++ 21865 The user dropped the data and you should process it now. You can 21866 access the data through the given [DropPackage]. 21867 +/ 21868 void drop(scope DropPackage*); 21869 /++ 21870 Called when the drop is complete. You should free whatever temporary 21871 resources you were using. It is often reasonable to simply forward 21872 this call to [dragLeave]. 21873 +/ 21874 void finish(); 21875 21876 /++ 21877 Parameters returned by [DropHandler.drop]. 21878 +/ 21879 static struct DropParameters { 21880 /++ 21881 Acceptable action over this area. 21882 +/ 21883 DragAndDropAction action; 21884 /++ 21885 Rectangle, in client coordinates, where the dragger can expect the same result during this drag session and thus need not ask again. 21886 21887 If you leave this as Rectangle.init, the dragger will continue to ask and this can waste resources. 21888 +/ 21889 Rectangle consistentWithin; 21890 } 21891 } 21892 21893 /++ 21894 History: 21895 Added February 19, 2021 21896 +/ 21897 /// Group: drag_and_drop 21898 enum DragAndDropAction { 21899 none = 0, 21900 copy, 21901 move, 21902 link, 21903 ask, 21904 custom 21905 } 21906 21907 /++ 21908 An opaque structure representing dropped data. It contains 21909 private, platform-specific data that your `drop` function 21910 should simply forward. 21911 21912 $(PITFALL This is not yet stable and may break in future versions without notice.) 21913 21914 History: 21915 Added February 19, 2021 21916 +/ 21917 /// Group: drag_and_drop 21918 struct DropPackage { 21919 /++ 21920 Lists the available formats as magic numbers. You should compare these 21921 against looked-up formats (see [DraggableData.getFormatId]) you know you support and can 21922 understand the passed data. 21923 +/ 21924 DraggableData.FormatId[] availableFormats() { 21925 version(X11) { 21926 return xFormats; 21927 } else version(Windows) { 21928 if(pDataObj is null) 21929 return null; 21930 21931 typeof(return) ret; 21932 21933 IEnumFORMATETC ef; 21934 if(pDataObj.EnumFormatEtc(DATADIR.DATADIR_GET, &ef) == S_OK) { 21935 FORMATETC fmt; 21936 ULONG fetched; 21937 while(ef.Next(1, &fmt, &fetched) == S_OK) { 21938 if(fetched == 0) 21939 break; 21940 21941 if(fmt.lindex != -1) 21942 continue; 21943 if(fmt.dwAspect != DVASPECT.DVASPECT_CONTENT) 21944 continue; 21945 if(!(fmt.tymed & TYMED.TYMED_HGLOBAL)) 21946 continue; 21947 21948 ret ~= fmt.cfFormat; 21949 } 21950 } 21951 21952 return ret; 21953 } else throw new NotYetImplementedException(); 21954 } 21955 21956 /++ 21957 Gets data from the drop and optionally accepts it. 21958 21959 Returns: 21960 void because the data is fed asynchronously through the `dg` parameter. 21961 21962 Params: 21963 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. 21964 21965 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. 21966 21967 Calling `getData` again after accepting a drop is not permitted. 21968 21969 format = the format you want, from [availableFormats]. Use [DraggableData.getFormatId] to convert from a MIME string or well-known standard format. 21970 21971 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. 21972 21973 Throws: 21974 if `format` was not compatible with the [availableFormats] or if the drop has already been accepted. 21975 21976 History: 21977 Included in first release of [DropPackage]. 21978 +/ 21979 void getData(DragAndDropAction acceptedAction, DraggableData.FormatId format, void delegate(scope ubyte[] data) dg) { 21980 version(X11) { 21981 21982 auto display = XDisplayConnection.get(); 21983 auto selectionAtom = GetAtom!"XdndSelection"(display); 21984 auto best = format; 21985 21986 static class X11GetSelectionHandler_Drop : X11GetSelectionHandler { 21987 21988 XDisplay* display; 21989 Atom selectionAtom; 21990 DraggableData.FormatId best; 21991 DraggableData.FormatId format; 21992 void delegate(scope ubyte[] data) dg; 21993 DragAndDropAction acceptedAction; 21994 Window sourceWindow; 21995 SimpleWindow win; 21996 this(XDisplay* display, SimpleWindow win, Window sourceWindow, DraggableData.FormatId format, Atom selectionAtom, DraggableData.FormatId best, void delegate(scope ubyte[] data) dg, DragAndDropAction acceptedAction) { 21997 this.display = display; 21998 this.win = win; 21999 this.sourceWindow = sourceWindow; 22000 this.format = format; 22001 this.selectionAtom = selectionAtom; 22002 this.best = best; 22003 this.dg = dg; 22004 this.acceptedAction = acceptedAction; 22005 } 22006 22007 22008 mixin X11GetSelectionHandler_Basics; 22009 22010 void handleData(Atom target, in ubyte[] data) { 22011 //if(target == GetAtom!"UTF8_STRING"(XDisplayConnection.get) || target == XA_STRING || target == GetAtom!"text/plain"(XDisplayConnection.get)) 22012 22013 dg(cast(ubyte[]) data); 22014 22015 if(acceptedAction != DragAndDropAction.none) { 22016 auto display = XDisplayConnection.get; 22017 22018 XClientMessageEvent xclient; 22019 22020 xclient.type = EventType.ClientMessage; 22021 xclient.window = sourceWindow; 22022 xclient.message_type = GetAtom!"XdndFinished"(display); 22023 xclient.format = 32; 22024 xclient.data.l[0] = win.impl.window; 22025 xclient.data.l[1] = 1; // drop successful 22026 xclient.data.l[2] = dndActionAtom(display, acceptedAction); 22027 22028 XSendEvent( 22029 display, 22030 sourceWindow, 22031 false, 22032 EventMask.NoEventMask, 22033 cast(XEvent*) &xclient 22034 ); 22035 22036 XFlush(display); 22037 } 22038 } 22039 22040 Atom findBestFormat(Atom[] answer) { 22041 Atom best = None; 22042 foreach(option; answer) { 22043 if(option == format) { 22044 best = option; 22045 break; 22046 } 22047 /* 22048 if(option == GetAtom!"UTF8_STRING"(XDisplayConnection.get)) { 22049 best = option; 22050 break; 22051 } else if(option == XA_STRING) { 22052 best = option; 22053 } else if(option == GetAtom!"text/plain"(XDisplayConnection.get)) { 22054 best = option; 22055 } 22056 */ 22057 } 22058 return best; 22059 } 22060 } 22061 22062 win.impl.getSelectionHandlers[selectionAtom] = new X11GetSelectionHandler_Drop(display, win, sourceWindow, format, selectionAtom, best, dg, acceptedAction); 22063 22064 XConvertSelection(display, selectionAtom, best, GetAtom!("SDD_DATA", true)(display), win.impl.window, dataTimestamp); 22065 22066 } else version(Windows) { 22067 22068 // clean up like DragLeave 22069 // pass effect back up 22070 22071 FORMATETC t; 22072 assert(format >= 0 && format <= ushort.max); 22073 t.cfFormat = cast(ushort) format; 22074 t.lindex = -1; 22075 t.dwAspect = DVASPECT.DVASPECT_CONTENT; 22076 t.tymed = TYMED.TYMED_HGLOBAL; 22077 22078 STGMEDIUM m; 22079 22080 if(pDataObj.GetData(&t, &m) != S_OK) { 22081 // fail 22082 } else { 22083 // succeed, take the data and clean up 22084 22085 // FIXME: ensure it is legit HGLOBAL 22086 auto handle = m.hGlobal; 22087 22088 if(handle) { 22089 auto sz = GlobalSize(handle); 22090 if(auto ptr = cast(ubyte*) GlobalLock(handle)) { 22091 scope(exit) GlobalUnlock(handle); 22092 scope(exit) GlobalFree(handle); 22093 22094 auto data = ptr[0 .. sz]; 22095 22096 dg(data); 22097 } 22098 } 22099 } 22100 } 22101 } 22102 22103 private: 22104 22105 version(X11) { 22106 SimpleWindow win; 22107 Window sourceWindow; 22108 Time dataTimestamp; 22109 22110 Atom[] xFormats; 22111 } 22112 version(Windows) { 22113 IDataObject pDataObj; 22114 } 22115 } 22116 22117 /++ 22118 A generic helper base class for making a drop handler with a preference list of custom types. 22119 This is the base for [TextDropHandler] and [FilesDropHandler] and you can use it for your own 22120 droppers too. 22121 22122 It assumes the whole window it used, but you can subclass to change that. 22123 22124 $(PITFALL This is not yet stable and may break in future versions without notice.) 22125 22126 History: 22127 Added February 19, 2021 22128 +/ 22129 /// Group: drag_and_drop 22130 class GenericDropHandlerBase : DropHandler { 22131 // no fancy state here so no need to do anything here 22132 void finish() { } 22133 void dragLeave() { } 22134 22135 private DragAndDropAction acceptedAction; 22136 private DraggableData.FormatId acceptedFormat; 22137 private void delegate(scope ubyte[]) acceptedHandler; 22138 22139 struct FormatHandler { 22140 DraggableData.FormatId format; 22141 void delegate(scope ubyte[]) handler; 22142 } 22143 22144 protected abstract FormatHandler[] formatHandlers(); 22145 22146 DragAndDropAction dragEnter(DropPackage* pkg) { 22147 debug(sdpy_dnd) { foreach(fmt; pkg.availableFormats()) writeln(fmt, " ", DraggableData.getFormatName(fmt)); } 22148 foreach(fmt; formatHandlers()) 22149 foreach(f; pkg.availableFormats()) 22150 if(f == fmt.format) { 22151 acceptedFormat = f; 22152 acceptedHandler = fmt.handler; 22153 return acceptedAction = DragAndDropAction.copy; 22154 } 22155 return acceptedAction = DragAndDropAction.none; 22156 } 22157 DropParameters dragOver(Point pt) { 22158 return DropParameters(acceptedAction); 22159 } 22160 22161 void drop(scope DropPackage* dropPackage) { 22162 if(!acceptedFormat || acceptedHandler is null) { 22163 debug(sdpy_dnd) { writeln("drop called w/ handler ", acceptedHandler, " and format ", acceptedFormat); } 22164 return; // prolly shouldn't happen anyway... 22165 } 22166 22167 dropPackage.getData(acceptedAction, acceptedFormat, acceptedHandler); 22168 } 22169 } 22170 22171 /++ 22172 A simple handler for making your window accept drops of plain text. 22173 22174 $(PITFALL This is not yet stable and may break in future versions without notice.) 22175 22176 History: 22177 Added February 22, 2021 22178 +/ 22179 /// Group: drag_and_drop 22180 class TextDropHandler : GenericDropHandlerBase { 22181 private void delegate(in char[] text) dg; 22182 22183 /++ 22184 22185 +/ 22186 this(void delegate(in char[] text) dg) { 22187 this.dg = dg; 22188 } 22189 22190 protected override FormatHandler[] formatHandlers() { 22191 version(X11) 22192 return [ 22193 FormatHandler(GetAtom!"UTF8_STRING"(XDisplayConnection.get), &translator), 22194 FormatHandler(GetAtom!"text/plain;charset=utf-8"(XDisplayConnection.get), &translator), 22195 ]; 22196 else version(Windows) 22197 return [ 22198 FormatHandler(CF_UNICODETEXT, &translator), 22199 ]; 22200 else throw new NotYetImplementedException(); 22201 } 22202 22203 private void translator(scope ubyte[] data) { 22204 version(X11) 22205 dg(cast(char[]) data); 22206 else version(Windows) 22207 dg(makeUtf8StringFromWindowsString(cast(wchar[]) data)); 22208 } 22209 } 22210 22211 /++ 22212 A simple handler for making your window accept drops of files, issued to you as file names. 22213 22214 $(PITFALL This is not yet stable and may break in future versions without notice.) 22215 22216 History: 22217 Added February 22, 2021 22218 +/ 22219 /// Group: drag_and_drop 22220 22221 class FilesDropHandler : GenericDropHandlerBase { 22222 private void delegate(in char[][]) dg; 22223 22224 /++ 22225 22226 +/ 22227 this(void delegate(in char[][] fileNames) dg) { 22228 this.dg = dg; 22229 } 22230 22231 protected override FormatHandler[] formatHandlers() { 22232 version(X11) 22233 return [ 22234 FormatHandler(GetAtom!"text/uri-list"(XDisplayConnection.get), &translator), 22235 ]; 22236 else version(Windows) 22237 return [ 22238 FormatHandler(CF_HDROP, &translator), 22239 ]; 22240 else throw new NotYetImplementedException(); 22241 } 22242 22243 private void translator(scope ubyte[] data) @system { 22244 version(X11) { 22245 char[] listString = cast(char[]) data; 22246 char[][16] buffer; 22247 int count; 22248 char[][] result = buffer[]; 22249 22250 void commit(char[] s) { 22251 if(count == result.length) 22252 result.length += 16; 22253 if(s.length > 7 && s[0 ..7] == "file://") 22254 s = s[7 .. $]; // FIXME: also may need to trim out the host and do some entity decoding 22255 result[count++] = s; 22256 } 22257 22258 size_t last; 22259 foreach(idx, char c; listString) { 22260 if(c == '\n') { 22261 commit(listString[last .. idx - 1]); // a \r 22262 last = idx + 1; // a \n 22263 } 22264 } 22265 22266 if(last < listString.length) { 22267 commit(listString[last .. $]); 22268 } 22269 22270 // FIXME: they are uris now, should I translate it to local file names? 22271 // of course the host name is supposed to be there cuz of X rokking... 22272 22273 dg(result[0 .. count]); 22274 } else version(Windows) { 22275 22276 static struct DROPFILES { 22277 DWORD pFiles; 22278 POINT pt; 22279 BOOL fNC; 22280 BOOL fWide; 22281 } 22282 22283 22284 const(char)[][16] buffer; 22285 int count; 22286 const(char)[][] result = buffer[]; 22287 size_t last; 22288 22289 void commitA(in char[] stuff) { 22290 if(count == result.length) 22291 result.length += 16; 22292 result[count++] = stuff; 22293 } 22294 22295 void commitW(in wchar[] stuff) { 22296 commitA(makeUtf8StringFromWindowsString(stuff)); 22297 } 22298 22299 void magic(T)(T chars) { 22300 size_t idx; 22301 while(chars[idx]) { 22302 last = idx; 22303 while(chars[idx]) { 22304 idx++; 22305 } 22306 static if(is(T == char*)) 22307 commitA(chars[last .. idx]); 22308 else 22309 commitW(chars[last .. idx]); 22310 idx++; 22311 } 22312 } 22313 22314 auto df = cast(DROPFILES*) data.ptr; 22315 if(df.fWide) { 22316 wchar* chars = cast(wchar*) (data.ptr + df.pFiles); 22317 magic(chars); 22318 } else { 22319 char* chars = cast(char*) (data.ptr + df.pFiles); 22320 magic(chars); 22321 } 22322 dg(result[0 .. count]); 22323 } 22324 else throw new NotYetImplementedException(); 22325 } 22326 } 22327 22328 /++ 22329 Interface to describe data being dragged. See also [draggable] helper function. 22330 22331 $(PITFALL This is not yet stable and may break in future versions without notice.) 22332 22333 History: 22334 Added February 19, 2021 22335 +/ 22336 interface DraggableData { 22337 version(X11) 22338 alias FormatId = Atom; 22339 else 22340 alias FormatId = uint; 22341 /++ 22342 Gets the platform-specific FormatId associated with the given named format. 22343 22344 This may be a MIME type, but may also be other various strings defined by the 22345 programs you want to interoperate with. 22346 22347 FIXME: sdpy needs to offer data adapter things that look for compatible formats 22348 and convert it to some particular type for you. 22349 +/ 22350 static FormatId getFormatId(string name)() { 22351 version(X11) 22352 return GetAtom!name(XDisplayConnection.get); 22353 else version(Windows) { 22354 static UINT cache; 22355 if(!cache) 22356 cache = RegisterClipboardFormatA(name); 22357 return cache; 22358 } else 22359 throw new NotYetImplementedException(); 22360 } 22361 22362 /++ 22363 Looks up a string to represent the name for the given format, if there is one. 22364 22365 You should avoid using this function because it is slow. It is provided more for 22366 debugging than for primary use. 22367 +/ 22368 static string getFormatName(FormatId format) { 22369 version(X11) { 22370 if(format == 0) 22371 return "None"; 22372 else 22373 return getAtomName(format, XDisplayConnection.get); 22374 } else version(Windows) { 22375 switch(format) { 22376 case CF_UNICODETEXT: return "CF_UNICODETEXT"; 22377 case CF_DIBV5: return "CF_DIBV5"; 22378 case CF_RIFF: return "CF_RIFF"; 22379 case CF_WAVE: return "CF_WAVE"; 22380 case CF_HDROP: return "CF_HDROP"; 22381 default: 22382 char[1024] name; 22383 auto count = GetClipboardFormatNameA(format, name.ptr, name.length); 22384 return name[0 .. count].idup; 22385 } 22386 } else throw new NotYetImplementedException(); 22387 } 22388 22389 FormatId[] availableFormats(); 22390 // Return the slice of data you filled, empty slice if done. 22391 // this is to support the incremental thing 22392 ubyte[] getData(FormatId format, return scope ubyte[] data); 22393 22394 size_t dataLength(FormatId format); 22395 } 22396 22397 /++ 22398 $(PITFALL This is not yet stable and may break in future versions without notice.) 22399 22400 History: 22401 Added February 19, 2021 22402 +/ 22403 DraggableData draggable(string s) { 22404 version(X11) 22405 return new class X11SetSelectionHandler_Text, DraggableData { 22406 this() { 22407 super(s); 22408 } 22409 22410 override FormatId[] availableFormats() { 22411 return X11SetSelectionHandler_Text.availableFormats(); 22412 } 22413 22414 override ubyte[] getData(FormatId format, return scope ubyte[] data) { 22415 return X11SetSelectionHandler_Text.getData(format, data); 22416 } 22417 22418 size_t dataLength(FormatId format) { 22419 return s.length; 22420 } 22421 }; 22422 else version(Windows) 22423 return new class DraggableData { 22424 FormatId[] availableFormats() { 22425 return [CF_UNICODETEXT]; 22426 } 22427 22428 ubyte[] getData(FormatId format, return scope ubyte[] data) { 22429 return cast(ubyte[]) makeWindowsString(s, cast(wchar[]) data, WindowsStringConversionFlags.convertNewLines | WindowsStringConversionFlags.zeroTerminate); 22430 } 22431 22432 size_t dataLength(FormatId format) { 22433 return sizeOfConvertedWstring(s, WindowsStringConversionFlags.convertNewLines | WindowsStringConversionFlags.zeroTerminate) * wchar.sizeof; 22434 } 22435 }; 22436 else 22437 throw new NotYetImplementedException(); 22438 } 22439 22440 /++ 22441 $(PITFALL This is not yet stable and may break in future versions without notice.) 22442 22443 History: 22444 Added February 19, 2021 22445 +/ 22446 /// Group: drag_and_drop 22447 int doDragDrop(SimpleWindow window, DraggableData handler, DragAndDropAction action = DragAndDropAction.copy) 22448 in { 22449 assert(window !is null); 22450 assert(handler !is null); 22451 } 22452 do 22453 { 22454 version(X11) { 22455 auto sh = cast(X11SetSelectionHandler) handler; 22456 if(sh is null) { 22457 // gotta make my own adapter. 22458 sh = new class X11SetSelectionHandler { 22459 mixin X11SetSelectionHandler_Basics; 22460 22461 Atom[] availableFormats() { return handler.availableFormats(); } 22462 ubyte[] getData(Atom format, return scope ubyte[] data) { 22463 return handler.getData(format, data); 22464 } 22465 22466 // since the drop selection is only ever used once it isn't important 22467 // to reset it. 22468 void done() {} 22469 }; 22470 } 22471 return doDragDropX11(window, sh, action); 22472 } else version(Windows) { 22473 return doDragDropWindows(window, handler, action); 22474 } else throw new NotYetImplementedException(); 22475 } 22476 22477 version(Windows) 22478 private int doDragDropWindows(SimpleWindow window, DraggableData handler, DragAndDropAction action = DragAndDropAction.copy) { 22479 IDataObject obj = new class IDataObject { 22480 ULONG refCount; 22481 ULONG AddRef() { 22482 return ++refCount; 22483 } 22484 ULONG Release() { 22485 return --refCount; 22486 } 22487 HRESULT QueryInterface(const (IID)*riid, LPVOID *ppv) { 22488 if (IID_IUnknown == *riid) { 22489 *ppv = cast(void*) cast(IUnknown) this; 22490 } 22491 else if (IID_IDataObject == *riid) { 22492 *ppv = cast(void*) cast(IDataObject) this; 22493 } 22494 else { 22495 *ppv = null; 22496 return E_NOINTERFACE; 22497 } 22498 22499 AddRef(); 22500 return NOERROR; 22501 } 22502 22503 HRESULT DAdvise(FORMATETC* pformatetc, DWORD advf, IAdviseSink pAdvSink, DWORD* pdwConnection) { 22504 // writeln("Advise"); 22505 return E_NOTIMPL; 22506 } 22507 HRESULT DUnadvise(DWORD dwConnection) { 22508 return E_NOTIMPL; 22509 } 22510 HRESULT EnumDAdvise(IEnumSTATDATA* ppenumAdvise) { 22511 // writeln("EnumDAdvise"); 22512 return OLE_E_ADVISENOTSUPPORTED; 22513 } 22514 // tell what formats it supports 22515 22516 FORMATETC[] types; 22517 this() { 22518 FORMATETC t; 22519 foreach(ty; handler.availableFormats()) { 22520 assert(ty <= ushort.max && ty >= 0); 22521 t.cfFormat = cast(ushort) ty; 22522 t.lindex = -1; 22523 t.dwAspect = DVASPECT.DVASPECT_CONTENT; 22524 t.tymed = TYMED.TYMED_HGLOBAL; 22525 } 22526 types ~= t; 22527 } 22528 HRESULT EnumFormatEtc(DWORD dwDirection, IEnumFORMATETC* ppenumFormatEtc) { 22529 if(dwDirection == DATADIR.DATADIR_GET) { 22530 *ppenumFormatEtc = new class IEnumFORMATETC { 22531 ULONG refCount; 22532 ULONG AddRef() { 22533 return ++refCount; 22534 } 22535 ULONG Release() { 22536 return --refCount; 22537 } 22538 HRESULT QueryInterface(const (IID)*riid, LPVOID *ppv) { 22539 if (IID_IUnknown == *riid) { 22540 *ppv = cast(void*) cast(IUnknown) this; 22541 } 22542 else if (IID_IEnumFORMATETC == *riid) { 22543 *ppv = cast(void*) cast(IEnumFORMATETC) this; 22544 } 22545 else { 22546 *ppv = null; 22547 return E_NOINTERFACE; 22548 } 22549 22550 AddRef(); 22551 return NOERROR; 22552 } 22553 22554 22555 int pos; 22556 this() { 22557 pos = 0; 22558 } 22559 22560 HRESULT Clone(IEnumFORMATETC* ppenum) { 22561 // writeln("clone"); 22562 return E_NOTIMPL; // FIXME 22563 } 22564 22565 // Caller is responsible for freeing memory 22566 HRESULT Next(ULONG celt, FORMATETC* rgelt, ULONG* pceltFetched) { 22567 // fetched may be null if celt is one 22568 if(celt != 1) 22569 return E_NOTIMPL; // FIXME 22570 22571 if(celt + pos > types.length) 22572 return S_FALSE; 22573 22574 *rgelt = types[pos++]; 22575 22576 if(pceltFetched !is null) 22577 *pceltFetched = 1; 22578 22579 // writeln("ok celt ", celt); 22580 return S_OK; 22581 } 22582 22583 HRESULT Reset() { 22584 pos = 0; 22585 return S_OK; 22586 } 22587 22588 HRESULT Skip(ULONG celt) { 22589 if(celt + pos <= types.length) { 22590 pos += celt; 22591 return S_OK; 22592 } 22593 return S_FALSE; 22594 } 22595 }; 22596 22597 return S_OK; 22598 } else 22599 return E_NOTIMPL; 22600 } 22601 // given a format, return the format you'd prefer to use cuz it is identical 22602 HRESULT GetCanonicalFormatEtc(FORMATETC* pformatectIn, FORMATETC* pformatetcOut) { 22603 // FIXME: prolly could be better but meh 22604 // writeln("gcf: ", *pformatectIn); 22605 *pformatetcOut = *pformatectIn; 22606 return S_OK; 22607 } 22608 HRESULT GetData(FORMATETC* pformatetcIn, STGMEDIUM* pmedium) { 22609 foreach(ty; types) { 22610 if(ty == *pformatetcIn) { 22611 auto format = ty.cfFormat; 22612 // writeln("A: ", *pformatetcIn, "\nB: ", ty); 22613 STGMEDIUM medium; 22614 medium.tymed = TYMED.TYMED_HGLOBAL; 22615 22616 auto sz = handler.dataLength(format); 22617 auto handle = GlobalAlloc(GMEM_MOVEABLE, sz); 22618 if(handle is null) throw new WindowsApiException("GlobalAlloc", GetLastError()); 22619 if(auto data = cast(wchar*) GlobalLock(handle)) { 22620 auto slice = data[0 .. sz]; 22621 scope(exit) 22622 GlobalUnlock(handle); 22623 22624 handler.getData(format, cast(ubyte[]) slice[]); 22625 } 22626 22627 22628 medium.hGlobal = handle; // FIXME 22629 *pmedium = medium; 22630 return S_OK; 22631 } 22632 } 22633 return DV_E_FORMATETC; 22634 } 22635 HRESULT GetDataHere(FORMATETC* pformatetcIn, STGMEDIUM* pmedium) { 22636 // writeln("GDH: ", *pformatetcIn); 22637 return E_NOTIMPL; // FIXME 22638 } 22639 HRESULT QueryGetData(FORMATETC* pformatetc) { 22640 auto search = *pformatetc; 22641 search.tymed &= TYMED.TYMED_HGLOBAL; 22642 foreach(ty; types) 22643 if(ty == search) { 22644 // writeln("QueryGetData ", search, " ", types[0]); 22645 return S_OK; 22646 } 22647 if(pformatetc.cfFormat==CF_UNICODETEXT) { 22648 //writeln("QueryGetData FALSE ", search, " ", types[0]); 22649 } 22650 return S_FALSE; 22651 } 22652 HRESULT SetData(FORMATETC* pformatetc, STGMEDIUM* pmedium, BOOL fRelease) { 22653 // writeln("SetData: "); 22654 return E_NOTIMPL; 22655 } 22656 }; 22657 22658 22659 IDropSource src = new class IDropSource { 22660 ULONG refCount; 22661 ULONG AddRef() { 22662 return ++refCount; 22663 } 22664 ULONG Release() { 22665 return --refCount; 22666 } 22667 HRESULT QueryInterface(const (IID)*riid, LPVOID *ppv) { 22668 if (IID_IUnknown == *riid) { 22669 *ppv = cast(void*) cast(IUnknown) this; 22670 } 22671 else if (IID_IDropSource == *riid) { 22672 *ppv = cast(void*) cast(IDropSource) this; 22673 } 22674 else { 22675 *ppv = null; 22676 return E_NOINTERFACE; 22677 } 22678 22679 AddRef(); 22680 return NOERROR; 22681 } 22682 22683 int QueryContinueDrag(int fEscapePressed, uint grfKeyState) { 22684 if(fEscapePressed) 22685 return DRAGDROP_S_CANCEL; 22686 if(!(grfKeyState & MK_LBUTTON)) 22687 return DRAGDROP_S_DROP; 22688 return S_OK; 22689 } 22690 22691 int GiveFeedback(uint dwEffect) { 22692 return DRAGDROP_S_USEDEFAULTCURSORS; 22693 } 22694 }; 22695 22696 DWORD effect; 22697 22698 if(action == DragAndDropAction.none) assert(0, "Don't drag something with a none effect."); 22699 22700 DROPEFFECT de = win32DragAndDropAction(action); 22701 22702 // I'm not as concerned about the GC here since DoDragDrop blocks so the stack frame still sane the whole time 22703 // but still prolly a FIXME 22704 22705 auto ret = DoDragDrop(obj, src, de, &effect); 22706 /+ 22707 if(ret == DRAGDROP_S_DROP) 22708 writeln("drop ", effect); 22709 else if(ret == DRAGDROP_S_CANCEL) 22710 writeln("cancel"); 22711 else if(ret == S_OK) 22712 writeln("ok"); 22713 else writeln(ret); 22714 +/ 22715 22716 return ret; 22717 } 22718 22719 version(Windows) 22720 DROPEFFECT win32DragAndDropAction(DragAndDropAction action) { 22721 DROPEFFECT de; 22722 22723 with(DragAndDropAction) 22724 with(DROPEFFECT) 22725 final switch(action) { 22726 case none: de = DROPEFFECT_NONE; break; 22727 case copy: de = DROPEFFECT_COPY; break; 22728 case move: de = DROPEFFECT_MOVE; break; 22729 case link: de = DROPEFFECT_LINK; break; 22730 case ask: throw new Exception("ask not implemented yet"); 22731 case custom: throw new Exception("custom not implemented yet"); 22732 } 22733 22734 return de; 22735 } 22736 22737 22738 /++ 22739 History: 22740 Added February 19, 2021 22741 +/ 22742 /// Group: drag_and_drop 22743 void enableDragAndDrop(SimpleWindow window, DropHandler handler) { 22744 version(X11) { 22745 auto display = XDisplayConnection.get; 22746 22747 Atom atom = 5; // right??? 22748 22749 XChangeProperty( 22750 display, 22751 window.impl.window, 22752 GetAtom!"XdndAware"(display), 22753 XA_ATOM, 22754 32 /* bits */, 22755 PropModeReplace, 22756 &atom, 22757 1); 22758 22759 window.dropHandler = handler; 22760 } else version(Windows) { 22761 22762 initDnd(); 22763 22764 auto dropTarget = new class (handler) IDropTarget { 22765 DropHandler handler; 22766 this(DropHandler handler) { 22767 this.handler = handler; 22768 } 22769 ULONG refCount; 22770 ULONG AddRef() { 22771 return ++refCount; 22772 } 22773 ULONG Release() { 22774 return --refCount; 22775 } 22776 HRESULT QueryInterface(const (IID)*riid, LPVOID *ppv) { 22777 if (IID_IUnknown == *riid) { 22778 *ppv = cast(void*) cast(IUnknown) this; 22779 } 22780 else if (IID_IDropTarget == *riid) { 22781 *ppv = cast(void*) cast(IDropTarget) this; 22782 } 22783 else { 22784 *ppv = null; 22785 return E_NOINTERFACE; 22786 } 22787 22788 AddRef(); 22789 return NOERROR; 22790 } 22791 22792 22793 // /////////////////// 22794 22795 HRESULT DragEnter(IDataObject pDataObj, DWORD grfKeyState, POINTL pt, DWORD* pdwEffect) { 22796 DropPackage dropPackage = DropPackage(pDataObj); 22797 *pdwEffect = win32DragAndDropAction(handler.dragEnter(&dropPackage)); 22798 return S_OK; // https://docs.microsoft.com/en-us/windows/win32/api/oleidl/nf-oleidl-idroptarget-dragenter 22799 } 22800 22801 HRESULT DragLeave() { 22802 handler.dragLeave(); 22803 // release the IDataObject if needed 22804 return S_OK; 22805 } 22806 22807 HRESULT DragOver(DWORD grfKeyState, POINTL pt, DWORD* pdwEffect) { 22808 auto res = handler.dragOver(Point(pt.x, pt.y)); // FIXME: translate screen coordinates back to window coordinates 22809 22810 *pdwEffect = win32DragAndDropAction(res.action); 22811 // same as DragEnter basically 22812 return S_OK; 22813 } 22814 22815 HRESULT Drop(IDataObject pDataObj, DWORD grfKeyState, POINTL pt, DWORD* pdwEffect) { 22816 DropPackage pkg = DropPackage(pDataObj); 22817 handler.drop(&pkg); 22818 22819 return S_OK; 22820 } 22821 }; 22822 // Windows can hold on to the handler and try to call it 22823 // during which time the GC can't see it. so important to 22824 // manually manage this. At some point i'll FIXME and make 22825 // all my com instances manually managed since they supposed 22826 // to respect the refcount. 22827 import core.memory; 22828 GC.addRoot(cast(void*) dropTarget); 22829 22830 if(RegisterDragDrop(window.impl.hwnd, dropTarget) != S_OK) 22831 throw new WindowsApiException("RegisterDragDrop", GetLastError()); 22832 22833 window.dropHandler = handler; 22834 } else throw new NotYetImplementedException(); 22835 } 22836 22837 22838 22839 static if(UsingSimpledisplayX11) { 22840 22841 enum _NET_WM_STATE_ADD = 1; 22842 enum _NET_WM_STATE_REMOVE = 0; 22843 enum _NET_WM_STATE_TOGGLE = 2; 22844 22845 /// X-specific. Use [SimpleWindow.requestAttention] instead for most cases. 22846 void demandAttention(SimpleWindow window, bool needs = true) { 22847 demandAttention(window.impl.window, needs); 22848 } 22849 22850 /// ditto 22851 void demandAttention(Window window, bool needs = true) { 22852 setNetWmStateAtom(window, GetAtom!("_NET_WM_STATE_DEMANDS_ATTENTION", false)(XDisplayConnection.get), needs); 22853 } 22854 22855 void setNetWmStateAtom(Window window, Atom atom, bool set = true, Atom atom2 = None) { 22856 auto display = XDisplayConnection.get(); 22857 if(atom == None) 22858 return; // non-failure error 22859 //auto atom2 = GetAtom!"_NET_WM_STATE_SHADED"(display); 22860 22861 XClientMessageEvent xclient; 22862 22863 xclient.type = EventType.ClientMessage; 22864 xclient.window = window; 22865 xclient.message_type = GetAtom!"_NET_WM_STATE"(display); 22866 xclient.format = 32; 22867 xclient.data.l[0] = set ? _NET_WM_STATE_ADD : _NET_WM_STATE_REMOVE; 22868 xclient.data.l[1] = atom; 22869 xclient.data.l[2] = atom2; 22870 xclient.data.l[3] = 1; 22871 // [3] == source. 0 == unknown, 1 == app, 2 == else 22872 22873 XSendEvent( 22874 display, 22875 RootWindow(display, DefaultScreen(display)), 22876 false, 22877 EventMask.SubstructureRedirectMask | EventMask.SubstructureNotifyMask, 22878 cast(XEvent*) &xclient 22879 ); 22880 22881 /+ 22882 XChangeProperty( 22883 display, 22884 window.impl.window, 22885 GetAtom!"_NET_WM_STATE"(display), 22886 XA_ATOM, 22887 32 /* bits */, 22888 PropModeAppend, 22889 &atom, 22890 1); 22891 +/ 22892 } 22893 22894 private Atom dndActionAtom(Display* display, DragAndDropAction action) { 22895 Atom actionAtom; 22896 with(DragAndDropAction) 22897 final switch(action) { 22898 case none: actionAtom = None; break; 22899 case copy: actionAtom = GetAtom!"XdndActionCopy"(display); break; 22900 case move: actionAtom = GetAtom!"XdndActionMove"(display); break; 22901 case link: actionAtom = GetAtom!"XdndActionLink"(display); break; 22902 case ask: actionAtom = GetAtom!"XdndActionAsk"(display); break; 22903 case custom: actionAtom = GetAtom!"XdndActionCustom"(display); break; 22904 } 22905 22906 return actionAtom; 22907 } 22908 22909 private int doDragDropX11(SimpleWindow window, X11SetSelectionHandler handler, DragAndDropAction action) { 22910 // FIXME: I need to show user feedback somehow. 22911 auto display = XDisplayConnection.get; 22912 22913 auto actionAtom = dndActionAtom(display, action); 22914 assert(actionAtom, "Don't use action none to accept a drop"); 22915 22916 setX11Selection!"XdndSelection"(window, handler, null); 22917 22918 auto oldKeyHandler = window.handleKeyEvent; 22919 scope(exit) window.handleKeyEvent = oldKeyHandler; 22920 22921 auto oldCharHandler = window.handleCharEvent; 22922 scope(exit) window.handleCharEvent = oldCharHandler; 22923 22924 auto oldMouseHandler = window.handleMouseEvent; 22925 scope(exit) window.handleMouseEvent = oldMouseHandler; 22926 22927 Window[Window] eligibility; // 0 == not eligible, otherwise it is the window id of an eligible child 22928 22929 import core.sys.posix.sys.time; 22930 timeval tv; 22931 gettimeofday(&tv, null); 22932 22933 Time dataTimestamp = cast(Time) ( tv.tv_sec * 1000 + tv.tv_usec / 1000 ); 22934 22935 Time lastMouseTimestamp; 22936 22937 bool dnding = true; 22938 Window lastIn = None; 22939 22940 void leave() { 22941 if(lastIn == None) 22942 return; 22943 22944 XEvent ev; 22945 ev.xclient.type = EventType.ClientMessage; 22946 ev.xclient.window = lastIn; 22947 ev.xclient.message_type = GetAtom!("XdndLeave", true)(display); 22948 ev.xclient.format = 32; 22949 ev.xclient.data.l[0] = window.impl.window; 22950 22951 XSendEvent(XDisplayConnection.get, lastIn, false, EventMask.NoEventMask, &ev); 22952 XFlush(display); 22953 22954 lastIn = None; 22955 } 22956 22957 void enter(Window w) { 22958 assert(lastIn == None); 22959 22960 lastIn = w; 22961 22962 XEvent ev; 22963 ev.xclient.type = EventType.ClientMessage; 22964 ev.xclient.window = lastIn; 22965 ev.xclient.message_type = GetAtom!("XdndEnter", true)(display); 22966 ev.xclient.format = 32; 22967 ev.xclient.data.l[0] = window.impl.window; 22968 ev.xclient.data.l[1] = (5 << 24) | 0; // version 5, no more sources. FIXME source types 22969 22970 auto types = handler.availableFormats(); 22971 assert(types.length > 0); 22972 22973 ev.xclient.data.l[2] = types[0]; 22974 if(types.length > 1) 22975 ev.xclient.data.l[3] = types[1]; 22976 if(types.length > 2) 22977 ev.xclient.data.l[4] = types[2]; 22978 22979 // FIXME: other types?!?!? and make sure we skip TARGETS 22980 22981 XSendEvent(XDisplayConnection.get, lastIn, false, EventMask.NoEventMask, &ev); 22982 XFlush(display); 22983 } 22984 22985 void position(int rootX, int rootY) { 22986 assert(lastIn != None); 22987 22988 XEvent ev; 22989 ev.xclient.type = EventType.ClientMessage; 22990 ev.xclient.window = lastIn; 22991 ev.xclient.message_type = GetAtom!("XdndPosition", true)(display); 22992 ev.xclient.format = 32; 22993 ev.xclient.data.l[0] = window.impl.window; 22994 ev.xclient.data.l[1] = 0; // reserved 22995 ev.xclient.data.l[2] = (rootX << 16) | rootY; 22996 ev.xclient.data.l[3] = dataTimestamp; 22997 ev.xclient.data.l[4] = actionAtom; 22998 22999 XSendEvent(XDisplayConnection.get, lastIn, false, EventMask.NoEventMask, &ev); 23000 XFlush(display); 23001 23002 } 23003 23004 void drop() { 23005 XEvent ev; 23006 ev.xclient.type = EventType.ClientMessage; 23007 ev.xclient.window = lastIn; 23008 ev.xclient.message_type = GetAtom!("XdndDrop", true)(display); 23009 ev.xclient.format = 32; 23010 ev.xclient.data.l[0] = window.impl.window; 23011 ev.xclient.data.l[1] = 0; // reserved 23012 ev.xclient.data.l[2] = dataTimestamp; 23013 23014 XSendEvent(XDisplayConnection.get, lastIn, false, EventMask.NoEventMask, &ev); 23015 XFlush(display); 23016 23017 lastIn = None; 23018 dnding = false; 23019 } 23020 23021 // fyi nativeEventHandler can return 0 if it handles it, or otherwise it goes back to the normal handler 23022 // but idk if i should... 23023 23024 window.setEventHandlers( 23025 delegate(KeyEvent ev) { 23026 if(ev.pressed == true && ev.key == Key.Escape) { 23027 // cancel 23028 dnding = false; 23029 } 23030 }, 23031 delegate(MouseEvent ev) { 23032 if(ev.timestamp < lastMouseTimestamp) 23033 return; 23034 23035 lastMouseTimestamp = ev.timestamp; 23036 23037 if(ev.type == MouseEventType.motion) { 23038 auto display = XDisplayConnection.get; 23039 auto root = RootWindow(display, DefaultScreen(display)); 23040 23041 Window topWindow; 23042 int rootX, rootY; 23043 23044 XTranslateCoordinates(display, window.impl.window, root, ev.x, ev.y, &rootX, &rootY, &topWindow); 23045 23046 if(topWindow == None) 23047 return; 23048 23049 top: 23050 if(auto result = topWindow in eligibility) { 23051 auto dropWindow = *result; 23052 if(dropWindow == None) { 23053 leave(); 23054 return; 23055 } 23056 23057 if(dropWindow != lastIn) { 23058 leave(); 23059 enter(dropWindow); 23060 position(rootX, rootY); 23061 } else { 23062 position(rootX, rootY); 23063 } 23064 } else { 23065 // determine eligibility 23066 auto data = cast(Atom[]) getX11PropertyData(topWindow, GetAtom!"XdndAware"(display), XA_ATOM); 23067 if(data.length == 1) { 23068 // in case there is no WM or it isn't reparenting 23069 eligibility[topWindow] = (data[0] == 5) ? topWindow : None; // FIXME I'm supposed to handle older versions too but meh 23070 } else { 23071 23072 Window tryScanChildren(Window search, int maxRecurse) { 23073 // could be reparenting window manager, so gotta check the next few children too 23074 Window child; 23075 int x; 23076 int y; 23077 XTranslateCoordinates(display, window.impl.window, search, ev.x, ev.y, &x, &y, &child); 23078 23079 if(child == None) 23080 return None; 23081 auto data = cast(Atom[]) getX11PropertyData(child, GetAtom!"XdndAware"(display), XA_ATOM); 23082 if(data.length == 1) { 23083 return (data[0] == 5) ? child : None; // FIXME I'm supposed to handle older versions too but meh 23084 } else { 23085 if(maxRecurse) 23086 return tryScanChildren(child, maxRecurse - 1); 23087 else 23088 return None; 23089 } 23090 23091 } 23092 23093 // if a WM puts more than 3 layers on it, like wtf is it doing, screw that. 23094 auto topResult = tryScanChildren(topWindow, 3); 23095 // it is easy to have a false negative due to the mouse going over a WM 23096 // child window like the close button if separate from the frame... so I 23097 // can't really cache negatives, :( 23098 if(topResult != None) { 23099 eligibility[topWindow] = topResult; 23100 goto top; // reload to do the positioning iff eligibility changed lest we endless loop 23101 } 23102 } 23103 23104 } 23105 23106 } else if(ev.type == MouseEventType.buttonReleased) { 23107 drop(); 23108 dnding = false; 23109 } 23110 } 23111 ); 23112 23113 window.grabInput(); 23114 scope(exit) 23115 window.releaseInputGrab(); 23116 23117 23118 EventLoop.get.run(() => dnding); 23119 23120 return 0; 23121 } 23122 23123 /// X-specific 23124 TrueColorImage getWindowNetWmIcon(Window window) { 23125 try { 23126 auto display = XDisplayConnection.get; 23127 23128 auto data = getX11PropertyData (window, GetAtom!"_NET_WM_ICON"(display), XA_CARDINAL); 23129 23130 if (data.length > arch_ulong.sizeof * 2) { 23131 auto meta = cast(arch_ulong[]) (data[0 .. arch_ulong.sizeof * 2]); 23132 // these are an array of rgba images that we have to convert into pixmaps ourself 23133 23134 int width = cast(int) meta[0]; 23135 int height = cast(int) meta[1]; 23136 23137 auto bytes = cast(ubyte[]) (data[arch_ulong.sizeof * 2 .. $]); 23138 23139 static if(arch_ulong.sizeof == 4) { 23140 bytes = bytes[0 .. width * height * 4]; 23141 alias imageData = bytes; 23142 } else static if(arch_ulong.sizeof == 8) { 23143 bytes = bytes[0 .. width * height * 8]; 23144 auto imageData = new ubyte[](4 * width * height); 23145 } else static assert(0); 23146 23147 23148 23149 // this returns ARGB. Remember it is little-endian so 23150 // we have BGRA 23151 // our thing uses RGBA, which in little endian, is ABGR 23152 for(int idx = 0, idx2 = 0; idx < bytes.length; idx += arch_ulong.sizeof, idx2 += 4) { 23153 auto r = bytes[idx + 2]; 23154 auto g = bytes[idx + 1]; 23155 auto b = bytes[idx + 0]; 23156 auto a = bytes[idx + 3]; 23157 23158 imageData[idx2 + 0] = r; 23159 imageData[idx2 + 1] = g; 23160 imageData[idx2 + 2] = b; 23161 imageData[idx2 + 3] = a; 23162 } 23163 23164 return new TrueColorImage(width, height, imageData); 23165 } 23166 23167 return null; 23168 } catch(Exception e) { 23169 return null; 23170 } 23171 } 23172 23173 } /* UsingSimpledisplayX11 */ 23174 23175 23176 void loadBinNameToWindowClassName () { 23177 import core.stdc.stdlib : realloc; 23178 version(linux) { 23179 // args[0] MAY be empty, so we'll just use this 23180 import core.sys.posix.unistd : readlink; 23181 char[1024] ebuf = void; // 1KB should be enough for everyone! 23182 auto len = readlink("/proc/self/exe", ebuf.ptr, ebuf.length); 23183 if (len < 1) return; 23184 } else /*version(Windows)*/ { 23185 import core.runtime : Runtime; 23186 if (Runtime.args.length == 0 || Runtime.args[0].length == 0) return; 23187 auto ebuf = Runtime.args[0]; 23188 auto len = ebuf.length; 23189 } 23190 auto pos = len; 23191 while (pos > 0 && ebuf[pos-1] != '/') --pos; 23192 sdpyWindowClassStr = cast(char*)realloc(sdpyWindowClassStr, len-pos+1); 23193 if (sdpyWindowClassStr is null) return; // oops 23194 sdpyWindowClassStr[0..len-pos+1] = 0; // just in case 23195 sdpyWindowClassStr[0..len-pos] = ebuf[pos..len]; 23196 } 23197 23198 /++ 23199 An interface representing a font that is drawn with custom facilities. 23200 23201 You might want [OperatingSystemFont] instead, which represents 23202 a font loaded and drawn by functions native to the operating system. 23203 23204 WARNING: I might still change this. 23205 +/ 23206 interface DrawableFont : MeasurableFont { 23207 /++ 23208 Please note the point is upperLeft, NOT baseline! This is the point of a bounding box of the string. 23209 23210 Implementations must use the painter's fillColor to draw a rectangle behind the string, 23211 then use the outlineColor to draw the string. It might alpha composite if there's a transparent 23212 fill color, but that's up to the implementation. 23213 +/ 23214 void drawString(ScreenPainter painter, Point upperLeft, in char[] text); 23215 23216 /++ 23217 Requests that the given string is added to the image cache. You should only do this rarely, but 23218 if you have a string that you know will be used over and over again, adding it to a cache can 23219 improve things (assuming the implementation actually has a cache; it is also valid for an implementation 23220 to implement this as a do-nothing method). 23221 +/ 23222 void cacheString(SimpleWindow window, Color foreground, Color background, string text); 23223 } 23224 23225 /++ 23226 Loads a true type font using [arsd.ttf] that can be drawn as images on windows 23227 through a [ScreenPainter]. That module must be compiled in if you choose to use this function. 23228 23229 You should also consider [OperatingSystemFont], which loads and draws a font with 23230 facilities native to the user's operating system. You might also consider 23231 [arsd.ttf.OpenGlLimitedFont] or using [arsd.nanovega] if you are making some kind 23232 of game, as they have their own ways to draw text too. 23233 23234 Be warned: this can be slow, especially on remote connections to the X server, since 23235 it needs to create and transfer bitmaps instead of just text. The [DrawableFont] interface 23236 offers [DrawableFont.cacheString] which can help with this, sometimes. You might want to 23237 experiment in your specific case. 23238 23239 Please note that the return type of [DrawableFont] also includes an implementation of 23240 [MeasurableFont]. 23241 +/ 23242 DrawableFont arsdTtfFont()(in ubyte[] data, int size) { 23243 import arsd.ttf; 23244 static class ArsdTtfFont : DrawableFont { 23245 TtfFont font; 23246 int size; 23247 this(in ubyte[] data, int size) { 23248 font = TtfFont(data); 23249 this.size = size; 23250 23251 23252 auto scale = stbtt_ScaleForPixelHeight(&font.font, size); 23253 int ascent_, descent_, line_gap; 23254 stbtt_GetFontVMetrics(&font.font, &ascent_, &descent_, &line_gap); 23255 23256 int advance, lsb; 23257 stbtt_GetCodepointHMetrics(&font.font, 'x', &advance, &lsb); 23258 xWidth = cast(int) (advance * scale); 23259 stbtt_GetCodepointHMetrics(&font.font, 'M', &advance, &lsb); 23260 MWidth = cast(int) (advance * scale); 23261 } 23262 23263 private int ascent_; 23264 private int descent_; 23265 private int xWidth; 23266 private int MWidth; 23267 23268 bool isMonospace() { 23269 return xWidth == MWidth; 23270 } 23271 int averageWidth() { 23272 return xWidth; 23273 } 23274 int height() { 23275 return size; 23276 } 23277 int ascent() { 23278 return ascent_; 23279 } 23280 int descent() { 23281 return descent_; 23282 } 23283 23284 int stringWidth(scope const(char)[] s, SimpleWindow window = null) { 23285 int width, height; 23286 font.getStringSize(s, size, width, height); 23287 return width; 23288 } 23289 23290 23291 23292 Sprite[string] cache; 23293 23294 void cacheString(SimpleWindow window, Color foreground, Color background, string text) { 23295 auto sprite = new Sprite(window, stringToImage(foreground, background, text)); 23296 cache[text] = sprite; 23297 } 23298 23299 Image stringToImage(Color fg, Color bg, in char[] text) { 23300 int width, height; 23301 auto data = font.renderString(text, size, width, height); 23302 auto image = new TrueColorImage(width, height); 23303 int pos = 0; 23304 foreach(y; 0 .. height) 23305 foreach(x; 0 .. width) { 23306 fg.a = data[0]; 23307 bg.a = 255; 23308 auto color = alphaBlend(fg, bg); 23309 image.imageData.bytes[pos++] = color.r; 23310 image.imageData.bytes[pos++] = color.g; 23311 image.imageData.bytes[pos++] = color.b; 23312 image.imageData.bytes[pos++] = data[0]; 23313 data = data[1 .. $]; 23314 } 23315 assert(data.length == 0); 23316 23317 return Image.fromMemoryImage(image); 23318 } 23319 23320 void drawString(ScreenPainter painter, Point upperLeft, in char[] text) { 23321 Sprite sprite = (text in cache) ? *(text in cache) : null; 23322 23323 auto fg = painter.impl._outlineColor; 23324 auto bg = painter.impl._fillColor; 23325 23326 if(sprite !is null) { 23327 auto w = cast(SimpleWindow) painter.window; 23328 assert(w !is null); 23329 23330 sprite.drawAt(painter, upperLeft); 23331 } else { 23332 painter.drawImage(upperLeft, stringToImage(fg, bg, text)); 23333 } 23334 } 23335 } 23336 23337 return new ArsdTtfFont(data, size); 23338 } 23339 23340 class NotYetImplementedException : Exception { 23341 this(string file = __FILE__, size_t line = __LINE__) { 23342 super("Not yet implemented", file, line); 23343 } 23344 } 23345 23346 /// 23347 __gshared bool librariesSuccessfullyLoaded = true; 23348 /// 23349 __gshared bool openGlLibrariesSuccessfullyLoaded = true; 23350 23351 private mixin template DynamicLoadSupplementalOpenGL(Iface) { 23352 // mixin(staticForeachReplacement!Iface); 23353 static foreach(name; __traits(derivedMembers, Iface)) 23354 mixin("__gshared typeof(&__traits(getMember, Iface, name)) " ~ name ~ ";"); 23355 23356 void loadDynamicLibrary() @nogc { 23357 (cast(void function() @nogc) &loadDynamicLibraryForReal)(); 23358 } 23359 23360 void loadDynamicLibraryForReal() { 23361 foreach(name; __traits(derivedMembers, Iface)) { 23362 mixin("alias tmp = " ~ name ~ ";"); 23363 tmp = cast(typeof(tmp)) glbindGetProcAddress(name); 23364 if(tmp is null) throw new Exception("load failure of function " ~ name ~ " from supplemental OpenGL"); 23365 } 23366 } 23367 } 23368 23369 /+ 23370 private const(char)[] staticForeachReplacement(Iface)() pure { 23371 /* 23372 // just this for gdc 9.... 23373 // when i drop support for it and switch to gdc10, we can put this original back for a slight compile time ram decrease 23374 23375 static foreach(name; __traits(derivedMembers, Iface)) 23376 mixin("__gshared typeof(&__traits(getMember, Iface, name)) " ~ name ~ ";"); 23377 */ 23378 23379 char[] code = new char[](__traits(derivedMembers, Iface).length * 64); 23380 size_t pos; 23381 23382 void append(in char[] what) { 23383 if(pos + what.length > code.length) 23384 code.length = (code.length * 3) / 2; 23385 code[pos .. pos + what.length] = what[]; 23386 pos += what.length; 23387 } 23388 23389 foreach(name; __traits(derivedMembers, Iface)) { 23390 append(`__gshared typeof(&__traits(getMember, Iface, "`); 23391 append(name); 23392 append(`")) `); 23393 append(name); 23394 append(";"); 23395 } 23396 23397 return code[0 .. pos]; 23398 } 23399 +/ 23400 23401 private mixin template DynamicLoad(Iface, string library, int majorVersion, alias success) { 23402 //mixin(staticForeachReplacement!Iface); 23403 static foreach(name; __traits(derivedMembers, Iface)) 23404 mixin("__gshared typeof(&__traits(getMember, Iface, name)) " ~ name ~ ";"); 23405 23406 private __gshared void* libHandle; 23407 private __gshared bool attempted; 23408 23409 void loadDynamicLibrary() @nogc { 23410 (cast(void function() @nogc) &loadDynamicLibraryForReal)(); 23411 } 23412 23413 bool loadAttempted() { 23414 return attempted; 23415 } 23416 bool loadSuccessful() { 23417 return libHandle !is null; 23418 } 23419 23420 void loadDynamicLibraryForReal() { 23421 attempted = true; 23422 version(Posix) { 23423 import core.sys.posix.dlfcn; 23424 version(OSX) { 23425 version(X11) 23426 libHandle = dlopen("/usr/X11/lib/lib" ~ library ~ ".dylib", RTLD_NOW); 23427 else 23428 libHandle = dlopen(library ~ ".dylib", RTLD_NOW); 23429 } else { 23430 version(apitrace) { 23431 if(library == "GL" || library == "GLX") { 23432 libHandle = dlopen("glxtrace.so", RTLD_NOW); 23433 if(libHandle is null) { 23434 assert(false, "Failed to load `glxtrace.so`."); 23435 } 23436 } 23437 else { 23438 libHandle = dlopen("lib" ~ library ~ ".so", RTLD_NOW); 23439 } 23440 } 23441 else { 23442 libHandle = dlopen("lib" ~ library ~ ".so", RTLD_NOW); 23443 } 23444 if(libHandle is null) { 23445 libHandle = dlopen(("lib" ~ library ~ ".so." ~ toInternal!string(majorVersion) ~ "\0").ptr, RTLD_NOW); 23446 } 23447 } 23448 23449 static void* loadsym(void* l, const char* name) { 23450 import core.stdc.stdlib; 23451 if(l is null) 23452 return &abort; 23453 return dlsym(l, name); 23454 } 23455 } else version(Windows) { 23456 import core.sys.windows.winbase; 23457 libHandle = LoadLibrary(library ~ ".dll"); 23458 static void* loadsym(void* l, const char* name) { 23459 import core.stdc.stdlib; 23460 if(l is null) 23461 return &abort; 23462 return GetProcAddress(l, name); 23463 } 23464 } 23465 if(libHandle is null) { 23466 success = false; 23467 //throw new Exception("load failure of library " ~ library); 23468 } 23469 foreach(name; __traits(derivedMembers, Iface)) { 23470 mixin("alias tmp = " ~ name ~ ";"); 23471 tmp = cast(typeof(tmp)) loadsym(libHandle, name); 23472 if(tmp is null) throw new Exception("load failure of function " ~ name ~ " from " ~ library); 23473 } 23474 } 23475 23476 void unloadDynamicLibrary() { 23477 version(Posix) { 23478 import core.sys.posix.dlfcn; 23479 dlclose(libHandle); 23480 } else version(Windows) { 23481 import core.sys.windows.winbase; 23482 FreeLibrary(libHandle); 23483 } 23484 foreach(name; __traits(derivedMembers, Iface)) 23485 mixin(name ~ " = null;"); 23486 } 23487 } 23488 23489 // version(X11) 23490 /++ 23491 Returns the custom scaling factor read out of environment["ARSD_SCALING_FACTOR"]. 23492 23493 $(WARNING 23494 This function is exempted from stability guarantees. 23495 ) 23496 +/ 23497 float customScalingFactorForMonitor(int monitorNumber) @system { 23498 import core.stdc.stdlib; 23499 auto val = getenv("ARSD_SCALING_FACTOR"); 23500 23501 // FIXME: maybe we should assume a default nbased on the dpi thing if this isn't given 23502 if(val is null) 23503 return 1.0; 23504 23505 char[16] buffer = 0; 23506 int pos; 23507 23508 const(char)* at = val; 23509 23510 foreach(item; 0 .. monitorNumber + 1) { 23511 if(*at == 0) 23512 break; // reuse the last number when we at the end of the string 23513 pos = 0; 23514 while(pos + 1 < buffer.length && *at && *at != ';') { 23515 buffer[pos++] = *at; 23516 at++; 23517 } 23518 if(*at) 23519 at++; // skip the semicolon 23520 buffer[pos] = 0; 23521 } 23522 23523 //sdpyPrintDebugString(buffer[0 .. pos]); 23524 23525 import core.stdc.math; 23526 auto f = atof(buffer.ptr); 23527 23528 if(f <= 0.0 || isnan(f) || isinf(f)) 23529 return 1.0; 23530 23531 return f; 23532 } 23533 23534 void guiAbortProcess(string msg) { 23535 import core.stdc.stdlib; 23536 version(Windows) { 23537 WCharzBuffer t = WCharzBuffer(msg); 23538 MessageBoxW(null, t.ptr, "Program Termination"w.ptr, 0); 23539 } else { 23540 import core.stdc.stdio; 23541 fwrite(msg.ptr, 1, msg.length, stderr); 23542 msg = "\n"; 23543 fwrite(msg.ptr, 1, msg.length, stderr); 23544 fflush(stderr); 23545 } 23546 23547 abort(); 23548 } 23549 23550 private int minInternal(int a, int b) { 23551 return (a < b) ? a : b; 23552 } 23553 23554 private alias scriptable = arsd_jsvar_compatible;