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 XSetInputFocus(XDisplayConnection.get, setTo.impl.window, RevertToParent, CurrentTime); 2410 } 2411 } 2412 if(mouse) { 2413 if(auto res = XGrabPointer(XDisplayConnection.get, this.impl.window, false /* owner_events */, 2414 EventMask.PointerMotionMask // FIXME: not efficient 2415 | EventMask.ButtonPressMask 2416 | EventMask.ButtonReleaseMask 2417 /* event mask */, GrabMode.GrabModeAsync, GrabMode.GrabModeAsync, confine ? this.impl.window : None, None, CurrentTime) 2418 ) 2419 { 2420 XSync(XDisplayConnection.get, 0); 2421 import core.stdc.stdio; 2422 printf("Grab input failed %d\n", res); 2423 //throw new Exception("Grab input failed"); 2424 } else { 2425 // cool 2426 } 2427 } 2428 2429 } else version(Windows) { 2430 // FIXME: keyboard? 2431 SetCapture(impl.hwnd); 2432 if(confine) { 2433 RECT rcClip; 2434 //RECT rcOldClip; 2435 //GetClipCursor(&rcOldClip); 2436 GetWindowRect(hwnd, &rcClip); 2437 ClipCursor(&rcClip); 2438 } 2439 } else version(Emscripten) { 2440 // nothing necessary 2441 } else version(OSXCocoa) { 2442 // throw new NotYetImplementedException(); 2443 } else static assert(0); 2444 } 2445 2446 private Point imePopupLocation = Point(0, 0); 2447 2448 /++ 2449 Sets the location for the IME (input method editor) to pop up when the user activates it. 2450 2451 Bugs: 2452 Not implemented outside X11. 2453 +/ 2454 void setIMEPopupLocation(Point location) { 2455 static if(UsingSimpledisplayX11) { 2456 imePopupLocation = location; 2457 updateIMEPopupLocation(); 2458 } else { 2459 // this is non-fatal at this point... but still wanna find it when i search for NotYetImplementedException at least 2460 // throw new NotYetImplementedException(); 2461 } 2462 } 2463 2464 /// ditto 2465 void setIMEPopupLocation(int x, int y) { 2466 return setIMEPopupLocation(Point(x, y)); 2467 } 2468 2469 // we need to remind XIM of where we wanted to place the IME whenever the window moves 2470 // so this function gets called in setIMEPopupLocation as well as whenever the window 2471 // receives a ConfigureNotify event 2472 private void updateIMEPopupLocation() { 2473 static if(UsingSimpledisplayX11) { 2474 if (xic is null) { 2475 return; 2476 } 2477 2478 XPoint nspot; 2479 nspot.x = cast(short) imePopupLocation.x; 2480 nspot.y = cast(short) imePopupLocation.y; 2481 XVaNestedList preeditAttr = XVaCreateNestedList(0, /*XNSpotLocation*/"spotLocation".ptr, &nspot, null); 2482 XSetICValues(xic, /*XNPreeditAttributes*/"preeditAttributes".ptr, preeditAttr, null); 2483 XFree(preeditAttr); 2484 } 2485 } 2486 2487 private bool imeFocused = true; 2488 2489 /++ 2490 Tells the IME whether or not an input field is currently focused in the window. 2491 2492 Bugs: 2493 Not implemented outside X11. 2494 +/ 2495 void setIMEFocused(bool value) { 2496 imeFocused = value; 2497 updateIMEFocused(); 2498 } 2499 2500 // used to focus/unfocus the IC if necessary when the window gains/loses focus 2501 private void updateIMEFocused() { 2502 static if(UsingSimpledisplayX11) { 2503 if (xic is null) { 2504 return; 2505 } 2506 2507 if (focused && imeFocused) { 2508 XSetICFocus(xic); 2509 } else { 2510 XUnsetICFocus(xic); 2511 } 2512 } 2513 } 2514 2515 /++ 2516 Returns the native window. 2517 2518 History: 2519 Added November 5, 2021 (dub v10.4). Prior to that, you'd have 2520 to access it through the `impl` member (which is semi-supported 2521 but platform specific and here it is simple enough to offer an accessor). 2522 2523 Bugs: 2524 Not implemented outside Windows or X11. 2525 +/ 2526 NativeWindowHandle nativeWindowHandle() { 2527 version(X11) 2528 return impl.window; 2529 else version(Windows) 2530 return impl.hwnd; 2531 else 2532 throw new NotYetImplementedException(); 2533 } 2534 2535 private bool isTransient() { 2536 with(WindowTypes) 2537 final switch(windowType) { 2538 case normal, undecorated, eventOnly: 2539 case nestedChild, minimallyWrapped: 2540 return (customizationFlags & WindowFlags.transient) ? true : false; 2541 case dropdownMenu, popupMenu, notification, dialog: 2542 return true; 2543 } 2544 } 2545 2546 private SimpleWindow inputProxy; 2547 2548 /++ 2549 Releases the grab acquired by [grabInput]. 2550 +/ 2551 void releaseInputGrab() { 2552 static if(UsingSimpledisplayX11) { 2553 XUngrabPointer(XDisplayConnection.get, CurrentTime); 2554 if(_parent) 2555 _parent.inputProxy = null; 2556 } else version(Windows) { 2557 ReleaseCapture(); 2558 ClipCursor(null); 2559 } else version(OSXCocoa) { 2560 // throw new NotYetImplementedException(); 2561 } else version(Emscripten) { 2562 // nothing needed 2563 } else static assert(0); 2564 } 2565 2566 /++ 2567 Sets the input focus to this window. 2568 2569 You shouldn't call this very often - please let the user control the input focus. 2570 +/ 2571 void focus() { 2572 static if(UsingSimpledisplayX11) { 2573 SimpleWindow setTo; 2574 if(setRequestedInputFocus !is null) 2575 setTo = setRequestedInputFocus(); 2576 if(setTo is null) 2577 setTo = this; 2578 XSetInputFocus(XDisplayConnection.get, setTo.impl.window, RevertToParent, CurrentTime); 2579 } else version(Windows) { 2580 SetFocus(this.impl.hwnd); 2581 } else version(Emscripten) { 2582 throw new NotYetImplementedException(); 2583 } else version(OSXCocoa) { 2584 throw new NotYetImplementedException(); 2585 } else static assert(0); 2586 } 2587 2588 /++ 2589 Requests attention from the user for this window. 2590 2591 2592 The typical result of this function is to change the color 2593 of the taskbar icon, though it may be tweaked on specific 2594 platforms. 2595 2596 It is meant to unobtrusively tell the user that something 2597 relevant to them happened in the background and they should 2598 check the window when they get a chance. Upon receiving the 2599 keyboard focus, the window will automatically return to its 2600 natural state. 2601 2602 If the window already has the keyboard focus, this function 2603 may do nothing, because the user is presumed to already be 2604 giving the window attention. 2605 2606 Implementation_note: 2607 2608 `requestAttention` uses the _NET_WM_STATE_DEMANDS_ATTENTION 2609 atom on X11 and the FlashWindow function on Windows. 2610 +/ 2611 void requestAttention() { 2612 if(_focused) 2613 return; 2614 2615 version(Windows) { 2616 FLASHWINFO info; 2617 info.cbSize = info.sizeof; 2618 info.hwnd = impl.hwnd; 2619 info.dwFlags = FLASHW_TRAY; 2620 info.uCount = 1; 2621 2622 FlashWindowEx(&info); 2623 2624 } else version(X11) { 2625 demandingAttention = true; 2626 demandAttention(this, true); 2627 } else version(Emscripten) { 2628 throw new NotYetImplementedException(); 2629 } else version(OSXCocoa) { 2630 throw new NotYetImplementedException(); 2631 } else static assert(0); 2632 } 2633 2634 private bool _focused; 2635 2636 version(X11) private bool demandingAttention; 2637 2638 /// This will be called when WM wants to close your window (i.e. user clicked "close" icon, for example). 2639 /// You'll have to call `close()` manually if you set this delegate. 2640 void delegate () closeQuery; 2641 2642 /// This will be called when window visibility was changed. 2643 void delegate (bool becomesVisible) visibilityChanged; 2644 2645 /// This will be called when window becomes visible for the first time. 2646 /// You can do OpenGL initialization here. Note that in X11 you can't call 2647 /// [setAsCurrentOpenGlContext] right after window creation, or X11 may 2648 /// fail to send reparent and map events (hit that with proprietary NVidia drivers). 2649 /// So you need to wait until this is called and call setAsCurrentOpenGlContext in there, then do the OpenGL initialization. 2650 private bool _visibleForTheFirstTimeCalled; 2651 void delegate () visibleForTheFirstTime; 2652 2653 /// Returns true if the window has been closed. 2654 final @property bool closed() { return _closed; } 2655 2656 private final @property bool notClosed() { return !_closed; } 2657 2658 /// Returns true if the window is focused. 2659 final @property bool focused() { return _focused; } 2660 2661 private bool _visible; 2662 /// Returns true if the window is visible (mapped). 2663 final @property bool visible() { return _visible; } 2664 2665 /// Closes the window. If there are no more open windows, the event loop will terminate. 2666 void close() { 2667 if (!_closed) { 2668 runInGuiThread( { 2669 if(_closed) return; // another thread got to it first. this isn't a big deal, it just means our message was queued 2670 if (onClosing !is null) onClosing(); 2671 impl.closeWindow(); 2672 _closed = true; 2673 } ); 2674 } 2675 } 2676 2677 /++ 2678 `close` is one of the few methods that can be called from other threads. This `shared` overload reflects that. 2679 2680 History: 2681 Overload added on March 7, 2021. 2682 +/ 2683 void close() shared { 2684 (cast() this).close(); 2685 } 2686 2687 /++ 2688 2689 +/ 2690 void maximize() { 2691 version(Windows) 2692 ShowWindow(impl.hwnd, SW_MAXIMIZE); 2693 else version(X11) { 2694 setNetWmStateAtom(this.impl.window, GetAtom!("_NET_WM_STATE_MAXIMIZED_VERT", false)(XDisplayConnection.get), true, GetAtom!("_NET_WM_STATE_MAXIMIZED_HORZ", false)(XDisplayConnection.get)); 2695 2696 // also note _NET_WM_STATE_FULLSCREEN 2697 } 2698 2699 } 2700 2701 private bool _fullscreen; 2702 version(Windows) 2703 private WINDOWPLACEMENT g_wpPrev; 2704 2705 /// not fully implemented but planned for a future release 2706 void fullscreen(bool yes) { 2707 version(Windows) { 2708 g_wpPrev.length = WINDOWPLACEMENT.sizeof; 2709 DWORD dwStyle = GetWindowLong(hwnd, GWL_STYLE); 2710 if (dwStyle & WS_OVERLAPPEDWINDOW) { 2711 MONITORINFO mi; 2712 mi.cbSize = MONITORINFO.sizeof; 2713 if (GetWindowPlacement(hwnd, &g_wpPrev) && 2714 GetMonitorInfo(MonitorFromWindow(hwnd, 2715 MONITOR_DEFAULTTOPRIMARY), &mi)) { 2716 SetWindowLong(hwnd, GWL_STYLE, 2717 dwStyle & ~WS_OVERLAPPEDWINDOW); 2718 SetWindowPos(hwnd, HWND_TOP, 2719 mi.rcMonitor.left, mi.rcMonitor.top, 2720 mi.rcMonitor.right - mi.rcMonitor.left, 2721 mi.rcMonitor.bottom - mi.rcMonitor.top, 2722 SWP_NOOWNERZORDER | SWP_FRAMECHANGED); 2723 } 2724 } else { 2725 SetWindowLong(hwnd, GWL_STYLE, 2726 dwStyle | WS_OVERLAPPEDWINDOW); 2727 SetWindowPlacement(hwnd, &g_wpPrev); 2728 SetWindowPos(hwnd, null, 0, 0, 0, 0, 2729 SWP_NOMOVE | SWP_NOSIZE | SWP_NOZORDER | 2730 SWP_NOOWNERZORDER | SWP_FRAMECHANGED); 2731 } 2732 2733 } else version(X11) { 2734 setNetWmStateAtom(this.impl.window, GetAtom!("_NET_WM_STATE_FULLSCREEN", false)(XDisplayConnection.get), yes); 2735 } 2736 2737 _fullscreen = yes; 2738 2739 } 2740 2741 bool fullscreen() { 2742 return _fullscreen; 2743 } 2744 2745 /++ 2746 Note: only implemented on Windows. No-op on other platforms. You may want to use [hide] instead. 2747 2748 +/ 2749 void minimize() { 2750 version(Windows) 2751 ShowWindow(impl.hwnd, SW_MINIMIZE); 2752 //else version(X11) 2753 //setNetWmStateAtom(this, GetAtom!("_NET_WM_STATE_MINIMIZED", false)(XDisplayConnection.get), true); 2754 } 2755 2756 /// Alias for `hidden = false` 2757 void show() { 2758 hidden = false; 2759 } 2760 2761 /// Alias for `hidden = true` 2762 void hide() { 2763 hidden = true; 2764 } 2765 2766 /// Hide cursor when it enters the window. 2767 void hideCursor() { 2768 version(OSXCocoa) throw new NotYetImplementedException(); else 2769 if (!_closed) impl.hideCursor(); 2770 } 2771 2772 /// Don't hide cursor when it enters the window. 2773 void showCursor() { 2774 version(OSXCocoa) throw new NotYetImplementedException(); else 2775 if (!_closed) impl.showCursor(); 2776 } 2777 2778 /** "Warp" mouse pointer to coordinates relative to window top-left corner. Return "success" flag. 2779 * 2780 * Please remember that the cursor is a shared resource that should usually be left to the user's 2781 * control. Try to think for other approaches before using this function. 2782 * 2783 * Note: "warping" pointer will not send any synthesised mouse events, so you probably doesn't want 2784 * to use it to move mouse pointer to some active GUI area, for example, as your window won't 2785 * receive "mouse moved here" event. 2786 */ 2787 bool warpMouse (int x, int y) { 2788 version(X11) { 2789 if (!_closed) { impl.warpMouse(x, y); return true; } 2790 } else version(Windows) { 2791 if (!_closed) { 2792 POINT point; 2793 point.x = x; 2794 point.y = y; 2795 if(ClientToScreen(impl.hwnd, &point)) { 2796 SetCursorPos(point.x, point.y); 2797 return true; 2798 } 2799 } 2800 } 2801 return false; 2802 } 2803 2804 /// Send dummy window event to ping event loop. Required to process NotificationIcon on X11, for example. 2805 void sendDummyEvent () { 2806 version(X11) { 2807 if (!_closed) { impl.sendDummyEvent(); } 2808 } 2809 } 2810 2811 /// Set window minimal size. 2812 void setMinSize (int minwidth, int minheight) { 2813 version(OSXCocoa) throw new NotYetImplementedException(); else 2814 if (!_closed) impl.setMinSize(minwidth, minheight); 2815 } 2816 2817 /// Set window maximal size. 2818 void setMaxSize (int maxwidth, int maxheight) { 2819 version(OSXCocoa) throw new NotYetImplementedException(); else 2820 if (!_closed) impl.setMaxSize(maxwidth, maxheight); 2821 } 2822 2823 /// Set window resize step (window size will be changed with the given granularity on supported platforms). 2824 /// Currently only supported on X11. 2825 void setResizeGranularity (int granx, int grany) { 2826 version(OSXCocoa) throw new NotYetImplementedException(); else 2827 if (!_closed) impl.setResizeGranularity(granx, grany); 2828 } 2829 2830 /// Move window. 2831 void move(int x, int y) { 2832 version(OSXCocoa) throw new NotYetImplementedException(); else 2833 if (!_closed) impl.move(x, y); 2834 } 2835 2836 /// ditto 2837 void move(Point p) { 2838 version(OSXCocoa) throw new NotYetImplementedException(); else 2839 if (!_closed) impl.move(p.x, p.y); 2840 } 2841 2842 /++ 2843 Resize window. 2844 2845 Note that the width and height of the window are NOT instantly 2846 updated - it waits for the window manager to approve the resize 2847 request, which means you must return to the event loop before the 2848 width and height are actually changed. 2849 +/ 2850 void resize(int w, int h) { 2851 if(!_closed && _fullscreen) fullscreen = false; 2852 version(OSXCocoa) throw new NotYetImplementedException(); else 2853 if (!_closed) impl.resize(w, h); 2854 } 2855 2856 /// Move and resize window (this can be faster and more visually pleasant than doing it separately). 2857 void moveResize (int x, int y, int w, int h) { 2858 if(!_closed && _fullscreen) fullscreen = false; 2859 version(OSXCocoa) throw new NotYetImplementedException(); else 2860 if (!_closed) impl.moveResize(x, y, w, h); 2861 } 2862 2863 private bool _hidden; 2864 2865 /// Returns true if the window is hidden. 2866 final @property bool hidden() { 2867 return _hidden; 2868 } 2869 2870 /// Shows or hides the window based on the bool argument. 2871 final @property void hidden(bool b) { 2872 _hidden = b; 2873 version(Windows) { 2874 ShowWindow(impl.hwnd, b ? SW_HIDE : SW_SHOW); 2875 } else version(X11) { 2876 if(b) 2877 //XUnmapWindow(impl.display, impl.window); 2878 XWithdrawWindow(impl.display, impl.window, DefaultScreen(impl.display)); 2879 else 2880 XMapWindow(impl.display, impl.window); 2881 } else version(OSXCocoa) { 2882 // throw new NotYetImplementedException(); 2883 } else version(Emscripten) { 2884 } else static assert(0); 2885 } 2886 2887 /// Sets the window opacity. On X11 this requires a compositor to be running. On windows the WindowFlags.extraComposite must be set at window creation. 2888 void opacity(double opacity) @property 2889 in { 2890 assert(opacity >= 0 && opacity <= 1); 2891 } do { 2892 version (Windows) { 2893 impl.setOpacity(cast(ubyte)(255 * opacity)); 2894 } else version (X11) { 2895 impl.setOpacity(cast(uint)(uint.max * opacity)); 2896 } else throw new NotYetImplementedException(); 2897 } 2898 2899 /++ 2900 Sets your event handlers, without entering the event loop. Useful if you 2901 have multiple windows - set the handlers on each window, then only do 2902 [eventLoop] on your main window or call `EventLoop.get.run();`. 2903 2904 This assigns the given handlers to [handleKeyEvent], [handleCharEvent], 2905 [handlePulse], and [handleMouseEvent] automatically based on the provide 2906 delegate signatures. 2907 +/ 2908 void setEventHandlers(T...)(T eventHandlers) { 2909 // FIXME: add more events 2910 foreach(handler; eventHandlers) { 2911 static if(__traits(compiles, handleKeyEvent = handler)) { 2912 handleKeyEvent = handler; 2913 } else static if(__traits(compiles, handleCharEvent = handler)) { 2914 handleCharEvent = handler; 2915 } else static if(__traits(compiles, handlePulse = handler)) { 2916 handlePulse = handler; 2917 } else static if(__traits(compiles, handleMouseEvent = handler)) { 2918 handleMouseEvent = handler; 2919 } else static assert(0, "I can't use this event handler " ~ typeof(handler).stringof ~ "\nHave you tried using the delegate keyword?"); 2920 } 2921 } 2922 2923 /++ 2924 The event loop automatically returns when the window is closed 2925 pulseTimeout is given in milliseconds. If pulseTimeout == 0, no 2926 pulse timer is created. The event loop will block until an event 2927 arrives or the pulse timer goes off. 2928 2929 The given `eventHandlers` are passed to [setEventHandlers], which in turn 2930 assigns them to [handleKeyEvent], [handleCharEvent], [handlePulse], and 2931 [handleMouseEvent], based on the signature of delegates you provide. 2932 2933 Give one with no parameters to set a timer pulse handler. Give one that 2934 takes [KeyEvent] for a key handler, [MouseEvent], for a mouse handler, 2935 and one that takes `dchar` for a char event handler. You can use as many 2936 or as few handlers as you need for your application. 2937 2938 Bugs: 2939 2940 $(PITFALL 2941 You should always have one event loop live for your application. 2942 If you make two windows in sequence, the second call to eventLoop 2943 might fail: 2944 2945 --- 2946 // don't do this! 2947 auto window = new SimpleWindow(); 2948 window.eventLoop(0); 2949 2950 auto window2 = new SimpleWindow(); 2951 window2.eventLoop(0); // problematic! might crash 2952 --- 2953 2954 simpledisplay's current implementation assumes that final cleanup is 2955 done when the event loop refcount reaches zero. So after the first 2956 eventLoop returns, when there isn't already another one active, it assumes 2957 the program will exit soon and cleans up. 2958 2959 This is arguably a bug that it doesn't reinitialize, and I'll probably change 2960 it eventually, but in the mean time, there's an easy solution: 2961 2962 --- 2963 // do this 2964 EventLoop mainEventLoop = EventLoop.get; // just add this line 2965 2966 auto window = new SimpleWindow(); 2967 window.eventLoop(0); 2968 2969 auto window2 = new SimpleWindow(); 2970 window2.eventLoop(0); // perfectly fine since mainEventLoop still alive 2971 --- 2972 2973 By adding a top-level reference to the event loop, it ensures the final cleanup 2974 is not performed until it goes out of scope too, letting the individual window loops 2975 work without trouble despite the bug. 2976 ) 2977 2978 History: 2979 The overload without `pulseTimeout` was added on December 8, 2021. 2980 2981 On December 9, 2021, the default blocking mode (which is now configurable 2982 because [eventLoopWithBlockingMode] was added) switched from 2983 [BlockingMode.untilApplicationQuits] over to [BlockingMode.automatic]. This 2984 should almost never be noticeable to you since the typical simpledisplay 2985 paradigm has been (and I still recommend) to have one `eventLoop` call. 2986 2987 See_Also: 2988 [eventLoopWithBlockingMode] 2989 +/ 2990 final int eventLoop(T...)( 2991 long pulseTimeout, /// set to zero if you don't want a pulse. 2992 T eventHandlers) /// delegate list like std.concurrency.receive 2993 { 2994 return eventLoopWithBlockingMode(BlockingMode.automatic, pulseTimeout, eventHandlers); 2995 } 2996 2997 /// ditto 2998 final int eventLoop(T...)(T eventHandlers) if(T.length == 0 || is(T[0] == delegate)) 2999 { 3000 return eventLoopWithBlockingMode(BlockingMode.automatic, 0, eventHandlers); 3001 } 3002 3003 /++ 3004 This is the function [eventLoop] forwards to. It, in turn, forwards to `EventLoop.get.run`. 3005 3006 History: 3007 Added December 8, 2021 (dub v10.5) 3008 3009 Previously, this implementation was right inside [eventLoop], but when I wanted 3010 to add the new [BlockingMode] parameter, the compiler got in a trouble loop so I 3011 just renamed it instead of adding as an overload. Besides, the new name makes it 3012 easier to remember the order and avoids ambiguity between two int-like params anyway. 3013 3014 See_Also: 3015 [SimpleWindow.eventLoop], [EventLoop] 3016 3017 Bugs: 3018 The blocking mode is not implemented on OSX Cocoa nor on the (deprecated) arsd.eventloop. 3019 +/ 3020 final int eventLoopWithBlockingMode(T...)( 3021 BlockingMode blockingMode, /// when you want this function to block until 3022 long pulseTimeout, /// set to zero if you don't want a pulse. 3023 T eventHandlers) /// delegate list like std.concurrency.receive 3024 { 3025 setEventHandlers(eventHandlers); 3026 3027 version(with_eventloop) { 3028 // delegates event loop to my other module 3029 version(X11) 3030 XFlush(display); 3031 3032 import arsd.eventloop; 3033 auto handle = setInterval(handlePulse, cast(int) pulseTimeout); 3034 scope(exit) clearInterval(handle); 3035 3036 loop(); 3037 return 0; 3038 } else version(OSXCocoa) { 3039 // FIXME 3040 if (handlePulse !is null && pulseTimeout != 0) { 3041 timer = NSTimer.schedule(pulseTimeout*1e-3, 3042 cast(NSid) view, sel_registerName("simpledisplay_pulse:"), 3043 null, true); 3044 } 3045 3046 view.setNeedsDisplay(true); 3047 3048 NSApp.run(); 3049 return 0; 3050 } else { 3051 EventLoop el = EventLoop(pulseTimeout, handlePulse); 3052 3053 if((blockingMode & BlockingMode.onlyIfNotNested) && el.impl.refcount > 1) 3054 return 0; 3055 3056 return el.run( 3057 ((blockingMode & 0x0f) == BlockingMode.untilApplicationQuits) ? 3058 null : 3059 &this.notClosed 3060 ); 3061 } 3062 } 3063 3064 /++ 3065 This lets you draw on the window (or its backing buffer) using basic 3066 2D primitives. 3067 3068 Be sure to call this in a limited scope because your changes will not 3069 actually appear on the window until ScreenPainter's destructor runs. 3070 3071 Returns: an instance of [ScreenPainter], which has the drawing methods 3072 on it to draw on this window. 3073 3074 Params: 3075 manualInvalidations = if you set this to true, you will need to 3076 set the invalid rectangle on the painter yourself. If false, it 3077 assumes the whole window has been redrawn each time you draw. 3078 3079 Only invalidated rectangles are blitted back to the window when 3080 the destructor runs. Doing this yourself can reduce flickering 3081 of child windows. 3082 3083 History: 3084 The `manualInvalidations` parameter overload was added on 3085 December 30, 2021 (dub v10.5) 3086 +/ 3087 ScreenPainter draw() { 3088 return draw(false); 3089 } 3090 /// ditto 3091 ScreenPainter draw(bool manualInvalidations) { 3092 return impl.getPainter(manualInvalidations); 3093 } 3094 3095 // This is here to implement the interface we use for various native handlers. 3096 NativeEventHandler getNativeEventHandler() { return handleNativeEvent; } 3097 3098 // maps native window handles to SimpleWindow instances, if there are any 3099 // you shouldn't need this, but it is public in case you do in a native event handler or something 3100 // mac uses void* cuz NSObject opHash won't pick up in typeinfo 3101 version(OSXCocoa) 3102 public __gshared SimpleWindow[void*] nativeMapping; 3103 else 3104 public __gshared SimpleWindow[NativeWindowHandle] nativeMapping; 3105 3106 // the size the user requested in the constructor, in automatic scale modes it always pretends to be this size 3107 private int _virtualWidth; 3108 private int _virtualHeight; 3109 3110 /// Width of the window's drawable client area, in pixels. 3111 @scriptable 3112 final @property int width() const pure nothrow @safe @nogc { 3113 if(resizability == Resizability.automaticallyScaleIfPossible) 3114 return _virtualWidth; 3115 else 3116 return _width; 3117 } 3118 3119 /// Height of the window's drawable client area, in pixels. 3120 @scriptable 3121 final @property int height() const pure nothrow @safe @nogc { 3122 if(resizability == Resizability.automaticallyScaleIfPossible) 3123 return _virtualHeight; 3124 else 3125 return _height; 3126 } 3127 3128 /++ 3129 Returns the actual size of the window, bypassing the logical 3130 illusions of [Resizability.automaticallyScaleIfPossible]. 3131 3132 History: 3133 Added November 11, 2022 (dub v10.10) 3134 +/ 3135 final @property Size actualWindowSize() const pure nothrow @safe @nogc { 3136 return Size(_width, _height); 3137 } 3138 3139 3140 private int _width; 3141 private int _height; 3142 3143 // HACK: making the best of some copy constructor woes with refcounting 3144 private ScreenPainterImplementation* activeScreenPainter_; 3145 3146 protected ScreenPainterImplementation* activeScreenPainter() { return activeScreenPainter_; } 3147 protected void activeScreenPainter(ScreenPainterImplementation* i) { activeScreenPainter_ = i; } 3148 3149 private OpenGlOptions openglMode; 3150 private Resizability resizability; 3151 private WindowTypes windowType; 3152 private int customizationFlags; 3153 3154 /// `true` if OpenGL was initialized for this window. 3155 @property bool isOpenGL () const pure nothrow @safe @nogc { 3156 version(without_opengl) 3157 return false; 3158 else 3159 return (openglMode == OpenGlOptions.yes); 3160 } 3161 @property Resizability resizingMode () const pure nothrow @safe @nogc { return resizability; } /// Original resizability. 3162 @property WindowTypes type () const pure nothrow @safe @nogc { return windowType; } /// Original window type. 3163 @property int customFlags () const pure nothrow @safe @nogc { return customizationFlags; } /// Original customization flags. 3164 3165 /// "Lock" this window handle, to do multithreaded synchronization. You probably won't need 3166 /// to call this, as it's not recommended to share window between threads. 3167 void mtLock () { 3168 version(X11) { 3169 XLockDisplay(this.display); 3170 } 3171 } 3172 3173 /// "Unlock" this window handle, to do multithreaded synchronization. You probably won't need 3174 /// to call this, as it's not recommended to share window between threads. 3175 void mtUnlock () { 3176 version(X11) { 3177 XUnlockDisplay(this.display); 3178 } 3179 } 3180 3181 /// Emit a beep to get user's attention. 3182 void beep () { 3183 version(X11) { 3184 XBell(this.display, 100); 3185 } else version(Windows) { 3186 MessageBeep(0xFFFFFFFF); 3187 } 3188 } 3189 3190 3191 3192 version(without_opengl) {} else { 3193 3194 /// 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`. 3195 void delegate() redrawOpenGlScene; 3196 3197 /// This will allow you to change OpenGL vsync state. 3198 final @property void vsync (bool wait) { 3199 if (this._closed) return; // window may be closed, but timer is still firing; avoid GLXBadDrawable error 3200 version(X11) { 3201 setAsCurrentOpenGlContext(); 3202 glxSetVSync(display, impl.window, wait); 3203 } else version(Windows) { 3204 setAsCurrentOpenGlContext(); 3205 wglSetVSync(wait); 3206 } 3207 } 3208 3209 /// Set this to `false` if you don't need to do `glFinish()` after `swapOpenGlBuffers()`. 3210 /// Note that at least NVidia proprietary driver may segfault if you will modify texture fast 3211 /// enough without waiting 'em to finish their frame business. 3212 bool useGLFinish = true; 3213 3214 // FIXME: it should schedule it for the end of the current iteration of the event loop... 3215 /// 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. 3216 void redrawOpenGlSceneNow() { 3217 version(X11) if (!this._visible) return; // no need to do this if window is invisible 3218 if (this._closed) return; // window may be closed, but timer is still firing; avoid GLXBadDrawable error 3219 if(redrawOpenGlScene is null) 3220 return; 3221 3222 this.mtLock(); 3223 scope(exit) this.mtUnlock(); 3224 3225 this.setAsCurrentOpenGlContext(); 3226 3227 redrawOpenGlScene(); 3228 3229 this.swapOpenGlBuffers(); 3230 // 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. 3231 if (useGLFinish) glFinish(); 3232 } 3233 3234 private bool redrawOpenGlSceneSoonSet = false; 3235 private static class RedrawOpenGlSceneEvent { 3236 SimpleWindow w; 3237 this(SimpleWindow w) { this.w = w; } 3238 } 3239 private RedrawOpenGlSceneEvent redrawOpenGlSceneEvent; 3240 /++ 3241 Queues an opengl redraw as soon as the other pending events are cleared. 3242 +/ 3243 void redrawOpenGlSceneSoon() { 3244 if(redrawOpenGlScene is null) 3245 return; 3246 3247 if(!redrawOpenGlSceneSoonSet) { 3248 redrawOpenGlSceneEvent = new RedrawOpenGlSceneEvent(this); 3249 this.addEventListener((RedrawOpenGlSceneEvent e) { e.w.redrawOpenGlSceneNow(); }); 3250 redrawOpenGlSceneSoonSet = true; 3251 } 3252 this.postEvent(redrawOpenGlSceneEvent, true); 3253 } 3254 3255 3256 /// Makes all gl* functions target this window until changed. This is only valid if you passed `OpenGlOptions.yes` to the constructor. 3257 void setAsCurrentOpenGlContext() { 3258 assert(openglMode == OpenGlOptions.yes); 3259 version(X11) { 3260 if(glXMakeCurrent(display, impl.window, impl.glc) == 0) 3261 throw new Exception("glXMakeCurrent"); 3262 } else version(Windows) { 3263 static if (SdpyIsUsingIVGLBinds) import iv.glbinds; // override druntime windows imports 3264 if (!wglMakeCurrent(ghDC, ghRC)) 3265 throw new Exception("wglMakeCurrent " ~ toInternal!int(GetLastError())); // let windows users suffer too 3266 } 3267 } 3268 3269 /// Makes all gl* functions target this window until changed. This is only valid if you passed `OpenGlOptions.yes` to the constructor. 3270 /// This doesn't throw, returning success flag instead. 3271 bool setAsCurrentOpenGlContextNT() nothrow { 3272 assert(openglMode == OpenGlOptions.yes); 3273 version(X11) { 3274 return (glXMakeCurrent(display, impl.window, impl.glc) != 0); 3275 } else version(Windows) { 3276 static if (SdpyIsUsingIVGLBinds) import iv.glbinds; // override druntime windows imports 3277 return wglMakeCurrent(ghDC, ghRC) ? true : false; 3278 } 3279 } 3280 3281 /// 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. 3282 /// This doesn't throw, returning success flag instead. 3283 bool releaseCurrentOpenGlContext() nothrow { 3284 assert(openglMode == OpenGlOptions.yes); 3285 version(X11) { 3286 return (glXMakeCurrent(display, 0, null) != 0); 3287 } else version(Windows) { 3288 static if (SdpyIsUsingIVGLBinds) import iv.glbinds; // override druntime windows imports 3289 return wglMakeCurrent(ghDC, null) ? true : false; 3290 } 3291 } 3292 3293 /++ 3294 simpledisplay always uses double buffering, usually automatically. This 3295 manually swaps the OpenGL buffers. You should only use this if you are NOT 3296 using the [redrawOpenGlScene] delegate. 3297 3298 3299 You must not this yourself if you use [redrawOpenGlScene] because simpledisplay will do it 3300 for you after calling your `redrawOpenGlScene`. Please note that once you swap 3301 buffers, the contents become undefined - the implementation, in the OpenGL driver 3302 or the desktop compositor, may not actually just swap two buffers. The back buffer's 3303 contents are $(B undefined) after calling this function. 3304 3305 See: https://learn.microsoft.com/en-us/windows/win32/api/wingdi/nf-wingdi-swapbuffers 3306 and https://linux.die.net/man/3/glxswapbuffers 3307 3308 Remember that this may throw an exception, which you can catch in a multithreaded 3309 application to keep your thread from dying from an unhandled exception. 3310 +/ 3311 void swapOpenGlBuffers() { 3312 assert(openglMode == OpenGlOptions.yes); 3313 version(X11) { 3314 if (!this._visible) return; // no need to do this if window is invisible 3315 if (this._closed) return; // window may be closed, but timer is still firing; avoid GLXBadDrawable error 3316 glXSwapBuffers(display, impl.window); 3317 } else version(Windows) { 3318 SwapBuffers(ghDC); 3319 } 3320 } 3321 } 3322 3323 /++ 3324 Set the window title, which is visible on the window manager title bar, operating system taskbar, etc. 3325 3326 3327 --- 3328 auto window = new SimpleWindow(100, 100, "First title"); 3329 window.title = "A new title"; 3330 --- 3331 3332 You may call this function at any time. 3333 +/ 3334 @property void title(string title) { 3335 _title = title; 3336 version(OSXCocoa) throw new NotYetImplementedException(); else 3337 impl.setTitle(title); 3338 } 3339 3340 private string _title; 3341 3342 /// Gets the title 3343 @property string title() { 3344 if(_title is null) 3345 _title = getRealTitle(); 3346 return _title; 3347 } 3348 3349 /++ 3350 Get the title as set by the window manager. 3351 May not match what you attempted to set. 3352 +/ 3353 string getRealTitle() { 3354 static if(is(typeof(impl.getTitle()))) 3355 return impl.getTitle(); 3356 else 3357 return null; 3358 } 3359 3360 // don't use this generally it is not yet really released 3361 version(X11) 3362 @property Image secret_icon() { 3363 return secret_icon_inner; 3364 } 3365 private Image secret_icon_inner; 3366 3367 3368 /// Set the icon that is seen in the title bar or taskbar, etc., for the user. If passed `null`, does nothing. 3369 @property void icon(MemoryImage icon) { 3370 if(icon is null) 3371 return; 3372 auto tci = icon.getAsTrueColorImage(); 3373 version(Windows) { 3374 winIcon = new WindowsIcon(icon); 3375 SendMessageA(impl.hwnd, 0x0080 /*WM_SETICON*/, 0 /*ICON_SMALL*/, cast(LPARAM) winIcon.hIcon); // there is also 1 == ICON_BIG 3376 } else version(X11) { 3377 secret_icon_inner = Image.fromMemoryImage(icon); 3378 // FIXME: ensure this is correct 3379 auto display = XDisplayConnection.get; 3380 arch_ulong[] buffer; 3381 buffer ~= icon.width; 3382 buffer ~= icon.height; 3383 foreach(c; tci.imageData.colors) { 3384 arch_ulong b; 3385 b |= c.a << 24; 3386 b |= c.r << 16; 3387 b |= c.g << 8; 3388 b |= c.b; 3389 buffer ~= b; 3390 } 3391 3392 XChangeProperty( 3393 display, 3394 impl.window, 3395 GetAtom!("_NET_WM_ICON", true)(display), 3396 GetAtom!"CARDINAL"(display), 3397 32 /* bits */, 3398 0 /*PropModeReplace*/, 3399 buffer.ptr, 3400 cast(int) buffer.length); 3401 } else version(OSXCocoa) { 3402 throw new NotYetImplementedException(); 3403 } else version(Emscripten) { 3404 throw new NotYetImplementedException(); 3405 } else static assert(0); 3406 } 3407 3408 version(Windows) 3409 private WindowsIcon winIcon; 3410 3411 bool _suppressDestruction; 3412 3413 ~this() { 3414 if(!thisIsGuiThread) return; // FIXME: leaks if multithreaded gc 3415 if(_suppressDestruction) 3416 return; 3417 impl.dispose(); 3418 } 3419 3420 private bool _closed; 3421 3422 // the idea here is to draw something temporary on top of the main picture e.g. a blinking cursor 3423 /* 3424 ScreenPainter drawTransiently() { 3425 return impl.getPainter(); 3426 } 3427 */ 3428 3429 /// Draws an image on the window. This is meant to provide quick look 3430 /// of a static image generated elsewhere. 3431 @property void image(Image i) { 3432 /+ 3433 version(Windows) { 3434 BITMAP bm; 3435 HDC hdc = GetDC(hwnd); 3436 HDC hdcMem = CreateCompatibleDC(hdc); 3437 HBITMAP hbmOld = SelectObject(hdcMem, i.handle); 3438 3439 GetObject(i.handle, bm.sizeof, &bm); 3440 3441 BitBlt(hdc, 0, 0, bm.bmWidth, bm.bmHeight, hdcMem, 0, 0, SRCCOPY); 3442 3443 SelectObject(hdcMem, hbmOld); 3444 DeleteDC(hdcMem); 3445 ReleaseDC(hwnd, hdc); 3446 3447 /* 3448 RECT r; 3449 r.right = i.width; 3450 r.bottom = i.height; 3451 InvalidateRect(hwnd, &r, false); 3452 */ 3453 } else 3454 version(X11) { 3455 if(!destroyed) { 3456 if(i.usingXshm) 3457 XShmPutImage(display, cast(Drawable) window, gc, i.handle, 0, 0, 0, 0, i.width, i.height, false); 3458 else 3459 XPutImage(display, cast(Drawable) window, gc, i.handle, 0, 0, 0, 0, i.width, i.height); 3460 } 3461 } else 3462 version(OSXCocoa) { 3463 draw().drawImage(Point(0, 0), i); 3464 setNeedsDisplay(view, true); 3465 } else static assert(0); 3466 +/ 3467 auto painter = this.draw; 3468 painter.drawImage(Point(0, 0), i); 3469 } 3470 3471 /++ 3472 Changes the cursor for the window. If the cursor is hidden via [hideCursor], this has no effect. 3473 3474 --- 3475 window.cursor = GenericCursor.Help; 3476 // now the window mouse cursor is set to a generic help 3477 --- 3478 3479 +/ 3480 @property void cursor(MouseCursor cursor) { 3481 version(OSXCocoa) 3482 {} // featureNotImplemented(); 3483 else 3484 if(this.impl.curHidden <= 0) { 3485 static if(UsingSimpledisplayX11) { 3486 auto ch = cursor.cursorHandle; 3487 XDefineCursor(XDisplayConnection.get(), this.impl.window, ch); 3488 } else version(Windows) { 3489 auto ch = cursor.cursorHandle; 3490 impl.currentCursor = ch; 3491 SetCursor(ch); // redraw without waiting for mouse movement to update 3492 } else featureNotImplemented(); 3493 } 3494 3495 } 3496 3497 /// What follows are the event handlers. These are set automatically 3498 /// by the eventLoop function, but are still public so you can change 3499 /// them later. wasPressed == true means key down. false == key up. 3500 3501 /// Handles a low-level keyboard event. Settable through setEventHandlers. 3502 void delegate(KeyEvent ke) handleKeyEvent; 3503 3504 /// Handles a higher level keyboard event - c is the character just pressed. Settable through setEventHandlers. 3505 void delegate(dchar c) handleCharEvent; 3506 3507 /// Handles a timer pulse. Settable through setEventHandlers. 3508 void delegate() handlePulse; 3509 3510 /// Called when the focus changes, param is if we have it (true) or are losing it (false). 3511 void delegate(bool) onFocusChange; 3512 3513 /** Called inside `close()` method. Our window is still alive, and we can free various resources. 3514 * Sometimes it is easier to setup the delegate instead of subclassing. */ 3515 void delegate() onClosing; 3516 3517 /** Called when we received destroy notification. At this stage we cannot do much with our window 3518 * (as it is already dead, and it's native handle cannot be used), but we still can do some 3519 * last minute cleanup. */ 3520 void delegate() onDestroyed; 3521 3522 static if (UsingSimpledisplayX11) 3523 /** Called when Expose event comes. See Xlib manual to understand the arguments. 3524 * Return `false` if you want Simpledisplay to copy backbuffer, or `true` if you did it yourself. 3525 * You will probably never need to setup this handler, it is for very low-level stuff. 3526 * 3527 * WARNING! Xlib is multithread-locked when this handles is called! */ 3528 bool delegate(int x, int y, int width, int height, int eventsLeft) handleExpose; 3529 3530 //version(Windows) 3531 //bool delegate(WPARAM wParam, LPARAM lParam) handleWM_PAINT; 3532 3533 private { 3534 int lastMouseX = int.min; 3535 int lastMouseY = int.min; 3536 void mdx(ref MouseEvent ev) { 3537 if(lastMouseX == int.min || lastMouseY == int.min) { 3538 ev.dx = 0; 3539 ev.dy = 0; 3540 } else { 3541 ev.dx = ev.x - lastMouseX; 3542 ev.dy = ev.y - lastMouseY; 3543 } 3544 3545 lastMouseX = ev.x; 3546 lastMouseY = ev.y; 3547 } 3548 } 3549 3550 /// Mouse event handler. Settable through setEventHandlers. 3551 void delegate(MouseEvent) handleMouseEvent; 3552 3553 /// use to redraw child widgets if you use system apis to add stuff 3554 void delegate() paintingFinished; 3555 3556 void delegate() paintingFinishedDg() { 3557 return paintingFinished; 3558 } 3559 3560 /// handle a resize, after it happens. You must construct the window with Resizability.allowResizing 3561 /// for this to ever happen. 3562 void delegate(int width, int height) windowResized; 3563 3564 /++ 3565 Platform specific - handle any native message this window gets. 3566 3567 Note: this is called *in addition to* other event handlers, unless you either: 3568 3569 1) On X11, return 0 indicating that you handled it. Any other return value is simply discarded. 3570 3571 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. 3572 3573 On Windows, your delegate takes the form of `int delegate(HWND hwnd, UINT message, WPARAM wParam, LPARAM lParam, out int mustReturn)`. 3574 3575 On X, it takes the form of `int delegate(XEvent)`. 3576 3577 History: 3578 In ancient versions, this was `static`. If you want a global hook, use [handleNativeGlobalEvent] instead. 3579 3580 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. 3581 +/ 3582 NativeEventHandler handleNativeEvent_; 3583 3584 @property NativeEventHandler handleNativeEvent() nothrow pure @nogc const @safe { 3585 return handleNativeEvent_; 3586 } 3587 @property void handleNativeEvent(NativeEventHandler neh) nothrow pure @nogc @safe { 3588 handleNativeEvent_ = neh; 3589 } 3590 3591 version(Windows) 3592 // compatibility shim with the old deprecated way 3593 // in this one, if you return 0, it means you must return. otherwise the ret value is ignored. 3594 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) { 3595 handleNativeEvent_ = delegate int(HWND h, UINT m, WPARAM w, LPARAM l, out int r) { 3596 auto ret = dg(h, m, w, l); 3597 if(ret == 0) 3598 r = 1; 3599 return ret; 3600 }; 3601 } 3602 3603 /// This is the same as handleNativeEvent, but static so it can hook ALL events in the loop. 3604 /// If you used to use handleNativeEvent depending on it being static, just change it to use 3605 /// this instead and it will work the same way. 3606 __gshared NativeEventHandler handleNativeGlobalEvent; 3607 3608 // private: 3609 /// The native implementation is available, but you shouldn't use it unless you are 3610 /// familiar with the underlying operating system, don't mind depending on it, and 3611 /// know simpledisplay.d's internals too. It is virtually private; you can hopefully 3612 /// do what you need to do with handleNativeEvent instead. 3613 /// 3614 /// This is likely to eventually change to be just a struct holding platform-specific 3615 /// handles instead of a template mixin at some point because I'm not happy with the 3616 /// code duplication here (ironically). 3617 mixin NativeSimpleWindowImplementation!() impl; 3618 3619 /** 3620 This is in-process one-way (from anything to window) event sending mechanics. 3621 It is thread-safe, so it can be used in multi-threaded applications to send, 3622 for example, "wake up and repaint" events when thread completed some operation. 3623 This will allow to avoid using timer pulse to check events with synchronization, 3624 'cause event handler will be called in UI thread. You can stop guessing which 3625 pulse frequency will be enough for your app. 3626 Note that events handlers may be called in arbitrary order, i.e. last registered 3627 handler can be called first, and vice versa. 3628 */ 3629 public: 3630 /** Is our custom event queue empty? Can be used in simple cases to prevent 3631 * "spamming" window with events it can't cope with. 3632 * It is safe to call this from non-UI threads. 3633 */ 3634 @property bool eventQueueEmpty() () { 3635 synchronized(this) { 3636 foreach (const ref o; eventQueue[0..eventQueueUsed]) if (!o.doProcess) return false; 3637 } 3638 return true; 3639 } 3640 3641 /** Does our custom event queue contains at least one with the given type? 3642 * Can be used in simple cases to prevent "spamming" window with events 3643 * it can't cope with. 3644 * It is safe to call this from non-UI threads. 3645 */ 3646 @property bool eventQueued(ET:Object) () { 3647 synchronized(this) { 3648 foreach (const ref o; eventQueue[0..eventQueueUsed]) { 3649 if (!o.doProcess) { 3650 if (cast(ET)(o.evt)) return true; 3651 } 3652 } 3653 } 3654 return false; 3655 } 3656 3657 /++ 3658 Event listeners added with [addEventListener] have their exceptions swallowed by the event loop. This delegate can handle them again before it proceeds. 3659 3660 History: 3661 Added May 12, 2021 3662 +/ 3663 void delegate(Exception e) nothrow eventUncaughtException; 3664 3665 /** Add listener for custom event. Can be used like this: 3666 * 3667 * --------------------- 3668 * auto eid = win.addEventListener((MyStruct evt) { ... }); 3669 * ... 3670 * win.removeEventListener(eid); 3671 * --------------------- 3672 * 3673 * Returns: 0 on failure (should never happen, so ignore it) 3674 * 3675 * $(WARNING Don't use this method in object destructors!) 3676 * 3677 * $(WARNING It is better to register all event handlers and don't remove 'em, 3678 * 'cause if event handler id counter will overflow, you won't be able 3679 * to register any more events.) 3680 */ 3681 uint addEventListener(ET:Object) (void delegate (ET) dg) { 3682 if (dg is null) return 0; // ignore empty handlers 3683 synchronized(this) { 3684 //FIXME: abort on overflow? 3685 if (++lastUsedHandlerId == 0) { --lastUsedHandlerId; return 0; } // alas, can't register more events. at all. 3686 EventHandlerEntry e; 3687 e.dg = delegate (Object o) { 3688 if (auto co = cast(ET)o) { 3689 try { 3690 dg(co); 3691 } catch (Exception e) { 3692 // sorry! 3693 if(eventUncaughtException) 3694 eventUncaughtException(e); 3695 } 3696 return true; 3697 } 3698 return false; 3699 }; 3700 e.id = lastUsedHandlerId; 3701 auto optr = eventHandlers.ptr; 3702 eventHandlers ~= e; 3703 if (eventHandlers.ptr !is optr) { 3704 import core.memory : GC; 3705 if (eventHandlers.ptr is GC.addrOf(eventHandlers.ptr)) GC.setAttr(eventHandlers.ptr, GC.BlkAttr.NO_INTERIOR); 3706 } 3707 return lastUsedHandlerId; 3708 } 3709 } 3710 3711 /// Remove event listener. It is safe to pass invalid event id here. 3712 /// $(WARNING Don't use this method in object destructors!) 3713 void removeEventListener() (uint id) { 3714 if (id == 0 || id > lastUsedHandlerId) return; 3715 synchronized(this) { 3716 foreach (immutable idx; 0..eventHandlers.length) { 3717 if (eventHandlers[idx].id == id) { 3718 foreach (immutable c; idx+1..eventHandlers.length) eventHandlers[c-1] = eventHandlers[c]; 3719 eventHandlers[$-1].dg = null; 3720 eventHandlers.length -= 1; 3721 eventHandlers.assumeSafeAppend; 3722 return; 3723 } 3724 } 3725 } 3726 } 3727 3728 /// Post event to queue. It is safe to call this from non-UI threads. 3729 /// If `timeoutmsecs` is greater than zero, the event will be delayed for at least `timeoutmsecs` milliseconds. 3730 /// if `replace` is `true`, replace all existing events typed `ET` with the new one (if `evt` is empty, remove 'em all) 3731 /// Returns `true` if event was queued. Always returns `false` if `evt` is null. 3732 bool postTimeout(ET:Object) (ET evt, uint timeoutmsecs, bool replace=false) { 3733 if (this.closed) return false; // closed windows can't handle events 3734 3735 // remove all events of type `ET` 3736 void removeAllET () { 3737 uint eidx = 0, ec = eventQueueUsed; 3738 auto eptr = eventQueue.ptr; 3739 while (eidx < ec) { 3740 if (eptr.doProcess) { ++eidx; ++eptr; continue; } 3741 if (cast(ET)eptr.evt !is null) { 3742 // i found her! 3743 if (inCustomEventProcessor) { 3744 // if we're in custom event processing loop, processor will clear it for us 3745 eptr.evt = null; 3746 ++eidx; 3747 ++eptr; 3748 } else { 3749 foreach (immutable c; eidx+1..ec) eventQueue.ptr[c-1] = eventQueue.ptr[c]; 3750 ec = --eventQueueUsed; 3751 // clear last event (it is already copied) 3752 eventQueue.ptr[ec].evt = null; 3753 } 3754 } else { 3755 ++eidx; 3756 ++eptr; 3757 } 3758 } 3759 } 3760 3761 if (evt is null) { 3762 if (replace) { synchronized(this) removeAllET(); } 3763 // ignore empty events, they can't be handled anyway 3764 return false; 3765 } 3766 3767 // add events even if no event FD/event object created yet 3768 synchronized(this) { 3769 if (replace) removeAllET(); 3770 if (eventQueueUsed == uint.max) return false; // just in case 3771 if (eventQueueUsed < eventQueue.length) { 3772 eventQueue[eventQueueUsed++] = QueuedEvent(evt, timeoutmsecs); 3773 } else { 3774 if (eventQueue.capacity == eventQueue.length) { 3775 // need to reallocate; do a trick to ensure that old array is cleared 3776 auto oarr = eventQueue; 3777 eventQueue ~= QueuedEvent(evt, timeoutmsecs); 3778 // just in case, do yet another check 3779 if (oarr.length != 0 && oarr.ptr !is eventQueue.ptr) foreach (ref e; oarr[0..eventQueueUsed]) e.evt = null; 3780 import core.memory : GC; 3781 if (eventQueue.ptr is GC.addrOf(eventQueue.ptr)) GC.setAttr(eventQueue.ptr, GC.BlkAttr.NO_INTERIOR); 3782 } else { 3783 auto optr = eventQueue.ptr; 3784 eventQueue ~= QueuedEvent(evt, timeoutmsecs); 3785 assert(eventQueue.ptr is optr); 3786 } 3787 ++eventQueueUsed; 3788 assert(eventQueueUsed == eventQueue.length); 3789 } 3790 if (!eventWakeUp()) { 3791 // can't wake up event processor, so there is no reason to keep the event 3792 assert(eventQueueUsed > 0); 3793 eventQueue[--eventQueueUsed].evt = null; 3794 return false; 3795 } 3796 return true; 3797 } 3798 } 3799 3800 /// Post event to queue. It is safe to call this from non-UI threads. 3801 /// if `replace` is `true`, replace all existing events typed `ET` with the new one (if `evt` is empty, remove 'em all) 3802 /// Returns `true` if event was queued. Always returns `false` if `evt` is null. 3803 bool postEvent(ET:Object) (ET evt, bool replace=false) { 3804 return postTimeout!ET(evt, 0, replace); 3805 } 3806 3807 private: 3808 private import core.time : MonoTime; 3809 3810 version(Posix) { 3811 __gshared int customEventFDRead = -1; 3812 __gshared int customEventFDWrite = -1; 3813 __gshared int customSignalFD = -1; 3814 } else version(Windows) { 3815 __gshared HANDLE customEventH = null; 3816 } 3817 3818 // wake up event processor 3819 static bool eventWakeUp () { 3820 version(X11) { 3821 import core.sys.posix.unistd : write; 3822 ulong n = 1; 3823 if (customEventFDWrite >= 0) write(customEventFDWrite, &n, n.sizeof); 3824 return true; 3825 } else version(Windows) { 3826 if (customEventH !is null) SetEvent(customEventH); 3827 return true; 3828 } else version(OSXCocoa) { 3829 if(globalAppDelegate) 3830 globalAppDelegate.performSelectorOnMainThread(sel_registerName("sdpyCustomEventWakeup:"), null, false); 3831 return true; 3832 } else { 3833 // not implemented for other OSes 3834 return false; 3835 } 3836 } 3837 3838 static struct QueuedEvent { 3839 Object evt; 3840 bool timed = false; 3841 MonoTime hittime = MonoTime.zero; 3842 bool doProcess = false; // process event at the current iteration (internal flag) 3843 3844 this (Object aevt, uint toutmsecs) { 3845 evt = aevt; 3846 if (toutmsecs > 0) { 3847 import core.time : msecs; 3848 timed = true; 3849 hittime = MonoTime.currTime+toutmsecs.msecs; 3850 } 3851 } 3852 } 3853 3854 alias CustomEventHandler = bool delegate (Object o) nothrow; 3855 static struct EventHandlerEntry { 3856 CustomEventHandler dg; 3857 uint id; 3858 } 3859 3860 uint lastUsedHandlerId; 3861 EventHandlerEntry[] eventHandlers; 3862 QueuedEvent[] eventQueue = null; 3863 uint eventQueueUsed = 0; // to avoid `.assumeSafeAppend` and length changes 3864 bool inCustomEventProcessor = false; // required to properly remove events 3865 3866 // process queued events and call custom event handlers 3867 // this will not process events posted from called handlers (such events are postponed for the next iteration) 3868 void processCustomEvents () @system { 3869 bool hasSomethingToDo = false; 3870 uint ecount; 3871 bool ocep; 3872 synchronized(this) { 3873 ocep = inCustomEventProcessor; 3874 inCustomEventProcessor = true; 3875 ecount = eventQueueUsed; // user may want to post new events from an event handler; process 'em on next iteration 3876 auto ctt = MonoTime.currTime; 3877 bool hasEmpty = false; 3878 // mark events to process (this is required for `eventQueued()`) 3879 foreach (ref qe; eventQueue[0..ecount]) { 3880 if (qe.evt is null) { hasEmpty = true; continue; } 3881 if (qe.timed) { 3882 qe.doProcess = (qe.hittime <= ctt); 3883 } else { 3884 qe.doProcess = true; 3885 } 3886 hasSomethingToDo = (hasSomethingToDo || qe.doProcess); 3887 } 3888 if (!hasSomethingToDo) { 3889 // remove empty events 3890 if (hasEmpty) { 3891 uint eidx = 0, ec = eventQueueUsed; 3892 auto eptr = eventQueue.ptr; 3893 while (eidx < ec) { 3894 if (eptr.evt is null) { 3895 foreach (immutable c; eidx+1..ec) eventQueue.ptr[c-1] = eventQueue.ptr[c]; 3896 ec = --eventQueueUsed; 3897 eventQueue.ptr[ec].evt = null; // make GC life easier 3898 } else { 3899 ++eidx; 3900 ++eptr; 3901 } 3902 } 3903 } 3904 inCustomEventProcessor = ocep; 3905 return; 3906 } 3907 } 3908 // process marked events 3909 uint efree = 0; // non-processed events will be put at this index 3910 EventHandlerEntry[] eh; 3911 Object evt; 3912 foreach (immutable eidx; 0..ecount) { 3913 synchronized(this) { 3914 if (!eventQueue[eidx].doProcess) { 3915 // skip this event 3916 assert(efree <= eidx); 3917 if (efree != eidx) { 3918 // copy this event to queue start 3919 eventQueue[efree] = eventQueue[eidx]; 3920 eventQueue[eidx].evt = null; // just in case 3921 } 3922 ++efree; 3923 continue; 3924 } 3925 evt = eventQueue[eidx].evt; 3926 eventQueue[eidx].evt = null; // in case event handler will hit GC 3927 if (evt is null) continue; // just in case 3928 // try all handlers; this can be slow, but meh... 3929 eh = eventHandlers; 3930 } 3931 foreach (ref evhan; eh) if (evhan.dg !is null) evhan.dg(evt); 3932 evt = null; 3933 eh = null; 3934 } 3935 synchronized(this) { 3936 // move all unprocessed events to queue top; efree holds first "free index" 3937 foreach (immutable eidx; ecount..eventQueueUsed) { 3938 assert(efree <= eidx); 3939 if (efree != eidx) eventQueue[efree] = eventQueue[eidx]; 3940 ++efree; 3941 } 3942 eventQueueUsed = efree; 3943 // wake up event processor on next event loop iteration if we have more queued events 3944 // also, remove empty events 3945 bool awaken = false; 3946 uint eidx = 0, ec = eventQueueUsed; 3947 auto eptr = eventQueue.ptr; 3948 while (eidx < ec) { 3949 if (eptr.evt is null) { 3950 foreach (immutable c; eidx+1..ec) eventQueue.ptr[c-1] = eventQueue.ptr[c]; 3951 ec = --eventQueueUsed; 3952 eventQueue.ptr[ec].evt = null; // make GC life easier 3953 } else { 3954 if (!awaken && !eptr.timed) { eventWakeUp(); awaken = true; } 3955 ++eidx; 3956 ++eptr; 3957 } 3958 } 3959 inCustomEventProcessor = ocep; 3960 } 3961 } 3962 3963 // for all windows in nativeMapping 3964 package static void processAllCustomEvents () @system { 3965 3966 cleanupQueue.process(); 3967 3968 justCommunication.processCustomEvents(); 3969 3970 foreach (SimpleWindow sw; SimpleWindow.nativeMapping.byValue) { 3971 if (sw is null || sw.closed) continue; 3972 sw.processCustomEvents(); 3973 } 3974 3975 runPendingRunInGuiThreadDelegates(); 3976 } 3977 3978 // 0: infinite (i.e. no scheduled events in queue) 3979 uint eventQueueTimeoutMSecs () { 3980 synchronized(this) { 3981 if (eventQueueUsed == 0) return 0; 3982 if (inCustomEventProcessor) assert(0, "WUTAFUUUUUUU..."); // the thing that should not be. ABSOLUTELY! (c) 3983 uint res = int.max; 3984 auto ctt = MonoTime.currTime; 3985 foreach (const ref qe; eventQueue[0..eventQueueUsed]) { 3986 if (qe.evt is null) assert(0, "WUTAFUUUUUUU..."); // the thing that should not be. ABSOLUTELY! (c) 3987 if (qe.doProcess) continue; // just in case 3988 if (!qe.timed) return 1; // minimal 3989 if (qe.hittime <= ctt) return 1; // minimal 3990 auto tms = (qe.hittime-ctt).total!"msecs"; 3991 if (tms < 1) tms = 1; // safety net 3992 if (tms >= int.max) tms = int.max-1; // and another safety net 3993 if (res > tms) res = cast(uint)tms; 3994 } 3995 return (res >= int.max ? 0 : res); 3996 } 3997 } 3998 3999 // for all windows in nativeMapping 4000 static uint eventAllQueueTimeoutMSecs () { 4001 uint res = uint.max; 4002 foreach (SimpleWindow sw; SimpleWindow.nativeMapping.byValue) { 4003 if (sw is null || sw.closed) continue; 4004 uint to = sw.eventQueueTimeoutMSecs(); 4005 if (to && to < res) { 4006 res = to; 4007 if (to == 1) break; // can't have less than this 4008 } 4009 } 4010 return (res >= int.max ? 0 : res); 4011 } 4012 4013 version(X11) { 4014 ResizeEvent pendingResizeEvent; 4015 } 4016 4017 /++ 4018 When in opengl mode and automatically resizing, it will set the opengl viewport to stretch. 4019 4020 If you work with multiple opengl contexts and/or threads, this might be more trouble than it is 4021 worth so you can disable it by setting this to `true`. 4022 4023 History: 4024 Added November 13, 2022. 4025 +/ 4026 public bool suppressAutoOpenglViewport = false; 4027 private void updateOpenglViewportIfNeeded(int width, int height) { 4028 if(suppressAutoOpenglViewport) return; 4029 4030 version(without_opengl) {} else 4031 if(openglMode == OpenGlOptions.yes && resizability == Resizability.automaticallyScaleIfPossible) { 4032 // writeln(width, " ", height); 4033 setAsCurrentOpenGlContextNT(); 4034 glViewport(0, 0, width, height); 4035 } 4036 } 4037 4038 // TODO: Implement on non-Windows platforms (where available). 4039 private CornerStyle _fauxCornerStyle = CornerStyle.automatic; 4040 4041 /++ 4042 Style of the window's corners 4043 4044 $(WARNING 4045 Currently only implemented on Windows targets. 4046 Has no visual effect elsewhere. 4047 4048 Windows: Requires Windows 11 or later. 4049 ) 4050 4051 History: 4052 Added September 09, 2024. 4053 +/ 4054 public CornerStyle cornerStyle() @trusted { 4055 version(Windows) { 4056 DWM_WINDOW_CORNER_PREFERENCE dwmCorner; 4057 const apiResult = DwmGetWindowAttribute( 4058 this.hwnd, 4059 DWMWINDOWATTRIBUTE.DWMWA_WINDOW_CORNER_PREFERENCE, 4060 &dwmCorner, 4061 typeof(dwmCorner).sizeof 4062 ); 4063 4064 if (apiResult != S_OK) { 4065 // Unsupported? 4066 if (apiResult == E_INVALIDARG) { 4067 // Feature unsupported; Windows version probably too old. 4068 // Requires Windows 11 (build 22000) or later. 4069 return _fauxCornerStyle; 4070 } 4071 4072 throw new WindowsApiException("DwmGetWindowAttribute", apiResult); 4073 } 4074 4075 CornerStyle corner; 4076 if (!dwmCorner.fromDWM(corner)) { 4077 throw ArsdException!"DwmGetWindowAttribute unfamiliar corner preference"(dwmCorner); 4078 } 4079 return corner; 4080 } else { 4081 return _fauxCornerStyle; 4082 } 4083 } 4084 4085 /// ditto 4086 public void cornerStyle(const CornerStyle corner) @trusted { 4087 version(Windows) { 4088 DWM_WINDOW_CORNER_PREFERENCE dwmCorner; 4089 if (!corner.toDWM(dwmCorner)) { 4090 assert(false, "This should have been impossible because of a final switch."); 4091 } 4092 4093 const apiResult = DwmSetWindowAttribute( 4094 this.hwnd, 4095 DWMWINDOWATTRIBUTE.DWMWA_WINDOW_CORNER_PREFERENCE, 4096 &dwmCorner, 4097 typeof(dwmCorner).sizeof 4098 ); 4099 4100 if (apiResult != S_OK) { 4101 // Unsupported? 4102 if (apiResult == E_INVALIDARG) { 4103 // Feature unsupported; Windows version probably too old. 4104 // Requires Windows 11 (build 22000) or later. 4105 _fauxCornerStyle = corner; 4106 return; 4107 } 4108 4109 throw new WindowsApiException("DwmSetWindowAttribute", apiResult); 4110 } 4111 } else { 4112 _fauxCornerStyle = corner; 4113 } 4114 } 4115 } 4116 4117 version(OSXCocoa) 4118 enum NSWindow NullWindow = null; 4119 else 4120 enum NullWindow = NativeWindowHandle.init; 4121 4122 /++ 4123 Magic pseudo-window for just posting events to a global queue. 4124 4125 Not entirely supported, I might delete it at any time. 4126 4127 Added Nov 5, 2021. 4128 +/ 4129 __gshared SimpleWindow justCommunication = new SimpleWindow(NullWindow); 4130 4131 /* Drag and drop support { */ 4132 version(X11) { 4133 4134 } else version(Windows) { 4135 import core.sys.windows.uuid; 4136 import core.sys.windows.ole2; 4137 import core.sys.windows.oleidl; 4138 import core.sys.windows.objidl; 4139 import core.sys.windows.wtypes; 4140 4141 pragma(lib, "ole32"); 4142 void initDnd() { 4143 auto err = OleInitialize(null); 4144 if(err != S_OK && err != S_FALSE) 4145 throw new Exception("init");//err); 4146 } 4147 } 4148 /* } End drag and drop support */ 4149 4150 4151 /// Represents a mouse cursor (aka the mouse pointer, the image seen on screen that indicates where the mouse is pointing). 4152 /// See [GenericCursor]. 4153 class MouseCursor { 4154 int osId; 4155 bool isStockCursor; 4156 private this(int osId) { 4157 this.osId = osId; 4158 this.isStockCursor = true; 4159 } 4160 4161 // https://msdn.microsoft.com/en-us/library/windows/desktop/ms648385(v=vs.85).aspx 4162 this(int xHotSpot, int yHotSpot, ubyte[] andMask, ubyte[] xorMask) {} 4163 4164 version(Windows) { 4165 HCURSOR cursor_; 4166 HCURSOR cursorHandle() { 4167 if(cursor_ is null) 4168 cursor_ = LoadCursor(null, MAKEINTRESOURCE(osId)); 4169 return cursor_; 4170 } 4171 4172 } else static if(UsingSimpledisplayX11) { 4173 Cursor cursor_ = None; 4174 int xDisplaySequence; 4175 4176 Cursor cursorHandle() { 4177 if(this.osId == None) 4178 return None; 4179 4180 // we need to reload if we on a new X connection 4181 if(cursor_ == None || XDisplayConnection.connectionSequenceNumber != xDisplaySequence) { 4182 cursor_ = XCreateFontCursor(XDisplayConnection.get(), this.osId); 4183 xDisplaySequence = XDisplayConnection.connectionSequenceNumber; 4184 } 4185 return cursor_; 4186 } 4187 } 4188 } 4189 4190 // https://developer.mozilla.org/en-US/docs/Web/CSS/cursor 4191 // https://tronche.com/gui/x/xlib/appendix/b/ 4192 // https://msdn.microsoft.com/en-us/library/windows/desktop/ms648391(v=vs.85).aspx 4193 /// 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. 4194 enum GenericCursorType { 4195 Default, /// The default arrow pointer. 4196 Wait, /// A cursor indicating something is loading and the user must wait. 4197 Hand, /// A pointing finger, like the one used hovering over hyperlinks in a web browser. 4198 Help, /// A cursor indicating the user can get help about the pointer location. 4199 Cross, /// A crosshair. 4200 Text, /// An i-beam shape, typically used to indicate text selection is possible. 4201 Move, /// Pointer indicating movement is possible. May also be used as SizeAll. 4202 UpArrow, /// An arrow pointing straight up. 4203 Progress, /// The hourglass and arrow, indicating the computer is working but the user can still work. Not great results on X11. 4204 NotAllowed, /// Indicates the current operation is not allowed. Not great results on X11. 4205 SizeNesw, /// Arrow pointing northeast and southwest (lower-left corner resize indicator). 4206 SizeNs, /// Arrow pointing north and south (upper/lower edge resize indicator). 4207 SizeNwse, /// Arrow pointing northwest and southeast (upper-left corner resize indicator). 4208 SizeWe, /// Arrow pointing west and east (left/right edge resize indicator). 4209 4210 } 4211 4212 /* 4213 X_plus == css cell == Windows ? 4214 */ 4215 4216 /// You get one by `GenericCursor.SomeTime`. See [GenericCursorType] for a list of types. 4217 static struct GenericCursor { 4218 static: 4219 /// 4220 MouseCursor opDispatch(string str)() if(__traits(hasMember, GenericCursorType, str)) { 4221 static MouseCursor mc; 4222 4223 auto type = __traits(getMember, GenericCursorType, str); 4224 4225 if(mc is null) { 4226 4227 version(Windows) { 4228 int osId; 4229 final switch(type) { 4230 case GenericCursorType.Default: osId = IDC_ARROW; break; 4231 case GenericCursorType.Wait: osId = IDC_WAIT; break; 4232 case GenericCursorType.Hand: osId = IDC_HAND; break; 4233 case GenericCursorType.Help: osId = IDC_HELP; break; 4234 case GenericCursorType.Cross: osId = IDC_CROSS; break; 4235 case GenericCursorType.Text: osId = IDC_IBEAM; break; 4236 case GenericCursorType.Move: osId = IDC_SIZEALL; break; 4237 case GenericCursorType.UpArrow: osId = IDC_UPARROW; break; 4238 case GenericCursorType.Progress: osId = IDC_APPSTARTING; break; 4239 case GenericCursorType.NotAllowed: osId = IDC_NO; break; 4240 case GenericCursorType.SizeNesw: osId = IDC_SIZENESW; break; 4241 case GenericCursorType.SizeNs: osId = IDC_SIZENS; break; 4242 case GenericCursorType.SizeNwse: osId = IDC_SIZENWSE; break; 4243 case GenericCursorType.SizeWe: osId = IDC_SIZEWE; break; 4244 } 4245 } else static if(UsingSimpledisplayX11) { 4246 int osId; 4247 final switch(type) { 4248 case GenericCursorType.Default: osId = None; break; 4249 case GenericCursorType.Wait: osId = 150 /* XC_watch */; break; 4250 case GenericCursorType.Hand: osId = 60 /* XC_hand2 */; break; 4251 case GenericCursorType.Help: osId = 92 /* XC_question_arrow */; break; 4252 case GenericCursorType.Cross: osId = 34 /* XC_crosshair */; break; 4253 case GenericCursorType.Text: osId = 152 /* XC_xterm */; break; 4254 case GenericCursorType.Move: osId = 52 /* XC_fleur */; break; 4255 case GenericCursorType.UpArrow: osId = 22 /* XC_center_ptr */; break; 4256 case GenericCursorType.Progress: osId = 150 /* XC_watch, best i can do i think */; break; 4257 4258 case GenericCursorType.NotAllowed: osId = 24 /* XC_circle. not great */; break; 4259 case GenericCursorType.SizeNesw: osId = 12 /* XC_bottom_left_corner */ ; break; 4260 case GenericCursorType.SizeNs: osId = 116 /* XC_sb_v_double_arrow */; break; 4261 case GenericCursorType.SizeNwse: osId = 14 /* XC_bottom_right_corner */; break; 4262 case GenericCursorType.SizeWe: osId = 108 /* XC_sb_h_double_arrow */; break; 4263 } 4264 4265 } else { 4266 int osId; 4267 // featureNotImplemented(); 4268 } 4269 4270 mc = new MouseCursor(osId); 4271 } 4272 return mc; 4273 } 4274 } 4275 4276 4277 /++ 4278 If you want to get more control over the event loop, you can use this. 4279 4280 Typically though, you can just call [SimpleWindow.eventLoop] which forwards 4281 to `EventLoop.get.run`. 4282 +/ 4283 struct EventLoop { 4284 @disable this(); 4285 4286 /// Gets a reference to an existing event loop 4287 static EventLoop get() { 4288 return EventLoop(0, null); 4289 } 4290 4291 static void quitApplication() { 4292 version(use_arsd_core) { 4293 import arsd.core; 4294 ICoreEventLoop.exitApplication(); 4295 } 4296 EventLoop.get().exit(); 4297 } 4298 4299 private __gshared static Object monitor = new Object(); // deliberate CTFE usage here fyi 4300 4301 /// Construct an application-global event loop for yourself 4302 /// See_Also: [SimpleWindow.setEventHandlers] 4303 this(long pulseTimeout, void delegate() handlePulse) { 4304 synchronized(monitor) { 4305 if(impl is null) { 4306 claimGuiThread(); 4307 version(sdpy_thread_checks) assert(thisIsGuiThread); 4308 impl = new EventLoopImpl(pulseTimeout, handlePulse); 4309 } else { 4310 if(pulseTimeout) { 4311 impl.pulseTimeout = pulseTimeout; 4312 impl.handlePulse = handlePulse; 4313 } 4314 } 4315 impl.refcount++; 4316 } 4317 } 4318 4319 ~this() { 4320 if(impl is null) 4321 return; 4322 impl.refcount--; 4323 if(impl.refcount == 0) { 4324 impl.dispose(); 4325 if(thisIsGuiThread) 4326 guiThreadFinalize(); 4327 } 4328 4329 } 4330 4331 this(this) { 4332 if(impl is null) 4333 return; 4334 impl.refcount++; 4335 } 4336 4337 /// Runs the event loop until the whileCondition, if present, returns false 4338 int run(bool delegate() whileCondition = null) { 4339 assert(impl !is null); 4340 impl.notExited = true; 4341 return impl.run(whileCondition); 4342 } 4343 4344 /// Exits the event loop, but allows you to reenter it again later (in contrast with quitApplication, which tries to terminate the program) 4345 void exit() { 4346 assert(impl !is null); 4347 impl.notExited = false; 4348 4349 version(use_arsd_core) { 4350 import arsd.core; 4351 ICoreEventLoop.exitApplication(); 4352 } 4353 } 4354 4355 version(linux) 4356 ref void delegate(int) signalHandler() { 4357 assert(impl !is null); 4358 return impl.signalHandler; 4359 } 4360 4361 __gshared static EventLoopImpl* impl; 4362 } 4363 4364 version(linux) 4365 void delegate(int, int) globalHupHandler; 4366 4367 version(Posix) 4368 void makeNonBlocking(int fd) { 4369 import fcntl = core.sys.posix.fcntl; 4370 auto flags = fcntl.fcntl(fd, fcntl.F_GETFL, 0); 4371 if(flags == -1) 4372 throw new Exception("fcntl get"); 4373 flags |= fcntl.O_NONBLOCK; 4374 auto s = fcntl.fcntl(fd, fcntl.F_SETFL, flags); 4375 if(s == -1) 4376 throw new Exception("fcntl set"); 4377 } 4378 4379 struct EventLoopImpl { 4380 int refcount; 4381 4382 bool notExited = true; 4383 4384 version(Emscripten) { 4385 void delegate(int) signalHandler; 4386 static import unix = core.sys.posix.unistd; 4387 static import err = core.stdc.errno; 4388 } else 4389 version(linux) { 4390 static import ep = core.sys.linux.epoll; 4391 static import unix = core.sys.posix.unistd; 4392 static import err = core.stdc.errno; 4393 import core.sys.linux.timerfd; 4394 4395 void delegate(int) signalHandler; 4396 } 4397 4398 version(X11) { 4399 int pulseFd = -1; 4400 version(Emscripten) {} else 4401 version(linux) ep.epoll_event[16] events = void; 4402 } else version(Windows) { 4403 Timer pulser; 4404 HANDLE[] handles; 4405 } 4406 4407 4408 /// "Lock" this window handle, to do multithreaded synchronization. You probably won't need 4409 /// to call this, as it's not recommended to share window between threads. 4410 void mtLock () { 4411 version(X11) { 4412 XLockDisplay(this.display); 4413 } 4414 } 4415 4416 version(X11) 4417 auto display() { return XDisplayConnection.get; } 4418 4419 /// "Unlock" this window handle, to do multithreaded synchronization. You probably won't need 4420 /// to call this, as it's not recommended to share window between threads. 4421 void mtUnlock () { 4422 version(X11) { 4423 XUnlockDisplay(this.display); 4424 } 4425 } 4426 4427 version(with_eventloop) 4428 void initialize(long pulseTimeout) {} 4429 else 4430 void initialize(long pulseTimeout) @system { 4431 version(Windows) { 4432 if(pulseTimeout && handlePulse !is null) 4433 pulser = new Timer(cast(int) pulseTimeout, handlePulse); 4434 4435 if (customEventH is null) { 4436 customEventH = CreateEvent(null, FALSE/*autoreset*/, FALSE/*initial state*/, null); 4437 if (customEventH !is null) { 4438 handles ~= customEventH; 4439 } else { 4440 // this is something that should not be; better be safe than sorry 4441 throw new Exception("can't create eventfd for custom event processing"); 4442 } 4443 } 4444 4445 SimpleWindow.processAllCustomEvents(); // process events added before event object creation 4446 } 4447 4448 version(Emscripten) { 4449 4450 } else version(linux) { 4451 prepareEventLoop(); 4452 { 4453 auto display = XDisplayConnection.get; 4454 // adding Xlib file 4455 ep.epoll_event ev = void; 4456 { import core.stdc.string : memset; memset(&ev, 0, ev.sizeof); } // this makes valgrind happy 4457 ev.events = ep.EPOLLIN; 4458 ev.data.fd = display.fd; 4459 if(ep.epoll_ctl(epollFd, ep.EPOLL_CTL_ADD, display.fd, &ev) == -1) 4460 throw new Exception("add x fd");// ~ to!string(epollFd)); 4461 displayFd = display.fd; 4462 } 4463 4464 if(pulseTimeout && handlePulse !is null) { 4465 pulseFd = timerfd_create(CLOCK_MONOTONIC, 0); 4466 if(pulseFd == -1) 4467 throw new Exception("pulse timer create failed"); 4468 4469 itimerspec value; 4470 value.it_value.tv_sec = cast(int) (pulseTimeout / 1000); 4471 value.it_value.tv_nsec = (pulseTimeout % 1000) * 1000_000; 4472 4473 value.it_interval.tv_sec = cast(int) (pulseTimeout / 1000); 4474 value.it_interval.tv_nsec = (pulseTimeout % 1000) * 1000_000; 4475 4476 if(timerfd_settime(pulseFd, 0, &value, null) == -1) 4477 throw new Exception("couldn't make pulse timer"); 4478 4479 ep.epoll_event ev = void; 4480 { import core.stdc.string : memset; memset(&ev, 0, ev.sizeof); } // this makes valgrind happy 4481 ev.events = ep.EPOLLIN; 4482 ev.data.fd = pulseFd; 4483 ep.epoll_ctl(epollFd, ep.EPOLL_CTL_ADD, pulseFd, &ev); 4484 } 4485 4486 // eventfd for custom events 4487 if (customEventFDWrite == -1) { 4488 customEventFDWrite = eventfd(0, 0); 4489 customEventFDRead = customEventFDWrite; 4490 if (customEventFDRead >= 0) { 4491 ep.epoll_event ev = void; 4492 { import core.stdc.string : memset; memset(&ev, 0, ev.sizeof); } // this makes valgrind happy 4493 ev.events = ep.EPOLLIN; 4494 ev.data.fd = customEventFDRead; 4495 ep.epoll_ctl(epollFd, ep.EPOLL_CTL_ADD, customEventFDRead, &ev); 4496 } else { 4497 // this is something that should not be; better be safe than sorry 4498 throw new Exception("can't create eventfd for custom event processing"); 4499 } 4500 } 4501 4502 if (customSignalFD == -1) { 4503 import core.sys.linux.sys.signalfd; 4504 4505 sigset_t sigset; 4506 auto err = sigemptyset(&sigset); 4507 assert(!err); 4508 err = sigaddset(&sigset, SIGINT); 4509 assert(!err); 4510 err = sigaddset(&sigset, SIGHUP); 4511 assert(!err); 4512 err = sigprocmask(SIG_BLOCK, &sigset, null); 4513 assert(!err); 4514 4515 customSignalFD = signalfd(-1, &sigset, SFD_NONBLOCK); 4516 assert(customSignalFD != -1); 4517 4518 ep.epoll_event ev = void; 4519 { import core.stdc.string : memset; memset(&ev, 0, ev.sizeof); } // this makes valgrind happy 4520 ev.events = ep.EPOLLIN; 4521 ev.data.fd = customSignalFD; 4522 ep.epoll_ctl(epollFd, ep.EPOLL_CTL_ADD, customSignalFD, &ev); 4523 } 4524 } else version(Posix) { 4525 prepareEventLoop(); 4526 if (customEventFDRead == -1) { 4527 int[2] bfr; 4528 import core.sys.posix.unistd; 4529 auto ret = pipe(bfr); 4530 if(ret == -1) throw new Exception("pipe"); 4531 customEventFDRead = bfr[0]; 4532 customEventFDWrite = bfr[1]; 4533 } 4534 4535 } 4536 4537 SimpleWindow.processAllCustomEvents(); // process events added before event FD creation 4538 4539 version(linux) { 4540 this.mtLock(); 4541 scope(exit) this.mtUnlock(); 4542 version(X11) 4543 XPending(display); // no, really 4544 } 4545 4546 disposed = false; 4547 } 4548 4549 bool disposed = true; 4550 version(X11) 4551 int displayFd = -1; 4552 4553 version(with_eventloop) 4554 void dispose() {} 4555 else 4556 void dispose() @system { 4557 disposed = true; 4558 version(X11) { 4559 if(pulseFd != -1) { 4560 import unix = core.sys.posix.unistd; 4561 unix.close(pulseFd); 4562 pulseFd = -1; 4563 } 4564 4565 version(Emscripten) {} else 4566 version(linux) 4567 if(displayFd != -1) { 4568 // 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 4569 ep.epoll_event ev = void; 4570 { import core.stdc.string : memset; memset(&ev, 0, ev.sizeof); } // this makes valgrind happy 4571 ev.events = ep.EPOLLIN; 4572 ev.data.fd = displayFd; 4573 ep.epoll_ctl(epollFd, ep.EPOLL_CTL_DEL, displayFd, &ev); 4574 displayFd = -1; 4575 } 4576 4577 } else version(Windows) { 4578 if(pulser !is null) { 4579 pulser.destroy(); 4580 pulser = null; 4581 } 4582 if (customEventH !is null) { 4583 CloseHandle(customEventH); 4584 customEventH = null; 4585 } 4586 } 4587 } 4588 4589 this(long pulseTimeout, void delegate() handlePulse) { 4590 this.pulseTimeout = pulseTimeout; 4591 this.handlePulse = handlePulse; 4592 initialize(pulseTimeout); 4593 } 4594 4595 private long pulseTimeout; 4596 void delegate() handlePulse; 4597 4598 ~this() { 4599 dispose(); 4600 } 4601 4602 version(Posix) 4603 ref int customEventFDRead() { return SimpleWindow.customEventFDRead; } 4604 version(Posix) 4605 ref int customEventFDWrite() { return SimpleWindow.customEventFDWrite; } 4606 version(linux) 4607 ref int customSignalFD() { return SimpleWindow.customSignalFD; } 4608 version(Windows) 4609 ref auto customEventH() { return SimpleWindow.customEventH; } 4610 4611 version(X11) { 4612 bool doXPending() { 4613 bool done = false; 4614 4615 this.mtLock(); 4616 scope(exit) this.mtUnlock(); 4617 //{ import core.stdc.stdio; printf("*** queued: %d\n", XEventsQueued(this.display, QueueMode.QueuedAlready)); } 4618 while(!done && XPending(display)) { 4619 done = doXNextEvent(this.display); 4620 } 4621 4622 return done; 4623 } 4624 void doXNextEventVoid() { 4625 doXPending(); 4626 } 4627 } 4628 4629 version(with_eventloop) { 4630 int loopHelper(bool delegate() whileCondition) { 4631 // FIXME: whileCondition 4632 import arsd.eventloop; 4633 loop(); 4634 return 0; 4635 } 4636 } else 4637 int loopHelper(bool delegate() whileCondition) { 4638 version(X11) { 4639 bool done = false; 4640 4641 XFlush(display); 4642 insideXEventLoop = true; 4643 scope(exit) insideXEventLoop = false; 4644 4645 version(use_arsd_core) { 4646 import arsd.core; 4647 auto el = getThisThreadEventLoop(EventLoopType.Ui); 4648 4649 static bool loopInitialized = false; 4650 if(!loopInitialized) { 4651 el.addDelegateOnLoopIteration(&doXNextEventVoid, 0); 4652 el.addDelegateOnLoopIteration(&SimpleWindow.processAllCustomEvents, 0); 4653 4654 if(customSignalFD != -1) 4655 cast(void) el.addCallbackOnFdReadable(customSignalFD, new CallbackHelper(() { 4656 version(linux) { 4657 import core.sys.linux.sys.signalfd; 4658 import core.sys.posix.unistd : read; 4659 signalfd_siginfo info; 4660 read(customSignalFD, &info, info.sizeof); 4661 4662 auto sig = info.ssi_signo; 4663 4664 if(EventLoop.get.signalHandler !is null) { 4665 EventLoop.get.signalHandler()(sig); 4666 } else { 4667 EventLoop.get.exit(); 4668 } 4669 } 4670 })); 4671 4672 if(display.fd != -1) 4673 cast(void) el.addCallbackOnFdReadable(display.fd, new CallbackHelper(() { 4674 this.mtLock(); 4675 scope(exit) this.mtUnlock(); 4676 while(!done && XPending(display)) { 4677 done = doXNextEvent(this.display); 4678 } 4679 })); 4680 4681 if(pulseFd != -1) 4682 cast(void) el.addCallbackOnFdReadable(pulseFd, new CallbackHelper(() { 4683 long expirationCount; 4684 // 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... 4685 4686 handlePulse(); 4687 4688 // read just to clear the buffer so poll doesn't trigger again 4689 // BTW I read AFTER the pulse because if the pulse handler takes 4690 // a lot of time to execute, we don't want the app to get stuck 4691 // in a loop of timer hits without a chance to do anything else 4692 // 4693 // IOW handlePulse happens at most once per pulse interval. 4694 unix.read(pulseFd, &expirationCount, expirationCount.sizeof); 4695 })); 4696 4697 if(customEventFDRead != -1) 4698 cast(void) el.addCallbackOnFdReadable(customEventFDRead, new CallbackHelper(() { 4699 // we have some custom events; process 'em 4700 import core.sys.posix.unistd : read; 4701 ulong n; 4702 read(customEventFDRead, &n, n.sizeof); // reset counter value to zero again 4703 //{ import core.stdc.stdio; printf("custom event! count=%u\n", eventQueueUsed); } 4704 //SimpleWindow.processAllCustomEvents(); 4705 })); 4706 4707 // FIXME: posix fds 4708 // FIXME up? 4709 4710 4711 loopInitialized = true; 4712 } 4713 4714 el.run(() => !whileCondition()); 4715 } else version(linux) { 4716 while(!done && (whileCondition is null || whileCondition() == true) && notExited) { 4717 bool forceXPending = false; 4718 auto wto = SimpleWindow.eventAllQueueTimeoutMSecs(); 4719 // eh... some events may be queued for "squashing" (or "late delivery"), so we have to do the following magic 4720 { 4721 this.mtLock(); 4722 scope(exit) this.mtUnlock(); 4723 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 4724 } 4725 //{ import core.stdc.stdio; printf("*** wto=%d; force=%d\n", wto, (forceXPending ? 1 : 0)); } 4726 auto nfds = ep.epoll_wait(epollFd, events.ptr, events.length, (wto == 0 || wto >= int.max ? -1 : cast(int)wto)); 4727 if(nfds == -1) { 4728 if(err.errno == err.EINTR) { 4729 //if(forceXPending) goto xpending; 4730 continue; // interrupted by signal, just try again 4731 } 4732 throw new Exception("epoll wait failure"); 4733 } 4734 // writeln(nfds, " ", events[0].data.fd); 4735 4736 SimpleWindow.processAllCustomEvents(); // anyway 4737 //version(sdddd) { writeln("nfds=", nfds, "; [0]=", events[0].data.fd); } 4738 foreach(idx; 0 .. nfds) { 4739 if(done) break; 4740 auto fd = events[idx].data.fd; 4741 assert(fd != -1); // should never happen cuz the api doesn't do that but better to assert than assume. 4742 auto flags = events[idx].events; 4743 if(flags & ep.EPOLLIN) { 4744 if (fd == customSignalFD) { 4745 version(linux) { 4746 import core.sys.linux.sys.signalfd; 4747 import core.sys.posix.unistd : read; 4748 signalfd_siginfo info; 4749 read(customSignalFD, &info, info.sizeof); 4750 4751 auto sig = info.ssi_signo; 4752 4753 if(EventLoop.get.signalHandler !is null) { 4754 EventLoop.get.signalHandler()(sig); 4755 } else { 4756 EventLoop.get.exit(); 4757 } 4758 } 4759 } else if(fd == display.fd) { 4760 version(sdddd) { writeln("X EVENT PENDING!"); } 4761 this.mtLock(); 4762 scope(exit) this.mtUnlock(); 4763 while(!done && XPending(display)) { 4764 done = doXNextEvent(this.display); 4765 } 4766 forceXPending = false; 4767 } else if(fd == pulseFd) { 4768 long expirationCount; 4769 // 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... 4770 4771 handlePulse(); 4772 4773 // read just to clear the buffer so poll doesn't trigger again 4774 // BTW I read AFTER the pulse because if the pulse handler takes 4775 // a lot of time to execute, we don't want the app to get stuck 4776 // in a loop of timer hits without a chance to do anything else 4777 // 4778 // IOW handlePulse happens at most once per pulse interval. 4779 unix.read(pulseFd, &expirationCount, expirationCount.sizeof); 4780 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 4781 } else if (fd == customEventFDRead) { 4782 // we have some custom events; process 'em 4783 import core.sys.posix.unistd : read; 4784 ulong n; 4785 read(customEventFDRead, &n, n.sizeof); // reset counter value to zero again 4786 //{ import core.stdc.stdio; printf("custom event! count=%u\n", eventQueueUsed); } 4787 //SimpleWindow.processAllCustomEvents(); 4788 4789 forceXPending = true; 4790 } else { 4791 // some other timer 4792 version(sdddd) { writeln("unknown fd: ", fd); } 4793 4794 if(Timer* t = fd in Timer.mapping) 4795 (*t).trigger(); 4796 4797 if(PosixFdReader* pfr = fd in PosixFdReader.mapping) 4798 (*pfr).ready(flags); 4799 4800 // we don't know what the user did in this timer, so we need to assume that 4801 // there's X data to be flushed and potentially processed 4802 forceXPending = true; 4803 4804 // or i might add support for other FDs too 4805 // but for now it is just timer 4806 // (if you want other fds, use arsd.eventloop and compile with -version=with_eventloop), it offers a fuller api for arbitrary stuff. 4807 } 4808 } 4809 if(flags & ep.EPOLLHUP) { 4810 if(PosixFdReader* pfr = fd in PosixFdReader.mapping) 4811 (*pfr).hup(flags); 4812 if(globalHupHandler) 4813 globalHupHandler(fd, flags); 4814 } 4815 /+ 4816 } else { 4817 // not interested in OUT, we are just reading here. 4818 // 4819 // error or hup might also be reported 4820 // but it shouldn't here since we are only 4821 // using a few types of FD and Xlib will report 4822 // if it dies. 4823 // so instead of thoughtfully handling it, I'll 4824 // just throw. for now at least 4825 4826 throw new Exception("epoll did something else"); 4827 } 4828 +/ 4829 } 4830 // if we won't call `XPending()` here, libX may delay some internal event delivery. 4831 // i.e. we HAVE to repeatedly call `XPending()` even if libX fd wasn't signalled! 4832 xpending: 4833 if (!done && forceXPending) { 4834 done = doXPending(); 4835 } 4836 } 4837 } else { 4838 // Generic fallback: yes to simple pulse support, 4839 // but NO timer support! 4840 4841 // FIXME: we could probably support the POSIX timer_create 4842 // signal-based option, but I'm in no rush to write it since 4843 // I prefer the fd-based functions. 4844 while (!done && (whileCondition is null || whileCondition() == true) && notExited) { 4845 4846 import core.sys.posix.poll; 4847 4848 pollfd[] pfds; 4849 pollfd[32] pfdsBuffer; 4850 auto len = PosixFdReader.mapping.length + 2; 4851 // FIXME: i should just reuse the buffer 4852 if(len < pfdsBuffer.length) 4853 pfds = pfdsBuffer[0 .. len]; 4854 else 4855 pfds = new pollfd[](len); 4856 4857 pfds[0].fd = display.fd; 4858 pfds[0].events = POLLIN; 4859 pfds[0].revents = 0; 4860 4861 int slot = 1; 4862 4863 if(customEventFDRead != -1) { 4864 pfds[slot].fd = customEventFDRead; 4865 pfds[slot].events = POLLIN; 4866 pfds[slot].revents = 0; 4867 4868 slot++; 4869 } 4870 4871 foreach(fd, obj; PosixFdReader.mapping) { 4872 if(!obj.enabled) continue; 4873 pfds[slot].fd = fd; 4874 pfds[slot].events = POLLIN; 4875 pfds[slot].revents = 0; 4876 4877 slot++; 4878 } 4879 4880 auto ret = poll(pfds.ptr, slot, pulseTimeout > 0 ? cast(int) pulseTimeout : -1); 4881 if(ret == -1) throw new Exception("poll"); 4882 4883 if(ret == 0) { 4884 // FIXME it may not necessarily time out if events keep coming 4885 if(handlePulse !is null) 4886 handlePulse(); 4887 } else { 4888 foreach(s; 0 .. slot) { 4889 if(pfds[s].revents == 0) continue; 4890 4891 if(pfds[s].fd == display.fd) { 4892 while(!done && XPending(display)) { 4893 this.mtLock(); 4894 scope(exit) this.mtUnlock(); 4895 done = doXNextEvent(this.display); 4896 } 4897 } else if(customEventFDRead != -1 && pfds[s].fd == customEventFDRead) { 4898 4899 import core.sys.posix.unistd : read; 4900 ulong n; 4901 read(customEventFDRead, &n, n.sizeof); 4902 SimpleWindow.processAllCustomEvents(); 4903 } else { 4904 auto obj = PosixFdReader.mapping[pfds[s].fd]; 4905 if(pfds[s].revents & POLLNVAL) { 4906 obj.dispose(); 4907 } else { 4908 obj.ready(pfds[s].revents); 4909 } 4910 } 4911 4912 ret--; 4913 if(ret == 0) break; 4914 } 4915 } 4916 } 4917 } 4918 } 4919 4920 version(Windows) { 4921 4922 version(use_arsd_core) { 4923 import arsd.core; 4924 auto el = getThisThreadEventLoop(EventLoopType.Ui); 4925 static bool loopInitialized = false; 4926 if(!loopInitialized) { 4927 el.addDelegateOnLoopIteration(&SimpleWindow.processAllCustomEvents, 0); 4928 el.addDelegateOnLoopIteration(function() { eventLoopRound++; }, 0); 4929 loopInitialized = true; 4930 } 4931 el.run(() => !whileCondition()); 4932 } else { 4933 int ret = -1; 4934 MSG message; 4935 while(ret != 0 && (whileCondition is null || whileCondition() == true) && notExited) { 4936 eventLoopRound++; 4937 auto wto = SimpleWindow.eventAllQueueTimeoutMSecs(); 4938 auto waitResult = MsgWaitForMultipleObjectsEx( 4939 cast(int) handles.length, handles.ptr, 4940 (wto == 0 ? INFINITE : wto), /* timeout */ 4941 0x04FF, /* QS_ALLINPUT */ 4942 0x0002 /* MWMO_ALERTABLE */ | 0x0004 /* MWMO_INPUTAVAILABLE */); 4943 4944 SimpleWindow.processAllCustomEvents(); // anyway 4945 enum WAIT_OBJECT_0 = 0; 4946 if(waitResult >= WAIT_OBJECT_0 && waitResult < handles.length + WAIT_OBJECT_0) { 4947 auto h = handles[waitResult - WAIT_OBJECT_0]; 4948 if(auto e = h in WindowsHandleReader.mapping) { 4949 (*e).ready(); 4950 } 4951 } else if(waitResult == handles.length + WAIT_OBJECT_0) { 4952 // message ready 4953 int count; 4954 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 4955 ret = GetMessage(&message, null, 0, 0); 4956 if(ret == -1) 4957 throw new WindowsApiException("GetMessage", GetLastError()); 4958 TranslateMessage(&message); 4959 DispatchMessage(&message); 4960 4961 count++; 4962 if(count > 10) 4963 break; // take the opportunity to catch up on other events 4964 4965 if(ret == 0) { // WM_QUIT 4966 EventLoop.quitApplication(); 4967 break; 4968 } 4969 } 4970 } else if(waitResult == 0x000000C0L /* WAIT_IO_COMPLETION */) { 4971 SleepEx(0, true); // I call this to give it a chance to do stuff like async io 4972 } else if(waitResult == 258L /* WAIT_TIMEOUT */) { 4973 // timeout, should never happen since we aren't using it 4974 } else if(waitResult == 0xFFFFFFFF) { 4975 // failed 4976 throw new WindowsApiException("MsgWaitForMultipleObjectsEx", GetLastError()); 4977 } else { 4978 // idk.... 4979 } 4980 } 4981 } 4982 4983 // return message.wParam; 4984 return 0; 4985 } else { 4986 return 0; 4987 } 4988 } 4989 4990 int run(bool delegate() whileCondition = null) { 4991 if(disposed) 4992 initialize(this.pulseTimeout); 4993 4994 version(X11) { 4995 try { 4996 return loopHelper(whileCondition); 4997 } catch(XDisconnectException e) { 4998 if(e.userRequested) { 4999 foreach(item; CapableOfHandlingNativeEvent.nativeHandleMapping) 5000 item.discardConnectionState(); 5001 XCloseDisplay(XDisplayConnection.display); 5002 } 5003 5004 XDisplayConnection.display = null; 5005 5006 this.dispose(); 5007 5008 throw e; 5009 } 5010 } else { 5011 return loopHelper(whileCondition); 5012 } 5013 } 5014 } 5015 5016 5017 /++ 5018 Provides an icon on the system notification area (also known as the system tray). 5019 5020 5021 If a notification area is not available with the NotificationIcon object is created, 5022 it will silently succeed and simply attempt to create one when an area becomes available. 5023 5024 5025 NotificationAreaIcon on Windows assumes you are on Windows Vista or later. Support for 5026 Windows XP was dropped on October 31, 2023. On the other hand, support for 32 bit transparency 5027 with true color was added at that time. I was just too lazy to write the fallback. 5028 5029 If this is an issue, let me know, it'd take about an hour to get it back in there, but I suggest 5030 you use arsd 10.x when targeting Windows XP. 5031 +/ 5032 version(Emscripten) {} else 5033 version(OSXCocoa) {} else // NotYetImplementedException 5034 class NotificationAreaIcon : CapableOfHandlingNativeEvent { 5035 5036 version(X11) { 5037 void recreateAfterDisconnect() { 5038 stateDiscarded = false; 5039 clippixmap = None; 5040 throw new Exception("NOT IMPLEMENTED"); 5041 } 5042 5043 bool stateDiscarded; 5044 void discardConnectionState() { 5045 stateDiscarded = true; 5046 } 5047 } 5048 5049 5050 version(X11) { 5051 Image img; 5052 5053 NativeEventHandler getNativeEventHandler() { 5054 return delegate int(XEvent e) { 5055 switch(e.type) { 5056 case EventType.Expose: 5057 //case EventType.VisibilityNotify: 5058 redraw(); 5059 break; 5060 case EventType.ClientMessage: 5061 version(sddddd) { 5062 writeln("\t", e.xclient.message_type == GetAtom!("_XEMBED")(XDisplayConnection.get)); 5063 writeln("\t", e.xclient.format); 5064 writeln("\t", e.xclient.data.l); 5065 } 5066 break; 5067 case EventType.ButtonPress: 5068 auto event = e.xbutton; 5069 if (onClick !is null || onClickEx !is null) { 5070 MouseButton mb = cast(MouseButton)0; 5071 switch (event.button) { 5072 case 1: mb = MouseButton.left; break; // left 5073 case 2: mb = MouseButton.middle; break; // middle 5074 case 3: mb = MouseButton.right; break; // right 5075 case 4: mb = MouseButton.wheelUp; break; // scroll up 5076 case 5: mb = MouseButton.wheelDown; break; // scroll down 5077 case 6: break; // scroll left... 5078 case 7: break; // scroll right... 5079 case 8: mb = MouseButton.backButton; break; 5080 case 9: mb = MouseButton.forwardButton; break; 5081 default: 5082 } 5083 if (mb) { 5084 try { onClick()(mb); } catch (Exception) {} 5085 if (onClickEx !is null) try { onClickEx(event.x_root, event.y_root, mb, cast(ModifierState)event.state); } catch (Exception) {} 5086 } 5087 } 5088 break; 5089 case EventType.EnterNotify: 5090 if (onEnter !is null) { 5091 onEnter(e.xcrossing.x_root, e.xcrossing.y_root, cast(ModifierState)e.xcrossing.state); 5092 } 5093 break; 5094 case EventType.LeaveNotify: 5095 if (onLeave !is null) try { onLeave(); } catch (Exception) {} 5096 break; 5097 case EventType.DestroyNotify: 5098 active = false; 5099 CapableOfHandlingNativeEvent.nativeHandleMapping.remove(nativeHandle); 5100 break; 5101 case EventType.ConfigureNotify: 5102 auto event = e.xconfigure; 5103 this.width = event.width; 5104 this.height = event.height; 5105 // writeln(width, " x " , height, " @ ", event.x, " ", event.y); 5106 redraw(); 5107 break; 5108 default: return 1; 5109 } 5110 return 1; 5111 }; 5112 } 5113 5114 /* private */ void hideBalloon() { 5115 balloon.close(); 5116 version(with_timer) 5117 timer.destroy(); 5118 balloon = null; 5119 version(with_timer) 5120 timer = null; 5121 } 5122 5123 void redraw() { 5124 if (!active) return; 5125 5126 auto display = XDisplayConnection.get; 5127 GC gc; 5128 5129 // from https://stackoverflow.com/questions/10492275/how-to-upload-32-bit-image-to-server-side-pixmap 5130 5131 int gc_depth(int depth, Display *dpy, Window root, GC *gc) { 5132 Visual *visual; 5133 XVisualInfo vis_info; 5134 XSetWindowAttributes win_attr; 5135 c_ulong win_mask; 5136 5137 if(!XMatchVisualInfo(dpy, 0, depth, 4 /*TrueColor*/, &vis_info)) { 5138 assert(0); 5139 // return 1; 5140 } 5141 5142 visual = vis_info.visual; 5143 5144 win_attr.colormap = XCreateColormap(dpy, root, visual, AllocNone); 5145 win_attr.background_pixel = 0; 5146 win_attr.border_pixel = 0; 5147 5148 win_mask = CWBackPixel | CWColormap | CWBorderPixel; 5149 5150 *gc = XCreateGC(dpy, nativeHandle, 0, null); 5151 5152 return 0; 5153 } 5154 5155 if(useAlpha) 5156 gc_depth(32, display, RootWindow(display, DefaultScreen(display)), &gc); 5157 else 5158 gc = DefaultGC(display, DefaultScreen(display)); 5159 5160 XClearWindow(display, nativeHandle); 5161 5162 if(!useAlpha && img !is null) 5163 XSetClipMask(display, gc, clippixmap); 5164 5165 /+ 5166 XSetForeground(display, gc, 5167 cast(uint) 0 << 16 | 5168 cast(uint) 0 << 8 | 5169 cast(uint) 0); 5170 XFillRectangle(display, nativeHandle, gc, 0, 0, width, height); 5171 +/ 5172 5173 if (img is null) { 5174 XSetForeground(display, gc, 5175 cast(uint) 0 << 16 | 5176 cast(uint) 127 << 8 | 5177 cast(uint) 0); 5178 XFillArc(display, nativeHandle, 5179 gc, width / 4, height / 4, width * 2 / 4, height * 2 / 4, 0 * 64, 360 * 64); 5180 } else { 5181 int dx = 0; 5182 int dy = 0; 5183 if(width > img.width) 5184 dx = (width - img.width) / 2; 5185 if(height > img.height) 5186 dy = (height - img.height) / 2; 5187 // writeln(img.width, " ", img.height, " vs ", width, " ", height); 5188 XSetClipOrigin(display, gc, dx, dy); 5189 5190 int max(int a, int b) { 5191 if(a > b) return a; else return b; 5192 } 5193 5194 if (img.usingXshm) 5195 XShmPutImage(display, cast(Drawable)nativeHandle, gc, img.handle, 0, 0, dx, dy, max(img.width, width), max(img.height, height), false); 5196 else 5197 XPutImage(display, cast(Drawable)nativeHandle, gc, img.handle, 0, 0, dx, dy, max(img.width, width), max(img.height, height)); 5198 } 5199 XSetClipMask(display, gc, None); 5200 flushGui(); 5201 } 5202 5203 static Window getTrayOwner() { 5204 auto display = XDisplayConnection.get; 5205 auto i = cast(int) DefaultScreen(display); 5206 if(i < 10 && i >= 0) { 5207 static Atom atom; 5208 if(atom == None) 5209 atom = XInternAtom(display, cast(char*) ("_NET_SYSTEM_TRAY_S"~(cast(char) (i + '0')) ~ '\0').ptr, false); 5210 return XGetSelectionOwner(display, atom); 5211 } 5212 return None; 5213 } 5214 5215 static void sendTrayMessage(arch_long message, arch_long d1, arch_long d2, arch_long d3) { 5216 auto to = getTrayOwner(); 5217 auto display = XDisplayConnection.get; 5218 XEvent ev; 5219 ev.xclient.type = EventType.ClientMessage; 5220 ev.xclient.window = to; 5221 ev.xclient.message_type = GetAtom!("_NET_SYSTEM_TRAY_OPCODE", true)(display); 5222 ev.xclient.format = 32; 5223 ev.xclient.data.l[0] = CurrentTime; 5224 ev.xclient.data.l[1] = message; 5225 ev.xclient.data.l[2] = d1; 5226 ev.xclient.data.l[3] = d2; 5227 ev.xclient.data.l[4] = d3; 5228 5229 XSendEvent(XDisplayConnection.get, to, false, EventMask.NoEventMask, &ev); 5230 } 5231 5232 private static NotificationAreaIcon[] activeIcons; 5233 5234 // FIXME: possible leak with this stuff, should be able to clear it and stuff. 5235 private void newManager() { 5236 close(); 5237 createXWin(); 5238 5239 if(this.clippixmap) 5240 XFreePixmap(XDisplayConnection.get, clippixmap); 5241 if(this.originalMemoryImage) 5242 this.icon = this.originalMemoryImage; 5243 else if(this.img) 5244 this.icon = this.img; 5245 } 5246 5247 private bool useAlpha = false; 5248 5249 private void createXWin () { 5250 // create window 5251 auto display = XDisplayConnection.get; 5252 5253 // to check for MANAGER on root window to catch new/changed tray owners 5254 XDisplayConnection.addRootInput(EventMask.StructureNotifyMask); 5255 // so if a thing does appear, we can handle it 5256 foreach(ai; activeIcons) 5257 if(ai is this) 5258 goto alreadythere; 5259 activeIcons ~= this; 5260 alreadythere: 5261 5262 // and check for an existing tray 5263 auto trayOwner = getTrayOwner(); 5264 if(trayOwner == None) 5265 return; 5266 //throw new Exception("No notification area found"); 5267 5268 Visual* v = cast(Visual*) CopyFromParent; 5269 5270 // GNOME's default is 22x22 and KDE assumes all icons are going to match that then bitmap scales 5271 // from there. It is ugly and stupid but this gives the fewest artifacts. Good environments will send 5272 // a resize event later. 5273 width = 22; 5274 height = 22; 5275 5276 // if they system gave us a 32 bit visual we need to switch to it too 5277 int depth = 24; 5278 5279 auto visualProp = getX11PropertyData(trayOwner, GetAtom!("_NET_SYSTEM_TRAY_VISUAL", true)(display)); 5280 if(visualProp !is null) { 5281 c_ulong[] info = cast(c_ulong[]) visualProp; 5282 if(info.length == 1) { 5283 auto vid = info[0]; 5284 int returned; 5285 XVisualInfo t; 5286 t.visualid = vid; 5287 auto got = XGetVisualInfo(display, VisualIDMask, &t, &returned); 5288 if(got !is null) { 5289 if(returned == 1) { 5290 v = got.visual; 5291 depth = got.depth; 5292 // writeln("using special visual ", got.depth); 5293 // writeln(depth); 5294 } 5295 XFree(got); 5296 } 5297 } 5298 } 5299 5300 int CWFlags = CWBackPixel | CWBorderPixel | CWOverrideRedirect; 5301 XSetWindowAttributes attr; 5302 attr.background_pixel = 0; 5303 attr.border_pixel = 0; 5304 attr.override_redirect = 0; 5305 if(v !is cast(Visual*) CopyFromParent) { 5306 attr.colormap = XCreateColormap(display, RootWindow(display, DefaultScreen(display)), v, AllocNone); 5307 CWFlags |= CWColormap; 5308 if(depth == 32) 5309 useAlpha = true; 5310 else 5311 goto plain; 5312 } else { 5313 plain: 5314 attr.background_pixmap = 1 /* ParentRelative */; 5315 CWFlags |= CWBackPixmap; 5316 } 5317 5318 auto nativeWindow = XCreateWindow(display, RootWindow(display, DefaultScreen(display)), 0, 0, width, height, 0, depth, InputOutput, v, CWFlags, &attr); 5319 5320 assert(nativeWindow); 5321 5322 if(!useAlpha) 5323 XSetWindowBackgroundPixmap(display, nativeWindow, 1 /* ParentRelative */); 5324 5325 nativeHandle = nativeWindow; 5326 5327 ///+ 5328 arch_ulong[2] info; 5329 info[0] = 0; 5330 info[1] = 1; 5331 5332 string title = this.name is null ? "simpledisplay.d program" : this.name; 5333 auto XA_UTF8 = XInternAtom(display, "UTF8_STRING".ptr, false); 5334 auto XA_NETWM_NAME = XInternAtom(display, "_NET_WM_NAME".ptr, false); 5335 XChangeProperty(display, nativeWindow, XA_NETWM_NAME, XA_UTF8, 8, PropModeReplace, title.ptr, cast(uint)title.length); 5336 5337 XChangeProperty( 5338 display, 5339 nativeWindow, 5340 GetAtom!("_XEMBED_INFO", true)(display), 5341 GetAtom!("_XEMBED_INFO", true)(display), 5342 32 /* bits */, 5343 0 /*PropModeReplace*/, 5344 info.ptr, 5345 2); 5346 5347 import core.sys.posix.unistd; 5348 arch_ulong pid = getpid(); 5349 5350 XChangeProperty( 5351 display, 5352 nativeWindow, 5353 GetAtom!("_NET_WM_PID", true)(display), 5354 XA_CARDINAL, 5355 32 /* bits */, 5356 0 /*PropModeReplace*/, 5357 &pid, 5358 1); 5359 5360 updateNetWmIcon(); 5361 5362 if (sdpyWindowClassStr !is null && sdpyWindowClassStr[0]) { 5363 //{ import core.stdc.stdio; printf("winclass: [%s]\n", sdpyWindowClassStr); } 5364 XClassHint klass; 5365 XWMHints wh; 5366 XSizeHints size; 5367 klass.res_name = sdpyWindowClassStr; 5368 klass.res_class = sdpyWindowClassStr; 5369 XSetWMProperties(display, nativeWindow, null, null, null, 0, &size, &wh, &klass); 5370 } 5371 5372 // believe it or not, THIS is what xfce needed for the 9999 issue 5373 XSizeHints sh; 5374 c_long spr; 5375 XGetWMNormalHints(display, nativeWindow, &sh, &spr); 5376 sh.flags |= PMaxSize | PMinSize; 5377 // FIXME maybe nicer resizing 5378 sh.min_width = 16; 5379 sh.min_height = 16; 5380 sh.max_width = 22; 5381 sh.max_height = 22; 5382 XSetWMNormalHints(display, nativeWindow, &sh); 5383 5384 5385 //+/ 5386 5387 5388 XSelectInput(display, nativeWindow, 5389 EventMask.ButtonPressMask | EventMask.ExposureMask | EventMask.StructureNotifyMask | EventMask.VisibilityChangeMask | 5390 EventMask.EnterWindowMask | EventMask.LeaveWindowMask); 5391 5392 sendTrayMessage(SYSTEM_TRAY_REQUEST_DOCK, nativeWindow, 0, 0); 5393 // XMapWindow(display, nativeWindow); // to demo it w/o a tray 5394 5395 CapableOfHandlingNativeEvent.nativeHandleMapping[nativeWindow] = this; 5396 active = true; 5397 } 5398 5399 void updateNetWmIcon() { 5400 if(img is null) return; 5401 auto display = XDisplayConnection.get; 5402 // FIXME: ensure this is correct 5403 arch_ulong[] buffer; 5404 auto imgMi = img.toTrueColorImage; 5405 buffer ~= imgMi.width; 5406 buffer ~= imgMi.height; 5407 foreach(c; imgMi.imageData.colors) { 5408 arch_ulong b; 5409 b |= c.a << 24; 5410 b |= c.r << 16; 5411 b |= c.g << 8; 5412 b |= c.b; 5413 buffer ~= b; 5414 } 5415 5416 XChangeProperty( 5417 display, 5418 nativeHandle, 5419 GetAtom!"_NET_WM_ICON"(display), 5420 GetAtom!"CARDINAL"(display), 5421 32 /* bits */, 5422 0 /*PropModeReplace*/, 5423 buffer.ptr, 5424 cast(int) buffer.length); 5425 } 5426 5427 5428 5429 private SimpleWindow balloon; 5430 version(with_timer) 5431 private Timer timer; 5432 5433 private Window nativeHandle; 5434 private Pixmap clippixmap = None; 5435 private int width = 16; 5436 private int height = 16; 5437 private bool active = false; 5438 5439 void delegate (int x, int y, MouseButton button, ModifierState mods) onClickEx; /// x and y are globals (relative to root window). X11 only. 5440 void delegate (int x, int y, ModifierState mods) onEnter; /// x and y are global window coordinates. X11 only. 5441 void delegate () onLeave; /// X11 only. 5442 5443 @property bool closed () const pure nothrow @safe @nogc { return !active; } /// 5444 5445 /// X11 only. Get global window coordinates and size. This can be used to show various notifications. 5446 void getWindowRect (out int x, out int y, out int width, out int height) { 5447 if (!active) { width = 1; height = 1; return; } // 1: just in case 5448 Window dummyw; 5449 auto dpy = XDisplayConnection.get; 5450 //XWindowAttributes xwa; 5451 //XGetWindowAttributes(dpy, nativeHandle, &xwa); 5452 //XTranslateCoordinates(dpy, nativeHandle, RootWindow(dpy, DefaultScreen(dpy)), xwa.x, xwa.y, &x, &y, &dummyw); 5453 XTranslateCoordinates(dpy, nativeHandle, RootWindow(dpy, DefaultScreen(dpy)), x, y, &x, &y, &dummyw); 5454 width = this.width; 5455 height = this.height; 5456 } 5457 } 5458 5459 /+ 5460 What I actually want from this: 5461 5462 * set / change: icon, tooltip 5463 * handle: mouse click, right click 5464 * show: notification bubble. 5465 +/ 5466 5467 version(Windows) { 5468 WindowsIcon win32Icon; 5469 HWND hwnd; 5470 5471 NOTIFYICONDATAW data; 5472 5473 NativeEventHandler getNativeEventHandler() { 5474 return delegate int(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam, out int mustReturn) { 5475 if(msg == WM_USER) { 5476 auto event = LOWORD(lParam); 5477 auto iconId = HIWORD(lParam); 5478 //auto x = GET_X_LPARAM(wParam); 5479 //auto y = GET_Y_LPARAM(wParam); 5480 switch(event) { 5481 case WM_LBUTTONDOWN: 5482 onClick()(MouseButton.left); 5483 break; 5484 case WM_RBUTTONDOWN: 5485 onClick()(MouseButton.right); 5486 break; 5487 case WM_MBUTTONDOWN: 5488 onClick()(MouseButton.middle); 5489 break; 5490 case WM_MOUSEMOVE: 5491 // sent, we could use it. 5492 break; 5493 case WM_MOUSEWHEEL: 5494 // NOT SENT 5495 break; 5496 //case NIN_KEYSELECT: 5497 //case NIN_SELECT: 5498 //break; 5499 default: {} 5500 } 5501 } 5502 return 0; 5503 }; 5504 } 5505 5506 enum NIF_SHOWTIP = 0x00000080; 5507 5508 private static struct NOTIFYICONDATAW { 5509 DWORD cbSize; 5510 HWND hWnd; 5511 UINT uID; 5512 UINT uFlags; 5513 UINT uCallbackMessage; 5514 HICON hIcon; 5515 WCHAR[128] szTip; 5516 DWORD dwState; 5517 DWORD dwStateMask; 5518 WCHAR[256] szInfo; 5519 union { 5520 UINT uTimeout; 5521 UINT uVersion; 5522 } 5523 WCHAR[64] szInfoTitle; 5524 DWORD dwInfoFlags; 5525 GUID guidItem; 5526 HICON hBalloonIcon; 5527 } 5528 5529 } 5530 5531 /++ 5532 Note that on Windows, only left, right, and middle buttons are sent. 5533 Mouse wheel buttons are NOT set, so don't rely on those events if your 5534 program is meant to be used on Windows too. 5535 +/ 5536 this(string name, MemoryImage icon, void delegate(MouseButton button) onClick) { 5537 // The canonical constructor for Windows needs the MemoryImage, so it is here, 5538 // but on X, we need an Image, so its canonical ctor is there. They should 5539 // forward to each other though. 5540 version(X11) { 5541 this.name = name; 5542 this.onClick = onClick; 5543 createXWin(); 5544 this.icon = icon; 5545 } else version(Windows) { 5546 this.onClick = onClick; 5547 this.win32Icon = new WindowsIcon(icon); 5548 5549 HINSTANCE hInstance = cast(HINSTANCE) GetModuleHandle(null); 5550 5551 static bool registered = false; 5552 if(!registered) { 5553 WNDCLASSEX wc; 5554 wc.cbSize = wc.sizeof; 5555 wc.hInstance = hInstance; 5556 wc.lpfnWndProc = &WndProc; 5557 wc.lpszClassName = "arsd_simpledisplay_notification_icon"w.ptr; 5558 if(!RegisterClassExW(&wc)) 5559 throw new WindowsApiException("RegisterClass", GetLastError()); 5560 registered = true; 5561 } 5562 5563 this.hwnd = CreateWindowW("arsd_simpledisplay_notification_icon"w.ptr, "test"w.ptr /* name */, 0 /* dwStyle */, 0, 0, 0, 0, HWND_MESSAGE, null, hInstance, null); 5564 if(hwnd is null) 5565 throw new WindowsApiException("CreateWindow", GetLastError()); 5566 5567 data.cbSize = data.sizeof; 5568 data.hWnd = hwnd; 5569 data.uID = cast(uint) cast(void*) this; 5570 data.uFlags = NIF_MESSAGE | NIF_ICON | NIF_TIP | NIF_STATE | NIF_SHOWTIP /* use default tooltip, for now. */; 5571 // NIF_INFO means show balloon 5572 data.uCallbackMessage = WM_USER; 5573 data.hIcon = this.win32Icon.hIcon; 5574 data.szTip = ""; // FIXME 5575 data.dwState = 0; // NIS_HIDDEN; // windows vista 5576 data.dwStateMask = NIS_HIDDEN; // windows vista 5577 5578 data.uVersion = 4; // NOTIFYICON_VERSION_4; // Windows Vista and up 5579 5580 5581 Shell_NotifyIcon(NIM_ADD, cast(NOTIFYICONDATA*) &data); 5582 5583 CapableOfHandlingNativeEvent.nativeHandleMapping[this.hwnd] = this; 5584 } else version(OSXCocoa) { 5585 throw new NotYetImplementedException(); 5586 } else static assert(0); 5587 } 5588 5589 /// ditto 5590 this(string name, Image icon, void delegate(MouseButton button) onClick) { 5591 version(X11) { 5592 this.onClick = onClick; 5593 this.name = name; 5594 createXWin(); 5595 this.icon = icon; 5596 } else version(Windows) { 5597 this(name, icon is null ? null : icon.toTrueColorImage(), onClick); 5598 } else version(OSXCocoa) { 5599 throw new NotYetImplementedException(); 5600 } else static assert(0); 5601 } 5602 5603 version(X11) { 5604 /++ 5605 X-specific extension (for now at least) 5606 +/ 5607 this(string name, MemoryImage icon, void delegate(int x, int y, MouseButton button, ModifierState mods) onClickEx) { 5608 this.onClickEx = onClickEx; 5609 createXWin(); 5610 if (icon !is null) this.icon = icon; 5611 } 5612 5613 /// ditto 5614 this(string name, Image icon, void delegate(int x, int y, MouseButton button, ModifierState mods) onClickEx) { 5615 this.onClickEx = onClickEx; 5616 createXWin(); 5617 this.icon = icon; 5618 } 5619 } 5620 5621 private void delegate (MouseButton button) onClick_; 5622 5623 /// 5624 @property final void delegate(MouseButton) onClick() { 5625 if(onClick_ is null) 5626 onClick_ = delegate void(MouseButton) {}; 5627 return onClick_; 5628 } 5629 5630 /// ditto 5631 @property final void onClick(void delegate(MouseButton) handler) { 5632 // I made this a property setter so we can wrap smaller arg 5633 // delegates and just forward all to onClickEx or something. 5634 onClick_ = handler; 5635 } 5636 5637 5638 string name_; 5639 @property void name(string n) { 5640 name_ = n; 5641 } 5642 5643 @property string name() { 5644 return name_; 5645 } 5646 5647 private MemoryImage originalMemoryImage; 5648 5649 /// 5650 @property void icon(MemoryImage i) { 5651 version(X11) { 5652 this.originalMemoryImage = i; 5653 if (!active) return; 5654 if (i !is null) { 5655 this.img = Image.fromMemoryImage(i, useAlpha, false); 5656 if(!useAlpha) 5657 this.clippixmap = transparencyMaskFromMemoryImage(i, nativeHandle); 5658 // writeln("using pixmap ", clippixmap); 5659 updateNetWmIcon(); 5660 redraw(); 5661 } else { 5662 if (this.img !is null) { 5663 this.img = null; 5664 redraw(); 5665 } 5666 } 5667 } else version(Windows) { 5668 this.win32Icon = new WindowsIcon(i); 5669 5670 data.uFlags = NIF_ICON; 5671 data.hIcon = this.win32Icon.hIcon; 5672 5673 Shell_NotifyIcon(NIM_MODIFY, cast(NOTIFYICONDATA*) &data); 5674 } else version(OSXCocoa) { 5675 throw new NotYetImplementedException(); 5676 } else static assert(0); 5677 } 5678 5679 /// ditto 5680 @property void icon (Image i) { 5681 version(X11) { 5682 if (!active) return; 5683 if (i !is img) { 5684 originalMemoryImage = null; 5685 img = i; 5686 redraw(); 5687 } 5688 } else version(Windows) { 5689 this.icon(i is null ? null : i.toTrueColorImage()); 5690 } else version(OSXCocoa) { 5691 throw new NotYetImplementedException(); 5692 } else static assert(0); 5693 } 5694 5695 /++ 5696 Shows a balloon notification. You can only show one balloon at a time, if you call 5697 it twice while one is already up, the first balloon will be replaced. 5698 5699 5700 The user is free to block notifications and they will automatically disappear after 5701 a timeout period. 5702 5703 Params: 5704 title = Title of the notification. Must be 40 chars or less or the OS may truncate it. 5705 message = The message to pop up. Must be 220 chars or less or the OS may truncate it. 5706 icon = the icon to display with the notification. If null, it uses your existing icon. 5707 onclick = delegate called if the user clicks the balloon. (not yet implemented) 5708 timeout = your suggested timeout period. The operating system is free to ignore your suggestion. 5709 +/ 5710 void showBalloon(string title, string message, MemoryImage icon = null, void delegate() onclick = null, int timeout = 2_500) { 5711 bool useCustom = true; 5712 version(libnotify) { 5713 if(onclick is null) // libnotify impl doesn't support callbacks yet because it doesn't do a dbus message loop 5714 try { 5715 if(!active) return; 5716 5717 if(libnotify is null) { 5718 libnotify = new C_DynamicLibrary("libnotify.so"); 5719 libnotify.call!("notify_init", int, const char*)()((ApplicationName ~ "\0").ptr); 5720 } 5721 5722 auto n = libnotify.call!("notify_notification_new", void*, const char*, const char*, const char*)()((title~"\0").ptr, (message~"\0").ptr, null /* icon */); 5723 5724 libnotify.call!("notify_notification_set_timeout", void, void*, int)()(n, timeout); 5725 5726 if(onclick) { 5727 libnotify_action_delegates[libnotify_action_delegates_count] = onclick; 5728 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); 5729 libnotify_action_delegates_count++; 5730 } 5731 5732 // FIXME icon 5733 5734 // set hint image-data 5735 // set default action for onclick 5736 5737 void* error; 5738 libnotify.call!("notify_notification_show", bool, void*, void**)()(n, &error); 5739 5740 useCustom = false; 5741 } catch(Exception e) { 5742 5743 } 5744 } 5745 5746 version(X11) { 5747 if(useCustom) { 5748 if(!active) return; 5749 if(balloon) { 5750 hideBalloon(); 5751 } 5752 // I know there are two specs for this, but one is never 5753 // implemented by any window manager I have ever seen, and 5754 // the other is a bloated mess and too complicated for simpledisplay... 5755 // so doing my own little window instead. 5756 balloon = new SimpleWindow(380, 120, null, OpenGlOptions.no, Resizability.fixedSize, WindowTypes.notification, WindowFlags.dontAutoShow/*, window*/); 5757 5758 int x, y, width, height; 5759 getWindowRect(x, y, width, height); 5760 5761 int bx = x - balloon.width; 5762 int by = y - balloon.height; 5763 if(bx < 0) 5764 bx = x + width + balloon.width; 5765 if(by < 0) 5766 by = y + height; 5767 5768 // just in case, make sure it is actually on scren 5769 if(bx < 0) 5770 bx = 0; 5771 if(by < 0) 5772 by = 0; 5773 5774 balloon.move(bx, by); 5775 auto painter = balloon.draw(); 5776 painter.fillColor = Color(220, 220, 220); 5777 painter.outlineColor = Color.black; 5778 painter.drawRectangle(Point(0, 0), balloon.width, balloon.height); 5779 auto iconWidth = icon is null ? 0 : icon.width; 5780 if(icon) 5781 painter.drawImage(Point(4, 4), Image.fromMemoryImage(icon)); 5782 iconWidth += 6; // margin around the icon 5783 5784 // draw a close button 5785 painter.outlineColor = Color(44, 44, 44); 5786 painter.fillColor = Color(255, 255, 255); 5787 painter.drawRectangle(Point(balloon.width - 15, 3), 13, 13); 5788 painter.pen = Pen(Color.black, 3); 5789 painter.drawLine(Point(balloon.width - 14, 4), Point(balloon.width - 4, 14)); 5790 painter.drawLine(Point(balloon.width - 4, 4), Point(balloon.width - 14, 13)); 5791 painter.pen = Pen(Color.black, 1); 5792 painter.fillColor = Color(220, 220, 220); 5793 5794 // Draw the title and message 5795 painter.drawText(Point(4 + iconWidth, 4), title); 5796 painter.drawLine( 5797 Point(4 + iconWidth, 4 + painter.fontHeight + 1), 5798 Point(balloon.width - 4, 4 + painter.fontHeight + 1), 5799 ); 5800 painter.drawText(Point(4 + iconWidth, 4 + painter.fontHeight + 4), message); 5801 5802 balloon.setEventHandlers( 5803 (MouseEvent ev) { 5804 if(ev.type == MouseEventType.buttonPressed) { 5805 if(ev.x > balloon.width - 16 && ev.y < 16) 5806 hideBalloon(); 5807 else if(onclick) 5808 onclick(); 5809 } 5810 } 5811 ); 5812 balloon.show(); 5813 5814 version(with_timer) 5815 timer = new Timer(timeout, &hideBalloon); 5816 else {} // FIXME 5817 } 5818 } else version(Windows) { 5819 enum NIF_INFO = 0x00000010; 5820 5821 data.uFlags = NIF_INFO; 5822 5823 // FIXME: go back to the last valid unicode code point 5824 if(title.length > 40) 5825 title = title[0 .. 40]; 5826 if(message.length > 220) 5827 message = message[0 .. 220]; 5828 5829 enum NIIF_RESPECT_QUIET_TIME = 0x00000080; 5830 enum NIIF_LARGE_ICON = 0x00000020; 5831 enum NIIF_NOSOUND = 0x00000010; 5832 enum NIIF_USER = 0x00000004; 5833 enum NIIF_ERROR = 0x00000003; 5834 enum NIIF_WARNING = 0x00000002; 5835 enum NIIF_INFO = 0x00000001; 5836 enum NIIF_NONE = 0; 5837 5838 WCharzBuffer t = WCharzBuffer(title); 5839 WCharzBuffer m = WCharzBuffer(message); 5840 5841 t.copyInto(data.szInfoTitle); 5842 m.copyInto(data.szInfo); 5843 data.dwInfoFlags = NIIF_RESPECT_QUIET_TIME; 5844 5845 if(icon !is null) { 5846 auto i = new WindowsIcon(icon); 5847 data.hBalloonIcon = i.hIcon; 5848 data.dwInfoFlags |= NIIF_USER; 5849 } 5850 5851 Shell_NotifyIcon(NIM_MODIFY, cast(NOTIFYICONDATA*) &data); 5852 } else version(OSXCocoa) { 5853 throw new NotYetImplementedException(); 5854 } else static assert(0); 5855 } 5856 5857 /// 5858 //version(Windows) 5859 void show() { 5860 version(X11) { 5861 if(!hidden) 5862 return; 5863 sendTrayMessage(SYSTEM_TRAY_REQUEST_DOCK, nativeHandle, 0, 0); 5864 hidden = false; 5865 } else version(Windows) { 5866 data.uFlags = NIF_STATE; 5867 data.dwState = 0; // NIS_HIDDEN; // windows vista 5868 data.dwStateMask = NIS_HIDDEN; // windows vista 5869 Shell_NotifyIcon(NIM_MODIFY, cast(NOTIFYICONDATA*) &data); 5870 } else version(OSXCocoa) { 5871 throw new NotYetImplementedException(); 5872 } else static assert(0); 5873 } 5874 5875 version(X11) 5876 bool hidden = false; 5877 5878 /// 5879 //version(Windows) 5880 void hide() { 5881 version(X11) { 5882 if(hidden) 5883 return; 5884 hidden = true; 5885 XUnmapWindow(XDisplayConnection.get, nativeHandle); 5886 } else version(Windows) { 5887 data.uFlags = NIF_STATE; 5888 data.dwState = NIS_HIDDEN; // windows vista 5889 data.dwStateMask = NIS_HIDDEN; // windows vista 5890 Shell_NotifyIcon(NIM_MODIFY, cast(NOTIFYICONDATA*) &data); 5891 } else version(OSXCocoa) { 5892 throw new NotYetImplementedException(); 5893 } else static assert(0); 5894 } 5895 5896 /// 5897 void close () { 5898 version(X11) { 5899 if (active) { 5900 active = false; // event handler will set this too, but meh 5901 XUnmapWindow(XDisplayConnection.get, nativeHandle); // 'cause why not; let's be polite 5902 XDestroyWindow(XDisplayConnection.get, nativeHandle); 5903 flushGui(); 5904 } 5905 } else version(Windows) { 5906 Shell_NotifyIcon(NIM_DELETE, cast(NOTIFYICONDATA*) &data); 5907 } else version(OSXCocoa) { 5908 throw new NotYetImplementedException(); 5909 } else static assert(0); 5910 } 5911 5912 ~this() { 5913 if(!thisIsGuiThread) return; // FIXME: leaks if multithreaded gc 5914 version(X11) 5915 if(clippixmap != None) 5916 XFreePixmap(XDisplayConnection.get, clippixmap); 5917 close(); 5918 } 5919 } 5920 5921 version(X11) 5922 /// Call `XFreePixmap` on the return value. 5923 Pixmap transparencyMaskFromMemoryImage(MemoryImage i, Window window) { 5924 char[] data = new char[](i.width * i.height / 8 + 2); 5925 data[] = 0; 5926 5927 int bitOffset = 0; 5928 foreach(c; i.getAsTrueColorImage().imageData.colors) { // FIXME inefficient unnecessary conversion in palette cases 5929 ubyte v = c.a > 128 ? 1 : 0; 5930 data[bitOffset / 8] |= v << (bitOffset%8); 5931 bitOffset++; 5932 } 5933 auto handle = XCreateBitmapFromData(XDisplayConnection.get, cast(Drawable) window, data.ptr, i.width, i.height); 5934 return handle; 5935 } 5936 5937 5938 // basic functions to make timers 5939 /** 5940 A timer that will trigger your function on a given interval. 5941 5942 5943 You create a timer with an interval and a callback. It will continue 5944 to fire on the interval until it is destroyed. 5945 5946 There are currently no one-off timers (instead, just create one and 5947 destroy it when it is triggered) nor are there pause/resume functions - 5948 the timer must again be destroyed and recreated if you want to pause it. 5949 5950 --- 5951 auto timer = new Timer(50, { it happened!; }); 5952 timer.destroy(); 5953 --- 5954 5955 Timers can only be expected to fire when the event loop is running and only 5956 once per iteration through the event loop. 5957 5958 History: 5959 Prior to December 9, 2020, a timer pulse set too high with a handler too 5960 slow could lock up the event loop. It now guarantees other things will 5961 get a chance to run between timer calls, even if that means not keeping up 5962 with the requested interval. 5963 */ 5964 version(with_timer) { 5965 version(use_arsd_core) 5966 alias Timer = arsd.core.Timer; // FIXME should probably wrap it for a stable api 5967 else 5968 class Timer { 5969 // FIXME: needs pause and unpause 5970 // FIXME: I might add overloads for ones that take a count of 5971 // how many elapsed since last time (on Windows, it will divide 5972 // the ticks thing given, on Linux it is just available) and 5973 // maybe one that takes an instance of the Timer itself too 5974 /// Create a timer with a callback when it triggers. 5975 this(int intervalInMilliseconds, void delegate() onPulse) @trusted { 5976 assert(onPulse !is null); 5977 5978 this.intervalInMilliseconds = intervalInMilliseconds; 5979 this.onPulse = onPulse; 5980 5981 version(Windows) { 5982 /* 5983 handle = SetTimer(null, handle, intervalInMilliseconds, &timerCallback); 5984 if(handle == 0) 5985 throw new WindowsApiException("SetTimer", GetLastError()); 5986 */ 5987 5988 // thanks to Archival 998 for the WaitableTimer blocks 5989 handle = CreateWaitableTimer(null, false, null); 5990 long initialTime = -intervalInMilliseconds; 5991 if(handle is null || !SetWaitableTimer(handle, cast(LARGE_INTEGER*)&initialTime, intervalInMilliseconds, &timerCallback, handle, false)) 5992 throw new WindowsApiException("SetWaitableTimer", GetLastError()); 5993 5994 mapping[handle] = this; 5995 5996 } else version(Emscripten) { 5997 } else version(linux) { 5998 static import ep = core.sys.linux.epoll; 5999 6000 import core.sys.linux.timerfd; 6001 6002 fd = timerfd_create(CLOCK_MONOTONIC, 0); 6003 if(fd == -1) 6004 throw new Exception("timer create failed"); 6005 6006 mapping[fd] = this; 6007 6008 itimerspec value = makeItimerspec(intervalInMilliseconds); 6009 6010 if(timerfd_settime(fd, 0, &value, null) == -1) 6011 throw new Exception("couldn't make pulse timer"); 6012 6013 version(with_eventloop) { 6014 import arsd.eventloop; 6015 addFileEventListeners(fd, &trigger, null, null); 6016 } else { 6017 prepareEventLoop(); 6018 6019 ep.epoll_event ev = void; 6020 ev.events = ep.EPOLLIN; 6021 ev.data.fd = fd; 6022 ep.epoll_ctl(epollFd, ep.EPOLL_CTL_ADD, fd, &ev); 6023 } 6024 } else featureNotImplemented(); 6025 } 6026 6027 private int intervalInMilliseconds; 6028 6029 // just cuz I sometimes call it this. 6030 alias dispose = destroy; 6031 6032 /// Stop and destroy the timer object. 6033 void destroy() { 6034 version(Windows) { 6035 staticDestroy(handle); 6036 handle = null; 6037 } else version(linux) { 6038 staticDestroy(fd); 6039 fd = -1; 6040 } else featureNotImplemented(); 6041 } 6042 6043 version(Windows) 6044 static void staticDestroy(HANDLE handle) { 6045 if(handle) { 6046 // KillTimer(null, handle); 6047 CancelWaitableTimer(cast(void*)handle); 6048 mapping.remove(handle); 6049 CloseHandle(handle); 6050 } 6051 } 6052 else version(Emscripten) 6053 static void staticDestroy(int fd) @system { 6054 assert(0); 6055 } 6056 else version(linux) 6057 static void staticDestroy(int fd) @system { 6058 if(fd != -1) { 6059 import unix = core.sys.posix.unistd; 6060 static import ep = core.sys.linux.epoll; 6061 6062 version(with_eventloop) { 6063 import arsd.eventloop; 6064 removeFileEventListeners(fd); 6065 } else { 6066 ep.epoll_event ev = void; 6067 ev.events = ep.EPOLLIN; 6068 ev.data.fd = fd; 6069 6070 ep.epoll_ctl(epollFd, ep.EPOLL_CTL_DEL, fd, &ev); 6071 } 6072 unix.close(fd); 6073 mapping.remove(fd); 6074 } 6075 } 6076 6077 ~this() { 6078 version(Windows) { if(handle) 6079 cleanupQueue.queue!staticDestroy(handle); 6080 } else version(linux) { if(fd != -1) 6081 cleanupQueue.queue!staticDestroy(fd); 6082 } 6083 } 6084 6085 void changeTime(int intervalInMilliseconds) 6086 { 6087 this.intervalInMilliseconds = intervalInMilliseconds; 6088 version(Windows) 6089 { 6090 if(handle) 6091 { 6092 //handle = SetTimer(null, handle, intervalInMilliseconds, &timerCallback); 6093 long initialTime = -intervalInMilliseconds; 6094 if(handle is null || !SetWaitableTimer(handle, cast(LARGE_INTEGER*)&initialTime, intervalInMilliseconds, &timerCallback, handle, false)) 6095 throw new WindowsApiException("couldn't change pulse timer", GetLastError()); 6096 } 6097 } else version(linux) { 6098 import core.sys.linux.timerfd; 6099 6100 itimerspec value = makeItimerspec(intervalInMilliseconds); 6101 if(timerfd_settime(fd, 0, &value, null) == -1) { 6102 throw new Exception("couldn't change pulse timer"); 6103 } 6104 } else { 6105 assert(false, "Timer.changeTime(int) is not implemented for this platform"); 6106 } 6107 } 6108 6109 6110 private: 6111 6112 void delegate() onPulse; 6113 6114 int lastEventLoopRoundTriggered; 6115 6116 version(linux) { 6117 static auto makeItimerspec(int intervalInMilliseconds) { 6118 import core.sys.linux.timerfd; 6119 6120 itimerspec value; 6121 value.it_value.tv_sec = cast(int) (intervalInMilliseconds / 1000); 6122 value.it_value.tv_nsec = (intervalInMilliseconds % 1000) * 1000_000; 6123 6124 value.it_interval.tv_sec = cast(int) (intervalInMilliseconds / 1000); 6125 value.it_interval.tv_nsec = (intervalInMilliseconds % 1000) * 1000_000; 6126 6127 return value; 6128 } 6129 } 6130 6131 void trigger() { 6132 version(linux) { 6133 import unix = core.sys.posix.unistd; 6134 long val; 6135 unix.read(fd, &val, val.sizeof); // gotta clear the pipe 6136 } else version(Windows) { 6137 if(this.lastEventLoopRoundTriggered == eventLoopRound) 6138 return; // never try to actually run faster than the event loop 6139 lastEventLoopRoundTriggered = eventLoopRound; 6140 } else featureNotImplemented(); 6141 6142 onPulse(); 6143 } 6144 6145 version(Windows) 6146 void rearm() { 6147 6148 } 6149 6150 version(Windows) 6151 extern(Windows) 6152 //static void timerCallback(HWND, UINT, UINT_PTR timer, DWORD dwTime) nothrow { 6153 static void timerCallback(HANDLE timer, DWORD lowTime, DWORD hiTime) nothrow { 6154 if(Timer* t = timer in mapping) { 6155 try 6156 (*t).trigger(); 6157 catch(Exception e) { sdpy_abort(e); assert(0); } 6158 } 6159 } 6160 6161 version(Windows) { 6162 //UINT_PTR handle; 6163 //static Timer[UINT_PTR] mapping; 6164 HANDLE handle; 6165 __gshared Timer[HANDLE] mapping; 6166 } else version(linux) { 6167 int fd = -1; 6168 __gshared Timer[int] mapping; 6169 } else version(OSXCocoa) { 6170 } else static assert(0, "timer not supported"); 6171 } 6172 } 6173 6174 version(Windows) 6175 private int eventLoopRound; 6176 6177 version(Windows) 6178 /// 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 6179 class WindowsHandleReader { 6180 /// 6181 this(void delegate() onReady, HANDLE handle) { 6182 this.onReady = onReady; 6183 this.handle = handle; 6184 6185 mapping[handle] = this; 6186 6187 enable(); 6188 } 6189 6190 version(use_arsd_core) 6191 ICoreEventLoop.UnregisterToken unregisterToken; 6192 6193 /// 6194 void enable() { 6195 version(use_arsd_core) { 6196 unregisterToken = getThisThreadEventLoop(EventLoopType.Ui).addCallbackOnHandleReady(handle, new CallbackHelper(&ready)); 6197 } else { 6198 auto el = EventLoop.get().impl; 6199 el.handles ~= handle; 6200 } 6201 } 6202 6203 /// 6204 void disable() { 6205 version(use_arsd_core) { 6206 unregisterToken.unregister(); 6207 } else { 6208 auto el = EventLoop.get().impl; 6209 for(int i = 0; i < el.handles.length; i++) { 6210 if(el.handles[i] is handle) { 6211 el.handles[i] = el.handles[$-1]; 6212 el.handles = el.handles[0 .. $-1]; 6213 return; 6214 } 6215 } 6216 } 6217 } 6218 6219 void dispose() { 6220 disable(); 6221 if(handle) 6222 mapping.remove(handle); 6223 handle = null; 6224 } 6225 6226 void ready() { 6227 if(onReady) 6228 onReady(); 6229 } 6230 6231 HANDLE handle; 6232 void delegate() onReady; 6233 6234 __gshared WindowsHandleReader[HANDLE] mapping; 6235 } 6236 6237 version(Posix) 6238 /// Lets you add files to the event loop for reading. Use at your own risk. 6239 class PosixFdReader { 6240 /// 6241 this(void delegate() onReady, int fd, bool captureReads = true, bool captureWrites = false) { 6242 this((int, bool, bool) { onReady(); }, fd, captureReads, captureWrites); 6243 } 6244 6245 /// 6246 this(void delegate(int) onReady, int fd, bool captureReads = true, bool captureWrites = false) { 6247 this((int fd, bool, bool) { onReady(fd); }, fd, captureReads, captureWrites); 6248 } 6249 6250 /// 6251 this(void delegate(int fd, bool read, bool write) onReady, int fd, bool captureReads = true, bool captureWrites = false) { 6252 this.onReady = onReady; 6253 this.fd = fd; 6254 this.captureWrites = captureWrites; 6255 this.captureReads = captureReads; 6256 6257 mapping[fd] = this; 6258 6259 version(with_eventloop) { 6260 import arsd.eventloop; 6261 addFileEventListeners(fd, &readyel); 6262 } else { 6263 enable(); 6264 } 6265 } 6266 6267 bool captureReads; 6268 bool captureWrites; 6269 6270 version(use_arsd_core) { 6271 import arsd.core; 6272 ICoreEventLoop.UnregisterToken unregisterToken; 6273 } 6274 6275 version(with_eventloop) {} else 6276 /// 6277 void enable() @system { 6278 enabled = true; 6279 6280 version(use_arsd_core) { 6281 unregisterToken = getThisThreadEventLoop(EventLoopType.Ui).addCallbackOnFdReadable(fd, new CallbackHelper( 6282 () { onReady(fd, true, false); } 6283 )); 6284 // FIXME: what if it is writeable? 6285 6286 } else version(linux) { 6287 prepareEventLoop(); 6288 static import ep = core.sys.linux.epoll; 6289 ep.epoll_event ev = void; 6290 ev.events = (captureReads ? ep.EPOLLIN : 0) | (captureWrites ? ep.EPOLLOUT : 0); 6291 // writeln("enable ", fd, " ", captureReads, " ", captureWrites); 6292 ev.data.fd = fd; 6293 ep.epoll_ctl(epollFd, ep.EPOLL_CTL_ADD, fd, &ev); 6294 } else { 6295 6296 } 6297 } 6298 6299 version(with_eventloop) {} else 6300 /// 6301 void disable() @system { 6302 enabled = false; 6303 6304 version(use_arsd_core) { 6305 unregisterToken.unregister(); 6306 } else 6307 version(linux) { 6308 prepareEventLoop(); 6309 static import ep = core.sys.linux.epoll; 6310 ep.epoll_event ev = void; 6311 ev.events = (captureReads ? ep.EPOLLIN : 0) | (captureWrites ? ep.EPOLLOUT : 0); 6312 // writeln("disable ", fd, " ", captureReads, " ", captureWrites); 6313 ev.data.fd = fd; 6314 ep.epoll_ctl(epollFd, ep.EPOLL_CTL_DEL, fd, &ev); 6315 } 6316 } 6317 6318 version(with_eventloop) {} else 6319 /// 6320 void dispose() { 6321 if(enabled) 6322 disable(); 6323 if(fd != -1) 6324 mapping.remove(fd); 6325 fd = -1; 6326 } 6327 6328 void delegate(int, bool, bool) onReady; 6329 6330 version(with_eventloop) 6331 void readyel() { 6332 onReady(fd, true, true); 6333 } 6334 6335 void ready(uint flags) { 6336 version(Emscripten) { 6337 assert(0); 6338 } else version(linux) { 6339 static import ep = core.sys.linux.epoll; 6340 onReady(fd, (flags & ep.EPOLLIN) ? true : false, (flags & ep.EPOLLOUT) ? true : false); 6341 } else { 6342 import core.sys.posix.poll; 6343 onReady(fd, (flags & POLLIN) ? true : false, (flags & POLLOUT) ? true : false); 6344 } 6345 } 6346 6347 void hup(uint flags) { 6348 if(onHup) 6349 onHup(); 6350 } 6351 6352 void delegate() onHup; 6353 6354 int fd = -1; 6355 private bool enabled; 6356 __gshared PosixFdReader[int] mapping; 6357 } 6358 6359 // basic functions to access the clipboard 6360 /+ 6361 6362 6363 http://msdn.microsoft.com/en-us/library/windows/desktop/ff729168%28v=vs.85%29.aspx 6364 http://msdn.microsoft.com/en-us/library/windows/desktop/ms649039%28v=vs.85%29.aspx 6365 http://msdn.microsoft.com/en-us/library/windows/desktop/ms649035%28v=vs.85%29.aspx 6366 http://msdn.microsoft.com/en-us/library/windows/desktop/ms649051%28v=vs.85%29.aspx 6367 http://msdn.microsoft.com/en-us/library/windows/desktop/ms649037%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/ms649016%28v=vs.85%29.aspx 6370 6371 +/ 6372 6373 /++ 6374 this does a delegate because it is actually an async call on X... 6375 the receiver may never be called if the clipboard is empty or unavailable 6376 gets plain text from the clipboard. 6377 +/ 6378 void getClipboardText(SimpleWindow clipboardOwner, void delegate(in char[]) receiver) @system { 6379 version(Windows) { 6380 HWND hwndOwner = clipboardOwner ? clipboardOwner.impl.hwnd : null; 6381 if(OpenClipboard(hwndOwner) == 0) 6382 throw new WindowsApiException("OpenClipboard", GetLastError()); 6383 scope(exit) 6384 CloseClipboard(); 6385 // see: https://docs.microsoft.com/en-us/windows/win32/api/winuser/nf-winuser-getpriorityclipboardformat 6386 if(auto dataHandle = GetClipboardData(CF_UNICODETEXT)) { 6387 6388 if(auto data = cast(wchar*) GlobalLock(dataHandle)) { 6389 scope(exit) 6390 GlobalUnlock(dataHandle); 6391 6392 // FIXME: CR/LF conversions 6393 // FIXME: I might not have to copy it now that the receiver is in char[] instead of string 6394 int len = 0; 6395 auto d = data; 6396 while(*d) { 6397 d++; 6398 len++; 6399 } 6400 string s; 6401 s.reserve(len); 6402 foreach(dchar ch; data[0 .. len]) { 6403 s ~= ch; 6404 } 6405 receiver(s); 6406 } 6407 } 6408 } else version(X11) { 6409 getX11Selection!"CLIPBOARD"(clipboardOwner, receiver); 6410 } else version(OSXCocoa) { 6411 throw new NotYetImplementedException(); 6412 } else version(Emscripten) { 6413 throw new NotYetImplementedException(); 6414 } else static assert(0); 6415 } 6416 6417 // FIXME: a clipboard listener might be cool btw 6418 6419 /++ 6420 this does a delegate because it is actually an async call on X... 6421 the receiver may never be called if the clipboard is empty or unavailable 6422 gets image from the clipboard. 6423 6424 templated because it introduces an optional dependency on arsd.bmp 6425 +/ 6426 void getClipboardImage()(SimpleWindow clipboardOwner, void delegate(MemoryImage) receiver) { 6427 version(Windows) { 6428 HWND hwndOwner = clipboardOwner ? clipboardOwner.impl.hwnd : null; 6429 if(OpenClipboard(hwndOwner) == 0) 6430 throw new WindowsApiException("OpenClipboard", GetLastError()); 6431 scope(exit) 6432 CloseClipboard(); 6433 if(auto dataHandle = GetClipboardData(CF_DIBV5)) { 6434 if(auto data = cast(ubyte*) GlobalLock(dataHandle)) { 6435 scope(exit) 6436 GlobalUnlock(dataHandle); 6437 6438 auto len = GlobalSize(dataHandle); 6439 6440 import arsd.bmp; 6441 auto img = readBmp(data[0 .. len], false); 6442 receiver(img); 6443 } 6444 } 6445 } else version(X11) { 6446 getX11Selection!"CLIPBOARD"(clipboardOwner, receiver); 6447 } else version(OSXCocoa) { 6448 throw new NotYetImplementedException(); 6449 } else version(Emscripten) { 6450 throw new NotYetImplementedException(); 6451 } else static assert(0); 6452 } 6453 6454 /// Copies some text to the clipboard. 6455 void setClipboardText(SimpleWindow clipboardOwner, string text) { 6456 assert(clipboardOwner !is null); 6457 version(Windows) { 6458 if(OpenClipboard(clipboardOwner.impl.hwnd) == 0) 6459 throw new WindowsApiException("OpenClipboard", GetLastError()); 6460 scope(exit) 6461 CloseClipboard(); 6462 EmptyClipboard(); 6463 auto sz = sizeOfConvertedWstring(text, WindowsStringConversionFlags.convertNewLines | WindowsStringConversionFlags.zeroTerminate); 6464 auto handle = GlobalAlloc(GMEM_MOVEABLE, sz * 2); // zero terminated wchars 6465 if(handle is null) throw new WindowsApiException("GlobalAlloc", GetLastError()); 6466 if(auto data = cast(wchar*) GlobalLock(handle)) { 6467 auto slice = data[0 .. sz]; 6468 scope(failure) 6469 GlobalUnlock(handle); 6470 6471 auto str = makeWindowsString(text, slice, WindowsStringConversionFlags.convertNewLines | WindowsStringConversionFlags.zeroTerminate); 6472 6473 GlobalUnlock(handle); 6474 SetClipboardData(CF_UNICODETEXT, handle); 6475 } 6476 } else version(X11) { 6477 setX11Selection!"CLIPBOARD"(clipboardOwner, text); 6478 } else version(OSXCocoa) { 6479 throw new NotYetImplementedException(); 6480 } else version(Emscripten) { 6481 throw new NotYetImplementedException(); 6482 } else static assert(0); 6483 } 6484 6485 void setClipboardImage()(SimpleWindow clipboardOwner, MemoryImage img) { 6486 assert(clipboardOwner !is null); 6487 version(Windows) { 6488 if(OpenClipboard(clipboardOwner.impl.hwnd) == 0) 6489 throw new WindowsApiException("OpenClipboard", GetLastError()); 6490 scope(exit) 6491 CloseClipboard(); 6492 EmptyClipboard(); 6493 6494 6495 import arsd.bmp; 6496 ubyte[] mdata; 6497 mdata.reserve(img.width * img.height); 6498 void sink(ubyte b) { 6499 mdata ~= b; 6500 } 6501 writeBmpIndirect(img, &sink, false); 6502 6503 auto handle = GlobalAlloc(GMEM_MOVEABLE, mdata.length); 6504 if(handle is null) throw new WindowsApiException("GlobalAlloc", GetLastError()); 6505 if(auto data = cast(ubyte*) GlobalLock(handle)) { 6506 auto slice = data[0 .. mdata.length]; 6507 scope(failure) 6508 GlobalUnlock(handle); 6509 6510 slice[] = mdata[]; 6511 6512 GlobalUnlock(handle); 6513 SetClipboardData(CF_DIB, handle); 6514 } 6515 } else version(X11) { 6516 static class X11SetSelectionHandler_Image : X11SetSelectionHandler { 6517 mixin X11SetSelectionHandler_Basics; 6518 private const(ubyte)[] mdata; 6519 private const(ubyte)[] mdata_original; 6520 this(MemoryImage img) { 6521 import arsd.bmp; 6522 6523 mdata.reserve(img.width * img.height); 6524 void sink(ubyte b) { 6525 mdata ~= b; 6526 } 6527 writeBmpIndirect(img, &sink, true); 6528 6529 mdata_original = mdata; 6530 } 6531 6532 Atom[] availableFormats() { 6533 auto display = XDisplayConnection.get; 6534 return [ 6535 GetAtom!"image/bmp"(display), 6536 GetAtom!"TARGETS"(display) 6537 ]; 6538 } 6539 6540 ubyte[] getData(Atom format, return scope ubyte[] data) { 6541 if(mdata.length < data.length) { 6542 data[0 .. mdata.length] = mdata[]; 6543 auto ret = data[0 .. mdata.length]; 6544 mdata = mdata[$..$]; 6545 return ret; 6546 } else { 6547 data[] = mdata[0 .. data.length]; 6548 mdata = mdata[data.length .. $]; 6549 return data[]; 6550 } 6551 } 6552 6553 void done() { 6554 mdata = mdata_original; 6555 } 6556 } 6557 6558 setX11Selection!"CLIPBOARD"(clipboardOwner, new X11SetSelectionHandler_Image(img)); 6559 } else version(OSXCocoa) { 6560 throw new NotYetImplementedException(); 6561 } else version(Emscripten) { 6562 throw new NotYetImplementedException(); 6563 } else static assert(0); 6564 } 6565 6566 6567 version(X11) { 6568 // and the PRIMARY on X, be sure to put these in static if(UsingSimpledisplayX11) 6569 6570 private __gshared Atom*[] interredAtoms; // for discardAndRecreate 6571 6572 // FIXME: do a GetAtomUpfront too that just queues all at CT and combines it all. 6573 /// Platform-specific for X11. 6574 /// History: On February 21, 2021, I changed the default value of `create` to be true. 6575 @property Atom GetAtom(string name, bool create = true)(Display* display) { 6576 __gshared static Atom a; 6577 if(!a) { 6578 a = XInternAtom(display, name, !create); 6579 // FIXME: might need to synchronize this and attach it to the actual object 6580 interredAtoms ~= &a; 6581 } 6582 if(a == None) 6583 throw new Exception("XInternAtom " ~ name ~ " " ~ (create ? "true":"false")); 6584 return a; 6585 } 6586 6587 /// Platform-specific for X11 - gets atom names as a string. 6588 string getAtomName(Atom atom, Display* display) { 6589 auto got = XGetAtomName(display, atom); 6590 scope(exit) XFree(got); 6591 import core.stdc.string; 6592 string s = got[0 .. strlen(got)].idup; 6593 return s; 6594 } 6595 6596 /// Asserts ownership of PRIMARY and copies the text into a buffer that clients can request later. 6597 void setPrimarySelection(SimpleWindow window, string text) { 6598 setX11Selection!"PRIMARY"(window, text); 6599 } 6600 6601 /// Asserts ownership of SECONDARY and copies the text into a buffer that clients can request later. 6602 void setSecondarySelection(SimpleWindow window, string text) { 6603 setX11Selection!"SECONDARY"(window, text); 6604 } 6605 6606 interface X11SetSelectionHandler { 6607 // should include TARGETS right now 6608 Atom[] availableFormats(); 6609 // Return the slice of data you filled, empty slice if done. 6610 // this is to support the incremental thing 6611 ubyte[] getData(Atom format, return scope ubyte[] data); 6612 6613 void done(); 6614 6615 void handleRequest(XEvent); 6616 6617 bool matchesIncr(Window, Atom); 6618 void sendMoreIncr(XPropertyEvent*); 6619 } 6620 6621 mixin template X11SetSelectionHandler_Basics() { 6622 Window incrWindow; 6623 Atom incrAtom; 6624 Atom selectionAtom; 6625 Atom formatAtom; 6626 ubyte[] toSend; 6627 bool matchesIncr(Window w, Atom a) { 6628 return incrAtom && incrAtom == a && w == incrWindow; 6629 } 6630 void sendMoreIncr(XPropertyEvent* event) { 6631 auto display = XDisplayConnection.get; 6632 6633 XChangeProperty (display, 6634 incrWindow, 6635 incrAtom, 6636 formatAtom, 6637 8 /* bits */, PropModeReplace, 6638 toSend.ptr, cast(int) toSend.length); 6639 6640 if(toSend.length != 0) { 6641 toSend = this.getData(formatAtom, toSend[]); 6642 } else { 6643 this.done(); 6644 incrWindow = None; 6645 incrAtom = None; 6646 selectionAtom = None; 6647 formatAtom = None; 6648 toSend = null; 6649 } 6650 } 6651 void handleRequest(XEvent ev) { 6652 6653 auto display = XDisplayConnection.get; 6654 6655 XSelectionRequestEvent* event = &ev.xselectionrequest; 6656 XSelectionEvent selectionEvent; 6657 selectionEvent.type = EventType.SelectionNotify; 6658 selectionEvent.display = event.display; 6659 selectionEvent.requestor = event.requestor; 6660 selectionEvent.selection = event.selection; 6661 selectionEvent.time = event.time; 6662 selectionEvent.target = event.target; 6663 6664 bool supportedType() { 6665 foreach(t; this.availableFormats()) 6666 if(t == event.target) 6667 return true; 6668 return false; 6669 } 6670 6671 if(event.property == None) { 6672 selectionEvent.property = event.target; 6673 6674 XSendEvent(display, selectionEvent.requestor, false, 0, cast(XEvent*) &selectionEvent); 6675 XFlush(display); 6676 } if(event.target == GetAtom!"TARGETS"(display)) { 6677 /* respond with the supported types */ 6678 auto tlist = this.availableFormats(); 6679 XChangeProperty(display, event.requestor, event.property, XA_ATOM, 32, PropModeReplace, cast(void*)tlist.ptr, cast(int) tlist.length); 6680 selectionEvent.property = event.property; 6681 6682 XSendEvent(display, selectionEvent.requestor, false, 0, cast(XEvent*) &selectionEvent); 6683 XFlush(display); 6684 } else if(supportedType()) { 6685 auto buffer = new ubyte[](1024 * 64); 6686 auto toSend = this.getData(event.target, buffer[]); 6687 6688 if(toSend.length < 32 * 1024) { 6689 // small enough to send directly... 6690 selectionEvent.property = event.property; 6691 XChangeProperty (display, 6692 selectionEvent.requestor, 6693 selectionEvent.property, 6694 event.target, 6695 8 /* bits */, 0 /* PropModeReplace */, 6696 toSend.ptr, cast(int) toSend.length); 6697 6698 XSendEvent(display, selectionEvent.requestor, false, 0, cast(XEvent*) &selectionEvent); 6699 XFlush(display); 6700 } else { 6701 // large, let's send incrementally 6702 arch_ulong l = toSend.length; 6703 6704 // if I wanted other events from this window don't want to clear that out.... 6705 XWindowAttributes xwa; 6706 XGetWindowAttributes(display, selectionEvent.requestor, &xwa); 6707 6708 XSelectInput(display, selectionEvent.requestor, cast(EventMask) (xwa.your_event_mask | EventMask.PropertyChangeMask)); 6709 6710 incrWindow = event.requestor; 6711 incrAtom = event.property; 6712 formatAtom = event.target; 6713 selectionAtom = event.selection; 6714 this.toSend = toSend; 6715 6716 selectionEvent.property = event.property; 6717 XChangeProperty (display, 6718 selectionEvent.requestor, 6719 selectionEvent.property, 6720 GetAtom!"INCR"(display), 6721 32 /* bits */, PropModeReplace, 6722 &l, 1); 6723 6724 XSendEvent(display, selectionEvent.requestor, false, 0, cast(XEvent*) &selectionEvent); 6725 XFlush(display); 6726 } 6727 //if(after) 6728 //after(); 6729 } else { 6730 debug(sdpy_clip) { 6731 writeln("Unsupported data ", getAtomName(event.target, display)); 6732 } 6733 selectionEvent.property = None; // I don't know how to handle this type... 6734 XSendEvent(display, selectionEvent.requestor, false, EventMask.NoEventMask, cast(XEvent*) &selectionEvent); 6735 XFlush(display); 6736 } 6737 } 6738 } 6739 6740 class X11SetSelectionHandler_Text : X11SetSelectionHandler { 6741 mixin X11SetSelectionHandler_Basics; 6742 private const(ubyte)[] text; 6743 private const(ubyte)[] text_original; 6744 this(string text) { 6745 this.text = cast(const ubyte[]) text; 6746 this.text_original = this.text; 6747 } 6748 Atom[] availableFormats() { 6749 auto display = XDisplayConnection.get; 6750 return [ 6751 GetAtom!"UTF8_STRING"(display), 6752 GetAtom!"text/plain"(display), 6753 XA_STRING, 6754 GetAtom!"TARGETS"(display) 6755 ]; 6756 } 6757 6758 ubyte[] getData(Atom format, return scope ubyte[] data) { 6759 if(text.length < data.length) { 6760 data[0 .. text.length] = text[]; 6761 return data[0 .. text.length]; 6762 } else { 6763 data[] = text[0 .. data.length]; 6764 text = text[data.length .. $]; 6765 return data[]; 6766 } 6767 } 6768 6769 void done() { 6770 text = text_original; 6771 } 6772 } 6773 6774 /// 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?!) 6775 void setX11Selection(string atomName)(SimpleWindow window, string text, void delegate() after = null) { 6776 setX11Selection!atomName(window, new X11SetSelectionHandler_Text(text), after); 6777 } 6778 6779 private __gshared bool mightShortCircuitClipboard; 6780 6781 void setX11Selection(string atomName)(SimpleWindow window, X11SetSelectionHandler data, void delegate() after = null) { 6782 assert(window !is null); 6783 6784 auto display = XDisplayConnection.get(); 6785 static if (atomName == "PRIMARY") Atom a = XA_PRIMARY; 6786 else static if (atomName == "SECONDARY") Atom a = XA_SECONDARY; 6787 else Atom a = GetAtom!atomName(display); 6788 6789 if(mightShortCircuitClipboard) 6790 if(auto ptr = a in window.impl.setSelectionHandlers) { 6791 // we already have it, don't even need to inform the X server 6792 // sdpyPrintDebugString("short circuit in set"); 6793 *ptr = data; 6794 return; 6795 } 6796 6797 // we don't have it, tell X we want it 6798 XSetSelectionOwner(display, a, window.impl.window, 0 /* CurrentTime */); 6799 window.impl.setSelectionHandlers[a] = data; 6800 mightShortCircuitClipboard = true; 6801 } 6802 6803 /+ 6804 /++ 6805 History: 6806 Added September 28, 2024 6807 +/ 6808 bool hasX11Selection(string atomName)(SimpleWindow window) { 6809 auto display = XDisplayConnection.get(); 6810 static if (atomName == "PRIMARY") Atom a = XA_PRIMARY; 6811 else static if (atomName == "SECONDARY") Atom a = XA_SECONDARY; 6812 else Atom a = GetAtom!atomName(display); 6813 6814 if(a in window.impl.setSelectionHandlers) 6815 return true; 6816 else 6817 return false; 6818 } 6819 +/ 6820 6821 /// 6822 void getPrimarySelection(SimpleWindow window, void delegate(in char[]) handler) { 6823 getX11Selection!"PRIMARY"(window, handler); 6824 } 6825 6826 // added July 28, 2020 6827 // undocumented as experimental tho 6828 interface X11GetSelectionHandler { 6829 void handleData(Atom target, in ubyte[] data); 6830 Atom findBestFormat(Atom[] answer); 6831 6832 void prepareIncremental(Window, Atom); 6833 bool matchesIncr(Window, Atom); 6834 void handleIncrData(Atom, in ubyte[] data); 6835 } 6836 6837 mixin template X11GetSelectionHandler_Basics() { 6838 Window incrWindow; 6839 Atom incrAtom; 6840 6841 void prepareIncremental(Window w, Atom a) { 6842 incrWindow = w; 6843 incrAtom = a; 6844 } 6845 bool matchesIncr(Window w, Atom a) { 6846 return incrWindow == w && incrAtom == a; 6847 } 6848 6849 Atom incrFormatAtom; 6850 ubyte[] incrData; 6851 void handleIncrData(Atom format, in ubyte[] data) { 6852 incrFormatAtom = format; 6853 6854 if(data.length) 6855 incrData ~= data; 6856 else 6857 handleData(incrFormatAtom, incrData); 6858 6859 } 6860 } 6861 6862 static class X11GetSelectionHandler_Text : X11GetSelectionHandler { 6863 this(void delegate(in char[]) handler) { 6864 this.handler = handler; 6865 } 6866 6867 mixin X11GetSelectionHandler_Basics; 6868 6869 void delegate(in char[]) handler; 6870 6871 void handleData(Atom target, in ubyte[] data) { 6872 // import std.stdio; writeln(target, " ", data); 6873 if(target == GetAtom!"UTF8_STRING"(XDisplayConnection.get) || target == XA_STRING || target == GetAtom!"text/plain"(XDisplayConnection.get)) 6874 handler(cast(const char[]) data); 6875 else if(target == None && data is null) 6876 handler(null); // no suitable selection exists 6877 } 6878 6879 Atom findBestFormat(Atom[] answer) { 6880 Atom best = None; 6881 foreach(option; answer) { 6882 if(option == GetAtom!"UTF8_STRING"(XDisplayConnection.get)) { 6883 best = option; 6884 break; 6885 } else if(option == XA_STRING) { 6886 best = option; 6887 } else if(option == GetAtom!"text/plain"(XDisplayConnection.get)) { 6888 best = option; 6889 } 6890 } 6891 return best; 6892 } 6893 } 6894 6895 /// 6896 void getX11Selection(string atomName)(SimpleWindow window, void delegate(in char[]) handler, Time timestamp = 0 /* CurrentTime */) { 6897 assert(window !is null); 6898 6899 auto display = XDisplayConnection.get(); 6900 6901 static if (atomName == "PRIMARY") Atom atom = XA_PRIMARY; 6902 else static if (atomName == "SECONDARY") Atom atom = XA_SECONDARY; 6903 else Atom atom = GetAtom!atomName(display); 6904 6905 if(mightShortCircuitClipboard) 6906 if(auto ptr = atom in window.impl.setSelectionHandlers) { 6907 if(auto txt = (cast(X11SetSelectionHandler_Text) *ptr)) { 6908 // we already have it! short circuit everything 6909 6910 // sdpyPrintDebugString("short circuit in get"); 6911 handler(cast(char[]) txt.text_original); 6912 return; 6913 } 6914 } 6915 6916 window.impl.getSelectionHandlers[atom] = new X11GetSelectionHandler_Text(handler); 6917 6918 auto target = GetAtom!"TARGETS"(display); 6919 6920 // SDD_DATA is "simpledisplay.d data" 6921 XConvertSelection(display, atom, target, GetAtom!("SDD_DATA", true)(display), window.impl.window, timestamp); 6922 } 6923 6924 /// Gets the image on the clipboard, if there is one. Added July 2020. 6925 /// only supports bmps. using this function will import arsd.bmp. 6926 void getX11Selection(string atomName)(SimpleWindow window, void delegate(MemoryImage) handler) { 6927 assert(window !is null); 6928 6929 auto display = XDisplayConnection.get(); 6930 auto atom = GetAtom!atomName(display); 6931 6932 static class X11GetSelectionHandler_Image : X11GetSelectionHandler { 6933 this(void delegate(MemoryImage) handler) { 6934 this.handler = handler; 6935 } 6936 6937 mixin X11GetSelectionHandler_Basics; 6938 6939 void delegate(MemoryImage) handler; 6940 6941 void handleData(Atom target, in ubyte[] data) { 6942 if(target == GetAtom!"image/bmp"(XDisplayConnection.get)) { 6943 import arsd.bmp; 6944 handler(readBmp(data)); 6945 } 6946 } 6947 6948 Atom findBestFormat(Atom[] answer) { 6949 Atom best = None; 6950 foreach(option; answer) { 6951 if(option == GetAtom!"image/bmp"(XDisplayConnection.get)) { 6952 best = option; 6953 } 6954 } 6955 return best; 6956 } 6957 6958 } 6959 6960 6961 window.impl.getSelectionHandlers[atom] = new X11GetSelectionHandler_Image(handler); 6962 6963 auto target = GetAtom!"TARGETS"(display); 6964 6965 // SDD_DATA is "simpledisplay.d data" 6966 XConvertSelection(display, atom, target, GetAtom!("SDD_DATA", true)(display), window.impl.window, 0 /*CurrentTime*/); 6967 } 6968 6969 6970 /// 6971 void[] getX11PropertyData(Window window, Atom property, Atom type = AnyPropertyType) { 6972 Atom actualType; 6973 int actualFormat; 6974 arch_ulong actualItems; 6975 arch_ulong bytesRemaining; 6976 void* data; 6977 6978 auto display = XDisplayConnection.get(); 6979 if(XGetWindowProperty(display, window, property, 0, 0x7fffffff, false, type, &actualType, &actualFormat, &actualItems, &bytesRemaining, &data) == Success) { 6980 if(actualFormat == 0) 6981 return null; 6982 else { 6983 int byteLength; 6984 if(actualFormat == 32) { 6985 // 32 means it is a C long... which is variable length 6986 actualFormat = cast(int) arch_long.sizeof * 8; 6987 } 6988 6989 // then it is just a bit count 6990 byteLength = cast(int) (actualItems * actualFormat / 8); 6991 6992 auto d = new ubyte[](byteLength); 6993 d[] = cast(ubyte[]) data[0 .. byteLength]; 6994 XFree(data); 6995 return d; 6996 } 6997 } 6998 return null; 6999 } 7000 7001 /* defined in the systray spec */ 7002 enum SYSTEM_TRAY_REQUEST_DOCK = 0; 7003 enum SYSTEM_TRAY_BEGIN_MESSAGE = 1; 7004 enum SYSTEM_TRAY_CANCEL_MESSAGE = 2; 7005 7006 7007 /** Global hotkey handler. Simpledisplay will usually create one for you, but if you want to use subclassing 7008 * instead of delegates, you can subclass this, and override `doHandle()` method. */ 7009 public class GlobalHotkey { 7010 KeyEvent key; 7011 void delegate () handler; 7012 7013 void doHandle () { if (handler !is null) handler(); } /// this will be called by hotkey manager 7014 7015 /// Create from initialzed KeyEvent object 7016 this (KeyEvent akey, void delegate () ahandler=null) { 7017 if (akey.key == 0 || !GlobalHotkeyManager.isGoodModifierMask(akey.modifierState)) throw new Exception("invalid global hotkey"); 7018 key = akey; 7019 handler = ahandler; 7020 } 7021 7022 /// Create from emacs-like key name ("C-M-Y", etc.) 7023 this (const(char)[] akey, void delegate () ahandler=null) { 7024 key = KeyEvent.parse(akey); 7025 if (key.key == 0 || !GlobalHotkeyManager.isGoodModifierMask(key.modifierState)) throw new Exception("invalid global hotkey"); 7026 handler = ahandler; 7027 } 7028 7029 } 7030 7031 private extern(C) int XGrabErrorHandler (Display* dpy, XErrorEvent* evt) nothrow @nogc { 7032 //conwriteln("failed to grab key"); 7033 GlobalHotkeyManager.ghfailed = true; 7034 return 0; 7035 } 7036 7037 private extern(C) int XShmErrorHandler (Display* dpy, XErrorEvent* evt) nothrow @nogc { 7038 Image.impl.xshmfailed = true; 7039 return 0; 7040 } 7041 7042 private __gshared int errorHappened; 7043 private extern(C) int adrlogger (Display* dpy, XErrorEvent* evt) nothrow @nogc { 7044 import core.stdc.stdio; 7045 char[265] buffer; 7046 XGetErrorText(dpy, evt.error_code, buffer.ptr, cast(int) buffer.length); 7047 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); 7048 errorHappened = true; 7049 return 0; 7050 } 7051 7052 /++ 7053 Global hotkey manager. It contains static methods to manage global hotkeys. 7054 7055 --- 7056 try { 7057 GlobalHotkeyManager.register("M-H-A", delegate () { hideShowWindows(); }); 7058 } catch (Exception e) { 7059 conwriteln("ERROR registering hotkey!"); 7060 } 7061 EventLoop.get.run(); 7062 --- 7063 7064 The key strings are based on Emacs. In practical terms, 7065 `M` means `alt` and `H` means the Windows logo key. `C` 7066 is `ctrl`. 7067 7068 $(WARNING 7069 This is X-specific right now. If you are on 7070 Windows, try [registerHotKey] instead. 7071 7072 We will probably merge these into a single 7073 interface later. 7074 ) 7075 +/ 7076 public class GlobalHotkeyManager : CapableOfHandlingNativeEvent { 7077 version(X11) { 7078 void recreateAfterDisconnect() { 7079 throw new Exception("NOT IMPLEMENTED"); 7080 } 7081 void discardConnectionState() { 7082 throw new Exception("NOT IMPLEMENTED"); 7083 } 7084 } 7085 7086 private static immutable uint[8] masklist = [ 0, 7087 KeyOrButtonMask.LockMask, 7088 KeyOrButtonMask.Mod2Mask, 7089 KeyOrButtonMask.Mod3Mask, 7090 KeyOrButtonMask.LockMask|KeyOrButtonMask.Mod2Mask, 7091 KeyOrButtonMask.LockMask|KeyOrButtonMask.Mod3Mask, 7092 KeyOrButtonMask.LockMask|KeyOrButtonMask.Mod2Mask|KeyOrButtonMask.Mod3Mask, 7093 KeyOrButtonMask.Mod2Mask|KeyOrButtonMask.Mod3Mask, 7094 ]; 7095 private __gshared GlobalHotkeyManager ghmanager; 7096 private __gshared bool ghfailed = false; 7097 7098 private static bool isGoodModifierMask (uint modmask) pure nothrow @safe @nogc { 7099 if (modmask == 0) return false; 7100 if (modmask&(KeyOrButtonMask.LockMask|KeyOrButtonMask.Mod2Mask|KeyOrButtonMask.Mod3Mask)) return false; 7101 if (modmask&~(KeyOrButtonMask.Mod5Mask-1)) return false; 7102 return true; 7103 } 7104 7105 private static uint cleanupModifiers (uint modmask) pure nothrow @safe @nogc { 7106 modmask &= ~(KeyOrButtonMask.LockMask|KeyOrButtonMask.Mod2Mask|KeyOrButtonMask.Mod3Mask); // remove caps, num, scroll 7107 modmask &= (KeyOrButtonMask.Mod5Mask-1); // and other modifiers 7108 return modmask; 7109 } 7110 7111 private static uint keyEvent2KeyCode() (scope auto ref const KeyEvent ke) { 7112 uint keycode = cast(uint)ke.key; 7113 auto dpy = XDisplayConnection.get; 7114 return XKeysymToKeycode(dpy, keycode); 7115 } 7116 7117 private static ulong keyCode2Hash() (uint keycode, uint modstate) pure nothrow @safe @nogc { return ((cast(ulong)modstate)<<32)|keycode; } 7118 7119 private __gshared GlobalHotkey[ulong] globalHotkeyList; 7120 7121 NativeEventHandler getNativeEventHandler () { 7122 return delegate int (XEvent e) { 7123 if (e.type != EventType.KeyPress) return 1; 7124 auto kev = cast(const(XKeyEvent)*)&e; 7125 auto hash = keyCode2Hash(e.xkey.keycode, cleanupModifiers(e.xkey.state)); 7126 if (auto ghkp = hash in globalHotkeyList) { 7127 try { 7128 ghkp.doHandle(); 7129 } catch (Exception e) { 7130 import core.stdc.stdio : stderr, fprintf; 7131 stderr.fprintf("HOTKEY HANDLER EXCEPTION: %.*s", cast(uint)e.msg.length, e.msg.ptr); 7132 } 7133 } 7134 return 1; 7135 }; 7136 } 7137 7138 private this () { 7139 auto dpy = XDisplayConnection.get; 7140 auto root = RootWindow(dpy, DefaultScreen(dpy)); 7141 CapableOfHandlingNativeEvent.nativeHandleMapping[root] = this; 7142 XDisplayConnection.addRootInput(EventMask.KeyPressMask); 7143 } 7144 7145 /// Register new global hotkey with initialized `GlobalHotkey` object. 7146 /// This function will throw if it failed to register hotkey (i.e. hotkey is invalid or already taken). 7147 static void register (GlobalHotkey gh) { 7148 if (gh is null) return; 7149 if (gh.key.key == 0 || !isGoodModifierMask(gh.key.modifierState)) throw new Exception("invalid global hotkey"); 7150 7151 auto dpy = XDisplayConnection.get; 7152 immutable keycode = keyEvent2KeyCode(gh.key); 7153 7154 auto hash = keyCode2Hash(keycode, gh.key.modifierState); 7155 if (hash in globalHotkeyList) throw new Exception("duplicate global hotkey"); 7156 if (ghmanager is null) ghmanager = new GlobalHotkeyManager(); 7157 XSync(dpy, 0/*False*/); 7158 7159 Window root = RootWindow(dpy, DefaultScreen(dpy)); 7160 XErrorHandler savedErrorHandler = XSetErrorHandler(&XGrabErrorHandler); 7161 ghfailed = false; 7162 foreach (immutable uint ormask; masklist[]) { 7163 XGrabKey(dpy, keycode, gh.key.modifierState|ormask, /*grab_window*/root, /*owner_events*/0/*False*/, GrabMode.GrabModeAsync, GrabMode.GrabModeAsync); 7164 } 7165 XSync(dpy, 0/*False*/); 7166 XSetErrorHandler(savedErrorHandler); 7167 7168 if (ghfailed) { 7169 savedErrorHandler = XSetErrorHandler(&XGrabErrorHandler); 7170 foreach (immutable uint ormask; masklist[]) XUngrabKey(dpy, keycode, gh.key.modifierState|ormask, /*grab_window*/root); 7171 XSync(dpy, 0/*False*/); 7172 XSetErrorHandler(savedErrorHandler); 7173 throw new Exception("cannot register global hotkey"); 7174 } 7175 7176 globalHotkeyList[hash] = gh; 7177 } 7178 7179 /// Ditto 7180 static void register (const(char)[] akey, void delegate () ahandler) { 7181 register(new GlobalHotkey(akey, ahandler)); 7182 } 7183 7184 private static void removeByHash (ulong hash) { 7185 if (auto ghp = hash in globalHotkeyList) { 7186 auto dpy = XDisplayConnection.get; 7187 immutable keycode = keyEvent2KeyCode(ghp.key); 7188 Window root = RootWindow(dpy, DefaultScreen(dpy)); 7189 XSync(dpy, 0/*False*/); 7190 XErrorHandler savedErrorHandler = XSetErrorHandler(&XGrabErrorHandler); 7191 foreach (immutable uint ormask; masklist[]) XUngrabKey(dpy, keycode, ghp.key.modifierState|ormask, /*grab_window*/root); 7192 XSync(dpy, 0/*False*/); 7193 XSetErrorHandler(savedErrorHandler); 7194 globalHotkeyList.remove(hash); 7195 } 7196 } 7197 7198 /// Register new global hotkey with previously used `GlobalHotkey` object. 7199 /// It is safe to unregister unknown or invalid hotkey. 7200 static void unregister (GlobalHotkey gh) { 7201 //TODO: add second AA for faster search? prolly doesn't worth it. 7202 if (gh is null) return; 7203 foreach (const ref kv; globalHotkeyList.byKeyValue) { 7204 if (kv.value is gh) { 7205 removeByHash(kv.key); 7206 return; 7207 } 7208 } 7209 } 7210 7211 /// Ditto. 7212 static void unregister (const(char)[] key) { 7213 auto kev = KeyEvent.parse(key); 7214 immutable keycode = keyEvent2KeyCode(kev); 7215 removeByHash(keyCode2Hash(keycode, kev.modifierState)); 7216 } 7217 } 7218 } 7219 7220 version(Windows) { 7221 /++ 7222 See [SyntheticInput.sendSyntheticInput] instead for cross-platform applications. 7223 7224 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). 7225 +/ 7226 void sendSyntheticInput(wstring s) { 7227 INPUT[] inputs; 7228 inputs.reserve(s.length * 2); 7229 7230 foreach(wchar c; s) { 7231 INPUT input; 7232 input.type = INPUT_KEYBOARD; 7233 input.ki.wScan = c; 7234 input.ki.dwFlags = KEYEVENTF_UNICODE; 7235 inputs ~= input; 7236 7237 input.ki.dwFlags |= KEYEVENTF_KEYUP; 7238 inputs ~= input; 7239 } 7240 7241 if(SendInput(cast(int) inputs.length, inputs.ptr, INPUT.sizeof) != inputs.length) { 7242 throw new WindowsApiException("SendInput", GetLastError()); 7243 } 7244 7245 } 7246 7247 7248 // global hotkey helper function 7249 7250 /// Platform-specific for Windows. Registers a global hotkey. Returns a registration ID. See [GlobalHotkeyManager] for Linux. Maybe some day I will merge these. 7251 int registerHotKey(SimpleWindow window, UINT modifiers, UINT vk, void delegate() handler) @system { 7252 __gshared int hotkeyId = 0; 7253 int id = ++hotkeyId; 7254 if(!RegisterHotKey(window.impl.hwnd, id, modifiers, vk)) 7255 throw new Exception("RegisterHotKey"); 7256 7257 __gshared void delegate()[WPARAM][HWND] handlers; 7258 7259 handlers[window.impl.hwnd][id] = handler; 7260 7261 int delegate(HWND, UINT, WPARAM, LPARAM, out int) oldHandler; 7262 7263 auto nativeEventHandler = delegate int(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam, out int mustReturn) { 7264 switch(msg) { 7265 // http://msdn.microsoft.com/en-us/library/windows/desktop/ms646279%28v=vs.85%29.aspx 7266 case WM_HOTKEY: 7267 if(auto list = hwnd in handlers) { 7268 if(auto h = wParam in *list) { 7269 (*h)(); 7270 return 0; 7271 } 7272 } 7273 goto default; 7274 default: 7275 } 7276 if(oldHandler) 7277 return oldHandler(hwnd, msg, wParam, lParam, mustReturn); 7278 return 1; // pass it on 7279 }; 7280 7281 if(window.handleNativeEvent.funcptr !is nativeEventHandler.funcptr) { 7282 oldHandler = window.handleNativeEvent; 7283 window.handleNativeEvent = nativeEventHandler; 7284 } 7285 7286 return id; 7287 } 7288 7289 /// Platform-specific for Windows. Unregisters a key. The id is the value returned by [registerHotKey]. 7290 void unregisterHotKey(SimpleWindow window, int id) { 7291 if(!UnregisterHotKey(window.impl.hwnd, id)) 7292 throw new WindowsApiException("UnregisterHotKey", GetLastError()); 7293 } 7294 } 7295 7296 version (X11) { 7297 pragma(lib, "dl"); 7298 import core.sys.posix.dlfcn; 7299 } 7300 7301 /++ 7302 Allows for sending synthetic input to the X server via the Xtst 7303 extension or on Windows using SendInput. 7304 7305 Please remember user input is meant to be user - don't use this 7306 if you have some other alternative! 7307 7308 History: 7309 Added May 17, 2020 with the X implementation. 7310 7311 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.) 7312 Bugs: 7313 All methods on OSX Cocoa will throw not yet implemented exceptions. 7314 +/ 7315 struct SyntheticInput { 7316 @disable this(); 7317 7318 private int* refcount; 7319 7320 version(X11) { 7321 private void* lib; 7322 7323 private extern(C) { 7324 void function(Display*, uint keycode, bool press, arch_ulong delay) XTestFakeKeyEvent; 7325 void function(Display*, uint button, bool press, arch_ulong delay) XTestFakeButtonEvent; 7326 } 7327 } 7328 7329 /// The dummy param must be 0. 7330 this(int dummy) { 7331 version(X11) { 7332 lib = dlopen("libXtst.so", RTLD_NOW); 7333 if(lib is null) 7334 throw new Exception("cannot load xtest lib extension"); 7335 scope(failure) 7336 dlclose(lib); 7337 7338 XTestFakeButtonEvent = cast(typeof(XTestFakeButtonEvent)) dlsym(lib, "XTestFakeButtonEvent"); 7339 XTestFakeKeyEvent = cast(typeof(XTestFakeKeyEvent)) dlsym(lib, "XTestFakeKeyEvent"); 7340 7341 if(XTestFakeKeyEvent is null) 7342 throw new Exception("No XTestFakeKeyEvent"); 7343 if(XTestFakeButtonEvent is null) 7344 throw new Exception("No XTestFakeButtonEvent"); 7345 } 7346 7347 refcount = new int; 7348 *refcount = 1; 7349 } 7350 7351 this(this) { 7352 if(refcount) 7353 *refcount += 1; 7354 } 7355 7356 ~this() { 7357 if(refcount) { 7358 *refcount -= 1; 7359 if(*refcount == 0) 7360 // I commented this because if I close the lib before 7361 // XCloseDisplay, it is liable to segfault... so just 7362 // gonna keep it loaded if it is loaded, no big deal 7363 // anyway. 7364 {} // dlclose(lib); 7365 } 7366 } 7367 7368 /++ 7369 Simulates typing a string into the keyboard. 7370 7371 Bugs: 7372 On X11, this ONLY works with basic ascii! On Windows, it can handle more. 7373 7374 Not implemented except on Windows and X11. 7375 +/ 7376 void sendSyntheticInput(string s) { 7377 version(Windows) { 7378 INPUT[] inputs; 7379 inputs.reserve(s.length * 2); 7380 7381 auto ei = GetMessageExtraInfo(); 7382 7383 foreach(wchar c; s) { 7384 INPUT input; 7385 input.type = INPUT_KEYBOARD; 7386 input.ki.wScan = c; 7387 input.ki.dwFlags = KEYEVENTF_UNICODE; 7388 input.ki.dwExtraInfo = ei; 7389 inputs ~= input; 7390 7391 input.ki.dwFlags |= KEYEVENTF_KEYUP; 7392 inputs ~= input; 7393 } 7394 7395 if(SendInput(cast(int) inputs.length, inputs.ptr, INPUT.sizeof) != inputs.length) { 7396 throw new WindowsApiException("SendInput", GetLastError()); 7397 } 7398 } else version(X11) { 7399 int delay = 0; 7400 foreach(ch; s) { 7401 pressKey(cast(Key) ch, true, delay); 7402 pressKey(cast(Key) ch, false, delay); 7403 delay += 5; 7404 } 7405 } else throw new NotYetImplementedException(); 7406 } 7407 7408 /++ 7409 Sends a fake press or release key event. 7410 7411 Please note you need to call [flushGui] or return to the event loop for this to actually be sent on X11. 7412 7413 Bugs: 7414 The `delay` parameter is not implemented yet on Windows. 7415 7416 Not implemented except on Windows and X11. 7417 +/ 7418 void pressKey(Key key, bool pressed, int delay = 0) { 7419 version(Windows) { 7420 INPUT input; 7421 input.type = INPUT_KEYBOARD; 7422 input.ki.wVk = cast(ushort) key; 7423 7424 input.ki.dwFlags = pressed ? 0 : KEYEVENTF_KEYUP; 7425 input.ki.dwExtraInfo = GetMessageExtraInfo(); 7426 7427 if(SendInput(1, &input, INPUT.sizeof) != 1) { 7428 throw new WindowsApiException("SendInput", GetLastError()); 7429 } 7430 } else version(X11) { 7431 XTestFakeKeyEvent(XDisplayConnection.get, XKeysymToKeycode(XDisplayConnection.get, key), pressed, delay + pressed ? 0 : 5); 7432 } else throw new NotYetImplementedException(); 7433 } 7434 7435 /++ 7436 Sends a fake mouse button press or release event. 7437 7438 Please note you need to call [flushGui] or return to the event loop for this to actually be sent on X11. 7439 7440 `pressed` param must be `true` if button is `wheelUp` or `wheelDown`. 7441 7442 Bugs: 7443 The `delay` parameter is not implemented yet on Windows. 7444 7445 The backButton and forwardButton will throw NotYetImplementedException on Windows. 7446 7447 All arguments will throw NotYetImplementedException on OSX Cocoa. 7448 +/ 7449 void pressMouseButton(MouseButton button, bool pressed, int delay = 0) { 7450 version(Windows) { 7451 INPUT input; 7452 input.type = INPUT_MOUSE; 7453 input.mi.dwExtraInfo = GetMessageExtraInfo(); 7454 7455 // input.mi.mouseData for a wheel event 7456 7457 switch(button) { 7458 case MouseButton.left: input.mi.dwFlags = pressed ? MOUSEEVENTF_LEFTDOWN : MOUSEEVENTF_LEFTUP; break; 7459 case MouseButton.middle: input.mi.dwFlags = pressed ? MOUSEEVENTF_MIDDLEDOWN : MOUSEEVENTF_MIDDLEUP; break; 7460 case MouseButton.right: input.mi.dwFlags = pressed ? MOUSEEVENTF_RIGHTDOWN : MOUSEEVENTF_RIGHTUP; break; 7461 case MouseButton.wheelUp: 7462 case MouseButton.wheelDown: 7463 input.mi.dwFlags = MOUSEEVENTF_WHEEL; 7464 input.mi.mouseData = button == MouseButton.wheelUp ? 120 : -120; 7465 break; 7466 case MouseButton.backButton: throw new NotYetImplementedException(); 7467 case MouseButton.forwardButton: throw new NotYetImplementedException(); 7468 default: 7469 } 7470 7471 if(SendInput(1, &input, INPUT.sizeof) != 1) { 7472 throw new WindowsApiException("SendInput", GetLastError()); 7473 } 7474 } else version(X11) { 7475 int btn; 7476 7477 switch(button) { 7478 case MouseButton.left: btn = 1; break; 7479 case MouseButton.middle: btn = 2; break; 7480 case MouseButton.right: btn = 3; break; 7481 case MouseButton.wheelUp: btn = 4; break; 7482 case MouseButton.wheelDown: btn = 5; break; 7483 case MouseButton.backButton: btn = 8; break; 7484 case MouseButton.forwardButton: btn = 9; break; 7485 default: 7486 } 7487 7488 assert(btn); 7489 7490 XTestFakeButtonEvent(XDisplayConnection.get, btn, pressed, delay); 7491 } else throw new NotYetImplementedException(); 7492 } 7493 7494 /// 7495 static void moveMouseArrowBy(int dx, int dy) { 7496 version(Windows) { 7497 INPUT input; 7498 input.type = INPUT_MOUSE; 7499 input.mi.dwExtraInfo = GetMessageExtraInfo(); 7500 input.mi.dx = dx; 7501 input.mi.dy = dy; 7502 input.mi.dwFlags = MOUSEEVENTF_MOVE; 7503 7504 if(SendInput(1, &input, INPUT.sizeof) != 1) { 7505 throw new WindowsApiException("SendInput", GetLastError()); 7506 } 7507 } else version(X11) { 7508 auto disp = XDisplayConnection.get(); 7509 XWarpPointer(disp, None, None, 0, 0, 0, 0, dx, dy); 7510 XFlush(disp); 7511 } else throw new NotYetImplementedException(); 7512 } 7513 7514 /// 7515 static void moveMouseArrowTo(int x, int y) { 7516 version(Windows) { 7517 INPUT input; 7518 input.type = INPUT_MOUSE; 7519 input.mi.dwExtraInfo = GetMessageExtraInfo(); 7520 input.mi.dx = x; 7521 input.mi.dy = y; 7522 input.mi.dwFlags = MOUSEEVENTF_MOVE | MOUSEEVENTF_ABSOLUTE; 7523 7524 if(SendInput(1, &input, INPUT.sizeof) != 1) { 7525 throw new WindowsApiException("SendInput", GetLastError()); 7526 } 7527 } else version(X11) { 7528 auto disp = XDisplayConnection.get(); 7529 auto root = RootWindow(disp, DefaultScreen(disp)); 7530 XWarpPointer(disp, None, root, 0, 0, 0, 0, x, y); 7531 XFlush(disp); 7532 } else throw new NotYetImplementedException(); 7533 } 7534 } 7535 7536 7537 7538 /++ 7539 [ScreenPainter] operations can use different operations to combine the color with the color on screen. 7540 7541 See_Also: 7542 $(LIST 7543 *[ScreenPainter] 7544 *[ScreenPainter.rasterOp] 7545 ) 7546 +/ 7547 enum RasterOp { 7548 normal, /// Replaces the pixel. 7549 xor, /// Uses bitwise xor to draw. 7550 } 7551 7552 // being phobos-free keeps the size WAY down 7553 private const(char)* toStringz(string s) { return (s ~ '\0').ptr; } 7554 package(arsd) const(wchar)* toWStringz(wstring s) { return (s ~ '\0').ptr; } 7555 package(arsd) const(wchar)* toWStringz(string s) { 7556 wstring r; 7557 foreach(dchar c; s) 7558 r ~= c; 7559 r ~= '\0'; 7560 return r.ptr; 7561 } 7562 private string[] split(in void[] a, char c) { 7563 string[] ret; 7564 size_t previous = 0; 7565 foreach(i, char ch; cast(ubyte[]) a) { 7566 if(ch == c) { 7567 ret ~= cast(string) a[previous .. i]; 7568 previous = i + 1; 7569 } 7570 } 7571 if(previous != a.length) 7572 ret ~= cast(string) a[previous .. $]; 7573 return ret; 7574 } 7575 7576 version(without_opengl) { 7577 enum OpenGlOptions { 7578 no, 7579 } 7580 } else { 7581 /++ 7582 Determines if you want an OpenGL context created on the new window. 7583 7584 7585 See more: [#topics-3d|in the 3d topic]. 7586 7587 --- 7588 import arsd.simpledisplay; 7589 void main() { 7590 auto window = new SimpleWindow(500, 500, "OpenGL Test", OpenGlOptions.yes); 7591 7592 // Set up the matrix 7593 window.setAsCurrentOpenGlContext(); // make this window active 7594 7595 // This is called on each frame, we will draw our scene 7596 window.redrawOpenGlScene = delegate() { 7597 7598 }; 7599 7600 window.eventLoop(0); 7601 } 7602 --- 7603 +/ 7604 enum OpenGlOptions { 7605 no, /// No OpenGL context is created 7606 yes, /// Yes, create an OpenGL context 7607 } 7608 7609 version(X11) { 7610 static if (!SdpyIsUsingIVGLBinds) { 7611 7612 7613 struct __GLXFBConfigRec {} 7614 alias GLXFBConfig = __GLXFBConfigRec*; 7615 7616 //pragma(lib, "GL"); 7617 //pragma(lib, "GLU"); 7618 interface GLX { 7619 extern(C) nothrow @nogc { 7620 XVisualInfo* glXChooseVisual(Display *dpy, int screen, 7621 const int *attrib_list); 7622 7623 void glXCopyContext(Display *dpy, GLXContext src, 7624 GLXContext dst, arch_ulong mask); 7625 7626 GLXContext glXCreateContext(Display *dpy, XVisualInfo *vis, 7627 GLXContext share_list, Bool direct); 7628 7629 GLXPixmap glXCreateGLXPixmap(Display *dpy, XVisualInfo *vis, 7630 Pixmap pixmap); 7631 7632 void glXDestroyContext(Display *dpy, GLXContext ctx); 7633 7634 void glXDestroyGLXPixmap(Display *dpy, GLXPixmap pix); 7635 7636 int glXGetConfig(Display *dpy, XVisualInfo *vis, 7637 int attrib, int *value); 7638 7639 GLXContext glXGetCurrentContext(); 7640 7641 GLXDrawable glXGetCurrentDrawable(); 7642 7643 Bool glXIsDirect(Display *dpy, GLXContext ctx); 7644 7645 Bool glXMakeCurrent(Display *dpy, GLXDrawable drawable, 7646 GLXContext ctx); 7647 7648 Bool glXQueryExtension(Display *dpy, int *error_base, int *event_base); 7649 7650 Bool glXQueryVersion(Display *dpy, int *major, int *minor); 7651 7652 void glXSwapBuffers(Display *dpy, GLXDrawable drawable); 7653 7654 void glXUseXFont(Font font, int first, int count, int list_base); 7655 7656 void glXWaitGL(); 7657 7658 void glXWaitX(); 7659 7660 7661 GLXFBConfig* glXChooseFBConfig (Display*, int, int*, int*); 7662 int glXGetFBConfigAttrib (Display*, GLXFBConfig, int, int*); 7663 XVisualInfo* glXGetVisualFromFBConfig (Display*, GLXFBConfig); 7664 7665 char* glXQueryExtensionsString (Display*, int); 7666 void* glXGetProcAddress (const(char)*); 7667 7668 } 7669 } 7670 7671 version(OSX) 7672 mixin DynamicLoad!(GLX, "GL", 0, openGlLibrariesSuccessfullyLoaded) glx; 7673 else 7674 mixin DynamicLoad!(GLX, "GLX", 0, openGlLibrariesSuccessfullyLoaded) glx; 7675 shared static this() { 7676 glx.loadDynamicLibrary(); 7677 } 7678 7679 alias glbindGetProcAddress = glXGetProcAddress; 7680 } 7681 } else version(Windows) { 7682 /* it is done below by interface GL */ 7683 } else 7684 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."); 7685 } 7686 7687 deprecated("Sorry, I misspelled it in the first version! Use `Resizability` instead.") 7688 alias Resizablity = Resizability; 7689 7690 /// When you create a SimpleWindow, you can see its resizability to be one of these via the constructor... 7691 enum Resizability { 7692 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. 7693 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. 7694 /++ 7695 $(PITFALL 7696 Planned for the future but not implemented. 7697 ) 7698 7699 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. 7700 7701 History: 7702 Added November 11, 2022, but not yet implemented and may not be for some time. 7703 +/ 7704 /*@__future*/ allowResizingMaintainingAspectRatio, 7705 /++ 7706 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. 7707 7708 History: 7709 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. 7710 7711 Your programs should not be affected, as they will continue to function as if the user simply never resized the window at all. 7712 +/ 7713 automaticallyScaleIfPossible, 7714 } 7715 /// ditto 7716 alias Resizeability = Resizability; 7717 7718 7719 /++ 7720 Alignment for [ScreenPainter.drawText]. Left, Center, or Right may be combined with VerticalTop, VerticalCenter, or VerticalBottom via bitwise or. 7721 +/ 7722 enum TextAlignment : uint { 7723 Left = 0, /// 7724 Center = 1, /// 7725 Right = 2, /// 7726 7727 VerticalTop = 0, /// 7728 VerticalCenter = 4, /// 7729 VerticalBottom = 8, /// 7730 } 7731 7732 public import arsd.color; // no longer stand alone... :-( but i need a common type for this to work with images easily. 7733 alias Rectangle = arsd.color.Rectangle; 7734 7735 7736 /++ 7737 Keyboard press and release events. 7738 +/ 7739 struct KeyEvent { 7740 /// see table below. Always use the symbolic names, even for ASCII characters, since the actual numbers vary across platforms. See [Key] 7741 Key key; 7742 ubyte hardwareCode; /// A platform and hardware specific code for the key 7743 bool pressed; /// true if the key was just pressed, false if it was just released. note: released events aren't always sent... 7744 7745 deprecated("This never actually worked anyway, you should do a character event handler instead.") dchar character; 7746 7747 uint modifierState; /// see enum [ModifierState]. They are bitwise combined together. 7748 7749 SimpleWindow window; /// associated Window 7750 7751 /++ 7752 A view into the upcoming buffer holding coming character events that are sent if and only if neither 7753 the alt or super modifier keys are pressed (check this with `!(modifierState & (ModifierState.window | ModifierState.alt))` 7754 to predict if char events are actually coming.. 7755 7756 Only available on X systems since this information is not given ahead of time elsewhere. 7757 (Well, you COULD probably dig it up, but as far as I know right now, it isn't terribly pretty.) 7758 7759 I'm adding this because it is useful to the terminal emulator, but given its platform specificness 7760 and potential quirks I'd recommend avoiding it. 7761 7762 History: 7763 Added April 26, 2021 (dub v9.5) 7764 +/ 7765 version(X11) 7766 dchar[] charsPossible; 7767 7768 // convert key event to simplified string representation a-la emacs 7769 const(char)[] toStrBuf(bool growdest=false) (char[] dest) const nothrow @trusted { 7770 uint dpos = 0; 7771 void put (const(char)[] s...) nothrow @trusted { 7772 static if (growdest) { 7773 foreach (char ch; s) if (dpos < dest.length) dest.ptr[dpos++] = ch; else { dest ~= ch; ++dpos; } 7774 } else { 7775 foreach (char ch; s) if (dpos < dest.length) dest.ptr[dpos++] = ch; 7776 } 7777 } 7778 7779 void putMod (ModifierState mod, Key key, string text) nothrow @trusted { 7780 if ((this.modifierState&mod) != 0 && (this.pressed || this.key != key)) put(text); 7781 } 7782 7783 if (!this.key && !(this.modifierState&(ModifierState.ctrl|ModifierState.alt|ModifierState.shift|ModifierState.windows))) return null; 7784 7785 // put modifiers 7786 // releasing modifier keys can produce bizarre things like "Ctrl+Ctrl", so hack around it 7787 putMod(ModifierState.ctrl, Key.Ctrl, "Ctrl+"); 7788 putMod(ModifierState.alt, Key.Alt, "Alt+"); 7789 putMod(ModifierState.windows, Key.Shift, "Windows+"); 7790 putMod(ModifierState.shift, Key.Shift, "Shift+"); 7791 7792 if (this.key) { 7793 foreach (string kn; __traits(allMembers, Key)) { 7794 if (this.key == __traits(getMember, Key, kn)) { 7795 // HACK! 7796 static if (kn == "N0") put("0"); 7797 else static if (kn == "N1") put("1"); 7798 else static if (kn == "N2") put("2"); 7799 else static if (kn == "N3") put("3"); 7800 else static if (kn == "N4") put("4"); 7801 else static if (kn == "N5") put("5"); 7802 else static if (kn == "N6") put("6"); 7803 else static if (kn == "N7") put("7"); 7804 else static if (kn == "N8") put("8"); 7805 else static if (kn == "N9") put("9"); 7806 else put(kn); 7807 return dest[0..dpos]; 7808 } 7809 } 7810 put("Unknown"); 7811 } else { 7812 if (dpos && dest[dpos-1] == '+') --dpos; 7813 } 7814 return dest[0..dpos]; 7815 } 7816 7817 string toStr() () { return cast(string)toStrBuf!true(null); } // it is safe to cast here 7818 7819 /** Parse string into key name with modifiers. It accepts things like: 7820 * 7821 * C-H-1 -- emacs style (ctrl, and windows, and 1) 7822 * 7823 * Ctrl+Win+1 -- windows style 7824 * 7825 * Ctrl-Win-1 -- '-' is a valid delimiter too 7826 * 7827 * Ctrl Win 1 -- and space 7828 * 7829 * and even "Win + 1 + Ctrl". 7830 */ 7831 static KeyEvent parse (const(char)[] name, bool* ignoreModsOut=null, int* updown=null) nothrow @trusted @nogc { 7832 auto nanchor = name; // keep it anchored, 'cause `name` may have NO_INTERIOR set 7833 7834 // remove trailing spaces 7835 while (name.length && name[$-1] <= ' ') name = name[0..$-1]; 7836 7837 // tokens delimited by blank, '+', or '-' 7838 // null on eol 7839 const(char)[] getToken () nothrow @trusted @nogc { 7840 // remove leading spaces and delimiters 7841 while (name.length && (name[0] <= ' ' || name[0] == '+' || name[0] == '-')) name = name[1..$]; 7842 if (name.length == 0) return null; // oops, no more tokens 7843 // get token 7844 size_t epos = 0; 7845 while (epos < name.length && name[epos] > ' ' && name[epos] != '+' && name[epos] != '-') ++epos; 7846 assert(epos > 0 && epos <= name.length); 7847 auto res = name[0..epos]; 7848 name = name[epos..$]; 7849 return res; 7850 } 7851 7852 static bool strEquCI (const(char)[] s0, const(char)[] s1) pure nothrow @trusted @nogc { 7853 if (s0.length != s1.length) return false; 7854 foreach (immutable ci, char c0; s0) { 7855 if (c0 >= 'A' && c0 <= 'Z') c0 += 32; // poor man's tolower 7856 char c1 = s1[ci]; 7857 if (c1 >= 'A' && c1 <= 'Z') c1 += 32; // poor man's tolower 7858 if (c0 != c1) return false; 7859 } 7860 return true; 7861 } 7862 7863 if (ignoreModsOut !is null) *ignoreModsOut = false; 7864 if (updown !is null) *updown = -1; 7865 KeyEvent res; 7866 res.key = cast(Key)0; // just in case 7867 const(char)[] tk, tkn; // last token 7868 bool allowEmascStyle = true; 7869 bool ignoreModifiers = false; 7870 tokenloop: for (;;) { 7871 tk = tkn; 7872 tkn = getToken(); 7873 //k8: yay, i took "Bloody Mess" trait from Fallout! 7874 if (tkn.length != 0 && tk.length == 0) { tk = tkn; continue tokenloop; } 7875 if (tkn.length == 0 && tk.length == 0) break; // no more tokens 7876 if (allowEmascStyle && tkn.length != 0) { 7877 if (tk.length == 1) { 7878 char mdc = tk[0]; 7879 if (mdc >= 'a' && mdc <= 'z') mdc -= 32; // poor man's toupper() 7880 if (mdc == 'C' && (res.modifierState&ModifierState.ctrl) == 0) {res.modifierState |= ModifierState.ctrl; continue tokenloop; } 7881 if (mdc == 'M' && (res.modifierState&ModifierState.alt) == 0) { res.modifierState |= ModifierState.alt; continue tokenloop; } 7882 if (mdc == 'H' && (res.modifierState&ModifierState.windows) == 0) { res.modifierState |= ModifierState.windows; continue tokenloop; } 7883 if (mdc == 'S' && (res.modifierState&ModifierState.shift) == 0) { res.modifierState |= ModifierState.shift; continue tokenloop; } 7884 if (mdc == '*') { ignoreModifiers = true; continue tokenloop; } 7885 if (mdc == 'U' || mdc == 'R') { if (updown !is null) *updown = 0; continue tokenloop; } 7886 if (mdc == 'D' || mdc == 'P') { if (updown !is null) *updown = 1; continue tokenloop; } 7887 } 7888 } 7889 allowEmascStyle = false; 7890 if (strEquCI(tk, "Ctrl")) { res.modifierState |= ModifierState.ctrl; continue tokenloop; } 7891 if (strEquCI(tk, "Alt")) { res.modifierState |= ModifierState.alt; continue tokenloop; } 7892 if (strEquCI(tk, "Win") || strEquCI(tk, "Windows")) { res.modifierState |= ModifierState.windows; continue tokenloop; } 7893 if (strEquCI(tk, "Shift")) { res.modifierState |= ModifierState.shift; continue tokenloop; } 7894 if (strEquCI(tk, "Release")) { if (updown !is null) *updown = 0; continue tokenloop; } 7895 if (strEquCI(tk, "Press")) { if (updown !is null) *updown = 1; continue tokenloop; } 7896 if (tk == "*") { ignoreModifiers = true; continue tokenloop; } 7897 if (tk.length == 0) continue; 7898 // try key name 7899 if (res.key == 0) { 7900 // little hack 7901 if (tk.length == 1 && tk[0] >= '0' && tk[0] <= '9') { 7902 final switch (tk[0]) { 7903 case '0': tk = "N0"; break; 7904 case '1': tk = "N1"; break; 7905 case '2': tk = "N2"; break; 7906 case '3': tk = "N3"; break; 7907 case '4': tk = "N4"; break; 7908 case '5': tk = "N5"; break; 7909 case '6': tk = "N6"; break; 7910 case '7': tk = "N7"; break; 7911 case '8': tk = "N8"; break; 7912 case '9': tk = "N9"; break; 7913 } 7914 } 7915 foreach (string kn; __traits(allMembers, Key)) { 7916 if (strEquCI(tk, kn)) { res.key = __traits(getMember, Key, kn); continue tokenloop; } 7917 } 7918 } 7919 // unknown or duplicate key name, get out of here 7920 break; 7921 } 7922 if (ignoreModsOut !is null) *ignoreModsOut = ignoreModifiers; 7923 return res; // something 7924 } 7925 7926 bool opEquals() (const(char)[] name) const nothrow @trusted @nogc { 7927 enum modmask = (ModifierState.ctrl|ModifierState.alt|ModifierState.shift|ModifierState.windows); 7928 void doModKey (ref uint mask, ref Key kk, Key k, ModifierState mst) { 7929 if (kk == k) { mask |= mst; kk = cast(Key)0; } 7930 } 7931 bool ignoreMods; 7932 int updown; 7933 auto ke = KeyEvent.parse(name, &ignoreMods, &updown); 7934 if ((updown == 0 && this.pressed) || (updown == 1 && !this.pressed)) return false; 7935 if (this.key != ke.key) { 7936 // things like "ctrl+alt" are complicated 7937 uint tkm = this.modifierState&modmask; 7938 uint kkm = ke.modifierState&modmask; 7939 Key tk = this.key; 7940 // ke 7941 doModKey(kkm, ke.key, Key.Ctrl, ModifierState.ctrl); 7942 doModKey(kkm, ke.key, Key.Alt, ModifierState.alt); 7943 doModKey(kkm, ke.key, Key.Windows, ModifierState.windows); 7944 doModKey(kkm, ke.key, Key.Shift, ModifierState.shift); 7945 // this 7946 doModKey(tkm, tk, Key.Ctrl, ModifierState.ctrl); 7947 doModKey(tkm, tk, Key.Alt, ModifierState.alt); 7948 doModKey(tkm, tk, Key.Windows, ModifierState.windows); 7949 doModKey(tkm, tk, Key.Shift, ModifierState.shift); 7950 return (tk == ke.key && tkm == kkm); 7951 } 7952 return (ignoreMods || ((this.modifierState&modmask) == (ke.modifierState&modmask))); 7953 } 7954 } 7955 7956 /// Sets the application name. 7957 @property string ApplicationName(string name) { 7958 return _applicationName = name; 7959 } 7960 7961 string _applicationName; 7962 7963 /// ditto 7964 @property string ApplicationName() { 7965 if(_applicationName is null) { 7966 import core.runtime; 7967 return Runtime.args[0]; 7968 } 7969 return _applicationName; 7970 } 7971 7972 7973 /// Type of a [MouseEvent]. 7974 enum MouseEventType : int { 7975 motion = 0, /// The mouse moved inside the window 7976 buttonPressed = 1, /// A mouse button was pressed or the wheel was spun 7977 buttonReleased = 2, /// A mouse button was released 7978 } 7979 7980 // FIXME: mouse move should be distinct from presses+releases, so we can avoid subscribing to those events in X unnecessarily 7981 /++ 7982 Listen for this on your event listeners if you are interested in mouse action. 7983 7984 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. 7985 7986 Examples: 7987 7988 This will draw boxes on the window with the mouse as you hold the left button. 7989 --- 7990 import arsd.simpledisplay; 7991 7992 void main() { 7993 auto window = new SimpleWindow(); 7994 7995 window.eventLoop(0, 7996 (MouseEvent ev) { 7997 if(ev.modifierState & ModifierState.leftButtonDown) { 7998 auto painter = window.draw(); 7999 painter.fillColor = Color.red; 8000 painter.outlineColor = Color.black; 8001 painter.drawRectangle(Point(ev.x / 16 * 16, ev.y / 16 * 16), 16, 16); 8002 } 8003 } 8004 ); 8005 } 8006 --- 8007 +/ 8008 struct MouseEvent { 8009 MouseEventType type; /// movement, press, release, double click. See [MouseEventType] 8010 8011 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. 8012 int y; /// Current Y position of the cursor when the event fired. 8013 8014 int dx; /// Change in X position since last report 8015 int dy; /// Change in Y position since last report 8016 8017 MouseButton button; /// See [MouseButton] 8018 int modifierState; /// See [ModifierState] 8019 8020 version(X11) 8021 private Time timestamp; 8022 8023 /// Returns a linear representation of mouse button, 8024 /// for use with static arrays. Guaranteed to be >= 0 && <= 15 8025 /// 8026 /// Its implementation is based on range-limiting `core.bitop.bsf(button) + 1`. 8027 @property ubyte buttonLinear() const { 8028 import core.bitop; 8029 if(button == 0) 8030 return 0; 8031 return (bsf(button) + 1) & 0b1111; 8032 } 8033 8034 bool doubleClick; /// was it a double click? Only set on type == [MouseEventType.buttonPressed] 8035 8036 SimpleWindow window; /// The window in which the event happened. 8037 8038 Point globalCoordinates() { 8039 Point p; 8040 if(window is null) 8041 throw new Exception("wtf"); 8042 static if(UsingSimpledisplayX11) { 8043 Window child; 8044 XTranslateCoordinates( 8045 XDisplayConnection.get, 8046 window.impl.window, 8047 RootWindow(XDisplayConnection.get, DefaultScreen(XDisplayConnection.get)), 8048 x, y, &p.x, &p.y, &child); 8049 return p; 8050 } else version(Windows) { 8051 POINT[1] points; 8052 points[0].x = x; 8053 points[0].y = y; 8054 MapWindowPoints( 8055 window.impl.hwnd, 8056 null, 8057 points.ptr, 8058 points.length 8059 ); 8060 p.x = points[0].x; 8061 p.y = points[0].y; 8062 8063 return p; 8064 } else version(OSXCocoa) { 8065 throw new NotYetImplementedException(); 8066 } else version(Emscripten) { 8067 throw new NotYetImplementedException(); 8068 } else static assert(0); 8069 } 8070 8071 bool opEquals() (const(char)[] str) pure nothrow @trusted @nogc { return equStr(this, str); } 8072 8073 /** 8074 can contain emacs-like modifier prefix 8075 case-insensitive names: 8076 lmbX/leftX 8077 rmbX/rightX 8078 mmbX/middleX 8079 wheelX 8080 motion (no prefix allowed) 8081 'X' is either "up" or "down" (or "-up"/"-down"); if omited, means "down" 8082 */ 8083 static bool equStr() (scope auto ref const MouseEvent event, const(char)[] str) pure nothrow @trusted @nogc { 8084 if (str.length == 0) return false; // just in case 8085 debug(arsd_mevent_strcmp) { import iv.cmdcon; conwriteln("str=<", str, ">"); } 8086 enum Flag : uint { Up = 0x8000_0000U, Down = 0x4000_0000U, Any = 0x1000_0000U } 8087 auto anchor = str; 8088 uint mods = 0; // uint.max == any 8089 // interesting bits in kmod 8090 uint kmodmask = 8091 ModifierState.shift| 8092 ModifierState.ctrl| 8093 ModifierState.alt| 8094 ModifierState.windows| 8095 ModifierState.leftButtonDown| 8096 ModifierState.middleButtonDown| 8097 ModifierState.rightButtonDown| 8098 0; 8099 uint lastButt = uint.max; // otherwise, bit 31 means "down" 8100 bool wasButtons = false; 8101 while (str.length) { 8102 if (str.ptr[0] <= ' ') { 8103 while (str.length && str.ptr[0] <= ' ') str = str[1..$]; 8104 continue; 8105 } 8106 // one-letter modifier? 8107 if (str.length >= 2 && str.ptr[1] == '-') { 8108 switch (str.ptr[0]) { 8109 case '*': // "any" modifier (cannot be undone) 8110 mods = mods.max; 8111 break; 8112 case 'C': case 'c': // emacs "ctrl" 8113 if (mods != mods.max) mods |= ModifierState.ctrl; 8114 break; 8115 case 'M': case 'm': // emacs "meta" 8116 if (mods != mods.max) mods |= ModifierState.alt; 8117 break; 8118 case 'S': case 's': // emacs "shift" 8119 if (mods != mods.max) mods |= ModifierState.shift; 8120 break; 8121 case 'H': case 'h': // emacs "hyper" (aka winkey) 8122 if (mods != mods.max) mods |= ModifierState.windows; 8123 break; 8124 default: 8125 return false; // unknown modifier 8126 } 8127 str = str[2..$]; 8128 continue; 8129 } 8130 // word 8131 char[16] buf = void; // locased 8132 auto wep = 0; 8133 while (str.length) { 8134 immutable char ch = str.ptr[0]; 8135 if (ch <= ' ' || ch == '-') break; 8136 str = str[1..$]; 8137 if (wep > buf.length) return false; // too long 8138 if (ch >= 'A' && ch <= 'Z') buf.ptr[wep++] = cast(char)(ch+32); // poor man tolower 8139 else if (ch >= 'a' && ch <= 'z') buf.ptr[wep++] = ch; 8140 else return false; // invalid char 8141 } 8142 if (wep == 0) return false; // just in case 8143 uint bnum; 8144 enum UpDown { None = -1, Up, Down, Any } 8145 auto updown = UpDown.None; // 0: up; 1: down 8146 switch (buf[0..wep]) { 8147 // left button 8148 case "lmbup": case "leftup": updown = UpDown.Up; goto case "lmb"; 8149 case "lmbdown": case "leftdown": updown = UpDown.Down; goto case "lmb"; 8150 case "lmbany": case "leftany": updown = UpDown.Any; goto case "lmb"; 8151 case "lmb": case "left": bnum = 0; break; 8152 // middle button 8153 case "mmbup": case "middleup": updown = UpDown.Up; goto case "mmb"; 8154 case "mmbdown": case "middledown": updown = UpDown.Down; goto case "mmb"; 8155 case "mmbany": case "middleany": updown = UpDown.Any; goto case "mmb"; 8156 case "mmb": case "middle": bnum = 1; break; 8157 // right button 8158 case "rmbup": case "rightup": updown = UpDown.Up; goto case "rmb"; 8159 case "rmbdown": case "rightdown": updown = UpDown.Down; goto case "rmb"; 8160 case "rmbany": case "rightany": updown = UpDown.Any; goto case "rmb"; 8161 case "rmb": case "right": bnum = 2; break; 8162 // wheel 8163 case "wheelup": updown = UpDown.Up; goto case "wheel"; 8164 case "wheeldown": updown = UpDown.Down; goto case "wheel"; 8165 case "wheelany": updown = UpDown.Any; goto case "wheel"; 8166 case "wheel": bnum = 3; break; 8167 // motion 8168 case "motion": bnum = 7; break; 8169 // unknown 8170 default: return false; 8171 } 8172 debug(arsd_mevent_strcmp) { import iv.cmdcon; conprintfln(" 0: mods=0x%08x; bnum=%u; updown=%s [%s]", mods, bnum, updown, str); } 8173 // parse possible "-up" or "-down" 8174 if (updown == UpDown.None && bnum < 7 && str.length > 0 && str.ptr[0] == '-') { 8175 wep = 0; 8176 foreach (immutable idx, immutable char ch; str[1..$]) { 8177 if (ch <= ' ' || ch == '-') break; 8178 assert(idx == wep); // for now; trick 8179 if (wep > buf.length) { wep = 0; break; } // too long 8180 if (ch >= 'A' && ch <= 'Z') buf.ptr[wep++] = cast(char)(ch+32); // poor man tolower 8181 else if (ch >= 'a' && ch <= 'z') buf.ptr[wep++] = ch; 8182 else { wep = 0; break; } // invalid char 8183 } 8184 if (wep == 2 && buf[0..wep] == "up") updown = UpDown.Up; 8185 else if (wep == 4 && buf[0..wep] == "down") updown = UpDown.Down; 8186 else if (wep == 3 && buf[0..wep] == "any") updown = UpDown.Any; 8187 // remove parsed part 8188 if (updown != UpDown.None) str = str[wep+1..$]; 8189 } 8190 if (updown == UpDown.None) { 8191 updown = UpDown.Down; 8192 } 8193 wasButtons = wasButtons || (bnum <= 2); 8194 //assert(updown != UpDown.None); 8195 debug(arsd_mevent_strcmp) { import iv.cmdcon; conprintfln(" 1: mods=0x%08x; bnum=%u; updown=%s [%s]", mods, bnum, updown, str); } 8196 // if we have a previous button, it goes to modifiers (unless it is a wheel or motion) 8197 if (lastButt != lastButt.max) { 8198 if ((lastButt&0xff) >= 3) return false; // wheel or motion 8199 if (mods != mods.max) { 8200 uint butbit = 0; 8201 final switch (lastButt&0x03) { 8202 case 0: butbit = ModifierState.leftButtonDown; break; 8203 case 1: butbit = ModifierState.middleButtonDown; break; 8204 case 2: butbit = ModifierState.rightButtonDown; break; 8205 } 8206 if (lastButt&Flag.Down) mods |= butbit; 8207 else if (lastButt&Flag.Up) mods &= ~butbit; 8208 else if (lastButt&Flag.Any) kmodmask &= ~butbit; 8209 } 8210 } 8211 // remember last button 8212 lastButt = bnum|(updown == UpDown.Up ? Flag.Up : updown == UpDown.Any ? Flag.Any : Flag.Down); 8213 } 8214 // no button -- nothing to do 8215 if (lastButt == lastButt.max) return false; 8216 // done parsing, check if something's left 8217 foreach (immutable char ch; str) if (ch > ' ') return false; // oops 8218 // remove action button from mask 8219 if ((lastButt&0xff) < 3) { 8220 final switch (lastButt&0x03) { 8221 case 0: kmodmask &= ~cast(uint)ModifierState.leftButtonDown; break; 8222 case 1: kmodmask &= ~cast(uint)ModifierState.middleButtonDown; break; 8223 case 2: kmodmask &= ~cast(uint)ModifierState.rightButtonDown; break; 8224 } 8225 } 8226 // special case: "Motion" means "ignore buttons" 8227 if ((lastButt&0xff) == 7 && !wasButtons) { 8228 debug(arsd_mevent_strcmp) { import iv.cmdcon; conwriteln(" *: special motion"); } 8229 kmodmask &= ~cast(uint)(ModifierState.leftButtonDown|ModifierState.middleButtonDown|ModifierState.rightButtonDown); 8230 } 8231 uint kmod = event.modifierState&kmodmask; 8232 debug(arsd_mevent_strcmp) { import iv.cmdcon; conprintfln(" *: mods=0x%08x; lastButt=0x%08x; kmod=0x%08x; type=%s", mods, lastButt, kmod, event.type); } 8233 // check modifier state 8234 if (mods != mods.max) { 8235 if (kmod != mods) return false; 8236 } 8237 // now check type 8238 if ((lastButt&0xff) == 7) { 8239 // motion 8240 if (event.type != MouseEventType.motion) return false; 8241 } else if ((lastButt&0xff) == 3) { 8242 // wheel 8243 if (lastButt&Flag.Up) return (event.type == MouseEventType.buttonPressed && event.button == MouseButton.wheelUp); 8244 if (lastButt&Flag.Down) return (event.type == MouseEventType.buttonPressed && event.button == MouseButton.wheelDown); 8245 if (lastButt&Flag.Any) return (event.type == MouseEventType.buttonPressed && (event.button == MouseButton.wheelUp || event.button == MouseButton.wheelUp)); 8246 return false; 8247 } else { 8248 // buttons 8249 if (((lastButt&Flag.Down) != 0 && event.type != MouseEventType.buttonPressed) || 8250 ((lastButt&Flag.Up) != 0 && event.type != MouseEventType.buttonReleased)) 8251 { 8252 return false; 8253 } 8254 // button number 8255 switch (lastButt&0x03) { 8256 case 0: if (event.button != MouseButton.left) return false; break; 8257 case 1: if (event.button != MouseButton.middle) return false; break; 8258 case 2: if (event.button != MouseButton.right) return false; break; 8259 default: return false; 8260 } 8261 } 8262 return true; 8263 } 8264 } 8265 8266 version(arsd_mevent_strcmp_test) unittest { 8267 MouseEvent event; 8268 event.type = MouseEventType.buttonPressed; 8269 event.button = MouseButton.left; 8270 event.modifierState = ModifierState.ctrl; 8271 assert(event == "C-LMB"); 8272 assert(event != "C-LMBUP"); 8273 assert(event != "C-LMB-UP"); 8274 assert(event != "C-S-LMB"); 8275 assert(event == "*-LMB"); 8276 assert(event != "*-LMB-UP"); 8277 8278 event.type = MouseEventType.buttonReleased; 8279 assert(event != "C-LMB"); 8280 assert(event == "C-LMBUP"); 8281 assert(event == "C-LMB-UP"); 8282 assert(event != "C-S-LMB"); 8283 assert(event != "*-LMB"); 8284 assert(event == "*-LMB-UP"); 8285 8286 event.button = MouseButton.right; 8287 event.modifierState |= ModifierState.shift; 8288 event.type = MouseEventType.buttonPressed; 8289 assert(event != "C-LMB"); 8290 assert(event != "C-LMBUP"); 8291 assert(event != "C-LMB-UP"); 8292 assert(event != "C-S-LMB"); 8293 assert(event != "*-LMB"); 8294 assert(event != "*-LMB-UP"); 8295 8296 assert(event != "C-RMB"); 8297 assert(event != "C-RMBUP"); 8298 assert(event != "C-RMB-UP"); 8299 assert(event == "C-S-RMB"); 8300 assert(event == "*-RMB"); 8301 assert(event != "*-RMB-UP"); 8302 } 8303 8304 /// This gives a few more options to drawing lines and such 8305 struct Pen { 8306 Color color; /// the foreground color 8307 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. 8308 Style style; /// See [Style] 8309 /+ 8310 // From X.h 8311 8312 #define LineSolid 0 8313 #define LineOnOffDash 1 8314 #define LineDoubleDash 2 8315 LineDou- The full path of the line is drawn, but the 8316 bleDash even dashes are filled differently from the 8317 odd dashes (see fill-style) with CapButt 8318 style used where even and odd dashes meet. 8319 8320 8321 8322 /* capStyle */ 8323 8324 #define CapNotLast 0 8325 #define CapButt 1 8326 #define CapRound 2 8327 #define CapProjecting 3 8328 8329 /* joinStyle */ 8330 8331 #define JoinMiter 0 8332 #define JoinRound 1 8333 #define JoinBevel 2 8334 8335 /* fillStyle */ 8336 8337 #define FillSolid 0 8338 #define FillTiled 1 8339 #define FillStippled 2 8340 #define FillOpaqueStippled 3 8341 8342 8343 +/ 8344 /// Style of lines drawn 8345 enum Style { 8346 Solid, /// a solid line 8347 Dashed, /// a dashed line 8348 Dotted, /// a dotted line 8349 } 8350 } 8351 8352 8353 /++ 8354 Represents an in-memory image in the format that the GUI expects, but with its raw data available to your program. 8355 8356 8357 On Windows, this means a device-independent bitmap. On X11, it is an XImage. 8358 8359 $(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.) 8360 8361 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. 8362 8363 If you intend to draw an image to screen several times, you will want to convert it into a [Sprite]. 8364 8365 $(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. 8366 8367 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! 8368 8369 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!) 8370 8371 Please call `destroy(image);` when you are done with it. The easiest way to do this is with scope: 8372 8373 --- 8374 auto image = new Image(256, 256); 8375 scope(exit) destroy(image); 8376 --- 8377 8378 As long as you don't hold on to it outside the scope. 8379 8380 I might change it to be an owned pointer at some point in the future. 8381 8382 ) 8383 8384 Drawing pixels on the image may be simple, using the `opIndexAssign` function, but 8385 you can also often get a fair amount of speedup by getting the raw data format and 8386 writing some custom code. 8387 8388 FIXME INSERT EXAMPLES HERE 8389 8390 8391 +/ 8392 final class Image { 8393 /// 8394 this(int width, int height, bool forcexshm=false, bool enableAlpha = false) { 8395 this.width = width; 8396 this.height = height; 8397 this.enableAlpha = enableAlpha; 8398 8399 impl.createImage(width, height, forcexshm, enableAlpha); 8400 } 8401 8402 /// 8403 this(Size size, bool forcexshm=false, bool enableAlpha = false) { 8404 this(size.width, size.height, forcexshm, enableAlpha); 8405 } 8406 8407 private bool suppressDestruction; 8408 8409 version(X11) 8410 this(XImage* handle) { 8411 this.handle = handle; 8412 this.rawData = cast(ubyte*) handle.data; 8413 this.width = handle.width; 8414 this.height = handle.height; 8415 this.enableAlpha = handle.depth == 32; 8416 suppressDestruction = true; 8417 } 8418 8419 ~this() { 8420 if(suppressDestruction) return; 8421 impl.dispose(); 8422 } 8423 8424 // these numbers are used for working with rawData itself, skipping putPixel and getPixel 8425 /// if you do the math yourself you might be able to optimize it. Call these functions only once and cache the value. 8426 pure const @system nothrow { 8427 /* 8428 To use these to draw a blue rectangle with size WxH at position X,Y... 8429 8430 // make certain that it will fit before we proceed 8431 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! 8432 8433 // gather all the values you'll need up front. These can be kept until the image changes size if you want 8434 // (though calculating them isn't really that expensive). 8435 auto nextLineAdjustment = img.adjustmentForNextLine(); 8436 auto offR = img.redByteOffset(); 8437 auto offB = img.blueByteOffset(); 8438 auto offG = img.greenByteOffset(); 8439 auto bpp = img.bytesPerPixel(); 8440 8441 auto data = img.getDataPointer(); 8442 8443 // figure out the starting byte offset 8444 auto offset = img.offsetForTopLeftPixel() + nextLineAdjustment*Y + bpp * X; 8445 8446 auto startOfLine = data + offset; // get our pointer lined up on the first pixel 8447 8448 // and now our drawing loop for the rectangle 8449 foreach(y; 0 .. H) { 8450 auto data = startOfLine; // we keep the start of line separately so moving to the next line is simple and portable 8451 foreach(x; 0 .. W) { 8452 // write our color 8453 data[offR] = 0; 8454 data[offG] = 0; 8455 data[offB] = 255; 8456 8457 data += bpp; // moving to the next pixel is just an addition... 8458 } 8459 startOfLine += nextLineAdjustment; 8460 } 8461 8462 8463 As you can see, the loop itself was very simple thanks to the calculations being moved outside. 8464 8465 FIXME: I wonder if I can make the pixel formats consistently 32 bit across platforms, so the color offsets 8466 can be made into a bitmask or something so we can write them as *uint... 8467 */ 8468 8469 /// 8470 int offsetForTopLeftPixel() { 8471 version(X11) { 8472 return 0; 8473 } else version(Windows) { 8474 if(enableAlpha) { 8475 return (width * 4) * (height - 1); 8476 } else { 8477 return (((cast(int) width * 3 + 3) / 4) * 4) * (height - 1); 8478 } 8479 } else version(OSXCocoa) { 8480 return 0 ; //throw new NotYetImplementedException(); 8481 } else version(Emscripten) { 8482 return 0; 8483 } else static assert(0, "fill in this info for other OSes"); 8484 } 8485 8486 /// 8487 int offsetForPixel(int x, int y) { 8488 version(X11) { 8489 auto offset = (y * width + x) * 4; 8490 return offset; 8491 } else version(Windows) { 8492 if(enableAlpha) { 8493 auto itemsPerLine = width * 4; 8494 // remember, bmps are upside down 8495 auto offset = itemsPerLine * (height - y - 1) + x * 4; 8496 return offset; 8497 } else { 8498 auto itemsPerLine = ((cast(int) width * 3 + 3) / 4) * 4; 8499 // remember, bmps are upside down 8500 auto offset = itemsPerLine * (height - y - 1) + x * 3; 8501 return offset; 8502 } 8503 } else version(OSXCocoa) { 8504 return (y * width + x) * 4 ; //throw new NotYetImplementedException(); 8505 } else version(Emscripten) { 8506 return (y * width + x) * 4 ; //throw new NotYetImplementedException(); 8507 } else static assert(0, "fill in this info for other OSes"); 8508 } 8509 8510 /// 8511 int adjustmentForNextLine() { 8512 version(X11) { 8513 return width * 4; 8514 } else version(Windows) { 8515 // windows bmps are upside down, so the adjustment is actually negative 8516 if(enableAlpha) 8517 return - (cast(int) width * 4); 8518 else 8519 return -((cast(int) width * 3 + 3) / 4) * 4; 8520 } else version(OSXCocoa) { 8521 return width * 4 ; //throw new NotYetImplementedException(); 8522 } else version(Emscripten) { 8523 return width * 4 ; //throw new NotYetImplementedException(); 8524 } else static assert(0, "fill in this info for other OSes"); 8525 } 8526 8527 /// once you have the position of a pixel, use these to get to the proper color 8528 int redByteOffset() { 8529 version(X11) { 8530 return 2; 8531 } else version(Windows) { 8532 return 2; 8533 } else version(OSXCocoa) { 8534 return 2 ; //throw new NotYetImplementedException(); 8535 } else version(Emscripten) { 8536 return 2 ; //throw new NotYetImplementedException(); 8537 } else static assert(0, "fill in this info for other OSes"); 8538 } 8539 8540 /// 8541 int greenByteOffset() { 8542 version(X11) { 8543 return 1; 8544 } else version(Windows) { 8545 return 1; 8546 } else version(OSXCocoa) { 8547 return 1 ; //throw new NotYetImplementedException(); 8548 } else version(Emscripten) { 8549 return 1 ; //throw new NotYetImplementedException(); 8550 } else static assert(0, "fill in this info for other OSes"); 8551 } 8552 8553 /// 8554 int blueByteOffset() { 8555 version(X11) { 8556 return 0; 8557 } else version(Windows) { 8558 return 0; 8559 } else version(OSXCocoa) { 8560 return 0 ; //throw new NotYetImplementedException(); 8561 } else version(Emscripten) { 8562 return 0 ; //throw new NotYetImplementedException(); 8563 } else static assert(0, "fill in this info for other OSes"); 8564 } 8565 8566 /// Only valid if [enableAlpha] is true 8567 int alphaByteOffset() { 8568 version(X11) { 8569 return 3; 8570 } else version(Windows) { 8571 return 3; 8572 } else version(OSXCocoa) { 8573 return 3; //throw new NotYetImplementedException(); 8574 } else version(Emscripten) { 8575 return 3 ; //throw new NotYetImplementedException(); 8576 } else static assert(0, "fill in this info for other OSes"); 8577 } 8578 } 8579 8580 /// 8581 final void putPixel(int x, int y, Color c) { 8582 if(x < 0 || x >= width) 8583 return; 8584 if(y < 0 || y >= height) 8585 return; 8586 8587 impl.setPixel(x, y, c); 8588 } 8589 8590 /// 8591 final Color getPixel(int x, int y) { 8592 if(x < 0 || x >= width) 8593 return Color.transparent; 8594 if(y < 0 || y >= height) 8595 return Color.transparent; 8596 8597 version(OSXCocoa) throw new NotYetImplementedException(); else 8598 return impl.getPixel(x, y); 8599 } 8600 8601 /// 8602 final void opIndexAssign(Color c, int x, int y) { 8603 putPixel(x, y, c); 8604 } 8605 8606 /// 8607 TrueColorImage toTrueColorImage() { 8608 auto tci = new TrueColorImage(width, height); 8609 convertToRgbaBytes(tci.imageData.bytes); 8610 return tci; 8611 } 8612 8613 /// 8614 static Image fromMemoryImage(MemoryImage i, bool enableAlpha = false, bool premultiply = true) { 8615 auto tci = i.getAsTrueColorImage(); 8616 auto img = new Image(tci.width, tci.height, false, enableAlpha); 8617 static if(UsingSimpledisplayX11) 8618 img.premultiply = premultiply; 8619 img.setRgbaBytes(tci.imageData.bytes); 8620 return img; 8621 } 8622 8623 /// this is here for interop with arsd.image. where can be a TrueColorImage's data member 8624 /// if you pass in a buffer, it will put it right there. length must be width*height*4 already 8625 /// if you pass null, it will allocate a new one. 8626 ubyte[] getRgbaBytes(ubyte[] where = null) { 8627 if(where is null) 8628 where = new ubyte[this.width*this.height*4]; 8629 convertToRgbaBytes(where); 8630 return where; 8631 } 8632 8633 /// this is here for interop with arsd.image. from can be a TrueColorImage's data member 8634 void setRgbaBytes(in ubyte[] from ) { 8635 assert(from.length == this.width * this.height * 4); 8636 setFromRgbaBytes(from); 8637 } 8638 8639 // FIXME: make properly cross platform by getting rgba right 8640 8641 /// warning: this is not portable across platforms because the data format can change 8642 ubyte* getDataPointer() { 8643 return impl.rawData; 8644 } 8645 8646 /// for use with getDataPointer 8647 final int bytesPerLine() const pure @safe nothrow { 8648 version(Windows) 8649 return enableAlpha ? (width * 4) : (((cast(int) width * 3 + 3) / 4) * 4); 8650 else version(X11) 8651 return 4 * width; 8652 else version(OSXCocoa) 8653 return 4 * width; 8654 else static assert(0); 8655 } 8656 8657 /// for use with getDataPointer 8658 final int bytesPerPixel() const pure @safe nothrow { 8659 version(Windows) 8660 return enableAlpha ? 4 : 3; 8661 else version(X11) 8662 return 4; 8663 else version(OSXCocoa) 8664 return 4; 8665 else static assert(0); 8666 } 8667 8668 /// 8669 immutable int width; 8670 8671 /// 8672 immutable int height; 8673 8674 /// 8675 immutable bool enableAlpha; 8676 //private: 8677 mixin NativeImageImplementation!() impl; 8678 } 8679 8680 /++ 8681 A convenience function to pop up a window displaying the image. 8682 If you pass a win, it will draw the image in it. Otherwise, it will 8683 create a window with the size of the image and run its event loop, closing 8684 when a key is pressed. 8685 8686 History: 8687 `BlockingMode` parameter added on December 8, 2021. Previously, it would 8688 always block until the application quit which could cause bizarre behavior 8689 inside a more complex application. Now, the default is to block until 8690 this window closes if it is the only event loop running, and otherwise, 8691 not to block at all and just pop up the display window asynchronously. 8692 +/ 8693 void displayImage(Image image, SimpleWindow win = null, BlockingMode bm = BlockingMode.untilWindowCloses | BlockingMode.onlyIfNotNested) { 8694 if(win is null) { 8695 win = new SimpleWindow(image); 8696 { 8697 auto p = win.draw; 8698 p.drawImage(Point(0, 0), image); 8699 } 8700 win.eventLoopWithBlockingMode( 8701 bm, 0, 8702 (KeyEvent ev) { 8703 if (ev.pressed && (ev.key == Key.Escape || ev.key == Key.Space)) win.close(); 8704 } ); 8705 } else { 8706 win.image = image; 8707 } 8708 } 8709 8710 enum FontWeight : int { 8711 dontcare = 0, 8712 thin = 100, 8713 extralight = 200, 8714 light = 300, 8715 regular = 400, 8716 medium = 500, 8717 semibold = 600, 8718 bold = 700, 8719 extrabold = 800, 8720 heavy = 900 8721 } 8722 8723 /++ 8724 Interface with the common functionality for font measurements between [OperatingSystemFont] and [DrawableFont]. 8725 8726 History: 8727 Added October 24, 2022. The methods were already on [OperatingSystemFont] before that. 8728 +/ 8729 interface MeasurableFont { 8730 /++ 8731 Returns true if it is a monospace font, meaning each of the 8732 glyphs (at least the ascii characters) have matching width 8733 and no kerning, so you can determine the display width of some 8734 strings by simply multiplying the string width by [averageWidth]. 8735 8736 (Please note that multiply doesn't $(I actually) work in general, 8737 consider characters like tab and newline, but it does sometimes.) 8738 +/ 8739 bool isMonospace(); 8740 8741 /++ 8742 The average width of glyphs in the font, traditionally equal to the 8743 width of the lowercase x. Can be used to estimate bounding boxes, 8744 especially if the font [isMonospace]. 8745 8746 Given in pixels. 8747 +/ 8748 int averageWidth(); 8749 /++ 8750 The height of the bounding box of a line. 8751 +/ 8752 int height(); 8753 /++ 8754 The maximum ascent of a glyph above the baseline. 8755 8756 Given in pixels. 8757 +/ 8758 int ascent(); 8759 /++ 8760 The maximum descent of a glyph below the baseline. For example, how low the g might go. 8761 8762 Given in pixels. 8763 +/ 8764 int descent(); 8765 /++ 8766 The display width of the given string, and if you provide a window, it will use it to 8767 make the pixel count on screen more accurate too, but this shouldn't generally be necessary. 8768 8769 Given in pixels. 8770 +/ 8771 int stringWidth(scope const(char)[] s, SimpleWindow window = null); 8772 8773 } 8774 8775 // FIXME: i need a font cache and it needs to handle disconnects. 8776 8777 /++ 8778 Represents a font loaded off the operating system or the X server. 8779 8780 8781 While the api here is unified cross platform, the fonts are not necessarily 8782 available, even across machines of the same platform, so be sure to always check 8783 for null (using [isNull]) and have a fallback plan. 8784 8785 When you have a font you like, use [ScreenPainter.setFont] to load it for drawing. 8786 8787 Worst case, a null font will automatically fall back to the default font loaded 8788 for your system. 8789 +/ 8790 class OperatingSystemFont : MeasurableFont { 8791 // FIXME: when the X Connection is lost, these need to be invalidated! 8792 // that means I need to store the original stuff again to reconstruct it too. 8793 8794 version(Emscripten) { 8795 void* font; 8796 } else version(X11) { 8797 XFontStruct* font; 8798 XFontSet fontset; 8799 8800 version(with_xft) { 8801 XftFont* xftFont; 8802 bool isXft; 8803 } 8804 } else version(Windows) { 8805 HFONT font; 8806 int width_; 8807 int height_; 8808 } else version(OSXCocoa) { 8809 NSFont font; 8810 } else static assert(0); 8811 8812 /++ 8813 Constructs the class and immediately calls [load]. 8814 +/ 8815 this(string name, int size = 0, FontWeight weight = FontWeight.dontcare, bool italic = false) { 8816 load(name, size, weight, italic); 8817 } 8818 8819 /++ 8820 Constructs the object, but does nothing. Call one of [load] or [loadDefault] to populate the object. 8821 8822 You can also call the platform-specific [loadXft], [loadCoreX], or [loadWin32] functions if appropriate for you. 8823 8824 History: 8825 Added January 24, 2021. 8826 +/ 8827 this() { 8828 // this space intentionally left blank 8829 } 8830 8831 /++ 8832 Constructs a copy of the given font object. 8833 8834 History: 8835 Added January 7, 2023. 8836 +/ 8837 this(OperatingSystemFont font) { 8838 if(font is null || font.loadedInfo is LoadedInfo.init) 8839 loadDefault(); 8840 else 8841 load(font.loadedInfo.tupleof); 8842 } 8843 8844 /++ 8845 Loads specifically with the Xft library - a freetype font from a fontconfig string. 8846 8847 History: 8848 Added November 13, 2020. 8849 +/ 8850 version(with_xft) 8851 bool loadXft(string name, int size = 0, FontWeight weight = FontWeight.dontcare, bool italic = false) { 8852 unload(); 8853 8854 if(!XftLibrary.attempted) { 8855 XftLibrary.loadDynamicLibrary(); 8856 } 8857 8858 if(!XftLibrary.loadSuccessful) 8859 return false; 8860 8861 auto display = XDisplayConnection.get; 8862 8863 char[256] nameBuffer = void; 8864 int nbp = 0; 8865 8866 void add(in char[] a) { 8867 nameBuffer[nbp .. nbp + a.length] = a[]; 8868 nbp += a.length; 8869 } 8870 add(name); 8871 8872 if(size) { 8873 add(":size="); 8874 add(toInternal!string(size)); 8875 } 8876 if(weight != FontWeight.dontcare && weight != 400) { 8877 if(weight < 400) 8878 add(":style=Light"); 8879 else 8880 add(":style=Bold"); 8881 add(":weight="); 8882 add(weightToString(weight)); 8883 } 8884 if(italic) { 8885 if(weight == FontWeight.dontcare) 8886 add(":style=Italic"); 8887 add(":slant=100"); 8888 } 8889 8890 nameBuffer[nbp] = 0; 8891 8892 this.xftFont = XftFontOpenName( 8893 display, 8894 DefaultScreen(display), 8895 nameBuffer.ptr 8896 ); 8897 8898 this.isXft = true; 8899 8900 if(xftFont !is null) { 8901 isMonospace_ = stringWidth("x") == stringWidth("M"); 8902 ascent_ = xftFont.ascent; 8903 descent_ = xftFont.descent; 8904 } 8905 8906 return !isNull(); 8907 } 8908 8909 /++ 8910 Lists available fonts from the system that match the given pattern, finding names that are suitable for passing to [OperatingSystemFont]'s constructor. 8911 8912 8913 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. 8914 8915 If `pattern` is null, it returns all available font families. 8916 8917 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. 8918 8919 The format of the pattern is platform-specific. 8920 8921 History: 8922 Added May 1, 2021 (dub v9.5) 8923 +/ 8924 static void listFonts(string pattern, bool delegate(in char[] name) handler) { 8925 version(Windows) { 8926 auto hdc = GetDC(null); 8927 scope(exit) ReleaseDC(null, hdc); 8928 LOGFONT logfont; 8929 static extern(Windows) int proc(const LOGFONT* lf, const TEXTMETRIC* tm, DWORD type, LPARAM p) { 8930 auto localHandler = *(cast(typeof(handler)*) p); 8931 return localHandler(lf.lfFaceName[].sliceCString) ? 1 : 0; 8932 } 8933 EnumFontFamiliesEx(hdc, &logfont, &proc, cast(LPARAM) &handler, 0); 8934 } else version(X11) { 8935 //import core.stdc.stdio; 8936 bool done = false; 8937 version(with_xft) { 8938 if(!XftLibrary.attempted) { 8939 XftLibrary.loadDynamicLibrary(); 8940 } 8941 8942 if(!XftLibrary.loadSuccessful) 8943 goto skipXft; 8944 8945 if(!FontConfigLibrary.attempted) 8946 FontConfigLibrary.loadDynamicLibrary(); 8947 if(!FontConfigLibrary.loadSuccessful) 8948 goto skipXft; 8949 8950 { 8951 auto got = XftListFonts(XDisplayConnection.get, 0, null, "family".ptr, "style".ptr, null); 8952 if(got is null) 8953 goto skipXft; 8954 scope(exit) FcFontSetDestroy(got); 8955 8956 auto fontPatterns = got.fonts[0 .. got.nfont]; 8957 foreach(candidate; fontPatterns) { 8958 char* where, whereStyle; 8959 8960 char* pmg = FcNameUnparse(candidate); 8961 8962 //FcPatternGetString(candidate, "family", 0, &where); 8963 //FcPatternGetString(candidate, "style", 0, &whereStyle); 8964 //if(where && whereStyle) { 8965 if(pmg) { 8966 if(!handler(pmg.sliceCString)) 8967 return; 8968 //printf("%s || %s %s\n", pmg, where, whereStyle); 8969 } 8970 } 8971 } 8972 } 8973 8974 skipXft: 8975 8976 if(pattern is null) 8977 pattern = "*"; 8978 8979 int count; 8980 auto coreFontsRaw = XListFonts(XDisplayConnection.get, pattern.toStringz, 10000 /* max return */, &count); 8981 scope(exit) XFreeFontNames(coreFontsRaw); 8982 8983 auto coreFonts = coreFontsRaw[0 .. count]; 8984 8985 foreach(font; coreFonts) { 8986 char[128] tmp; 8987 tmp[0 ..5] = "core:"; 8988 auto cf = font.sliceCString; 8989 if(5 + cf.length > tmp.length) 8990 assert(0, "a font name was too long, sorry i didn't bother implementing a fallback"); 8991 tmp[5 .. 5 + cf.length] = cf; 8992 if(!handler(tmp[0 .. 5 + cf.length])) 8993 return; 8994 } 8995 } 8996 } 8997 8998 /++ 8999 Returns the raw content of the ttf file, if possible. This allows you to use OperatingSystemFont 9000 to look up fonts that you then pass to things like [arsd.ttf.OpenGlLimitedFont] or [arsd.nanovega]. 9001 9002 Returns null if impossible. It is impossible if the loaded font is not a local TTF file or if the 9003 underlying system doesn't support returning the raw bytes. 9004 9005 History: 9006 Added September 10, 2021 (dub v10.3) 9007 +/ 9008 ubyte[] getTtfBytes() { 9009 if(isNull) 9010 return null; 9011 9012 version(Windows) { 9013 auto dc = GetDC(null); 9014 auto orig = SelectObject(dc, font); 9015 9016 scope(exit) { 9017 SelectObject(dc, orig); 9018 ReleaseDC(null, dc); 9019 } 9020 9021 auto res = GetFontData(dc, 0 /* whole file */, 0 /* offset */, null, 0); 9022 if(res == GDI_ERROR) 9023 return null; 9024 9025 ubyte[] buffer = new ubyte[](res); 9026 res = GetFontData(dc, 0 /* whole file */, 0 /* offset */, buffer.ptr, cast(DWORD) buffer.length); 9027 if(res == GDI_ERROR) 9028 return null; // wtf really tbh 9029 9030 return buffer; 9031 } else version(with_xft) { 9032 if(isXft && xftFont) { 9033 if(!FontConfigLibrary.attempted) 9034 FontConfigLibrary.loadDynamicLibrary(); 9035 if(!FontConfigLibrary.loadSuccessful) 9036 return null; 9037 9038 char* file; 9039 if (FcPatternGetString(xftFont.pattern, "file", 0, &file) == 0 /*FcResultMatch*/) { 9040 if (file !is null && file[0]) { 9041 import core.stdc.stdio; 9042 auto fp = fopen(file, "rb"); 9043 if(fp is null) 9044 return null; 9045 scope(exit) 9046 fclose(fp); 9047 fseek(fp, 0, SEEK_END); 9048 ubyte[] buffer = new ubyte[](ftell(fp)); 9049 fseek(fp, 0, SEEK_SET); 9050 9051 auto got = fread(buffer.ptr, 1, buffer.length, fp); 9052 if(got != buffer.length) 9053 return null; 9054 9055 return buffer; 9056 } 9057 } 9058 } 9059 return null; 9060 } else throw new NotYetImplementedException(); 9061 } 9062 9063 // see also: XftLockFace(font) which gives a FT_Face. from /usr/include/X11/Xft/Xft.h line 352 9064 9065 private string weightToString(FontWeight weight) { 9066 with(FontWeight) 9067 final switch(weight) { 9068 case dontcare: return "*"; 9069 case thin: return "extralight"; 9070 case extralight: return "extralight"; 9071 case light: return "light"; 9072 case regular: return "regular"; 9073 case medium: return "medium"; 9074 case semibold: return "demibold"; 9075 case bold: return "bold"; 9076 case extrabold: return "demibold"; 9077 case heavy: return "black"; 9078 } 9079 } 9080 9081 /++ 9082 Loads specifically a Core X font - rendered on the X server without antialiasing. Best performance. 9083 9084 History: 9085 Added November 13, 2020. Before then, this code was integrated in the [load] function. 9086 +/ 9087 version(X11) 9088 bool loadCoreX(string name, int size = 0, FontWeight weight = FontWeight.dontcare, bool italic = false) { 9089 unload(); 9090 9091 string xfontstr; 9092 9093 if(name.length > 3 && name[0 .. 3] == "-*-") { 9094 // this is kinda a disgusting hack but if the user sends an exact 9095 // string I'd like to honor it... 9096 xfontstr = name; 9097 } else { 9098 string weightstr = weightToString(weight); 9099 string sizestr; 9100 if(size == 0) 9101 sizestr = "*"; 9102 else 9103 sizestr = toInternal!string(size); 9104 xfontstr = "-*-"~name~"-"~weightstr~"-"~(italic ? "i" : "r")~"-*-*-"~sizestr~"-*-*-*-*-*-*-*\0"; 9105 } 9106 9107 // writeln(xfontstr); 9108 9109 auto display = XDisplayConnection.get; 9110 9111 font = XLoadQueryFont(display, xfontstr.ptr); 9112 if(font is null) 9113 return false; 9114 9115 char** lol; 9116 int lol2; 9117 char* lol3; 9118 fontset = XCreateFontSet(display, xfontstr.ptr, &lol, &lol2, &lol3); 9119 9120 prepareFontInfo(); 9121 9122 return !isNull(); 9123 } 9124 9125 version(X11) 9126 private void prepareFontInfo() { 9127 if(font !is null) { 9128 isMonospace_ = stringWidth("l") == stringWidth("M"); 9129 ascent_ = font.max_bounds.ascent; 9130 descent_ = font.max_bounds.descent; 9131 } 9132 } 9133 9134 version(OSXCocoa) 9135 private void prepareFontInfo() { 9136 if(font !is null) { 9137 isMonospace_ = font.isFixedPitch; 9138 ascent_ = cast(int) font.ascender; 9139 descent_ = cast(int) - font.descender; 9140 } 9141 } 9142 9143 9144 /++ 9145 Loads a Windows font. You probably want to use [load] instead to be more generic. 9146 9147 History: 9148 Added November 13, 2020. Before then, this code was integrated in the [load] function. 9149 +/ 9150 version(Windows) 9151 bool loadWin32(string name, int size = 0, FontWeight weight = FontWeight.dontcare, bool italic = false, HDC hdc = null) { 9152 unload(); 9153 9154 WCharzBuffer buffer = WCharzBuffer(name); 9155 font = CreateFont(size, 0, 0, 0, cast(int) weight, italic, 0, 0, 0, 0, 0, 0, 0, buffer.ptr); 9156 9157 prepareFontInfo(hdc); 9158 9159 return !isNull(); 9160 } 9161 9162 version(Windows) 9163 void prepareFontInfo(HDC hdc = null) { 9164 if(font is null) 9165 return; 9166 9167 TEXTMETRIC tm; 9168 auto dc = hdc ? hdc : GetDC(null); 9169 auto orig = SelectObject(dc, font); 9170 GetTextMetrics(dc, &tm); 9171 SelectObject(dc, orig); 9172 if(hdc is null) 9173 ReleaseDC(null, dc); 9174 9175 width_ = tm.tmAveCharWidth; 9176 height_ = tm.tmHeight; 9177 ascent_ = tm.tmAscent; 9178 descent_ = tm.tmDescent; 9179 // 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. 9180 isMonospace_ = (tm.tmPitchAndFamily & TMPF_FIXED_PITCH) == 0; 9181 } 9182 9183 9184 /++ 9185 `name` is a font name, but it can also be a more complicated string parsed in an OS-specific way. 9186 9187 On X, you may prefix a name with `core:` to bypass the freetype engine causing this function to forward to [loadCoreX]. Otherwise, 9188 it calls [loadXft] if the library is available. If the library or font is not available on Xft, it falls back on [loadCoreX]. 9189 9190 On Windows, it forwards directly to [loadWin32]. 9191 9192 Params: 9193 name = font name. This is looked up by the operating system and may be interpreted differently across platforms or user machines and their preferences. 9194 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. 9195 weight = approximate boldness, results may vary. 9196 italic = try to get a slanted version of the given font. 9197 9198 History: 9199 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. 9200 +/ 9201 bool load(string name, int size = 0, FontWeight weight = FontWeight.dontcare, bool italic = false) { 9202 this.loadedInfo = LoadedInfo(name, size, weight, italic); 9203 version(X11) { 9204 version(with_xft) { 9205 if(name.length > 5 && name[0 .. 5] == "core:") { 9206 goto core; 9207 } 9208 9209 if(loadXft(name, size, weight, italic)) 9210 return true; 9211 // if xft fails, fallback to core to avoid breaking 9212 // code that already depended on this. 9213 } 9214 9215 core: 9216 9217 if(name.length > 5 && name[0 .. 5] == "core:") { 9218 name = name[5 .. $]; 9219 } 9220 9221 return loadCoreX(name, size, weight, italic); 9222 } else version(Windows) { 9223 return loadWin32(name, size, weight, italic); 9224 } else version(OSXCocoa) { 9225 return loadCocoa(name, size, weight, italic); 9226 } else static assert(0); 9227 } 9228 9229 version(OSXCocoa) 9230 bool loadCocoa(string name, int size, FontWeight weight, bool italic) { 9231 unload(); 9232 9233 font = NSFont.fontWithName(MacString(name).borrow, size); // FIXME: weight and italic? 9234 prepareFontInfo(); 9235 9236 return !isNull(); 9237 } 9238 9239 private struct LoadedInfo { 9240 string name; 9241 int size; 9242 FontWeight weight; 9243 bool italic; 9244 } 9245 private LoadedInfo loadedInfo; 9246 9247 /// 9248 void unload() { 9249 if(isNull()) 9250 return; 9251 9252 version(X11) { 9253 auto display = XDisplayConnection.display; 9254 9255 if(display is null) 9256 return; 9257 9258 version(with_xft) { 9259 if(isXft) { 9260 if(xftFont) 9261 XftFontClose(display, xftFont); 9262 isXft = false; 9263 xftFont = null; 9264 return; 9265 } 9266 } 9267 9268 if(font && font !is ScreenPainterImplementation.defaultfont) 9269 XFreeFont(display, font); 9270 if(fontset && fontset !is ScreenPainterImplementation.defaultfontset) 9271 XFreeFontSet(display, fontset); 9272 9273 font = null; 9274 fontset = null; 9275 } else version(Windows) { 9276 DeleteObject(font); 9277 font = null; 9278 } else version(OSXCocoa) { 9279 font.release(); 9280 font = null; 9281 } else static assert(0); 9282 } 9283 9284 private bool isMonospace_; 9285 9286 /++ 9287 History: 9288 Added January 16, 2021 9289 +/ 9290 bool isMonospace() { 9291 return isMonospace_; 9292 } 9293 9294 /++ 9295 Returns the average width of the font, conventionally defined as the width of the lowercase 'x' character. 9296 9297 History: 9298 Added March 26, 2020 9299 Documented January 16, 2021 9300 +/ 9301 int averageWidth() { 9302 version(X11) { 9303 return stringWidth("x"); 9304 } version(OSXCocoa) { 9305 return stringWidth("x"); 9306 } else version(Windows) 9307 return width_; 9308 else assert(0); 9309 } 9310 9311 /++ 9312 Returns the width of the string as drawn on the specified window, or the default screen if the window is null. 9313 9314 History: 9315 Added January 16, 2021 9316 +/ 9317 int stringWidth(scope const(char)[] s, SimpleWindow window = null) { 9318 // FIXME: what about tab? 9319 if(isNull) 9320 return 0; 9321 9322 version(X11) { 9323 version(with_xft) 9324 if(isXft && xftFont !is null) { 9325 //return xftFont.max_advance_width; 9326 XGlyphInfo extents; 9327 XftTextExtentsUtf8(XDisplayConnection.get, xftFont, s.ptr, cast(int) s.length, &extents); 9328 // writeln(extents); 9329 return extents.xOff; 9330 } 9331 if(font is null) 9332 return 0; 9333 else if(fontset) { 9334 XRectangle rect; 9335 Xutf8TextExtents(fontset, s.ptr, cast(int) s.length, null, &rect); 9336 9337 return rect.width; 9338 } else { 9339 return XTextWidth(font, s.ptr, cast(int) s.length); 9340 } 9341 } else version(Windows) { 9342 WCharzBuffer buffer = WCharzBuffer(s); 9343 9344 return stringWidth(buffer.slice, window); 9345 } else version(OSXCocoa) { 9346 /+ 9347 int charCount = [string length]; 9348 CGGlyph glyphs[charCount]; 9349 CGRect rects[charCount]; 9350 9351 CTFontGetGlyphsForCharacters(theCTFont, (const unichar*)[string cStringUsingEncoding:NSUnicodeStringEncoding], glyphs, charCount); 9352 CTFontGetBoundingRectsForGlyphs(theCTFont, kCTFontDefaultOrientation, glyphs, rects, charCount); 9353 9354 int totalwidth = 0, maxheight = 0; 9355 for (int i=0; i < charCount; i++) 9356 { 9357 totalwidth += rects[i].size.width; 9358 maxheight = maxheight < rects[i].size.height ? rects[i].size.height : maxheight; 9359 } 9360 9361 dim = CGSizeMake(totalwidth, maxheight); 9362 +/ 9363 9364 return 16; // FIXME 9365 } 9366 else assert(0); 9367 } 9368 9369 version(Windows) 9370 /// ditto 9371 int stringWidth(scope const(wchar)[] s, SimpleWindow window = null) { 9372 if(isNull) 9373 return 0; 9374 version(Windows) { 9375 SIZE size; 9376 9377 prepareContext(window); 9378 scope(exit) releaseContext(); 9379 9380 GetTextExtentPoint32W(dc, s.ptr, cast(int) s.length, &size); 9381 9382 return size.cx; 9383 } else { 9384 // std.conv can do this easily but it is slow to import and i don't think it is worth it 9385 static assert(0, "not implemented yet"); 9386 //return stringWidth(s, window); 9387 } 9388 } 9389 9390 private { 9391 int prepRefcount; 9392 9393 version(Windows) { 9394 HDC dc; 9395 HANDLE orig; 9396 HWND hwnd; 9397 } 9398 } 9399 /++ 9400 [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. 9401 9402 History: 9403 Added January 23, 2021 9404 +/ 9405 void prepareContext(SimpleWindow window = null) { 9406 prepRefcount++; 9407 if(prepRefcount == 1) { 9408 version(Windows) { 9409 hwnd = window is null ? null : window.impl.hwnd; 9410 dc = GetDC(hwnd); 9411 orig = SelectObject(dc, font); 9412 } 9413 } 9414 } 9415 /// ditto 9416 void releaseContext() { 9417 prepRefcount--; 9418 if(prepRefcount == 0) { 9419 version(Windows) { 9420 SelectObject(dc, orig); 9421 ReleaseDC(hwnd, dc); 9422 hwnd = null; 9423 dc = null; 9424 orig = null; 9425 } 9426 } 9427 } 9428 9429 /+ 9430 FIXME: I think I need advance and kerning pair 9431 9432 int advance(dchar from, dchar to) { } // use dchar.init for first item in string 9433 +/ 9434 9435 /++ 9436 Returns the height of the font. 9437 9438 History: 9439 Added March 26, 2020 9440 Documented January 16, 2021 9441 +/ 9442 int height() { 9443 version(X11) { 9444 version(with_xft) 9445 if(isXft && xftFont !is null) { 9446 return xftFont.ascent + xftFont.descent; // i don't use height here because it doesn't include the baseline pixel 9447 } 9448 if(font is null) 9449 return 0; 9450 return font.max_bounds.ascent + font.max_bounds.descent; 9451 } else version(Windows) { 9452 return height_; 9453 } else version(OSXCocoa) { 9454 if(font is null) 9455 return 0; 9456 return cast(int) (font.ascender + font.descender + 0.9 /* to round up */); // font.capHeight 9457 } 9458 else assert(0); 9459 } 9460 9461 private int ascent_; 9462 private int descent_; 9463 9464 /++ 9465 Max ascent above the baseline. 9466 9467 History: 9468 Added January 22, 2021 9469 +/ 9470 int ascent() { 9471 return ascent_; 9472 } 9473 9474 /++ 9475 Max descent below the baseline. 9476 9477 History: 9478 Added January 22, 2021 9479 +/ 9480 int descent() { 9481 return descent_; 9482 } 9483 9484 /++ 9485 Loads the default font used by [ScreenPainter] if none others are loaded. 9486 9487 Returns: 9488 This method mutates the `this` object, but then returns `this` for 9489 easy chaining like: 9490 9491 --- 9492 auto font = foo.isNull ? foo : foo.loadDefault 9493 --- 9494 9495 History: 9496 Added previously, but left unimplemented until January 24, 2021. 9497 +/ 9498 OperatingSystemFont loadDefault() { 9499 unload(); 9500 9501 loadedInfo = LoadedInfo.init; 9502 9503 version(X11) { 9504 // another option would be https://tronche.com/gui/x/xlib/graphics/font-metrics/XQueryFont.html 9505 // but meh since sdpy does its own thing, this should be ok too 9506 9507 ScreenPainterImplementation.ensureDefaultFontLoaded(); 9508 this.font = ScreenPainterImplementation.defaultfont; 9509 this.fontset = ScreenPainterImplementation.defaultfontset; 9510 9511 prepareFontInfo(); 9512 return this; 9513 } else version(Windows) { 9514 ScreenPainterImplementation.ensureDefaultFontLoaded(); 9515 this.font = ScreenPainterImplementation.defaultGuiFont; 9516 9517 prepareFontInfo(); 9518 return this; 9519 } else version(OSXCocoa) { 9520 this.font = NSFont.systemFontOfSize(15); 9521 9522 prepareFontInfo(); 9523 9524 // import std.stdio; writeln("Load default: ", this.height()); 9525 return this; 9526 } else throw new NotYetImplementedException(); 9527 } 9528 9529 /// 9530 bool isNull() { 9531 version(with_xft) 9532 if(isXft) 9533 return xftFont is null; 9534 return font is null; 9535 } 9536 9537 /* Metrics */ 9538 /+ 9539 GetABCWidth 9540 GetKerningPairs 9541 9542 if I do it right, I can size it all here, and match 9543 what happens when I draw the full string with the OS functions. 9544 9545 subclasses might do the same thing while getting the glyphs on images 9546 struct GlyphInfo { 9547 int glyph; 9548 9549 size_t stringIdxStart; 9550 size_t stringIdxEnd; 9551 9552 Rectangle boundingBox; 9553 } 9554 GlyphInfo[] getCharBoxes() { 9555 // XftTextExtentsUtf8 9556 return null; 9557 9558 } 9559 +/ 9560 9561 ~this() { 9562 if(!thisIsGuiThread) return; // FIXME: leaks if multithreaded gc 9563 unload(); 9564 } 9565 } 9566 9567 version(Windows) 9568 private string sliceCString(const(wchar)[] w) { 9569 return makeUtf8StringFromWindowsString(cast(wchar*) w.ptr); 9570 } 9571 9572 private inout(char)[] sliceCString(inout(char)* s) { 9573 import core.stdc.string; 9574 auto len = strlen(s); 9575 return s[0 .. len]; 9576 } 9577 9578 version(OSXCocoa) 9579 alias PaintingHandle = NSObject; 9580 else 9581 alias PaintingHandle = NativeWindowHandle; 9582 9583 /** 9584 The 2D drawing proxy. You acquire one of these with [SimpleWindow.draw] rather 9585 than constructing it directly. Then, it is reference counted so you can pass it 9586 at around and when the last ref goes out of scope, the buffered drawing activities 9587 are all carried out. 9588 9589 9590 Most functions use the outlineColor instead of taking a color themselves. 9591 ScreenPainter is reference counted and draws its buffer to the screen when its 9592 final reference goes out of scope. 9593 */ 9594 struct ScreenPainter { 9595 CapableOfBeingDrawnUpon window; 9596 this(CapableOfBeingDrawnUpon window, PaintingHandle handle, bool manualInvalidations) { 9597 this.window = window; 9598 if(window.closed) 9599 return; // null painter is now allowed so no need to throw anymore, this likely happens at the end of a program anyway 9600 //currentClipRectangle = arsd.color.Rectangle(0, 0, window.width, window.height); 9601 currentClipRectangle = arsd.color.Rectangle(short.min, short.min, short.max, short.max); 9602 if(window.activeScreenPainter !is null) { 9603 impl = window.activeScreenPainter; 9604 if(impl.referenceCount == 0) { 9605 impl.window = window; 9606 impl.create(handle); 9607 } 9608 impl.manualInvalidations = manualInvalidations; 9609 impl.referenceCount++; 9610 // writeln("refcount ++ ", impl.referenceCount); 9611 } else { 9612 impl = new ScreenPainterImplementation; 9613 impl.window = window; 9614 impl.create(handle); 9615 impl.referenceCount = 1; 9616 impl.manualInvalidations = manualInvalidations; 9617 window.activeScreenPainter = impl; 9618 // writeln("constructed"); 9619 } 9620 9621 copyActiveOriginals(); 9622 } 9623 9624 /++ 9625 EXPERIMENTAL. subject to change. 9626 9627 When you draw a cursor, you can draw this to notify your window of where it is, 9628 for IME systems to use. 9629 +/ 9630 void notifyCursorPosition(int x, int y, int width, int height) { 9631 if(auto w = cast(SimpleWindow) window) { 9632 w.setIMEPopupLocation(x + _originX + width, y + _originY + height); 9633 } 9634 } 9635 9636 /++ 9637 If you are using manual invalidations, this informs the 9638 window system that a section needs to be redrawn. 9639 9640 If you didn't opt into manual invalidation, you don't 9641 have to call this. 9642 9643 History: 9644 Added December 30, 2021 (dub v10.5) 9645 +/ 9646 void invalidateRect(Rectangle rect) { 9647 if(impl is null) return; 9648 9649 // transform(rect) 9650 rect.left += _originX; 9651 rect.right += _originX; 9652 rect.top += _originY; 9653 rect.bottom += _originY; 9654 9655 impl.invalidateRect(rect); 9656 } 9657 9658 private Pen originalPen; 9659 private Color originalFillColor; 9660 private arsd.color.Rectangle originalClipRectangle; 9661 private OperatingSystemFont originalFont; 9662 void copyActiveOriginals() { 9663 if(impl is null) return; 9664 originalPen = impl._activePen; 9665 originalFillColor = impl._fillColor; 9666 originalClipRectangle = impl._clipRectangle; 9667 version(OSXCocoa) {} else 9668 originalFont = impl._activeFont; 9669 } 9670 9671 ~this() { 9672 if(impl is null) return; 9673 impl.referenceCount--; 9674 //writeln("refcount -- ", impl.referenceCount); 9675 if(impl.referenceCount == 0) { 9676 // writeln("destructed"); 9677 impl.dispose(); 9678 *window.activeScreenPainter = ScreenPainterImplementation.init; 9679 // writeln("paint finished"); 9680 } else { 9681 // there is still an active reference, reset stuff so the 9682 // next user doesn't get weirdness via the reference 9683 this.rasterOp = RasterOp.normal; 9684 pen = originalPen; 9685 fillColor = originalFillColor; 9686 if(originalFont) 9687 setFont(originalFont); 9688 impl.setClipRectangle(originalClipRectangle.left, originalClipRectangle.top, originalClipRectangle.width, originalClipRectangle.height); 9689 } 9690 } 9691 9692 this(this) { 9693 if(impl is null) return; 9694 impl.referenceCount++; 9695 //writeln("refcount ++ ", impl.referenceCount); 9696 9697 copyActiveOriginals(); 9698 } 9699 9700 private int _originX; 9701 private int _originY; 9702 @property int originX() { return _originX; } 9703 @property int originY() { return _originY; } 9704 @property int originX(int a) { 9705 _originX = a; 9706 return _originX; 9707 } 9708 @property int originY(int a) { 9709 _originY = a; 9710 return _originY; 9711 } 9712 arsd.color.Rectangle currentClipRectangle; // set BEFORE doing any transformations 9713 private void transform(ref Point p) { 9714 if(impl is null) return; 9715 p.x += _originX; 9716 p.y += _originY; 9717 } 9718 9719 // this needs to be checked BEFORE the originX/Y transformation 9720 private bool isClipped(Point p) { 9721 return !currentClipRectangle.contains(p); 9722 } 9723 private bool isClipped(Point p, int width, int height) { 9724 return !currentClipRectangle.overlaps(arsd.color.Rectangle(p, Size(width + 1, height + 1))); 9725 } 9726 private bool isClipped(Point p, Size s) { 9727 return !currentClipRectangle.overlaps(arsd.color.Rectangle(p, Size(s.width + 1, s.height + 1))); 9728 } 9729 private bool isClipped(Point p, Point p2) { 9730 // need to ensure the end points are actually included inside, so the +1 does that 9731 return !currentClipRectangle.overlaps(arsd.color.Rectangle(p, p2 + Point(1, 1))); 9732 } 9733 9734 9735 /++ 9736 Sets the clipping region for drawing. If width == 0 && height == 0, disabled clipping. 9737 9738 Returns: 9739 The old clip rectangle. 9740 9741 History: 9742 Return value was `void` prior to May 10, 2021. 9743 9744 +/ 9745 arsd.color.Rectangle setClipRectangle(Point pt, int width, int height) { 9746 if(impl is null) return currentClipRectangle; 9747 if(pt == currentClipRectangle.upperLeft && width == currentClipRectangle.width && height == currentClipRectangle.height) 9748 return currentClipRectangle; // no need to do anything 9749 auto old = currentClipRectangle; 9750 currentClipRectangle = arsd.color.Rectangle(pt, Size(width, height)); 9751 transform(pt); 9752 9753 impl.setClipRectangle(pt.x, pt.y, width, height); 9754 9755 return old; 9756 } 9757 9758 /// ditto 9759 arsd.color.Rectangle setClipRectangle(arsd.color.Rectangle rect) { 9760 if(impl is null) return currentClipRectangle; 9761 return setClipRectangle(rect.upperLeft, rect.width, rect.height); 9762 } 9763 9764 /// 9765 void setFont(OperatingSystemFont font) { 9766 if(impl is null) return; 9767 impl.setFont(font); 9768 } 9769 9770 /// 9771 int fontHeight() { 9772 if(impl is null) return 0; 9773 return impl.fontHeight(); 9774 } 9775 9776 private Pen activePen; 9777 9778 /// 9779 @property void pen(Pen p) { 9780 if(impl is null) return; 9781 activePen = p; 9782 impl.pen(p); 9783 } 9784 9785 /// 9786 @scriptable 9787 @property void outlineColor(Color c) { 9788 if(impl is null) return; 9789 if(activePen.color == c) 9790 return; 9791 activePen.color = c; 9792 impl.pen(activePen); 9793 } 9794 9795 /// 9796 @scriptable 9797 @property void fillColor(Color c) { 9798 if(impl is null) return; 9799 impl.fillColor(c); 9800 } 9801 9802 /// 9803 @property void rasterOp(RasterOp op) { 9804 if(impl is null) return; 9805 impl.rasterOp(op); 9806 } 9807 9808 9809 void updateDisplay() { 9810 // FIXME this should do what the dtor does 9811 } 9812 9813 /// 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) 9814 void scrollArea(Point upperLeft, int width, int height, int dx, int dy) { 9815 if(impl is null) return; 9816 if(isClipped(upperLeft, width, height)) return; 9817 transform(upperLeft); 9818 version(Windows) { 9819 // http://msdn.microsoft.com/en-us/library/windows/desktop/bb787589%28v=vs.85%29.aspx 9820 RECT scroll = RECT(upperLeft.x, upperLeft.y, upperLeft.x + width, upperLeft.y + height); 9821 RECT clip = scroll; 9822 RECT uncovered; 9823 HRGN hrgn; 9824 if(!ScrollDC(impl.hdc, -dx, -dy, &scroll, &clip, hrgn, &uncovered)) 9825 throw new WindowsApiException("ScrollDC", GetLastError()); 9826 9827 } else version(X11) { 9828 // FIXME: clip stuff outside this rectangle 9829 XCopyArea(impl.display, impl.d, impl.d, impl.gc, upperLeft.x, upperLeft.y, width, height, upperLeft.x - dx, upperLeft.y - dy); 9830 } else version(OSXCocoa) { 9831 throw new NotYetImplementedException(); 9832 } else static assert(0); 9833 } 9834 9835 /// 9836 void clear(Color color = Color.white()) { 9837 if(impl is null) return; 9838 fillColor = color; 9839 outlineColor = color; 9840 drawRectangle(Point(0, 0), window.width, window.height); 9841 } 9842 9843 /++ 9844 Draws a pixmap (represented by the [Sprite] class) on the drawable. 9845 9846 Params: 9847 upperLeft = point on the window where the upper left corner of the image will be drawn 9848 imageUpperLeft = point on the image to start the slice to draw 9849 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. 9850 History: 9851 The `imageUpperLeft` and `sliceSize` parameters were added on March 11, 2021 (dub v9.3.0) 9852 +/ 9853 void drawPixmap(Sprite s, Point upperLeft, Point imageUpperLeft = Point(0, 0), Size sliceSize = Size(0, 0)) { 9854 if(impl is null) return; 9855 if(isClipped(upperLeft, s.width, s.height)) return; 9856 transform(upperLeft); 9857 impl.drawPixmap(s, upperLeft.x, upperLeft.y, imageUpperLeft.x, imageUpperLeft.y, sliceSize.width, sliceSize.height); 9858 } 9859 9860 /// 9861 void drawImage(Point upperLeft, Image i, Point upperLeftOfImage = Point(0, 0), int w = 0, int h = 0) { 9862 if(impl is null) return; 9863 //if(isClipped(upperLeft, w, h)) return; // FIXME 9864 transform(upperLeft); 9865 if(w == 0 || w > i.width) 9866 w = i.width; 9867 if(h == 0 || h > i.height) 9868 h = i.height; 9869 if(upperLeftOfImage.x < 0) 9870 upperLeftOfImage.x = 0; 9871 if(upperLeftOfImage.y < 0) 9872 upperLeftOfImage.y = 0; 9873 9874 impl.drawImage(upperLeft.x, upperLeft.y, i, upperLeftOfImage.x, upperLeftOfImage.y, w, h); 9875 } 9876 9877 /// 9878 Size textSize(in char[] text) { 9879 if(impl is null) return Size(0, 0); 9880 return impl.textSize(text); 9881 } 9882 9883 /++ 9884 Draws a string in the window with the set font (see [setFont] to change it). 9885 9886 Params: 9887 upperLeft = the upper left point of the bounding box of the text 9888 text = the string to draw 9889 lowerRight = the lower right point of the bounding box of the text. If 0, 0, there is no lower right bound. 9890 alignment = A [arsd.docs.general_concepts#bitflags|combination] of [TextAlignment] flags 9891 +/ 9892 @scriptable 9893 void drawText(Point upperLeft, in char[] text, Point lowerRight = Point(0, 0), uint alignment = 0) { 9894 if(impl is null) return; 9895 if(lowerRight.x != 0 || lowerRight.y != 0) { 9896 if(isClipped(upperLeft, lowerRight)) return; 9897 transform(lowerRight); 9898 } else { 9899 if(isClipped(upperLeft, textSize(text))) return; 9900 } 9901 transform(upperLeft); 9902 impl.drawText(upperLeft.x, upperLeft.y, lowerRight.x, lowerRight.y, text, alignment); 9903 } 9904 9905 /++ 9906 Draws text using a custom font. 9907 9908 This is still MAJOR work in progress. 9909 9910 Creating a [DrawableFont] can be tricky and require additional dependencies. 9911 +/ 9912 void drawText(DrawableFont font, Point upperLeft, in char[] text) { 9913 if(impl is null) return; 9914 if(isClipped(upperLeft, Point(int.max, int.max))) return; 9915 transform(upperLeft); 9916 font.drawString(this, upperLeft, text); 9917 } 9918 9919 version(Windows) 9920 void drawText(Point upperLeft, scope const(wchar)[] text) { 9921 if(impl is null) return; 9922 if(isClipped(upperLeft, Point(int.max, int.max))) return; 9923 transform(upperLeft); 9924 9925 if(text.length && text[$-1] == '\n') 9926 text = text[0 .. $-1]; // tailing newlines are weird on windows... 9927 9928 TextOutW(impl.hdc, upperLeft.x, upperLeft.y, text.ptr, cast(int) text.length); 9929 } 9930 9931 static struct TextDrawingContext { 9932 Point boundingBoxUpperLeft; 9933 Point boundingBoxLowerRight; 9934 9935 Point currentLocation; 9936 9937 Point lastDrewUpperLeft; 9938 Point lastDrewLowerRight; 9939 9940 // how do i do right aligned rich text? 9941 // i kinda want to do a pre-made drawing then right align 9942 // draw the whole block. 9943 // 9944 // That's exactly the diff: inline vs block stuff. 9945 9946 // I need to get coordinates of an inline section out too, 9947 // not just a bounding box, but a series of bounding boxes 9948 // should be ok. Consider what's needed to detect a click 9949 // on a link in the middle of a paragraph breaking a line. 9950 // 9951 // Generally, we should be able to get the rectangles of 9952 // any portion we draw. 9953 // 9954 // It also needs to tell what text is left if it overflows 9955 // out of the box, so we can do stuff like float images around 9956 // it. It should not attempt to draw a letter that would be 9957 // clipped. 9958 // 9959 // I might also turn off word wrap stuff. 9960 } 9961 9962 void drawText(TextDrawingContext context, in char[] text, uint alignment = 0) { 9963 if(impl is null) return; 9964 // FIXME 9965 } 9966 9967 /// Drawing an individual pixel is slow. Avoid it if possible. 9968 void drawPixel(Point where) { 9969 if(impl is null) return; 9970 if(isClipped(where)) return; 9971 transform(where); 9972 impl.drawPixel(where.x, where.y); 9973 } 9974 9975 9976 /// Draws a pen using the current pen / outlineColor 9977 @scriptable 9978 void drawLine(Point starting, Point ending) { 9979 if(impl is null) return; 9980 if(isClipped(starting, ending)) return; 9981 transform(starting); 9982 transform(ending); 9983 impl.drawLine(starting.x, starting.y, ending.x, ending.y); 9984 } 9985 9986 /// Draws a rectangle using the current pen/outline color for the border and brush/fill color for the insides 9987 /// The outer lines, inclusive of x = 0, y = 0, x = width - 1, and y = height - 1 are drawn with the outlineColor 9988 /// The rest of the pixels are drawn with the fillColor. If fillColor is transparent, those pixels are not drawn. 9989 @scriptable 9990 void drawRectangle(Point upperLeft, int width, int height) { 9991 if(impl is null) return; 9992 if(isClipped(upperLeft, width, height)) return; 9993 transform(upperLeft); 9994 impl.drawRectangle(upperLeft.x, upperLeft.y, width, height); 9995 } 9996 9997 /// ditto 9998 void drawRectangle(Point upperLeft, Size size) { 9999 if(impl is null) return; 10000 if(isClipped(upperLeft, size.width, size.height)) return; 10001 transform(upperLeft); 10002 impl.drawRectangle(upperLeft.x, upperLeft.y, size.width, size.height); 10003 } 10004 10005 /// ditto 10006 void drawRectangle(Point upperLeft, Point lowerRightInclusive) { 10007 if(impl is null) return; 10008 if(isClipped(upperLeft, lowerRightInclusive + Point(1, 1))) return; 10009 transform(upperLeft); 10010 transform(lowerRightInclusive); 10011 impl.drawRectangle(upperLeft.x, upperLeft.y, 10012 lowerRightInclusive.x - upperLeft.x + 1, lowerRightInclusive.y - upperLeft.y + 1); 10013 } 10014 10015 // overload added on May 12, 2021 10016 /// ditto 10017 void drawRectangle(Rectangle rect) { 10018 drawRectangle(rect.upperLeft, rect.size); 10019 } 10020 10021 /// Arguments are the points of the bounding rectangle 10022 void drawEllipse(Point upperLeft, Point lowerRight) { 10023 if(impl is null) return; 10024 if(isClipped(upperLeft, lowerRight)) return; 10025 transform(upperLeft); 10026 transform(lowerRight); 10027 impl.drawEllipse(upperLeft.x, upperLeft.y, lowerRight.x, lowerRight.y); 10028 } 10029 10030 /++ 10031 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. 10032 10033 10034 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. 10035 10036 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. 10037 10038 Bugs: 10039 They still don't exactly match in outlining the arc with straight lines (Windows does, Linux doesn't for now). 10040 10041 The arc outline on Linux sometimes goes over the target. 10042 10043 The fill on Windows sometimes stops short. 10044 10045 History: 10046 This function was broken af, totally inconsistent on platforms until September 24, 2021. 10047 10048 The interpretation of the final argument was incorrectly documented and implemented until August 2, 2024. 10049 +/ 10050 void drawArc(Point upperLeft, int width, int height, int start, int length) { 10051 if(impl is null) return; 10052 // FIXME: not actually implemented 10053 if(isClipped(upperLeft, width, height)) return; 10054 transform(upperLeft); 10055 impl.drawArc(upperLeft.x, upperLeft.y, width, height, start, length); 10056 } 10057 10058 /// this function draws a circle with the drawEllipse() function above, it requires the upper left point and the radius 10059 void drawCircle(Point upperLeft, int diameter) { 10060 drawEllipse(upperLeft, Point(upperLeft.x + diameter, upperLeft.y + diameter)); 10061 } 10062 10063 /++ 10064 Draws a rectangle with rounded corners. It is outlined with the current foreground pen and filled with the current background brush. 10065 10066 10067 Bugs: 10068 Not implemented on Mac; it will instead draw a non-rounded rectangle for now. 10069 10070 History: 10071 Added August 3, 2024 10072 +/ 10073 void drawRectangleRounded(Rectangle rect, int borderRadius) { 10074 drawRectangleRounded(rect.upperLeft, rect.lowerRight, borderRadius); 10075 } 10076 10077 /// ditto 10078 void drawRectangleRounded(Point upperLeft, Size size, int borderRadius) { 10079 drawRectangleRounded(upperLeft, upperLeft + Point(size.width, size.height), borderRadius); 10080 } 10081 10082 /// ditto 10083 void drawRectangleRounded(Point upperLeft, Point lowerRight, int borderRadius) { 10084 if(borderRadius <= 0) { 10085 drawRectangle(upperLeft, lowerRight); 10086 return; 10087 } 10088 10089 transform(upperLeft); 10090 transform(lowerRight); 10091 10092 impl.drawRectangleRounded(upperLeft, lowerRight, borderRadius); 10093 } 10094 10095 /// . 10096 void drawPolygon(Point[] vertexes) { 10097 if(impl is null) return; 10098 assert(vertexes.length); 10099 int minX = int.max, minY = int.max, maxX = int.min, maxY = int.min; 10100 foreach(ref vertex; vertexes) { 10101 if(vertex.x < minX) 10102 minX = vertex.x; 10103 if(vertex.y < minY) 10104 minY = vertex.y; 10105 if(vertex.x > maxX) 10106 maxX = vertex.x; 10107 if(vertex.y > maxY) 10108 maxY = vertex.y; 10109 transform(vertex); 10110 } 10111 if(isClipped(Point(minX, maxY), Point(maxX + 1, maxY + 1))) return; 10112 impl.drawPolygon(vertexes); 10113 } 10114 10115 /// ditto 10116 void drawPolygon(Point[] vertexes...) { 10117 if(impl is null) return; 10118 drawPolygon(vertexes); 10119 } 10120 10121 10122 // and do a draw/fill in a single call maybe. Windows can do it... but X can't, though it could do two calls. 10123 10124 //mixin NativeScreenPainterImplementation!() impl; 10125 10126 10127 // HACK: if I mixin the impl directly, it won't let me override the copy 10128 // constructor! The linker complains about there being multiple definitions. 10129 // I'll make the best of it and reference count it though. 10130 ScreenPainterImplementation* impl; 10131 } 10132 10133 // HACK: I need a pointer to the implementation so it's separate 10134 struct ScreenPainterImplementation { 10135 CapableOfBeingDrawnUpon window; 10136 int referenceCount; 10137 mixin NativeScreenPainterImplementation!(); 10138 } 10139 10140 // FIXME: i haven't actually tested the sprite class on MS Windows 10141 10142 /** 10143 Sprites are optimized for fast drawing on the screen, but slow for direct pixel 10144 access. They are best for drawing a relatively unchanging image repeatedly on the screen. 10145 10146 10147 On X11, this corresponds to an `XPixmap`. On Windows, it still uses a bitmap, 10148 though I'm not sure that's ideal and the implementation might change. 10149 10150 You create one by giving a window and an image. It optimizes for that window, 10151 and copies the image into it to use as the initial picture. Creating a sprite 10152 can be quite slow (especially over a network connection) so you should do it 10153 as little as possible and just hold on to your sprite handles after making them. 10154 simpledisplay does try to do its best though, using the XSHM extension if available, 10155 but you should still write your code as if it will always be slow. 10156 10157 Then you can use `sprite.drawAt(painter, point);` to draw it, which should be 10158 a fast operation - much faster than drawing the Image itself every time. 10159 10160 `Sprite` represents a scarce resource which should be freed when you 10161 are done with it. Use the `dispose` method to do this. Do not use a `Sprite` 10162 after it has been disposed. If you are unsure about this, don't take chances, 10163 just let the garbage collector do it for you. But ideally, you can manage its 10164 lifetime more efficiently. 10165 10166 $(NOTE `Sprite`, like the rest of simpledisplay's `ScreenPainter`, does not 10167 support alpha blending in its drawing at this time. That might change in the 10168 future, but if you need alpha blending right now, use OpenGL instead. See 10169 `gamehelpers.d` for a similar class to `Sprite` that uses OpenGL: `OpenGlTexture`.) 10170 10171 Update: on April 23, 2021, I finally added alpha blending support. You must opt 10172 in by setting the enableAlpha = true in the constructor. 10173 */ 10174 class Sprite : CapableOfBeingDrawnUpon { 10175 10176 /// 10177 ScreenPainter draw() { 10178 return ScreenPainter(this, handle, false); 10179 } 10180 10181 /++ 10182 Copies the sprite's current state into a [TrueColorImage]. 10183 10184 Be warned: this can be a very slow operation 10185 10186 History: 10187 Actually implemented on March 14, 2021 10188 +/ 10189 TrueColorImage takeScreenshot() { 10190 return trueColorImageFromNativeHandle(handle, width, height); 10191 } 10192 10193 void delegate() paintingFinishedDg() { return null; } 10194 bool closed() { return false; } 10195 ScreenPainterImplementation* activeScreenPainter_; 10196 protected ScreenPainterImplementation* activeScreenPainter() { return activeScreenPainter_; } 10197 protected void activeScreenPainter(ScreenPainterImplementation* i) { activeScreenPainter_ = i; } 10198 10199 version(Windows) 10200 private ubyte* rawData; 10201 // FIXME: sprites are lost when disconnecting from X! We need some way to invalidate them... 10202 // ditto on the XPicture stuff 10203 10204 version(X11) { 10205 private static XRenderPictFormat* RGB24; 10206 private static XRenderPictFormat* ARGB32; 10207 10208 private Picture xrenderPicture; 10209 } 10210 10211 version(X11) 10212 private static void requireXRender() { 10213 if(!XRenderLibrary.loadAttempted) { 10214 XRenderLibrary.loadDynamicLibrary(); 10215 } 10216 10217 if(!XRenderLibrary.loadSuccessful) 10218 throw new Exception("XRender library load failure"); 10219 10220 auto display = XDisplayConnection.get; 10221 10222 // FIXME: if we migrate X displays, these need to be changed 10223 if(RGB24 is null) 10224 RGB24 = XRenderFindStandardFormat(display, PictStandardRGB24); 10225 if(ARGB32 is null) 10226 ARGB32 = XRenderFindStandardFormat(display, PictStandardARGB32); 10227 } 10228 10229 protected this() {} 10230 10231 this(SimpleWindow win, int width, int height, bool enableAlpha = false) { 10232 this._width = width; 10233 this._height = height; 10234 this.enableAlpha = enableAlpha; 10235 10236 version(X11) { 10237 auto display = XDisplayConnection.get(); 10238 10239 if(enableAlpha) { 10240 requireXRender(); 10241 } 10242 10243 handle = XCreatePixmap(display, cast(Drawable) win.window, width, height, enableAlpha ? 32 : DefaultDepthOfDisplay(display)); 10244 10245 if(enableAlpha) { 10246 XRenderPictureAttributes attrs; 10247 xrenderPicture = XRenderCreatePicture(display, handle, ARGB32, 0, &attrs); 10248 } 10249 } else version(Windows) { 10250 version(CRuntime_DigitalMars) { 10251 //if(enableAlpha) 10252 //throw new Exception("Alpha support not available, try recompiling with -m32mscoff"); 10253 } 10254 10255 BITMAPINFO infoheader; 10256 infoheader.bmiHeader.biSize = infoheader.bmiHeader.sizeof; 10257 infoheader.bmiHeader.biWidth = width; 10258 infoheader.bmiHeader.biHeight = height; 10259 infoheader.bmiHeader.biPlanes = 1; 10260 infoheader.bmiHeader.biBitCount = enableAlpha ? 32 : 24; 10261 infoheader.bmiHeader.biCompression = BI_RGB; 10262 10263 // FIXME: this should prolly be a device dependent bitmap... 10264 handle = CreateDIBSection( 10265 null, 10266 &infoheader, 10267 DIB_RGB_COLORS, 10268 cast(void**) &rawData, 10269 null, 10270 0); 10271 10272 if(handle is null) 10273 throw new WindowsApiException("couldn't create pixmap", GetLastError()); 10274 } 10275 } 10276 10277 /// Makes a sprite based on the image with the initial contents from the Image 10278 this(SimpleWindow win, Image i) { 10279 this(win, i.width, i.height, i.enableAlpha); 10280 10281 version(X11) { 10282 auto display = XDisplayConnection.get(); 10283 auto gc = XCreateGC(display, this.handle, 0, null); 10284 scope(exit) XFreeGC(display, gc); 10285 if(i.usingXshm) 10286 XShmPutImage(display, cast(Drawable) handle, gc, i.handle, 0, 0, 0, 0, i.width, i.height, false); 10287 else 10288 XPutImage(display, cast(Drawable) handle, gc, i.handle, 0, 0, 0, 0, i.width, i.height); 10289 } else version(Windows) { 10290 auto itemsPerLine = enableAlpha ? (4 * width) : (((cast(int) width * 3 + 3) / 4) * 4); 10291 auto arrLength = itemsPerLine * height; 10292 rawData[0..arrLength] = i.rawData[0..arrLength]; 10293 } else version(OSXCocoa) { 10294 // FIXME: I have no idea if this is even any good 10295 auto colorSpace = CGColorSpaceCreateDeviceRGB(); 10296 handle = CGBitmapContextCreate(null, width, height, 8, 4*width, 10297 colorSpace, 10298 kCGImageAlphaPremultipliedLast 10299 |kCGBitmapByteOrder32Big); 10300 CGColorSpaceRelease(colorSpace); 10301 auto rawData = CGBitmapContextGetData(handle); 10302 10303 auto rdl = (width * height * 4); 10304 rawData[0 .. rdl] = i.rawData[0 .. rdl]; 10305 } else static assert(0); 10306 } 10307 10308 /++ 10309 Draws the image on the specified painter at the specified point. The point is the upper-left point where the image will be drawn. 10310 10311 Params: 10312 where = point on the window where the upper left corner of the image will be drawn 10313 imageUpperLeft = point on the image to start the slice to draw 10314 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. 10315 History: 10316 The `imageUpperLeft` and `sliceSize` parameters were added on March 11, 2021 (dub v9.3.0) 10317 +/ 10318 void drawAt(ScreenPainter painter, Point where, Point imageUpperLeft = Point(0, 0), Size sliceSize = Size(0, 0)) { 10319 painter.drawPixmap(this, where, imageUpperLeft, sliceSize); 10320 } 10321 10322 /// Call this when you're ready to get rid of it 10323 void dispose() { 10324 version(X11) { 10325 staticDispose(xrenderPicture, handle); 10326 xrenderPicture = None; 10327 handle = None; 10328 } else version(Windows) { 10329 staticDispose(handle); 10330 handle = null; 10331 } else version(OSXCocoa) { 10332 staticDispose(handle); 10333 handle = null; 10334 } else static assert(0); 10335 10336 } 10337 10338 version(X11) 10339 static void staticDispose(Picture xrenderPicture, Pixmap handle) { 10340 if(xrenderPicture) 10341 XRenderFreePicture(XDisplayConnection.get, xrenderPicture); 10342 if(handle) 10343 XFreePixmap(XDisplayConnection.get(), handle); 10344 } 10345 else version(Windows) 10346 static void staticDispose(HBITMAP handle) { 10347 if(handle) 10348 DeleteObject(handle); 10349 } 10350 else version(OSXCocoa) 10351 static void staticDispose(CGContextRef context) { 10352 if(context) 10353 CGContextRelease(context); 10354 } 10355 10356 ~this() { 10357 version(X11) { if(xrenderPicture || handle) 10358 cleanupQueue.queue!staticDispose(xrenderPicture, handle); 10359 } else version(Windows) { if(handle) 10360 cleanupQueue.queue!staticDispose(handle); 10361 } else version(OSXCocoa) { if(handle) 10362 cleanupQueue.queue!staticDispose(handle); 10363 } else static assert(0); 10364 } 10365 10366 /// 10367 final @property int width() { return _width; } 10368 10369 /// 10370 final @property int height() { return _height; } 10371 10372 /// 10373 static Sprite fromMemoryImage(SimpleWindow win, MemoryImage img, bool enableAlpha = false) { 10374 return new Sprite(win, Image.fromMemoryImage(img, enableAlpha)); 10375 } 10376 10377 auto nativeHandle() { 10378 return handle; 10379 } 10380 10381 private: 10382 10383 int _width; 10384 int _height; 10385 bool enableAlpha; 10386 version(X11) 10387 Pixmap handle; 10388 else version(Windows) 10389 HBITMAP handle; 10390 else version(OSXCocoa) 10391 CGContextRef handle; 10392 else version(Emscripten) 10393 void* handle; 10394 else static assert(0); 10395 } 10396 10397 /++ 10398 Represents a display-side gradient pseudo-image. Actually construct it with [LinearGradient], [RadialGradient], or [ConicalGradient]. 10399 10400 History: 10401 Added November 20, 2021 (dub v10.4) 10402 +/ 10403 version(OSXCocoa) {} else // NotYetImplementedException 10404 abstract class Gradient : Sprite { 10405 protected this(int w, int h) { 10406 version(X11) { 10407 Sprite.requireXRender(); 10408 10409 super(); 10410 enableAlpha = true; 10411 _width = w; 10412 _height = h; 10413 } else version(Windows) { 10414 super(null, w, h, true); // on Windows i'm just making a bitmap myself 10415 } 10416 } 10417 10418 version(Windows) 10419 final void forEachPixel(scope Color delegate(int x, int y) dg) @system { 10420 auto ptr = rawData; 10421 foreach(j; 0 .. _height) 10422 foreach(i; 0 .. _width) { 10423 auto color = dg(i, _height - j - 1); // cuz of upside down bitmap 10424 *rawData = (color.a * color.b) / 255; rawData++; 10425 *rawData = (color.a * color.g) / 255; rawData++; 10426 *rawData = (color.a * color.r) / 255; rawData++; 10427 *rawData = color.a; rawData++; 10428 } 10429 } 10430 10431 version(X11) 10432 protected void helper(scope Stop[] stops, scope Picture delegate(scope XFixed[] stopsPositions, scope XRenderColor[] colors) dg) { 10433 assert(stops.length > 0); 10434 assert(stops.length <= 16, "I got lazy with buffers"); 10435 10436 XFixed[16] stopsPositions = void; 10437 XRenderColor[16] colors = void; 10438 10439 foreach(idx, stop; stops) { 10440 stopsPositions[idx] = cast(int)(stop.percentage * ushort.max); 10441 auto c = stop.c; 10442 colors[idx] = XRenderColor( 10443 cast(ushort)(c.r * ushort.max / 255), 10444 cast(ushort)(c.g * ushort.max / 255), 10445 cast(ushort)(c.b * ushort.max / 255), 10446 cast(ushort)(c.a * ubyte.max) // max value here is fractional 10447 ); 10448 } 10449 10450 xrenderPicture = dg(stopsPositions, colors); 10451 } 10452 10453 /// 10454 static struct Stop { 10455 float percentage; /// between 0 and 1.0 10456 Color c; 10457 } 10458 } 10459 10460 /++ 10461 Creates a linear gradient between p1 and p2. 10462 10463 X ONLY RIGHT NOW 10464 10465 History: 10466 Added November 20, 2021 (dub v10.4) 10467 10468 Bugs: 10469 Not yet implemented on Windows. 10470 +/ 10471 version(OSXCocoa) {} else // NotYetImplementedException 10472 class LinearGradient : Gradient { 10473 /++ 10474 10475 +/ 10476 this(Point p1, Point p2, Stop[] stops...) { 10477 super(p2.x, p2.y); 10478 10479 version(X11) { 10480 XLinearGradient gradient; 10481 gradient.p1 = XPointFixed(p1.x * ushort.max, p1.y * ushort.max); 10482 gradient.p2 = XPointFixed(p2.x * ushort.max, p2.y * ushort.max); 10483 10484 helper(stops, (scope XFixed[] stopsPositions, scope XRenderColor[] colors) { 10485 return XRenderCreateLinearGradient( 10486 XDisplayConnection.get, 10487 &gradient, 10488 stopsPositions.ptr, 10489 colors.ptr, 10490 cast(int) stops.length); 10491 }); 10492 } else version(Windows) { 10493 // FIXME 10494 forEachPixel((int x, int y) { 10495 import core.stdc.math; 10496 10497 //sqrtf( 10498 10499 return Color.transparent; 10500 // return Color(x * 2, y * 2, 0, 128); // this result is so beautiful 10501 }); 10502 } 10503 } 10504 } 10505 10506 /++ 10507 A conical gradient goes from color to color around a circumference from a center point. 10508 10509 X ONLY RIGHT NOW 10510 10511 History: 10512 Added November 20, 2021 (dub v10.4) 10513 10514 Bugs: 10515 Not yet implemented on Windows. 10516 +/ 10517 version(OSXCocoa) {} else // NotYetImplementedException 10518 class ConicalGradient : Gradient { 10519 /++ 10520 10521 +/ 10522 this(Point center, float angleInDegrees, Stop[] stops...) { 10523 super(center.x * 2, center.y * 2); 10524 10525 version(X11) { 10526 XConicalGradient gradient; 10527 gradient.center = XPointFixed(center.x * ushort.max, center.y * ushort.max); 10528 gradient.angle = cast(int)(angleInDegrees * ushort.max); 10529 10530 helper(stops, (scope XFixed[] stopsPositions, scope XRenderColor[] colors) { 10531 return XRenderCreateConicalGradient( 10532 XDisplayConnection.get, 10533 &gradient, 10534 stopsPositions.ptr, 10535 colors.ptr, 10536 cast(int) stops.length); 10537 }); 10538 } else version(Windows) { 10539 // FIXME 10540 forEachPixel((int x, int y) { 10541 import core.stdc.math; 10542 10543 //sqrtf( 10544 10545 return Color.transparent; 10546 // return Color(x * 2, y * 2, 0, 128); // this result is so beautiful 10547 }); 10548 10549 } 10550 } 10551 } 10552 10553 /++ 10554 A radial gradient goes from color to color based on distance from the center. 10555 It is like rings of color. 10556 10557 X ONLY RIGHT NOW 10558 10559 10560 More specifically, you create two circles: an inner circle and an outer circle. 10561 The gradient is only drawn in the area outside the inner circle but inside the outer 10562 circle. The closest line between those two circles forms the line for the gradient 10563 and the stops are calculated the same as the [LinearGradient]. Then it just sweeps around. 10564 10565 History: 10566 Added November 20, 2021 (dub v10.4) 10567 10568 Bugs: 10569 Not yet implemented on Windows. 10570 +/ 10571 version(OSXCocoa) {} else // NotYetImplementedException 10572 class RadialGradient : Gradient { 10573 /++ 10574 10575 +/ 10576 this(Point innerCenter, float innerRadius, Point outerCenter, float outerRadius, Stop[] stops...) { 10577 super(cast(int)(outerCenter.x + outerRadius + 0.5), cast(int)(outerCenter.y + outerRadius + 0.5)); 10578 10579 version(X11) { 10580 XRadialGradient gradient; 10581 gradient.inner = XCircle(innerCenter.x * ushort.max, innerCenter.y * ushort.max, cast(int) (innerRadius * ushort.max)); 10582 gradient.outer = XCircle(outerCenter.x * ushort.max, outerCenter.y * ushort.max, cast(int) (outerRadius * ushort.max)); 10583 10584 helper(stops, (scope XFixed[] stopsPositions, scope XRenderColor[] colors) { 10585 return XRenderCreateRadialGradient( 10586 XDisplayConnection.get, 10587 &gradient, 10588 stopsPositions.ptr, 10589 colors.ptr, 10590 cast(int) stops.length); 10591 }); 10592 } else version(Windows) { 10593 // FIXME 10594 forEachPixel((int x, int y) { 10595 import core.stdc.math; 10596 10597 //sqrtf( 10598 10599 return Color.transparent; 10600 // return Color(x * 2, y * 2, 0, 128); // this result is so beautiful 10601 }); 10602 } 10603 } 10604 } 10605 10606 10607 10608 /+ 10609 NOT IMPLEMENTED 10610 10611 A display-stored image optimized for relatively quick drawing, like 10612 [Sprite], but this one supports alpha channel blending and does NOT 10613 support direct drawing upon it with a [ScreenPainter]. 10614 10615 You can think of it as an [arsd.game.OpenGlTexture] for usage with a 10616 plain [ScreenPainter]... sort of. 10617 10618 On X11, it requires the Xrender extension and library. This is available 10619 almost everywhere though. 10620 10621 History: 10622 Added November 14, 2020 but NOT ACTUALLY IMPLEMENTED 10623 +/ 10624 version(none) 10625 class AlphaSprite { 10626 /++ 10627 Copies the given image into it. 10628 +/ 10629 this(MemoryImage img) { 10630 10631 if(!XRenderLibrary.loadAttempted) { 10632 XRenderLibrary.loadDynamicLibrary(); 10633 10634 // FIXME: this needs to be reconstructed when the X server changes 10635 repopulateX(); 10636 } 10637 if(!XRenderLibrary.loadSuccessful) 10638 throw new Exception("XRender library load failure"); 10639 10640 // I probably need to put the alpha mask in a separate Picture 10641 // ugh 10642 // maybe the Sprite itself can have an alpha bitmask anyway 10643 10644 10645 auto display = XDisplayConnection.get(); 10646 pixmap = XCreatePixmap(display, cast(Drawable) win.window, width, height, DefaultDepthOfDisplay(display)); 10647 10648 10649 XRenderPictureAttributes attrs; 10650 10651 handle = XRenderCreatePicture( 10652 XDisplayConnection.get, 10653 pixmap, 10654 RGBA, 10655 0, 10656 &attrs 10657 ); 10658 10659 } 10660 10661 // maybe i'll use the create gradient functions too with static factories.. 10662 10663 void drawAt(ScreenPainter painter, Point where) { 10664 //painter.drawPixmap(this, where); 10665 10666 XRenderPictureAttributes attrs; 10667 10668 auto pic = XRenderCreatePicture( 10669 XDisplayConnection.get, 10670 painter.impl.d, 10671 RGB, 10672 0, 10673 &attrs 10674 ); 10675 10676 XRenderComposite( 10677 XDisplayConnection.get, 10678 3, // PictOpOver 10679 handle, 10680 None, 10681 pic, 10682 0, // src 10683 0, 10684 0, // mask 10685 0, 10686 10, // dest 10687 10, 10688 100, // width 10689 100 10690 ); 10691 10692 /+ 10693 XRenderFreePicture( 10694 XDisplayConnection.get, 10695 pic 10696 ); 10697 10698 XRenderFreePicture( 10699 XDisplayConnection.get, 10700 fill 10701 ); 10702 +/ 10703 // on Windows you can stretch but Xrender still can't :( 10704 } 10705 10706 static XRenderPictFormat* RGB; 10707 static XRenderPictFormat* RGBA; 10708 static void repopulateX() { 10709 auto display = XDisplayConnection.get; 10710 RGB = XRenderFindStandardFormat(display, PictStandardRGB24); 10711 RGBA = XRenderFindStandardFormat(display, PictStandardARGB32); 10712 } 10713 10714 XPixmap pixmap; 10715 Picture handle; 10716 } 10717 10718 /// 10719 interface CapableOfBeingDrawnUpon { 10720 /// 10721 ScreenPainter draw(); 10722 /// 10723 int width(); 10724 /// 10725 int height(); 10726 protected ScreenPainterImplementation* activeScreenPainter(); 10727 protected void activeScreenPainter(ScreenPainterImplementation*); 10728 bool closed(); 10729 10730 void delegate() paintingFinishedDg(); 10731 10732 /// Be warned: this can be a very slow operation 10733 TrueColorImage takeScreenshot(); 10734 } 10735 10736 /// 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]. 10737 void flushGui() { 10738 version(X11) { 10739 auto dpy = XDisplayConnection.get(); 10740 XLockDisplay(dpy); 10741 scope(exit) XUnlockDisplay(dpy); 10742 XFlush(dpy); 10743 } 10744 } 10745 10746 /++ 10747 Runs the given code in the GUI thread when its event loop 10748 is available, blocking until it completes. This allows you 10749 to create and manipulate windows from another thread without 10750 invoking undefined behavior. 10751 10752 If this is the gui thread, it runs the code immediately. 10753 10754 If no gui thread exists yet, the current thread is assumed 10755 to be it. Attempting to create windows or run the event loop 10756 in any other thread will cause an assertion failure. 10757 10758 10759 $(TIP 10760 Did you know you can use UFCS on delegate literals? 10761 10762 () { 10763 // code here 10764 }.runInGuiThread; 10765 ) 10766 10767 Returns: 10768 `true` if the function was called, `false` if it was not. 10769 The function may not be called because the gui thread had 10770 already terminated by the time you called this. 10771 10772 History: 10773 Added April 10, 2020 (v7.2.0) 10774 10775 Return value added and implementation tweaked to avoid locking 10776 at program termination on February 24, 2021 (v9.2.1). 10777 +/ 10778 bool runInGuiThread(scope void delegate() dg) @trusted { 10779 claimGuiThread(); 10780 10781 if(thisIsGuiThread) { 10782 dg(); 10783 return true; 10784 } 10785 10786 if(guiThreadTerminating) 10787 return false; 10788 10789 import core.sync.semaphore; 10790 static Semaphore sc; 10791 if(sc is null) 10792 sc = new Semaphore(); 10793 10794 static RunQueueMember* rqm; 10795 if(rqm is null) 10796 rqm = new RunQueueMember; 10797 rqm.dg = cast(typeof(rqm.dg)) dg; 10798 rqm.signal = sc; 10799 rqm.thrown = null; 10800 10801 synchronized(runInGuiThreadLock) { 10802 runInGuiThreadQueue ~= rqm; 10803 } 10804 10805 if(!SimpleWindow.eventWakeUp()) 10806 throw new Error("runInGuiThread impossible; eventWakeUp failed"); 10807 10808 rqm.signal.wait(); 10809 auto t = rqm.thrown; 10810 10811 if(t) 10812 throw t; 10813 10814 return true; 10815 } 10816 10817 // note it runs sync if this is the gui thread.... 10818 void runInGuiThreadAsync(void delegate() dg, void delegate(Exception) nothrow handleError = null) nothrow { 10819 claimGuiThread(); 10820 10821 try { 10822 10823 if(thisIsGuiThread) { 10824 dg(); 10825 return; 10826 } 10827 10828 if(guiThreadTerminating) 10829 return; 10830 10831 RunQueueMember* rqm = new RunQueueMember; 10832 rqm.dg = cast(typeof(rqm.dg)) dg; 10833 rqm.signal = null; 10834 rqm.thrown = null; 10835 10836 synchronized(runInGuiThreadLock) { 10837 runInGuiThreadQueue ~= rqm; 10838 } 10839 10840 if(!SimpleWindow.eventWakeUp()) 10841 throw new Error("runInGuiThread impossible; eventWakeUp failed"); 10842 } catch(Exception e) { 10843 if(handleError) 10844 handleError(e); 10845 } 10846 } 10847 10848 private void runPendingRunInGuiThreadDelegates() { 10849 more: 10850 RunQueueMember* next; 10851 synchronized(runInGuiThreadLock) { 10852 if(runInGuiThreadQueue.length) { 10853 next = runInGuiThreadQueue[0]; 10854 runInGuiThreadQueue = runInGuiThreadQueue[1 .. $]; 10855 } else { 10856 next = null; 10857 } 10858 } 10859 10860 if(next) { 10861 try { 10862 next.dg(); 10863 next.thrown = null; 10864 } catch(Throwable t) { 10865 next.thrown = t; 10866 } 10867 10868 if(next.signal) 10869 next.signal.notify(); 10870 10871 goto more; 10872 } 10873 } 10874 10875 private void claimGuiThread() nothrow { 10876 import core.atomic; 10877 if(cas(&guiThreadExists_, false, true)) 10878 thisIsGuiThread = true; 10879 } 10880 10881 private struct RunQueueMember { 10882 void delegate() dg; 10883 import core.sync.semaphore; 10884 Semaphore signal; 10885 Throwable thrown; 10886 } 10887 10888 private __gshared RunQueueMember*[] runInGuiThreadQueue; 10889 private __gshared Object runInGuiThreadLock = new Object; // intentional CTFE 10890 private bool thisIsGuiThread = false; 10891 private shared bool guiThreadExists_ = false; 10892 private shared bool guiThreadTerminating = false; 10893 10894 /++ 10895 Returns `true` if a gui thread exists, that is, a thread running the simpledisplay.d 10896 event loop. All windows must be exclusively created and managed by a single thread. 10897 10898 If no gui thread exists, simpledisplay.d will automatically adopt the current thread 10899 when you call one of its constructors. 10900 10901 If a gui thread exists, you should check [thisThreadRunningGui] to see if it is this 10902 one. If so, you can run gui functions on it. If not, don't. The helper functions 10903 [runInGuiThread] and [runInGuiThreadAsync] can be used to help you with this automatically. 10904 10905 The reason this function is available is in case you want to message pass between a gui 10906 thread and your current thread. If no gui thread exists or if this is the gui thread, 10907 you're liable to deadlock when trying to communicate since you'd end up talking to yourself. 10908 10909 History: 10910 Added December 3, 2021 (dub v10.5) 10911 +/ 10912 public bool guiThreadExists() { 10913 return guiThreadExists_; 10914 } 10915 10916 /++ 10917 Returns `true` if this thread is either running or set to be running the 10918 simpledisplay.d gui core event loop because it owns windows. 10919 10920 It is important to keep gui-related functionality in the right thread, so you will 10921 want to `runInGuiThread` when you call them (with some specific exceptions called 10922 out in those specific functions' documentation). Notably, all windows must be 10923 created and managed only from the gui thread. 10924 10925 Will return false if simpledisplay's other functions haven't been called 10926 yet; check [guiThreadExists] in addition to this. 10927 10928 History: 10929 Added December 3, 2021 (dub v10.5) 10930 +/ 10931 public bool thisThreadRunningGui() { 10932 return thisIsGuiThread; 10933 } 10934 10935 /++ 10936 Function to help temporarily print debugging info. It will bypass any stdout/err redirection 10937 and go to the controlling tty or console (attaching to the parent and/or allocating one as 10938 needed on Windows. Please note it may overwrite output from other programs in the parent and the 10939 allocated one will not survive if your program crashes. Use the `fileOverride` to print to a log 10940 file instead if you are in one of those situations). 10941 10942 It does not support outputting very many types; just strings and ints are likely to actually work. 10943 10944 It will perform very slowly and swallows any errors that may occur. Moreover, the specific output 10945 is unspecified meaning I can change it at any time. The only point of this function is to help 10946 in temporary use for printf-style debugging. It is NOT nogc, but you can use the `debug` keyword 10947 and the compiler will cheat for you. It is, however, formally nothrow and trusted to ease its use 10948 in those contexts. 10949 10950 $(WARNING 10951 I reserve the right to change this function at any time. You can use it if it helps you 10952 but do not rely on it for anything permanent. 10953 ) 10954 10955 History: 10956 Added December 3, 2021. Not formally supported under any stable tag. 10957 +/ 10958 void sdpyPrintDebugString(string fileOverride = null, T...)(T t) nothrow @trusted { 10959 try { 10960 version(Windows) { 10961 import core.sys.windows.wincon; 10962 if(!AttachConsole(ATTACH_PARENT_PROCESS)) 10963 AllocConsole(); 10964 const(char)* fn = "CONOUT$"; 10965 } else version(Posix) { 10966 const(char)* fn = "/dev/tty"; 10967 } else static assert(0, "Function not implemented for your system"); 10968 10969 if(fileOverride.length) 10970 fn = fileOverride.ptr; 10971 10972 import core.stdc.stdio; 10973 auto fp = fopen(fn, "wt"); 10974 if(fp is null) return; 10975 scope(exit) fclose(fp); 10976 10977 string str; 10978 foreach(item; t) { 10979 static if(is(typeof(item) : const(char)[])) 10980 str ~= item; 10981 else 10982 str ~= toInternal!string(item); 10983 str ~= " "; 10984 } 10985 str ~= "\n"; 10986 10987 fwrite(str.ptr, 1, str.length, fp); 10988 fflush(fp); 10989 } catch(Exception e) { 10990 // sorry no hope 10991 } 10992 } 10993 10994 private void guiThreadFinalize() { 10995 assert(thisIsGuiThread); 10996 10997 guiThreadTerminating = true; // don't add any more from this point on 10998 runPendingRunInGuiThreadDelegates(); 10999 } 11000 11001 /+ 11002 interface IPromise { 11003 void reportProgress(int current, int max, string message); 11004 11005 /+ // not formally in cuz of templates but still 11006 IPromise Then(); 11007 IPromise Catch(); 11008 IPromise Finally(); 11009 +/ 11010 } 11011 11012 /+ 11013 auto promise = async({ ... }); 11014 promise.Then(whatever). 11015 Then(whateverelse). 11016 Catch((exception) { }); 11017 11018 11019 A promise is run inside a fiber and it looks something like: 11020 11021 try { 11022 auto res = whatever(); 11023 auto res2 = whateverelse(res); 11024 } catch(Exception e) { 11025 { }(e); 11026 } 11027 11028 When a thing succeeds, it is passed as an arg to the next 11029 +/ 11030 class Promise(T) : IPromise { 11031 auto Then() { return null; } 11032 auto Catch() { return null; } 11033 auto Finally() { return null; } 11034 11035 // wait for it to resolve and return the value, or rethrow the error if that occurred. 11036 // cannot be called from the gui thread, but this is caught at runtime instead of compile time. 11037 T await(); 11038 } 11039 11040 interface Task { 11041 } 11042 11043 interface Resolvable(T) : Task { 11044 void run(); 11045 11046 void resolve(T); 11047 11048 Resolvable!T then(void delegate(T)); // returns a new promise 11049 Resolvable!T error(Throwable); // js catch 11050 Resolvable!T completed(); // js finally 11051 11052 } 11053 11054 /++ 11055 Runs `work` in a helper thread and sends its return value back to the main gui 11056 thread as the argument to `uponCompletion`. If `work` throws, the exception is 11057 sent to the `uponThrown` if given, or if null, rethrown from the event loop to 11058 kill the program. 11059 11060 You can call reportProgress(position, max, message) to update your parent window 11061 on your progress. 11062 11063 I should also use `shared` methods. FIXME 11064 11065 History: 11066 Added March 6, 2021 (dub version 9.3). 11067 +/ 11068 void runInWorkerThread(T)(T delegate(Task) work, void delegate(T) uponCompletion) { 11069 uponCompletion(work(null)); 11070 } 11071 11072 +/ 11073 11074 /// Used internal to dispatch events to various classes. 11075 interface CapableOfHandlingNativeEvent { 11076 NativeEventHandler getNativeEventHandler(); 11077 11078 /*private*//*protected*/ __gshared CapableOfHandlingNativeEvent[NativeWindowHandle] nativeHandleMapping; 11079 11080 version(X11) { 11081 // if this is impossible, you are allowed to just throw from it 11082 // Note: if you call it from another object, set a flag cuz the manger will call you again 11083 void recreateAfterDisconnect(); 11084 // discard any *connection specific* state, but keep enough that you 11085 // can be recreated if possible. discardConnectionState() is always called immediately 11086 // before recreateAfterDisconnect(), so you can set a flag there to decide if 11087 // you need initialization order 11088 void discardConnectionState(); 11089 } 11090 } 11091 11092 version(X11) 11093 /++ 11094 State of keys on mouse events, especially motion. 11095 11096 Do not trust the actual integer values in this, they are platform-specific. Always use the names. 11097 +/ 11098 enum ModifierState : uint { 11099 shift = 1, /// 11100 capsLock = 2, /// 11101 ctrl = 4, /// 11102 alt = 8, /// Not always available on Windows 11103 windows = 64, /// ditto 11104 numLock = 16, /// 11105 11106 leftButtonDown = 256, /// these aren't available on Windows for key events, so don't use them for that unless your app is X only. 11107 middleButtonDown = 512, /// ditto 11108 rightButtonDown = 1024, /// ditto 11109 } 11110 else version(Emscripten) 11111 enum ModifierState : uint { 11112 shift = 1, /// 11113 capsLock = 2, /// 11114 ctrl = 4, /// 11115 alt = 8, /// Not always available on Windows 11116 windows = 64, /// ditto 11117 numLock = 16, /// 11118 11119 leftButtonDown = 256, /// these aren't available on Windows for key events, so don't use them for that unless your app is X only. 11120 middleButtonDown = 512, /// ditto 11121 rightButtonDown = 1024, /// ditto 11122 } 11123 else version(Windows) 11124 /// ditto 11125 enum ModifierState : uint { 11126 shift = 4, /// 11127 ctrl = 8, /// 11128 11129 // i'm not sure if the next two are available 11130 alt = 256, /// not always available on Windows 11131 windows = 512, /// ditto 11132 11133 capsLock = 1024, /// 11134 numLock = 2048, /// 11135 11136 leftButtonDown = 1, /// not available on key events 11137 middleButtonDown = 16, /// ditto 11138 rightButtonDown = 2, /// ditto 11139 11140 backButtonDown = 0x20, /// not available on X 11141 forwardButtonDown = 0x40, /// ditto 11142 } 11143 else version(OSXCocoa) 11144 // FIXME FIXME NotYetImplementedException 11145 enum ModifierState : uint { 11146 shift = 1, /// 11147 capsLock = 2, /// 11148 ctrl = 4, /// 11149 alt = 8, /// Not always available on Windows 11150 windows = 64, /// ditto 11151 numLock = 16, /// 11152 11153 leftButtonDown = 256, /// these aren't available on Windows for key events, so don't use them for that unless your app is X only. 11154 middleButtonDown = 512, /// ditto 11155 rightButtonDown = 1024, /// ditto 11156 } 11157 11158 /// The names assume a right-handed mouse. These are bitwise combined on the events that use them. 11159 enum MouseButton : int { 11160 none = 0, 11161 left = 1, /// 11162 right = 2, /// 11163 middle = 4, /// 11164 wheelUp = 8, /// 11165 wheelDown = 16, /// 11166 backButton = 32, /// often found on the thumb and used for back in browsers 11167 forwardButton = 64, /// often found on the thumb and used for forward in browsers 11168 } 11169 11170 /// Corresponds to the values found in MouseEvent.buttonLinear, being equal to `core.bitop.bsf(button) + 1` 11171 enum MouseButtonLinear : ubyte { 11172 left = 1, /// 11173 right, /// 11174 middle, /// 11175 wheelUp, /// 11176 wheelDown, /// 11177 backButton, /// often found on the thumb and used for back in browsers 11178 forwardButton, /// often found on the thumb and used for forward in browsers 11179 } 11180 11181 version(WebAssembly) { 11182 /// Do not trust the numeric values as they are platform-specific. Always use the symbolic name. 11183 enum Key { 11184 Escape = 0xff1b, /// 11185 F1 = 0xffbe, /// 11186 F2 = 0xffbf, /// 11187 F3 = 0xffc0, /// 11188 F4 = 0xffc1, /// 11189 F5 = 0xffc2, /// 11190 F6 = 0xffc3, /// 11191 F7 = 0xffc4, /// 11192 F8 = 0xffc5, /// 11193 F9 = 0xffc6, /// 11194 F10 = 0xffc7, /// 11195 F11 = 0xffc8, /// 11196 F12 = 0xffc9, /// 11197 PrintScreen = 0xff61, /// 11198 ScrollLock = 0xff14, /// 11199 Pause = 0xff13, /// 11200 Grave = 0x60, /// The $(BACKTICK) ~ key 11201 // number keys across the top of the keyboard 11202 N1 = 0x31, /// Number key atop the keyboard 11203 N2 = 0x32, /// 11204 N3 = 0x33, /// 11205 N4 = 0x34, /// 11206 N5 = 0x35, /// 11207 N6 = 0x36, /// 11208 N7 = 0x37, /// 11209 N8 = 0x38, /// 11210 N9 = 0x39, /// 11211 N0 = 0x30, /// 11212 Dash = 0x2d, /// 11213 Equals = 0x3d, /// 11214 Backslash = 0x5c, /// The \ | key 11215 Backspace = 0xff08, /// 11216 Insert = 0xff63, /// 11217 Home = 0xff50, /// 11218 PageUp = 0xff55, /// 11219 Delete = 0xffff, /// 11220 End = 0xff57, /// 11221 PageDown = 0xff56, /// 11222 Up = 0xff52, /// 11223 Down = 0xff54, /// 11224 Left = 0xff51, /// 11225 Right = 0xff53, /// 11226 11227 Tab = 0xff09, /// 11228 Q = 0x71, /// 11229 W = 0x77, /// 11230 E = 0x65, /// 11231 R = 0x72, /// 11232 T = 0x74, /// 11233 Y = 0x79, /// 11234 U = 0x75, /// 11235 I = 0x69, /// 11236 O = 0x6f, /// 11237 P = 0x70, /// 11238 LeftBracket = 0x5b, /// the [ { key 11239 RightBracket = 0x5d, /// the ] } key 11240 CapsLock = 0xffe5, /// 11241 A = 0x61, /// 11242 S = 0x73, /// 11243 D = 0x64, /// 11244 F = 0x66, /// 11245 G = 0x67, /// 11246 H = 0x68, /// 11247 J = 0x6a, /// 11248 K = 0x6b, /// 11249 L = 0x6c, /// 11250 Semicolon = 0x3b, /// 11251 Apostrophe = 0x27, /// 11252 Enter = 0xff0d, /// 11253 Shift = 0xffe1, /// 11254 Z = 0x7a, /// 11255 X = 0x78, /// 11256 C = 0x63, /// 11257 V = 0x76, /// 11258 B = 0x62, /// 11259 N = 0x6e, /// 11260 M = 0x6d, /// 11261 Comma = 0x2c, /// 11262 Period = 0x2e, /// 11263 Slash = 0x2f, /// the / ? key 11264 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 11265 Ctrl = 0xffe3, /// 11266 Windows = 0xffeb, /// 11267 Alt = 0xffe9, /// 11268 Space = 0x20, /// 11269 Alt_r = 0xffea, /// ditto of shift_r 11270 Windows_r = 0xffec, /// 11271 Menu = 0xff67, /// 11272 Ctrl_r = 0xffe4, /// 11273 11274 NumLock = 0xff7f, /// 11275 Divide = 0xffaf, /// The / key on the number pad 11276 Multiply = 0xffaa, /// The * key on the number pad 11277 Minus = 0xffad, /// The - key on the number pad 11278 Plus = 0xffab, /// The + key on the number pad 11279 PadEnter = 0xff8d, /// Numberpad enter key 11280 Pad1 = 0xff9c, /// Numberpad keys 11281 Pad2 = 0xff99, /// 11282 Pad3 = 0xff9b, /// 11283 Pad4 = 0xff96, /// 11284 Pad5 = 0xff9d, /// 11285 Pad6 = 0xff98, /// 11286 Pad7 = 0xff95, /// 11287 Pad8 = 0xff97, /// 11288 Pad9 = 0xff9a, /// 11289 Pad0 = 0xff9e, /// 11290 PadDot = 0xff9f, /// 11291 } 11292 } version(X11) { 11293 // FIXME: match ASCII whenever we can. Most of it is already there, 11294 // but there's a few exceptions and mismatches with Windows 11295 11296 /// Do not trust the numeric values as they are platform-specific. Always use the symbolic name. 11297 enum Key { 11298 Escape = 0xff1b, /// 11299 F1 = 0xffbe, /// 11300 F2 = 0xffbf, /// 11301 F3 = 0xffc0, /// 11302 F4 = 0xffc1, /// 11303 F5 = 0xffc2, /// 11304 F6 = 0xffc3, /// 11305 F7 = 0xffc4, /// 11306 F8 = 0xffc5, /// 11307 F9 = 0xffc6, /// 11308 F10 = 0xffc7, /// 11309 F11 = 0xffc8, /// 11310 F12 = 0xffc9, /// 11311 PrintScreen = 0xff61, /// 11312 ScrollLock = 0xff14, /// 11313 Pause = 0xff13, /// 11314 Grave = 0x60, /// The $(BACKTICK) ~ key 11315 // number keys across the top of the keyboard 11316 N1 = 0x31, /// Number key atop the keyboard 11317 N2 = 0x32, /// 11318 N3 = 0x33, /// 11319 N4 = 0x34, /// 11320 N5 = 0x35, /// 11321 N6 = 0x36, /// 11322 N7 = 0x37, /// 11323 N8 = 0x38, /// 11324 N9 = 0x39, /// 11325 N0 = 0x30, /// 11326 Dash = 0x2d, /// 11327 Equals = 0x3d, /// 11328 Backslash = 0x5c, /// The \ | key 11329 Backspace = 0xff08, /// 11330 Insert = 0xff63, /// 11331 Home = 0xff50, /// 11332 PageUp = 0xff55, /// 11333 Delete = 0xffff, /// 11334 End = 0xff57, /// 11335 PageDown = 0xff56, /// 11336 Up = 0xff52, /// 11337 Down = 0xff54, /// 11338 Left = 0xff51, /// 11339 Right = 0xff53, /// 11340 11341 Tab = 0xff09, /// 11342 Q = 0x71, /// 11343 W = 0x77, /// 11344 E = 0x65, /// 11345 R = 0x72, /// 11346 T = 0x74, /// 11347 Y = 0x79, /// 11348 U = 0x75, /// 11349 I = 0x69, /// 11350 O = 0x6f, /// 11351 P = 0x70, /// 11352 LeftBracket = 0x5b, /// the [ { key 11353 RightBracket = 0x5d, /// the ] } key 11354 CapsLock = 0xffe5, /// 11355 A = 0x61, /// 11356 S = 0x73, /// 11357 D = 0x64, /// 11358 F = 0x66, /// 11359 G = 0x67, /// 11360 H = 0x68, /// 11361 J = 0x6a, /// 11362 K = 0x6b, /// 11363 L = 0x6c, /// 11364 Semicolon = 0x3b, /// 11365 Apostrophe = 0x27, /// 11366 Enter = 0xff0d, /// 11367 Shift = 0xffe1, /// 11368 Z = 0x7a, /// 11369 X = 0x78, /// 11370 C = 0x63, /// 11371 V = 0x76, /// 11372 B = 0x62, /// 11373 N = 0x6e, /// 11374 M = 0x6d, /// 11375 Comma = 0x2c, /// 11376 Period = 0x2e, /// 11377 Slash = 0x2f, /// the / ? key 11378 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 11379 Ctrl = 0xffe3, /// 11380 Windows = 0xffeb, /// 11381 Alt = 0xffe9, /// 11382 Space = 0x20, /// 11383 Alt_r = 0xffea, /// ditto of shift_r 11384 Windows_r = 0xffec, /// 11385 Menu = 0xff67, /// 11386 Ctrl_r = 0xffe4, /// 11387 11388 NumLock = 0xff7f, /// 11389 Divide = 0xffaf, /// The / key on the number pad 11390 Multiply = 0xffaa, /// The * key on the number pad 11391 Minus = 0xffad, /// The - key on the number pad 11392 Plus = 0xffab, /// The + key on the number pad 11393 PadEnter = 0xff8d, /// Numberpad enter key 11394 Pad1 = 0xff9c, /// Numberpad keys 11395 Pad2 = 0xff99, /// 11396 Pad3 = 0xff9b, /// 11397 Pad4 = 0xff96, /// 11398 Pad5 = 0xff9d, /// 11399 Pad6 = 0xff98, /// 11400 Pad7 = 0xff95, /// 11401 Pad8 = 0xff97, /// 11402 Pad9 = 0xff9a, /// 11403 Pad0 = 0xff9e, /// 11404 PadDot = 0xff9f, /// 11405 } 11406 } else version(Windows) { 11407 // the character here is for en-us layouts and for illustration only 11408 // if you actually want to get characters, wait for character events 11409 // (the argument to your event handler is simply a dchar) 11410 // those will be converted by the OS for the right locale. 11411 11412 enum Key { 11413 Escape = 0x1b, 11414 F1 = 0x70, 11415 F2 = 0x71, 11416 F3 = 0x72, 11417 F4 = 0x73, 11418 F5 = 0x74, 11419 F6 = 0x75, 11420 F7 = 0x76, 11421 F8 = 0x77, 11422 F9 = 0x78, 11423 F10 = 0x79, 11424 F11 = 0x7a, 11425 F12 = 0x7b, 11426 PrintScreen = 0x2c, 11427 ScrollLock = 0x91, 11428 Pause = 0x13, 11429 Grave = 0xc0, 11430 // number keys across the top of the keyboard 11431 N1 = 0x31, 11432 N2 = 0x32, 11433 N3 = 0x33, 11434 N4 = 0x34, 11435 N5 = 0x35, 11436 N6 = 0x36, 11437 N7 = 0x37, 11438 N8 = 0x38, 11439 N9 = 0x39, 11440 N0 = 0x30, 11441 Dash = 0xbd, 11442 Equals = 0xbb, 11443 Backslash = 0xdc, 11444 Backspace = 0x08, 11445 Insert = 0x2d, 11446 Home = 0x24, 11447 PageUp = 0x21, 11448 Delete = 0x2e, 11449 End = 0x23, 11450 PageDown = 0x22, 11451 Up = 0x26, 11452 Down = 0x28, 11453 Left = 0x25, 11454 Right = 0x27, 11455 11456 Tab = 0x09, 11457 Q = 0x51, 11458 W = 0x57, 11459 E = 0x45, 11460 R = 0x52, 11461 T = 0x54, 11462 Y = 0x59, 11463 U = 0x55, 11464 I = 0x49, 11465 O = 0x4f, 11466 P = 0x50, 11467 LeftBracket = 0xdb, 11468 RightBracket = 0xdd, 11469 CapsLock = 0x14, 11470 A = 0x41, 11471 S = 0x53, 11472 D = 0x44, 11473 F = 0x46, 11474 G = 0x47, 11475 H = 0x48, 11476 J = 0x4a, 11477 K = 0x4b, 11478 L = 0x4c, 11479 Semicolon = 0xba, 11480 Apostrophe = 0xde, 11481 Enter = 0x0d, 11482 Shift = 0x10, 11483 Z = 0x5a, 11484 X = 0x58, 11485 C = 0x43, 11486 V = 0x56, 11487 B = 0x42, 11488 N = 0x4e, 11489 M = 0x4d, 11490 Comma = 0xbc, 11491 Period = 0xbe, 11492 Slash = 0xbf, 11493 Shift_r = 0xa1, // FIXME Note: this isn't sent on all computers, sometimes it just sends Shift, so don't rely on it 11494 Ctrl = 0x11, 11495 Windows = 0x5b, 11496 Alt = -5, // FIXME 11497 Space = 0x20, 11498 Alt_r = 0xffea, // ditto of shift_r 11499 Windows_r = 0x5c, // ditto of shift_r 11500 Menu = 0x5d, 11501 Ctrl_r = 0xa3, // ditto of shift_r 11502 11503 NumLock = 0x90, 11504 Divide = 0x6f, 11505 Multiply = 0x6a, 11506 Minus = 0x6d, 11507 Plus = 0x6b, 11508 PadEnter = -8, // FIXME 11509 Pad1 = 0x61, 11510 Pad2 = 0x62, 11511 Pad3 = 0x63, 11512 Pad4 = 0x64, 11513 Pad5 = 0x65, 11514 Pad6 = 0x66, 11515 Pad7 = 0x67, 11516 Pad8 = 0x68, 11517 Pad9 = 0x69, 11518 Pad0 = 0x60, 11519 PadDot = 0x6e, 11520 } 11521 11522 // I'm keeping this around for reference purposes 11523 // ideally all these buttons will be listed for all platforms, 11524 // but now now I'm just focusing on my US keyboard 11525 version(none) 11526 enum Key { 11527 LBUTTON = 0x01, 11528 RBUTTON = 0x02, 11529 CANCEL = 0x03, 11530 MBUTTON = 0x04, 11531 //static if (_WIN32_WINNT > = 0x500) { 11532 XBUTTON1 = 0x05, 11533 XBUTTON2 = 0x06, 11534 //} 11535 BACK = 0x08, 11536 TAB = 0x09, 11537 CLEAR = 0x0C, 11538 RETURN = 0x0D, 11539 SHIFT = 0x10, 11540 CONTROL = 0x11, 11541 MENU = 0x12, 11542 PAUSE = 0x13, 11543 CAPITAL = 0x14, 11544 KANA = 0x15, 11545 HANGEUL = 0x15, 11546 HANGUL = 0x15, 11547 JUNJA = 0x17, 11548 FINAL = 0x18, 11549 HANJA = 0x19, 11550 KANJI = 0x19, 11551 ESCAPE = 0x1B, 11552 CONVERT = 0x1C, 11553 NONCONVERT = 0x1D, 11554 ACCEPT = 0x1E, 11555 MODECHANGE = 0x1F, 11556 SPACE = 0x20, 11557 PRIOR = 0x21, 11558 NEXT = 0x22, 11559 END = 0x23, 11560 HOME = 0x24, 11561 LEFT = 0x25, 11562 UP = 0x26, 11563 RIGHT = 0x27, 11564 DOWN = 0x28, 11565 SELECT = 0x29, 11566 PRINT = 0x2A, 11567 EXECUTE = 0x2B, 11568 SNAPSHOT = 0x2C, 11569 INSERT = 0x2D, 11570 DELETE = 0x2E, 11571 HELP = 0x2F, 11572 LWIN = 0x5B, 11573 RWIN = 0x5C, 11574 APPS = 0x5D, 11575 SLEEP = 0x5F, 11576 NUMPAD0 = 0x60, 11577 NUMPAD1 = 0x61, 11578 NUMPAD2 = 0x62, 11579 NUMPAD3 = 0x63, 11580 NUMPAD4 = 0x64, 11581 NUMPAD5 = 0x65, 11582 NUMPAD6 = 0x66, 11583 NUMPAD7 = 0x67, 11584 NUMPAD8 = 0x68, 11585 NUMPAD9 = 0x69, 11586 MULTIPLY = 0x6A, 11587 ADD = 0x6B, 11588 SEPARATOR = 0x6C, 11589 SUBTRACT = 0x6D, 11590 DECIMAL = 0x6E, 11591 DIVIDE = 0x6F, 11592 F1 = 0x70, 11593 F2 = 0x71, 11594 F3 = 0x72, 11595 F4 = 0x73, 11596 F5 = 0x74, 11597 F6 = 0x75, 11598 F7 = 0x76, 11599 F8 = 0x77, 11600 F9 = 0x78, 11601 F10 = 0x79, 11602 F11 = 0x7A, 11603 F12 = 0x7B, 11604 F13 = 0x7C, 11605 F14 = 0x7D, 11606 F15 = 0x7E, 11607 F16 = 0x7F, 11608 F17 = 0x80, 11609 F18 = 0x81, 11610 F19 = 0x82, 11611 F20 = 0x83, 11612 F21 = 0x84, 11613 F22 = 0x85, 11614 F23 = 0x86, 11615 F24 = 0x87, 11616 NUMLOCK = 0x90, 11617 SCROLL = 0x91, 11618 LSHIFT = 0xA0, 11619 RSHIFT = 0xA1, 11620 LCONTROL = 0xA2, 11621 RCONTROL = 0xA3, 11622 LMENU = 0xA4, 11623 RMENU = 0xA5, 11624 //static if (_WIN32_WINNT > = 0x500) { 11625 BROWSER_BACK = 0xA6, 11626 BROWSER_FORWARD = 0xA7, 11627 BROWSER_REFRESH = 0xA8, 11628 BROWSER_STOP = 0xA9, 11629 BROWSER_SEARCH = 0xAA, 11630 BROWSER_FAVORITES = 0xAB, 11631 BROWSER_HOME = 0xAC, 11632 VOLUME_MUTE = 0xAD, 11633 VOLUME_DOWN = 0xAE, 11634 VOLUME_UP = 0xAF, 11635 MEDIA_NEXT_TRACK = 0xB0, 11636 MEDIA_PREV_TRACK = 0xB1, 11637 MEDIA_STOP = 0xB2, 11638 MEDIA_PLAY_PAUSE = 0xB3, 11639 LAUNCH_MAIL = 0xB4, 11640 LAUNCH_MEDIA_SELECT = 0xB5, 11641 LAUNCH_APP1 = 0xB6, 11642 LAUNCH_APP2 = 0xB7, 11643 //} 11644 OEM_1 = 0xBA, 11645 //static if (_WIN32_WINNT > = 0x500) { 11646 OEM_PLUS = 0xBB, 11647 OEM_COMMA = 0xBC, 11648 OEM_MINUS = 0xBD, 11649 OEM_PERIOD = 0xBE, 11650 //} 11651 OEM_2 = 0xBF, 11652 OEM_3 = 0xC0, 11653 OEM_4 = 0xDB, 11654 OEM_5 = 0xDC, 11655 OEM_6 = 0xDD, 11656 OEM_7 = 0xDE, 11657 OEM_8 = 0xDF, 11658 //static if (_WIN32_WINNT > = 0x500) { 11659 OEM_102 = 0xE2, 11660 //} 11661 PROCESSKEY = 0xE5, 11662 //static if (_WIN32_WINNT > = 0x500) { 11663 PACKET = 0xE7, 11664 //} 11665 ATTN = 0xF6, 11666 CRSEL = 0xF7, 11667 EXSEL = 0xF8, 11668 EREOF = 0xF9, 11669 PLAY = 0xFA, 11670 ZOOM = 0xFB, 11671 NONAME = 0xFC, 11672 PA1 = 0xFD, 11673 OEM_CLEAR = 0xFE, 11674 } 11675 11676 } else version(OSXCocoa) { 11677 enum Key { 11678 Escape = 53, 11679 F1 = 122, 11680 F2 = 120, 11681 F3 = 99, 11682 F4 = 118, 11683 F5 = 96, 11684 F6 = 97, 11685 F7 = 98, 11686 F8 = 100, 11687 F9 = 101, 11688 F10 = 109, 11689 F11 = 103, 11690 F12 = 111, 11691 PrintScreen = 105, 11692 ScrollLock = 107, 11693 Pause = 113, 11694 Grave = 50, 11695 // number keys across the top of the keyboard 11696 N1 = 18, 11697 N2 = 19, 11698 N3 = 20, 11699 N4 = 21, 11700 N5 = 23, 11701 N6 = 22, 11702 N7 = 26, 11703 N8 = 28, 11704 N9 = 25, 11705 N0 = 29, 11706 Dash = 27, 11707 Equals = 24, 11708 Backslash = 42, 11709 Backspace = 51, 11710 Insert = 114, 11711 Home = 115, 11712 PageUp = 116, 11713 Delete = 117, 11714 End = 119, 11715 PageDown = 121, 11716 Up = 126, 11717 Down = 125, 11718 Left = 123, 11719 Right = 124, 11720 11721 Tab = 48, 11722 Q = 12, 11723 W = 13, 11724 E = 14, 11725 R = 15, 11726 T = 17, 11727 Y = 16, 11728 U = 32, 11729 I = 34, 11730 O = 31, 11731 P = 35, 11732 LeftBracket = 33, 11733 RightBracket = 30, 11734 CapsLock = 57, 11735 A = 0, 11736 S = 1, 11737 D = 2, 11738 F = 3, 11739 G = 5, 11740 H = 4, 11741 J = 38, 11742 K = 40, 11743 L = 37, 11744 Semicolon = 41, 11745 Apostrophe = 39, 11746 Enter = 36, 11747 Shift = 56, 11748 Z = 6, 11749 X = 7, 11750 C = 8, 11751 V = 9, 11752 B = 11, 11753 N = 45, 11754 M = 46, 11755 Comma = 43, 11756 Period = 47, 11757 Slash = 44, 11758 Shift_r = -4, // FIXME Note: this isn't sent on all computers, sometimes it just sends Shift, so don't rely on it 11759 Ctrl = 59, 11760 Windows = 55, 11761 Alt = 58, 11762 Space = 49, 11763 Alt_r = -3, // ditto of shift_r 11764 Windows_r = -2, 11765 Menu = 110, 11766 Ctrl_r = -1, 11767 11768 NumLock = 1, 11769 Divide = 75, 11770 Multiply = 67, 11771 Minus = 78, 11772 Plus = 69, 11773 PadEnter = 76, 11774 Pad1 = 83, 11775 Pad2 = 84, 11776 Pad3 = 85, 11777 Pad4 = 86, 11778 Pad5 = 87, 11779 Pad6 = 88, 11780 Pad7 = 89, 11781 Pad8 = 91, 11782 Pad9 = 92, 11783 Pad0 = 82, 11784 PadDot = 65, 11785 } 11786 11787 } 11788 11789 /* Additional utilities */ 11790 11791 11792 Color fromHsl(real h, real s, real l) { 11793 return arsd.color.fromHsl([h,s,l]); 11794 } 11795 11796 11797 11798 /* ********** What follows is the system-specific implementations *********/ 11799 version(Windows) { 11800 11801 11802 // helpers for making HICONs from MemoryImages 11803 class WindowsIcon { 11804 struct Win32Icon { 11805 align(1): 11806 uint biSize; 11807 int biWidth; 11808 int biHeight; 11809 ushort biPlanes; 11810 ushort biBitCount; 11811 uint biCompression; 11812 uint biSizeImage; 11813 int biXPelsPerMeter; 11814 int biYPelsPerMeter; 11815 uint biClrUsed; 11816 uint biClrImportant; 11817 // RGBQUAD[colorCount] biColors; 11818 /* Pixels: 11819 Uint8 pixels[] 11820 */ 11821 /* Mask: 11822 Uint8 mask[] 11823 */ 11824 } 11825 11826 ubyte[] fromMemoryImage(MemoryImage mi, out int icon_len, out int width, out int height) { 11827 11828 assert(mi.width <= 256, "image too wide"); 11829 assert(mi.height <= 256, "image too tall"); 11830 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 11831 assert(mi.height % 4 == 0, "image not multiple of 4 height"); 11832 11833 int icon_plen = mi.width * mi.height * 4; 11834 int icon_mlen = mi.width * mi.height / 8; 11835 11836 int colorCount = 0; 11837 icon_len = 40 + icon_plen + icon_mlen + cast(int) RGBQUAD.sizeof * colorCount; 11838 11839 ubyte[] memory = new ubyte[](Win32Icon.sizeof + icon_plen + icon_mlen); 11840 Win32Icon* icon_win32 = cast(Win32Icon*) memory.ptr; 11841 11842 auto data = memory[Win32Icon.sizeof .. $]; 11843 11844 width = mi.width; 11845 height = mi.height; 11846 11847 auto trueColorImage = mi.getAsTrueColorImage(); 11848 11849 icon_win32.biSize = 40; 11850 icon_win32.biWidth = mi.width; 11851 icon_win32.biHeight = mi.height*2; 11852 icon_win32.biPlanes = 1; 11853 icon_win32.biBitCount = 32; 11854 icon_win32.biSizeImage = icon_plen + icon_mlen; 11855 11856 int offset = 0; 11857 int andOff = icon_plen * 8; // the and offset is in bits 11858 11859 // leaving the and mask as the default 0 so the rgba alpha blend 11860 // does its thing instead 11861 for(int y = height - 1; y >= 0; y--) { 11862 int off2 = y * width * 4; 11863 foreach(x; 0 .. width) { 11864 data[offset + 2] = trueColorImage.imageData.bytes[off2 + 0]; 11865 data[offset + 1] = trueColorImage.imageData.bytes[off2 + 1]; 11866 data[offset + 0] = trueColorImage.imageData.bytes[off2 + 2]; 11867 data[offset + 3] = trueColorImage.imageData.bytes[off2 + 3]; 11868 11869 offset += 4; 11870 off2 += 4; 11871 } 11872 } 11873 11874 return memory; 11875 } 11876 11877 this(MemoryImage mi) { 11878 int icon_len, width, height; 11879 11880 auto icon_win32 = fromMemoryImage(mi, icon_len, width, height); 11881 11882 /* 11883 PNG* png = readPnpngData); 11884 PNGHeader pngh = getHeader(png); 11885 void* icon_win32; 11886 if(pngh.depth == 4) { 11887 auto i = new Win32Icon!(16); 11888 i.fromPNG(png, pngh, icon_len, width, height); 11889 icon_win32 = i; 11890 } 11891 else if(pngh.depth == 8) { 11892 auto i = new Win32Icon!(256); 11893 i.fromPNG(png, pngh, icon_len, width, height); 11894 icon_win32 = i; 11895 } else assert(0); 11896 */ 11897 11898 hIcon = CreateIconFromResourceEx(icon_win32.ptr, icon_len, true, 0x00030000, width, height, 0); 11899 11900 if(hIcon is null) throw new WindowsApiException("CreateIconFromResourceEx", GetLastError()); 11901 } 11902 11903 ~this() { 11904 if(!thisIsGuiThread) return; // FIXME: leaks if multithreaded gc 11905 DestroyIcon(hIcon); 11906 } 11907 11908 HICON hIcon; 11909 } 11910 11911 11912 11913 11914 11915 11916 alias int delegate(HWND, UINT, WPARAM, LPARAM, out int) NativeEventHandler; 11917 alias HWND NativeWindowHandle; 11918 11919 extern(Windows) 11920 LRESULT WndProc(HWND hWnd, UINT iMessage, WPARAM wParam, LPARAM lParam) nothrow { 11921 try { 11922 if(SimpleWindow.handleNativeGlobalEvent !is null) { 11923 // it returns zero if the message is handled, so we won't do anything more there 11924 // do I like that though? 11925 int mustReturn; 11926 auto ret = SimpleWindow.handleNativeGlobalEvent(hWnd, iMessage, wParam, lParam, mustReturn); 11927 if(mustReturn) 11928 return ret; 11929 } 11930 11931 if(auto window = hWnd in CapableOfHandlingNativeEvent.nativeHandleMapping) { 11932 if(window.getNativeEventHandler !is null) { 11933 int mustReturn; 11934 auto ret = window.getNativeEventHandler()(hWnd, iMessage, wParam, lParam, mustReturn); 11935 if(mustReturn) 11936 return ret; 11937 } 11938 if(auto w = cast(SimpleWindow) (*window)) 11939 return w.windowProcedure(hWnd, iMessage, wParam, lParam); 11940 else 11941 return DefWindowProc(hWnd, iMessage, wParam, lParam); 11942 } else { 11943 return DefWindowProc(hWnd, iMessage, wParam, lParam); 11944 } 11945 } catch (Exception e) { 11946 try { 11947 sdpy_abort(e); 11948 return 0; 11949 } catch(Exception e) { assert(0); } 11950 } 11951 } 11952 11953 void sdpy_abort(Throwable e) nothrow { 11954 try 11955 MessageBoxA(null, (e.toString() ~ "\0").ptr, "Exception caught in WndProc", 0); 11956 catch(Exception e) 11957 MessageBoxA(null, "Exception.toString threw too!", "Exception caught in WndProc", 0); 11958 ExitProcess(1); 11959 } 11960 11961 mixin template NativeScreenPainterImplementation() { 11962 HDC hdc; 11963 HWND hwnd; 11964 //HDC windowHdc; 11965 HBITMAP oldBmp; 11966 11967 void create(PaintingHandle window) { 11968 hwnd = window; 11969 11970 if(auto sw = cast(SimpleWindow) this.window) { 11971 // drawing on a window, double buffer 11972 auto windowHdc = GetDC(hwnd); 11973 11974 auto buffer = sw.impl.buffer; 11975 if(buffer is null) { 11976 hdc = windowHdc; 11977 windowDc = true; 11978 } else { 11979 hdc = CreateCompatibleDC(windowHdc); 11980 11981 ReleaseDC(hwnd, windowHdc); 11982 11983 oldBmp = SelectObject(hdc, buffer); 11984 } 11985 } else { 11986 // drawing on something else, draw directly 11987 hdc = CreateCompatibleDC(null); 11988 SelectObject(hdc, window); 11989 } 11990 11991 // X doesn't draw a text background, so neither should we 11992 SetBkMode(hdc, TRANSPARENT); 11993 11994 ensureDefaultFontLoaded(); 11995 11996 if(defaultGuiFont) { 11997 SelectObject(hdc, defaultGuiFont); 11998 // DeleteObject(defaultGuiFont); 11999 } 12000 } 12001 12002 static HFONT defaultGuiFont; 12003 static void ensureDefaultFontLoaded() { 12004 static bool triedDefaultGuiFont = false; 12005 if(!triedDefaultGuiFont) { 12006 NONCLIENTMETRICS params; 12007 params.cbSize = params.sizeof; 12008 if(SystemParametersInfo(SPI_GETNONCLIENTMETRICS, params.sizeof, ¶ms, 0)) { 12009 defaultGuiFont = CreateFontIndirect(¶ms.lfMessageFont); 12010 } 12011 triedDefaultGuiFont = true; 12012 } 12013 } 12014 12015 private OperatingSystemFont _activeFont; 12016 12017 void setFont(OperatingSystemFont font) { 12018 _activeFont = font; 12019 if(font && font.font) { 12020 if(SelectObject(hdc, font.font) == HGDI_ERROR) { 12021 // error... how to handle tho? 12022 } else { 12023 12024 } 12025 } 12026 else if(defaultGuiFont) 12027 SelectObject(hdc, defaultGuiFont); 12028 } 12029 12030 arsd.color.Rectangle _clipRectangle; 12031 12032 void setClipRectangle(int x, int y, int width, int height) { 12033 auto old = _clipRectangle; 12034 _clipRectangle = arsd.color.Rectangle(Point(x, y), Size(width, height)); 12035 if(old == _clipRectangle) 12036 return; 12037 12038 if(width == 0 || height == 0) { 12039 SelectClipRgn(hdc, null); 12040 } else { 12041 auto region = CreateRectRgn(x, y, x + width, y + height); 12042 SelectClipRgn(hdc, region); 12043 DeleteObject(region); 12044 } 12045 } 12046 12047 12048 // just because we can on Windows... 12049 //void create(Image image); 12050 12051 void invalidateRect(Rectangle invalidRect) { 12052 RECT rect; 12053 rect.left = invalidRect.left; 12054 rect.right = invalidRect.right; 12055 rect.top = invalidRect.top; 12056 rect.bottom = invalidRect.bottom; 12057 InvalidateRect(hwnd, &rect, false); 12058 } 12059 bool manualInvalidations; 12060 12061 void dispose() { 12062 // FIXME: this.window.width/height is probably wrong 12063 // BitBlt(windowHdc, 0, 0, this.window.width, this.window.height, hdc, 0, 0, SRCCOPY); 12064 // ReleaseDC(hwnd, windowHdc); 12065 12066 // FIXME: it shouldn't invalidate the whole thing in all cases... it would be ideal to do this right 12067 if(cast(SimpleWindow) this.window) { 12068 if(!manualInvalidations) 12069 InvalidateRect(hwnd, cast(RECT*)null, false); // no need to erase bg as the whole thing gets bitblt'd ove 12070 } 12071 12072 if(originalPen !is null) 12073 SelectObject(hdc, originalPen); 12074 if(currentPen !is null) 12075 DeleteObject(currentPen); 12076 if(originalBrush !is null) 12077 SelectObject(hdc, originalBrush); 12078 if(currentBrush !is null) 12079 DeleteObject(currentBrush); 12080 12081 SelectObject(hdc, oldBmp); 12082 12083 if(windowDc) 12084 ReleaseDC(hwnd, hdc); 12085 else 12086 DeleteDC(hdc); 12087 12088 if(window.paintingFinishedDg !is null) 12089 window.paintingFinishedDg()(); 12090 } 12091 12092 bool windowDc; 12093 HPEN originalPen; 12094 HPEN currentPen; 12095 12096 Pen _activePen; 12097 12098 Color _outlineColor; 12099 12100 @property void pen(Pen p) { 12101 _activePen = p; 12102 _outlineColor = p.color; 12103 12104 HPEN pen; 12105 if(p.color.a == 0) { 12106 pen = GetStockObject(NULL_PEN); 12107 } else { 12108 int style = PS_SOLID; 12109 final switch(p.style) { 12110 case Pen.Style.Solid: 12111 style = PS_SOLID; 12112 break; 12113 case Pen.Style.Dashed: 12114 style = PS_DASH; 12115 break; 12116 case Pen.Style.Dotted: 12117 style = PS_DOT; 12118 break; 12119 } 12120 pen = CreatePen(style, p.width, RGB(p.color.r, p.color.g, p.color.b)); 12121 } 12122 auto orig = SelectObject(hdc, pen); 12123 if(originalPen is null) 12124 originalPen = orig; 12125 12126 if(currentPen !is null) 12127 DeleteObject(currentPen); 12128 12129 currentPen = pen; 12130 12131 // the outline is like a foreground since it's done that way on X 12132 SetTextColor(hdc, RGB(p.color.r, p.color.g, p.color.b)); 12133 12134 } 12135 12136 @property void rasterOp(RasterOp op) { 12137 int mode; 12138 final switch(op) { 12139 case RasterOp.normal: 12140 mode = R2_COPYPEN; 12141 break; 12142 case RasterOp.xor: 12143 mode = R2_XORPEN; 12144 break; 12145 } 12146 SetROP2(hdc, mode); 12147 } 12148 12149 HBRUSH originalBrush; 12150 HBRUSH currentBrush; 12151 Color _fillColor = Color(1, 1, 1, 1); // what are the odds that they'd set this?? 12152 @property void fillColor(Color c) { 12153 if(c == _fillColor) 12154 return; 12155 _fillColor = c; 12156 HBRUSH brush; 12157 if(c.a == 0) { 12158 brush = GetStockObject(HOLLOW_BRUSH); 12159 } else { 12160 brush = CreateSolidBrush(RGB(c.r, c.g, c.b)); 12161 } 12162 auto orig = SelectObject(hdc, brush); 12163 if(originalBrush is null) 12164 originalBrush = orig; 12165 12166 if(currentBrush !is null) 12167 DeleteObject(currentBrush); 12168 12169 currentBrush = brush; 12170 12171 // background color is NOT set because X doesn't draw text backgrounds 12172 // SetBkColor(hdc, RGB(255, 255, 255)); 12173 } 12174 12175 void drawImage(int x, int y, Image i, int ix, int iy, int w, int h) { 12176 BITMAP bm; 12177 12178 HDC hdcMem = CreateCompatibleDC(hdc); 12179 HBITMAP hbmOld = SelectObject(hdcMem, i.handle); 12180 12181 GetObject(i.handle, bm.sizeof, &bm); 12182 12183 // or should I AlphaBlend!??!?! 12184 BitBlt(hdc, x, y, w /* bm.bmWidth */, /*bm.bmHeight*/ h, hdcMem, ix, iy, SRCCOPY); 12185 12186 SelectObject(hdcMem, hbmOld); 12187 DeleteDC(hdcMem); 12188 } 12189 12190 void drawPixmap(Sprite s, int x, int y, int ix, int iy, int w, int h) { 12191 BITMAP bm; 12192 12193 HDC hdcMem = CreateCompatibleDC(hdc); 12194 HBITMAP hbmOld = SelectObject(hdcMem, s.handle); 12195 12196 GetObject(s.handle, bm.sizeof, &bm); 12197 12198 version(CRuntime_DigitalMars) goto noalpha; 12199 12200 // or should I AlphaBlend!??!?! note it is supposed to be premultiplied http://www.fengyuan.com/article/alphablend.html 12201 if(s.enableAlpha) { 12202 auto dw = w ? w : bm.bmWidth; 12203 auto dh = h ? h : bm.bmHeight; 12204 BLENDFUNCTION bf; 12205 bf.BlendOp = AC_SRC_OVER; 12206 bf.SourceConstantAlpha = 255; 12207 bf.AlphaFormat = AC_SRC_ALPHA; 12208 AlphaBlend(hdc, x, y, dw, dh, hdcMem, ix, iy, dw, dh, bf); 12209 } else { 12210 noalpha: 12211 BitBlt(hdc, x, y, w ? w : bm.bmWidth, h ? h : bm.bmHeight, hdcMem, ix, iy, SRCCOPY); 12212 } 12213 12214 SelectObject(hdcMem, hbmOld); 12215 DeleteDC(hdcMem); 12216 } 12217 12218 Size textSize(scope const(char)[] text) { 12219 bool dummyX; 12220 if(text.length == 0) { 12221 text = " "; 12222 dummyX = true; 12223 } 12224 RECT rect; 12225 WCharzBuffer buffer = WCharzBuffer(text); 12226 DrawTextW(hdc, buffer.ptr, cast(int) buffer.length, &rect, DT_CALCRECT | DT_NOPREFIX); 12227 return Size(dummyX ? 0 : rect.right, rect.bottom); 12228 } 12229 12230 void drawText(int x, int y, int x2, int y2, scope const(char)[] text, uint alignment) { 12231 if(text.length && text[$-1] == '\n') 12232 text = text[0 .. $-1]; // tailing newlines are weird on windows... 12233 if(text.length && text[$-1] == '\r') 12234 text = text[0 .. $-1]; 12235 12236 WCharzBuffer buffer = WCharzBuffer(text, WindowsStringConversionFlags.convertNewLines); 12237 if(x2 == 0 && y2 == 0) { 12238 TextOutW(hdc, x, y, buffer.ptr, cast(int) buffer.length); 12239 } else { 12240 RECT rect; 12241 rect.left = x; 12242 rect.top = y; 12243 rect.right = x2; 12244 rect.bottom = y2; 12245 12246 uint mode = DT_LEFT; 12247 if(alignment & TextAlignment.Right) 12248 mode = DT_RIGHT; 12249 else if(alignment & TextAlignment.Center) 12250 mode = DT_CENTER; 12251 12252 // FIXME: vcenter on windows only works with single line, but I want it to work in all cases 12253 if(alignment & TextAlignment.VerticalCenter) 12254 mode |= DT_VCENTER | DT_SINGLELINE; 12255 12256 DrawTextW(hdc, buffer.ptr, cast(int) buffer.length, &rect, mode | DT_NOPREFIX); 12257 } 12258 12259 /* 12260 uint mode; 12261 12262 if(alignment & TextAlignment.Center) 12263 mode = TA_CENTER; 12264 12265 SetTextAlign(hdc, mode); 12266 */ 12267 } 12268 12269 int fontHeight() { 12270 TEXTMETRIC metric; 12271 if(GetTextMetricsW(hdc, &metric)) { 12272 return metric.tmHeight; 12273 } 12274 12275 return 16; // idk just guessing here, maybe we should throw 12276 } 12277 12278 void drawPixel(int x, int y) { 12279 SetPixel(hdc, x, y, RGB(_activePen.color.r, _activePen.color.g, _activePen.color.b)); 12280 } 12281 12282 // The basic shapes, outlined 12283 12284 void drawLine(int x1, int y1, int x2, int y2) { 12285 MoveToEx(hdc, x1, y1, null); 12286 LineTo(hdc, x2, y2); 12287 } 12288 12289 void drawRectangle(int x, int y, int width, int height) { 12290 // FIXME: with a wider pen this might not draw quite right. im not sure. 12291 gdi.Rectangle(hdc, x, y, x + width, y + height); 12292 } 12293 12294 void drawRectangleRounded(Point upperLeft, Point lowerRight, int borderRadius) { 12295 RoundRect( 12296 hdc, 12297 upperLeft.x, upperLeft.y, 12298 lowerRight.x, lowerRight.y, 12299 borderRadius, borderRadius 12300 ); 12301 } 12302 12303 /// Arguments are the points of the bounding rectangle 12304 void drawEllipse(int x1, int y1, int x2, int y2) { 12305 Ellipse(hdc, x1, y1, x2, y2); 12306 } 12307 12308 void drawArc(int x1, int y1, int width, int height, int start, int length) { 12309 //if(length > 360*64) 12310 //length = 360*64; 12311 12312 if((start == 0 && length == 360*64)) { 12313 drawEllipse(x1, y1, x1 + width, y1 + height); 12314 } else { 12315 import core.stdc.math; 12316 12317 bool clockwise = false; 12318 if(length < 0) { 12319 clockwise = true; 12320 length = -length; 12321 } 12322 12323 double startAngle = cast(double) start / 64.0 / 180.0 * 3.14159265358979323; 12324 double endAngle = cast(double) (start + length) / 64.0 / 180.0 * 3.14159265358979323; 12325 12326 auto c1 = cast(int) (cos(startAngle) * width / 2.0 + double(x1) + double(width) / 2.0); 12327 auto c2 = cast(int) (-sin(startAngle) * height / 2.0 + double(y1) + double(height) / 2.0); 12328 auto c3 = cast(int) (cos(endAngle) * width / 2.0 + double(x1) + double(width) / 2.0); 12329 auto c4 = cast(int) (-sin(endAngle) * height / 2.0 + double(y1) + double(height) / 2.0); 12330 12331 if(clockwise) { 12332 auto t1 = c1; 12333 auto t2 = c2; 12334 c1 = c3; 12335 c2 = c4; 12336 c3 = t1; 12337 c4 = t2; 12338 } 12339 12340 //if(_activePen.color.a) 12341 //Arc(hdc, x1, y1, x1 + width + 0, y1 + height + 0, c1, c2, c3, c4); 12342 //if(_fillColor.a) 12343 12344 Pie(hdc, x1, y1, x1 + width + 0, y1 + height + 0, c1, c2, c3, c4); 12345 } 12346 } 12347 12348 void drawPolygon(Point[] vertexes) { 12349 POINT[] points; 12350 points.length = vertexes.length; 12351 12352 foreach(i, p; vertexes) { 12353 points[i].x = p.x; 12354 points[i].y = p.y; 12355 } 12356 12357 Polygon(hdc, points.ptr, cast(int) points.length); 12358 } 12359 } 12360 12361 12362 // Mix this into the SimpleWindow class 12363 mixin template NativeSimpleWindowImplementation() { 12364 int curHidden = 0; // counter 12365 __gshared static bool[string] knownWinClasses; 12366 static bool altPressed = false; 12367 12368 HANDLE oldCursor; 12369 12370 void hideCursor () { 12371 if(curHidden == 0) 12372 oldCursor = SetCursor(null); 12373 ++curHidden; 12374 } 12375 12376 void showCursor () { 12377 --curHidden; 12378 if(curHidden == 0) { 12379 SetCursor(currentCursor is null ? oldCursor : currentCursor); // show it immediately without waiting for mouse movement 12380 } 12381 } 12382 12383 12384 int minWidth = 0, minHeight = 0, maxWidth = int.max, maxHeight = int.max; 12385 12386 void setMinSize (int minwidth, int minheight) { 12387 minWidth = minwidth; 12388 minHeight = minheight; 12389 } 12390 void setMaxSize (int maxwidth, int maxheight) { 12391 maxWidth = maxwidth; 12392 maxHeight = maxheight; 12393 } 12394 12395 // FIXME i'm not sure that Windows has this functionality 12396 // though it is nonessential anyway. 12397 void setResizeGranularity (int granx, int grany) {} 12398 12399 ScreenPainter getPainter(bool manualInvalidations) { 12400 return ScreenPainter(this, hwnd, manualInvalidations); 12401 } 12402 12403 HBITMAP buffer; 12404 12405 void setTitle(string title) { 12406 WCharzBuffer bfr = WCharzBuffer(title); 12407 SetWindowTextW(hwnd, bfr.ptr); 12408 } 12409 12410 string getTitle() { 12411 auto len = GetWindowTextLengthW(hwnd); 12412 if (!len) 12413 return null; 12414 wchar[256] tmpBuffer; 12415 wchar[] buffer = (len <= tmpBuffer.length) ? tmpBuffer[] : new wchar[len]; 12416 auto len2 = GetWindowTextW(hwnd, buffer.ptr, cast(int) buffer.length); 12417 auto str = buffer[0 .. len2]; 12418 return makeUtf8StringFromWindowsString(str); 12419 } 12420 12421 void move(int x, int y) { 12422 RECT rect; 12423 GetWindowRect(hwnd, &rect); 12424 // move it while maintaining the same size... 12425 MoveWindow(hwnd, x, y, rect.right - rect.left, rect.bottom - rect.top, true); 12426 } 12427 12428 void resize(int w, int h) { 12429 RECT rect; 12430 GetWindowRect(hwnd, &rect); 12431 12432 RECT client; 12433 GetClientRect(hwnd, &client); 12434 12435 rect.right = rect.right - client.right + w; 12436 rect.bottom = rect.bottom - client.bottom + h; 12437 12438 // same position, new size for the client rectangle 12439 MoveWindow(hwnd, rect.left, rect.top, rect.right, rect.bottom, true); 12440 12441 updateOpenglViewportIfNeeded(w, h); 12442 } 12443 12444 void moveResize (int x, int y, int w, int h) { 12445 // what's given is the client rectangle, we need to adjust 12446 12447 RECT rect; 12448 rect.left = x; 12449 rect.top = y; 12450 rect.right = w + x; 12451 rect.bottom = h + y; 12452 if(!AdjustWindowRect(&rect, GetWindowLong(hwnd, GWL_STYLE), GetMenu(hwnd) !is null)) 12453 throw new WindowsApiException("AdjustWindowRect", GetLastError()); 12454 12455 MoveWindow(hwnd, rect.left, rect.top, rect.right - rect.left, rect.bottom - rect.top, true); 12456 updateOpenglViewportIfNeeded(w, h); 12457 if (windowResized !is null) windowResized(w, h); 12458 } 12459 12460 version(without_opengl) {} else { 12461 HGLRC ghRC; 12462 HDC ghDC; 12463 } 12464 12465 void createWindow(int width, int height, string title, OpenGlOptions opengl, SimpleWindow parent) { 12466 string cnamec; 12467 if (sdpyWindowClassStr is null) loadBinNameToWindowClassName(); 12468 if (sdpyWindowClassStr is null || sdpyWindowClassStr[0] == 0) { 12469 cnamec = "DSimpleWindow"; 12470 } else { 12471 cnamec = sdpyWindowClass; 12472 } 12473 12474 WCharzBuffer cn = WCharzBuffer(cnamec); 12475 12476 HINSTANCE hInstance = cast(HINSTANCE) GetModuleHandle(null); 12477 12478 if(cnamec !in knownWinClasses) { 12479 WNDCLASSEX wc; 12480 12481 // FIXME: I might be able to use cbWndExtra to hold the pointer back 12482 // to the object. Maybe. 12483 wc.cbSize = wc.sizeof; 12484 wc.cbClsExtra = 0; 12485 wc.cbWndExtra = 0; 12486 wc.hbrBackground = cast(HBRUSH) (COLOR_WINDOW+1); // GetStockObject(WHITE_BRUSH); 12487 wc.hCursor = LoadCursorW(null, IDC_ARROW); 12488 wc.hIcon = LoadIcon(hInstance, null); 12489 wc.hInstance = hInstance; 12490 wc.lpfnWndProc = &WndProc; 12491 wc.lpszClassName = cn.ptr; 12492 wc.hIconSm = null; 12493 wc.style = CS_HREDRAW | CS_VREDRAW | CS_DBLCLKS; 12494 if(!RegisterClassExW(&wc)) 12495 throw new WindowsApiException("RegisterClassExW", GetLastError()); 12496 knownWinClasses[cnamec] = true; 12497 } 12498 12499 int style; 12500 uint flags = WS_EX_ACCEPTFILES; // accept drag-drop files 12501 12502 // FIXME: windowType and customizationFlags 12503 final switch(windowType) { 12504 case WindowTypes.normal: 12505 if(resizability == Resizability.fixedSize) { 12506 style = WS_SYSMENU | WS_OVERLAPPED | WS_CAPTION; 12507 } else { 12508 style = WS_OVERLAPPEDWINDOW; 12509 } 12510 break; 12511 case WindowTypes.undecorated: 12512 style = WS_POPUP | WS_SYSMENU; 12513 break; 12514 case WindowTypes.eventOnly: 12515 _hidden = true; 12516 break; 12517 case WindowTypes.dropdownMenu: 12518 case WindowTypes.popupMenu: 12519 case WindowTypes.notification: 12520 style = WS_POPUP; 12521 flags |= WS_EX_NOACTIVATE; 12522 break; 12523 case WindowTypes.dialog: 12524 style = WS_OVERLAPPEDWINDOW; 12525 break; 12526 case WindowTypes.nestedChild: 12527 style = WS_CHILD; 12528 break; 12529 case WindowTypes.minimallyWrapped: 12530 assert(0, "construct minimally wrapped through the other ctor overlad"); 12531 } 12532 12533 if ((customizationFlags & WindowFlags.extraComposite) != 0) 12534 flags |= WS_EX_LAYERED; // composite window for better performance and effects support 12535 12536 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 12537 CW_USEDEFAULT, CW_USEDEFAULT, width, height, 12538 parent is null ? null : parent.impl.hwnd, null, hInstance, null); 12539 12540 if(!hwnd) 12541 throw new WindowsApiException("CreateWindowEx", GetLastError()); 12542 12543 if ((customizationFlags & WindowFlags.extraComposite) != 0) 12544 setOpacity(255); 12545 12546 SimpleWindow.nativeMapping[hwnd] = this; 12547 CapableOfHandlingNativeEvent.nativeHandleMapping[hwnd] = this; 12548 12549 if(windowType == WindowTypes.eventOnly) 12550 return; 12551 12552 HDC hdc = GetDC(hwnd); 12553 12554 if(!hdc) 12555 throw new WindowsApiException("GetDC", GetLastError()); 12556 12557 version(without_opengl) {} 12558 else { 12559 if(opengl == OpenGlOptions.yes) { 12560 if(!openGlLibrariesSuccessfullyLoaded) throw new Exception("OpenGL libraries did not load"); 12561 static if (SdpyIsUsingIVGLBinds) {if (glbindGetProcAddress("glHint") is null) assert(0, "GL: error loading OpenGL"); } // loads all necessary functions 12562 static if (SdpyIsUsingIVGLBinds) import iv.glbinds; // override druntime windows imports 12563 ghDC = hdc; 12564 PIXELFORMATDESCRIPTOR pfd; 12565 12566 pfd.nSize = PIXELFORMATDESCRIPTOR.sizeof; 12567 pfd.nVersion = 1; 12568 pfd.dwFlags = PFD_DRAW_TO_WINDOW | PFD_SUPPORT_OPENGL | PFD_DOUBLEBUFFER; 12569 pfd.dwLayerMask = PFD_MAIN_PLANE; 12570 pfd.iPixelType = PFD_TYPE_RGBA; 12571 pfd.cColorBits = 24; 12572 pfd.cDepthBits = 24; 12573 pfd.cAccumBits = 0; 12574 pfd.cStencilBits = 8; // any reasonable OpenGL implementation should support this anyway 12575 12576 auto pixelformat = ChoosePixelFormat(hdc, &pfd); 12577 12578 if (pixelformat == 0) 12579 throw new WindowsApiException("ChoosePixelFormat", GetLastError()); 12580 12581 if (SetPixelFormat(hdc, pixelformat, &pfd) == 0) 12582 throw new WindowsApiException("SetPixelFormat", GetLastError()); 12583 12584 if (sdpyOpenGLContextVersion && wglCreateContextAttribsARB is null) { 12585 // windoze is idiotic: we have to have OpenGL context to get function addresses 12586 // so we will create fake context to get that stupid address 12587 auto tmpcc = wglCreateContext(ghDC); 12588 if (tmpcc !is null) { 12589 scope(exit) { wglMakeCurrent(ghDC, null); wglDeleteContext(tmpcc); } 12590 wglMakeCurrent(ghDC, tmpcc); 12591 wglInitOtherFunctions(); 12592 } 12593 } 12594 12595 if (wglCreateContextAttribsARB !is null && sdpyOpenGLContextVersion) { 12596 int[9] contextAttribs = [ 12597 WGL_CONTEXT_MAJOR_VERSION_ARB, (sdpyOpenGLContextVersion>>8), 12598 WGL_CONTEXT_MINOR_VERSION_ARB, (sdpyOpenGLContextVersion&0xff), 12599 WGL_CONTEXT_PROFILE_MASK_ARB, (sdpyOpenGLContextCompatible ? WGL_CONTEXT_COMPATIBILITY_PROFILE_BIT_ARB : WGL_CONTEXT_CORE_PROFILE_BIT_ARB), 12600 // for modern context, set "forward compatibility" flag too 12601 (sdpyOpenGLContextCompatible ? 0/*None*/ : WGL_CONTEXT_FLAGS_ARB), WGL_CONTEXT_FORWARD_COMPATIBLE_BIT_ARB, 12602 0/*None*/, 12603 ]; 12604 ghRC = wglCreateContextAttribsARB(ghDC, null, contextAttribs.ptr); 12605 if (ghRC is null && sdpyOpenGLContextAllowFallback) { 12606 // activate fallback mode 12607 // 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; 12608 ghRC = wglCreateContext(ghDC); 12609 } 12610 if (ghRC is null) 12611 throw new WindowsApiException("wglCreateContextAttribsARB", GetLastError()); 12612 } else { 12613 // try to do at least something 12614 if (sdpyOpenGLContextAllowFallback || sdpyOpenGLContextVersion == 0) { 12615 sdpyOpenGLContextVersion = 0; 12616 ghRC = wglCreateContext(ghDC); 12617 } 12618 if (ghRC is null) 12619 throw new WindowsApiException("wglCreateContext", GetLastError()); 12620 } 12621 } 12622 } 12623 12624 if(opengl == OpenGlOptions.no) { 12625 buffer = CreateCompatibleBitmap(hdc, width, height); 12626 12627 auto hdcBmp = CreateCompatibleDC(hdc); 12628 // make sure it's filled with a blank slate 12629 auto oldBmp = SelectObject(hdcBmp, buffer); 12630 auto oldBrush = SelectObject(hdcBmp, GetStockObject(WHITE_BRUSH)); 12631 auto oldPen = SelectObject(hdcBmp, GetStockObject(WHITE_PEN)); 12632 gdi.Rectangle(hdcBmp, 0, 0, width, height); 12633 SelectObject(hdcBmp, oldBmp); 12634 SelectObject(hdcBmp, oldBrush); 12635 SelectObject(hdcBmp, oldPen); 12636 DeleteDC(hdcBmp); 12637 12638 bmpWidth = width; 12639 bmpHeight = height; 12640 12641 ReleaseDC(hwnd, hdc); // we keep this in opengl mode since it is a class member now 12642 } 12643 12644 // We want the window's client area to match the image size 12645 RECT rcClient, rcWindow; 12646 POINT ptDiff; 12647 GetClientRect(hwnd, &rcClient); 12648 GetWindowRect(hwnd, &rcWindow); 12649 ptDiff.x = (rcWindow.right - rcWindow.left) - rcClient.right; 12650 ptDiff.y = (rcWindow.bottom - rcWindow.top) - rcClient.bottom; 12651 MoveWindow(hwnd,rcWindow.left, rcWindow.top, width + ptDiff.x, height + ptDiff.y, true); 12652 12653 if ((customizationFlags&WindowFlags.dontAutoShow) == 0) { 12654 ShowWindow(hwnd, SW_SHOWNORMAL); 12655 } else { 12656 _hidden = true; 12657 } 12658 this._visibleForTheFirstTimeCalled = false; // hack! 12659 } 12660 12661 12662 void dispose() { 12663 if(buffer) 12664 DeleteObject(buffer); 12665 } 12666 12667 void closeWindow() { 12668 if(ghRC) { 12669 wglDeleteContext(ghRC); 12670 ghRC = null; 12671 } 12672 DestroyWindow(hwnd); 12673 } 12674 12675 bool setOpacity(ubyte alpha) { 12676 return SetLayeredWindowAttributes(hwnd, 0, alpha, LWA_ALPHA) == TRUE; 12677 } 12678 12679 HANDLE currentCursor; 12680 12681 // returns zero if it recognized the event 12682 static int triggerEvents(HWND hwnd, uint msg, WPARAM wParam, LPARAM lParam, int offsetX, int offsetY, SimpleWindow wind) { 12683 MouseEvent mouse; 12684 12685 void mouseEvent(bool isScreen, ulong mods) { 12686 auto x = LOWORD(lParam); 12687 auto y = HIWORD(lParam); 12688 if(isScreen) { 12689 POINT p; 12690 p.x = x; 12691 p.y = y; 12692 ScreenToClient(hwnd, &p); 12693 x = cast(ushort) p.x; 12694 y = cast(ushort) p.y; 12695 } 12696 12697 if(wind.resizability == Resizability.automaticallyScaleIfPossible) { 12698 x = cast(ushort)( x * wind._virtualWidth / wind._width ); 12699 y = cast(ushort)( y * wind._virtualHeight / wind._height ); 12700 } 12701 12702 mouse.x = x + offsetX; 12703 mouse.y = y + offsetY; 12704 12705 wind.mdx(mouse); 12706 mouse.modifierState = cast(int) mods; 12707 mouse.window = wind; 12708 12709 if(wind.handleMouseEvent) 12710 wind.handleMouseEvent(mouse); 12711 } 12712 12713 switch(msg) { 12714 case WM_GETMINMAXINFO: 12715 MINMAXINFO* mmi = cast(MINMAXINFO*) lParam; 12716 12717 if(wind.minWidth > 0) { 12718 RECT rect; 12719 rect.left = 100; 12720 rect.top = 100; 12721 rect.right = wind.minWidth + 100; 12722 rect.bottom = wind.minHeight + 100; 12723 if(!AdjustWindowRect(&rect, GetWindowLong(wind.hwnd, GWL_STYLE), GetMenu(wind.hwnd) !is null)) 12724 throw new WindowsApiException("AdjustWindowRect", GetLastError()); 12725 12726 mmi.ptMinTrackSize.x = rect.right - rect.left; 12727 mmi.ptMinTrackSize.y = rect.bottom - rect.top; 12728 } 12729 12730 if(wind.maxWidth < int.max) { 12731 RECT rect; 12732 rect.left = 100; 12733 rect.top = 100; 12734 rect.right = wind.maxWidth + 100; 12735 rect.bottom = wind.maxHeight + 100; 12736 if(!AdjustWindowRect(&rect, GetWindowLong(wind.hwnd, GWL_STYLE), GetMenu(wind.hwnd) !is null)) 12737 throw new WindowsApiException("AdjustWindowRect", GetLastError()); 12738 12739 mmi.ptMaxTrackSize.x = rect.right - rect.left; 12740 mmi.ptMaxTrackSize.y = rect.bottom - rect.top; 12741 } 12742 break; 12743 case WM_CHAR: 12744 wchar c = cast(wchar) wParam; 12745 if(wind.handleCharEvent) 12746 wind.handleCharEvent(cast(dchar) c); 12747 break; 12748 case WM_SETFOCUS: 12749 case WM_KILLFOCUS: 12750 wind._focused = (msg == WM_SETFOCUS); 12751 if (msg == WM_SETFOCUS) altPressed = false; //k8: reset alt state on defocus (it is better than nothing...) 12752 if(wind.onFocusChange) 12753 wind.onFocusChange(msg == WM_SETFOCUS); 12754 break; 12755 12756 case WM_SYSKEYDOWN: 12757 goto case; 12758 case WM_SYSKEYUP: 12759 if(lParam & (1 << 29)) { 12760 goto case; 12761 } else { 12762 // no window has keyboard focus 12763 goto default; 12764 } 12765 case WM_KEYDOWN: 12766 case WM_KEYUP: 12767 KeyEvent ev; 12768 ev.key = cast(Key) wParam; 12769 ev.pressed = (msg == WM_KEYDOWN || msg == WM_SYSKEYDOWN); 12770 if (wParam == 0x12) ev.key = Key.Alt; // windows does it this way 12771 12772 ev.hardwareCode = (lParam & 0xff0000) >> 16; 12773 12774 if(GetKeyState(Key.Shift)&0x8000 || GetKeyState(Key.Shift_r)&0x8000) 12775 ev.modifierState |= ModifierState.shift; 12776 //k8: this doesn't work; thanks for nothing, windows 12777 /*if(GetKeyState(Key.Alt)&0x8000 || GetKeyState(Key.Alt_r)&0x8000) 12778 ev.modifierState |= ModifierState.alt;*/ 12779 // this never seems to actually be set 12780 // if (lParam & 0x2000 /* KF_ALTDOWN */) ev.modifierState |= ModifierState.alt; else ev.modifierState &= ~ModifierState.alt; 12781 12782 if (wParam == 0x12) { 12783 altPressed = (msg == WM_SYSKEYDOWN); 12784 } 12785 12786 if(msg == WM_KEYDOWN || msg == WM_KEYUP) { 12787 altPressed = false; 12788 } 12789 // sdpyPrintDebugString(altPressed ? "alt down" : " up "); 12790 12791 if (altPressed) ev.modifierState |= ModifierState.alt; else ev.modifierState &= ~ModifierState.alt; 12792 if(GetKeyState(Key.Ctrl)&0x8000 || GetKeyState(Key.Ctrl_r)&0x8000) 12793 ev.modifierState |= ModifierState.ctrl; 12794 if(GetKeyState(Key.Windows)&0x8000 || GetKeyState(Key.Windows_r)&0x8000) 12795 ev.modifierState |= ModifierState.windows; 12796 if(GetKeyState(Key.NumLock)) 12797 ev.modifierState |= ModifierState.numLock; 12798 if(GetKeyState(Key.CapsLock)) 12799 ev.modifierState |= ModifierState.capsLock; 12800 12801 /+ 12802 // we always want to send the character too, so let's convert it 12803 ubyte[256] state; 12804 wchar[16] buffer; 12805 GetKeyboardState(state.ptr); 12806 ToUnicodeEx(wParam, lParam, state.ptr, buffer.ptr, buffer.length, 0, null); 12807 12808 foreach(dchar d; buffer) { 12809 ev.character = d; 12810 break; 12811 } 12812 +/ 12813 12814 ev.window = wind; 12815 if(wind.handleKeyEvent) 12816 wind.handleKeyEvent(ev); 12817 break; 12818 case 0x020a /*WM_MOUSEWHEEL*/: 12819 // send click 12820 mouse.type = cast(MouseEventType) 1; 12821 mouse.button = ((GET_WHEEL_DELTA_WPARAM(wParam) > 0) ? MouseButton.wheelUp : MouseButton.wheelDown); 12822 mouseEvent(true, LOWORD(wParam)); 12823 12824 // also send release 12825 mouse.type = cast(MouseEventType) 2; 12826 mouse.button = ((GET_WHEEL_DELTA_WPARAM(wParam) > 0) ? MouseButton.wheelUp : MouseButton.wheelDown); 12827 mouseEvent(true, LOWORD(wParam)); 12828 break; 12829 case WM_MOUSEMOVE: 12830 mouse.type = cast(MouseEventType) 0; 12831 mouseEvent(false, wParam); 12832 break; 12833 case WM_LBUTTONDOWN: 12834 case WM_LBUTTONDBLCLK: 12835 mouse.type = cast(MouseEventType) 1; 12836 mouse.button = MouseButton.left; 12837 mouse.doubleClick = msg == WM_LBUTTONDBLCLK; 12838 mouseEvent(false, wParam); 12839 break; 12840 case WM_LBUTTONUP: 12841 mouse.type = cast(MouseEventType) 2; 12842 mouse.button = MouseButton.left; 12843 mouseEvent(false, wParam); 12844 break; 12845 case WM_RBUTTONDOWN: 12846 case WM_RBUTTONDBLCLK: 12847 mouse.type = cast(MouseEventType) 1; 12848 mouse.button = MouseButton.right; 12849 mouse.doubleClick = msg == WM_RBUTTONDBLCLK; 12850 mouseEvent(false, wParam); 12851 break; 12852 case WM_RBUTTONUP: 12853 mouse.type = cast(MouseEventType) 2; 12854 mouse.button = MouseButton.right; 12855 mouseEvent(false, wParam); 12856 break; 12857 case WM_MBUTTONDOWN: 12858 case WM_MBUTTONDBLCLK: 12859 mouse.type = cast(MouseEventType) 1; 12860 mouse.button = MouseButton.middle; 12861 mouse.doubleClick = msg == WM_MBUTTONDBLCLK; 12862 mouseEvent(false, wParam); 12863 break; 12864 case WM_MBUTTONUP: 12865 mouse.type = cast(MouseEventType) 2; 12866 mouse.button = MouseButton.middle; 12867 mouseEvent(false, wParam); 12868 break; 12869 case WM_XBUTTONDOWN: 12870 case WM_XBUTTONDBLCLK: 12871 mouse.type = cast(MouseEventType) 1; 12872 mouse.button = HIWORD(wParam) == 1 ? MouseButton.backButton : MouseButton.forwardButton; 12873 mouse.doubleClick = msg == WM_XBUTTONDBLCLK; 12874 mouseEvent(false, wParam); 12875 return 1; // MSDN says special treatment here, return TRUE to bypass simulation programs 12876 case WM_XBUTTONUP: 12877 mouse.type = cast(MouseEventType) 2; 12878 mouse.button = HIWORD(wParam) == 1 ? MouseButton.backButton : MouseButton.forwardButton; 12879 mouseEvent(false, wParam); 12880 return 1; // see: https://msdn.microsoft.com/en-us/library/windows/desktop/ms646246(v=vs.85).aspx 12881 12882 default: return 1; 12883 } 12884 return 0; 12885 } 12886 12887 HWND hwnd; 12888 private int oldWidth; 12889 private int oldHeight; 12890 private bool inSizeMove; 12891 12892 /++ 12893 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. 12894 12895 History: 12896 Added November 23, 2021 12897 12898 Not fully stable, may be moved out of the impl struct. 12899 12900 Default value changed to `true` on February 15, 2021 12901 +/ 12902 bool doLiveResizing = true; 12903 12904 package int bmpWidth; 12905 package int bmpHeight; 12906 12907 // the extern(Windows) wndproc should just forward to this 12908 LRESULT windowProcedure(HWND hwnd, uint msg, WPARAM wParam, LPARAM lParam) { 12909 try { 12910 assert(hwnd is this.hwnd); 12911 12912 if(triggerEvents(hwnd, msg, wParam, lParam, 0, 0, this)) 12913 switch(msg) { 12914 case WM_MENUCHAR: // menu active but key not associated with a thing. 12915 // you would ideally use this for like a search function but sdpy not that ideally designed. alas. 12916 // The main things we can do are select, execute, close, or ignore 12917 // the default is ignore, but it doesn't *just* ignore it - it also dings an audio alert to 12918 // the user. This can be a bit annoying for sdpy things so instead im overriding and setting it 12919 // to close, which can be really annoying when you hit the wrong button. but meh i think for sdpy 12920 // that's the lesser bad choice rn. Can always override by returning true in triggerEvents.... 12921 12922 // returns the value in the *high order word* of the return value 12923 // hence the << 16 12924 return 1 << 16; // MNC_CLOSE, close the menu without dinging at the user 12925 case WM_SETCURSOR: 12926 if(cast(HWND) wParam !is hwnd) 12927 return 0; // further processing elsewhere 12928 12929 if(LOWORD(lParam) == HTCLIENT && (this.curHidden > 0 || currentCursor !is null)) { 12930 SetCursor(this.curHidden > 0 ? null : currentCursor); 12931 return 1; 12932 } else { 12933 return DefWindowProc(hwnd, msg, wParam, lParam); 12934 } 12935 //break; 12936 12937 case WM_CLOSE: 12938 if (this.closeQuery !is null) this.closeQuery(); else this.close(); 12939 break; 12940 case WM_DESTROY: 12941 if (this.onDestroyed !is null) try { this.onDestroyed(); } catch (Exception e) {} // sorry 12942 SimpleWindow.nativeMapping.remove(hwnd); 12943 CapableOfHandlingNativeEvent.nativeHandleMapping.remove(hwnd); 12944 12945 bool anyImportant = false; 12946 foreach(SimpleWindow w; SimpleWindow.nativeMapping) 12947 if(w.beingOpenKeepsAppOpen) { 12948 anyImportant = true; 12949 break; 12950 } 12951 if(!anyImportant) { 12952 PostQuitMessage(0); 12953 } 12954 break; 12955 case 0x02E0 /*WM_DPICHANGED*/: 12956 this.actualDpi_ = LOWORD(wParam); // hiword is the y param but it is the same per docs 12957 12958 RECT* prcNewWindow = cast(RECT*)lParam; 12959 // docs say this is the recommended position and we should honor it 12960 SetWindowPos(hwnd, 12961 null, 12962 prcNewWindow.left, 12963 prcNewWindow.top, 12964 prcNewWindow.right - prcNewWindow.left, 12965 prcNewWindow.bottom - prcNewWindow.top, 12966 SWP_NOZORDER | SWP_NOACTIVATE); 12967 12968 // doing this because of https://github.com/microsoft/Windows-classic-samples/blob/main/Samples/DPIAwarenessPerWindow/client/DpiAwarenessContext.cpp 12969 // im not sure it is completely correct 12970 // but without it the tabs and such do look weird as things change. 12971 if(SystemParametersInfoForDpi) { 12972 LOGFONT lfText; 12973 SystemParametersInfoForDpi(SPI_GETICONTITLELOGFONT, lfText.sizeof, &lfText, FALSE, this.actualDpi_); 12974 HFONT hFontNew = CreateFontIndirect(&lfText); 12975 if (hFontNew) 12976 { 12977 //DeleteObject(hFontOld); 12978 static extern(Windows) BOOL helper(HWND hWnd, LPARAM lParam) { 12979 SendMessage(hWnd, WM_SETFONT, cast(WPARAM)lParam, MAKELPARAM(TRUE, 0)); 12980 return TRUE; 12981 } 12982 EnumChildWindows(hwnd, &helper, cast(LPARAM) hFontNew); 12983 } 12984 } 12985 12986 if(this.onDpiChanged) 12987 this.onDpiChanged(); 12988 break; 12989 case WM_ENTERIDLE: 12990 // when a menu is up, it stops normal event processing (modal message loop) 12991 // but this at least gives us a chance to SOMETIMES catch up 12992 // FIXME: I can use SetTimer while idle to keep working i think... but idk when i'd destroy it. 12993 SimpleWindow.processAllCustomEvents; 12994 SimpleWindow.processAllCustomEvents; 12995 SleepEx(0, true); 12996 break; 12997 case WM_SIZE: 12998 if(wParam == 1 /* SIZE_MINIMIZED */) 12999 break; 13000 _width = LOWORD(lParam); 13001 _height = HIWORD(lParam); 13002 13003 // I want to avoid tearing in the windows (my code is inefficient 13004 // so this is a hack around that) so while sizing, we don't trigger, 13005 // but we do want to trigger on events like mazimize. 13006 if(!inSizeMove || doLiveResizing) 13007 goto size_changed; 13008 break; 13009 /+ 13010 case WM_SIZING: 13011 writeln("size"); 13012 break; 13013 +/ 13014 // I don't like the tearing I get when redrawing on WM_SIZE 13015 // (I know there's other ways to fix that but I don't like that behavior anyway) 13016 // so instead it is going to redraw only at the end of a size. 13017 case 0x0231: /* WM_ENTERSIZEMOVE */ 13018 inSizeMove = true; 13019 break; 13020 case 0x0232: /* WM_EXITSIZEMOVE */ 13021 inSizeMove = false; 13022 13023 size_changed: 13024 13025 // nothing relevant changed, don't bother redrawing 13026 if(oldWidth == _width && oldHeight == _height) { 13027 if(msg == 0x0232) 13028 goto finalize_resize; 13029 break; 13030 } 13031 13032 // note: OpenGL windows don't use a backing bmp, so no need to change them 13033 // if resizability is anything other than allowResizing, it is meant to either stretch the one image or just do nothing 13034 if(openglMode == OpenGlOptions.no) { // && resizability == Resizability.allowResizing) { 13035 // gotta get the double buffer bmp to match the window 13036 // FIXME: could this be more efficient? it never relinquishes a large bitmap 13037 13038 // if it is auto-scaled, we keep the backing bitmap the same size all the time 13039 if(resizability != Resizability.automaticallyScaleIfPossible) 13040 if(_width > bmpWidth || _height > bmpHeight) { 13041 auto hdc = GetDC(hwnd); 13042 auto oldBuffer = buffer; 13043 buffer = CreateCompatibleBitmap(hdc, _width, _height); 13044 13045 auto hdcBmp = CreateCompatibleDC(hdc); 13046 auto oldBmp = SelectObject(hdcBmp, buffer); 13047 13048 auto hdcOldBmp = CreateCompatibleDC(hdc); 13049 auto oldOldBmp = SelectObject(hdcOldBmp, oldBuffer); 13050 13051 /+ 13052 RECT r; 13053 r.left = 0; 13054 r.top = 0; 13055 r.right = width; 13056 r.bottom = height; 13057 auto c = Color.green; 13058 auto brush = CreateSolidBrush(RGB(c.r, c.g, c.b)); 13059 FillRect(hdcBmp, &r, brush); 13060 DeleteObject(brush); 13061 +/ 13062 13063 BitBlt(hdcBmp, 0, 0, bmpWidth, bmpHeight, hdcOldBmp, 0, 0, SRCCOPY); 13064 13065 bmpWidth = _width; 13066 bmpHeight = _height; 13067 13068 SelectObject(hdcOldBmp, oldOldBmp); 13069 DeleteDC(hdcOldBmp); 13070 13071 SelectObject(hdcBmp, oldBmp); 13072 DeleteDC(hdcBmp); 13073 13074 ReleaseDC(hwnd, hdc); 13075 13076 DeleteObject(oldBuffer); 13077 } 13078 } 13079 13080 updateOpenglViewportIfNeeded(_width, _height); 13081 13082 if(resizability != Resizability.automaticallyScaleIfPossible) 13083 if(windowResized !is null) 13084 windowResized(_width, _height); 13085 13086 /+ 13087 if(inSizeMove) { 13088 // SimpleWindow.processAllCustomEvents(); 13089 // SimpleWindow.processAllCustomEvents(); 13090 13091 //RedrawWindow(hwnd, null, null, RDW_ERASE | RDW_INVALIDATE | RDW_ALLCHILDREN); 13092 //sdpyPrintDebugString("redraw b"); 13093 } else { 13094 +/ { 13095 finalize_resize: 13096 // when it is all done, make sure everything is freshly drawn or there might be 13097 // weird bugs left. 13098 SimpleWindow.processAllCustomEvents(); 13099 SimpleWindow.processAllCustomEvents(); 13100 13101 RedrawWindow(hwnd, null, null, RDW_ERASE | RDW_INVALIDATE | RDW_ALLCHILDREN); 13102 // sdpyPrintDebugString("redraw"); 13103 } 13104 13105 oldWidth = this._width; 13106 oldHeight = this._height; 13107 break; 13108 case WM_ERASEBKGND: 13109 // call `visibleForTheFirstTime` here, so we can do initialization as early as possible 13110 if (!this._visibleForTheFirstTimeCalled) { 13111 this._visibleForTheFirstTimeCalled = true; 13112 if (this.visibleForTheFirstTime !is null) { 13113 this.visibleForTheFirstTime(); 13114 } 13115 } 13116 // block it in OpenGL mode, 'cause no sane person will (or should) draw windows controls over OpenGL scene 13117 version(without_opengl) {} else { 13118 if (openglMode == OpenGlOptions.yes) return 1; 13119 } 13120 // call windows default handler, so it can paint standard controls 13121 goto default; 13122 case WM_CTLCOLORBTN: 13123 case WM_CTLCOLORSTATIC: 13124 SetBkMode(cast(HDC) wParam, TRANSPARENT); 13125 return cast(typeof(return)) //GetStockObject(NULL_BRUSH); 13126 GetSysColorBrush(COLOR_3DFACE); 13127 //break; 13128 case WM_SHOWWINDOW: 13129 this._visible = (wParam != 0); 13130 if (!this._visibleForTheFirstTimeCalled && this._visible) { 13131 this._visibleForTheFirstTimeCalled = true; 13132 if (this.visibleForTheFirstTime !is null) { 13133 this.visibleForTheFirstTime(); 13134 } 13135 } 13136 if (this.visibilityChanged !is null) this.visibilityChanged(this._visible); 13137 break; 13138 case WM_PAINT: { 13139 if (!this._visibleForTheFirstTimeCalled) { 13140 this._visibleForTheFirstTimeCalled = true; 13141 if (this.visibleForTheFirstTime !is null) { 13142 this.visibleForTheFirstTime(); 13143 } 13144 } 13145 13146 BITMAP bm; 13147 PAINTSTRUCT ps; 13148 13149 HDC hdc = BeginPaint(hwnd, &ps); 13150 13151 if(openglMode == OpenGlOptions.no) { 13152 13153 HDC hdcMem = CreateCompatibleDC(hdc); 13154 HBITMAP hbmOld = SelectObject(hdcMem, buffer); 13155 13156 GetObject(buffer, bm.sizeof, &bm); 13157 13158 // FIXME: only BitBlt the invalidated rectangle, not the whole thing 13159 if(resizability == Resizability.automaticallyScaleIfPossible) 13160 StretchBlt(hdc, 0, 0, this._width, this._height, hdcMem, 0, 0, bm.bmWidth, bm.bmHeight, SRCCOPY); 13161 else 13162 BitBlt(hdc, 0, 0, bm.bmWidth, bm.bmHeight, hdcMem, 0, 0, SRCCOPY); 13163 //BitBlt(hdc, ps.rcPaint.left, ps.rcPaint.top, ps.rcPaint.right - ps.rcPaint.left, ps.rcPaint.top - ps.rcPaint.bottom, hdcMem, 0, 0, SRCCOPY); 13164 13165 SelectObject(hdcMem, hbmOld); 13166 DeleteDC(hdcMem); 13167 EndPaint(hwnd, &ps); 13168 } else { 13169 EndPaint(hwnd, &ps); 13170 version(without_opengl) {} else 13171 redrawOpenGlSceneSoon(); 13172 } 13173 } break; 13174 default: 13175 return DefWindowProc(hwnd, msg, wParam, lParam); 13176 } 13177 return 0; 13178 13179 } 13180 catch(Throwable t) { 13181 sdpyPrintDebugString(t.toString); 13182 return 0; 13183 } 13184 } 13185 } 13186 13187 mixin template NativeImageImplementation() { 13188 HBITMAP handle; 13189 ubyte* rawData; 13190 13191 final: 13192 13193 Color getPixel(int x, int y) @system { 13194 auto itemsPerLine = ((cast(int) width * 3 + 3) / 4) * 4; 13195 // remember, bmps are upside down 13196 auto offset = itemsPerLine * (height - y - 1) + x * 3; 13197 13198 Color c; 13199 if(enableAlpha) 13200 c.a = rawData[offset + 3]; 13201 else 13202 c.a = 255; 13203 c.b = rawData[offset + 0]; 13204 c.g = rawData[offset + 1]; 13205 c.r = rawData[offset + 2]; 13206 c.unPremultiply(); 13207 return c; 13208 } 13209 13210 void setPixel(int x, int y, Color c) @system { 13211 auto itemsPerLine = ((cast(int) width * 3 + 3) / 4) * 4; 13212 // remember, bmps are upside down 13213 auto offset = itemsPerLine * (height - y - 1) + x * 3; 13214 13215 if(enableAlpha) 13216 c.premultiply(); 13217 13218 rawData[offset + 0] = c.b; 13219 rawData[offset + 1] = c.g; 13220 rawData[offset + 2] = c.r; 13221 if(enableAlpha) 13222 rawData[offset + 3] = c.a; 13223 } 13224 13225 void convertToRgbaBytes(ubyte[] where) @system { 13226 assert(where.length == this.width * this.height * 4); 13227 13228 auto itemsPerLine = ((cast(int) width * 3 + 3) / 4) * 4; 13229 int idx = 0; 13230 int offset = itemsPerLine * (height - 1); 13231 // remember, bmps are upside down 13232 for(int y = height - 1; y >= 0; y--) { 13233 auto offsetStart = offset; 13234 for(int x = 0; x < width; x++) { 13235 where[idx + 0] = rawData[offset + 2]; // r 13236 where[idx + 1] = rawData[offset + 1]; // g 13237 where[idx + 2] = rawData[offset + 0]; // b 13238 if(enableAlpha) { 13239 where[idx + 3] = rawData[offset + 3]; // a 13240 unPremultiplyRgba(where[idx .. idx + 4]); 13241 offset++; 13242 } else 13243 where[idx + 3] = 255; // a 13244 idx += 4; 13245 offset += 3; 13246 } 13247 13248 offset = offsetStart - itemsPerLine; 13249 } 13250 } 13251 13252 void setFromRgbaBytes(in ubyte[] what) @system { 13253 assert(what.length == this.width * this.height * 4); 13254 13255 auto itemsPerLine = enableAlpha ? (width * 4) : (((cast(int) width * 3 + 3) / 4) * 4); 13256 int idx = 0; 13257 int offset = itemsPerLine * (height - 1); 13258 // remember, bmps are upside down 13259 for(int y = height - 1; y >= 0; y--) { 13260 auto offsetStart = offset; 13261 for(int x = 0; x < width; x++) { 13262 if(enableAlpha) { 13263 auto a = what[idx + 3]; 13264 13265 rawData[offset + 2] = (a * what[idx + 0]) / 255; // r 13266 rawData[offset + 1] = (a * what[idx + 1]) / 255; // g 13267 rawData[offset + 0] = (a * what[idx + 2]) / 255; // b 13268 rawData[offset + 3] = a; // a 13269 //premultiplyBgra(rawData[offset .. offset + 4]); 13270 offset++; 13271 } else { 13272 rawData[offset + 2] = what[idx + 0]; // r 13273 rawData[offset + 1] = what[idx + 1]; // g 13274 rawData[offset + 0] = what[idx + 2]; // b 13275 } 13276 idx += 4; 13277 offset += 3; 13278 } 13279 13280 offset = offsetStart - itemsPerLine; 13281 } 13282 } 13283 13284 13285 void createImage(int width, int height, bool forcexshm=false, bool enableAlpha = false) { 13286 BITMAPINFO infoheader; 13287 infoheader.bmiHeader.biSize = infoheader.bmiHeader.sizeof; 13288 infoheader.bmiHeader.biWidth = width; 13289 infoheader.bmiHeader.biHeight = height; 13290 infoheader.bmiHeader.biPlanes = 1; 13291 infoheader.bmiHeader.biBitCount = enableAlpha ? 32: 24; 13292 infoheader.bmiHeader.biCompression = BI_RGB; 13293 13294 handle = CreateDIBSection( 13295 null, 13296 &infoheader, 13297 DIB_RGB_COLORS, 13298 cast(void**) &rawData, 13299 null, 13300 0); 13301 if(handle is null) 13302 throw new WindowsApiException("create image failed", GetLastError()); 13303 13304 } 13305 13306 void dispose() { 13307 DeleteObject(handle); 13308 } 13309 } 13310 13311 enum KEY_ESCAPE = 27; 13312 } 13313 13314 version(Emscripten) { 13315 alias int delegate(void*) NativeEventHandler; 13316 alias void* NativeWindowHandle; 13317 13318 mixin template NativeSimpleWindowImplementation() { } 13319 mixin template NativeScreenPainterImplementation() { } 13320 mixin template NativeImageImplementation() { } 13321 } 13322 13323 version(X11) { 13324 /// This is the default font used. You might change this before doing anything else with 13325 /// the library if you want to try something else. Surround that in `static if(UsingSimpledisplayX11)` 13326 /// for cross-platform compatibility. 13327 //__gshared string xfontstr = "-*-dejavu sans-medium-r-*-*-12-*-*-*-*-*-*-*"; 13328 //__gshared string xfontstr = "-*-dejavu sans-medium-r-*-*-12-*-*-*-*-*-*-*"; 13329 __gshared string xfontstr = "-*-lucida-medium-r-normal-sans-12-*-*-*-*-*-*-*"; 13330 //__gshared string xfontstr = "-*-fixed-medium-r-*-*-14-*-*-*-*-*-*-*"; 13331 13332 alias int delegate(XEvent) NativeEventHandler; 13333 alias Window NativeWindowHandle; 13334 13335 enum KEY_ESCAPE = 9; 13336 13337 mixin template NativeScreenPainterImplementation() { 13338 Display* display; 13339 Drawable d; 13340 Drawable destiny; 13341 13342 // FIXME: should the gc be static too so it isn't recreated every time draw is called? 13343 GC gc; 13344 13345 __gshared bool fontAttempted; 13346 13347 __gshared XFontStruct* defaultfont; 13348 __gshared XFontSet defaultfontset; 13349 13350 XFontStruct* font; 13351 XFontSet fontset; 13352 13353 void create(PaintingHandle window) { 13354 this.display = XDisplayConnection.get(); 13355 13356 Drawable buffer = None; 13357 if(auto sw = cast(SimpleWindow) this.window) { 13358 buffer = sw.impl.buffer; 13359 this.destiny = cast(Drawable) window; 13360 } else { 13361 buffer = cast(Drawable) window; 13362 this.destiny = None; 13363 } 13364 13365 this.d = cast(Drawable) buffer; 13366 13367 auto dgc = DefaultGC(display, DefaultScreen(display)); 13368 13369 this.gc = XCreateGC(display, d, 0, null); 13370 13371 XCopyGC(display, dgc, 0xffffffff, this.gc); 13372 13373 ensureDefaultFontLoaded(); 13374 13375 font = defaultfont; 13376 fontset = defaultfontset; 13377 13378 if(font) { 13379 XSetFont(display, gc, font.fid); 13380 } 13381 } 13382 13383 static void ensureDefaultFontLoaded() { 13384 if(!fontAttempted) { 13385 auto display = XDisplayConnection.get; 13386 auto font = XLoadQueryFont(display, xfontstr.ptr); 13387 // if the user font choice fails, fixed is pretty reliable (required by X to start!) and not bad either 13388 if(font is null) { 13389 xfontstr = "-*-fixed-medium-r-*-*-13-*-*-*-*-*-*-*"; 13390 font = XLoadQueryFont(display, xfontstr.ptr); 13391 } 13392 13393 char** lol; 13394 int lol2; 13395 char* lol3; 13396 auto fontset = XCreateFontSet(display, xfontstr.ptr, &lol, &lol2, &lol3); 13397 13398 fontAttempted = true; 13399 13400 defaultfont = font; 13401 defaultfontset = fontset; 13402 } 13403 } 13404 13405 arsd.color.Rectangle _clipRectangle; 13406 void setClipRectangle(int x, int y, int width, int height) { 13407 auto old = _clipRectangle; 13408 _clipRectangle = arsd.color.Rectangle(Point(x, y), Size(width, height)); 13409 if(old == _clipRectangle) 13410 return; 13411 13412 if(width == 0 || height == 0) { 13413 XSetClipMask(display, gc, None); 13414 13415 if(xrenderPicturePainter) { 13416 13417 XRectangle[1] rects; 13418 rects[0] = XRectangle(short.min, short.min, short.max, short.max); 13419 XRenderSetPictureClipRectangles(display, xrenderPicturePainter, 0, 0, rects.ptr, cast(int) rects.length); 13420 } 13421 13422 version(with_xft) { 13423 if(xftFont is null || xftDraw is null) 13424 return; 13425 XftDrawSetClip(xftDraw, null); 13426 } 13427 } else { 13428 XRectangle[1] rects; 13429 rects[0] = XRectangle(cast(short)(x), cast(short)(y), cast(short) width, cast(short) height); 13430 XSetClipRectangles(XDisplayConnection.get, gc, 0, 0, rects.ptr, 1, 0); 13431 13432 if(xrenderPicturePainter) 13433 XRenderSetPictureClipRectangles(display, xrenderPicturePainter, 0, 0, rects.ptr, cast(int) rects.length); 13434 13435 version(with_xft) { 13436 if(xftFont is null || xftDraw is null) 13437 return; 13438 XftDrawSetClipRectangles(xftDraw, 0, 0, rects.ptr, 1); 13439 } 13440 } 13441 } 13442 13443 version(with_xft) { 13444 XftFont* xftFont; 13445 XftDraw* xftDraw; 13446 13447 XftColor xftColor; 13448 13449 void updateXftColor() { 13450 if(xftFont is null) 13451 return; 13452 13453 // not bothering with XftColorFree since p sure i don't need it on 24 bit displays.... 13454 XRenderColor colorIn = XRenderColor(_outlineColor.r * 255, _outlineColor.g * 255, _outlineColor.b * 255, _outlineColor.a * 255); 13455 13456 XftColorAllocValue( 13457 display, 13458 DefaultVisual(display, DefaultScreen(display)), 13459 DefaultColormap(display, 0), 13460 &colorIn, 13461 &xftColor 13462 ); 13463 } 13464 } 13465 13466 private OperatingSystemFont _activeFont; 13467 void setFont(OperatingSystemFont font) { 13468 _activeFont = font; 13469 version(with_xft) { 13470 if(font && font.isXft && font.xftFont) 13471 this.xftFont = font.xftFont; 13472 else 13473 this.xftFont = null; 13474 13475 if(this.xftFont) { 13476 if(xftDraw is null) { 13477 xftDraw = XftDrawCreate( 13478 display, 13479 d, 13480 DefaultVisual(display, DefaultScreen(display)), 13481 DefaultColormap(display, 0) 13482 ); 13483 13484 updateXftColor(); 13485 } 13486 13487 return; 13488 } 13489 } 13490 13491 if(font && font.font) { 13492 this.font = font.font; 13493 this.fontset = font.fontset; 13494 XSetFont(display, gc, font.font.fid); 13495 } else { 13496 this.font = defaultfont; 13497 this.fontset = defaultfontset; 13498 } 13499 13500 } 13501 13502 private Picture xrenderPicturePainter; 13503 13504 bool manualInvalidations; 13505 void invalidateRect(Rectangle invalidRect) { 13506 // FIXME if manualInvalidations 13507 } 13508 13509 void dispose() { 13510 this.rasterOp = RasterOp.normal; 13511 13512 if(xrenderPicturePainter) { 13513 XRenderFreePicture(display, xrenderPicturePainter); 13514 xrenderPicturePainter = None; 13515 } 13516 13517 // FIXME: this.window.width/height is probably wrong 13518 13519 // src x,y then dest x, y 13520 if(destiny != None) { 13521 // FIXME: if manual invalidations we can actually only copy some of the area. 13522 // if(manualInvalidations) 13523 XSetClipMask(display, gc, None); 13524 XCopyArea(display, d, destiny, gc, 0, 0, this.window.width, this.window.height, 0, 0); 13525 } 13526 13527 XFreeGC(display, gc); 13528 13529 version(with_xft) 13530 if(xftDraw) { 13531 XftDrawDestroy(xftDraw); 13532 xftDraw = null; 13533 } 13534 13535 /+ 13536 // this should prolly legit never be used since if it destroys the font handle from a OperatingSystemFont, it also ruins a reusable resource. 13537 if(font && font !is defaultfont) { 13538 XFreeFont(display, font); 13539 font = null; 13540 } 13541 if(fontset && fontset !is defaultfontset) { 13542 XFreeFontSet(display, fontset); 13543 fontset = null; 13544 } 13545 +/ 13546 XFlush(display); 13547 13548 if(window.paintingFinishedDg !is null) 13549 window.paintingFinishedDg()(); 13550 } 13551 13552 bool backgroundIsNotTransparent = true; 13553 bool foregroundIsNotTransparent = true; 13554 13555 bool _penInitialized = false; 13556 Pen _activePen; 13557 13558 Color _outlineColor; 13559 Color _fillColor; 13560 13561 @property void pen(Pen p) { 13562 if(_penInitialized && p == _activePen) { 13563 return; 13564 } 13565 _penInitialized = true; 13566 _activePen = p; 13567 _outlineColor = p.color; 13568 13569 int style; 13570 13571 byte dashLength; 13572 13573 final switch(p.style) { 13574 case Pen.Style.Solid: 13575 style = 0 /*LineSolid*/; 13576 break; 13577 case Pen.Style.Dashed: 13578 style = 1 /*LineOnOffDash*/; 13579 dashLength = 4; 13580 break; 13581 case Pen.Style.Dotted: 13582 style = 1 /*LineOnOffDash*/; 13583 dashLength = 1; 13584 break; 13585 } 13586 13587 XSetLineAttributes(display, gc, p.width, style, style == 0 ? 3 : 0, 0); 13588 if(dashLength) 13589 XSetDashes(display, gc, 0, &dashLength, 1); 13590 13591 if(p.color.a == 0) { 13592 foregroundIsNotTransparent = false; 13593 return; 13594 } 13595 13596 foregroundIsNotTransparent = true; 13597 13598 XSetForeground(display, gc, colorToX(p.color, display)); 13599 13600 version(with_xft) 13601 updateXftColor(); 13602 } 13603 13604 RasterOp _currentRasterOp; 13605 bool _currentRasterOpInitialized = false; 13606 @property void rasterOp(RasterOp op) { 13607 if(_currentRasterOpInitialized && _currentRasterOp == op) 13608 return; 13609 _currentRasterOp = op; 13610 _currentRasterOpInitialized = true; 13611 int mode; 13612 final switch(op) { 13613 case RasterOp.normal: 13614 mode = GXcopy; 13615 break; 13616 case RasterOp.xor: 13617 mode = GXxor; 13618 break; 13619 } 13620 XSetFunction(display, gc, mode); 13621 } 13622 13623 13624 bool _fillColorInitialized = false; 13625 13626 @property void fillColor(Color c) { 13627 if(_fillColorInitialized && _fillColor == c) 13628 return; // already good, no need to waste time calling it 13629 _fillColor = c; 13630 _fillColorInitialized = true; 13631 if(c.a == 0) { 13632 backgroundIsNotTransparent = false; 13633 return; 13634 } 13635 13636 backgroundIsNotTransparent = true; 13637 13638 XSetBackground(display, gc, colorToX(c, display)); 13639 13640 } 13641 13642 void swapColors() { 13643 auto tmp = _fillColor; 13644 fillColor = _outlineColor; 13645 auto newPen = _activePen; 13646 newPen.color = tmp; 13647 pen(newPen); 13648 } 13649 13650 uint colorToX(Color c, Display* display) { 13651 auto visual = DefaultVisual(display, DefaultScreen(display)); 13652 import core.bitop; 13653 uint color = 0; 13654 { 13655 auto startBit = bsf(visual.red_mask); 13656 auto lastBit = bsr(visual.red_mask); 13657 auto r = cast(uint) c.r; 13658 r >>= 7 - (lastBit - startBit); 13659 r <<= startBit; 13660 color |= r; 13661 } 13662 { 13663 auto startBit = bsf(visual.green_mask); 13664 auto lastBit = bsr(visual.green_mask); 13665 auto g = cast(uint) c.g; 13666 g >>= 7 - (lastBit - startBit); 13667 g <<= startBit; 13668 color |= g; 13669 } 13670 { 13671 auto startBit = bsf(visual.blue_mask); 13672 auto lastBit = bsr(visual.blue_mask); 13673 auto b = cast(uint) c.b; 13674 b >>= 7 - (lastBit - startBit); 13675 b <<= startBit; 13676 color |= b; 13677 } 13678 13679 13680 13681 return color; 13682 } 13683 13684 void drawImage(int x, int y, Image i, int ix, int iy, int w, int h) { 13685 // source x, source y 13686 if(ix >= i.width) return; 13687 if(iy >= i.height) return; 13688 if(ix + w > i.width) w = i.width - ix; 13689 if(iy + h > i.height) h = i.height - iy; 13690 if(i.usingXshm) 13691 XShmPutImage(display, d, gc, i.handle, ix, iy, x, y, w, h, false); 13692 else 13693 XPutImage(display, d, gc, i.handle, ix, iy, x, y, w, h); 13694 } 13695 13696 void drawPixmap(Sprite s, int x, int y, int ix, int iy, int w, int h) { 13697 if(s.enableAlpha) { 13698 // the Sprite must be created first, meaning if we're here, XRender is already loaded 13699 if(this.xrenderPicturePainter == None) { 13700 XRenderPictureAttributes attrs; 13701 // FIXME: I can prolly reuse this as long as the pixmap itself is valid. 13702 xrenderPicturePainter = XRenderCreatePicture(display, d, Sprite.RGB24, 0, &attrs); 13703 13704 // need to initialize the clip 13705 XRectangle[1] rects; 13706 rects[0] = XRectangle(cast(short)(_clipRectangle.left), cast(short)(_clipRectangle.top), cast(short) _clipRectangle.width, cast(short) _clipRectangle.height); 13707 13708 if(_clipRectangle != Rectangle.init) 13709 XRenderSetPictureClipRectangles(display, xrenderPicturePainter, 0, 0, rects.ptr, cast(int) rects.length); 13710 } 13711 13712 XRenderComposite( 13713 display, 13714 3, // PicOpOver 13715 s.xrenderPicture, 13716 None, 13717 this.xrenderPicturePainter, 13718 ix, 13719 iy, 13720 0, 13721 0, 13722 x, 13723 y, 13724 w ? w : s.width, 13725 h ? h : s.height 13726 ); 13727 } else { 13728 XCopyArea(display, s.handle, d, gc, ix, iy, w ? w : s.width, h ? h : s.height, x, y); 13729 } 13730 } 13731 13732 int fontHeight() { 13733 version(with_xft) 13734 if(xftFont !is null) 13735 return xftFont.height; 13736 if(font) 13737 return font.max_bounds.ascent + font.max_bounds.descent; 13738 return 12; // pretty common default... 13739 } 13740 13741 int textWidth(in char[] line) { 13742 version(with_xft) 13743 if(xftFont) { 13744 if(line.length == 0) 13745 return 0; 13746 XGlyphInfo extents; 13747 XftTextExtentsUtf8(display, xftFont, line.ptr, cast(int) line.length, &extents); 13748 return extents.width; 13749 } 13750 13751 if(fontset) { 13752 if(line.length == 0) 13753 return 0; 13754 XRectangle rect; 13755 Xutf8TextExtents(fontset, line.ptr, cast(int) line.length, null, &rect); 13756 13757 return rect.width; 13758 } 13759 13760 if(font) 13761 // FIXME: unicode 13762 return XTextWidth( font, line.ptr, cast(int) line.length); 13763 else 13764 return fontHeight / 2 * cast(int) line.length; // if no font is loaded, it is prolly Fixed, which is a 2:1 ratio 13765 } 13766 13767 Size textSize(in char[] text) { 13768 auto maxWidth = 0; 13769 auto lineHeight = fontHeight; 13770 int h = text.length ? 0 : lineHeight + 4; // if text is empty, it still gives the line height 13771 foreach(line; text.split('\n')) { 13772 int textWidth = this.textWidth(line); 13773 if(textWidth > maxWidth) 13774 maxWidth = textWidth; 13775 h += lineHeight + 4; 13776 } 13777 return Size(maxWidth, h); 13778 } 13779 13780 void drawText(in int x, in int y, in int x2, in int y2, in char[] originalText, in uint alignment) { 13781 const(char)[] text; 13782 version(with_xft) 13783 if(xftFont) { 13784 text = originalText; 13785 goto loaded; 13786 } 13787 13788 if(fontset) 13789 text = originalText; 13790 else { 13791 text.reserve(originalText.length); 13792 // the first 256 unicode codepoints are the same as ascii and latin-1, which is what X expects, so we can keep all those 13793 // then strip the rest so there isn't garbage 13794 foreach(dchar ch; originalText) 13795 if(ch < 256) 13796 text ~= cast(ubyte) ch; 13797 else 13798 text ~= 191; // FIXME: using a random character (upside down question mark) to fill the space 13799 } 13800 loaded: 13801 if(text.length == 0) 13802 return; 13803 13804 // FIXME: should we clip it to the bounding box? 13805 int textHeight = fontHeight; 13806 13807 auto lines = text.split('\n'); 13808 13809 const lineHeight = textHeight; 13810 textHeight *= lines.length; 13811 13812 int cy = y; 13813 13814 if(alignment & TextAlignment.VerticalBottom) { 13815 if(y2 <= 0) 13816 return; 13817 auto h = y2 - y; 13818 if(h > textHeight) { 13819 cy += h - textHeight; 13820 cy -= lineHeight / 2; 13821 } 13822 } else if(alignment & TextAlignment.VerticalCenter) { 13823 if(y2 <= 0) 13824 return; 13825 auto h = y2 - y; 13826 if(textHeight < h) { 13827 cy += (h - textHeight) / 2; 13828 //cy -= lineHeight / 4; 13829 } 13830 } 13831 13832 foreach(line; text.split('\n')) { 13833 int textWidth = this.textWidth(line); 13834 13835 int px = x, py = cy; 13836 13837 if(alignment & TextAlignment.Center) { 13838 if(x2 <= 0) 13839 return; 13840 auto w = x2 - x; 13841 if(w > textWidth) 13842 px += (w - textWidth) / 2; 13843 } else if(alignment & TextAlignment.Right) { 13844 if(x2 <= 0) 13845 return; 13846 auto pos = x2 - textWidth; 13847 if(pos > x) 13848 px = pos; 13849 } 13850 13851 version(with_xft) 13852 if(xftFont) { 13853 XftDrawStringUtf8(xftDraw, &xftColor, xftFont, px, py + xftFont.ascent, line.ptr, cast(int) line.length); 13854 13855 goto carry_on; 13856 } 13857 13858 if(fontset) 13859 Xutf8DrawString(display, d, fontset, gc, px, py + (font ? font.max_bounds.ascent : lineHeight), line.ptr, cast(int) line.length); 13860 else 13861 XDrawString(display, d, gc, px, py + (font ? font.max_bounds.ascent : lineHeight), line.ptr, cast(int) line.length); 13862 carry_on: 13863 cy += lineHeight + 4; 13864 } 13865 } 13866 13867 void drawPixel(int x, int y) { 13868 XDrawPoint(display, d, gc, x, y); 13869 } 13870 13871 // The basic shapes, outlined 13872 13873 void drawLine(int x1, int y1, int x2, int y2) { 13874 if(foregroundIsNotTransparent) 13875 XDrawLine(display, d, gc, x1, y1, x2, y2); 13876 } 13877 13878 void drawRectangle(int x, int y, int width, int height) { 13879 if(backgroundIsNotTransparent) { 13880 swapColors(); 13881 XFillRectangle(display, d, gc, x+1, y+1, width-2, height-2); // Need to ensure pixels are only drawn once... 13882 swapColors(); 13883 } 13884 // 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 13885 if(foregroundIsNotTransparent) 13886 XDrawRectangle(display, d, gc, x + _activePen.width / 2, y + _activePen.width / 2, width - 1 - _activePen.width / 2, height - 1 - _activePen.width / 2); 13887 } 13888 13889 void drawRectangleRounded(Point upperLeft, Point lowerRight, int borderRadius) { 13890 int[4] radii = borderRadius; 13891 auto r = Rectangle(upperLeft, lowerRight); 13892 13893 if(backgroundIsNotTransparent) { 13894 swapColors(); 13895 // FIXME these overlap and thus draw the pixels multiple times 13896 XFillRectangle(display, d, gc, r.left, r.top + borderRadius/2, r.width, r.height - borderRadius); 13897 XFillRectangle(display, d, gc, r.left + borderRadius/2, r.top, r.width - borderRadius, r.height); 13898 swapColors(); 13899 } 13900 13901 drawLine(r.left + borderRadius / 2, r.top, r.right - borderRadius / 2, r.top); 13902 drawLine(r.left + borderRadius / 2, r.bottom-1, r.right - borderRadius / 2, r.bottom-1); 13903 drawLine(r.left, r.top + borderRadius / 2, r.left, r.bottom - borderRadius / 2); 13904 drawLine(r.right - 1, r.top + borderRadius / 2, r.right - 1, r.bottom - borderRadius / 2); 13905 13906 //drawRectangle(r.left + borderRadius/2, r.top, r.width - borderRadius, r.height); 13907 13908 drawArc(r.upperLeft.x, r.upperLeft.y, radii[0], radii[0], 90*64, 90*64); 13909 drawArc(r.upperRight.x - radii[1], r.upperRight.y, radii[1] - 1, radii[1], 0*64, 90*64); 13910 drawArc(r.lowerLeft.x, r.lowerLeft.y - radii[2], radii[2], radii[2] - 1, 180*64, 90*64); 13911 drawArc(r.lowerRight.x - radii[3], r.lowerRight.y - radii[3], radii[3] - 1, radii[3] - 1, 270*64, 90*64); 13912 } 13913 13914 13915 /// Arguments are the points of the bounding rectangle 13916 void drawEllipse(int x1, int y1, int x2, int y2) { 13917 drawArc(x1, y1, x2 - x1, y2 - y1, 0, 360 * 64); 13918 } 13919 13920 // NOTE: start and finish are in units of degrees * 64 13921 void drawArc(int x1, int y1, int width, int height, int start, int length) { 13922 if(backgroundIsNotTransparent) { 13923 swapColors(); 13924 XFillArc(display, d, gc, x1, y1, width, height, start, length); 13925 swapColors(); 13926 } 13927 if(foregroundIsNotTransparent) { 13928 XDrawArc(display, d, gc, x1, y1, width, height, start, length); 13929 13930 // Windows draws the straight lines on the edges too so FIXME sort of 13931 } 13932 } 13933 13934 void drawPolygon(Point[] vertexes) { 13935 XPoint[16] pointsBuffer; 13936 XPoint[] points; 13937 if(vertexes.length <= pointsBuffer.length) 13938 points = pointsBuffer[0 .. vertexes.length]; 13939 else 13940 points.length = vertexes.length; 13941 13942 foreach(i, p; vertexes) { 13943 points[i].x = cast(short) p.x; 13944 points[i].y = cast(short) p.y; 13945 } 13946 13947 if(backgroundIsNotTransparent) { 13948 swapColors(); 13949 XFillPolygon(display, d, gc, points.ptr, cast(int) points.length, PolygonShape.Complex, CoordMode.CoordModeOrigin); 13950 swapColors(); 13951 } 13952 if(foregroundIsNotTransparent) { 13953 XDrawLines(display, d, gc, points.ptr, cast(int) points.length, CoordMode.CoordModeOrigin); 13954 } 13955 } 13956 } 13957 13958 /* XRender { */ 13959 13960 struct XRenderColor { 13961 ushort red; 13962 ushort green; 13963 ushort blue; 13964 ushort alpha; 13965 } 13966 13967 alias Picture = XID; 13968 alias PictFormat = XID; 13969 13970 struct XGlyphInfo { 13971 ushort width; 13972 ushort height; 13973 short x; 13974 short y; 13975 short xOff; 13976 short yOff; 13977 } 13978 13979 struct XRenderDirectFormat { 13980 short red; 13981 short redMask; 13982 short green; 13983 short greenMask; 13984 short blue; 13985 short blueMask; 13986 short alpha; 13987 short alphaMask; 13988 } 13989 13990 struct XRenderPictFormat { 13991 PictFormat id; 13992 int type; 13993 int depth; 13994 XRenderDirectFormat direct; 13995 Colormap colormap; 13996 } 13997 13998 enum PictFormatID = (1 << 0); 13999 enum PictFormatType = (1 << 1); 14000 enum PictFormatDepth = (1 << 2); 14001 enum PictFormatRed = (1 << 3); 14002 enum PictFormatRedMask =(1 << 4); 14003 enum PictFormatGreen = (1 << 5); 14004 enum PictFormatGreenMask=(1 << 6); 14005 enum PictFormatBlue = (1 << 7); 14006 enum PictFormatBlueMask =(1 << 8); 14007 enum PictFormatAlpha = (1 << 9); 14008 enum PictFormatAlphaMask=(1 << 10); 14009 enum PictFormatColormap =(1 << 11); 14010 14011 struct XRenderPictureAttributes { 14012 int repeat; 14013 Picture alpha_map; 14014 int alpha_x_origin; 14015 int alpha_y_origin; 14016 int clip_x_origin; 14017 int clip_y_origin; 14018 Pixmap clip_mask; 14019 Bool graphics_exposures; 14020 int subwindow_mode; 14021 int poly_edge; 14022 int poly_mode; 14023 Atom dither; 14024 Bool component_alpha; 14025 } 14026 14027 alias int XFixed; 14028 14029 struct XPointFixed { 14030 XFixed x, y; 14031 } 14032 14033 struct XCircle { 14034 XFixed x; 14035 XFixed y; 14036 XFixed radius; 14037 } 14038 14039 struct XTransform { 14040 XFixed[3][3] matrix; 14041 } 14042 14043 struct XFilters { 14044 int nfilter; 14045 char **filter; 14046 int nalias; 14047 short *alias_; 14048 } 14049 14050 struct XIndexValue { 14051 c_ulong pixel; 14052 ushort red, green, blue, alpha; 14053 } 14054 14055 struct XAnimCursor { 14056 Cursor cursor; 14057 c_ulong delay; 14058 } 14059 14060 struct XLinearGradient { 14061 XPointFixed p1; 14062 XPointFixed p2; 14063 } 14064 14065 struct XRadialGradient { 14066 XCircle inner; 14067 XCircle outer; 14068 } 14069 14070 struct XConicalGradient { 14071 XPointFixed center; 14072 XFixed angle; /* in degrees */ 14073 } 14074 14075 enum PictStandardARGB32 = 0; 14076 enum PictStandardRGB24 = 1; 14077 enum PictStandardA8 = 2; 14078 enum PictStandardA4 = 3; 14079 enum PictStandardA1 = 4; 14080 enum PictStandardNUM = 5; 14081 14082 interface XRender { 14083 extern(C) @nogc: 14084 14085 Bool XRenderQueryExtension (Display *dpy, int *event_basep, int *error_basep); 14086 14087 Status XRenderQueryVersion (Display *dpy, 14088 int *major_versionp, 14089 int *minor_versionp); 14090 14091 Status XRenderQueryFormats (Display *dpy); 14092 14093 int XRenderQuerySubpixelOrder (Display *dpy, int screen); 14094 14095 Bool XRenderSetSubpixelOrder (Display *dpy, int screen, int subpixel); 14096 14097 XRenderPictFormat * 14098 XRenderFindVisualFormat (Display *dpy, const Visual *visual); 14099 14100 XRenderPictFormat * 14101 XRenderFindFormat (Display *dpy, 14102 c_ulong mask, 14103 const XRenderPictFormat *templ, 14104 int count); 14105 XRenderPictFormat * 14106 XRenderFindStandardFormat (Display *dpy, 14107 int format); 14108 14109 XIndexValue * 14110 XRenderQueryPictIndexValues(Display *dpy, 14111 const XRenderPictFormat *format, 14112 int *num); 14113 14114 Picture XRenderCreatePicture( 14115 Display *dpy, 14116 Drawable drawable, 14117 const XRenderPictFormat *format, 14118 c_ulong valuemask, 14119 const XRenderPictureAttributes *attributes); 14120 14121 void XRenderChangePicture (Display *dpy, 14122 Picture picture, 14123 c_ulong valuemask, 14124 const XRenderPictureAttributes *attributes); 14125 14126 void 14127 XRenderSetPictureClipRectangles (Display *dpy, 14128 Picture picture, 14129 int xOrigin, 14130 int yOrigin, 14131 const XRectangle *rects, 14132 int n); 14133 14134 void 14135 XRenderSetPictureClipRegion (Display *dpy, 14136 Picture picture, 14137 Region r); 14138 14139 void 14140 XRenderSetPictureTransform (Display *dpy, 14141 Picture picture, 14142 XTransform *transform); 14143 14144 void 14145 XRenderFreePicture (Display *dpy, 14146 Picture picture); 14147 14148 void 14149 XRenderComposite (Display *dpy, 14150 int op, 14151 Picture src, 14152 Picture mask, 14153 Picture dst, 14154 int src_x, 14155 int src_y, 14156 int mask_x, 14157 int mask_y, 14158 int dst_x, 14159 int dst_y, 14160 uint width, 14161 uint height); 14162 14163 14164 Picture XRenderCreateSolidFill (Display *dpy, 14165 const XRenderColor *color); 14166 14167 Picture XRenderCreateLinearGradient (Display *dpy, 14168 const XLinearGradient *gradient, 14169 const XFixed *stops, 14170 const XRenderColor *colors, 14171 int nstops); 14172 14173 Picture XRenderCreateRadialGradient (Display *dpy, 14174 const XRadialGradient *gradient, 14175 const XFixed *stops, 14176 const XRenderColor *colors, 14177 int nstops); 14178 14179 Picture XRenderCreateConicalGradient (Display *dpy, 14180 const XConicalGradient *gradient, 14181 const XFixed *stops, 14182 const XRenderColor *colors, 14183 int nstops); 14184 14185 14186 14187 Cursor 14188 XRenderCreateCursor (Display *dpy, 14189 Picture source, 14190 uint x, 14191 uint y); 14192 14193 XFilters * 14194 XRenderQueryFilters (Display *dpy, Drawable drawable); 14195 14196 void 14197 XRenderSetPictureFilter (Display *dpy, 14198 Picture picture, 14199 const char *filter, 14200 XFixed *params, 14201 int nparams); 14202 14203 Cursor 14204 XRenderCreateAnimCursor (Display *dpy, 14205 int ncursor, 14206 XAnimCursor *cursors); 14207 } 14208 14209 __gshared bool XRenderLibrarySuccessfullyLoaded = true; 14210 mixin DynamicLoad!(XRender, "Xrender", 1, XRenderLibrarySuccessfullyLoaded) XRenderLibrary; 14211 14212 /* XRender } */ 14213 14214 /* Xrandr { */ 14215 14216 struct XRRMonitorInfo { 14217 Atom name; 14218 Bool primary; 14219 Bool automatic; 14220 int noutput; 14221 int x; 14222 int y; 14223 int width; 14224 int height; 14225 int mwidth; 14226 int mheight; 14227 /*RROutput*/ void *outputs; 14228 } 14229 14230 struct XRRScreenChangeNotifyEvent { 14231 int type; /* event base */ 14232 c_ulong serial; /* # of last request processed by server */ 14233 Bool send_event; /* true if this came from a SendEvent request */ 14234 Display *display; /* Display the event was read from */ 14235 Window window; /* window which selected for this event */ 14236 Window root; /* Root window for changed screen */ 14237 Time timestamp; /* when the screen change occurred */ 14238 Time config_timestamp; /* when the last configuration change */ 14239 ushort/*SizeID*/ size_index; 14240 ushort/*SubpixelOrder*/ subpixel_order; 14241 ushort/*Rotation*/ rotation; 14242 int width; 14243 int height; 14244 int mwidth; 14245 int mheight; 14246 } 14247 14248 enum RRScreenChangeNotify = 0; 14249 14250 enum RRScreenChangeNotifyMask = 1; 14251 14252 __gshared int xrrEventBase = -1; 14253 14254 14255 interface XRandr { 14256 extern(C) @nogc: 14257 Bool XRRQueryExtension (Display *dpy, int *event_base_return, int *error_base_return); 14258 Status XRRQueryVersion (Display *dpy, int *major_version_return, int *minor_version_return); 14259 14260 XRRMonitorInfo * XRRGetMonitors(Display *dpy, Window window, Bool get_active, int *nmonitors); 14261 void XRRFreeMonitors(XRRMonitorInfo *monitors); 14262 14263 void XRRSelectInput(Display *dpy, Window window, int mask); 14264 } 14265 14266 __gshared bool XRandrLibrarySuccessfullyLoaded = true; 14267 mixin DynamicLoad!(XRandr, "Xrandr", 2, XRandrLibrarySuccessfullyLoaded) XRandrLibrary; 14268 /* Xrandr } */ 14269 14270 /* Xft { */ 14271 14272 // actually freetype 14273 alias void FT_Face; 14274 14275 // actually fontconfig 14276 private alias FcBool = int; 14277 alias void FcCharSet; 14278 alias void FcPattern; 14279 alias void FcResult; 14280 enum FcEndian { FcEndianBig, FcEndianLittle } 14281 struct FcFontSet { 14282 int nfont; 14283 int sfont; 14284 FcPattern** fonts; 14285 } 14286 14287 // actually XRegion 14288 struct BOX { 14289 short x1, x2, y1, y2; 14290 } 14291 struct _XRegion { 14292 c_long size; 14293 c_long numRects; 14294 BOX* rects; 14295 BOX extents; 14296 } 14297 14298 alias Region = _XRegion*; 14299 14300 // ok actually Xft 14301 14302 struct XftFontInfo; 14303 14304 struct XftFont { 14305 int ascent; 14306 int descent; 14307 int height; 14308 int max_advance_width; 14309 FcCharSet* charset; 14310 FcPattern* pattern; 14311 } 14312 14313 struct XftDraw; 14314 14315 struct XftColor { 14316 c_ulong pixel; 14317 XRenderColor color; 14318 } 14319 14320 struct XftCharSpec { 14321 dchar ucs4; 14322 short x; 14323 short y; 14324 } 14325 14326 struct XftCharFontSpec { 14327 XftFont *font; 14328 dchar ucs4; 14329 short x; 14330 short y; 14331 } 14332 14333 struct XftGlyphSpec { 14334 uint glyph; 14335 short x; 14336 short y; 14337 } 14338 14339 struct XftGlyphFontSpec { 14340 XftFont *font; 14341 uint glyph; 14342 short x; 14343 short y; 14344 } 14345 14346 interface Xft { 14347 extern(C) @nogc pure: 14348 14349 Bool XftColorAllocName (Display *dpy, 14350 const Visual *visual, 14351 Colormap cmap, 14352 const char *name, 14353 XftColor *result); 14354 14355 Bool XftColorAllocValue (Display *dpy, 14356 Visual *visual, 14357 Colormap cmap, 14358 const XRenderColor *color, 14359 XftColor *result); 14360 14361 void XftColorFree (Display *dpy, 14362 Visual *visual, 14363 Colormap cmap, 14364 XftColor *color); 14365 14366 Bool XftDefaultHasRender (Display *dpy); 14367 14368 Bool XftDefaultSet (Display *dpy, FcPattern *defaults); 14369 14370 void XftDefaultSubstitute (Display *dpy, int screen, FcPattern *pattern); 14371 14372 XftDraw * XftDrawCreate (Display *dpy, 14373 Drawable drawable, 14374 Visual *visual, 14375 Colormap colormap); 14376 14377 XftDraw * XftDrawCreateBitmap (Display *dpy, 14378 Pixmap bitmap); 14379 14380 XftDraw * XftDrawCreateAlpha (Display *dpy, 14381 Pixmap pixmap, 14382 int depth); 14383 14384 void XftDrawChange (XftDraw *draw, 14385 Drawable drawable); 14386 14387 Display * XftDrawDisplay (XftDraw *draw); 14388 14389 Drawable XftDrawDrawable (XftDraw *draw); 14390 14391 Colormap XftDrawColormap (XftDraw *draw); 14392 14393 Visual * XftDrawVisual (XftDraw *draw); 14394 14395 void XftDrawDestroy (XftDraw *draw); 14396 14397 Picture XftDrawPicture (XftDraw *draw); 14398 14399 Picture XftDrawSrcPicture (XftDraw *draw, const XftColor *color); 14400 14401 void XftDrawGlyphs (XftDraw *draw, 14402 const XftColor *color, 14403 XftFont *pub, 14404 int x, 14405 int y, 14406 const uint *glyphs, 14407 int nglyphs); 14408 14409 void XftDrawString8 (XftDraw *draw, 14410 const XftColor *color, 14411 XftFont *pub, 14412 int x, 14413 int y, 14414 const char *string, 14415 int len); 14416 14417 void XftDrawString16 (XftDraw *draw, 14418 const XftColor *color, 14419 XftFont *pub, 14420 int x, 14421 int y, 14422 const wchar *string, 14423 int len); 14424 14425 void XftDrawString32 (XftDraw *draw, 14426 const XftColor *color, 14427 XftFont *pub, 14428 int x, 14429 int y, 14430 const dchar *string, 14431 int len); 14432 14433 void XftDrawStringUtf8 (XftDraw *draw, 14434 const XftColor *color, 14435 XftFont *pub, 14436 int x, 14437 int y, 14438 const char *string, 14439 int len); 14440 void XftDrawStringUtf16 (XftDraw *draw, 14441 const XftColor *color, 14442 XftFont *pub, 14443 int x, 14444 int y, 14445 const char *string, 14446 FcEndian endian, 14447 int len); 14448 14449 void XftDrawCharSpec (XftDraw *draw, 14450 const XftColor *color, 14451 XftFont *pub, 14452 const XftCharSpec *chars, 14453 int len); 14454 14455 void XftDrawCharFontSpec (XftDraw *draw, 14456 const XftColor *color, 14457 const XftCharFontSpec *chars, 14458 int len); 14459 14460 void XftDrawGlyphSpec (XftDraw *draw, 14461 const XftColor *color, 14462 XftFont *pub, 14463 const XftGlyphSpec *glyphs, 14464 int len); 14465 14466 void XftDrawGlyphFontSpec (XftDraw *draw, 14467 const XftColor *color, 14468 const XftGlyphFontSpec *glyphs, 14469 int len); 14470 14471 void XftDrawRect (XftDraw *draw, 14472 const XftColor *color, 14473 int x, 14474 int y, 14475 uint width, 14476 uint height); 14477 14478 Bool XftDrawSetClip (XftDraw *draw, 14479 Region r); 14480 14481 14482 Bool XftDrawSetClipRectangles (XftDraw *draw, 14483 int xOrigin, 14484 int yOrigin, 14485 const XRectangle *rects, 14486 int n); 14487 14488 void XftDrawSetSubwindowMode (XftDraw *draw, 14489 int mode); 14490 14491 void XftGlyphExtents (Display *dpy, 14492 XftFont *pub, 14493 const uint *glyphs, 14494 int nglyphs, 14495 XGlyphInfo *extents); 14496 14497 void XftTextExtents8 (Display *dpy, 14498 XftFont *pub, 14499 const char *string, 14500 int len, 14501 XGlyphInfo *extents); 14502 14503 void XftTextExtents16 (Display *dpy, 14504 XftFont *pub, 14505 const wchar *string, 14506 int len, 14507 XGlyphInfo *extents); 14508 14509 void XftTextExtents32 (Display *dpy, 14510 XftFont *pub, 14511 const dchar *string, 14512 int len, 14513 XGlyphInfo *extents); 14514 14515 void XftTextExtentsUtf8 (Display *dpy, 14516 XftFont *pub, 14517 const char *string, 14518 int len, 14519 XGlyphInfo *extents); 14520 14521 void XftTextExtentsUtf16 (Display *dpy, 14522 XftFont *pub, 14523 const char *string, 14524 FcEndian endian, 14525 int len, 14526 XGlyphInfo *extents); 14527 14528 FcPattern * XftFontMatch (Display *dpy, 14529 int screen, 14530 const FcPattern *pattern, 14531 FcResult *result); 14532 14533 XftFont * XftFontOpen (Display *dpy, int screen, ...); 14534 14535 XftFont * XftFontOpenName (Display *dpy, int screen, const char *name); 14536 14537 XftFont * XftFontOpenXlfd (Display *dpy, int screen, const char *xlfd); 14538 14539 FT_Face XftLockFace (XftFont *pub); 14540 14541 void XftUnlockFace (XftFont *pub); 14542 14543 XftFontInfo * XftFontInfoCreate (Display *dpy, const FcPattern *pattern); 14544 14545 void XftFontInfoDestroy (Display *dpy, XftFontInfo *fi); 14546 14547 dchar XftFontInfoHash (const XftFontInfo *fi); 14548 14549 FcBool XftFontInfoEqual (const XftFontInfo *a, const XftFontInfo *b); 14550 14551 XftFont * XftFontOpenInfo (Display *dpy, 14552 FcPattern *pattern, 14553 XftFontInfo *fi); 14554 14555 XftFont * XftFontOpenPattern (Display *dpy, FcPattern *pattern); 14556 14557 XftFont * XftFontCopy (Display *dpy, XftFont *pub); 14558 14559 void XftFontClose (Display *dpy, XftFont *pub); 14560 14561 FcBool XftInitFtLibrary(); 14562 void XftFontLoadGlyphs (Display *dpy, 14563 XftFont *pub, 14564 FcBool need_bitmaps, 14565 const uint *glyphs, 14566 int nglyph); 14567 14568 void XftFontUnloadGlyphs (Display *dpy, 14569 XftFont *pub, 14570 const uint *glyphs, 14571 int nglyph); 14572 14573 FcBool XftFontCheckGlyph (Display *dpy, 14574 XftFont *pub, 14575 FcBool need_bitmaps, 14576 uint glyph, 14577 uint *missing, 14578 int *nmissing); 14579 14580 FcBool XftCharExists (Display *dpy, 14581 XftFont *pub, 14582 dchar ucs4); 14583 14584 uint XftCharIndex (Display *dpy, 14585 XftFont *pub, 14586 dchar ucs4); 14587 FcBool XftInit (const char *config); 14588 14589 int XftGetVersion (); 14590 14591 FcFontSet * XftListFonts (Display *dpy, 14592 int screen, 14593 ...); 14594 14595 FcPattern *XftNameParse (const char *name); 14596 14597 void XftGlyphRender (Display *dpy, 14598 int op, 14599 Picture src, 14600 XftFont *pub, 14601 Picture dst, 14602 int srcx, 14603 int srcy, 14604 int x, 14605 int y, 14606 const uint *glyphs, 14607 int nglyphs); 14608 14609 void XftGlyphSpecRender (Display *dpy, 14610 int op, 14611 Picture src, 14612 XftFont *pub, 14613 Picture dst, 14614 int srcx, 14615 int srcy, 14616 const XftGlyphSpec *glyphs, 14617 int nglyphs); 14618 14619 void XftCharSpecRender (Display *dpy, 14620 int op, 14621 Picture src, 14622 XftFont *pub, 14623 Picture dst, 14624 int srcx, 14625 int srcy, 14626 const XftCharSpec *chars, 14627 int len); 14628 void XftGlyphFontSpecRender (Display *dpy, 14629 int op, 14630 Picture src, 14631 Picture dst, 14632 int srcx, 14633 int srcy, 14634 const XftGlyphFontSpec *glyphs, 14635 int nglyphs); 14636 14637 void XftCharFontSpecRender (Display *dpy, 14638 int op, 14639 Picture src, 14640 Picture dst, 14641 int srcx, 14642 int srcy, 14643 const XftCharFontSpec *chars, 14644 int len); 14645 14646 void XftTextRender8 (Display *dpy, 14647 int op, 14648 Picture src, 14649 XftFont *pub, 14650 Picture dst, 14651 int srcx, 14652 int srcy, 14653 int x, 14654 int y, 14655 const char *string, 14656 int len); 14657 void XftTextRender16 (Display *dpy, 14658 int op, 14659 Picture src, 14660 XftFont *pub, 14661 Picture dst, 14662 int srcx, 14663 int srcy, 14664 int x, 14665 int y, 14666 const wchar *string, 14667 int len); 14668 14669 void XftTextRender16BE (Display *dpy, 14670 int op, 14671 Picture src, 14672 XftFont *pub, 14673 Picture dst, 14674 int srcx, 14675 int srcy, 14676 int x, 14677 int y, 14678 const char *string, 14679 int len); 14680 14681 void XftTextRender16LE (Display *dpy, 14682 int op, 14683 Picture src, 14684 XftFont *pub, 14685 Picture dst, 14686 int srcx, 14687 int srcy, 14688 int x, 14689 int y, 14690 const char *string, 14691 int len); 14692 14693 void XftTextRender32 (Display *dpy, 14694 int op, 14695 Picture src, 14696 XftFont *pub, 14697 Picture dst, 14698 int srcx, 14699 int srcy, 14700 int x, 14701 int y, 14702 const dchar *string, 14703 int len); 14704 14705 void XftTextRender32BE (Display *dpy, 14706 int op, 14707 Picture src, 14708 XftFont *pub, 14709 Picture dst, 14710 int srcx, 14711 int srcy, 14712 int x, 14713 int y, 14714 const char *string, 14715 int len); 14716 14717 void XftTextRender32LE (Display *dpy, 14718 int op, 14719 Picture src, 14720 XftFont *pub, 14721 Picture dst, 14722 int srcx, 14723 int srcy, 14724 int x, 14725 int y, 14726 const char *string, 14727 int len); 14728 14729 void XftTextRenderUtf8 (Display *dpy, 14730 int op, 14731 Picture src, 14732 XftFont *pub, 14733 Picture dst, 14734 int srcx, 14735 int srcy, 14736 int x, 14737 int y, 14738 const char *string, 14739 int len); 14740 14741 void XftTextRenderUtf16 (Display *dpy, 14742 int op, 14743 Picture src, 14744 XftFont *pub, 14745 Picture dst, 14746 int srcx, 14747 int srcy, 14748 int x, 14749 int y, 14750 const char *string, 14751 FcEndian endian, 14752 int len); 14753 FcPattern * XftXlfdParse (const char *xlfd_orig, Bool ignore_scalable, Bool complete); 14754 14755 } 14756 14757 interface FontConfig { 14758 extern(C) @nogc pure: 14759 int FcPatternGetString(const FcPattern *p, const char *object, int n, char ** s); 14760 void FcFontSetDestroy(FcFontSet*); 14761 char* FcNameUnparse(const FcPattern *); 14762 } 14763 14764 mixin DynamicLoad!(Xft, "Xft", 2, librariesSuccessfullyLoaded) XftLibrary; 14765 mixin DynamicLoad!(FontConfig, "fontconfig", 1, librariesSuccessfullyLoaded) FontConfigLibrary; 14766 14767 14768 /* Xft } */ 14769 14770 class XDisconnectException : Exception { 14771 bool userRequested; 14772 this(bool userRequested = true) { 14773 this.userRequested = userRequested; 14774 super("X disconnected"); 14775 } 14776 } 14777 14778 /++ 14779 Platform-specific for X11. Traps errors for the duration of `dg`. Avoid calling this from inside a call to this. 14780 14781 Please note that it returns 14782 +/ 14783 XErrorEvent[] trapXErrors(scope void delegate() dg) { 14784 14785 static XErrorEvent[] errorBuffer; 14786 14787 static extern(C) int handler (Display* dpy, XErrorEvent* evt) nothrow { 14788 errorBuffer ~= *evt; 14789 return 0; 14790 } 14791 14792 auto savedErrorHandler = XSetErrorHandler(&handler); 14793 14794 try { 14795 dg(); 14796 } finally { 14797 XSync(XDisplayConnection.get, 0/*False*/); 14798 XSetErrorHandler(savedErrorHandler); 14799 } 14800 14801 auto bfr = errorBuffer; 14802 errorBuffer = null; 14803 14804 return bfr; 14805 } 14806 14807 /// Platform-specific for X11. A singleton class (well, all its methods are actually static... so more like a namespace) wrapping a `Display*`. 14808 class XDisplayConnection { 14809 private __gshared Display* display; 14810 private __gshared XIM xim; 14811 private __gshared char* displayName; 14812 14813 private __gshared int connectionSequence_; 14814 private __gshared bool isLocal_; 14815 14816 /// use this for lazy caching when reconnection 14817 static int connectionSequenceNumber() { return connectionSequence_; } 14818 14819 /++ 14820 Guesses if the connection appears to be local. 14821 14822 History: 14823 Added June 3, 2021 14824 +/ 14825 static @property bool isLocal() nothrow @trusted @nogc { 14826 return isLocal_; 14827 } 14828 14829 /// Attempts recreation of state, may require application assistance 14830 /// You MUST call this OUTSIDE the event loop. Let the exception kill the loop, 14831 /// then call this, and if successful, reenter the loop. 14832 static void discardAndRecreate(string newDisplayString = null) { 14833 if(insideXEventLoop) 14834 throw new Error("You MUST call discardAndRecreate from OUTSIDE the event loop"); 14835 14836 // 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 14837 auto chnenhm = CapableOfHandlingNativeEvent.nativeHandleMapping.dup; 14838 14839 foreach(handle; chnenhm) { 14840 handle.discardConnectionState(); 14841 } 14842 14843 discardState(); 14844 14845 if(newDisplayString !is null) 14846 setDisplayName(newDisplayString); 14847 14848 auto display = get(); 14849 14850 foreach(handle; chnenhm) { 14851 handle.recreateAfterDisconnect(); 14852 } 14853 } 14854 14855 private __gshared EventMask rootEventMask; 14856 14857 /++ 14858 Requests the specified input from the root window on the connection, in addition to any other request. 14859 14860 14861 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. 14862 14863 $(WARNING it calls XSelectInput itself, which will override any other root window input you have!) 14864 +/ 14865 static void addRootInput(EventMask mask) { 14866 auto old = rootEventMask; 14867 rootEventMask |= mask; 14868 get(); // to ensure display connected 14869 if(display !is null && rootEventMask != old) 14870 XSelectInput(display, RootWindow(display, DefaultScreen(display)), rootEventMask); 14871 } 14872 14873 static void discardState() { 14874 freeImages(); 14875 14876 foreach(atomPtr; interredAtoms) 14877 *atomPtr = 0; 14878 interredAtoms = null; 14879 interredAtoms.assumeSafeAppend(); 14880 14881 ScreenPainterImplementation.fontAttempted = false; 14882 ScreenPainterImplementation.defaultfont = null; 14883 ScreenPainterImplementation.defaultfontset = null; 14884 14885 Image.impl.xshmQueryCompleted = false; 14886 Image.impl._xshmAvailable = false; 14887 14888 SimpleWindow.nativeMapping = null; 14889 CapableOfHandlingNativeEvent.nativeHandleMapping = null; 14890 // GlobalHotkeyManager 14891 14892 display = null; 14893 xim = null; 14894 } 14895 14896 // Do you want to know why do we need all this horrible-looking code? See comment at the bottom. 14897 private static void createXIM () { 14898 import core.stdc.locale : setlocale, LC_ALL; 14899 import core.stdc.stdio : stderr, fprintf; 14900 import core.stdc.stdlib : free; 14901 import core.stdc.string : strdup; 14902 14903 static immutable string[3] mtry = [ "", "@im=local", "@im=" ]; 14904 14905 auto olocale = strdup(setlocale(LC_ALL, null)); 14906 setlocale(LC_ALL, (sdx_isUTF8Locale ? "" : "en_US.UTF-8")); 14907 scope(exit) { setlocale(LC_ALL, olocale); free(olocale); } 14908 14909 //fprintf(stderr, "opening IM...\n"); 14910 foreach (string s; mtry) { 14911 XSetLocaleModifiers(s.ptr); // it's safe, as `s` is string literal 14912 if ((xim = XOpenIM(display, null, null, null)) !is null) return; 14913 } 14914 fprintf(stderr, "createXIM: XOpenIM failed!\n"); 14915 } 14916 14917 // for X11 we will keep all XShm-allocated images in this list, so we can free 'em on connection closing. 14918 // we'll use glibc malloc()/free(), 'cause `unregisterImage()` can be called from object dtor. 14919 static struct ImgList { 14920 size_t img; // class; hide it from GC 14921 ImgList* next; 14922 } 14923 14924 static __gshared ImgList* imglist = null; 14925 static __gshared bool imglistLocked = false; // true: don't register and unregister images 14926 14927 static void registerImage (Image img) { 14928 if (!imglistLocked && img !is null) { 14929 import core.stdc.stdlib : malloc; 14930 auto it = cast(ImgList*)malloc(ImgList.sizeof); 14931 assert(it !is null); // do proper checks 14932 it.img = cast(size_t)cast(void*)img; 14933 it.next = imglist; 14934 imglist = it; 14935 version(sdpy_debug_xshm) { import core.stdc.stdio : printf; printf("registering image %p\n", cast(void*)img); } 14936 } 14937 } 14938 14939 static void unregisterImage (Image img) { 14940 if (!imglistLocked && img !is null) { 14941 import core.stdc.stdlib : free; 14942 ImgList* prev = null; 14943 ImgList* cur = imglist; 14944 while (cur !is null) { 14945 if (cur.img == cast(size_t)cast(void*)img) break; // i found her! 14946 prev = cur; 14947 cur = cur.next; 14948 } 14949 if (cur !is null) { 14950 if (prev is null) imglist = cur.next; else prev.next = cur.next; 14951 free(cur); 14952 version(sdpy_debug_xshm) { import core.stdc.stdio : printf; printf("unregistering image %p\n", cast(void*)img); } 14953 } else { 14954 version(sdpy_debug_xshm) { import core.stdc.stdio : printf; printf("trying to unregister unknown image %p\n", cast(void*)img); } 14955 } 14956 } 14957 } 14958 14959 static void freeImages () { // needed for discardAndRecreate 14960 imglistLocked = true; 14961 scope(exit) imglistLocked = false; 14962 ImgList* cur = imglist; 14963 ImgList* next = null; 14964 while (cur !is null) { 14965 import core.stdc.stdlib : free; 14966 next = cur.next; 14967 version(sdpy_debug_xshm) { import core.stdc.stdio : printf; printf("disposing image %p\n", cast(void*)cur.img); } 14968 (cast(Image)cast(void*)cur.img).dispose(); 14969 free(cur); 14970 cur = next; 14971 } 14972 imglist = null; 14973 } 14974 14975 /// can be used to override normal handling of display name 14976 /// from environment and/or command line 14977 static setDisplayName(string newDisplayName) { 14978 displayName = cast(char*) (newDisplayName ~ '\0'); 14979 } 14980 14981 /// resets to the default display string 14982 static resetDisplayName() { 14983 displayName = null; 14984 } 14985 14986 /// 14987 static Display* get() { 14988 if(display is null) { 14989 if(!librariesSuccessfullyLoaded) 14990 throw new Exception("Unable to load X11 client libraries"); 14991 display = XOpenDisplay(displayName); 14992 14993 isLocal_ = false; 14994 14995 connectionSequence_++; 14996 if(display is null) 14997 throw new Exception("Unable to open X display"); 14998 14999 auto str = display.display_name; 15000 // this is a bit of a hack but like if it looks like a unix socket we assume it is local 15001 // and otherwise it probably isn't 15002 if(str is null || (str[0] != ':' && str[0] != '/')) 15003 isLocal_ = false; 15004 else 15005 isLocal_ = true; 15006 15007 XSetErrorHandler(&adrlogger); 15008 15009 debug(sdpy_x_errors) { 15010 XSynchronize(display, true); 15011 15012 extern(C) int wtf() { 15013 if(errorHappened) { 15014 asm { int 3; } 15015 errorHappened = false; 15016 } 15017 return 0; 15018 } 15019 XSetAfterFunction(display, &wtf); 15020 } 15021 15022 15023 XSetIOErrorHandler(&x11ioerrCB); 15024 Bool sup; 15025 XkbSetDetectableAutoRepeat(display, 1, &sup); // so we will not receive KeyRelease until key is really released 15026 createXIM(); 15027 version(with_eventloop) { 15028 import arsd.eventloop; 15029 addFileEventListeners(display.fd, &eventListener, null, null); 15030 } 15031 } 15032 15033 return display; 15034 } 15035 15036 extern(C) 15037 static int x11ioerrCB(Display* dpy) { 15038 throw new XDisconnectException(false); 15039 } 15040 15041 version(with_eventloop) { 15042 import arsd.eventloop; 15043 static void eventListener(OsFileHandle fd) { 15044 //this.mtLock(); 15045 //scope(exit) this.mtUnlock(); 15046 while(XPending(display)) 15047 doXNextEvent(display); 15048 } 15049 } 15050 15051 // close connection on program exit -- we need this to properly free all images 15052 static ~this () { 15053 // the gui thread must clean up after itself or else Xlib might deadlock 15054 // using this flag on any thread destruction is the easiest way i know of 15055 // (shared static this is run by the LAST thread to exit, which may not be 15056 // the gui thread, and normal static this run by ALL threads, so we gotta check.) 15057 if(thisIsGuiThread) 15058 close(); 15059 } 15060 15061 /// 15062 static void close() { 15063 if(display is null) 15064 return; 15065 15066 version(with_eventloop) { 15067 import arsd.eventloop; 15068 removeFileEventListeners(display.fd); 15069 } 15070 15071 // now remove all registered images to prevent shared memory leaks 15072 freeImages(); 15073 15074 // tbh I don't know why it is doing this but like if this happens to run 15075 // from the other thread there's frequent hanging inside here. 15076 if(thisIsGuiThread) 15077 XCloseDisplay(display); 15078 display = null; 15079 } 15080 } 15081 15082 mixin template NativeImageImplementation() { 15083 XImage* handle; 15084 ubyte* rawData; 15085 15086 XShmSegmentInfo shminfo; 15087 bool premultiply = true; 15088 15089 __gshared bool xshmQueryCompleted; 15090 __gshared bool _xshmAvailable; 15091 public static @property bool xshmAvailable() { 15092 if(!xshmQueryCompleted) { 15093 int i1, i2, i3; 15094 xshmQueryCompleted = true; 15095 15096 if(!XDisplayConnection.isLocal) 15097 _xshmAvailable = false; 15098 else 15099 _xshmAvailable = XQueryExtension(XDisplayConnection.get(), "MIT-SHM", &i1, &i2, &i3) != 0; 15100 } 15101 return _xshmAvailable; 15102 } 15103 15104 bool usingXshm; 15105 final: 15106 15107 private __gshared bool xshmfailed; 15108 15109 void createImage(int width, int height, bool forcexshm=false, bool enableAlpha = false) { 15110 auto display = XDisplayConnection.get(); 15111 assert(display !is null); 15112 auto screen = DefaultScreen(display); 15113 15114 // it will only use shared memory for somewhat largish images, 15115 // since otherwise we risk wasting shared memory handles on a lot of little ones 15116 if (xshmAvailable && (forcexshm || (width > 100 && height > 100))) { 15117 15118 15119 // it is possible for the query extension to return true, the DISPLAY check to pass, yet 15120 // the actual use still fails. For example, if the program is in a container and permission denied 15121 // on shared memory, or if it is a local thing forwarded to a remote server, etc. 15122 // 15123 // If it does fail, we need to detect it now, abort the xshm and fall back to core protocol. 15124 15125 15126 // synchronize so preexisting buffers are clear 15127 XSync(display, false); 15128 xshmfailed = false; 15129 15130 auto oldErrorHandler = XSetErrorHandler(&XShmErrorHandler); 15131 15132 15133 usingXshm = true; 15134 handle = XShmCreateImage( 15135 display, 15136 DefaultVisual(display, screen), 15137 enableAlpha ? 32: 24, 15138 ImageFormat.ZPixmap, 15139 null, 15140 &shminfo, 15141 width, height); 15142 if(handle is null) 15143 goto abortXshm1; 15144 15145 if(handle.bytes_per_line != 4 * width) 15146 goto abortXshm2; 15147 15148 shminfo.shmid = shmget(IPC_PRIVATE, handle.bytes_per_line * height, IPC_CREAT | 511 /* 0777 */); 15149 if(shminfo.shmid < 0) 15150 goto abortXshm3; 15151 handle.data = shminfo.shmaddr = rawData = cast(ubyte*) shmat(shminfo.shmid, null, 0); 15152 if(rawData == cast(ubyte*) -1) 15153 goto abortXshm4; 15154 shminfo.readOnly = 0; 15155 XShmAttach(display, &shminfo); 15156 15157 // and now to the final error check to ensure it actually worked. 15158 XSync(display, false); 15159 if(xshmfailed) 15160 goto abortXshm5; 15161 15162 XSetErrorHandler(oldErrorHandler); 15163 15164 XDisplayConnection.registerImage(this); 15165 // if I don't flush here there's a chance the dtor will run before the 15166 // ctor and lead to a bad value X error. While this hurts the efficiency 15167 // it is local anyway so prolly better to keep it simple 15168 XFlush(display); 15169 15170 return; 15171 15172 abortXshm5: 15173 shmdt(shminfo.shmaddr); 15174 rawData = null; 15175 15176 abortXshm4: 15177 shmctl(shminfo.shmid, IPC_RMID, null); 15178 15179 abortXshm3: 15180 // nothing needed, the shmget failed so there's nothing to free 15181 15182 abortXshm2: 15183 XDestroyImage(handle); 15184 handle = null; 15185 15186 abortXshm1: 15187 XSetErrorHandler(oldErrorHandler); 15188 usingXshm = false; 15189 handle = null; 15190 15191 shminfo = typeof(shminfo).init; 15192 15193 _xshmAvailable = false; // don't try again in the future 15194 15195 // writeln("fallingback"); 15196 15197 goto fallback; 15198 15199 } else { 15200 fallback: 15201 15202 if (forcexshm) throw new Exception("can't create XShm Image"); 15203 // This actually needs to be malloc to avoid a double free error when XDestroyImage is called 15204 import core.stdc.stdlib : malloc; 15205 rawData = cast(ubyte*) malloc(width * height * 4); 15206 15207 handle = XCreateImage( 15208 display, 15209 DefaultVisual(display, screen), 15210 enableAlpha ? 32 : 24, // bpp 15211 ImageFormat.ZPixmap, 15212 0, // offset 15213 rawData, 15214 width, height, 15215 enableAlpha ? 32 : 8 /* FIXME */, 4 * width); // padding, bytes per line 15216 } 15217 } 15218 15219 void dispose() { 15220 // note: this calls free(rawData) for us 15221 if(handle) { 15222 if (usingXshm) { 15223 XDisplayConnection.unregisterImage(this); 15224 if (XDisplayConnection.get()) XShmDetach(XDisplayConnection.get(), &shminfo); 15225 } 15226 XDestroyImage(handle); 15227 if(usingXshm) { 15228 shmdt(shminfo.shmaddr); 15229 shmctl(shminfo.shmid, IPC_RMID, null); 15230 } 15231 handle = null; 15232 } 15233 } 15234 15235 Color getPixel(int x, int y) @system { 15236 auto offset = (y * width + x) * 4; 15237 Color c; 15238 c.a = enableAlpha ? rawData[offset + 3] : 255; 15239 c.b = rawData[offset + 0]; 15240 c.g = rawData[offset + 1]; 15241 c.r = rawData[offset + 2]; 15242 if(enableAlpha && premultiply) 15243 c.unPremultiply; 15244 return c; 15245 } 15246 15247 void setPixel(int x, int y, Color c) @system { 15248 if(enableAlpha && premultiply) 15249 c.premultiply(); 15250 auto offset = (y * width + x) * 4; 15251 rawData[offset + 0] = c.b; 15252 rawData[offset + 1] = c.g; 15253 rawData[offset + 2] = c.r; 15254 if(enableAlpha) 15255 rawData[offset + 3] = c.a; 15256 } 15257 15258 void convertToRgbaBytes(ubyte[] where) @system { 15259 assert(where.length == this.width * this.height * 4); 15260 15261 // if rawData had a length.... 15262 //assert(rawData.length == where.length); 15263 for(int idx = 0; idx < where.length; idx += 4) { 15264 where[idx + 0] = rawData[idx + 2]; // r 15265 where[idx + 1] = rawData[idx + 1]; // g 15266 where[idx + 2] = rawData[idx + 0]; // b 15267 where[idx + 3] = enableAlpha ? rawData[idx + 3] : 255; // a 15268 15269 if(enableAlpha && premultiply) 15270 unPremultiplyRgba(where[idx .. idx + 4]); 15271 } 15272 } 15273 15274 void setFromRgbaBytes(in ubyte[] where) @system { 15275 assert(where.length == this.width * this.height * 4); 15276 15277 // if rawData had a length.... 15278 //assert(rawData.length == where.length); 15279 for(int idx = 0; idx < where.length; idx += 4) { 15280 rawData[idx + 2] = where[idx + 0]; // r 15281 rawData[idx + 1] = where[idx + 1]; // g 15282 rawData[idx + 0] = where[idx + 2]; // b 15283 if(enableAlpha) { 15284 rawData[idx + 3] = where[idx + 3]; // a 15285 if(premultiply) 15286 premultiplyBgra(rawData[idx .. idx + 4]); 15287 } 15288 } 15289 } 15290 15291 } 15292 15293 mixin template NativeSimpleWindowImplementation() { 15294 GC gc; 15295 Window window; 15296 Display* display; 15297 15298 Pixmap buffer; 15299 int bufferw, bufferh; // size of the buffer; can be bigger than window 15300 XIC xic; // input context 15301 int curHidden = 0; // counter 15302 Cursor blankCurPtr = 0; 15303 int cursorSequenceNumber = 0; 15304 int warpEventCount = 0; // number of mouse movement events to eat 15305 15306 __gshared X11SetSelectionHandler[Atom] setSelectionHandlers; // FIXME: make sure this is not accessed from other threads. it might be ok to make it TLS 15307 X11GetSelectionHandler[Atom] getSelectionHandlers; 15308 15309 version(without_opengl) {} else 15310 GLXContext glc; 15311 15312 private void fixFixedSize(bool forced=false) (int width, int height) { 15313 if (forced || this.resizability == Resizability.fixedSize) { 15314 //{ import core.stdc.stdio; printf("fixing size to: %dx%d\n", width, height); } 15315 XSizeHints sh; 15316 static if (!forced) { 15317 c_long spr; 15318 XGetWMNormalHints(display, window, &sh, &spr); 15319 sh.flags |= PMaxSize | PMinSize; 15320 } else { 15321 sh.flags = PMaxSize | PMinSize; 15322 } 15323 sh.min_width = width; 15324 sh.min_height = height; 15325 sh.max_width = width; 15326 sh.max_height = height; 15327 XSetWMNormalHints(display, window, &sh); 15328 //XFlush(display); 15329 } 15330 } 15331 15332 ScreenPainter getPainter(bool manualInvalidations) { 15333 return ScreenPainter(this, window, manualInvalidations); 15334 } 15335 15336 void move(int x, int y) { 15337 XMoveWindow(display, window, x, y); 15338 } 15339 15340 void resize(int w, int h) { 15341 if (w < 1) w = 1; 15342 if (h < 1) h = 1; 15343 XResizeWindow(display, window, w, h); 15344 15345 // calling this now to avoid waiting for the server to 15346 // acknowledge the resize; draws without returning to the 15347 // event loop will thus actually work. the server's event 15348 // btw might overrule this and resize it again 15349 recordX11Resize(display, this, w, h); 15350 15351 updateOpenglViewportIfNeeded(w, h); 15352 } 15353 15354 void moveResize (int x, int y, int w, int h) { 15355 if (w < 1) w = 1; 15356 if (h < 1) h = 1; 15357 XMoveResizeWindow(display, window, x, y, w, h); 15358 updateOpenglViewportIfNeeded(w, h); 15359 } 15360 15361 void hideCursor () { 15362 if (curHidden++ == 0) { 15363 if (!blankCurPtr || cursorSequenceNumber != XDisplayConnection.connectionSequenceNumber) { 15364 static const(char)[1] cmbmp = 0; 15365 XColor blackcolor = { 0, 0, 0, 0, 0, 0 }; 15366 Pixmap pm = XCreateBitmapFromData(display, window, cmbmp.ptr, 1, 1); 15367 blankCurPtr = XCreatePixmapCursor(display, pm, pm, &blackcolor, &blackcolor, 0, 0); 15368 cursorSequenceNumber = XDisplayConnection.connectionSequenceNumber; 15369 XFreePixmap(display, pm); 15370 } 15371 XDefineCursor(display, window, blankCurPtr); 15372 } 15373 } 15374 15375 void showCursor () { 15376 if (--curHidden == 0) XUndefineCursor(display, window); 15377 } 15378 15379 void warpMouse (int x, int y) { 15380 // here i will send dummy "ignore next mouse motion" event, 15381 // 'cause `XWarpPointer()` sends synthesised mouse motion, 15382 // and we don't need to report it to the user (as warping is 15383 // used when the user needs movement deltas). 15384 //XClientMessageEvent xclient; 15385 XEvent e; 15386 e.xclient.type = EventType.ClientMessage; 15387 e.xclient.window = window; 15388 e.xclient.message_type = GetAtom!("_X11SDPY_INSMME_FLAG_EVENT_", true)(display); // let's hope nobody else will use such stupid name ;-) 15389 e.xclient.format = 32; 15390 e.xclient.data.l[0] = 0; 15391 debug(x11sdpy_warp_debug) { import core.stdc.stdio : printf; printf("X11: sending \"INSMME\"...\n"); } 15392 //{ 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]); } 15393 XSendEvent(display, window, false, EventMask.NoEventMask, /*cast(XEvent*)&xclient*/&e); 15394 // now warp pointer... 15395 debug(x11sdpy_warp_debug) { import core.stdc.stdio : printf; printf("X11: sending \"warp\"...\n"); } 15396 XWarpPointer(display, None, window, 0, 0, 0, 0, x, y); 15397 // ...and flush 15398 debug(x11sdpy_warp_debug) { import core.stdc.stdio : printf; printf("X11: flushing...\n"); } 15399 XFlush(display); 15400 } 15401 15402 void sendDummyEvent () { 15403 // here i will send dummy event to ping event queue 15404 XEvent e; 15405 e.xclient.type = EventType.ClientMessage; 15406 e.xclient.window = window; 15407 e.xclient.message_type = GetAtom!("_X11SDPY_DUMMY_EVENT_", true)(display); // let's hope nobody else will use such stupid name ;-) 15408 e.xclient.format = 32; 15409 e.xclient.data.l[0] = 0; 15410 XSendEvent(display, window, false, EventMask.NoEventMask, /*cast(XEvent*)&xclient*/&e); 15411 XFlush(display); 15412 } 15413 15414 void setTitle(string title) { 15415 if (title.ptr is null) title = ""; 15416 auto XA_UTF8 = XInternAtom(display, "UTF8_STRING".ptr, false); 15417 auto XA_NETWM_NAME = XInternAtom(display, "_NET_WM_NAME".ptr, false); 15418 XTextProperty windowName; 15419 windowName.value = title.ptr; 15420 windowName.encoding = XA_UTF8; //XA_STRING; 15421 windowName.format = 8; 15422 windowName.nitems = cast(uint)title.length; 15423 XSetWMName(display, window, &windowName); 15424 char[1024] namebuf = 0; 15425 auto maxlen = namebuf.length-1; 15426 if (maxlen > title.length) maxlen = title.length; 15427 namebuf[0..maxlen] = title[0..maxlen]; 15428 XStoreName(display, window, namebuf.ptr); 15429 XChangeProperty(display, window, XA_NETWM_NAME, XA_UTF8, 8, PropModeReplace, title.ptr, cast(uint)title.length); 15430 flushGui(); // without this OpenGL windows has a LONG delay before changing title 15431 } 15432 15433 string[] getTitles() { 15434 auto XA_UTF8 = XInternAtom(display, "UTF8_STRING".ptr, false); 15435 auto XA_NETWM_NAME = XInternAtom(display, "_NET_WM_NAME".ptr, false); 15436 XTextProperty textProp; 15437 if (XGetTextProperty(display, window, &textProp, XA_NETWM_NAME) != 0 || XGetWMName(display, window, &textProp) != 0) { 15438 if ((textProp.encoding == XA_UTF8 || textProp.encoding == XA_STRING) && textProp.format == 8) { 15439 return textProp.value[0 .. textProp.nitems].idup.split('\0'); 15440 } else 15441 return []; 15442 } else 15443 return null; 15444 } 15445 15446 string getTitle() { 15447 auto titles = getTitles(); 15448 return titles.length ? titles[0] : null; 15449 } 15450 15451 void setMinSize (int minwidth, int minheight) { 15452 import core.stdc.config : c_long; 15453 if (minwidth < 1) minwidth = 1; 15454 if (minheight < 1) minheight = 1; 15455 XSizeHints sh; 15456 c_long spr; 15457 XGetWMNormalHints(display, window, &sh, &spr); 15458 sh.min_width = minwidth; 15459 sh.min_height = minheight; 15460 sh.flags |= PMinSize; 15461 XSetWMNormalHints(display, window, &sh); 15462 flushGui(); 15463 } 15464 15465 void setMaxSize (int maxwidth, int maxheight) { 15466 import core.stdc.config : c_long; 15467 if (maxwidth < 1) maxwidth = 1; 15468 if (maxheight < 1) maxheight = 1; 15469 XSizeHints sh; 15470 c_long spr; 15471 XGetWMNormalHints(display, window, &sh, &spr); 15472 sh.max_width = maxwidth; 15473 sh.max_height = maxheight; 15474 sh.flags |= PMaxSize; 15475 XSetWMNormalHints(display, window, &sh); 15476 flushGui(); 15477 } 15478 15479 void setResizeGranularity (int granx, int grany) { 15480 import core.stdc.config : c_long; 15481 if (granx < 1) granx = 1; 15482 if (grany < 1) grany = 1; 15483 XSizeHints sh; 15484 c_long spr; 15485 XGetWMNormalHints(display, window, &sh, &spr); 15486 sh.width_inc = granx; 15487 sh.height_inc = grany; 15488 sh.flags |= PResizeInc; 15489 XSetWMNormalHints(display, window, &sh); 15490 flushGui(); 15491 } 15492 15493 void setOpacity (uint opacity) { 15494 arch_ulong o = opacity; 15495 if (opacity == uint.max) 15496 XDeleteProperty(display, window, XInternAtom(display, "_NET_WM_WINDOW_OPACITY".ptr, false)); 15497 else 15498 XChangeProperty(display, window, XInternAtom(display, "_NET_WM_WINDOW_OPACITY".ptr, false), 15499 XA_CARDINAL, 32, PropModeReplace, &o, 1); 15500 } 15501 15502 void createWindow(int width, int height, string title, in OpenGlOptions opengl, SimpleWindow parent) @trusted { 15503 version(without_opengl) {} else if(opengl == OpenGlOptions.yes && !openGlLibrariesSuccessfullyLoaded) throw new Exception("OpenGL libraries did not load"); 15504 display = XDisplayConnection.get(); 15505 auto screen = DefaultScreen(display); 15506 15507 bool overrideRedirect = false; 15508 if(windowType == WindowTypes.dropdownMenu || windowType == WindowTypes.popupMenu || windowType == WindowTypes.notification)// || windowType == WindowTypes.nestedChild) 15509 overrideRedirect = true; 15510 15511 version(without_opengl) {} 15512 else { 15513 if(opengl == OpenGlOptions.yes) { 15514 GLXFBConfig fbconf = null; 15515 XVisualInfo* vi = null; 15516 bool useLegacy = false; 15517 static if (SdpyIsUsingIVGLBinds) {if (glbindGetProcAddress("glHint") is null) assert(0, "GL: error loading OpenGL"); } // loads all necessary functions 15518 if (sdpyOpenGLContextVersion != 0 && glXCreateContextAttribsARB_present()) { 15519 int[23] visualAttribs = [ 15520 GLX_X_RENDERABLE , 1/*True*/, 15521 GLX_DRAWABLE_TYPE, GLX_WINDOW_BIT, 15522 GLX_RENDER_TYPE , GLX_RGBA_BIT, 15523 GLX_X_VISUAL_TYPE, GLX_TRUE_COLOR, 15524 GLX_RED_SIZE , 8, 15525 GLX_GREEN_SIZE , 8, 15526 GLX_BLUE_SIZE , 8, 15527 GLX_ALPHA_SIZE , 8, 15528 GLX_DEPTH_SIZE , 24, 15529 GLX_STENCIL_SIZE , 8, 15530 GLX_DOUBLEBUFFER , 1/*True*/, 15531 0/*None*/, 15532 ]; 15533 int fbcount; 15534 GLXFBConfig* fbc = glXChooseFBConfig(display, screen, visualAttribs.ptr, &fbcount); 15535 if (fbcount == 0) { 15536 useLegacy = true; // try to do at least something 15537 } else { 15538 // pick the FB config/visual with the most samples per pixel 15539 int bestidx = -1, bestns = -1; 15540 foreach (int fbi; 0..fbcount) { 15541 int sb, samples; 15542 glXGetFBConfigAttrib(display, fbc[fbi], GLX_SAMPLE_BUFFERS, &sb); 15543 glXGetFBConfigAttrib(display, fbc[fbi], GLX_SAMPLES, &samples); 15544 if (bestidx < 0 || sb && samples > bestns) { bestidx = fbi; bestns = samples; } 15545 } 15546 //{ import core.stdc.stdio; printf("found gl visual with %d samples\n", bestns); } 15547 fbconf = fbc[bestidx]; 15548 // Be sure to free the FBConfig list allocated by glXChooseFBConfig() 15549 XFree(fbc); 15550 vi = cast(XVisualInfo*)glXGetVisualFromFBConfig(display, fbconf); 15551 } 15552 } 15553 if (vi is null || useLegacy) { 15554 static immutable GLint[5] attrs = [ GLX_RGBA, GLX_DEPTH_SIZE, 24, GLX_DOUBLEBUFFER, None ]; 15555 vi = cast(XVisualInfo*)glXChooseVisual(display, 0, attrs.ptr); 15556 useLegacy = true; 15557 } 15558 if (vi is null) throw new Exception("no open gl visual found"); 15559 15560 XSetWindowAttributes swa; 15561 auto root = RootWindow(display, screen); 15562 swa.colormap = XCreateColormap(display, root, vi.visual, AllocNone); 15563 15564 swa.override_redirect = overrideRedirect; 15565 15566 window = XCreateWindow(display, (windowType != WindowTypes.nestedChild || parent is null) ? root : parent.impl.window, 15567 0, 0, width, height, 15568 0, vi.depth, 1 /* InputOutput */, vi.visual, CWColormap | CWOverrideRedirect, &swa); 15569 15570 // now try to use `glXCreateContextAttribsARB()` if it's here 15571 if (!useLegacy) { 15572 // request fairly advanced context, even with stencil buffer! 15573 int[9] contextAttribs = [ 15574 GLX_CONTEXT_MAJOR_VERSION_ARB, (sdpyOpenGLContextVersion>>8), 15575 GLX_CONTEXT_MINOR_VERSION_ARB, (sdpyOpenGLContextVersion&0xff), 15576 /*GLX_CONTEXT_PROFILE_MASK_ARB*/0x9126, (sdpyOpenGLContextCompatible ? /*GLX_CONTEXT_COMPATIBILITY_PROFILE_BIT_ARB*/0x02 : /*GLX_CONTEXT_CORE_PROFILE_BIT_ARB*/ 0x01), 15577 // for modern context, set "forward compatibility" flag too 15578 (sdpyOpenGLContextCompatible ? None : /*GLX_CONTEXT_FLAGS_ARB*/ 0x2094), /*GLX_CONTEXT_FORWARD_COMPATIBLE_BIT_ARB*/ 0x02, 15579 0/*None*/, 15580 ]; 15581 glc = glXCreateContextAttribsARB(display, fbconf, null, 1/*True*/, contextAttribs.ptr); 15582 if (glc is null && sdpyOpenGLContextAllowFallback) { 15583 sdpyOpenGLContextVersion = 0; 15584 glc = glXCreateContext(display, vi, null, /*GL_TRUE*/1); 15585 } 15586 //{ import core.stdc.stdio; printf("using modern ogl v%d.%d\n", contextAttribs[1], contextAttribs[3]); } 15587 } else { 15588 // fallback to old GLX call 15589 if (sdpyOpenGLContextAllowFallback || sdpyOpenGLContextVersion == 0) { 15590 sdpyOpenGLContextVersion = 0; 15591 glc = glXCreateContext(display, vi, null, /*GL_TRUE*/1); 15592 } 15593 } 15594 // sync to ensure any errors generated are processed 15595 XSync(display, 0/*False*/); 15596 //{ import core.stdc.stdio; printf("ogl is here\n"); } 15597 if(glc is null) 15598 throw new Exception("glc"); 15599 } 15600 } 15601 15602 if(opengl == OpenGlOptions.no) { 15603 15604 XSetWindowAttributes swa; 15605 swa.background_pixel = WhitePixel(display, screen); 15606 swa.border_pixel = BlackPixel(display, screen); 15607 swa.override_redirect = overrideRedirect; 15608 auto root = RootWindow(display, screen); 15609 swa.colormap = XCreateColormap(display, root, DefaultVisual(display, screen), AllocNone); 15610 15611 window = XCreateWindow(display, (windowType != WindowTypes.nestedChild || parent is null) ? root : parent.impl.window, 15612 0, 0, width, height, 15613 // I'm commenting that CWBackPixel thing just because it actually causes flicker for no apparent benefit. 15614 0, CopyFromParent, 1 /* InputOutput */, cast(Visual*) CopyFromParent, CWColormap /*| CWBackPixel*/ | CWBorderPixel | CWOverrideRedirect, &swa); 15615 15616 15617 15618 /* 15619 window = XCreateSimpleWindow( 15620 display, 15621 parent is null ? RootWindow(display, screen) : parent.impl.window, 15622 0, 0, // x, y 15623 width, height, 15624 1, // border width 15625 BlackPixel(display, screen), // border 15626 WhitePixel(display, screen)); // background 15627 */ 15628 15629 buffer = XCreatePixmap(display, cast(Drawable) window, width, height, DefaultDepthOfDisplay(display)); 15630 bufferw = width; 15631 bufferh = height; 15632 15633 gc = DefaultGC(display, screen); 15634 15635 // clear out the buffer to get us started... 15636 XSetForeground(display, gc, WhitePixel(display, screen)); 15637 XFillRectangle(display, cast(Drawable) buffer, gc, 0, 0, width, height); 15638 XSetForeground(display, gc, BlackPixel(display, screen)); 15639 } 15640 15641 // input context 15642 //TODO: create this only for top-level windows, and reuse that? 15643 populateXic(); 15644 15645 if (sdpyWindowClassStr is null) loadBinNameToWindowClassName(); 15646 if (sdpyWindowClassStr is null) sdpyWindowClass = "DSimpleWindow"; 15647 // window class 15648 if (sdpyWindowClassStr !is null && sdpyWindowClassStr[0]) { 15649 //{ import core.stdc.stdio; printf("winclass: [%s]\n", sdpyWindowClassStr); } 15650 XClassHint klass; 15651 XWMHints wh; 15652 if(this.customizationFlags & WindowFlags.managesChildWindowFocus) { 15653 wh.input = true; 15654 wh.flags |= InputHint; 15655 } 15656 XSizeHints size; 15657 klass.res_name = sdpyWindowClassStr; 15658 klass.res_class = sdpyWindowClassStr; 15659 XSetWMProperties(display, window, null, null, null, 0, &size, &wh, &klass); 15660 } 15661 15662 setTitle(title); 15663 SimpleWindow.nativeMapping[window] = this; 15664 CapableOfHandlingNativeEvent.nativeHandleMapping[window] = this; 15665 15666 // This gives our window a close button 15667 if (windowType != WindowTypes.eventOnly) { 15668 Atom[2] atoms = [GetAtom!"WM_DELETE_WINDOW"(display), GetAtom!"WM_TAKE_FOCUS"(display)]; 15669 int useAtoms; 15670 if(this.customizationFlags & WindowFlags.managesChildWindowFocus) { 15671 useAtoms = 2; 15672 } else { 15673 useAtoms = 1; 15674 } 15675 assert(useAtoms <= atoms.length); 15676 XSetWMProtocols(display, window, atoms.ptr, useAtoms); 15677 } 15678 15679 // FIXME: windowType and customizationFlags 15680 Atom[8] wsatoms; // here, due to goto 15681 int wmsacount = 0; // here, due to goto 15682 15683 try 15684 final switch(windowType) { 15685 case WindowTypes.normal: 15686 setNetWMWindowType(GetAtom!"_NET_WM_WINDOW_TYPE_NORMAL"(display)); 15687 break; 15688 case WindowTypes.undecorated: 15689 motifHideDecorations(); 15690 setNetWMWindowType(GetAtom!"_NET_WM_WINDOW_TYPE_NORMAL"(display)); 15691 break; 15692 case WindowTypes.eventOnly: 15693 _hidden = true; 15694 XSelectInput(display, window, EventMask.StructureNotifyMask); // without this, we won't get destroy notification 15695 goto hiddenWindow; 15696 //break; 15697 case WindowTypes.nestedChild: 15698 // handled in XCreateWindow calls 15699 break; 15700 15701 case WindowTypes.dropdownMenu: 15702 motifHideDecorations(); 15703 setNetWMWindowType(GetAtom!"_NET_WM_WINDOW_TYPE_DROPDOWN_MENU"(display)); 15704 customizationFlags |= WindowFlags.skipTaskbar | WindowFlags.alwaysOnTop; 15705 break; 15706 case WindowTypes.popupMenu: 15707 motifHideDecorations(); 15708 setNetWMWindowType(GetAtom!"_NET_WM_WINDOW_TYPE_POPUP_MENU"(display)); 15709 customizationFlags |= WindowFlags.skipTaskbar | WindowFlags.alwaysOnTop; 15710 break; 15711 case WindowTypes.notification: 15712 motifHideDecorations(); 15713 setNetWMWindowType(GetAtom!"_NET_WM_WINDOW_TYPE_NOTIFICATION"(display)); 15714 customizationFlags |= WindowFlags.skipTaskbar | WindowFlags.alwaysOnTop; 15715 break; 15716 case WindowTypes.minimallyWrapped: 15717 assert(0, "don't create a minimallyWrapped thing explicitly!"); 15718 15719 case WindowTypes.dialog: 15720 setNetWMWindowType(GetAtom!"_NET_WM_WINDOW_TYPE_DIALOG"(display)); 15721 break; 15722 /+ 15723 case WindowTypes.menu: 15724 atoms[0] = GetAtom!"_NET_WM_WINDOW_TYPE_MENU"(display); 15725 motifHideDecorations(); 15726 break; 15727 case WindowTypes.desktop: 15728 atoms[0] = GetAtom!"_NET_WM_WINDOW_TYPE_DESKTOP"(display); 15729 break; 15730 case WindowTypes.dock: 15731 atoms[0] = GetAtom!"_NET_WM_WINDOW_TYPE_DOCK"(display); 15732 break; 15733 case WindowTypes.toolbar: 15734 atoms[0] = GetAtom!"_NET_WM_WINDOW_TYPE_TOOLBAR"(display); 15735 break; 15736 case WindowTypes.menu: 15737 atoms[0] = GetAtom!"_NET_WM_WINDOW_TYPE_MENU"(display); 15738 break; 15739 case WindowTypes.utility: 15740 atoms[0] = GetAtom!"_NET_WM_WINDOW_TYPE_UTILITY"(display); 15741 break; 15742 case WindowTypes.splash: 15743 atoms[0] = GetAtom!"_NET_WM_WINDOW_TYPE_SPLASH"(display); 15744 break; 15745 case WindowTypes.tooltip: 15746 atoms[0] = GetAtom!"_NET_WM_WINDOW_TYPE_TOOLTIP"(display); 15747 break; 15748 case WindowTypes.notification: 15749 atoms[0] = GetAtom!"_NET_WM_WINDOW_TYPE_NOTIFICATION"(display); 15750 break; 15751 case WindowTypes.combo: 15752 atoms[0] = GetAtom!"_NET_WM_WINDOW_TYPE_COMBO"(display); 15753 break; 15754 case WindowTypes.dnd: 15755 atoms[0] = GetAtom!"_NET_WM_WINDOW_TYPE_DND"(display); 15756 break; 15757 +/ 15758 } 15759 catch(Exception e) { 15760 // XInternAtom failed, prolly a WM 15761 // that doesn't support these things 15762 } 15763 15764 if (customizationFlags&WindowFlags.skipTaskbar) wsatoms[wmsacount++] = GetAtom!("_NET_WM_STATE_SKIP_TASKBAR", true)(display); 15765 // the two following flags may be ignored by WM 15766 if (customizationFlags&WindowFlags.alwaysOnTop) wsatoms[wmsacount++] = GetAtom!("_NET_WM_STATE_ABOVE", true)(display); 15767 if (customizationFlags&WindowFlags.alwaysOnBottom) wsatoms[wmsacount++] = GetAtom!("_NET_WM_STATE_BELOW", true)(display); 15768 15769 if (wmsacount != 0) XChangeProperty(display, window, GetAtom!("_NET_WM_STATE", true)(display), XA_ATOM, 32 /* bits */,0 /*PropModeReplace*/, wsatoms.ptr, wmsacount); 15770 15771 if (this.resizability == Resizability.fixedSize || (opengl == OpenGlOptions.no && this.resizability != Resizability.allowResizing)) fixFixedSize!true(width, height); 15772 15773 // What would be ideal here is if they only were 15774 // selected if there was actually an event handler 15775 // for them... 15776 15777 selectDefaultInput((customizationFlags & WindowFlags.alwaysRequestMouseMotionEvents)?true:false); 15778 15779 hiddenWindow: 15780 15781 // set the pid property for lookup later by window managers 15782 // a standard convenience 15783 import core.sys.posix.unistd; 15784 arch_ulong pid = getpid(); 15785 15786 XChangeProperty( 15787 display, 15788 impl.window, 15789 GetAtom!("_NET_WM_PID", true)(display), 15790 XA_CARDINAL, 15791 32 /* bits */, 15792 0 /*PropModeReplace*/, 15793 &pid, 15794 1); 15795 15796 if(isTransient && parent) { // customizationFlags & WindowFlags.transient) { 15797 if(parent is null) assert(0); 15798 // sdpyPrintDebugString("transient"); 15799 XChangeProperty( 15800 display, 15801 impl.window, 15802 GetAtom!("WM_TRANSIENT_FOR", true)(display), 15803 XA_WINDOW, 15804 32 /* bits */, 15805 0 /*PropModeReplace*/, 15806 &parent.impl.window, 15807 1); 15808 15809 } 15810 15811 if(windowType != WindowTypes.eventOnly && (customizationFlags&WindowFlags.dontAutoShow) == 0) { 15812 XMapWindow(display, window); 15813 } else { 15814 _hidden = true; 15815 } 15816 } 15817 15818 void populateXic() { 15819 if (XDisplayConnection.xim !is null) { 15820 xic = XCreateIC(XDisplayConnection.xim, 15821 /*XNInputStyle*/"inputStyle".ptr, XIMPreeditNothing|XIMStatusNothing, 15822 /*XNClientWindow*/"clientWindow".ptr, window, 15823 /*XNFocusWindow*/"focusWindow".ptr, window, 15824 null); 15825 if (xic is null) { 15826 import core.stdc.stdio : stderr, fprintf; 15827 fprintf(stderr, "XCreateIC failed for window %u\n", cast(uint)window); 15828 } 15829 } 15830 } 15831 15832 void selectDefaultInput(bool forceIncludeMouseMotion) { 15833 auto mask = EventMask.ExposureMask | 15834 EventMask.KeyPressMask | 15835 EventMask.KeyReleaseMask | 15836 EventMask.PropertyChangeMask | 15837 EventMask.FocusChangeMask | 15838 EventMask.StructureNotifyMask | 15839 EventMask.SubstructureNotifyMask | 15840 EventMask.VisibilityChangeMask 15841 | EventMask.ButtonPressMask 15842 | EventMask.ButtonReleaseMask 15843 ; 15844 15845 // xshm is our shortcut for local connections 15846 if(XDisplayConnection.isLocal || forceIncludeMouseMotion) 15847 mask |= EventMask.PointerMotionMask; 15848 else 15849 mask |= EventMask.ButtonMotionMask; 15850 15851 XSelectInput(display, window, mask); 15852 } 15853 15854 15855 void setNetWMWindowType(Atom type) { 15856 Atom[2] atoms; 15857 15858 atoms[0] = type; 15859 // generic fallback 15860 atoms[1] = GetAtom!"_NET_WM_WINDOW_TYPE_NORMAL"(display); 15861 15862 XChangeProperty( 15863 display, 15864 impl.window, 15865 GetAtom!"_NET_WM_WINDOW_TYPE"(display), 15866 XA_ATOM, 15867 32 /* bits */, 15868 0 /*PropModeReplace*/, 15869 atoms.ptr, 15870 cast(int) atoms.length); 15871 } 15872 15873 void motifHideDecorations(bool hide = true) { 15874 MwmHints hints; 15875 hints.flags = MWM_HINTS_DECORATIONS; 15876 hints.decorations = hide ? 0 : 1; 15877 15878 XChangeProperty( 15879 display, 15880 impl.window, 15881 GetAtom!"_MOTIF_WM_HINTS"(display), 15882 GetAtom!"_MOTIF_WM_HINTS"(display), 15883 32 /* bits */, 15884 0 /*PropModeReplace*/, 15885 &hints, 15886 hints.sizeof / 4); 15887 } 15888 15889 /*k8: unused 15890 void createOpenGlContext() { 15891 15892 } 15893 */ 15894 15895 void closeWindow() { 15896 // I can't close this or a child window closing will 15897 // break events for everyone. So I'm just leaking it right 15898 // now and that is probably perfectly fine... 15899 version(none) 15900 if (customEventFDRead != -1) { 15901 import core.sys.posix.unistd : close; 15902 auto same = customEventFDRead == customEventFDWrite; 15903 15904 close(customEventFDRead); 15905 if(!same) 15906 close(customEventFDWrite); 15907 customEventFDRead = -1; 15908 customEventFDWrite = -1; 15909 } 15910 15911 version(without_opengl) {} else 15912 if(glc !is null) { 15913 glXDestroyContext(display, glc); 15914 glc = null; 15915 } 15916 15917 if(buffer) 15918 XFreePixmap(display, buffer); 15919 bufferw = bufferh = 0; 15920 if (blankCurPtr && cursorSequenceNumber == XDisplayConnection.connectionSequenceNumber) XFreeCursor(display, blankCurPtr); 15921 XDestroyWindow(display, window); 15922 XFlush(display); 15923 } 15924 15925 void dispose() { 15926 } 15927 15928 bool destroyed = false; 15929 } 15930 15931 bool insideXEventLoop; 15932 } 15933 15934 version(X11) { 15935 15936 int mouseDoubleClickTimeout = 350; /// Double click timeout. X only, you probably shouldn't change this. 15937 15938 private class ResizeEvent { 15939 int width, height; 15940 } 15941 15942 void recordX11ResizeAsync(Display* display, SimpleWindow win, int width, int height) { 15943 if(win.windowType == WindowTypes.minimallyWrapped) 15944 return; 15945 15946 if(win.pendingResizeEvent is null) { 15947 win.pendingResizeEvent = new ResizeEvent(); 15948 win.addEventListener((ResizeEvent re) { 15949 recordX11Resize(XDisplayConnection.get, win, re.width, re.height); 15950 }); 15951 } 15952 win.pendingResizeEvent.width = width; 15953 win.pendingResizeEvent.height = height; 15954 if(!win.eventQueued!ResizeEvent) { 15955 win.postEvent(win.pendingResizeEvent); 15956 } 15957 } 15958 15959 void recordX11Resize(Display* display, SimpleWindow win, int width, int height) { 15960 if(win.windowType == WindowTypes.minimallyWrapped) 15961 return; 15962 if(win.closed) 15963 return; 15964 15965 if(width != win.width || height != win.height) { 15966 15967 // writeln("RESIZE: ", width, "x", height, " was ", win._width, "x", win._height, " window: ", win.windowType, "-", win.title, " ", win.window); 15968 win._width = width; 15969 win._height = height; 15970 15971 if(win.openglMode == OpenGlOptions.no) { 15972 // FIXME: could this be more efficient? 15973 15974 if (win.bufferw < width || win.bufferh < height) { 15975 //{ 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); } 15976 // grow the internal buffer to match the window... 15977 auto newPixmap = XCreatePixmap(display, cast(Drawable) win.window, width, height, DefaultDepthOfDisplay(display)); 15978 { 15979 GC xgc = XCreateGC(win.display, cast(Drawable)win.window, 0, null); 15980 XCopyGC(win.display, win.gc, 0xffffffff, xgc); 15981 scope(exit) XFreeGC(win.display, xgc); 15982 XSetClipMask(win.display, xgc, None); 15983 XSetForeground(win.display, xgc, 0); 15984 XFillRectangle(display, cast(Drawable)newPixmap, xgc, 0, 0, width, height); 15985 } 15986 XCopyArea(display, 15987 cast(Drawable) win.buffer, 15988 cast(Drawable) newPixmap, 15989 win.gc, 0, 0, 15990 win.bufferw < width ? win.bufferw : win.width, 15991 win.bufferh < height ? win.bufferh : win.height, 15992 0, 0); 15993 15994 XFreePixmap(display, win.buffer); 15995 win.buffer = newPixmap; 15996 win.bufferw = width; 15997 win.bufferh = height; 15998 } 15999 16000 // clear unused parts of the buffer 16001 if (win.bufferw > width || win.bufferh > height) { 16002 GC xgc = XCreateGC(win.display, cast(Drawable)win.window, 0, null); 16003 XCopyGC(win.display, win.gc, 0xffffffff, xgc); 16004 scope(exit) XFreeGC(win.display, xgc); 16005 XSetClipMask(win.display, xgc, None); 16006 XSetForeground(win.display, xgc, 0); 16007 immutable int maxw = (win.bufferw > width ? win.bufferw : width); 16008 immutable int maxh = (win.bufferh > height ? win.bufferh : height); 16009 XFillRectangle(win.display, cast(Drawable)win.buffer, xgc, width, 0, maxw, maxh); // let X11 do clipping 16010 XFillRectangle(win.display, cast(Drawable)win.buffer, xgc, 0, height, maxw, maxh); // let X11 do clipping 16011 } 16012 16013 } 16014 16015 win.updateOpenglViewportIfNeeded(width, height); 16016 16017 win.fixFixedSize(width, height); //k8: this does nothing on my FluxBox; wtf?! 16018 16019 if(win.resizability != Resizability.automaticallyScaleIfPossible) 16020 if(win.windowResized !is null) { 16021 XUnlockDisplay(display); 16022 scope(exit) XLockDisplay(display); 16023 win.windowResized(width, height); 16024 } 16025 } 16026 } 16027 16028 16029 /// Platform-specific, you might use it when doing a custom event loop. 16030 bool doXNextEvent(Display* display) { 16031 bool done; 16032 XEvent e; 16033 XNextEvent(display, &e); 16034 version(sddddd) { 16035 if(auto win = e.xany.window in CapableOfHandlingNativeEvent.nativeHandleMapping) { 16036 if(typeid(cast(Object) *win) == NotificationAreaIcon.classinfo) 16037 writeln("event for: ", e.xany.window, "; type is ", to!string(cast(EventType)e.type)); 16038 } 16039 } 16040 16041 // filter out compose events 16042 if (XFilterEvent(&e, None)) { 16043 //{ import core.stdc.stdio : printf; printf("XFilterEvent filtered!\n"); } 16044 //NOTE: we should ungrab keyboard here, but simpledisplay doesn't use keyboard grabbing (yet) 16045 return false; 16046 } 16047 // process keyboard mapping changes 16048 if (e.type == EventType.KeymapNotify) { 16049 //{ import core.stdc.stdio : printf; printf("KeymapNotify processed!\n"); } 16050 XRefreshKeyboardMapping(&e.xmapping); 16051 return false; 16052 } 16053 16054 version(with_eventloop) 16055 import arsd.eventloop; 16056 16057 if(SimpleWindow.handleNativeGlobalEvent !is null) { 16058 // see windows impl's comments 16059 XUnlockDisplay(display); 16060 scope(exit) XLockDisplay(display); 16061 auto ret = SimpleWindow.handleNativeGlobalEvent(e); 16062 if(ret == 0) 16063 return done; 16064 } 16065 16066 16067 if(auto win = e.xany.window in CapableOfHandlingNativeEvent.nativeHandleMapping) { 16068 if(win.getNativeEventHandler !is null) { 16069 XUnlockDisplay(display); 16070 scope(exit) XLockDisplay(display); 16071 auto ret = win.getNativeEventHandler()(e); 16072 if(ret == 0) 16073 return done; 16074 } 16075 } 16076 16077 if(xrrEventBase != -1 && e.type == xrrEventBase + RRScreenChangeNotify) { 16078 if(auto win = e.xany.window in SimpleWindow.nativeMapping) { 16079 // we get this because of the RRScreenChangeNotifyMask 16080 16081 // this isn't actually an ideal way to do it since it wastes time 16082 // but meh it is simple and it works. 16083 win.actualDpiLoadAttempted = false; 16084 SimpleWindow.xRandrInfoLoadAttemped = false; 16085 win.updateActualDpi(); // trigger a reload 16086 } 16087 } 16088 16089 switch(e.type) { 16090 case EventType.SelectionClear: 16091 // writeln("SelectionClear"); 16092 if(auto win = e.xselectionclear.window in SimpleWindow.nativeMapping) { 16093 // FIXME so it is supposed to finish any in progress transfers... but idk... 16094 // writeln("SelectionClear"); 16095 } 16096 SimpleWindow.impl.setSelectionHandlers.remove(e.xselectionclear.selection); 16097 mightShortCircuitClipboard = false; 16098 break; 16099 case EventType.SelectionRequest: 16100 if(auto win = e.xselectionrequest.owner in SimpleWindow.nativeMapping) 16101 if(auto ssh = e.xselectionrequest.selection in SimpleWindow.impl.setSelectionHandlers) { 16102 // printf("SelectionRequest %s\n", XGetAtomName(e.xselectionrequest.display, e.xselectionrequest.target)); 16103 XUnlockDisplay(display); 16104 scope(exit) XLockDisplay(display); 16105 (*ssh).handleRequest(e); 16106 } 16107 break; 16108 case EventType.PropertyNotify: 16109 // import core.stdc.stdio; printf("PropertyNotify %s %d\n", XGetAtomName(e.xproperty.display, e.xproperty.atom), e.xproperty.state); 16110 16111 foreach(ssh; SimpleWindow.impl.setSelectionHandlers) { 16112 if(ssh.matchesIncr(e.xproperty.window, e.xproperty.atom) && e.xproperty.state == PropertyNotification.PropertyDelete) 16113 ssh.sendMoreIncr(&e.xproperty); 16114 } 16115 16116 16117 if(auto win = e.xproperty.window in SimpleWindow.nativeMapping) 16118 if(auto handler = e.xproperty.atom in win.getSelectionHandlers) { 16119 if(handler.matchesIncr(e.xproperty.window, e.xproperty.atom) && e.xproperty.state == PropertyNotification.PropertyNewValue) { 16120 Atom target; 16121 int format; 16122 arch_ulong bytesafter, length; 16123 void* value; 16124 16125 ubyte[] s; 16126 Atom targetToKeep; 16127 16128 XGetWindowProperty( 16129 e.xproperty.display, 16130 e.xproperty.window, 16131 e.xproperty.atom, 16132 0, 16133 100000 /* length */, 16134 true, /* erase it to signal we got it and want more */ 16135 0 /*AnyPropertyType*/, 16136 &target, &format, &length, &bytesafter, &value); 16137 16138 if(!targetToKeep) 16139 targetToKeep = target; 16140 16141 auto id = (cast(ubyte*) value)[0 .. length]; 16142 16143 handler.handleIncrData(targetToKeep, id); 16144 if(length == 0) { 16145 win.getSelectionHandlers.remove(e.xproperty.atom); 16146 } 16147 16148 XFree(value); 16149 } 16150 } 16151 break; 16152 case EventType.SelectionNotify: 16153 // import std.stdio; writefln("SelectionNotify %06x %06x", e.xselection.requestor, e.xproperty.atom); 16154 if(auto win = e.xselection.requestor in SimpleWindow.nativeMapping) 16155 if(auto handler = e.xproperty.atom in win.getSelectionHandlers) { 16156 if(e.xselection.property == None) { // || e.xselection.property == GetAtom!("NULL", true)(e.xselection.display)) { 16157 XUnlockDisplay(display); 16158 scope(exit) XLockDisplay(display); 16159 handler.handleData(None, null); 16160 win.getSelectionHandlers.remove(e.xproperty.atom); 16161 } else { 16162 Atom target; 16163 int format; 16164 arch_ulong bytesafter, length; 16165 void* value; 16166 XGetWindowProperty( 16167 e.xselection.display, 16168 e.xselection.requestor, 16169 e.xselection.property, 16170 0, 16171 100000 /* length */, 16172 //false, /* don't erase it */ 16173 true, /* do erase it lol */ 16174 0 /*AnyPropertyType*/, 16175 &target, &format, &length, &bytesafter, &value); 16176 16177 // FIXME: I don't have to copy it now since it is in char[] instead of string 16178 16179 { 16180 XUnlockDisplay(display); 16181 scope(exit) XLockDisplay(display); 16182 16183 if(target == XA_ATOM) { 16184 // initial request, see what they are able to work with and request the best one 16185 // we can handle, if available 16186 16187 Atom[] answer = (cast(Atom*) value)[0 .. length]; 16188 Atom best = handler.findBestFormat(answer); 16189 16190 /+ 16191 writeln("got ", answer); 16192 foreach(a; answer) 16193 writeln(XGetAtomName(display, a).stringz); 16194 writeln("best ", best); 16195 +/ 16196 16197 if(best != None) { 16198 // actually request the best format 16199 XConvertSelection(e.xselection.display, e.xselection.selection, best, GetAtom!("SDD_DATA", true)(display), e.xselection.requestor, 0 /*CurrentTime*/); 16200 } 16201 } else if(target == GetAtom!"INCR"(display)) { 16202 // incremental 16203 16204 handler.prepareIncremental(e.xselection.requestor, e.xselection.property); 16205 16206 // signal the sending program that we see 16207 // the incr and are ready to receive more. 16208 XDeleteProperty( 16209 e.xselection.display, 16210 e.xselection.requestor, 16211 e.xselection.property); 16212 } else { 16213 // unsupported type... maybe, forward, then we done with it 16214 if(target != None) { 16215 handler.handleData(target, cast(ubyte[]) value[0 .. length]); 16216 win.getSelectionHandlers.remove(e.xproperty.atom); 16217 } 16218 } 16219 } 16220 XFree(value); 16221 /* 16222 XDeleteProperty( 16223 e.xselection.display, 16224 e.xselection.requestor, 16225 e.xselection.property); 16226 */ 16227 } 16228 } 16229 break; 16230 case EventType.ConfigureNotify: 16231 auto event = e.xconfigure; 16232 if(auto win = event.window in SimpleWindow.nativeMapping) { 16233 if(win.windowType == WindowTypes.minimallyWrapped) 16234 break; 16235 //version(sdddd) { writeln(" w=", event.width, "; h=", event.height); } 16236 16237 /+ 16238 The ICCCM says window managers must send a synthetic event when the window 16239 is moved but NOT when it is resized. In the resize case, an event is sent 16240 with position (0, 0) which can be wrong and break the dpi calculations. 16241 16242 So we only consider the synthetic events from the WM and otherwise 16243 need to wait for some other event to get the position which... sucks. 16244 16245 I'd rather not have windows changing their layout on mouse motion after 16246 switching monitors... might be forced to but for now just ignoring it. 16247 16248 Easiest way to switch monitors without sending a size position is by 16249 maximize or fullscreen in a setup like mine, but on most setups those 16250 work on the monitor it is already living on, so it should be ok most the 16251 time. 16252 +/ 16253 if(event.send_event) { 16254 win.screenPositionKnown = true; 16255 win.screenPositionX = event.x; 16256 win.screenPositionY = event.y; 16257 win.updateActualDpi(); 16258 } 16259 16260 win.updateIMEPopupLocation(); 16261 recordX11ResizeAsync(display, *win, event.width, event.height); 16262 } 16263 break; 16264 case EventType.Expose: 16265 if(auto win = e.xexpose.window in SimpleWindow.nativeMapping) { 16266 if(win.windowType == WindowTypes.minimallyWrapped) 16267 break; 16268 // if it is closing from a popup menu, it can get 16269 // an Expose event right by the end and trigger a 16270 // BadDrawable error ... we'll just check 16271 // closed to handle that. 16272 if((*win).closed) break; 16273 if((*win).openglMode == OpenGlOptions.no) { 16274 bool doCopy = true;// e.xexpose.count == 0; // the count is better if we copy all area but meh 16275 if (win.handleExpose !is null) doCopy = !win.handleExpose(e.xexpose.x, e.xexpose.y, e.xexpose.width, e.xexpose.height, e.xexpose.count); 16276 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); 16277 } else { 16278 // need to redraw the scene somehow 16279 if(e.xexpose.count == 0) { // only do the last one since redrawOpenGlSceneNow always does it all 16280 XUnlockDisplay(display); 16281 scope(exit) XLockDisplay(display); 16282 version(without_opengl) {} else 16283 win.redrawOpenGlSceneSoon(); 16284 } 16285 } 16286 } 16287 break; 16288 case EventType.FocusIn: 16289 case EventType.FocusOut: 16290 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 16291 16292 if(auto win = e.xfocus.window in SimpleWindow.nativeMapping) { 16293 /+ 16294 16295 void info(string detail) { 16296 string s; 16297 // import std.conv; 16298 // import std.datetime; 16299 s ~= to!string(Clock.currTime); 16300 s ~= " "; 16301 s ~= e.type == EventType.FocusIn ? "in " : "out"; 16302 s ~= " "; 16303 s ~= win.windowType == WindowTypes.nestedChild ? "child " : "main "; 16304 s ~= e.xfocus.mode == NotifyModes.NotifyNormal ? " normal ": " grabbed "; 16305 s ~= detail; 16306 s ~= " "; 16307 16308 sdpyPrintDebugString(s); 16309 16310 } 16311 16312 switch(e.xfocus.detail) { 16313 case NotifyDetail.NotifyAncestor: info("Ancestor"); break; 16314 case NotifyDetail.NotifyVirtual: info("Virtual"); break; 16315 case NotifyDetail.NotifyInferior: info("Inferior"); break; 16316 case NotifyDetail.NotifyNonlinear: info("Nonlinear"); break; 16317 case NotifyDetail.NotifyNonlinearVirtual: info("nlinearvirtual"); break; 16318 case NotifyDetail.NotifyPointer: info("pointer"); break; 16319 case NotifyDetail.NotifyPointerRoot: info("pointerroot"); break; 16320 case NotifyDetail.NotifyDetailNone: info("none"); break; 16321 default: 16322 16323 } 16324 +/ 16325 16326 16327 if(e.xfocus.detail == NotifyDetail.NotifyPointer) 16328 break; // just ignore these they seem irrelevant 16329 16330 auto old = win._focused; 16331 win._focused = e.type == EventType.FocusIn; 16332 16333 // yes, we are losing the focus, but to our own child. that's actually kinda keeping it. 16334 if(e.type == EventType.FocusOut && e.xfocus.detail == NotifyDetail.NotifyInferior) 16335 win._focused = true; 16336 16337 if(win.demandingAttention) 16338 demandAttention(*win, false); 16339 16340 win.updateIMEFocused(); 16341 16342 if(old != win._focused && win.onFocusChange) { 16343 XUnlockDisplay(display); 16344 scope(exit) XLockDisplay(display); 16345 win.onFocusChange(win._focused); 16346 } 16347 } 16348 break; 16349 case EventType.VisibilityNotify: 16350 if(auto win = e.xfocus.window in SimpleWindow.nativeMapping) { 16351 if (e.xvisibility.state == VisibilityNotify.VisibilityFullyObscured) { 16352 if (win.visibilityChanged !is null) { 16353 XUnlockDisplay(display); 16354 scope(exit) XLockDisplay(display); 16355 win.visibilityChanged(false); 16356 } 16357 } else { 16358 if (win.visibilityChanged !is null) { 16359 XUnlockDisplay(display); 16360 scope(exit) XLockDisplay(display); 16361 win.visibilityChanged(true); 16362 } 16363 } 16364 } 16365 break; 16366 case EventType.ClientMessage: 16367 if (e.xclient.message_type == GetAtom!("_X11SDPY_INSMME_FLAG_EVENT_", true)(e.xany.display)) { 16368 // "ignore next mouse motion" event, increment ignore counter for teh window 16369 if (auto win = e.xclient.window in SimpleWindow.nativeMapping) { 16370 ++(*win).warpEventCount; 16371 debug(x11sdpy_warp_debug) { import core.stdc.stdio : printf; printf("X11: got \"INSMME\" message, new count=%d\n", (*win).warpEventCount); } 16372 } else { 16373 debug(x11sdpy_warp_debug) { import core.stdc.stdio : printf; printf("X11: got \"INSMME\" WTF?!!\n"); } 16374 } 16375 } else if(e.xclient.data.l[0] == GetAtom!"WM_DELETE_WINDOW"(e.xany.display)) { 16376 // user clicked the close button on the window manager 16377 if(auto win = e.xclient.window in SimpleWindow.nativeMapping) { 16378 XUnlockDisplay(display); 16379 scope(exit) XLockDisplay(display); 16380 if ((*win).closeQuery !is null) (*win).closeQuery(); else (*win).close(); 16381 } 16382 16383 } else if(e.xclient.data.l[0] == GetAtom!"WM_TAKE_FOCUS"(e.xany.display)) { 16384 // writeln("HAPPENED"); 16385 // user clicked the close button on the window manager 16386 if(auto win = e.xclient.window in SimpleWindow.nativeMapping) { 16387 XUnlockDisplay(display); 16388 scope(exit) XLockDisplay(display); 16389 16390 auto setTo = *win; 16391 16392 if(win.setRequestedInputFocus !is null) { 16393 auto s = win.setRequestedInputFocus(); 16394 if(s !is null) { 16395 setTo = s; 16396 } 16397 } 16398 16399 assert(setTo !is null); 16400 16401 // FIXME: so this is actually supposed to focus to a relevant child window if appropriate 16402 16403 XSetInputFocus(display, setTo.impl.window, RevertToParent, e.xclient.data.l[1]); 16404 } 16405 } else if(e.xclient.message_type == GetAtom!"MANAGER"(e.xany.display)) { 16406 foreach(nai; NotificationAreaIcon.activeIcons) 16407 nai.newManager(); 16408 } else if(auto win = e.xclient.window in SimpleWindow.nativeMapping) { 16409 16410 bool xDragWindow = true; 16411 if(xDragWindow && e.xclient.message_type == GetAtom!"XdndStatus"(e.xany.display)) { 16412 //XDefineCursor(display, xDragWindow.impl.window, 16413 //writeln("XdndStatus ", e.xclient.data.l); 16414 } 16415 if(auto dh = win.dropHandler) { 16416 16417 static Atom[3] xFormatsBuffer; 16418 static Atom[] xFormats; 16419 16420 void resetXFormats() { 16421 xFormatsBuffer[] = 0; 16422 xFormats = xFormatsBuffer[]; 16423 } 16424 16425 if(e.xclient.message_type == GetAtom!"XdndEnter"(e.xany.display)) { 16426 // on Windows it is supposed to return the effect you actually do FIXME 16427 16428 auto sourceWindow = e.xclient.data.l[0]; 16429 16430 xFormatsBuffer[0] = e.xclient.data.l[2]; 16431 xFormatsBuffer[1] = e.xclient.data.l[3]; 16432 xFormatsBuffer[2] = e.xclient.data.l[4]; 16433 16434 if(e.xclient.data.l[1] & 1) { 16435 // can just grab it all but like we don't necessarily need them... 16436 xFormats = cast(Atom[]) getX11PropertyData(sourceWindow, GetAtom!"XdndTypeList"(display), XA_ATOM); 16437 } else { 16438 int len; 16439 foreach(fmt; xFormatsBuffer) 16440 if(fmt) len++; 16441 xFormats = xFormatsBuffer[0 .. len]; 16442 } 16443 16444 auto pkg = DropPackage(*win, e.xclient.data.l[0], 0, xFormats); 16445 16446 dh.dragEnter(&pkg); 16447 } else if(e.xclient.message_type == GetAtom!"XdndPosition"(e.xany.display)) { 16448 16449 auto pack = e.xclient.data.l[2]; 16450 16451 auto result = dh.dragOver(Point((pack & 0xffff0000) >> 16, pack & 0xffff)); // FIXME: translate screen coordinates back to window coords 16452 16453 16454 XClientMessageEvent xclient; 16455 16456 xclient.type = EventType.ClientMessage; 16457 xclient.window = e.xclient.data.l[0]; 16458 xclient.message_type = GetAtom!"XdndStatus"(display); 16459 xclient.format = 32; 16460 xclient.data.l[0] = win.impl.window; 16461 xclient.data.l[1] = (result.action != DragAndDropAction.none) ? 1 : 0; // will accept 16462 auto r = result.consistentWithin; 16463 xclient.data.l[2] = ((cast(short) r.left) << 16) | (cast(short) r.top); 16464 xclient.data.l[3] = ((cast(short) r.width) << 16) | (cast(short) r.height); 16465 xclient.data.l[4] = dndActionAtom(e.xany.display, result.action); 16466 16467 XSendEvent( 16468 display, 16469 e.xclient.data.l[0], 16470 false, 16471 EventMask.NoEventMask, 16472 cast(XEvent*) &xclient 16473 ); 16474 16475 16476 } else if(e.xclient.message_type == GetAtom!"XdndLeave"(e.xany.display)) { 16477 //writeln("XdndLeave"); 16478 // drop cancelled. 16479 // data.l[0] is the source window 16480 dh.dragLeave(); 16481 16482 resetXFormats(); 16483 } else if(e.xclient.message_type == GetAtom!"XdndDrop"(e.xany.display)) { 16484 // drop happening, should fetch data, then send finished 16485 // writeln("XdndDrop"); 16486 16487 auto pkg = DropPackage(*win, e.xclient.data.l[0], e.xclient.data.l[2], xFormats); 16488 16489 dh.drop(&pkg); 16490 16491 resetXFormats(); 16492 } else if(e.xclient.message_type == GetAtom!"XdndFinished"(e.xany.display)) { 16493 // writeln("XdndFinished"); 16494 16495 dh.finish(); 16496 } 16497 16498 } 16499 } 16500 break; 16501 case EventType.MapNotify: 16502 if(auto win = e.xmap.window in SimpleWindow.nativeMapping) { 16503 (*win)._visible = true; 16504 if (!(*win)._visibleForTheFirstTimeCalled) { 16505 (*win)._visibleForTheFirstTimeCalled = true; 16506 if ((*win).visibleForTheFirstTime !is null) { 16507 XUnlockDisplay(display); 16508 scope(exit) XLockDisplay(display); 16509 (*win).visibleForTheFirstTime(); 16510 } 16511 } 16512 if ((*win).visibilityChanged !is null) { 16513 XUnlockDisplay(display); 16514 scope(exit) XLockDisplay(display); 16515 (*win).visibilityChanged(true); 16516 } 16517 } 16518 break; 16519 case EventType.UnmapNotify: 16520 if(auto win = e.xunmap.window in SimpleWindow.nativeMapping) { 16521 win._visible = false; 16522 if (win.visibilityChanged !is null) { 16523 XUnlockDisplay(display); 16524 scope(exit) XLockDisplay(display); 16525 win.visibilityChanged(false); 16526 } 16527 } 16528 break; 16529 case EventType.DestroyNotify: 16530 if(auto win = e.xdestroywindow.window in SimpleWindow.nativeMapping) { 16531 if(win.destroyed) 16532 break; // might get a notification both for itself and from its parent 16533 if (win.onDestroyed !is null) try { win.onDestroyed(); } catch (Exception e) {} // sorry 16534 win._closed = true; // just in case 16535 win.destroyed = true; 16536 if (win.xic !is null) { 16537 XDestroyIC(win.xic); 16538 win.xic = null; // just in case 16539 } 16540 SimpleWindow.nativeMapping.remove(e.xdestroywindow.window); 16541 bool anyImportant = false; 16542 foreach(SimpleWindow w; SimpleWindow.nativeMapping) 16543 if(w.beingOpenKeepsAppOpen) { 16544 anyImportant = true; 16545 break; 16546 } 16547 if(!anyImportant) { 16548 EventLoop.quitApplication(); 16549 done = true; 16550 } 16551 } 16552 auto window = e.xdestroywindow.window; 16553 if(window in CapableOfHandlingNativeEvent.nativeHandleMapping) 16554 CapableOfHandlingNativeEvent.nativeHandleMapping.remove(window); 16555 16556 version(with_eventloop) { 16557 if(done) exit(); 16558 } 16559 break; 16560 16561 case EventType.MotionNotify: 16562 MouseEvent mouse; 16563 auto event = e.xmotion; 16564 16565 mouse.type = MouseEventType.motion; 16566 mouse.x = event.x; 16567 mouse.y = event.y; 16568 mouse.modifierState = event.state; 16569 16570 mouse.timestamp = event.time; 16571 16572 if(auto win = e.xmotion.window in SimpleWindow.nativeMapping) { 16573 mouse.window = *win; 16574 if (win.warpEventCount > 0) { 16575 debug(x11sdpy_warp_debug) { import core.stdc.stdio : printf; printf("X11: got \"warp motion\" message, current count=%d\n", (*win).warpEventCount); } 16576 --(*win).warpEventCount; 16577 (*win).mdx(mouse); // so deltas will be correctly updated 16578 } else { 16579 win.warpEventCount = 0; // just in case 16580 (*win).mdx(mouse); 16581 if((*win).handleMouseEvent) { 16582 XUnlockDisplay(display); 16583 scope(exit) XLockDisplay(display); 16584 (*win).handleMouseEvent(mouse); 16585 } 16586 } 16587 } 16588 16589 version(with_eventloop) 16590 send(mouse); 16591 break; 16592 case EventType.ButtonPress: 16593 case EventType.ButtonRelease: 16594 MouseEvent mouse; 16595 auto event = e.xbutton; 16596 16597 mouse.timestamp = event.time; 16598 16599 mouse.type = cast(MouseEventType) (e.type == EventType.ButtonPress ? 1 : 2); 16600 mouse.x = event.x; 16601 mouse.y = event.y; 16602 16603 static Time lastMouseDownTime = 0; 16604 static int lastMouseDownButton = -1; 16605 16606 mouse.doubleClick = e.type == EventType.ButtonPress && event.button == lastMouseDownButton && (event.time - lastMouseDownTime) < mouseDoubleClickTimeout; 16607 if(e.type == EventType.ButtonPress) { 16608 lastMouseDownTime = event.time; 16609 lastMouseDownButton = event.button; 16610 } 16611 16612 switch(event.button) { 16613 case 1: mouse.button = MouseButton.left; break; // left 16614 case 2: mouse.button = MouseButton.middle; break; // middle 16615 case 3: mouse.button = MouseButton.right; break; // right 16616 case 4: mouse.button = MouseButton.wheelUp; break; // scroll up 16617 case 5: mouse.button = MouseButton.wheelDown; break; // scroll down 16618 case 6: break; // idk 16619 case 7: break; // idk 16620 case 8: mouse.button = MouseButton.backButton; break; 16621 case 9: mouse.button = MouseButton.forwardButton; break; 16622 default: 16623 } 16624 16625 // FIXME: double check this 16626 mouse.modifierState = event.state; 16627 16628 //mouse.modifierState = event.detail; 16629 16630 if(auto win = e.xbutton.window in SimpleWindow.nativeMapping) { 16631 mouse.window = *win; 16632 (*win).mdx(mouse); 16633 if((*win).handleMouseEvent) { 16634 XUnlockDisplay(display); 16635 scope(exit) XLockDisplay(display); 16636 (*win).handleMouseEvent(mouse); 16637 } 16638 } 16639 version(with_eventloop) 16640 send(mouse); 16641 break; 16642 16643 case EventType.KeyPress: 16644 case EventType.KeyRelease: 16645 //if (e.type == EventType.KeyPress) { import core.stdc.stdio : stderr, fprintf; fprintf(stderr, "X11 keyboard event!\n"); } 16646 KeyEvent ke; 16647 ke.pressed = e.type == EventType.KeyPress; 16648 ke.hardwareCode = cast(ubyte) e.xkey.keycode; 16649 16650 auto sym = XKeycodeToKeysym( 16651 XDisplayConnection.get(), 16652 e.xkey.keycode, 16653 0); 16654 16655 ke.key = cast(Key) sym;//e.xkey.keycode; 16656 16657 ke.modifierState = e.xkey.state; 16658 16659 // writefln("%x", sym); 16660 wchar_t[128] charbuf = void; // buffer for XwcLookupString; composed value can consist of many chars! 16661 int charbuflen = 0; // return value of XwcLookupString 16662 if (ke.pressed) { 16663 auto win = e.xkey.window in SimpleWindow.nativeMapping; 16664 if (win !is null && win.xic !is null) { 16665 //{ import core.stdc.stdio : printf; printf("using xic!\n"); } 16666 Status status; 16667 charbuflen = XwcLookupString(win.xic, &e.xkey, charbuf.ptr, cast(int)charbuf.length, &sym, &status); 16668 //{ import core.stdc.stdio : printf; printf("charbuflen=%d\n", charbuflen); } 16669 } else { 16670 //{ import core.stdc.stdio : printf; printf("NOT using xic!\n"); } 16671 // If XIM initialization failed, don't process intl chars. Sorry, boys and girls. 16672 char[16] buffer; 16673 auto res = XLookupString(&e.xkey, buffer.ptr, buffer.length, null, null); 16674 if (res && buffer[0] < 128) charbuf[charbuflen++] = cast(wchar_t)buffer[0]; 16675 } 16676 } 16677 16678 // if there's no char, subst one 16679 if (charbuflen == 0) { 16680 switch (sym) { 16681 case 0xff09: charbuf[charbuflen++] = '\t'; break; 16682 case 0xff8d: // keypad enter 16683 case 0xff0d: charbuf[charbuflen++] = '\n'; break; 16684 default : // ignore 16685 } 16686 } 16687 16688 if (auto win = e.xkey.window in SimpleWindow.nativeMapping) { 16689 ke.window = *win; 16690 16691 16692 if(win.inputProxy) 16693 win = &win.inputProxy; 16694 16695 // char events are separate since they are on Windows too 16696 // also, xcompose can generate long char sequences 16697 // don't send char events if Meta and/or Hyper is pressed 16698 // TODO: ctrl+char should only send control chars; not yet 16699 if ((e.xkey.state&ModifierState.ctrl) != 0) { 16700 if (charbuflen > 1 || charbuf[0] >= ' ') charbuflen = 0; 16701 } 16702 16703 dchar[32] charsComingBuffer; 16704 int charsComingPosition; 16705 dchar[] charsComing = charsComingBuffer[]; 16706 16707 if (ke.pressed && charbuflen > 0) { 16708 // FIXME: I think Windows sends these on releases... we should try to match that, but idk about repeats. 16709 foreach (immutable dchar ch; charbuf[0..charbuflen]) { 16710 if(charsComingPosition >= charsComing.length) 16711 charsComing.length = charsComingPosition + 8; 16712 16713 charsComing[charsComingPosition++] = ch; 16714 } 16715 16716 charsComing = charsComing[0 .. charsComingPosition]; 16717 } else { 16718 charsComing = null; 16719 } 16720 16721 ke.charsPossible = charsComing; 16722 16723 if (win.handleKeyEvent) { 16724 XUnlockDisplay(display); 16725 scope(exit) XLockDisplay(display); 16726 win.handleKeyEvent(ke); 16727 } 16728 16729 // Super and alt modifier keys never actually send the chars, they are assumed to be special. 16730 if ((e.xkey.state&(ModifierState.alt|ModifierState.windows)) == 0 && win.handleCharEvent) { 16731 XUnlockDisplay(display); 16732 scope(exit) XLockDisplay(display); 16733 foreach(ch; charsComing) 16734 win.handleCharEvent(ch); 16735 } 16736 } 16737 16738 version(with_eventloop) 16739 send(ke); 16740 break; 16741 default: 16742 } 16743 16744 return done; 16745 } 16746 } 16747 16748 /* *************************************** */ 16749 /* Done with simpledisplay stuff */ 16750 /* *************************************** */ 16751 16752 // Necessary C library bindings follow 16753 version(Windows) {} else 16754 version(Emscripten) {} else 16755 version(X11) { 16756 16757 extern(C) int eventfd (uint initval, int flags) nothrow @trusted @nogc; 16758 16759 // X11 bindings needed here 16760 /* 16761 A little of this is from the bindings project on 16762 D Source and some of it is copy/paste from the C 16763 header. 16764 16765 The DSource listing consistently used D's long 16766 where C used long. That's wrong - C long is 32 bit, so 16767 it should be int in D. I changed that here. 16768 16769 Note: 16770 This isn't complete, just took what I needed for myself. 16771 */ 16772 16773 import core.stdc.stddef : wchar_t; 16774 16775 interface XLib { 16776 extern(C) nothrow @nogc { 16777 char* XResourceManagerString(Display*); 16778 void XrmInitialize(); 16779 XrmDatabase XrmGetStringDatabase(char* data); 16780 bool XrmGetResource(XrmDatabase, const char*, const char*, char**, XrmValue*); 16781 16782 Cursor XCreateFontCursor(Display*, uint shape); 16783 int XDefineCursor(Display* display, Window w, Cursor cursor); 16784 int XUndefineCursor(Display* display, Window w); 16785 16786 Pixmap XCreateBitmapFromData(Display* display, Drawable d, const(char)* data, uint width, uint height); 16787 Cursor XCreatePixmapCursor(Display* display, Pixmap source, Pixmap mask, XColor* foreground_color, XColor* background_color, uint x, uint y); 16788 int XFreeCursor(Display* display, Cursor cursor); 16789 16790 int XLookupString(XKeyEvent *event_struct, char *buffer_return, int bytes_buffer, KeySym *keysym_return, void *status_in_out); 16791 16792 int XwcLookupString(XIC ic, XKeyPressedEvent* event, wchar_t* buffer_return, int wchars_buffer, KeySym* keysym_return, Status* status_return); 16793 16794 XVaNestedList XVaCreateNestedList(int unused, ...); 16795 16796 char *XKeysymToString(KeySym keysym); 16797 KeySym XKeycodeToKeysym( 16798 Display* /* display */, 16799 KeyCode /* keycode */, 16800 int /* index */ 16801 ); 16802 16803 int XConvertSelection(Display *display, Atom selection, Atom target, Atom property, Window requestor, Time time); 16804 16805 int XFree(void*); 16806 int XDeleteProperty(Display *display, Window w, Atom property); 16807 16808 int XChangeProperty(Display *display, Window w, Atom property, Atom type, int format, int mode, scope const void *data, int nelements); 16809 16810 int XGetWindowProperty(Display *display, Window w, Atom property, arch_long 16811 long_offset, arch_long long_length, Bool del, Atom req_type, Atom 16812 *actual_type_return, int *actual_format_return, arch_ulong 16813 *nitems_return, arch_ulong *bytes_after_return, void** prop_return); 16814 Atom* XListProperties(Display *display, Window w, int *num_prop_return); 16815 Status XGetTextProperty(Display *display, Window w, XTextProperty *text_prop_return, Atom property); 16816 Status XQueryTree(Display *display, Window w, Window *root_return, Window *parent_return, Window **children_return, uint *nchildren_return); 16817 16818 int XSetSelectionOwner(Display *display, Atom selection, Window owner, Time time); 16819 16820 Window XGetSelectionOwner(Display *display, Atom selection); 16821 16822 XVisualInfo* XGetVisualInfo(Display*, c_long, XVisualInfo*, int*); 16823 16824 char** XListFonts(Display*, const char*, int, int*); 16825 void XFreeFontNames(char**); 16826 16827 Display* XOpenDisplay(const char*); 16828 int XCloseDisplay(Display*); 16829 16830 int function() XSynchronize(Display*, bool); 16831 int function() XSetAfterFunction(Display*, int function() proc); 16832 16833 Bool XQueryExtension(Display*, const char*, int*, int*, int*); 16834 16835 Bool XSupportsLocale(); 16836 char* XSetLocaleModifiers(const(char)* modifier_list); 16837 XOM XOpenOM(Display* display, _XrmHashBucketRec* rdb, const(char)* res_name, const(char)* res_class); 16838 Status XCloseOM(XOM om); 16839 16840 XIM XOpenIM(Display* dpy, _XrmHashBucketRec* rdb, const(char)* res_name, const(char)* res_class); 16841 Status XCloseIM(XIM im); 16842 16843 char* XGetIMValues(XIM im, ...) /*_X_SENTINEL(0)*/; 16844 char* XSetIMValues(XIM im, ...) /*_X_SENTINEL(0)*/; 16845 Display* XDisplayOfIM(XIM im); 16846 char* XLocaleOfIM(XIM im); 16847 XIC XCreateIC(XIM im, ...) /*_X_SENTINEL(0)*/; 16848 void XDestroyIC(XIC ic); 16849 void XSetICFocus(XIC ic); 16850 void XUnsetICFocus(XIC ic); 16851 //wchar_t* XwcResetIC(XIC ic); 16852 char* XmbResetIC(XIC ic); 16853 char* Xutf8ResetIC(XIC ic); 16854 char* XSetICValues(XIC ic, ...) /*_X_SENTINEL(0)*/; 16855 char* XGetICValues(XIC ic, ...) /*_X_SENTINEL(0)*/; 16856 XIM XIMOfIC(XIC ic); 16857 16858 uint XSendEvent(Display* display, Window w, Bool propagate, arch_long event_mask, XEvent* event_send); 16859 16860 16861 XFontStruct *XLoadQueryFont(Display *display, scope const char *name); 16862 int XFreeFont(Display *display, XFontStruct *font_struct); 16863 int XSetFont(Display* display, GC gc, Font font); 16864 int XTextWidth(XFontStruct*, scope const char*, int); 16865 16866 int XSetLineAttributes(Display *display, GC gc, uint line_width, int line_style, int cap_style, int join_style); 16867 int XSetDashes(Display *display, GC gc, int dash_offset, scope const byte* dash_list, int n); 16868 16869 Window XCreateSimpleWindow( 16870 Display* /* display */, 16871 Window /* parent */, 16872 int /* x */, 16873 int /* y */, 16874 uint /* width */, 16875 uint /* height */, 16876 uint /* border_width */, 16877 uint /* border */, 16878 uint /* background */ 16879 ); 16880 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); 16881 16882 int XReparentWindow(Display*, Window, Window, int, int); 16883 int XClearWindow(Display*, Window); 16884 int XMoveResizeWindow(Display*, Window, int, int, uint, uint); 16885 int XMoveWindow(Display*, Window, int, int); 16886 int XResizeWindow(Display *display, Window w, uint width, uint height); 16887 16888 Colormap XCreateColormap(Display *display, Window w, Visual *visual, int alloc); 16889 16890 Status XMatchVisualInfo(Display *display, int screen, int depth, int class_, XVisualInfo *vinfo_return); 16891 16892 Status XGetWindowAttributes(Display*, Window, XWindowAttributes*); 16893 16894 XImage *XCreateImage( 16895 Display* /* display */, 16896 Visual* /* visual */, 16897 uint /* depth */, 16898 int /* format */, 16899 int /* offset */, 16900 ubyte* /* data */, 16901 uint /* width */, 16902 uint /* height */, 16903 int /* bitmap_pad */, 16904 int /* bytes_per_line */ 16905 ); 16906 16907 Status XInitImage (XImage* image); 16908 16909 Atom XInternAtom( 16910 Display* /* display */, 16911 const char* /* atom_name */, 16912 Bool /* only_if_exists */ 16913 ); 16914 16915 Status XInternAtoms(Display*, const char**, int, Bool, Atom*); 16916 char* XGetAtomName(Display*, Atom); 16917 Status XGetAtomNames(Display*, Atom*, int count, char**); 16918 16919 int XPutImage( 16920 Display* /* display */, 16921 Drawable /* d */, 16922 GC /* gc */, 16923 XImage* /* image */, 16924 int /* src_x */, 16925 int /* src_y */, 16926 int /* dest_x */, 16927 int /* dest_y */, 16928 uint /* width */, 16929 uint /* height */ 16930 ); 16931 16932 XImage *XGetImage(Display *display, Drawable d, int x, int y, uint width, uint height, c_ulong plane_mask, int format); 16933 16934 16935 int XDestroyWindow( 16936 Display* /* display */, 16937 Window /* w */ 16938 ); 16939 16940 int XDestroyImage(XImage*); 16941 16942 int XSelectInput( 16943 Display* /* display */, 16944 Window /* w */, 16945 EventMask /* event_mask */ 16946 ); 16947 16948 int XMapWindow( 16949 Display* /* display */, 16950 Window /* w */ 16951 ); 16952 16953 Status XIconifyWindow(Display*, Window, int); 16954 int XMapRaised(Display*, Window); 16955 int XMapSubwindows(Display*, Window); 16956 16957 int XNextEvent( 16958 Display* /* display */, 16959 XEvent* /* event_return */ 16960 ); 16961 16962 int XMaskEvent(Display*, arch_long, XEvent*); 16963 16964 Bool XFilterEvent(XEvent *event, Window window); 16965 int XRefreshKeyboardMapping(XMappingEvent *event_map); 16966 16967 Status XSetWMProtocols( 16968 Display* /* display */, 16969 Window /* w */, 16970 Atom* /* protocols */, 16971 int /* count */ 16972 ); 16973 16974 void XSetWMNormalHints(Display *display, Window w, XSizeHints *hints); 16975 Status XGetWMNormalHints(Display *display, Window w, XSizeHints *hints, c_long* supplied_return); 16976 16977 16978 Status XInitThreads(); 16979 void XLockDisplay (Display* display); 16980 void XUnlockDisplay (Display* display); 16981 16982 void XSetWMProperties(Display*, Window, XTextProperty*, XTextProperty*, char**, int, XSizeHints*, XWMHints*, XClassHint*); 16983 16984 int XSetWindowBackground (Display* display, Window w, c_ulong background_pixel); 16985 int XSetWindowBackgroundPixmap (Display* display, Window w, Pixmap background_pixmap); 16986 //int XSetWindowBorder (Display* display, Window w, c_ulong border_pixel); 16987 //int XSetWindowBorderPixmap (Display* display, Window w, Pixmap border_pixmap); 16988 //int XSetWindowBorderWidth (Display* display, Window w, uint width); 16989 16990 16991 // check out Xft too: http://www.keithp.com/~keithp/render/Xft.tutorial 16992 int XDrawString(Display*, Drawable, GC, int, int, scope const char*, int); 16993 int XDrawLine(Display*, Drawable, GC, int, int, int, int); 16994 int XDrawRectangle(Display*, Drawable, GC, int, int, uint, uint); 16995 int XDrawArc(Display*, Drawable, GC, int, int, uint, uint, int, int); 16996 int XFillRectangle(Display*, Drawable, GC, int, int, uint, uint); 16997 int XFillArc(Display*, Drawable, GC, int, int, uint, uint, int, int); 16998 int XDrawPoint(Display*, Drawable, GC, int, int); 16999 int XSetForeground(Display*, GC, uint); 17000 int XSetBackground(Display*, GC, uint); 17001 17002 XFontSet XCreateFontSet(Display*, const char*, char***, int*, char**); 17003 void XFreeFontSet(Display*, XFontSet); 17004 void Xutf8DrawString(Display*, Drawable, XFontSet, GC, int, int, scope const char*, int); 17005 void Xutf8DrawText(Display*, Drawable, GC, int, int, XmbTextItem*, int); 17006 17007 int Xutf8TextExtents(XFontSet font_set, const char *, int num_bytes, XRectangle *overall_ink_return, XRectangle *overall_logical_return); 17008 17009 17010 //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); 17011 17012 void XDrawText(Display*, Drawable, GC, int, int, XTextItem*, int); 17013 int XSetFunction(Display*, GC, int); 17014 17015 GC XCreateGC(Display*, Drawable, uint, void*); 17016 int XCopyGC(Display*, GC, uint, GC); 17017 int XFreeGC(Display*, GC); 17018 17019 bool XCheckWindowEvent(Display*, Window, int, XEvent*); 17020 bool XCheckMaskEvent(Display*, int, XEvent*); 17021 17022 int XPending(Display*); 17023 int XEventsQueued(Display* display, int mode); 17024 17025 Pixmap XCreatePixmap(Display*, Drawable, uint, uint, uint); 17026 int XFreePixmap(Display*, Pixmap); 17027 int XCopyArea(Display*, Drawable, Drawable, GC, int, int, uint, uint, int, int); 17028 int XFlush(Display*); 17029 int XBell(Display*, int); 17030 int XSync(Display*, bool); 17031 17032 int XGrabKey (Display* display, int keycode, uint modifiers, Window grab_window, Bool owner_events, int pointer_mode, int keyboard_mode); 17033 int XUngrabKey (Display* display, int keycode, uint modifiers, Window grab_window); 17034 17035 int XGrabKeyboard(Display*, Window, Bool, int, int, Time); 17036 int XUngrabKeyboard(Display*, Time); 17037 17038 KeyCode XKeysymToKeycode (Display* display, KeySym keysym); 17039 17040 KeySym XStringToKeysym(const char *string); 17041 17042 Bool XCheckTypedEvent(Display *display, int event_type, XEvent *event_return); 17043 17044 Window XDefaultRootWindow(Display*); 17045 17046 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); 17047 17048 int XUngrabButton(Display *display, uint button, uint modifiers, Window grab_window); 17049 17050 int XDrawLines(Display*, Drawable, GC, XPoint*, int, CoordMode); 17051 int XFillPolygon(Display*, Drawable, GC, XPoint*, int, PolygonShape, CoordMode); 17052 17053 Status XAllocColor(Display*, Colormap, XColor*); 17054 17055 int XWithdrawWindow(Display*, Window, int); 17056 int XUnmapWindow(Display*, Window); 17057 int XLowerWindow(Display*, Window); 17058 int XRaiseWindow(Display*, Window); 17059 17060 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); 17061 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); 17062 17063 int XGetInputFocus(Display*, Window*, int*); 17064 int XSetInputFocus(Display*, Window, int, Time); 17065 17066 XErrorHandler XSetErrorHandler(XErrorHandler); 17067 17068 int XGetErrorText(Display*, int, char*, int); 17069 17070 Bool XkbSetDetectableAutoRepeat(Display* dpy, Bool detectable, Bool* supported); 17071 17072 17073 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); 17074 int XUngrabPointer(Display *display, Time time); 17075 int XChangeActivePointerGrab(Display *display, uint event_mask, Cursor cursor, Time time); 17076 17077 int XCopyPlane(Display*, Drawable, Drawable, GC, int, int, uint, uint, int, int, arch_ulong); 17078 17079 Status XGetGeometry(Display*, Drawable, Window*, int*, int*, uint*, uint*, uint*, uint*); 17080 int XSetClipMask(Display*, GC, Pixmap); 17081 int XSetClipOrigin(Display*, GC, int, int); 17082 17083 void XSetClipRectangles(Display*, GC, int, int, XRectangle*, int, int); 17084 17085 void XSetWMName(Display*, Window, XTextProperty*); 17086 Status XGetWMName(Display*, Window, XTextProperty*); 17087 int XStoreName(Display* display, Window w, const(char)* window_name); 17088 17089 XIOErrorHandler XSetIOErrorHandler (XIOErrorHandler handler); 17090 17091 } 17092 } 17093 17094 interface Xext { 17095 extern(C) nothrow @nogc { 17096 Status XShmAttach(Display*, XShmSegmentInfo*); 17097 Status XShmDetach(Display*, XShmSegmentInfo*); 17098 Status XShmPutImage( 17099 Display* /* dpy */, 17100 Drawable /* d */, 17101 GC /* gc */, 17102 XImage* /* image */, 17103 int /* src_x */, 17104 int /* src_y */, 17105 int /* dst_x */, 17106 int /* dst_y */, 17107 uint /* src_width */, 17108 uint /* src_height */, 17109 Bool /* send_event */ 17110 ); 17111 17112 Status XShmQueryExtension(Display*); 17113 17114 XImage *XShmCreateImage( 17115 Display* /* dpy */, 17116 Visual* /* visual */, 17117 uint /* depth */, 17118 int /* format */, 17119 char* /* data */, 17120 XShmSegmentInfo* /* shminfo */, 17121 uint /* width */, 17122 uint /* height */ 17123 ); 17124 17125 Pixmap XShmCreatePixmap( 17126 Display* /* dpy */, 17127 Drawable /* d */, 17128 char* /* data */, 17129 XShmSegmentInfo* /* shminfo */, 17130 uint /* width */, 17131 uint /* height */, 17132 uint /* depth */ 17133 ); 17134 17135 } 17136 } 17137 17138 // this requires -lXpm 17139 //int XpmCreatePixmapFromData(Display*, Drawable, scope const char**, Pixmap*, Pixmap*, void*); // FIXME: void* should be XpmAttributes 17140 17141 17142 mixin DynamicLoad!(XLib, "X11", 6, librariesSuccessfullyLoaded) xlib; 17143 mixin DynamicLoad!(Xext, "Xext", 6, librariesSuccessfullyLoaded) xext; 17144 shared static this() { 17145 xlib.loadDynamicLibrary(); 17146 xext.loadDynamicLibrary(); 17147 } 17148 17149 17150 extern(C) nothrow @nogc { 17151 17152 alias XrmDatabase = void*; 17153 struct XrmValue { 17154 uint size; 17155 void* addr; 17156 } 17157 17158 struct XVisualInfo { 17159 Visual* visual; 17160 VisualID visualid; 17161 int screen; 17162 uint depth; 17163 int c_class; 17164 c_ulong red_mask; 17165 c_ulong green_mask; 17166 c_ulong blue_mask; 17167 int colormap_size; 17168 int bits_per_rgb; 17169 } 17170 17171 enum VisualNoMask= 0x0; 17172 enum VisualIDMask= 0x1; 17173 enum VisualScreenMask=0x2; 17174 enum VisualDepthMask= 0x4; 17175 enum VisualClassMask= 0x8; 17176 enum VisualRedMaskMask=0x10; 17177 enum VisualGreenMaskMask=0x20; 17178 enum VisualBlueMaskMask=0x40; 17179 enum VisualColormapSizeMask=0x80; 17180 enum VisualBitsPerRGBMask=0x100; 17181 enum VisualAllMask= 0x1FF; 17182 17183 enum AnyKey = 0; 17184 enum AnyModifier = 1 << 15; 17185 17186 // XIM and other crap 17187 struct _XOM {} 17188 struct _XIM {} 17189 struct _XIC {} 17190 alias XOM = _XOM*; 17191 alias XIM = _XIM*; 17192 alias XIC = _XIC*; 17193 17194 alias XVaNestedList = void*; 17195 17196 alias XIMStyle = arch_ulong; 17197 enum : arch_ulong { 17198 XIMPreeditArea = 0x0001, 17199 XIMPreeditCallbacks = 0x0002, 17200 XIMPreeditPosition = 0x0004, 17201 XIMPreeditNothing = 0x0008, 17202 XIMPreeditNone = 0x0010, 17203 XIMStatusArea = 0x0100, 17204 XIMStatusCallbacks = 0x0200, 17205 XIMStatusNothing = 0x0400, 17206 XIMStatusNone = 0x0800, 17207 } 17208 17209 17210 /* X Shared Memory Extension functions */ 17211 //pragma(lib, "Xshm"); 17212 alias arch_ulong ShmSeg; 17213 struct XShmSegmentInfo { 17214 ShmSeg shmseg; 17215 int shmid; 17216 ubyte* shmaddr; 17217 Bool readOnly; 17218 } 17219 17220 // and the necessary OS functions 17221 int shmget(int, size_t, int); 17222 void* shmat(int, scope const void*, int); 17223 int shmdt(scope const void*); 17224 int shmctl (int shmid, int cmd, void* ptr /*struct shmid_ds *buf*/); 17225 17226 enum IPC_PRIVATE = 0; 17227 enum IPC_CREAT = 512; 17228 enum IPC_RMID = 0; 17229 17230 /* MIT-SHM end */ 17231 17232 17233 enum MappingType:int { 17234 MappingModifier =0, 17235 MappingKeyboard =1, 17236 MappingPointer =2 17237 } 17238 17239 /* ImageFormat -- PutImage, GetImage */ 17240 enum ImageFormat:int { 17241 XYBitmap =0, /* depth 1, XYFormat */ 17242 XYPixmap =1, /* depth == drawable depth */ 17243 ZPixmap =2 /* depth == drawable depth */ 17244 } 17245 17246 enum ModifierName:int { 17247 ShiftMapIndex =0, 17248 LockMapIndex =1, 17249 ControlMapIndex =2, 17250 Mod1MapIndex =3, 17251 Mod2MapIndex =4, 17252 Mod3MapIndex =5, 17253 Mod4MapIndex =6, 17254 Mod5MapIndex =7 17255 } 17256 17257 enum ButtonMask:int { 17258 Button1Mask =1<<8, 17259 Button2Mask =1<<9, 17260 Button3Mask =1<<10, 17261 Button4Mask =1<<11, 17262 Button5Mask =1<<12, 17263 AnyModifier =1<<15/* used in GrabButton, GrabKey */ 17264 } 17265 17266 enum KeyOrButtonMask:uint { 17267 ShiftMask =1<<0, 17268 LockMask =1<<1, 17269 ControlMask =1<<2, 17270 Mod1Mask =1<<3, 17271 Mod2Mask =1<<4, 17272 Mod3Mask =1<<5, 17273 Mod4Mask =1<<6, 17274 Mod5Mask =1<<7, 17275 Button1Mask =1<<8, 17276 Button2Mask =1<<9, 17277 Button3Mask =1<<10, 17278 Button4Mask =1<<11, 17279 Button5Mask =1<<12, 17280 AnyModifier =1<<15/* used in GrabButton, GrabKey */ 17281 } 17282 17283 enum ButtonName:int { 17284 Button1 =1, 17285 Button2 =2, 17286 Button3 =3, 17287 Button4 =4, 17288 Button5 =5 17289 } 17290 17291 /* Notify modes */ 17292 enum NotifyModes:int 17293 { 17294 NotifyNormal =0, 17295 NotifyGrab =1, 17296 NotifyUngrab =2, 17297 NotifyWhileGrabbed =3 17298 } 17299 enum NotifyHint = 1; /* for MotionNotify events */ 17300 17301 /* Notify detail */ 17302 enum NotifyDetail:int 17303 { 17304 NotifyAncestor =0, 17305 NotifyVirtual =1, 17306 NotifyInferior =2, 17307 NotifyNonlinear =3, 17308 NotifyNonlinearVirtual =4, 17309 NotifyPointer =5, 17310 NotifyPointerRoot =6, 17311 NotifyDetailNone =7 17312 } 17313 17314 /* Visibility notify */ 17315 17316 enum VisibilityNotify:int 17317 { 17318 VisibilityUnobscured =0, 17319 VisibilityPartiallyObscured =1, 17320 VisibilityFullyObscured =2 17321 } 17322 17323 17324 enum WindowStackingMethod:int 17325 { 17326 Above =0, 17327 Below =1, 17328 TopIf =2, 17329 BottomIf =3, 17330 Opposite =4 17331 } 17332 17333 /* Circulation request */ 17334 enum CirculationRequest:int 17335 { 17336 PlaceOnTop =0, 17337 PlaceOnBottom =1 17338 } 17339 17340 enum PropertyNotification:int 17341 { 17342 PropertyNewValue =0, 17343 PropertyDelete =1 17344 } 17345 17346 enum ColorMapNotification:int 17347 { 17348 ColormapUninstalled =0, 17349 ColormapInstalled =1 17350 } 17351 17352 17353 struct _XPrivate {} 17354 struct _XrmHashBucketRec {} 17355 17356 alias void* XPointer; 17357 alias void* XExtData; 17358 17359 version( X86_64 ) { 17360 alias ulong XID; 17361 alias ulong arch_ulong; 17362 alias long arch_long; 17363 } else version (AArch64) { 17364 alias ulong XID; 17365 alias ulong arch_ulong; 17366 alias long arch_long; 17367 } else { 17368 alias uint XID; 17369 alias uint arch_ulong; 17370 alias int arch_long; 17371 } 17372 17373 alias XID Window; 17374 alias XID Drawable; 17375 alias XID Pixmap; 17376 17377 alias arch_ulong Atom; 17378 alias int Bool; 17379 alias Display XDisplay; 17380 17381 alias int ByteOrder; 17382 alias arch_ulong Time; 17383 alias void ScreenFormat; 17384 17385 struct XImage { 17386 int width, height; /* size of image */ 17387 int xoffset; /* number of pixels offset in X direction */ 17388 ImageFormat format; /* XYBitmap, XYPixmap, ZPixmap */ 17389 void *data; /* pointer to image data */ 17390 ByteOrder byte_order; /* data byte order, LSBFirst, MSBFirst */ 17391 int bitmap_unit; /* quant. of scanline 8, 16, 32 */ 17392 int bitmap_bit_order; /* LSBFirst, MSBFirst */ 17393 int bitmap_pad; /* 8, 16, 32 either XY or ZPixmap */ 17394 int depth; /* depth of image */ 17395 int bytes_per_line; /* accelarator to next line */ 17396 int bits_per_pixel; /* bits per pixel (ZPixmap) */ 17397 arch_ulong red_mask; /* bits in z arrangment */ 17398 arch_ulong green_mask; 17399 arch_ulong blue_mask; 17400 XPointer obdata; /* hook for the object routines to hang on */ 17401 static struct F { /* image manipulation routines */ 17402 XImage* function( 17403 XDisplay* /* display */, 17404 Visual* /* visual */, 17405 uint /* depth */, 17406 int /* format */, 17407 int /* offset */, 17408 ubyte* /* data */, 17409 uint /* width */, 17410 uint /* height */, 17411 int /* bitmap_pad */, 17412 int /* bytes_per_line */) create_image; 17413 int function(XImage *) destroy_image; 17414 arch_ulong function(XImage *, int, int) get_pixel; 17415 int function(XImage *, int, int, arch_ulong) put_pixel; 17416 XImage* function(XImage *, int, int, uint, uint) sub_image; 17417 int function(XImage *, arch_long) add_pixel; 17418 } 17419 F f; 17420 } 17421 version(X86_64) static assert(XImage.sizeof == 136); 17422 else version(X86) static assert(XImage.sizeof == 88); 17423 17424 struct XCharStruct { 17425 short lbearing; /* origin to left edge of raster */ 17426 short rbearing; /* origin to right edge of raster */ 17427 short width; /* advance to next char's origin */ 17428 short ascent; /* baseline to top edge of raster */ 17429 short descent; /* baseline to bottom edge of raster */ 17430 ushort attributes; /* per char flags (not predefined) */ 17431 } 17432 17433 /* 17434 * To allow arbitrary information with fonts, there are additional properties 17435 * returned. 17436 */ 17437 struct XFontProp { 17438 Atom name; 17439 arch_ulong card32; 17440 } 17441 17442 alias Atom Font; 17443 17444 struct XFontStruct { 17445 XExtData *ext_data; /* Hook for extension to hang data */ 17446 Font fid; /* Font ID for this font */ 17447 uint direction; /* Direction the font is painted */ 17448 uint min_char_or_byte2; /* First character */ 17449 uint max_char_or_byte2; /* Last character */ 17450 uint min_byte1; /* First row that exists (for two-byte fonts) */ 17451 uint max_byte1; /* Last row that exists (for two-byte fonts) */ 17452 Bool all_chars_exist; /* Flag if all characters have nonzero size */ 17453 uint default_char; /* Char to print for undefined character */ 17454 int n_properties; /* How many properties there are */ 17455 XFontProp *properties; /* Pointer to array of additional properties*/ 17456 XCharStruct min_bounds; /* Minimum bounds over all existing char*/ 17457 XCharStruct max_bounds; /* Maximum bounds over all existing char*/ 17458 XCharStruct *per_char; /* first_char to last_char information */ 17459 int ascent; /* Max extent above baseline for spacing */ 17460 int descent; /* Max descent below baseline for spacing */ 17461 } 17462 17463 17464 /* 17465 * Definitions of specific events. 17466 */ 17467 struct XKeyEvent 17468 { 17469 int type; /* of event */ 17470 arch_ulong serial; /* # of last request processed by server */ 17471 Bool send_event; /* true if this came from a SendEvent request */ 17472 Display *display; /* Display the event was read from */ 17473 Window window; /* "event" window it is reported relative to */ 17474 Window root; /* root window that the event occurred on */ 17475 Window subwindow; /* child window */ 17476 Time time; /* milliseconds */ 17477 int x, y; /* pointer x, y coordinates in event window */ 17478 int x_root, y_root; /* coordinates relative to root */ 17479 KeyOrButtonMask state; /* key or button mask */ 17480 uint keycode; /* detail */ 17481 Bool same_screen; /* same screen flag */ 17482 } 17483 version(X86_64) static assert(XKeyEvent.sizeof == 96); 17484 alias XKeyEvent XKeyPressedEvent; 17485 alias XKeyEvent XKeyReleasedEvent; 17486 17487 struct XButtonEvent 17488 { 17489 int type; /* of event */ 17490 arch_ulong serial; /* # of last request processed by server */ 17491 Bool send_event; /* true if this came from a SendEvent request */ 17492 Display *display; /* Display the event was read from */ 17493 Window window; /* "event" window it is reported relative to */ 17494 Window root; /* root window that the event occurred on */ 17495 Window subwindow; /* child window */ 17496 Time time; /* milliseconds */ 17497 int x, y; /* pointer x, y coordinates in event window */ 17498 int x_root, y_root; /* coordinates relative to root */ 17499 KeyOrButtonMask state; /* key or button mask */ 17500 uint button; /* detail */ 17501 Bool same_screen; /* same screen flag */ 17502 } 17503 alias XButtonEvent XButtonPressedEvent; 17504 alias XButtonEvent XButtonReleasedEvent; 17505 17506 struct XMotionEvent{ 17507 int type; /* of event */ 17508 arch_ulong serial; /* # of last request processed by server */ 17509 Bool send_event; /* true if this came from a SendEvent request */ 17510 Display *display; /* Display the event was read from */ 17511 Window window; /* "event" window reported relative to */ 17512 Window root; /* root window that the event occurred on */ 17513 Window subwindow; /* child window */ 17514 Time time; /* milliseconds */ 17515 int x, y; /* pointer x, y coordinates in event window */ 17516 int x_root, y_root; /* coordinates relative to root */ 17517 KeyOrButtonMask state; /* key or button mask */ 17518 byte is_hint; /* detail */ 17519 Bool same_screen; /* same screen flag */ 17520 } 17521 alias XMotionEvent XPointerMovedEvent; 17522 17523 struct XCrossingEvent{ 17524 int type; /* of event */ 17525 arch_ulong serial; /* # of last request processed by server */ 17526 Bool send_event; /* true if this came from a SendEvent request */ 17527 Display *display; /* Display the event was read from */ 17528 Window window; /* "event" window reported relative to */ 17529 Window root; /* root window that the event occurred on */ 17530 Window subwindow; /* child window */ 17531 Time time; /* milliseconds */ 17532 int x, y; /* pointer x, y coordinates in event window */ 17533 int x_root, y_root; /* coordinates relative to root */ 17534 NotifyModes mode; /* NotifyNormal, NotifyGrab, NotifyUngrab */ 17535 NotifyDetail detail; 17536 /* 17537 * NotifyAncestor, NotifyVirtual, NotifyInferior, 17538 * NotifyNonlinear,NotifyNonlinearVirtual 17539 */ 17540 Bool same_screen; /* same screen flag */ 17541 Bool focus; /* Boolean focus */ 17542 KeyOrButtonMask state; /* key or button mask */ 17543 } 17544 alias XCrossingEvent XEnterWindowEvent; 17545 alias XCrossingEvent XLeaveWindowEvent; 17546 17547 struct XFocusChangeEvent{ 17548 int type; /* FocusIn or FocusOut */ 17549 arch_ulong serial; /* # of last request processed by server */ 17550 Bool send_event; /* true if this came from a SendEvent request */ 17551 Display *display; /* Display the event was read from */ 17552 Window window; /* window of event */ 17553 NotifyModes mode; /* NotifyNormal, NotifyWhileGrabbed, 17554 NotifyGrab, NotifyUngrab */ 17555 NotifyDetail detail; 17556 /* 17557 * NotifyAncestor, NotifyVirtual, NotifyInferior, 17558 * NotifyNonlinear,NotifyNonlinearVirtual, NotifyPointer, 17559 * NotifyPointerRoot, NotifyDetailNone 17560 */ 17561 } 17562 alias XFocusChangeEvent XFocusInEvent; 17563 alias XFocusChangeEvent XFocusOutEvent; 17564 17565 enum CWBackPixmap = (1L<<0); 17566 enum CWBackPixel = (1L<<1); 17567 enum CWBorderPixmap = (1L<<2); 17568 enum CWBorderPixel = (1L<<3); 17569 enum CWBitGravity = (1L<<4); 17570 enum CWWinGravity = (1L<<5); 17571 enum CWBackingStore = (1L<<6); 17572 enum CWBackingPlanes = (1L<<7); 17573 enum CWBackingPixel = (1L<<8); 17574 enum CWOverrideRedirect = (1L<<9); 17575 enum CWSaveUnder = (1L<<10); 17576 enum CWEventMask = (1L<<11); 17577 enum CWDontPropagate = (1L<<12); 17578 enum CWColormap = (1L<<13); 17579 enum CWCursor = (1L<<14); 17580 17581 struct XWindowAttributes { 17582 int x, y; /* location of window */ 17583 int width, height; /* width and height of window */ 17584 int border_width; /* border width of window */ 17585 int depth; /* depth of window */ 17586 Visual *visual; /* the associated visual structure */ 17587 Window root; /* root of screen containing window */ 17588 int class_; /* InputOutput, InputOnly*/ 17589 int bit_gravity; /* one of the bit gravity values */ 17590 int win_gravity; /* one of the window gravity values */ 17591 int backing_store; /* NotUseful, WhenMapped, Always */ 17592 arch_ulong backing_planes; /* planes to be preserved if possible */ 17593 arch_ulong backing_pixel; /* value to be used when restoring planes */ 17594 Bool save_under; /* boolean, should bits under be saved? */ 17595 Colormap colormap; /* color map to be associated with window */ 17596 Bool map_installed; /* boolean, is color map currently installed*/ 17597 int map_state; /* IsUnmapped, IsUnviewable, IsViewable */ 17598 arch_long all_event_masks; /* set of events all people have interest in*/ 17599 arch_long your_event_mask; /* my event mask */ 17600 arch_long do_not_propagate_mask; /* set of events that should not propagate */ 17601 Bool override_redirect; /* boolean value for override-redirect */ 17602 Screen *screen; /* back pointer to correct screen */ 17603 } 17604 17605 enum IsUnmapped = 0; 17606 enum IsUnviewable = 1; 17607 enum IsViewable = 2; 17608 17609 struct XSetWindowAttributes { 17610 Pixmap background_pixmap;/* background, None, or ParentRelative */ 17611 arch_ulong background_pixel;/* background pixel */ 17612 Pixmap border_pixmap; /* border of the window or CopyFromParent */ 17613 arch_ulong border_pixel;/* border pixel value */ 17614 int bit_gravity; /* one of bit gravity values */ 17615 int win_gravity; /* one of the window gravity values */ 17616 int backing_store; /* NotUseful, WhenMapped, Always */ 17617 arch_ulong backing_planes;/* planes to be preserved if possible */ 17618 arch_ulong backing_pixel;/* value to use in restoring planes */ 17619 Bool save_under; /* should bits under be saved? (popups) */ 17620 arch_long event_mask; /* set of events that should be saved */ 17621 arch_long do_not_propagate_mask;/* set of events that should not propagate */ 17622 Bool override_redirect; /* boolean value for override_redirect */ 17623 Colormap colormap; /* color map to be associated with window */ 17624 Cursor cursor; /* cursor to be displayed (or None) */ 17625 } 17626 17627 17628 alias int Status; 17629 17630 17631 enum EventMask:int 17632 { 17633 NoEventMask =0, 17634 KeyPressMask =1<<0, 17635 KeyReleaseMask =1<<1, 17636 ButtonPressMask =1<<2, 17637 ButtonReleaseMask =1<<3, 17638 EnterWindowMask =1<<4, 17639 LeaveWindowMask =1<<5, 17640 PointerMotionMask =1<<6, 17641 PointerMotionHintMask =1<<7, 17642 Button1MotionMask =1<<8, 17643 Button2MotionMask =1<<9, 17644 Button3MotionMask =1<<10, 17645 Button4MotionMask =1<<11, 17646 Button5MotionMask =1<<12, 17647 ButtonMotionMask =1<<13, 17648 KeymapStateMask =1<<14, 17649 ExposureMask =1<<15, 17650 VisibilityChangeMask =1<<16, 17651 StructureNotifyMask =1<<17, 17652 ResizeRedirectMask =1<<18, 17653 SubstructureNotifyMask =1<<19, 17654 SubstructureRedirectMask=1<<20, 17655 FocusChangeMask =1<<21, 17656 PropertyChangeMask =1<<22, 17657 ColormapChangeMask =1<<23, 17658 OwnerGrabButtonMask =1<<24 17659 } 17660 17661 struct MwmHints { 17662 c_ulong flags; 17663 c_ulong functions; 17664 c_ulong decorations; 17665 c_long input_mode; 17666 c_ulong status; 17667 } 17668 17669 enum { 17670 MWM_HINTS_FUNCTIONS = (1L << 0), 17671 MWM_HINTS_DECORATIONS = (1L << 1), 17672 17673 MWM_FUNC_ALL = (1L << 0), 17674 MWM_FUNC_RESIZE = (1L << 1), 17675 MWM_FUNC_MOVE = (1L << 2), 17676 MWM_FUNC_MINIMIZE = (1L << 3), 17677 MWM_FUNC_MAXIMIZE = (1L << 4), 17678 MWM_FUNC_CLOSE = (1L << 5), 17679 17680 MWM_DECOR_ALL = (1L << 0), 17681 MWM_DECOR_BORDER = (1L << 1), 17682 MWM_DECOR_RESIZEH = (1L << 2), 17683 MWM_DECOR_TITLE = (1L << 3), 17684 MWM_DECOR_MENU = (1L << 4), 17685 MWM_DECOR_MINIMIZE = (1L << 5), 17686 MWM_DECOR_MAXIMIZE = (1L << 6), 17687 } 17688 17689 import core.stdc.config : c_long, c_ulong; 17690 17691 /* Size hints mask bits */ 17692 17693 enum USPosition = (1L << 0) /* user specified x, y */; 17694 enum USSize = (1L << 1) /* user specified width, height */; 17695 enum PPosition = (1L << 2) /* program specified position */; 17696 enum PSize = (1L << 3) /* program specified size */; 17697 enum PMinSize = (1L << 4) /* program specified minimum size */; 17698 enum PMaxSize = (1L << 5) /* program specified maximum size */; 17699 enum PResizeInc = (1L << 6) /* program specified resize increments */; 17700 enum PAspect = (1L << 7) /* program specified min and max aspect ratios */; 17701 enum PBaseSize = (1L << 8); 17702 enum PWinGravity = (1L << 9); 17703 enum PAllHints = (PPosition|PSize| PMinSize|PMaxSize| PResizeInc|PAspect); 17704 struct XSizeHints { 17705 arch_long flags; /* marks which fields in this structure are defined */ 17706 int x, y; /* Obsolete */ 17707 int width, height; /* Obsolete */ 17708 int min_width, min_height; 17709 int max_width, max_height; 17710 int width_inc, height_inc; 17711 struct Aspect { 17712 int x; /* numerator */ 17713 int y; /* denominator */ 17714 } 17715 17716 Aspect min_aspect; 17717 Aspect max_aspect; 17718 int base_width, base_height; 17719 int win_gravity; 17720 /* this structure may be extended in the future */ 17721 } 17722 17723 17724 17725 enum EventType:int 17726 { 17727 KeyPress =2, 17728 KeyRelease =3, 17729 ButtonPress =4, 17730 ButtonRelease =5, 17731 MotionNotify =6, 17732 EnterNotify =7, 17733 LeaveNotify =8, 17734 FocusIn =9, 17735 FocusOut =10, 17736 KeymapNotify =11, 17737 Expose =12, 17738 GraphicsExpose =13, 17739 NoExpose =14, 17740 VisibilityNotify =15, 17741 CreateNotify =16, 17742 DestroyNotify =17, 17743 UnmapNotify =18, 17744 MapNotify =19, 17745 MapRequest =20, 17746 ReparentNotify =21, 17747 ConfigureNotify =22, 17748 ConfigureRequest =23, 17749 GravityNotify =24, 17750 ResizeRequest =25, 17751 CirculateNotify =26, 17752 CirculateRequest =27, 17753 PropertyNotify =28, 17754 SelectionClear =29, 17755 SelectionRequest =30, 17756 SelectionNotify =31, 17757 ColormapNotify =32, 17758 ClientMessage =33, 17759 MappingNotify =34, 17760 LASTEvent =35 /* must be bigger than any event # */ 17761 } 17762 /* generated on EnterWindow and FocusIn when KeyMapState selected */ 17763 struct XKeymapEvent 17764 { 17765 int type; 17766 arch_ulong serial; /* # of last request processed by server */ 17767 Bool send_event; /* true if this came from a SendEvent request */ 17768 Display *display; /* Display the event was read from */ 17769 Window window; 17770 byte[32] key_vector; 17771 } 17772 17773 struct XExposeEvent 17774 { 17775 int type; 17776 arch_ulong serial; /* # of last request processed by server */ 17777 Bool send_event; /* true if this came from a SendEvent request */ 17778 Display *display; /* Display the event was read from */ 17779 Window window; 17780 int x, y; 17781 int width, height; 17782 int count; /* if non-zero, at least this many more */ 17783 } 17784 17785 struct XGraphicsExposeEvent{ 17786 int type; 17787 arch_ulong serial; /* # of last request processed by server */ 17788 Bool send_event; /* true if this came from a SendEvent request */ 17789 Display *display; /* Display the event was read from */ 17790 Drawable drawable; 17791 int x, y; 17792 int width, height; 17793 int count; /* if non-zero, at least this many more */ 17794 int major_code; /* core is CopyArea or CopyPlane */ 17795 int minor_code; /* not defined in the core */ 17796 } 17797 17798 struct XNoExposeEvent{ 17799 int type; 17800 arch_ulong serial; /* # of last request processed by server */ 17801 Bool send_event; /* true if this came from a SendEvent request */ 17802 Display *display; /* Display the event was read from */ 17803 Drawable drawable; 17804 int major_code; /* core is CopyArea or CopyPlane */ 17805 int minor_code; /* not defined in the core */ 17806 } 17807 17808 struct XVisibilityEvent{ 17809 int type; 17810 arch_ulong serial; /* # of last request processed by server */ 17811 Bool send_event; /* true if this came from a SendEvent request */ 17812 Display *display; /* Display the event was read from */ 17813 Window window; 17814 VisibilityNotify state; /* Visibility state */ 17815 } 17816 17817 struct XCreateWindowEvent{ 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 Window parent; /* parent of the window */ 17823 Window window; /* window id of window created */ 17824 int x, y; /* window location */ 17825 int width, height; /* size of window */ 17826 int border_width; /* border width */ 17827 Bool override_redirect; /* creation should be overridden */ 17828 } 17829 17830 struct XDestroyWindowEvent 17831 { 17832 int type; 17833 arch_ulong serial; /* # of last request processed by server */ 17834 Bool send_event; /* true if this came from a SendEvent request */ 17835 Display *display; /* Display the event was read from */ 17836 Window event; 17837 Window window; 17838 } 17839 17840 struct XUnmapEvent 17841 { 17842 int type; 17843 arch_ulong serial; /* # of last request processed by server */ 17844 Bool send_event; /* true if this came from a SendEvent request */ 17845 Display *display; /* Display the event was read from */ 17846 Window event; 17847 Window window; 17848 Bool from_configure; 17849 } 17850 17851 struct XMapEvent 17852 { 17853 int type; 17854 arch_ulong serial; /* # of last request processed by server */ 17855 Bool send_event; /* true if this came from a SendEvent request */ 17856 Display *display; /* Display the event was read from */ 17857 Window event; 17858 Window window; 17859 Bool override_redirect; /* Boolean, is override set... */ 17860 } 17861 17862 struct XMapRequestEvent 17863 { 17864 int type; 17865 arch_ulong serial; /* # of last request processed by server */ 17866 Bool send_event; /* true if this came from a SendEvent request */ 17867 Display *display; /* Display the event was read from */ 17868 Window parent; 17869 Window window; 17870 } 17871 17872 struct XReparentEvent 17873 { 17874 int type; 17875 arch_ulong serial; /* # of last request processed by server */ 17876 Bool send_event; /* true if this came from a SendEvent request */ 17877 Display *display; /* Display the event was read from */ 17878 Window event; 17879 Window window; 17880 Window parent; 17881 int x, y; 17882 Bool override_redirect; 17883 } 17884 17885 struct XConfigureEvent 17886 { 17887 int type; 17888 arch_ulong serial; /* # of last request processed by server */ 17889 Bool send_event; /* true if this came from a SendEvent request */ 17890 Display *display; /* Display the event was read from */ 17891 Window event; 17892 Window window; 17893 int x, y; 17894 int width, height; 17895 int border_width; 17896 Window above; 17897 Bool override_redirect; 17898 } 17899 17900 struct XGravityEvent 17901 { 17902 int type; 17903 arch_ulong serial; /* # of last request processed by server */ 17904 Bool send_event; /* true if this came from a SendEvent request */ 17905 Display *display; /* Display the event was read from */ 17906 Window event; 17907 Window window; 17908 int x, y; 17909 } 17910 17911 struct XResizeRequestEvent 17912 { 17913 int type; 17914 arch_ulong serial; /* # of last request processed by server */ 17915 Bool send_event; /* true if this came from a SendEvent request */ 17916 Display *display; /* Display the event was read from */ 17917 Window window; 17918 int width, height; 17919 } 17920 17921 struct XConfigureRequestEvent 17922 { 17923 int type; 17924 arch_ulong serial; /* # of last request processed by server */ 17925 Bool send_event; /* true if this came from a SendEvent request */ 17926 Display *display; /* Display the event was read from */ 17927 Window parent; 17928 Window window; 17929 int x, y; 17930 int width, height; 17931 int border_width; 17932 Window above; 17933 WindowStackingMethod detail; /* Above, Below, TopIf, BottomIf, Opposite */ 17934 arch_ulong value_mask; 17935 } 17936 17937 struct XCirculateEvent 17938 { 17939 int type; 17940 arch_ulong serial; /* # of last request processed by server */ 17941 Bool send_event; /* true if this came from a SendEvent request */ 17942 Display *display; /* Display the event was read from */ 17943 Window event; 17944 Window window; 17945 CirculationRequest place; /* PlaceOnTop, PlaceOnBottom */ 17946 } 17947 17948 struct XCirculateRequestEvent 17949 { 17950 int type; 17951 arch_ulong serial; /* # of last request processed by server */ 17952 Bool send_event; /* true if this came from a SendEvent request */ 17953 Display *display; /* Display the event was read from */ 17954 Window parent; 17955 Window window; 17956 CirculationRequest place; /* PlaceOnTop, PlaceOnBottom */ 17957 } 17958 17959 struct XPropertyEvent 17960 { 17961 int type; 17962 arch_ulong serial; /* # of last request processed by server */ 17963 Bool send_event; /* true if this came from a SendEvent request */ 17964 Display *display; /* Display the event was read from */ 17965 Window window; 17966 Atom atom; 17967 Time time; 17968 PropertyNotification state; /* NewValue, Deleted */ 17969 } 17970 17971 struct XSelectionClearEvent 17972 { 17973 int type; 17974 arch_ulong serial; /* # of last request processed by server */ 17975 Bool send_event; /* true if this came from a SendEvent request */ 17976 Display *display; /* Display the event was read from */ 17977 Window window; 17978 Atom selection; 17979 Time time; 17980 } 17981 17982 struct XSelectionRequestEvent 17983 { 17984 int type; 17985 arch_ulong serial; /* # of last request processed by server */ 17986 Bool send_event; /* true if this came from a SendEvent request */ 17987 Display *display; /* Display the event was read from */ 17988 Window owner; 17989 Window requestor; 17990 Atom selection; 17991 Atom target; 17992 Atom property; 17993 Time time; 17994 } 17995 17996 struct XSelectionEvent 17997 { 17998 int type; 17999 arch_ulong serial; /* # of last request processed by server */ 18000 Bool send_event; /* true if this came from a SendEvent request */ 18001 Display *display; /* Display the event was read from */ 18002 Window requestor; 18003 Atom selection; 18004 Atom target; 18005 Atom property; /* ATOM or None */ 18006 Time time; 18007 } 18008 version(X86_64) static assert(XSelectionClearEvent.sizeof == 56); 18009 18010 struct XColormapEvent 18011 { 18012 int type; 18013 arch_ulong serial; /* # of last request processed by server */ 18014 Bool send_event; /* true if this came from a SendEvent request */ 18015 Display *display; /* Display the event was read from */ 18016 Window window; 18017 Colormap colormap; /* COLORMAP or None */ 18018 Bool new_; /* C++ */ 18019 ColorMapNotification state; /* ColormapInstalled, ColormapUninstalled */ 18020 } 18021 version(X86_64) static assert(XColormapEvent.sizeof == 56); 18022 18023 struct XClientMessageEvent 18024 { 18025 int type; 18026 arch_ulong serial; /* # of last request processed by server */ 18027 Bool send_event; /* true if this came from a SendEvent request */ 18028 Display *display; /* Display the event was read from */ 18029 Window window; 18030 Atom message_type; 18031 int format; 18032 union Data{ 18033 byte[20] b; 18034 short[10] s; 18035 arch_ulong[5] l; 18036 } 18037 Data data; 18038 18039 } 18040 version(X86_64) static assert(XClientMessageEvent.sizeof == 96); 18041 18042 struct XMappingEvent 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; /* unused */ 18049 MappingType request; /* one of MappingModifier, MappingKeyboard, 18050 MappingPointer */ 18051 int first_keycode; /* first keycode */ 18052 int count; /* defines range of change w. first_keycode*/ 18053 } 18054 18055 struct XErrorEvent 18056 { 18057 int type; 18058 Display *display; /* Display the event was read from */ 18059 XID resourceid; /* resource id */ 18060 arch_ulong serial; /* serial number of failed request */ 18061 ubyte error_code; /* error code of failed request */ 18062 ubyte request_code; /* Major op-code of failed request */ 18063 ubyte minor_code; /* Minor op-code of failed request */ 18064 } 18065 18066 struct XAnyEvent 18067 { 18068 int type; 18069 arch_ulong serial; /* # of last request processed by server */ 18070 Bool send_event; /* true if this came from a SendEvent request */ 18071 Display *display;/* Display the event was read from */ 18072 Window window; /* window on which event was requested in event mask */ 18073 } 18074 18075 union XEvent{ 18076 int type; /* must not be changed; first element */ 18077 XAnyEvent xany; 18078 XKeyEvent xkey; 18079 XButtonEvent xbutton; 18080 XMotionEvent xmotion; 18081 XCrossingEvent xcrossing; 18082 XFocusChangeEvent xfocus; 18083 XExposeEvent xexpose; 18084 XGraphicsExposeEvent xgraphicsexpose; 18085 XNoExposeEvent xnoexpose; 18086 XVisibilityEvent xvisibility; 18087 XCreateWindowEvent xcreatewindow; 18088 XDestroyWindowEvent xdestroywindow; 18089 XUnmapEvent xunmap; 18090 XMapEvent xmap; 18091 XMapRequestEvent xmaprequest; 18092 XReparentEvent xreparent; 18093 XConfigureEvent xconfigure; 18094 XGravityEvent xgravity; 18095 XResizeRequestEvent xresizerequest; 18096 XConfigureRequestEvent xconfigurerequest; 18097 XCirculateEvent xcirculate; 18098 XCirculateRequestEvent xcirculaterequest; 18099 XPropertyEvent xproperty; 18100 XSelectionClearEvent xselectionclear; 18101 XSelectionRequestEvent xselectionrequest; 18102 XSelectionEvent xselection; 18103 XColormapEvent xcolormap; 18104 XClientMessageEvent xclient; 18105 XMappingEvent xmapping; 18106 XErrorEvent xerror; 18107 XKeymapEvent xkeymap; 18108 arch_ulong[24] pad; 18109 } 18110 18111 18112 struct Display { 18113 XExtData *ext_data; /* hook for extension to hang data */ 18114 _XPrivate *private1; 18115 int fd; /* Network socket. */ 18116 int private2; 18117 int proto_major_version;/* major version of server's X protocol */ 18118 int proto_minor_version;/* minor version of servers X protocol */ 18119 char *vendor; /* vendor of the server hardware */ 18120 XID private3; 18121 XID private4; 18122 XID private5; 18123 int private6; 18124 XID function(Display*)resource_alloc;/* allocator function */ 18125 ByteOrder byte_order; /* screen byte order, LSBFirst, MSBFirst */ 18126 int bitmap_unit; /* padding and data requirements */ 18127 int bitmap_pad; /* padding requirements on bitmaps */ 18128 ByteOrder bitmap_bit_order; /* LeastSignificant or MostSignificant */ 18129 int nformats; /* number of pixmap formats in list */ 18130 ScreenFormat *pixmap_format; /* pixmap format list */ 18131 int private8; 18132 int release; /* release of the server */ 18133 _XPrivate *private9; 18134 _XPrivate *private10; 18135 int qlen; /* Length of input event queue */ 18136 arch_ulong last_request_read; /* seq number of last event read */ 18137 arch_ulong request; /* sequence number of last request. */ 18138 XPointer private11; 18139 XPointer private12; 18140 XPointer private13; 18141 XPointer private14; 18142 uint max_request_size; /* maximum number 32 bit words in request*/ 18143 _XrmHashBucketRec *db; 18144 int function (Display*)private15; 18145 char *display_name; /* "host:display" string used on this connect*/ 18146 int default_screen; /* default screen for operations */ 18147 int nscreens; /* number of screens on this server*/ 18148 Screen *screens; /* pointer to list of screens */ 18149 arch_ulong motion_buffer; /* size of motion buffer */ 18150 arch_ulong private16; 18151 int min_keycode; /* minimum defined keycode */ 18152 int max_keycode; /* maximum defined keycode */ 18153 XPointer private17; 18154 XPointer private18; 18155 int private19; 18156 byte *xdefaults; /* contents of defaults from server */ 18157 /* there is more to this structure, but it is private to Xlib */ 18158 } 18159 18160 // I got these numbers from a C program as a sanity test 18161 version(X86_64) { 18162 static assert(Display.sizeof == 296); 18163 static assert(XPointer.sizeof == 8); 18164 static assert(XErrorEvent.sizeof == 40); 18165 static assert(XAnyEvent.sizeof == 40); 18166 static assert(XMappingEvent.sizeof == 56); 18167 static assert(XEvent.sizeof == 192); 18168 } else version (AArch64) { 18169 // omit check for aarch64 18170 } else { 18171 static assert(Display.sizeof == 176); 18172 static assert(XPointer.sizeof == 4); 18173 static assert(XEvent.sizeof == 96); 18174 } 18175 18176 struct Depth 18177 { 18178 int depth; /* this depth (Z) of the depth */ 18179 int nvisuals; /* number of Visual types at this depth */ 18180 Visual *visuals; /* list of visuals possible at this depth */ 18181 } 18182 18183 alias void* GC; 18184 alias c_ulong VisualID; 18185 alias XID Colormap; 18186 alias XID Cursor; 18187 alias XID KeySym; 18188 alias uint KeyCode; 18189 enum None = 0; 18190 } 18191 18192 version(without_opengl) {} 18193 else { 18194 extern(C) nothrow @nogc { 18195 18196 18197 static if(!SdpyIsUsingIVGLBinds) { 18198 enum GLX_USE_GL= 1; /* support GLX rendering */ 18199 enum GLX_BUFFER_SIZE= 2; /* depth of the color buffer */ 18200 enum GLX_LEVEL= 3; /* level in plane stacking */ 18201 enum GLX_RGBA= 4; /* true if RGBA mode */ 18202 enum GLX_DOUBLEBUFFER= 5; /* double buffering supported */ 18203 enum GLX_STEREO= 6; /* stereo buffering supported */ 18204 enum GLX_AUX_BUFFERS= 7; /* number of aux buffers */ 18205 enum GLX_RED_SIZE= 8; /* number of red component bits */ 18206 enum GLX_GREEN_SIZE= 9; /* number of green component bits */ 18207 enum GLX_BLUE_SIZE= 10; /* number of blue component bits */ 18208 enum GLX_ALPHA_SIZE= 11; /* number of alpha component bits */ 18209 enum GLX_DEPTH_SIZE= 12; /* number of depth bits */ 18210 enum GLX_STENCIL_SIZE= 13; /* number of stencil bits */ 18211 enum GLX_ACCUM_RED_SIZE= 14; /* number of red accum bits */ 18212 enum GLX_ACCUM_GREEN_SIZE= 15; /* number of green accum bits */ 18213 enum GLX_ACCUM_BLUE_SIZE= 16; /* number of blue accum bits */ 18214 enum GLX_ACCUM_ALPHA_SIZE= 17; /* number of alpha accum bits */ 18215 18216 18217 //XVisualInfo* glXChooseVisual(Display *dpy, int screen, in int *attrib_list); 18218 18219 18220 18221 enum GL_TRUE = 1; 18222 enum GL_FALSE = 0; 18223 } 18224 18225 alias XID GLXContextID; 18226 alias XID GLXPixmap; 18227 alias XID GLXDrawable; 18228 alias XID GLXPbuffer; 18229 alias XID GLXWindow; 18230 alias XID GLXFBConfigID; 18231 alias void* GLXContext; 18232 18233 } 18234 } 18235 18236 enum AllocNone = 0; 18237 18238 extern(C) { 18239 /* WARNING, this type not in Xlib spec */ 18240 extern(C) alias XIOErrorHandler = int function (Display* display); 18241 } 18242 18243 extern(C) nothrow 18244 alias XErrorHandler = int function(Display*, XErrorEvent*); 18245 18246 extern(C) nothrow @nogc { 18247 struct Screen{ 18248 XExtData *ext_data; /* hook for extension to hang data */ 18249 Display *display; /* back pointer to display structure */ 18250 Window root; /* Root window id. */ 18251 int width, height; /* width and height of screen */ 18252 int mwidth, mheight; /* width and height of in millimeters */ 18253 int ndepths; /* number of depths possible */ 18254 Depth *depths; /* list of allowable depths on the screen */ 18255 int root_depth; /* bits per pixel */ 18256 Visual *root_visual; /* root visual */ 18257 GC default_gc; /* GC for the root root visual */ 18258 Colormap cmap; /* default color map */ 18259 uint white_pixel; 18260 uint black_pixel; /* White and Black pixel values */ 18261 int max_maps, min_maps; /* max and min color maps */ 18262 int backing_store; /* Never, WhenMapped, Always */ 18263 bool save_unders; 18264 int root_input_mask; /* initial root input mask */ 18265 } 18266 18267 struct Visual 18268 { 18269 XExtData *ext_data; /* hook for extension to hang data */ 18270 VisualID visualid; /* visual id of this visual */ 18271 int class_; /* class of screen (monochrome, etc.) */ 18272 c_ulong red_mask, green_mask, blue_mask; /* mask values */ 18273 int bits_per_rgb; /* log base 2 of distinct color values */ 18274 int map_entries; /* color map entries */ 18275 } 18276 18277 alias Display* _XPrivDisplay; 18278 18279 extern(D) Screen* ScreenOfDisplay(Display* dpy, int scr) @system { 18280 assert(dpy !is null); 18281 return &dpy.screens[scr]; 18282 } 18283 18284 extern(D) Window RootWindow(Display *dpy,int scr) { 18285 return ScreenOfDisplay(dpy,scr).root; 18286 } 18287 18288 struct XWMHints { 18289 arch_long flags; 18290 Bool input; 18291 int initial_state; 18292 Pixmap icon_pixmap; 18293 Window icon_window; 18294 int icon_x, icon_y; 18295 Pixmap icon_mask; 18296 XID window_group; 18297 } 18298 18299 struct XClassHint { 18300 char* res_name; 18301 char* res_class; 18302 } 18303 18304 extern(D) int DefaultScreen(Display *dpy) { 18305 return dpy.default_screen; 18306 } 18307 18308 extern(D) int DefaultDepth(Display* dpy, int scr) { return ScreenOfDisplay(dpy, scr).root_depth; } 18309 extern(D) int DisplayWidth(Display* dpy, int scr) { return ScreenOfDisplay(dpy, scr).width; } 18310 extern(D) int DisplayHeight(Display* dpy, int scr) { return ScreenOfDisplay(dpy, scr).height; } 18311 extern(D) int DisplayWidthMM(Display* dpy, int scr) { return ScreenOfDisplay(dpy, scr).mwidth; } 18312 extern(D) int DisplayHeightMM(Display* dpy, int scr) { return ScreenOfDisplay(dpy, scr).mheight; } 18313 extern(D) auto DefaultColormap(Display* dpy, int scr) { return ScreenOfDisplay(dpy, scr).cmap; } 18314 18315 extern(D) int ConnectionNumber(Display* dpy) { return dpy.fd; } 18316 18317 enum int AnyPropertyType = 0; 18318 enum int Success = 0; 18319 18320 enum int RevertToNone = None; 18321 enum int PointerRoot = 1; 18322 enum Time CurrentTime = 0; 18323 enum int RevertToPointerRoot = PointerRoot; 18324 enum int RevertToParent = 2; 18325 18326 extern(D) int DefaultDepthOfDisplay(Display* dpy) { 18327 return ScreenOfDisplay(dpy, DefaultScreen(dpy)).root_depth; 18328 } 18329 18330 extern(D) Visual* DefaultVisual(Display *dpy,int scr) { 18331 return ScreenOfDisplay(dpy,scr).root_visual; 18332 } 18333 18334 extern(D) GC DefaultGC(Display *dpy,int scr) { 18335 return ScreenOfDisplay(dpy,scr).default_gc; 18336 } 18337 18338 extern(D) uint BlackPixel(Display *dpy,int scr) { 18339 return ScreenOfDisplay(dpy,scr).black_pixel; 18340 } 18341 18342 extern(D) uint WhitePixel(Display *dpy,int scr) { 18343 return ScreenOfDisplay(dpy,scr).white_pixel; 18344 } 18345 18346 alias void* XFontSet; // i think 18347 struct XmbTextItem { 18348 char* chars; 18349 int nchars; 18350 int delta; 18351 XFontSet font_set; 18352 } 18353 18354 struct XTextItem { 18355 char* chars; 18356 int nchars; 18357 int delta; 18358 Font font; 18359 } 18360 18361 enum { 18362 GXclear = 0x0, /* 0 */ 18363 GXand = 0x1, /* src AND dst */ 18364 GXandReverse = 0x2, /* src AND NOT dst */ 18365 GXcopy = 0x3, /* src */ 18366 GXandInverted = 0x4, /* NOT src AND dst */ 18367 GXnoop = 0x5, /* dst */ 18368 GXxor = 0x6, /* src XOR dst */ 18369 GXor = 0x7, /* src OR dst */ 18370 GXnor = 0x8, /* NOT src AND NOT dst */ 18371 GXequiv = 0x9, /* NOT src XOR dst */ 18372 GXinvert = 0xa, /* NOT dst */ 18373 GXorReverse = 0xb, /* src OR NOT dst */ 18374 GXcopyInverted = 0xc, /* NOT src */ 18375 GXorInverted = 0xd, /* NOT src OR dst */ 18376 GXnand = 0xe, /* NOT src OR NOT dst */ 18377 GXset = 0xf, /* 1 */ 18378 } 18379 enum QueueMode : int { 18380 QueuedAlready, 18381 QueuedAfterReading, 18382 QueuedAfterFlush 18383 } 18384 18385 enum GrabMode { GrabModeSync = 0, GrabModeAsync = 1 } 18386 18387 struct XPoint { 18388 short x; 18389 short y; 18390 } 18391 18392 enum CoordMode:int { 18393 CoordModeOrigin = 0, 18394 CoordModePrevious = 1 18395 } 18396 18397 enum PolygonShape:int { 18398 Complex = 0, 18399 Nonconvex = 1, 18400 Convex = 2 18401 } 18402 18403 struct XTextProperty { 18404 const(char)* value; /* same as Property routines */ 18405 Atom encoding; /* prop type */ 18406 int format; /* prop data format: 8, 16, or 32 */ 18407 arch_ulong nitems; /* number of data items in value */ 18408 } 18409 18410 version( X86_64 ) { 18411 static assert(XTextProperty.sizeof == 32); 18412 } 18413 18414 18415 struct XGCValues { 18416 int function_; /* logical operation */ 18417 arch_ulong plane_mask;/* plane mask */ 18418 arch_ulong foreground;/* foreground pixel */ 18419 arch_ulong background;/* background pixel */ 18420 int line_width; /* line width */ 18421 int line_style; /* LineSolid, LineOnOffDash, LineDoubleDash */ 18422 int cap_style; /* CapNotLast, CapButt, 18423 CapRound, CapProjecting */ 18424 int join_style; /* JoinMiter, JoinRound, JoinBevel */ 18425 int fill_style; /* FillSolid, FillTiled, 18426 FillStippled, FillOpaeueStippled */ 18427 int fill_rule; /* EvenOddRule, WindingRule */ 18428 int arc_mode; /* ArcChord, ArcPieSlice */ 18429 Pixmap tile; /* tile pixmap for tiling operations */ 18430 Pixmap stipple; /* stipple 1 plane pixmap for stipping */ 18431 int ts_x_origin; /* offset for tile or stipple operations */ 18432 int ts_y_origin; 18433 Font font; /* default text font for text operations */ 18434 int subwindow_mode; /* ClipByChildren, IncludeInferiors */ 18435 Bool graphics_exposures;/* boolean, should exposures be generated */ 18436 int clip_x_origin; /* origin for clipping */ 18437 int clip_y_origin; 18438 Pixmap clip_mask; /* bitmap clipping; other calls for rects */ 18439 int dash_offset; /* patterned/dashed line information */ 18440 char dashes; 18441 } 18442 18443 struct XColor { 18444 arch_ulong pixel; 18445 ushort red, green, blue; 18446 byte flags; 18447 byte pad; 18448 } 18449 18450 struct XRectangle { 18451 short x; 18452 short y; 18453 ushort width; 18454 ushort height; 18455 } 18456 18457 enum ClipByChildren = 0; 18458 enum IncludeInferiors = 1; 18459 18460 enum Atom XA_PRIMARY = 1; 18461 enum Atom XA_SECONDARY = 2; 18462 enum Atom XA_STRING = 31; 18463 enum Atom XA_CARDINAL = 6; 18464 enum Atom XA_WM_NAME = 39; 18465 enum Atom XA_ATOM = 4; 18466 enum Atom XA_WINDOW = 33; 18467 enum Atom XA_WM_HINTS = 35; 18468 enum int PropModeAppend = 2; 18469 enum int PropModeReplace = 0; 18470 enum int PropModePrepend = 1; 18471 18472 enum int CopyFromParent = 0; 18473 enum int InputOutput = 1; 18474 18475 // XWMHints 18476 enum InputHint = 1 << 0; 18477 enum StateHint = 1 << 1; 18478 enum IconPixmapHint = (1L << 2); 18479 enum IconWindowHint = (1L << 3); 18480 enum IconPositionHint = (1L << 4); 18481 enum IconMaskHint = (1L << 5); 18482 enum WindowGroupHint = (1L << 6); 18483 enum AllHints = (InputHint|StateHint|IconPixmapHint|IconWindowHint|IconPositionHint|IconMaskHint|WindowGroupHint); 18484 enum XUrgencyHint = (1L << 8); 18485 18486 // GC Components 18487 enum GCFunction = (1L<<0); 18488 enum GCPlaneMask = (1L<<1); 18489 enum GCForeground = (1L<<2); 18490 enum GCBackground = (1L<<3); 18491 enum GCLineWidth = (1L<<4); 18492 enum GCLineStyle = (1L<<5); 18493 enum GCCapStyle = (1L<<6); 18494 enum GCJoinStyle = (1L<<7); 18495 enum GCFillStyle = (1L<<8); 18496 enum GCFillRule = (1L<<9); 18497 enum GCTile = (1L<<10); 18498 enum GCStipple = (1L<<11); 18499 enum GCTileStipXOrigin = (1L<<12); 18500 enum GCTileStipYOrigin = (1L<<13); 18501 enum GCFont = (1L<<14); 18502 enum GCSubwindowMode = (1L<<15); 18503 enum GCGraphicsExposures= (1L<<16); 18504 enum GCClipXOrigin = (1L<<17); 18505 enum GCClipYOrigin = (1L<<18); 18506 enum GCClipMask = (1L<<19); 18507 enum GCDashOffset = (1L<<20); 18508 enum GCDashList = (1L<<21); 18509 enum GCArcMode = (1L<<22); 18510 enum GCLastBit = 22; 18511 18512 18513 enum int WithdrawnState = 0; 18514 enum int NormalState = 1; 18515 enum int IconicState = 3; 18516 18517 } 18518 } else version (OSXCocoa) { 18519 18520 /+ 18521 DON'T FORGET TO MARK THE CLASSES `extern`!! can cause "unrecognized selector sent to class" errors if you do. 18522 +/ 18523 18524 private __gshared AppDelegate globalAppDelegate; 18525 18526 extern(Objective-C) 18527 class AppDelegate : NSObject, NSApplicationDelegate { 18528 override static AppDelegate alloc() @selector("alloc"); 18529 18530 18531 void sdpyCustomEventWakeup(NSid arg) @selector("sdpyCustomEventWakeup:") { 18532 SimpleWindow.processAllCustomEvents(); 18533 } 18534 18535 override void applicationWillFinishLaunching(NSNotification notification) @selector("applicationWillFinishLaunching:") { 18536 immutable style = NSWindowStyleMask.resizable | 18537 NSWindowStyleMask.closable | 18538 NSWindowStyleMask.miniaturizable | 18539 NSWindowStyleMask.titled; 18540 18541 NSMenu mainMenu = NSMenu.alloc.init(MacString("Main").borrow); 18542 18543 { 18544 auto item = mainMenu.addItem(MacString("Test").borrow, null, MacString("").borrow); 18545 auto menu = NSMenu.alloc.init(MacString("Test2").borrow); 18546 mainMenu.setSubmenu(menu, item); 18547 18548 auto newItem = menu.addItem(MacString("Quit").borrow, sel_registerName("terminate:"), MacString("q").borrow); 18549 newItem.target = NSApp; 18550 auto newItem2 = menu.addItem(MacString("Disabled").borrow, sel_registerName("doesnotexist:"), MacString("x").borrow); 18551 newItem2.target = NSApp; 18552 } 18553 18554 { 18555 auto item = mainMenu.addItem(MacString("Test3").borrow, null, MacString("").borrow); 18556 auto menu = NSMenu.alloc.init(MacString("Test4").borrow); // this is the title actually used 18557 mainMenu.setSubmenu(menu, item); 18558 18559 auto newItem = menu.addItem(MacString("Quit2").borrow, sel_registerName("stop:"), MacString("s").borrow); 18560 menu.addItem(MacString("Pulse").borrow, sel_registerName("simpledisplay_pulse:"), MacString("p").borrow); 18561 } 18562 18563 18564 NSApp.menu = mainMenu; 18565 18566 18567 // auto controller = ViewController.alloc.init; 18568 18569 // auto timer = NSTimer.schedule(1.0, cast(NSid) view, sel_registerName("simpledisplay_pulse:"), null, true); 18570 18571 /+ 18572 this.window = window; 18573 this.controller = controller; 18574 +/ 18575 } 18576 18577 override void applicationDidFinishLaunching(NSNotification notification) @selector("applicationDidFinishLaunching:") { 18578 NSApplication.shared_.activateIgnoringOtherApps(true); 18579 } 18580 override bool applicationShouldTerminateAfterLastWindowClosed(NSNotification notification) @selector("applicationShouldTerminateAfterLastWindowClosed:") { 18581 return true; 18582 } 18583 } 18584 18585 extern(Objective-C) 18586 class SDWindowDelegate : NSObject, NSWindowDelegate { 18587 override static SDWindowDelegate alloc() @selector("alloc"); 18588 override SDWindowDelegate init() @selector("init"); 18589 18590 SimpleWindow simpleWindow; 18591 18592 override void windowWillClose(NSNotification notification) @selector("windowWillClose:") { 18593 auto window = cast(void*) notification.object; 18594 18595 // FIXME: do i need to release it? 18596 SimpleWindow.nativeMapping.remove(window); 18597 } 18598 18599 override NSSize windowWillResize(NSWindow sender, NSSize frameSize) @selector("windowWillResize:toSize:") { 18600 if(simpleWindow.windowResized) { 18601 // FIXME: automaticallyScaleIfPossible behaviors 18602 18603 simpleWindow._width = cast(int) frameSize.width; 18604 simpleWindow._height = cast(int) frameSize.height; 18605 18606 simpleWindow.view.setFrameSize(frameSize); 18607 18608 /+ 18609 auto size = simpleWindow.view.frame.size; 18610 writeln(cast(int) size.width, "x", cast(int) size.height); 18611 +/ 18612 18613 simpleWindow.createNewDrawingContext(simpleWindow._width, simpleWindow._height); 18614 18615 simpleWindow.windowResized(simpleWindow._width, simpleWindow._height); 18616 18617 // simpleWindow.view.setNeedsDisplay(true); 18618 } 18619 18620 return frameSize; 18621 } 18622 18623 /+ 18624 override void windowDidResize(NSNotification notification) @selector("windowDidResize:") { 18625 if(simpleWindow.windowResized) { 18626 auto window = simpleWindow.window; 18627 auto rect = window.contentRectForFrameRect(window.frame); 18628 import std.stdio; writeln(window.frame.size); 18629 simpleWindow.windowResized(cast(int) rect.size.width, cast(int) rect.size.height); 18630 } 18631 } 18632 +/ 18633 } 18634 18635 extern(Objective-C) 18636 class SDGraphicsView : NSView { 18637 SimpleWindow simpleWindow; 18638 18639 override static SDGraphicsView alloc() @selector("alloc"); 18640 override SDGraphicsView init() @selector("init") { 18641 super.init(); 18642 return this; 18643 } 18644 18645 override void drawRect(NSRect rect) @selector("drawRect:") { 18646 auto curCtx = NSGraphicsContext.currentContext.graphicsPort; 18647 auto cgImage = CGBitmapContextCreateImage(simpleWindow.drawingContext); 18648 auto size = CGSize(CGBitmapContextGetWidth(simpleWindow.drawingContext), CGBitmapContextGetHeight(simpleWindow.drawingContext)); 18649 CGContextDrawImage(curCtx, CGRect(CGPoint(0, 0), size), cgImage); 18650 CGImageRelease(cgImage); 18651 } 18652 18653 extern(D) 18654 private void mouseHelper(NSEvent event, MouseEventType type, MouseButton button) { 18655 MouseEvent me; 18656 me.type = type; 18657 18658 auto pos = event.locationInWindow; 18659 18660 me.x = cast(int) pos.x; 18661 me.y = cast(int) (simpleWindow.height - pos.y); 18662 18663 me.dx = 0; // FIXME 18664 me.dy = 0; // FIXME 18665 18666 me.button = button; 18667 me.modifierState = cast(uint) event.modifierFlags; 18668 me.window = simpleWindow; 18669 18670 me.doubleClick = false; 18671 18672 if(simpleWindow && simpleWindow.handleMouseEvent) 18673 simpleWindow.handleMouseEvent(me); 18674 } 18675 18676 override void mouseDown(NSEvent event) @selector("mouseDown:") { 18677 // writeln(event.pressedMouseButtons); 18678 18679 mouseHelper(event, MouseEventType.buttonPressed, MouseButton.left); 18680 } 18681 override void mouseDragged(NSEvent event) @selector("mouseDragged:") { 18682 mouseHelper(event, MouseEventType.motion, MouseButton.left); 18683 } 18684 override void mouseUp(NSEvent event) @selector("mouseUp:") { 18685 mouseHelper(event, MouseEventType.buttonReleased, MouseButton.left); 18686 } 18687 override void mouseMoved(NSEvent event) @selector("mouseMoved:") { 18688 mouseHelper(event, MouseEventType.motion, MouseButton.left); // button wrong prolly 18689 } 18690 /+ 18691 // FIXME 18692 override void mouseEntered(NSEvent event) @selector("mouseEntered:") { 18693 mouseHelper(event, MouseEventType.buttonPressed, MouseButton.left); 18694 } 18695 override void mouseExited(NSEvent event) @selector("mouseExited:") { 18696 mouseHelper(event, MouseEventType.buttonPressed, MouseButton.left); 18697 } 18698 +/ 18699 18700 override void rightMouseDown(NSEvent event) @selector("rightMouseDown:") { 18701 mouseHelper(event, MouseEventType.buttonPressed, MouseButton.right); 18702 } 18703 override void rightMouseDragged(NSEvent event) @selector("rightMouseDragged:") { 18704 mouseHelper(event, MouseEventType.motion, MouseButton.right); 18705 } 18706 override void rightMouseUp(NSEvent event) @selector("rightMouseUp:") { 18707 mouseHelper(event, MouseEventType.buttonReleased, MouseButton.right); 18708 } 18709 18710 override void otherMouseDown(NSEvent event) @selector("otherMouseDown:") { 18711 mouseHelper(event, MouseEventType.buttonPressed, MouseButton.middle); 18712 } 18713 override void otherMouseDragged(NSEvent event) @selector("otherMouseDragged:") { 18714 mouseHelper(event, MouseEventType.motion, MouseButton.middle); 18715 } 18716 override void otherMouseUp(NSEvent event) @selector("otherMouseUp:") { 18717 mouseHelper(event, MouseEventType.buttonReleased, MouseButton.middle); 18718 } 18719 18720 override void scrollWheel(NSEvent event) @selector("scrollWheel:") { 18721 // import std.stdio; writeln(event.deltaY); 18722 } 18723 18724 override void keyDown(NSEvent event) @selector("keyDown:") { 18725 // the event may have multiple characters, and we send them all at once. 18726 if (simpleWindow.handleCharEvent) { 18727 auto chars = DeifiedNSString(event.characters); 18728 foreach (dchar dc; chars.str) 18729 simpleWindow.handleCharEvent(dc); 18730 } 18731 18732 keyHelper(event, true); 18733 } 18734 18735 override void keyUp(NSEvent event) @selector("keyUp:") { 18736 keyHelper(event, false); 18737 } 18738 18739 extern(D) 18740 private void keyHelper(NSEvent event, bool pressed) { 18741 if(simpleWindow.handleKeyEvent) { 18742 KeyEvent ev; 18743 ev.key = cast(Key) event.keyCode;// (event.specialKey ? event.specialKey : event.keyCode); 18744 ev.pressed = pressed; 18745 ev.hardwareCode = cast(ubyte) event.keyCode; 18746 ev.modifierState = cast(uint) event.modifierFlags; 18747 ev.window = simpleWindow; 18748 18749 simpleWindow.handleKeyEvent(ev); 18750 } 18751 } 18752 18753 override bool isFlipped() @selector("isFlipped") { 18754 return true; 18755 } 18756 override bool acceptsFirstResponder() @selector("acceptsFirstResponder") { 18757 return true; 18758 } 18759 18760 void simpledisplay_pulse(NSTimer timer) @selector("simpledisplay_pulse:") { 18761 if(simpleWindow && simpleWindow.handlePulse) 18762 simpleWindow.handlePulse(); 18763 /+ 18764 setNeedsDisplay = true; 18765 +/ 18766 } 18767 } 18768 18769 private: 18770 alias const(void)* CFStringRef; 18771 alias const(void)* CFAllocatorRef; 18772 alias const(void)* CFTypeRef; 18773 alias const(void)* CGColorSpaceRef; 18774 alias const(void)* CGImageRef; 18775 alias ulong CGBitmapInfo; 18776 alias NSGraphicsContext CGContextRef; 18777 18778 alias NSPoint CGPoint; 18779 alias NSSize CGSize; 18780 alias NSRect CGRect; 18781 18782 struct CGAffineTransform { 18783 double a, b, c, d, tx, ty; 18784 } 18785 18786 enum NSApplicationActivationPolicyRegular = 0; 18787 enum NSBackingStoreBuffered = 2; 18788 enum kCFStringEncodingUTF8 = 0x08000100; 18789 18790 enum : size_t { 18791 NSBorderlessWindowMask = 0, 18792 NSTitledWindowMask = 1 << 0, 18793 NSClosableWindowMask = 1 << 1, 18794 NSMiniaturizableWindowMask = 1 << 2, 18795 NSResizableWindowMask = 1 << 3, 18796 NSTexturedBackgroundWindowMask = 1 << 8 18797 } 18798 18799 enum : ulong { 18800 kCGImageAlphaNone, 18801 kCGImageAlphaPremultipliedLast, 18802 kCGImageAlphaPremultipliedFirst, 18803 kCGImageAlphaLast, 18804 kCGImageAlphaFirst, 18805 kCGImageAlphaNoneSkipLast, 18806 kCGImageAlphaNoneSkipFirst 18807 } 18808 enum : ulong { 18809 kCGBitmapAlphaInfoMask = 0x1F, 18810 kCGBitmapFloatComponents = (1 << 8), 18811 kCGBitmapByteOrderMask = 0x7000, 18812 kCGBitmapByteOrderDefault = (0 << 12), 18813 kCGBitmapByteOrder16Little = (1 << 12), 18814 kCGBitmapByteOrder32Little = (2 << 12), 18815 kCGBitmapByteOrder16Big = (3 << 12), 18816 kCGBitmapByteOrder32Big = (4 << 12) 18817 } 18818 enum CGPathDrawingMode { 18819 kCGPathFill, 18820 kCGPathEOFill, 18821 kCGPathStroke, 18822 kCGPathFillStroke, 18823 kCGPathEOFillStroke 18824 } 18825 enum objc_AssociationPolicy : size_t { 18826 OBJC_ASSOCIATION_ASSIGN = 0, 18827 OBJC_ASSOCIATION_RETAIN_NONATOMIC = 1, 18828 OBJC_ASSOCIATION_COPY_NONATOMIC = 3, 18829 OBJC_ASSOCIATION_RETAIN = 0x301, //01401, 18830 OBJC_ASSOCIATION_COPY = 0x303 //01403 18831 } 18832 18833 extern(C) { 18834 CGContextRef CGBitmapContextCreate(void* data, size_t width, size_t height, size_t bitsPerComponent, size_t bytesPerRow, CGColorSpaceRef colorspace, CGBitmapInfo bitmapInfo); 18835 void CGContextRelease(CGContextRef c); 18836 ubyte* CGBitmapContextGetData(CGContextRef c); 18837 CGImageRef CGBitmapContextCreateImage(CGContextRef c); 18838 size_t CGBitmapContextGetWidth(CGContextRef c); 18839 size_t CGBitmapContextGetHeight(CGContextRef c); 18840 18841 CGColorSpaceRef CGColorSpaceCreateDeviceRGB(); 18842 void CGColorSpaceRelease(CGColorSpaceRef cs); 18843 18844 void CGContextSetRGBStrokeColor(CGContextRef c, double red, double green, double blue, double alpha); 18845 void CGContextSetRGBFillColor(CGContextRef c, double red, double green, double blue, double alpha); 18846 void CGContextDrawImage(CGContextRef c, CGRect rect, CGImageRef image); 18847 void CGContextShowTextAtPoint(CGContextRef c, double x, double y, const(char)* str, size_t length); 18848 void CGContextStrokeLineSegments(CGContextRef c, const(CGPoint)* points, size_t count); 18849 void CGContextSetLineDash(CGContextRef c, CGFloat phase, const CGFloat *lengths, size_t count); 18850 18851 void CGContextBeginPath(CGContextRef c); 18852 void CGContextDrawPath(CGContextRef c, CGPathDrawingMode mode); 18853 void CGContextAddEllipseInRect(CGContextRef c, CGRect rect); 18854 void CGContextAddArc(CGContextRef c, double x, double y, double radius, double startAngle, double endAngle, long clockwise); 18855 void CGContextAddRect(CGContextRef c, CGRect rect); 18856 void CGContextAddLines(CGContextRef c, const(CGPoint)* points, size_t count); 18857 void CGContextSaveGState(CGContextRef c); 18858 void CGContextRestoreGState(CGContextRef c); 18859 void CGContextSelectFont(CGContextRef c, const(char)* name, double size, ulong textEncoding); 18860 CGAffineTransform CGContextGetTextMatrix(CGContextRef c); 18861 void CGContextSetTextMatrix(CGContextRef c, CGAffineTransform t); 18862 18863 void CGImageRelease(CGImageRef image); 18864 } 18865 } else static assert(0, "Unsupported operating system"); 18866 18867 18868 version(OSXCocoa) { 18869 // I don't know anything about the Mac, but a couple years ago, KennyTM on the newsgroup wrote this for me 18870 // 18871 // http://forum.dlang.org/thread/innr0v$1deh$1@digitalmars.com?page=4#post-int88l:24uaf:241:40digitalmars.com 18872 // https://github.com/kennytm/simpledisplay.d/blob/osx/simpledisplay.d 18873 // 18874 // and it is about time I merged it in here. It is available with -version=OSXCocoa until someone tests it for me! 18875 // Probably won't even fully compile right now 18876 18877 private enum double PI = 3.14159265358979323; 18878 18879 alias NSWindow NativeWindowHandle; 18880 alias void delegate(NSid) NativeEventHandler; 18881 18882 enum KEY_ESCAPE = 27; 18883 18884 mixin template NativeImageImplementation() { 18885 CGContextRef context; 18886 ubyte* rawData; 18887 18888 final: 18889 18890 void convertToRgbaBytes(ubyte[] where) @system { 18891 assert(where.length == this.width * this.height * 4); 18892 18893 // if rawData had a length.... 18894 //assert(rawData.length == where.length); 18895 for(long idx = 0; idx < where.length; idx += 4) { 18896 auto alpha = rawData[idx + 3]; 18897 if(alpha == 255) { 18898 where[idx + 0] = rawData[idx + 0]; // r 18899 where[idx + 1] = rawData[idx + 1]; // g 18900 where[idx + 2] = rawData[idx + 2]; // b 18901 where[idx + 3] = rawData[idx + 3]; // a 18902 } else { 18903 where[idx + 0] = cast(ubyte)(rawData[idx + 0] * 255 / alpha); // r 18904 where[idx + 1] = cast(ubyte)(rawData[idx + 1] * 255 / alpha); // g 18905 where[idx + 2] = cast(ubyte)(rawData[idx + 2] * 255 / alpha); // b 18906 where[idx + 3] = rawData[idx + 3]; // a 18907 18908 } 18909 } 18910 } 18911 18912 void setFromRgbaBytes(in ubyte[] where) @system { 18913 // FIXME: this is probably wrong 18914 assert(where.length == this.width * this.height * 4); 18915 18916 // if rawData had a length.... 18917 //assert(rawData.length == where.length); 18918 for(long idx = 0; idx < where.length; idx += 4) { 18919 auto alpha = where[idx + 3]; 18920 if(alpha == 255) { 18921 rawData[idx + 0] = where[idx + 0]; // r 18922 rawData[idx + 1] = where[idx + 1]; // g 18923 rawData[idx + 2] = where[idx + 2]; // b 18924 rawData[idx + 3] = where[idx + 3]; // a 18925 } else if(alpha == 0) { 18926 rawData[idx + 0] = 0; 18927 rawData[idx + 1] = 0; 18928 rawData[idx + 2] = 0; 18929 rawData[idx + 3] = 0; 18930 } else { 18931 rawData[idx + 0] = cast(ubyte)(where[idx + 0] * 255 / alpha); // r 18932 rawData[idx + 1] = cast(ubyte)(where[idx + 1] * 255 / alpha); // g 18933 rawData[idx + 2] = cast(ubyte)(where[idx + 2] * 255 / alpha); // b 18934 rawData[idx + 3] = where[idx + 3]; // a 18935 } 18936 } 18937 } 18938 18939 18940 void createImage(int width, int height, bool forcexshm=false, bool ignored = false) { 18941 auto colorSpace = CGColorSpaceCreateDeviceRGB(); 18942 context = CGBitmapContextCreate(null, width, height, 8, 4*width, colorSpace, kCGImageAlphaPremultipliedLast |kCGBitmapByteOrder32Big); 18943 CGColorSpaceRelease(colorSpace); 18944 rawData = CGBitmapContextGetData(context); 18945 } 18946 void dispose() { 18947 CGContextRelease(context); 18948 } 18949 18950 void setPixel(int x, int y, Color c) @system { 18951 auto offset = (y * width + x) * 4; 18952 if (c.a == 255) { 18953 rawData[offset + 0] = c.r; 18954 rawData[offset + 1] = c.g; 18955 rawData[offset + 2] = c.b; 18956 rawData[offset + 3] = c.a; 18957 } else { 18958 rawData[offset + 0] = cast(ubyte)(c.r*c.a/255); 18959 rawData[offset + 1] = cast(ubyte)(c.g*c.a/255); 18960 rawData[offset + 2] = cast(ubyte)(c.b*c.a/255); 18961 rawData[offset + 3] = c.a; 18962 } 18963 } 18964 } 18965 18966 mixin template NativeScreenPainterImplementation() { 18967 CGContextRef context; 18968 ubyte[4] _outlineComponents; 18969 NSView view; 18970 18971 Pen _activePen; 18972 Color _fillColor; 18973 Rectangle _clipRectangle; 18974 OperatingSystemFont _font; 18975 18976 OperatingSystemFont getFont() { 18977 if(_font is null) { 18978 static OperatingSystemFont _defaultFont; 18979 if(_defaultFont is null) { 18980 _defaultFont = new OperatingSystemFont(); 18981 _defaultFont.loadDefault(); 18982 } 18983 _font = _defaultFont; 18984 } 18985 18986 return _font; 18987 } 18988 18989 void create(PaintingHandle window) { 18990 // this.destiny = window; 18991 if(auto sw = cast(SimpleWindow) this.window) { 18992 context = sw.drawingContext; 18993 view = sw.view; 18994 } else { 18995 throw new NotYetImplementedException(); 18996 } 18997 } 18998 18999 void dispose() { 19000 view.setNeedsDisplay(true); 19001 } 19002 19003 bool manualInvalidations; 19004 void invalidateRect(Rectangle invalidRect) { } 19005 19006 // NotYetImplementedException 19007 void rasterOp(RasterOp op) { 19008 } 19009 void setClipRectangle(int, int, int, int) { 19010 } 19011 Size textSize(in char[] txt) { 19012 auto font = getFont(); 19013 return Size(font.stringWidth(txt), font.height()); 19014 } 19015 19016 void setFont(OperatingSystemFont font) { 19017 _font = font; 19018 //font.font.setInContext(context); 19019 } 19020 int fontHeight() { 19021 auto font = getFont(); 19022 return font.height; 19023 } 19024 19025 // end 19026 19027 void pen(Pen pen) { 19028 _activePen = pen; 19029 auto color = pen.color; // FIXME 19030 double alphaComponent = color.a/255.0f; 19031 CGContextSetRGBStrokeColor(context, color.r/255.0f, color.g/255.0f, color.b/255.0f, alphaComponent); 19032 19033 double[2] patternBuffer; 19034 double[] pattern; 19035 final switch(pen.style) { 19036 case Pen.Style.Solid: 19037 pattern = null; 19038 break; 19039 case Pen.Style.Dashed: 19040 patternBuffer[0] = 4; 19041 patternBuffer[1] = 1; 19042 pattern = patternBuffer[]; 19043 break; 19044 case Pen.Style.Dotted: 19045 patternBuffer[0] = 1; 19046 patternBuffer[1] = 1; 19047 pattern = patternBuffer[]; 19048 break; 19049 } 19050 19051 CGContextSetLineDash(context, 0, pattern.ptr, pattern.length); 19052 19053 if (color.a != 255) { 19054 _outlineComponents[0] = cast(ubyte)(color.r*color.a/255); 19055 _outlineComponents[1] = cast(ubyte)(color.g*color.a/255); 19056 _outlineComponents[2] = cast(ubyte)(color.b*color.a/255); 19057 _outlineComponents[3] = color.a; 19058 } else { 19059 _outlineComponents[0] = color.r; 19060 _outlineComponents[1] = color.g; 19061 _outlineComponents[2] = color.b; 19062 _outlineComponents[3] = color.a; 19063 } 19064 } 19065 19066 @property void fillColor(Color color) { 19067 CGContextSetRGBFillColor(context, color.r/255.0f, color.g/255.0f, color.b/255.0f, color.a/255.0f); 19068 } 19069 19070 void drawImage(int x, int y, Image image, int ulx, int upy, int width, int height) { 19071 // NotYetImplementedException for upper left/width/height 19072 auto cgImage = CGBitmapContextCreateImage(image.context); 19073 auto size = CGSize(CGBitmapContextGetWidth(image.context), CGBitmapContextGetHeight(image.context)); 19074 CGContextDrawImage(context, CGRect(CGPoint(x, y), size), cgImage); 19075 CGImageRelease(cgImage); 19076 } 19077 19078 void drawPixmap(Sprite s, int x, int y, int ix, int iy, int w, int h) { 19079 // FIXME: is this efficient? 19080 auto cgImage = CGBitmapContextCreateImage(s.handle); 19081 auto size = CGSize(CGBitmapContextGetWidth(s.handle), CGBitmapContextGetHeight(s.handle)); 19082 CGContextDrawImage(context, CGRect(CGPoint(x, y), size), cgImage); 19083 CGImageRelease(cgImage); 19084 } 19085 19086 19087 void drawText(int x, int y, int x2, int y2, in char[] text, uint alignment) { 19088 // FIXME: alignment 19089 if (_outlineComponents[3] != 0) { 19090 CGContextSaveGState(context); 19091 auto invAlpha = 1.0f/_outlineComponents[3]; 19092 CGContextSetRGBFillColor(context, _outlineComponents[0]*invAlpha, 19093 _outlineComponents[1]*invAlpha, 19094 _outlineComponents[2]*invAlpha, 19095 _outlineComponents[3]/255.0f); 19096 19097 19098 19099 // FIXME: should we clip it to the bounding box? 19100 int textHeight = fontHeight; 19101 19102 auto lines = text.split('\n'); 19103 19104 const lineHeight = textHeight; 19105 textHeight *= lines.length; 19106 19107 int cy = y; 19108 19109 if(alignment & TextAlignment.VerticalBottom) { 19110 if(y2 <= 0) 19111 return; 19112 auto h = y2 - y; 19113 if(h > textHeight) { 19114 cy += h - textHeight; 19115 cy -= lineHeight / 2; 19116 } 19117 } else if(alignment & TextAlignment.VerticalCenter) { 19118 if(y2 <= 0) 19119 return; 19120 auto h = y2 - y; 19121 if(textHeight < h) { 19122 cy += (h - textHeight) / 2; 19123 //cy -= lineHeight / 4; 19124 } 19125 } 19126 19127 foreach(line; text.split('\n')) { 19128 int textWidth = this.textSize(line).width; 19129 19130 int px = x, py = cy; 19131 19132 if(alignment & TextAlignment.Center) { 19133 if(x2 <= 0) 19134 return; 19135 auto w = x2 - x; 19136 if(w > textWidth) 19137 px += (w - textWidth) / 2; 19138 } else if(alignment & TextAlignment.Right) { 19139 if(x2 <= 0) 19140 return; 19141 auto pos = x2 - textWidth; 19142 if(pos > x) 19143 px = pos; 19144 } 19145 19146 CGContextShowTextAtPoint(context, px, py + getFont.ascent /* this is cuz this picks baseline but i want bounding box */, line.ptr, line.length); 19147 19148 carry_on: 19149 cy += lineHeight + 4; 19150 } 19151 19152 // auto cfstr = cast(NSid)createCFString(text); 19153 // objc_msgSend(cfstr, sel_registerName("drawAtPoint:withAttributes:"), 19154 // NSPoint(x, y), null); 19155 // CFRelease(cfstr); 19156 CGContextRestoreGState(context); 19157 } 19158 } 19159 19160 void drawPixel(int x, int y) { 19161 auto rawData = CGBitmapContextGetData(context); 19162 auto width = CGBitmapContextGetWidth(context); 19163 auto height = CGBitmapContextGetHeight(context); 19164 auto offset = ((height - y - 1) * width + x) * 4; 19165 rawData[offset .. offset+4] = _outlineComponents; 19166 } 19167 19168 void drawLine(int x1, int y1, int x2, int y2) { 19169 CGPoint[2] linePoints; 19170 linePoints[0] = CGPoint(x1, y1); 19171 linePoints[1] = CGPoint(x2, y2); 19172 CGContextStrokeLineSegments(context, linePoints.ptr, linePoints.length); 19173 } 19174 19175 void drawRectangleRounded(Point upperLeft, Point lowerRight, int borderRadius) { 19176 drawRectangle(upperLeft.x, upperLeft.y, lowerRight.x - upperLeft.x, lowerRight.y - upperLeft.y); // FIXME not rounded 19177 } 19178 19179 void drawRectangle(int x, int y, int width, int height) { 19180 CGContextBeginPath(context); 19181 auto rect = CGRect(CGPoint(x, y), CGSize(width, height)); 19182 CGContextAddRect(context, rect); 19183 CGContextDrawPath(context, CGPathDrawingMode.kCGPathFillStroke); 19184 } 19185 19186 void drawEllipse(int x1, int y1, int x2, int y2) { 19187 CGContextBeginPath(context); 19188 auto rect = CGRect(CGPoint(x1, y1), CGSize(x2-x1, y2-y1)); 19189 CGContextAddEllipseInRect(context, rect); 19190 CGContextDrawPath(context, CGPathDrawingMode.kCGPathFillStroke); 19191 } 19192 19193 void drawArc(int x1, int y1, int width, int height, int start, int length) { 19194 // @@@BUG@@@ Does not support elliptic arc (width != height). 19195 CGContextBeginPath(context); 19196 int clockwise = 0; 19197 if(length < 0) { 19198 clockwise = 1; 19199 length = -length; 19200 } 19201 CGContextAddArc(context, x1+width*0.5f, y1+height*0.5f, width, 19202 start*PI/(180*64), (start+length)*PI/(180*64), clockwise); 19203 CGContextDrawPath(context, CGPathDrawingMode.kCGPathFillStroke); 19204 } 19205 19206 void drawPolygon(Point[] intPoints) { 19207 CGContextBeginPath(context); 19208 CGPoint[16] pointsBuffer; 19209 CGPoint[] points; 19210 if(intPoints.length <= pointsBuffer.length) 19211 points = pointsBuffer[0 .. intPoints.length]; 19212 else 19213 points = new CGPoint[](intPoints.length); 19214 19215 foreach(idx, pt; intPoints) 19216 points[idx] = CGPoint(pt.x, pt.y); 19217 19218 CGContextAddLines(context, points.ptr, points.length); 19219 CGContextDrawPath(context, CGPathDrawingMode.kCGPathFillStroke); 19220 } 19221 } 19222 19223 private bool appInitialized = false; 19224 void initializeApp() { 19225 if(appInitialized) 19226 return; 19227 synchronized { 19228 if(appInitialized) 19229 return; 19230 19231 auto app = NSApp(); // ensure the is initialized 19232 19233 auto dg = AppDelegate.alloc; 19234 globalAppDelegate = dg; 19235 NSApp.delegate_ = dg; 19236 19237 NSApp.setActivationPolicy(NSApplicationActivationPolicy.regular); 19238 19239 appInitialized = true; 19240 } 19241 } 19242 19243 mixin template NativeSimpleWindowImplementation() { 19244 void createWindow(int width, int height, string title, OpenGlOptions opengl, SimpleWindow parent) { 19245 initializeApp(); 19246 19247 auto contentRect = NSRect(NSPoint(0, 0), NSSize(width, height)); 19248 19249 auto window = NSWindow.alloc.initWithContentRect( 19250 contentRect, 19251 NSWindowStyleMask.resizable | NSWindowStyleMask.closable | NSWindowStyleMask.miniaturizable | NSWindowStyleMask.titled, 19252 NSBackingStoreType.buffered, 19253 true 19254 ); 19255 19256 SimpleWindow.nativeMapping[cast(void*) window] = this; 19257 19258 window.title = MacString(title).borrow; 19259 19260 auto dg = SDWindowDelegate.alloc.init; 19261 dg.simpleWindow = this; 19262 window.delegate_ = dg; 19263 19264 auto view = SDGraphicsView.alloc.init; 19265 assert(view !is null); 19266 window.contentView = view; 19267 this.view = view; 19268 view.simpleWindow = this; 19269 19270 window.center(); 19271 19272 window.makeKeyAndOrderFront(null); 19273 19274 // no need to make a bitmap on mac since everything is double buffered already 19275 19276 // create area to draw on. 19277 createNewDrawingContext(width, height); 19278 19279 window.setBackgroundColor(NSColor.whiteColor); 19280 } 19281 19282 void createNewDrawingContext(int width, int height) { 19283 // FIXME need to preserve info from the old context too i think... maybe. or at least setNeedsDisplay 19284 if(this.drawingContext) 19285 CGContextRelease(this.drawingContext); 19286 auto colorSpace = CGColorSpaceCreateDeviceRGB(); 19287 this.drawingContext = CGBitmapContextCreate(null, width, height, 8, 4*width, colorSpace, kCGImageAlphaPremultipliedLast |kCGBitmapByteOrder32Big); 19288 CGColorSpaceRelease(colorSpace); 19289 CGContextSelectFont(drawingContext, "Lucida Grande", 12.0f, 1); 19290 auto matrix = CGContextGetTextMatrix(drawingContext); 19291 matrix.c = -matrix.c; 19292 matrix.d = -matrix.d; 19293 CGContextSetTextMatrix(drawingContext, matrix); 19294 19295 } 19296 19297 void dispose() { 19298 closeWindow(); 19299 // window.release(); // closing the window does this automatically i think 19300 } 19301 void closeWindow() { 19302 if(timer) 19303 timer.invalidate(); 19304 window.close(); 19305 } 19306 19307 ScreenPainter getPainter(bool manualInvalidations) { 19308 return ScreenPainter(this, this.window, manualInvalidations); 19309 } 19310 19311 NSWindow window; 19312 NSTimer timer; 19313 NSView view; 19314 CGContextRef drawingContext; 19315 } 19316 } 19317 19318 version(without_opengl) {} else 19319 extern(System) nothrow @nogc { 19320 //enum uint GL_VERSION = 0x1F02; 19321 //const(char)* glGetString (/*GLenum*/uint); 19322 version(X11) { 19323 static if (!SdpyIsUsingIVGLBinds) { 19324 19325 enum GLX_X_RENDERABLE = 0x8012; 19326 enum GLX_DRAWABLE_TYPE = 0x8010; 19327 enum GLX_RENDER_TYPE = 0x8011; 19328 enum GLX_X_VISUAL_TYPE = 0x22; 19329 enum GLX_TRUE_COLOR = 0x8002; 19330 enum GLX_WINDOW_BIT = 0x00000001; 19331 enum GLX_RGBA_BIT = 0x00000001; 19332 enum GLX_COLOR_INDEX_BIT = 0x00000002; 19333 enum GLX_SAMPLE_BUFFERS = 0x186a0; 19334 enum GLX_SAMPLES = 0x186a1; 19335 enum GLX_CONTEXT_MAJOR_VERSION_ARB = 0x2091; 19336 enum GLX_CONTEXT_MINOR_VERSION_ARB = 0x2092; 19337 } 19338 19339 // GLX_EXT_swap_control 19340 alias glXSwapIntervalEXT = void function (Display* dpy, /*GLXDrawable*/Drawable drawable, int interval); 19341 private __gshared glXSwapIntervalEXT _glx_swapInterval_fn = null; 19342 19343 //k8: ugly code to prevent warnings when sdpy is compiled into .a 19344 extern(System) { 19345 alias glXCreateContextAttribsARB_fna = GLXContext function (Display *dpy, GLXFBConfig config, GLXContext share_context, /*Bool*/int direct, const(int)* attrib_list); 19346 } 19347 private __gshared /*glXCreateContextAttribsARB_fna*/void* glXCreateContextAttribsARBFn = cast(void*)1; //HACK! 19348 19349 // this made public so we don't have to get it again and again 19350 public bool glXCreateContextAttribsARB_present () @system { 19351 if (glXCreateContextAttribsARBFn is cast(void*)1) { 19352 // get it 19353 glXCreateContextAttribsARBFn = cast(void*)glbindGetProcAddress("glXCreateContextAttribsARB"); 19354 //{ import core.stdc.stdio; printf("checking glXCreateContextAttribsARB: %shere\n", (glXCreateContextAttribsARBFn !is null ? "".ptr : "not ".ptr)); } 19355 } 19356 return (glXCreateContextAttribsARBFn !is null); 19357 } 19358 19359 // this made public so we don't have to get it again and again 19360 public GLXContext glXCreateContextAttribsARB (Display *dpy, GLXFBConfig config, GLXContext share_context, /*Bool*/int direct, const(int)* attrib_list) @system { 19361 if (!glXCreateContextAttribsARB_present()) assert(0, "glXCreateContextAttribsARB is not present"); 19362 return (cast(glXCreateContextAttribsARB_fna)glXCreateContextAttribsARBFn)(dpy, config, share_context, direct, attrib_list); 19363 } 19364 19365 // extern(C) private __gshared int function(int) glXSwapIntervalSGI; // seems totally redundant to the tohers 19366 extern(C) private __gshared int function(int) glXSwapIntervalMESA; 19367 19368 void glxSetVSync (Display* dpy, /*GLXDrawable*/Drawable drawable, bool wait) { 19369 if (cast(void*)_glx_swapInterval_fn is cast(void*)1) return; 19370 if (_glx_swapInterval_fn is null) { 19371 _glx_swapInterval_fn = cast(glXSwapIntervalEXT)glXGetProcAddress("glXSwapIntervalEXT"); 19372 if (_glx_swapInterval_fn is null) { 19373 _glx_swapInterval_fn = cast(glXSwapIntervalEXT)1; 19374 return; 19375 } 19376 version(sdddd) { debug writeln("glXSwapIntervalEXT found!"); } 19377 } 19378 19379 if(glXSwapIntervalMESA is null) { 19380 // it seems to require both to actually take effect on many computers 19381 // idk why 19382 glXSwapIntervalMESA = cast(typeof(glXSwapIntervalMESA)) glXGetProcAddress("glXSwapIntervalMESA"); 19383 if(glXSwapIntervalMESA is null) 19384 glXSwapIntervalMESA = cast(typeof(glXSwapIntervalMESA)) 1; 19385 } 19386 19387 if(cast(void*) glXSwapIntervalMESA > cast(void*) 1) 19388 glXSwapIntervalMESA(wait ? 1 : 0); 19389 19390 _glx_swapInterval_fn(dpy, drawable, (wait ? 1 : 0)); 19391 } 19392 } else version(Windows) { 19393 static if (!SdpyIsUsingIVGLBinds) { 19394 enum GL_TRUE = 1; 19395 enum GL_FALSE = 0; 19396 19397 public void* glbindGetProcAddress (const(char)* name) { 19398 void* res = wglGetProcAddress(name); 19399 if (res is null) { 19400 /+ 19401 //{ import core.stdc.stdio; printf("GL: '%s' not found (0)\n", name); } 19402 import core.sys.windows.windef, core.sys.windows.winbase; 19403 __gshared HINSTANCE dll = null; 19404 if (dll is null) { 19405 dll = LoadLibraryA("opengl32.dll"); 19406 if (dll is null) return null; // <32, but idc 19407 } 19408 res = GetProcAddress(dll, name); 19409 +/ 19410 res = GetProcAddress(gl.libHandle, name); 19411 } 19412 //{ import core.stdc.stdio; printf(" GL: '%s' is 0x%08x\n", name, cast(uint)res); } 19413 return res; 19414 } 19415 } 19416 19417 19418 private __gshared extern(System) BOOL function(int) wglSwapIntervalEXT; 19419 void wglSetVSync(bool wait) { 19420 if(wglSwapIntervalEXT is null) { 19421 wglSwapIntervalEXT = cast(typeof(wglSwapIntervalEXT)) wglGetProcAddress("wglSwapIntervalEXT"); 19422 if(wglSwapIntervalEXT is null) 19423 wglSwapIntervalEXT = cast(typeof(wglSwapIntervalEXT)) 1; 19424 } 19425 if(cast(void*) wglSwapIntervalEXT is cast(void*) 1) 19426 return; 19427 19428 wglSwapIntervalEXT(wait ? 1 : 0); 19429 } 19430 19431 enum WGL_CONTEXT_MAJOR_VERSION_ARB = 0x2091; 19432 enum WGL_CONTEXT_MINOR_VERSION_ARB = 0x2092; 19433 enum WGL_CONTEXT_LAYER_PLANE_ARB = 0x2093; 19434 enum WGL_CONTEXT_FLAGS_ARB = 0x2094; 19435 enum WGL_CONTEXT_PROFILE_MASK_ARB = 0x9126; 19436 19437 enum WGL_CONTEXT_DEBUG_BIT_ARB = 0x0001; 19438 enum WGL_CONTEXT_FORWARD_COMPATIBLE_BIT_ARB = 0x0002; 19439 19440 enum WGL_CONTEXT_CORE_PROFILE_BIT_ARB = 0x00000001; 19441 enum WGL_CONTEXT_COMPATIBILITY_PROFILE_BIT_ARB = 0x00000002; 19442 19443 alias wglCreateContextAttribsARB_fna = HGLRC function (HDC hDC, HGLRC hShareContext, const(int)* attribList); 19444 __gshared wglCreateContextAttribsARB_fna wglCreateContextAttribsARB = null; 19445 19446 void wglInitOtherFunctions () { 19447 if (wglCreateContextAttribsARB is null) { 19448 wglCreateContextAttribsARB = cast(wglCreateContextAttribsARB_fna)glbindGetProcAddress("wglCreateContextAttribsARB"); 19449 } 19450 } 19451 } 19452 19453 static if (!SdpyIsUsingIVGLBinds) { 19454 19455 interface GL { 19456 extern(System) @nogc nothrow: 19457 19458 void glGetIntegerv(int, void*); 19459 void glMatrixMode(int); 19460 void glPushMatrix(); 19461 void glLoadIdentity(); 19462 void glOrtho(double, double, double, double, double, double); 19463 void glFrustum(double, double, double, double, double, double); 19464 19465 void glPopMatrix(); 19466 void glEnable(int); 19467 void glDisable(int); 19468 void glClear(int); 19469 void glBegin(int); 19470 void glVertex2f(float, float); 19471 void glVertex3f(float, float, float); 19472 void glEnd(); 19473 void glColor3b(byte, byte, byte); 19474 void glColor3ub(ubyte, ubyte, ubyte); 19475 void glColor4b(byte, byte, byte, byte); 19476 void glColor4ub(ubyte, ubyte, ubyte, ubyte); 19477 void glColor3i(int, int, int); 19478 void glColor3ui(uint, uint, uint); 19479 void glColor4i(int, int, int, int); 19480 void glColor4ui(uint, uint, uint, uint); 19481 void glColor3f(float, float, float); 19482 void glColor4f(float, float, float, float); 19483 void glTranslatef(float, float, float); 19484 void glScalef(float, float, float); 19485 version(X11) { 19486 void glSecondaryColor3b(byte, byte, byte); 19487 void glSecondaryColor3ub(ubyte, ubyte, ubyte); 19488 void glSecondaryColor3i(int, int, int); 19489 void glSecondaryColor3ui(uint, uint, uint); 19490 void glSecondaryColor3f(float, float, float); 19491 } 19492 19493 void glDrawElements(int, int, int, void*); 19494 19495 void glRotatef(float, float, float, float); 19496 19497 uint glGetError(); 19498 19499 void glDeleteTextures(int, uint*); 19500 19501 19502 void glRasterPos2i(int, int); 19503 void glDrawPixels(int, int, uint, uint, void*); 19504 void glClearColor(float, float, float, float); 19505 19506 19507 void glPixelStorei(uint, int); 19508 19509 void glGenTextures(uint, uint*); 19510 void glBindTexture(int, int); 19511 void glTexParameteri(uint, uint, int); 19512 void glTexParameterf(uint/*GLenum*/ target, uint/*GLenum*/ pname, float param); 19513 void glTexImage2D(int, int, int, int, int, int, int, int, scope const void*); 19514 void glTexSubImage2D(uint/*GLenum*/ target, int level, int xoffset, int yoffset, 19515 /*GLsizei*/int width, /*GLsizei*/int height, 19516 uint/*GLenum*/ format, uint/*GLenum*/ type, scope const void* pixels); 19517 void glTexEnvf(uint/*GLenum*/ target, uint/*GLenum*/ pname, float param); 19518 19519 void glLineWidth(int); 19520 19521 19522 void glTexCoord2f(float, float); 19523 void glVertex2i(int, int); 19524 void glBlendFunc (int, int); 19525 void glDepthFunc (int); 19526 void glViewport(int, int, int, int); 19527 19528 void glClearDepth(double); 19529 19530 void glReadBuffer(uint); 19531 void glReadPixels(int, int, int, int, int, int, void*); 19532 19533 void glScissor(GLint x, GLint y, GLsizei width, GLsizei height); 19534 19535 void glFlush(); 19536 void glFinish(); 19537 19538 version(Windows) { 19539 BOOL wglCopyContext(HGLRC, HGLRC, UINT); 19540 HGLRC wglCreateContext(HDC); 19541 HGLRC wglCreateLayerContext(HDC, int); 19542 BOOL wglDeleteContext(HGLRC); 19543 BOOL wglDescribeLayerPlane(HDC, int, int, UINT, LPLAYERPLANEDESCRIPTOR); 19544 HGLRC wglGetCurrentContext(); 19545 HDC wglGetCurrentDC(); 19546 int wglGetLayerPaletteEntries(HDC, int, int, int, COLORREF*); 19547 PROC wglGetProcAddress(LPCSTR); 19548 BOOL wglMakeCurrent(HDC, HGLRC); 19549 BOOL wglRealizeLayerPalette(HDC, int, BOOL); 19550 int wglSetLayerPaletteEntries(HDC, int, int, int, const(COLORREF)*); 19551 BOOL wglShareLists(HGLRC, HGLRC); 19552 BOOL wglSwapLayerBuffers(HDC, UINT); 19553 BOOL wglUseFontBitmapsA(HDC, DWORD, DWORD, DWORD); 19554 BOOL wglUseFontBitmapsW(HDC, DWORD, DWORD, DWORD); 19555 BOOL wglUseFontOutlinesA(HDC, DWORD, DWORD, DWORD, FLOAT, FLOAT, int, LPGLYPHMETRICSFLOAT); 19556 BOOL wglUseFontOutlinesW(HDC, DWORD, DWORD, DWORD, FLOAT, FLOAT, int, LPGLYPHMETRICSFLOAT); 19557 } 19558 19559 } 19560 19561 interface GL3 { 19562 extern(System) @nogc nothrow: 19563 19564 void glGenVertexArrays(GLsizei, GLuint*); 19565 void glBindVertexArray(GLuint); 19566 void glDeleteVertexArrays(GLsizei, const(GLuint)*); 19567 void glGenerateMipmap(GLenum); 19568 void glBufferSubData(GLenum, GLintptr, GLsizeiptr, const(GLvoid)*); 19569 void glStencilMask(GLuint); 19570 void glStencilFunc(GLenum, GLint, GLuint); 19571 void glGetShaderInfoLog(GLuint, GLsizei, GLsizei*, GLchar*); 19572 void glGetProgramInfoLog(GLuint, GLsizei, GLsizei*, GLchar*); 19573 GLuint glCreateProgram(); 19574 GLuint glCreateShader(GLenum); 19575 void glShaderSource(GLuint, GLsizei, const(GLchar*)*, const(GLint)*); 19576 void glCompileShader(GLuint); 19577 void glGetShaderiv(GLuint, GLenum, GLint*); 19578 void glAttachShader(GLuint, GLuint); 19579 void glBindAttribLocation(GLuint, GLuint, const(GLchar)*); 19580 void glLinkProgram(GLuint); 19581 void glGetProgramiv(GLuint, GLenum, GLint*); 19582 void glDeleteProgram(GLuint); 19583 void glDeleteShader(GLuint); 19584 GLint glGetUniformLocation(GLuint, const(GLchar)*); 19585 void glGenBuffers(GLsizei, GLuint*); 19586 19587 void glUniform1f(GLint location, GLfloat v0); 19588 void glUniform2f(GLint location, GLfloat v0, GLfloat v1); 19589 void glUniform3f(GLint location, GLfloat v0, GLfloat v1, GLfloat v2); 19590 void glUniform4f(GLint location, GLfloat v0, GLfloat v1, GLfloat v2, GLfloat v3); 19591 void glUniform1i(GLint location, GLint v0); 19592 void glUniform2i(GLint location, GLint v0, GLint v1); 19593 void glUniform3i(GLint location, GLint v0, GLint v1, GLint v2); 19594 void glUniform4i(GLint location, GLint v0, GLint v1, GLint v2, GLint v3); 19595 void glUniform1ui(GLint location, GLuint v0); 19596 void glUniform2ui(GLint location, GLuint v0, GLuint v1); 19597 void glUniform3ui(GLint location, GLuint v0, GLuint v1, GLuint v2); 19598 void glUniform4ui(GLint location, GLuint v0, GLuint v1, GLuint v2, GLuint v3); 19599 void glUniform1fv(GLint location, GLsizei count, const GLfloat *value); 19600 void glUniform2fv(GLint location, GLsizei count, const GLfloat *value); 19601 void glUniform3fv(GLint location, GLsizei count, const GLfloat *value); 19602 void glUniform4fv(GLint location, GLsizei count, const GLfloat *value); 19603 void glUniform1iv(GLint location, GLsizei count, const GLint *value); 19604 void glUniform2iv(GLint location, GLsizei count, const GLint *value); 19605 void glUniform3iv(GLint location, GLsizei count, const GLint *value); 19606 void glUniform4iv(GLint location, GLsizei count, const GLint *value); 19607 void glUniform1uiv(GLint location, GLsizei count, const GLuint *value); 19608 void glUniform2uiv(GLint location, GLsizei count, const GLuint *value); 19609 void glUniform3uiv(GLint location, GLsizei count, const GLuint *value); 19610 void glUniform4uiv(GLint location, GLsizei count, const GLuint *value); 19611 void glUniformMatrix2fv(GLint location, GLsizei count, GLboolean transpose, const GLfloat *value); 19612 void glUniformMatrix3fv(GLint location, GLsizei count, GLboolean transpose, const GLfloat *value); 19613 void glUniformMatrix4fv(GLint location, GLsizei count, GLboolean transpose, const GLfloat *value); 19614 void glUniformMatrix2x3fv(GLint location, GLsizei count, GLboolean transpose, const GLfloat *value); 19615 void glUniformMatrix3x2fv(GLint location, GLsizei count, GLboolean transpose, const GLfloat *value); 19616 void glUniformMatrix2x4fv(GLint location, GLsizei count, GLboolean transpose, const GLfloat *value); 19617 void glUniformMatrix4x2fv(GLint location, GLsizei count, GLboolean transpose, const GLfloat *value); 19618 void glUniformMatrix3x4fv(GLint location, GLsizei count, GLboolean transpose, const GLfloat *value); 19619 void glUniformMatrix4x3fv(GLint location, GLsizei count, GLboolean transpose, const GLfloat *value); 19620 19621 void glColorMask(GLboolean, GLboolean, GLboolean, GLboolean); 19622 void glStencilOpSeparate(GLenum, GLenum, GLenum, GLenum); 19623 void glDrawArrays(GLenum, GLint, GLsizei); 19624 void glStencilOp(GLenum, GLenum, GLenum); 19625 void glUseProgram(GLuint); 19626 void glCullFace(GLenum); 19627 void glFrontFace(GLenum); 19628 void glActiveTexture(GLenum); 19629 void glBindBuffer(GLenum, GLuint); 19630 void glBufferData(GLenum, GLsizeiptr, const(void)*, GLenum); 19631 void glEnableVertexAttribArray(GLuint); 19632 void glVertexAttribPointer(GLuint, GLint, GLenum, GLboolean, GLsizei, const(void)*); 19633 void glUniform1i(GLint, GLint); 19634 void glUniform2fv(GLint, GLsizei, const(GLfloat)*); 19635 void glDisableVertexAttribArray(GLuint); 19636 void glDeleteBuffers(GLsizei, const(GLuint)*); 19637 void glBlendFuncSeparate(GLenum, GLenum, GLenum, GLenum); 19638 void glLogicOp (GLenum opcode); 19639 void glFramebufferTexture2D (GLenum target, GLenum attachment, GLenum textarget, GLuint texture, GLint level); 19640 void glDeleteFramebuffers (GLsizei n, const(GLuint)* framebuffers); 19641 void glGenFramebuffers (GLsizei n, GLuint* framebuffers); 19642 GLenum glCheckFramebufferStatus (GLenum target); 19643 void glBindFramebuffer (GLenum target, GLuint framebuffer); 19644 } 19645 19646 interface GL4 { 19647 extern(System) @nogc nothrow: 19648 19649 void glTextureSubImage2D(uint texture, int level, int xoffset, int yoffset, 19650 /*GLsizei*/int width, /*GLsizei*/int height, 19651 uint/*GLenum*/ format, uint/*GLenum*/ type, scope const void* pixels); 19652 } 19653 19654 interface GLU { 19655 extern(System) @nogc nothrow: 19656 19657 void gluLookAt(double, double, double, double, double, double, double, double, double); 19658 void gluPerspective(double, double, double, double); 19659 19660 char* gluErrorString(uint); 19661 } 19662 19663 19664 enum GL_RED = 0x1903; 19665 enum GL_ALPHA = 0x1906; 19666 19667 enum uint GL_FRONT = 0x0404; 19668 19669 enum uint GL_BLEND = 0x0be2; 19670 enum uint GL_LEQUAL = 0x0203; 19671 19672 19673 enum uint GL_RGB = 0x1907; 19674 enum uint GL_BGRA = 0x80e1; 19675 enum uint GL_RGBA = 0x1908; 19676 enum uint GL_RGBA8 = 0x8058; 19677 enum uint GL_TEXTURE_2D = 0x0DE1; 19678 enum uint GL_TEXTURE_MIN_FILTER = 0x2801; 19679 enum uint GL_NEAREST = 0x2600; 19680 enum uint GL_LINEAR = 0x2601; 19681 enum uint GL_TEXTURE_MAG_FILTER = 0x2800; 19682 enum uint GL_TEXTURE_WRAP_S = 0x2802; 19683 enum uint GL_TEXTURE_WRAP_T = 0x2803; 19684 enum uint GL_REPEAT = 0x2901; 19685 enum uint GL_CLAMP = 0x2900; 19686 enum uint GL_CLAMP_TO_EDGE = 0x812F; 19687 enum uint GL_CLAMP_TO_BORDER = 0x812D; 19688 enum uint GL_DECAL = 0x2101; 19689 enum uint GL_MODULATE = 0x2100; 19690 enum uint GL_TEXTURE_ENV = 0x2300; 19691 enum uint GL_TEXTURE_ENV_MODE = 0x2200; 19692 enum uint GL_REPLACE = 0x1E01; 19693 enum uint GL_LIGHTING = 0x0B50; 19694 enum uint GL_DITHER = 0x0BD0; 19695 19696 enum uint GL_NO_ERROR = 0; 19697 19698 19699 19700 enum int GL_VIEWPORT = 0x0BA2; 19701 enum int GL_MODELVIEW = 0x1700; 19702 enum int GL_TEXTURE = 0x1702; 19703 enum int GL_PROJECTION = 0x1701; 19704 enum int GL_DEPTH_TEST = 0x0B71; 19705 19706 enum int GL_COLOR_BUFFER_BIT = 0x00004000; 19707 enum int GL_ACCUM_BUFFER_BIT = 0x00000200; 19708 enum int GL_DEPTH_BUFFER_BIT = 0x00000100; 19709 enum uint GL_STENCIL_BUFFER_BIT = 0x00000400; 19710 19711 enum int GL_POINTS = 0x0000; 19712 enum int GL_LINES = 0x0001; 19713 enum int GL_LINE_LOOP = 0x0002; 19714 enum int GL_LINE_STRIP = 0x0003; 19715 enum int GL_TRIANGLES = 0x0004; 19716 enum int GL_TRIANGLE_STRIP = 5; 19717 enum int GL_TRIANGLE_FAN = 6; 19718 enum int GL_QUADS = 7; 19719 enum int GL_QUAD_STRIP = 8; 19720 enum int GL_POLYGON = 9; 19721 19722 alias GLvoid = void; 19723 alias GLboolean = ubyte; 19724 alias GLint = int; 19725 alias GLuint = uint; 19726 alias GLenum = uint; 19727 alias GLchar = char; 19728 alias GLsizei = int; 19729 alias GLfloat = float; 19730 alias GLintptr = size_t; 19731 alias GLsizeiptr = ptrdiff_t; 19732 19733 19734 enum uint GL_INVALID_ENUM = 0x0500; 19735 19736 enum uint GL_ZERO = 0; 19737 enum uint GL_ONE = 1; 19738 19739 enum uint GL_BYTE = 0x1400; 19740 enum uint GL_UNSIGNED_BYTE = 0x1401; 19741 enum uint GL_SHORT = 0x1402; 19742 enum uint GL_UNSIGNED_SHORT = 0x1403; 19743 enum uint GL_INT = 0x1404; 19744 enum uint GL_UNSIGNED_INT = 0x1405; 19745 enum uint GL_FLOAT = 0x1406; 19746 enum uint GL_2_BYTES = 0x1407; 19747 enum uint GL_3_BYTES = 0x1408; 19748 enum uint GL_4_BYTES = 0x1409; 19749 enum uint GL_DOUBLE = 0x140A; 19750 19751 enum uint GL_STREAM_DRAW = 0x88E0; 19752 19753 enum uint GL_CCW = 0x0901; 19754 19755 enum uint GL_STENCIL_TEST = 0x0B90; 19756 enum uint GL_SCISSOR_TEST = 0x0C11; 19757 19758 enum uint GL_EQUAL = 0x0202; 19759 enum uint GL_NOTEQUAL = 0x0205; 19760 19761 enum uint GL_ALWAYS = 0x0207; 19762 enum uint GL_KEEP = 0x1E00; 19763 19764 enum uint GL_INCR = 0x1E02; 19765 19766 enum uint GL_INCR_WRAP = 0x8507; 19767 enum uint GL_DECR_WRAP = 0x8508; 19768 19769 enum uint GL_CULL_FACE = 0x0B44; 19770 enum uint GL_BACK = 0x0405; 19771 19772 enum uint GL_FRAGMENT_SHADER = 0x8B30; 19773 enum uint GL_VERTEX_SHADER = 0x8B31; 19774 19775 enum uint GL_COMPILE_STATUS = 0x8B81; 19776 enum uint GL_LINK_STATUS = 0x8B82; 19777 19778 enum uint GL_ELEMENT_ARRAY_BUFFER = 0x8893; 19779 19780 enum uint GL_STATIC_DRAW = 0x88E4; 19781 19782 enum uint GL_UNPACK_ALIGNMENT = 0x0CF5; 19783 enum uint GL_UNPACK_ROW_LENGTH = 0x0CF2; 19784 enum uint GL_UNPACK_SKIP_PIXELS = 0x0CF4; 19785 enum uint GL_UNPACK_SKIP_ROWS = 0x0CF3; 19786 19787 enum uint GL_GENERATE_MIPMAP = 0x8191; 19788 enum uint GL_LINEAR_MIPMAP_LINEAR = 0x2703; 19789 19790 enum uint GL_TEXTURE0 = 0x84C0U; 19791 enum uint GL_TEXTURE1 = 0x84C1U; 19792 19793 enum uint GL_ARRAY_BUFFER = 0x8892; 19794 19795 enum uint GL_SRC_COLOR = 0x0300; 19796 enum uint GL_ONE_MINUS_SRC_COLOR = 0x0301; 19797 enum uint GL_SRC_ALPHA = 0x0302; 19798 enum uint GL_ONE_MINUS_SRC_ALPHA = 0x0303; 19799 enum uint GL_DST_ALPHA = 0x0304; 19800 enum uint GL_ONE_MINUS_DST_ALPHA = 0x0305; 19801 enum uint GL_DST_COLOR = 0x0306; 19802 enum uint GL_ONE_MINUS_DST_COLOR = 0x0307; 19803 enum uint GL_SRC_ALPHA_SATURATE = 0x0308; 19804 19805 enum uint GL_INVERT = 0x150AU; 19806 19807 enum uint GL_DEPTH_STENCIL = 0x84F9U; 19808 enum uint GL_UNSIGNED_INT_24_8 = 0x84FAU; 19809 19810 enum uint GL_FRAMEBUFFER = 0x8D40U; 19811 enum uint GL_COLOR_ATTACHMENT0 = 0x8CE0U; 19812 enum uint GL_DEPTH_STENCIL_ATTACHMENT = 0x821AU; 19813 19814 enum uint GL_FRAMEBUFFER_COMPLETE = 0x8CD5U; 19815 enum uint GL_FRAMEBUFFER_INCOMPLETE_ATTACHMENT = 0x8CD6U; 19816 enum uint GL_FRAMEBUFFER_INCOMPLETE_MISSING_ATTACHMENT = 0x8CD7U; 19817 enum uint GL_FRAMEBUFFER_INCOMPLETE_DIMENSIONS = 0x8CD9U; 19818 enum uint GL_FRAMEBUFFER_UNSUPPORTED = 0x8CDDU; 19819 19820 enum uint GL_COLOR_LOGIC_OP = 0x0BF2U; 19821 enum uint GL_CLEAR = 0x1500U; 19822 enum uint GL_COPY = 0x1503U; 19823 enum uint GL_XOR = 0x1506U; 19824 19825 enum uint GL_FRAMEBUFFER_BINDING = 0x8CA6U; 19826 19827 enum uint GL_TEXTURE_LOD_BIAS = 0x8501; 19828 19829 } 19830 } 19831 19832 /++ 19833 History: 19834 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. 19835 +/ 19836 __gshared bool gluSuccessfullyLoaded = true; 19837 19838 version(without_opengl) {} else { 19839 static if(!SdpyIsUsingIVGLBinds) { 19840 version(Windows) { 19841 mixin DynamicLoad!(GL, "opengl32", 1, openGlLibrariesSuccessfullyLoaded) gl; 19842 mixin DynamicLoad!(GLU, "glu32", 1, gluSuccessfullyLoaded) glu; 19843 } else { 19844 mixin DynamicLoad!(GL, "GL", 1, openGlLibrariesSuccessfullyLoaded) gl; 19845 mixin DynamicLoad!(GLU, "GLU", 3, gluSuccessfullyLoaded) glu; 19846 } 19847 mixin DynamicLoadSupplementalOpenGL!(GL3) gl3; 19848 19849 19850 shared static this() { 19851 gl.loadDynamicLibrary(); 19852 19853 // FIXME: this is NOT actually required and should NOT fail if it is not loaded 19854 // unless those functions are actually used 19855 // go to mark b openGlLibrariesSuccessfullyLoaded = false; 19856 glu.loadDynamicLibrary(); 19857 } 19858 } 19859 } 19860 19861 /++ 19862 Convenience method for converting D arrays to opengl buffer data 19863 19864 I would LOVE to overload it with the original glBufferData, but D won't 19865 let me since glBufferData is a function pointer :( 19866 19867 Added: August 25, 2020 (version 8.5) 19868 +/ 19869 version(without_opengl) {} else 19870 void glBufferDataSlice(GLenum target, const(void[]) data, GLenum usage) { 19871 glBufferData(target, data.length, data.ptr, usage); 19872 } 19873 19874 /++ 19875 History: 19876 Added September 1, 2024 19877 +/ 19878 version(without_opengl) {} else 19879 void glBufferSubDataSlice(GLenum target, size_t offset, const(void[]) data, GLenum usage) { 19880 glBufferSubData(target, offset, data.length, data.ptr); 19881 } 19882 19883 /++ 19884 Convenience class for using opengl shaders. 19885 19886 Ensure that you've loaded opengl 3+ and set your active 19887 context before trying to use this. 19888 19889 Added: August 25, 2020 (version 8.5) 19890 +/ 19891 version(without_opengl) {} else 19892 final class OpenGlShader { 19893 private int shaderProgram_; 19894 private @property void shaderProgram(int a) { 19895 shaderProgram_ = a; 19896 } 19897 /// Get the program ID for use in OpenGL functions. 19898 public @property int shaderProgram() { 19899 return shaderProgram_; 19900 } 19901 19902 /++ 19903 19904 +/ 19905 static struct Source { 19906 uint type; /// GL_FRAGMENT_SHADER, GL_VERTEX_SHADER, etc. 19907 string code; /// 19908 } 19909 19910 /++ 19911 Helper method to just compile some shader code and check for errors 19912 while you do glCreateShader, etc. on the outside yourself. 19913 19914 This just does `glShaderSource` and `glCompileShader` for the given code. 19915 19916 If you the OpenGlShader class constructor, you never need to call this yourself. 19917 +/ 19918 static void compile(int sid, Source code) { 19919 const(char)*[1] buffer; 19920 int[1] lengthBuffer; 19921 19922 buffer[0] = code.code.ptr; 19923 lengthBuffer[0] = cast(int) code.code.length; 19924 19925 glShaderSource(sid, cast(int) buffer.length, buffer.ptr, lengthBuffer.ptr); 19926 glCompileShader(sid); 19927 19928 int success; 19929 glGetShaderiv(sid, GL_COMPILE_STATUS, &success); 19930 if(!success) { 19931 char[512] info; 19932 int len; 19933 glGetShaderInfoLog(sid, info.length, &len, info.ptr); 19934 19935 throw new Exception("Shader compile failure: " ~ cast(immutable) info[0 .. len]); 19936 } 19937 } 19938 19939 /++ 19940 Calls `glLinkProgram` and throws if error a occurs. 19941 19942 If you the OpenGlShader class constructor, you never need to call this yourself. 19943 +/ 19944 static void link(int shaderProgram) { 19945 glLinkProgram(shaderProgram); 19946 int success; 19947 glGetProgramiv(shaderProgram, GL_LINK_STATUS, &success); 19948 if(!success) { 19949 char[512] info; 19950 int len; 19951 glGetProgramInfoLog(shaderProgram, info.length, &len, info.ptr); 19952 19953 throw new Exception("Shader link failure: " ~ cast(immutable) info[0 .. len]); 19954 } 19955 } 19956 19957 /++ 19958 Constructs the shader object by calling `glCreateProgram`, then 19959 compiling each given [Source], and finally, linking them together. 19960 19961 Throws: on compile or link failure. 19962 +/ 19963 this(Source[] codes...) { 19964 shaderProgram = glCreateProgram(); 19965 19966 int[16] shadersBufferStack; 19967 19968 int[] shadersBuffer = codes.length <= shadersBufferStack.length ? 19969 shadersBufferStack[0 .. codes.length] : 19970 new int[](codes.length); 19971 19972 foreach(idx, code; codes) { 19973 shadersBuffer[idx] = glCreateShader(code.type); 19974 19975 compile(shadersBuffer[idx], code); 19976 19977 glAttachShader(shaderProgram, shadersBuffer[idx]); 19978 } 19979 19980 link(shaderProgram); 19981 19982 foreach(s; shadersBuffer) 19983 glDeleteShader(s); 19984 } 19985 19986 /// Calls `glUseProgram(this.shaderProgram)` 19987 void use() { 19988 glUseProgram(this.shaderProgram); 19989 } 19990 19991 /// Deletes the program. 19992 void delete_() { 19993 glDeleteProgram(shaderProgram); 19994 shaderProgram = 0; 19995 } 19996 19997 /++ 19998 [OpenGlShader.uniforms].name gives you one of these. 19999 20000 You can get the id out of it or just assign 20001 +/ 20002 static struct Uniform { 20003 /// the id passed to glUniform* 20004 int id; 20005 20006 /// Assigns the 4 floats. You will probably have to call this via the .opAssign name 20007 void opAssign(float x, float y, float z, float w) { 20008 if(id != -1) 20009 glUniform4f(id, x, y, z, w); 20010 } 20011 20012 void opAssign(float x) { 20013 if(id != -1) 20014 glUniform1f(id, x); 20015 } 20016 20017 void opAssign(float x, float y) { 20018 if(id != -1) 20019 glUniform2f(id, x, y); 20020 } 20021 20022 void opAssign(T)(T t) { 20023 t.glUniform(id); 20024 } 20025 } 20026 20027 static struct UniformsHelper { 20028 OpenGlShader _shader; 20029 20030 @property Uniform opDispatch(string name)() { 20031 auto i = glGetUniformLocation(_shader.shaderProgram, name.ptr); 20032 // FIXME: decide what to do here; the exception is liable to be swallowed by the event syste 20033 //if(i == -1) 20034 //throw new Exception("Could not find uniform " ~ name); 20035 return Uniform(i); 20036 } 20037 20038 @property void opDispatch(string name, T)(T t) { 20039 Uniform f = this.opDispatch!name; 20040 t.glUniform(f); 20041 } 20042 } 20043 20044 /++ 20045 Gives access to the uniforms through dot access. 20046 `OpenGlShader.Uniform = shader.uniforms.foo; // calls glGetUniformLocation(this, "foo"); 20047 +/ 20048 @property UniformsHelper uniforms() { return UniformsHelper(this); } 20049 } 20050 20051 version(without_opengl) {} else { 20052 /++ 20053 A static container of experimental types and value constructors for opengl 3+ shaders. 20054 20055 20056 You can declare variables like: 20057 20058 ``` 20059 OGL.vec3f something; 20060 ``` 20061 20062 But generally it would be used with [OpenGlShader]'s uniform helpers like 20063 20064 ``` 20065 shader.uniforms.mouse = OGL.vec(mouseX, mouseY); // or OGL.vec2f if you want to be more specific 20066 ``` 20067 20068 This is still extremely experimental, not very useful at this point, and thus subject to change at random. 20069 20070 20071 History: 20072 Added December 7, 2021. Not yet stable. 20073 +/ 20074 final class OGL { 20075 static: 20076 20077 private template typeFromSpecifier(string specifier) { 20078 static if(specifier == "f") 20079 alias typeFromSpecifier = GLfloat; 20080 else static if(specifier == "i") 20081 alias typeFromSpecifier = GLint; 20082 else static if(specifier == "ui") 20083 alias typeFromSpecifier = GLuint; 20084 else static assert(0, "I don't know this ogl type suffix " ~ specifier); 20085 } 20086 20087 private template CommonType(T...) { 20088 static if(T.length == 1) 20089 alias CommonType = T[0]; 20090 else static if(is(typeof(true ? T[0].init : T[1].init) C)) 20091 alias CommonType = CommonType!(C, T[2 .. $]); 20092 } 20093 20094 private template typesToSpecifier(T...) { 20095 static if(is(CommonType!T == float)) 20096 enum typesToSpecifier = "f"; 20097 else static if(is(CommonType!T == int)) 20098 enum typesToSpecifier = "i"; 20099 else static if(is(CommonType!T == uint)) 20100 enum typesToSpecifier = "ui"; 20101 else static assert(0, "I can't find a gl type suffix for common type " ~ CommonType!T.stringof); 20102 } 20103 20104 private template genNames(size_t dim, size_t dim2 = 0) { 20105 string helper() { 20106 string s; 20107 if(dim2) { 20108 static if(__VERSION__ < 2102) 20109 s ~= "type["~(dim + '0')~"]["~(dim2 + '0')~"] matrix = void;"; // stupid compiler bug 20110 else 20111 s ~= "type["~(dim + '0')~"]["~(dim2 + '0')~"] matrix = 0;"; 20112 } else { 20113 if(dim > 0) s ~= "type x = 0;"; 20114 if(dim > 1) s ~= "type y = 0;"; 20115 if(dim > 2) s ~= "type z = 0;"; 20116 if(dim > 3) s ~= "type w = 0;"; 20117 } 20118 20119 s ~= "this(typeof(this.tupleof) args) { this.tupleof = args; }"; 20120 if(dim2) 20121 s ~= "this(type["~(dim*dim2).stringof~"] t) { (cast(typeof(t)) this.matrix)[] = t[]; }"; 20122 20123 return s; 20124 } 20125 20126 enum genNames = helper(); 20127 } 20128 20129 // there's vec, arrays of vec, mat, and arrays of mat 20130 template opDispatch(string name) 20131 if(name.length > 4 && (name[0 .. 3] == "vec" || name[0 .. 3] == "mat")) 20132 { 20133 static if(name[4] == 'x') { 20134 enum dimX = cast(int) (name[3] - '0'); 20135 static assert(dimX > 0 && dimX <= 4, "Bad dimension for OGL X type " ~ name[3]); 20136 20137 enum dimY = cast(int) (name[5] - '0'); 20138 static assert(dimY > 0 && dimY <= 4, "Bad dimension for OGL Y type " ~ name[5]); 20139 20140 enum isArray = name[$ - 1] == 'v'; 20141 enum typeSpecifier = isArray ? name[6 .. $ - 1] : name[6 .. $]; 20142 alias type = typeFromSpecifier!typeSpecifier; 20143 } else { 20144 enum dim = cast(int) (name[3] - '0'); 20145 static assert(dim > 0 && dim <= 4, "Bad dimension for OGL type " ~ name[3]); 20146 enum isArray = name[$ - 1] == 'v'; 20147 enum typeSpecifier = isArray ? name[4 .. $ - 1] : name[4 .. $]; 20148 alias type = typeFromSpecifier!typeSpecifier; 20149 } 20150 20151 align(1) 20152 struct opDispatch { 20153 align(1): 20154 static if(name[4] == 'x') 20155 mixin(genNames!(dimX, dimY)); 20156 else 20157 mixin(genNames!dim); 20158 20159 private void glUniform(OpenGlShader.Uniform assignTo) { 20160 glUniform(assignTo.id); 20161 } 20162 private void glUniform(int assignTo) { 20163 static if(name[4] == 'x') { 20164 static if(name[3] == name[5]) { 20165 // import std.stdio; writeln(name, " ", this.matrix, dimX, " ", dimY); 20166 mixin("glUniformMatrix" ~ name[5 .. $] ~ "v")(assignTo, 1, true, &this.matrix[0][0]); 20167 } else { 20168 mixin("glUniformMatrix" ~ name[3 .. $] ~ "v")(assignTo, 1, false, this.matrix.ptr); 20169 } 20170 } else 20171 mixin("glUniform" ~ name[3 .. $])(assignTo, this.tupleof); 20172 } 20173 } 20174 } 20175 20176 auto vec(T...)(T members) { 20177 return typeof(this).opDispatch!("vec" ~ toInternal!string(cast(int) T.length)~ typesToSpecifier!T)(members); 20178 } 20179 } 20180 20181 void checkGlError() { 20182 auto error = glGetError(); 20183 int[] errors; 20184 string[] errorStrings; 20185 while(error != GL_NO_ERROR) { 20186 errors ~= error; 20187 switch(error) { 20188 case 0x0500: errorStrings ~= "GL_INVALID_ENUM"; break; 20189 case 0x0501: errorStrings ~= "GL_INVALID_VALUE"; break; 20190 case 0x0502: errorStrings ~= "GL_INVALID_OPERATION"; break; 20191 case 0x0503: errorStrings ~= "GL_STACK_OVERFLOW"; break; 20192 case 0x0504: errorStrings ~= "GL_STACK_UNDERFLOW"; break; 20193 case 0x0505: errorStrings ~= "GL_OUT_OF_MEMORY"; break; 20194 default: errorStrings ~= "idk"; 20195 } 20196 error = glGetError(); 20197 } 20198 if(errors.length) 20199 throw ArsdException!"glGetError"(errors, errorStrings); 20200 } 20201 20202 /++ 20203 A matrix for simple uses that easily integrates with [OpenGlShader]. 20204 20205 Might not be useful to you since it only as some simple functions and 20206 probably isn't that fast. 20207 20208 Note it uses an inline static array for its storage, so copying it 20209 may be expensive. 20210 +/ 20211 struct BasicMatrix(int columns, int rows, T = float) { 20212 static import core.stdc.math; 20213 static if(is(T == float)) { 20214 alias cos = core.stdc.math.cosf; 20215 alias sin = core.stdc.math.sinf; 20216 } else { 20217 alias cos = core.stdc.math.cos; 20218 alias sin = core.stdc.math.sin; 20219 } 20220 20221 T[columns * rows] data = 0.0; 20222 20223 /++ 20224 20225 +/ 20226 this(T[columns * rows] data) { 20227 this.data = data; 20228 } 20229 20230 /++ 20231 Basic operations that operate *in place*. 20232 +/ 20233 static if(columns == 4 && rows == 4) 20234 void translate(T x, T y, T z) { 20235 BasicMatrix m = [ 20236 1, 0, 0, x, 20237 0, 1, 0, y, 20238 0, 0, 1, z, 20239 0, 0, 0, 1 20240 ]; 20241 20242 this *= m; 20243 } 20244 20245 /// ditto 20246 static if(columns == 4 && rows == 4) 20247 void scale(T x, T y, T z) { 20248 BasicMatrix m = [ 20249 x, 0, 0, 0, 20250 0, y, 0, 0, 20251 0, 0, z, 0, 20252 0, 0, 0, 1 20253 ]; 20254 20255 this *= m; 20256 } 20257 20258 /// ditto 20259 static if(columns == 4 && rows == 4) 20260 void rotateX(T theta) { 20261 BasicMatrix m = [ 20262 1, 0, 0, 0, 20263 0, cos(theta), -sin(theta), 0, 20264 0, sin(theta), cos(theta), 0, 20265 0, 0, 0, 1 20266 ]; 20267 20268 this *= m; 20269 } 20270 20271 /// ditto 20272 static if(columns == 4 && rows == 4) 20273 void rotateY(T theta) { 20274 BasicMatrix m = [ 20275 cos(theta), 0, sin(theta), 0, 20276 0, 1, 0, 0, 20277 -sin(theta), 0, cos(theta), 0, 20278 0, 0, 0, 1 20279 ]; 20280 20281 this *= m; 20282 } 20283 20284 /// ditto 20285 static if(columns == 4 && rows == 4) 20286 void rotateZ(T theta) { 20287 BasicMatrix m = [ 20288 cos(theta), -sin(theta), 0, 0, 20289 sin(theta), cos(theta), 0, 0, 20290 0, 0, 1, 0, 20291 0, 0, 0, 1 20292 ]; 20293 20294 this *= m; 20295 } 20296 20297 /++ 20298 20299 +/ 20300 static if(columns == rows) 20301 static BasicMatrix identity() { 20302 BasicMatrix m; 20303 foreach(i; 0 .. columns) 20304 m.data[0 + i + i * columns] = 1.0; 20305 return m; 20306 } 20307 20308 static if(columns == rows) 20309 void loadIdentity() { 20310 this = identity(); 20311 } 20312 20313 static if(columns == 4 && rows == 4) 20314 static BasicMatrix ortho(T l, T r, T b, T t, T n, T f) { 20315 return BasicMatrix([ 20316 2/(r-l), 0, 0, -(r+l)/(r-l), 20317 0, 2/(t-b), 0, -(t+b)/(t-b), 20318 0, 0, -2/(f-n), -(f+n)/(f-n), 20319 0, 0, 0, 1 20320 ]); 20321 } 20322 20323 static if(columns == 4 && rows == 4) 20324 void loadOrtho(T l, T r, T b, T t, T n, T f) { 20325 this = ortho(l, r, b, t, n, f); 20326 } 20327 20328 void opOpAssign(string op : "+")(const BasicMatrix rhs) { 20329 this.data[] += rhs.data; 20330 } 20331 void opOpAssign(string op : "-")(const BasicMatrix rhs) { 20332 this.data[] -= rhs.data; 20333 } 20334 void opOpAssign(string op : "*")(const T rhs) { 20335 this.data[] *= rhs; 20336 } 20337 void opOpAssign(string op : "/")(const T rhs) { 20338 this.data[] /= rhs; 20339 } 20340 void opOpAssign(string op : "*", BM : BasicMatrix!(rhsColumns, rhsRows, rhsT), int rhsColumns, int rhsRows, rhsT)(const BM rhs) { 20341 static assert(columns == rhsRows); 20342 auto multiplySize = columns; 20343 20344 auto tmp = this.data; // copy cuz it is a value type 20345 20346 int idx = 0; 20347 foreach(r; 0 .. rows) 20348 foreach(c; 0 .. columns) { 20349 T sum = 0.0; 20350 20351 foreach(i; 0 .. multiplySize) 20352 sum += this.data[r * columns + i] * rhs.data[i * rhsColumns + c]; 20353 20354 tmp[idx++] = sum; 20355 } 20356 20357 this.data = tmp; 20358 } 20359 } 20360 20361 unittest { 20362 auto m = BasicMatrix!(2, 2)([ 20363 1, 2, 20364 3, 4 20365 ]); 20366 20367 auto m2 = BasicMatrix!(2, 2)([ 20368 5, 6, 20369 7, 8 20370 ]); 20371 20372 //import std.conv; 20373 m *= m2; 20374 assert(m.data == [ 20375 19, 22, 20376 43, 50 20377 ]);//, to!string(m.data)); 20378 } 20379 20380 20381 20382 class GlObjectBase { 20383 protected uint _vao; 20384 protected uint _elementsCount; 20385 20386 protected uint element_buffer; 20387 20388 void gen() { 20389 glGenVertexArrays(1, &_vao); 20390 } 20391 20392 void bind() { 20393 glBindVertexArray(_vao); 20394 } 20395 20396 void dispose() { 20397 glDeleteVertexArrays(1, &_vao); 20398 } 20399 20400 void draw() { 20401 bind(); 20402 glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, element_buffer); 20403 glDrawElements(GL_TRIANGLES, _elementsCount, GL_UNSIGNED_INT, null); 20404 } 20405 } 20406 20407 /++ 20408 20409 +/ 20410 class GlObject(T) : GlObjectBase { 20411 protected uint VBO; 20412 20413 this(T[] arr, uint[] indices) { 20414 gen(); 20415 bind(); 20416 20417 glGenBuffers(1, &VBO); 20418 glGenBuffers(1, &element_buffer); 20419 20420 glBindBuffer(GL_ARRAY_BUFFER, VBO); 20421 glBufferDataSlice(GL_ARRAY_BUFFER, arr, GL_STATIC_DRAW); 20422 20423 glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, element_buffer); 20424 glBufferDataSlice(GL_ELEMENT_ARRAY_BUFFER, indices, GL_STATIC_DRAW); 20425 _elementsCount = cast(int) indices.length; 20426 20427 foreach(int idx, memberName; __traits(allMembers, T)) { 20428 static if(memberName != "__ctor") { 20429 static if(is(typeof(__traits(getMember, T, memberName)) == float[N], size_t N)) { 20430 glVertexAttribPointer(idx, N, GL_FLOAT, GL_FALSE, T.sizeof, cast(void*) __traits(getMember, T, memberName).offsetof); 20431 glEnableVertexAttribArray(idx); 20432 } else static assert(0); } 20433 } 20434 } 20435 20436 static string generateShaderDefinitions() { 20437 string code; 20438 20439 foreach(idx, memberName; __traits(allMembers, T)) { 20440 // never use stringof ladies and gents it has a LU thing at the end of it 20441 static if(memberName != "__ctor") 20442 code ~= "layout (location = " ~ idx.stringof[0..$-2] ~ ") in " ~ typeToGl!(typeof(__traits(getMember, T, memberName))) ~ " " ~ memberName ~ ";\n"; 20443 } 20444 20445 return code; 20446 } 20447 } 20448 20449 private string typeToGl(T)() { 20450 static if(is(T == float[4])) 20451 return "vec4"; 20452 else static if(is(T == float[3])) 20453 return "vec3"; 20454 else static if(is(T == float[2])) 20455 return "vec2"; 20456 else static assert(0, T.stringof); 20457 } 20458 20459 20460 } 20461 20462 version(Emscripten) { 20463 20464 } else version(linux) { 20465 version(with_eventloop) {} else { 20466 private int epollFd = -1; 20467 void prepareEventLoop() { 20468 if(epollFd != -1) 20469 return; // already initialized, no need to do it again 20470 import ep = core.sys.linux.epoll; 20471 20472 epollFd = ep.epoll_create1(ep.EPOLL_CLOEXEC); 20473 if(epollFd == -1) 20474 throw new Exception("epoll create failure"); 20475 } 20476 } 20477 } else version(Posix) { 20478 void prepareEventLoop() {} 20479 } 20480 20481 version(X11) { 20482 import core.stdc.locale : LC_ALL; // rdmd fix 20483 __gshared bool sdx_isUTF8Locale; 20484 20485 // This whole crap is used to initialize X11 locale, so that you can use XIM methods later. 20486 // Yes, there are people with non-utf locale (it's me, Ketmar!), but XIM (composing) will 20487 // not work right if app/X11 locale is not utf. This sux. That's why all that "utf detection" 20488 // anal magic is here. I (Ketmar) hope you like it. 20489 // We will use `sdx_isUTF8Locale` on XIM creation to enforce UTF-8 locale, so XCompose will 20490 // always return correct unicode symbols. The detection is here 'cause user can change locale 20491 // later. 20492 20493 // NOTE: IT IS VERY IMPORTANT THAT THIS BE THE LAST STATIC CTOR OF THE FILE since it tests librariesSuccessfullyLoaded 20494 shared static this () @system { 20495 if(!librariesSuccessfullyLoaded) 20496 return; 20497 20498 import core.stdc.locale : setlocale, LC_ALL, LC_CTYPE; 20499 20500 // this doesn't hurt; it may add some locking, but the speed is still 20501 // allows doing 60 FPS videogames; also, ignore the result, as most 20502 // users will probably won't do mulththreaded X11 anyway (and I (ketmar) 20503 // never seen this failing). 20504 if (XInitThreads() == 0) { import core.stdc.stdio; fprintf(stderr, "XInitThreads() failed!\n"); } 20505 20506 setlocale(LC_ALL, ""); 20507 // check if out locale is UTF-8 20508 auto lct = setlocale(LC_CTYPE, null); 20509 if (lct is null) { 20510 sdx_isUTF8Locale = false; 20511 } else { 20512 for (size_t idx = 0; lct[idx] && lct[idx+1] && lct[idx+2]; ++idx) { 20513 if ((lct[idx+0] == 'u' || lct[idx+0] == 'U') && 20514 (lct[idx+1] == 't' || lct[idx+1] == 'T') && 20515 (lct[idx+2] == 'f' || lct[idx+2] == 'F')) 20516 { 20517 sdx_isUTF8Locale = true; 20518 break; 20519 } 20520 } 20521 } 20522 //{ import core.stdc.stdio : stderr, fprintf; fprintf(stderr, "UTF8: %s\n", sdx_isUTF8Locale ? "tan".ptr : "ona".ptr); } 20523 } 20524 } 20525 20526 class ExperimentalTextComponent2 { 20527 /+ 20528 Stage 1: get it working monospace 20529 Stage 2: use proportional font 20530 Stage 3: allow changes in inline style 20531 Stage 4: allow new fonts and sizes in the middle 20532 Stage 5: optimize gap buffer 20533 Stage 6: optimize layout 20534 Stage 7: word wrap 20535 Stage 8: justification 20536 Stage 9: editing, selection, etc. 20537 20538 Operations: 20539 insert text 20540 overstrike text 20541 select 20542 cut 20543 modify 20544 +/ 20545 20546 /++ 20547 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. 20548 +/ 20549 this(SimpleWindow window) { 20550 this.window = window; 20551 } 20552 20553 private SimpleWindow window; 20554 20555 20556 /++ 20557 When you render a [ComponentInFlow], it returns an arbitrary number of these interfaces 20558 representing the internal parts. The first pass is focused on the x parameter, then the 20559 renderer is responsible for going back to the parts in the current line and calling 20560 adjustDownForAscent to change the y params. 20561 +/ 20562 static interface ComponentRenderHelper { 20563 20564 /+ 20565 When you do an edit, possibly stuff on the same line previously need to move (to adjust 20566 the baseline), stuff subsequent needs to move (adjust x) and possibly stuff below needs 20567 to move (adjust y to make room for new line) until you get back to the same position, 20568 then you can stop - if one thing is unchanged, nothing after it is changed too. 20569 20570 Word wrap might change this as if can rewrap tons of stuff, but the same idea applies, 20571 once you reach something that is unchanged, you can stop. 20572 +/ 20573 20574 void adjustDownForAscent(int amount); // at the end of the line it needs to do these 20575 20576 int ascent() const; 20577 int descent() const; 20578 20579 int advance() const; 20580 20581 bool endsWithExplititLineBreak() const; 20582 } 20583 20584 static interface RenderResult { 20585 /++ 20586 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. 20587 +/ 20588 void popFront(); 20589 @property bool empty() const; 20590 @property ComponentRenderHelper front() const; 20591 20592 void repositionForNextLine(Point baseline, int availableWidth); 20593 } 20594 20595 static interface ComponentInFlow { 20596 void draw(ScreenPainter painter); 20597 //RenderResult render(Point baseline, int availableWidth); // FIXME: it needs to be able to say "my cache is good, nothing different" 20598 20599 bool startsWithExplicitLineBreak() const; 20600 } 20601 20602 static class TextFlowComponent : ComponentInFlow { 20603 bool startsWithExplicitLineBreak() const { return false; } // FIXME: if it is block this can return true 20604 20605 Color foreground; 20606 Color background; 20607 20608 OperatingSystemFont font; // should NEVER be null 20609 20610 ubyte attributes; // underline, strike through, display on new block 20611 20612 version(Windows) 20613 const(wchar)[] content; 20614 else 20615 const(char)[] content; // this should NEVER have a newline, except at the end 20616 20617 RenderedComponent[] rendered; // entirely controlled by [rerender] 20618 20619 // could prolly put some spacing around it too like margin / padding 20620 20621 this(Color f, Color b, OperatingSystemFont font, ubyte attr, const(char)[] c) 20622 in { assert(font !is null); 20623 assert(!font.isNull); } 20624 do 20625 { 20626 this.foreground = f; 20627 this.background = b; 20628 this.font = font; 20629 20630 this.attributes = attr; 20631 version(Windows) { 20632 auto conversionFlags = 0;//WindowsStringConversionFlags.convertNewLines; 20633 auto sz = sizeOfConvertedWstring(c, conversionFlags); 20634 auto buffer = new wchar[](sz); 20635 this.content = makeWindowsString(c, buffer, conversionFlags); 20636 } else { 20637 this.content = c.dup; 20638 } 20639 } 20640 20641 void draw(ScreenPainter painter) { 20642 painter.setFont(this.font); 20643 painter.outlineColor = this.foreground; 20644 painter.fillColor = Color.transparent; 20645 foreach(rendered; this.rendered) { 20646 // the component works in term of baseline, 20647 // but the painter works in term of upper left bounding box 20648 // so need to translate that 20649 20650 if(this.background.a) { 20651 painter.fillColor = this.background; 20652 painter.outlineColor = this.background; 20653 20654 painter.drawRectangle(Point(rendered.startX, rendered.startY - this.font.ascent), Size(rendered.width, this.font.height)); 20655 20656 painter.outlineColor = this.foreground; 20657 painter.fillColor = Color.transparent; 20658 } 20659 20660 painter.drawText(Point(rendered.startX, rendered.startY - this.font.ascent), rendered.slice); 20661 20662 // FIXME: strike through, underline, highlight selection, etc. 20663 } 20664 } 20665 } 20666 20667 // I could split the parts into words on render 20668 // for easier word-wrap, each one being an unbreakable "inline-block" 20669 private TextFlowComponent[] parts; 20670 private int needsRerenderFrom; 20671 20672 void addPart(Color f, Color b, OperatingSystemFont font, ubyte attr, const(char)[] c) { 20673 // FIXME: needsRerenderFrom. Basically if the bounding box and baseline is the same as the previous thing, it can prolly just stop. 20674 parts ~= new TextFlowComponent(f, b, font, attr, c); 20675 } 20676 20677 static struct RenderedComponent { 20678 int startX; 20679 int startY; 20680 short width; 20681 // 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! 20682 // for individual chars in here you've gotta process on demand 20683 version(Windows) 20684 const(wchar)[] slice; 20685 else 20686 const(char)[] slice; 20687 } 20688 20689 20690 void rerender(Rectangle boundingBox) { 20691 Point baseline = boundingBox.upperLeft; 20692 20693 this.boundingBox.left = boundingBox.left; 20694 this.boundingBox.top = boundingBox.top; 20695 20696 auto remainingParts = parts; 20697 20698 int largestX; 20699 20700 20701 foreach(part; parts) 20702 part.font.prepareContext(window); 20703 scope(exit) 20704 foreach(part; parts) 20705 part.font.releaseContext(); 20706 20707 calculateNextLine: 20708 20709 int nextLineHeight = 0; 20710 int nextBiggestDescent = 0; 20711 20712 foreach(part; remainingParts) { 20713 auto height = part.font.ascent; 20714 if(height > nextLineHeight) 20715 nextLineHeight = height; 20716 if(part.font.descent > nextBiggestDescent) 20717 nextBiggestDescent = part.font.descent; 20718 if(part.content.length && part.content[$-1] == '\n') 20719 break; 20720 } 20721 20722 baseline.y += nextLineHeight; 20723 auto lineStart = baseline; 20724 20725 while(remainingParts.length) { 20726 remainingParts[0].rendered = null; 20727 20728 bool eol; 20729 if(remainingParts[0].content.length && remainingParts[0].content[$-1] == '\n') 20730 eol = true; 20731 20732 // FIXME: word wrap 20733 auto font = remainingParts[0].font; 20734 auto slice = remainingParts[0].content[0 .. $ - (eol ? 1 : 0)]; 20735 auto width = font.stringWidth(slice, window); 20736 remainingParts[0].rendered ~= RenderedComponent(baseline.x, baseline.y, cast(short) width, slice); 20737 20738 remainingParts = remainingParts[1 .. $]; 20739 baseline.x += width; 20740 20741 if(eol) { 20742 baseline.y += nextBiggestDescent; 20743 if(baseline.x > largestX) 20744 largestX = baseline.x; 20745 baseline.x = lineStart.x; 20746 goto calculateNextLine; 20747 } 20748 } 20749 20750 if(baseline.x > largestX) 20751 largestX = baseline.x; 20752 20753 this.boundingBox.right = largestX; 20754 this.boundingBox.bottom = baseline.y; 20755 } 20756 20757 // you must call rerender first! 20758 void draw(ScreenPainter painter) { 20759 foreach(part; parts) { 20760 part.draw(painter); 20761 } 20762 } 20763 20764 struct IdentifyResult { 20765 TextFlowComponent part; 20766 int charIndexInPart; 20767 int totalCharIndex = -1; // if this is -1, it just means the end 20768 20769 Rectangle boundingBox; 20770 } 20771 20772 IdentifyResult identify(Point pt, bool exact = false) { 20773 if(parts.length == 0) 20774 return IdentifyResult(null, 0); 20775 20776 if(pt.y < boundingBox.top) { 20777 if(exact) 20778 return IdentifyResult(null, 1); 20779 return IdentifyResult(parts[0], 0); 20780 } 20781 if(pt.y > boundingBox.bottom) { 20782 if(exact) 20783 return IdentifyResult(null, 2); 20784 return IdentifyResult(parts[$-1], cast(int) parts[$-1].content.length); 20785 } 20786 20787 int tci = 0; 20788 20789 // I should probably like binary search this or something... 20790 foreach(ref part; parts) { 20791 foreach(rendered; part.rendered) { 20792 auto rect = Rectangle(rendered.startX, rendered.startY - part.font.ascent, rendered.startX + rendered.width, rendered.startY + part.font.descent); 20793 if(rect.contains(pt)) { 20794 auto x = pt.x - rendered.startX; 20795 auto estimatedIdx = x / part.font.averageWidth; 20796 20797 if(estimatedIdx < 0) 20798 estimatedIdx = 0; 20799 20800 if(estimatedIdx > rendered.slice.length) 20801 estimatedIdx = cast(int) rendered.slice.length; 20802 20803 int idx; 20804 int x1, x2; 20805 if(part.font.isMonospace) { 20806 auto w = part.font.averageWidth; 20807 if(!exact && x > (estimatedIdx + 1) * w) 20808 return IdentifyResult(null, 4); 20809 idx = estimatedIdx; 20810 x1 = idx * w; 20811 x2 = (idx + 1) * w; 20812 } else { 20813 idx = estimatedIdx; 20814 20815 part.font.prepareContext(window); 20816 scope(exit) part.font.releaseContext(); 20817 20818 // int iterations; 20819 20820 while(true) { 20821 // iterations++; 20822 x1 = idx ? part.font.stringWidth(rendered.slice[0 .. idx - 1]) : 0; 20823 x2 = part.font.stringWidth(rendered.slice[0 .. idx]); // should be the maximum since `averageWidth` kinda lies. 20824 20825 x1 += rendered.startX; 20826 x2 += rendered.startX; 20827 20828 if(pt.x < x1) { 20829 if(idx == 0) { 20830 if(exact) 20831 return IdentifyResult(null, 6); 20832 else 20833 break; 20834 } 20835 idx--; 20836 } else if(pt.x > x2) { 20837 idx++; 20838 if(idx > rendered.slice.length) { 20839 if(exact) 20840 return IdentifyResult(null, 5); 20841 else 20842 break; 20843 } 20844 } else if(pt.x >= x1 && pt.x <= x2) { 20845 if(idx) 20846 idx--; // point it at the original index 20847 break; // we fit 20848 } 20849 } 20850 20851 // writeln(iterations) 20852 } 20853 20854 20855 return IdentifyResult(part, idx, tci + idx, Rectangle(x1, rect.top, x2, rect.bottom)); // FIXME: utf-8? 20856 } 20857 } 20858 tci += cast(int) part.content.length; // FIXME: utf-8? 20859 } 20860 return IdentifyResult(null, 3); 20861 } 20862 20863 Rectangle boundingBox; // only set after [rerender] 20864 20865 // text will be positioned around the exclusion zone 20866 static struct ExclusionZone { 20867 20868 } 20869 20870 ExclusionZone[] exclusionZones; 20871 } 20872 20873 20874 // Don't use this yet. When I'm happy with it, I will move it to the 20875 // regular module namespace. 20876 mixin template ExperimentalTextComponent() { 20877 20878 static: 20879 20880 alias Rectangle = arsd.color.Rectangle; 20881 20882 struct ForegroundColor { 20883 Color color; 20884 alias color this; 20885 20886 this(Color c) { 20887 color = c; 20888 } 20889 20890 this(int r, int g, int b, int a = 255) { 20891 color = Color(r, g, b, a); 20892 } 20893 20894 static ForegroundColor opDispatch(string s)() if(__traits(compiles, ForegroundColor(mixin("Color." ~ s)))) { 20895 return ForegroundColor(mixin("Color." ~ s)); 20896 } 20897 } 20898 20899 struct BackgroundColor { 20900 Color color; 20901 alias color this; 20902 20903 this(Color c) { 20904 color = c; 20905 } 20906 20907 this(int r, int g, int b, int a = 255) { 20908 color = Color(r, g, b, a); 20909 } 20910 20911 static BackgroundColor opDispatch(string s)() if(__traits(compiles, BackgroundColor(mixin("Color." ~ s)))) { 20912 return BackgroundColor(mixin("Color." ~ s)); 20913 } 20914 } 20915 20916 static class InlineElement { 20917 string text; 20918 20919 BlockElement containingBlock; 20920 20921 Color color = Color.black; 20922 Color backgroundColor = Color.transparent; 20923 ushort styles; 20924 20925 string font; 20926 int fontSize; 20927 20928 int lineHeight; 20929 20930 void* identifier; 20931 20932 Rectangle boundingBox; 20933 int[] letterXs; // FIXME: maybe i should do bounding boxes for every character 20934 20935 bool isMergeCompatible(InlineElement other) { 20936 return 20937 containingBlock is other.containingBlock && 20938 color == other.color && 20939 backgroundColor == other.backgroundColor && 20940 styles == other.styles && 20941 font == other.font && 20942 fontSize == other.fontSize && 20943 lineHeight == other.lineHeight && 20944 true; 20945 } 20946 20947 int xOfIndex(size_t index) { 20948 if(index < letterXs.length) 20949 return letterXs[index]; 20950 else 20951 return boundingBox.right; 20952 } 20953 20954 InlineElement clone() { 20955 auto ie = new InlineElement(); 20956 ie.tupleof = this.tupleof; 20957 return ie; 20958 } 20959 20960 InlineElement getPreviousInlineElement() { 20961 InlineElement prev = null; 20962 foreach(ie; this.containingBlock.parts) { 20963 if(ie is this) 20964 break; 20965 prev = ie; 20966 } 20967 if(prev is null) { 20968 BlockElement pb; 20969 BlockElement cb = this.containingBlock; 20970 moar: 20971 foreach(ie; this.containingBlock.containingLayout.blocks) { 20972 if(ie is cb) 20973 break; 20974 pb = ie; 20975 } 20976 if(pb is null) 20977 return null; 20978 if(pb.parts.length == 0) { 20979 cb = pb; 20980 goto moar; 20981 } 20982 20983 prev = pb.parts[$-1]; 20984 20985 } 20986 return prev; 20987 } 20988 20989 InlineElement getNextInlineElement() { 20990 InlineElement next = null; 20991 foreach(idx, ie; this.containingBlock.parts) { 20992 if(ie is this) { 20993 if(idx + 1 < this.containingBlock.parts.length) 20994 next = this.containingBlock.parts[idx + 1]; 20995 break; 20996 } 20997 } 20998 if(next is null) { 20999 BlockElement n; 21000 foreach(idx, ie; this.containingBlock.containingLayout.blocks) { 21001 if(ie is this.containingBlock) { 21002 if(idx + 1 < this.containingBlock.containingLayout.blocks.length) 21003 n = this.containingBlock.containingLayout.blocks[idx + 1]; 21004 break; 21005 } 21006 } 21007 if(n is null) 21008 return null; 21009 21010 if(n.parts.length) 21011 next = n.parts[0]; 21012 else {} // FIXME 21013 21014 } 21015 return next; 21016 } 21017 21018 } 21019 21020 // Block elements are used entirely for positioning inline elements, 21021 // which are the things that are actually drawn. 21022 class BlockElement { 21023 InlineElement[] parts; 21024 uint alignment; 21025 21026 int whiteSpace; // pre, pre-wrap, wrap 21027 21028 TextLayout containingLayout; 21029 21030 // inputs 21031 Point where; 21032 Size minimumSize; 21033 Size maximumSize; 21034 Rectangle[] excludedBoxes; // like if you want it to write around a floated image or something. Coordinates are relative to the bounding box. 21035 void* identifier; 21036 21037 Rectangle margin; 21038 Rectangle padding; 21039 21040 // outputs 21041 Rectangle[] boundingBoxes; 21042 } 21043 21044 struct TextIdentifyResult { 21045 InlineElement element; 21046 int offset; 21047 21048 private TextIdentifyResult fixupNewline() { 21049 if(element !is null && offset < element.text.length && element.text[offset] == '\n') { 21050 offset--; 21051 } else if(element !is null && offset == element.text.length && element.text.length > 1 && element.text[$-1] == '\n') { 21052 offset--; 21053 } 21054 return this; 21055 } 21056 } 21057 21058 class TextLayout { 21059 BlockElement[] blocks; 21060 Rectangle boundingBox_; 21061 Rectangle boundingBox() { return boundingBox_; } 21062 void boundingBox(Rectangle r) { 21063 if(r != boundingBox_) { 21064 boundingBox_ = r; 21065 layoutInvalidated = true; 21066 } 21067 } 21068 21069 Rectangle contentBoundingBox() { 21070 Rectangle r; 21071 foreach(block; blocks) 21072 foreach(ie; block.parts) { 21073 if(ie.boundingBox.right > r.right) 21074 r.right = ie.boundingBox.right; 21075 if(ie.boundingBox.bottom > r.bottom) 21076 r.bottom = ie.boundingBox.bottom; 21077 } 21078 return r; 21079 } 21080 21081 BlockElement[] getBlocks() { 21082 return blocks; 21083 } 21084 21085 InlineElement[] getTexts() { 21086 InlineElement[] elements; 21087 foreach(block; blocks) 21088 elements ~= block.parts; 21089 return elements; 21090 } 21091 21092 string getPlainText() { 21093 string text; 21094 foreach(block; blocks) 21095 foreach(part; block.parts) 21096 text ~= part.text; 21097 return text; 21098 } 21099 21100 string getHtml() { 21101 return null; // FIXME 21102 } 21103 21104 this(Rectangle boundingBox) { 21105 this.boundingBox = boundingBox; 21106 } 21107 21108 BlockElement addBlock(InlineElement after = null, Rectangle margin = Rectangle(0, 0, 0, 0), Rectangle padding = Rectangle(0, 0, 0, 0)) { 21109 auto be = new BlockElement(); 21110 be.containingLayout = this; 21111 if(after is null) 21112 blocks ~= be; 21113 else { 21114 foreach(idx, b; blocks) { 21115 if(b is after.containingBlock) { 21116 blocks = blocks[0 .. idx + 1] ~ be ~ blocks[idx + 1 .. $]; 21117 break; 21118 } 21119 } 21120 } 21121 return be; 21122 } 21123 21124 void clear() { 21125 blocks = null; 21126 selectionStart = selectionEnd = caret = Caret.init; 21127 } 21128 21129 void addText(Args...)(Args args) { 21130 if(blocks.length == 0) 21131 addBlock(); 21132 21133 InlineElement ie = new InlineElement(); 21134 foreach(idx, arg; args) { 21135 static if(is(typeof(arg) == ForegroundColor)) 21136 ie.color = arg; 21137 else static if(is(typeof(arg) == TextFormat)) { 21138 if(arg & 0x8000) // ~TextFormat.something turns it off 21139 ie.styles &= arg; 21140 else 21141 ie.styles |= arg; 21142 } else static if(is(typeof(arg) == string)) { 21143 static if(idx == 0 && args.length > 1) 21144 static assert(0, "Put styles before the string."); 21145 size_t lastLineIndex; 21146 foreach(cidx, char a; arg) { 21147 if(a == '\n') { 21148 ie.text = arg[lastLineIndex .. cidx + 1]; 21149 lastLineIndex = cidx + 1; 21150 ie.containingBlock = blocks[$-1]; 21151 blocks[$-1].parts ~= ie.clone; 21152 ie.text = null; 21153 } else { 21154 21155 } 21156 } 21157 21158 ie.text = arg[lastLineIndex .. $]; 21159 ie.containingBlock = blocks[$-1]; 21160 blocks[$-1].parts ~= ie.clone; 21161 caret = Caret(this, blocks[$-1].parts[$-1], cast(int) blocks[$-1].parts[$-1].text.length); 21162 } 21163 } 21164 21165 invalidateLayout(); 21166 } 21167 21168 void tryMerge(InlineElement into, InlineElement what) { 21169 if(!into.isMergeCompatible(what)) { 21170 return; // cannot merge, different configs 21171 } 21172 21173 // cool, can merge, bring text together... 21174 into.text ~= what.text; 21175 21176 // and remove what 21177 for(size_t a = 0; a < what.containingBlock.parts.length; a++) { 21178 if(what.containingBlock.parts[a] is what) { 21179 for(size_t i = a; i < what.containingBlock.parts.length - 1; i++) 21180 what.containingBlock.parts[i] = what.containingBlock.parts[i + 1]; 21181 what.containingBlock.parts = what.containingBlock.parts[0 .. $-1]; 21182 21183 } 21184 } 21185 21186 // FIXME: ensure no other carets have a reference to it 21187 } 21188 21189 /// exact = true means return null if no match. otherwise, get the closest one that makes sense for a mouse click. 21190 TextIdentifyResult identify(int x, int y, bool exact = false) { 21191 TextIdentifyResult inexactMatch; 21192 foreach(block; blocks) { 21193 foreach(part; block.parts) { 21194 if(x >= part.boundingBox.left && x < part.boundingBox.right && y >= part.boundingBox.top && y < part.boundingBox.bottom) { 21195 21196 // FIXME binary search 21197 int tidx; 21198 int lastX; 21199 foreach_reverse(idxo, lx; part.letterXs) { 21200 int idx = cast(int) idxo; 21201 if(lx <= x) { 21202 if(lastX && lastX - x < x - lx) 21203 tidx = idx + 1; 21204 else 21205 tidx = idx; 21206 break; 21207 } 21208 lastX = lx; 21209 } 21210 21211 return TextIdentifyResult(part, tidx).fixupNewline; 21212 } else if(!exact) { 21213 // we're not in the box, but are we on the same line? 21214 if(y >= part.boundingBox.top && y < part.boundingBox.bottom) 21215 inexactMatch = TextIdentifyResult(part, x == 0 ? 0 : cast(int) part.text.length); 21216 } 21217 } 21218 } 21219 21220 if(!exact && inexactMatch is TextIdentifyResult.init && blocks.length && blocks[$-1].parts.length) 21221 return TextIdentifyResult(blocks[$-1].parts[$-1], cast(int) blocks[$-1].parts[$-1].text.length).fixupNewline; 21222 21223 return exact ? TextIdentifyResult.init : inexactMatch.fixupNewline; 21224 } 21225 21226 void moveCaretToPixelCoordinates(int x, int y) { 21227 auto result = identify(x, y); 21228 caret.inlineElement = result.element; 21229 caret.offset = result.offset; 21230 } 21231 21232 void selectToPixelCoordinates(int x, int y) { 21233 auto result = identify(x, y); 21234 21235 if(y < caretLastDrawnY1) { 21236 // on a previous line, carat is selectionEnd 21237 selectionEnd = caret; 21238 21239 selectionStart = Caret(this, result.element, result.offset); 21240 } else if(y > caretLastDrawnY2) { 21241 // on a later line 21242 selectionStart = caret; 21243 21244 selectionEnd = Caret(this, result.element, result.offset); 21245 } else { 21246 // on the same line... 21247 if(x <= caretLastDrawnX) { 21248 selectionEnd = caret; 21249 selectionStart = Caret(this, result.element, result.offset); 21250 } else { 21251 selectionStart = caret; 21252 selectionEnd = Caret(this, result.element, result.offset); 21253 } 21254 21255 } 21256 } 21257 21258 21259 /// Call this if the inputs change. It will reflow everything 21260 void redoLayout(ScreenPainter painter) { 21261 //painter.setClipRectangle(boundingBox); 21262 auto pos = Point(boundingBox.left, boundingBox.top); 21263 21264 int lastHeight; 21265 void nl() { 21266 pos.x = boundingBox.left; 21267 pos.y += lastHeight; 21268 } 21269 foreach(block; blocks) { 21270 nl(); 21271 foreach(part; block.parts) { 21272 part.letterXs = null; 21273 21274 auto size = painter.textSize(part.text); 21275 version(Windows) 21276 if(part.text.length && part.text[$-1] == '\n') 21277 size.height /= 2; // windows counts the new line at the end, but we don't want that 21278 21279 part.boundingBox = Rectangle(pos.x, pos.y, pos.x + size.width, pos.y + size.height); 21280 21281 foreach(idx, char c; part.text) { 21282 // FIXME: unicode 21283 part.letterXs ~= painter.textSize(part.text[0 .. idx]).width + pos.x; 21284 } 21285 21286 pos.x += size.width; 21287 if(pos.x >= boundingBox.right) { 21288 pos.y += size.height; 21289 pos.x = boundingBox.left; 21290 lastHeight = 0; 21291 } else { 21292 lastHeight = size.height; 21293 } 21294 21295 if(part.text.length && part.text[$-1] == '\n') 21296 nl(); 21297 } 21298 } 21299 21300 layoutInvalidated = false; 21301 } 21302 21303 bool layoutInvalidated = true; 21304 void invalidateLayout() { 21305 layoutInvalidated = true; 21306 } 21307 21308 // FIXME: caret can remain sometimes when inserting 21309 // FIXME: inserting at the beginning once you already have something can eff it up. 21310 void drawInto(ScreenPainter painter, bool focused = false) { 21311 if(layoutInvalidated) 21312 redoLayout(painter); 21313 foreach(block; blocks) { 21314 foreach(part; block.parts) { 21315 painter.outlineColor = part.color; 21316 painter.fillColor = part.backgroundColor; 21317 21318 auto pos = part.boundingBox.upperLeft; 21319 auto size = part.boundingBox.size; 21320 21321 painter.drawText(pos, part.text); 21322 if(part.styles & TextFormat.underline) 21323 painter.drawLine(Point(pos.x, pos.y + size.height - 4), Point(pos.x + size.width, pos.y + size.height - 4)); 21324 if(part.styles & TextFormat.strikethrough) 21325 painter.drawLine(Point(pos.x, pos.y + size.height/2), Point(pos.x + size.width, pos.y + size.height/2)); 21326 } 21327 } 21328 21329 // on every redraw, I will force the caret to be 21330 // redrawn too, in order to eliminate perceived lag 21331 // when moving around with the mouse. 21332 eraseCaret(painter); 21333 21334 if(focused) { 21335 highlightSelection(painter); 21336 drawCaret(painter); 21337 } 21338 } 21339 21340 Color selectionXorColor = Color(255, 255, 127); 21341 21342 void highlightSelection(ScreenPainter painter) { 21343 if(selectionStart is selectionEnd) 21344 return; // no selection 21345 21346 if(selectionStart.inlineElement is null) return; 21347 if(selectionEnd.inlineElement is null) return; 21348 21349 assert(selectionStart.inlineElement !is null); 21350 assert(selectionEnd.inlineElement !is null); 21351 21352 painter.rasterOp = RasterOp.xor; 21353 painter.outlineColor = Color.transparent; 21354 painter.fillColor = selectionXorColor; 21355 21356 auto at = selectionStart.inlineElement; 21357 auto atOffset = selectionStart.offset; 21358 bool done; 21359 while(at) { 21360 auto box = at.boundingBox; 21361 if(atOffset < at.letterXs.length) 21362 box.left = at.letterXs[atOffset]; 21363 21364 if(at is selectionEnd.inlineElement) { 21365 if(selectionEnd.offset < at.letterXs.length) 21366 box.right = at.letterXs[selectionEnd.offset]; 21367 done = true; 21368 } 21369 21370 painter.drawRectangle(box.upperLeft, box.width, box.height); 21371 21372 if(done) 21373 break; 21374 21375 at = at.getNextInlineElement(); 21376 atOffset = 0; 21377 } 21378 } 21379 21380 int caretLastDrawnX, caretLastDrawnY1, caretLastDrawnY2; 21381 bool caretShowingOnScreen = false; 21382 void drawCaret(ScreenPainter painter) { 21383 //painter.setClipRectangle(boundingBox); 21384 int x, y1, y2; 21385 if(caret.inlineElement is null) { 21386 x = boundingBox.left; 21387 y1 = boundingBox.top + 2; 21388 y2 = boundingBox.top + painter.fontHeight; 21389 } else { 21390 x = caret.inlineElement.xOfIndex(caret.offset); 21391 y1 = caret.inlineElement.boundingBox.top + 2; 21392 y2 = caret.inlineElement.boundingBox.bottom - 2; 21393 } 21394 21395 if(caretShowingOnScreen && (x != caretLastDrawnX || y1 != caretLastDrawnY1 || y2 != caretLastDrawnY2)) 21396 eraseCaret(painter); 21397 21398 painter.pen = Pen(Color.white, 1); 21399 painter.rasterOp = RasterOp.xor; 21400 painter.drawLine( 21401 Point(x, y1), 21402 Point(x, y2) 21403 ); 21404 painter.rasterOp = RasterOp.normal; 21405 caretShowingOnScreen = !caretShowingOnScreen; 21406 21407 if(caretShowingOnScreen) { 21408 caretLastDrawnX = x; 21409 caretLastDrawnY1 = y1; 21410 caretLastDrawnY2 = y2; 21411 } 21412 } 21413 21414 Rectangle caretBoundingBox() { 21415 int x, y1, y2; 21416 if(caret.inlineElement is null) { 21417 x = boundingBox.left; 21418 y1 = boundingBox.top + 2; 21419 y2 = boundingBox.top + 16; 21420 } else { 21421 x = caret.inlineElement.xOfIndex(caret.offset); 21422 y1 = caret.inlineElement.boundingBox.top + 2; 21423 y2 = caret.inlineElement.boundingBox.bottom - 2; 21424 } 21425 21426 return Rectangle(x, y1, x + 1, y2); 21427 } 21428 21429 void eraseCaret(ScreenPainter painter) { 21430 //painter.setClipRectangle(boundingBox); 21431 if(!caretShowingOnScreen) return; 21432 painter.pen = Pen(Color.white, 1); 21433 painter.rasterOp = RasterOp.xor; 21434 painter.drawLine( 21435 Point(caretLastDrawnX, caretLastDrawnY1), 21436 Point(caretLastDrawnX, caretLastDrawnY2) 21437 ); 21438 21439 caretShowingOnScreen = false; 21440 painter.rasterOp = RasterOp.normal; 21441 } 21442 21443 /// Caret movement api 21444 /// These should give the user a logical result based on what they see on screen... 21445 /// thus they locate predominately by *pixels* not char index. (These will generally coincide with monospace fonts tho!) 21446 void moveUp() { 21447 if(caret.inlineElement is null) return; 21448 auto x = caret.inlineElement.xOfIndex(caret.offset); 21449 auto y = caret.inlineElement.boundingBox.top + 2; 21450 21451 y -= caret.inlineElement.boundingBox.bottom - caret.inlineElement.boundingBox.top; 21452 if(y < 0) 21453 return; 21454 21455 auto i = identify(x, y); 21456 21457 if(i.element) { 21458 caret.inlineElement = i.element; 21459 caret.offset = i.offset; 21460 } 21461 } 21462 void moveDown() { 21463 if(caret.inlineElement is null) return; 21464 auto x = caret.inlineElement.xOfIndex(caret.offset); 21465 auto y = caret.inlineElement.boundingBox.bottom - 2; 21466 21467 y += caret.inlineElement.boundingBox.bottom - caret.inlineElement.boundingBox.top; 21468 21469 auto i = identify(x, y); 21470 if(i.element) { 21471 caret.inlineElement = i.element; 21472 caret.offset = i.offset; 21473 } 21474 } 21475 void moveLeft() { 21476 if(caret.inlineElement is null) return; 21477 if(caret.offset) 21478 caret.offset--; 21479 else { 21480 auto p = caret.inlineElement.getPreviousInlineElement(); 21481 if(p) { 21482 caret.inlineElement = p; 21483 if(p.text.length && p.text[$-1] == '\n') 21484 caret.offset = cast(int) p.text.length - 1; 21485 else 21486 caret.offset = cast(int) p.text.length; 21487 } 21488 } 21489 } 21490 void moveRight() { 21491 if(caret.inlineElement is null) return; 21492 if(caret.offset < caret.inlineElement.text.length && caret.inlineElement.text[caret.offset] != '\n') { 21493 caret.offset++; 21494 } else { 21495 auto p = caret.inlineElement.getNextInlineElement(); 21496 if(p) { 21497 caret.inlineElement = p; 21498 caret.offset = 0; 21499 } 21500 } 21501 } 21502 void moveHome() { 21503 if(caret.inlineElement is null) return; 21504 auto x = 0; 21505 auto y = caret.inlineElement.boundingBox.top + 2; 21506 21507 auto i = identify(x, y); 21508 21509 if(i.element) { 21510 caret.inlineElement = i.element; 21511 caret.offset = i.offset; 21512 } 21513 } 21514 void moveEnd() { 21515 if(caret.inlineElement is null) return; 21516 auto x = int.max; 21517 auto y = caret.inlineElement.boundingBox.top + 2; 21518 21519 auto i = identify(x, y); 21520 21521 if(i.element) { 21522 caret.inlineElement = i.element; 21523 caret.offset = i.offset; 21524 } 21525 21526 } 21527 void movePageUp(ref Caret caret) {} 21528 void movePageDown(ref Caret caret) {} 21529 21530 void moveDocumentStart(ref Caret caret) { 21531 if(blocks.length && blocks[0].parts.length) 21532 caret = Caret(this, blocks[0].parts[0], 0); 21533 else 21534 caret = Caret.init; 21535 } 21536 21537 void moveDocumentEnd(ref Caret caret) { 21538 if(blocks.length) { 21539 auto parts = blocks[$-1].parts; 21540 if(parts.length) { 21541 caret = Caret(this, parts[$-1], cast(int) parts[$-1].text.length); 21542 } else { 21543 caret = Caret.init; 21544 } 21545 } else 21546 caret = Caret.init; 21547 } 21548 21549 void deleteSelection() { 21550 if(selectionStart is selectionEnd) 21551 return; 21552 21553 if(selectionStart.inlineElement is null) return; 21554 if(selectionEnd.inlineElement is null) return; 21555 21556 assert(selectionStart.inlineElement !is null); 21557 assert(selectionEnd.inlineElement !is null); 21558 21559 auto at = selectionStart.inlineElement; 21560 21561 if(selectionEnd.inlineElement is at) { 21562 // same element, need to chop out 21563 at.text = at.text[0 .. selectionStart.offset] ~ at.text[selectionEnd.offset .. $]; 21564 at.letterXs = at.letterXs[0 .. selectionStart.offset] ~ at.letterXs[selectionEnd.offset .. $]; 21565 selectionEnd.offset -= selectionEnd.offset - selectionStart.offset; 21566 } else { 21567 // different elements, we can do it with slicing 21568 at.text = at.text[0 .. selectionStart.offset]; 21569 if(selectionStart.offset < at.letterXs.length) 21570 at.letterXs = at.letterXs[0 .. selectionStart.offset]; 21571 21572 at = at.getNextInlineElement(); 21573 21574 while(at) { 21575 if(at is selectionEnd.inlineElement) { 21576 at.text = at.text[selectionEnd.offset .. $]; 21577 if(selectionEnd.offset < at.letterXs.length) 21578 at.letterXs = at.letterXs[selectionEnd.offset .. $]; 21579 selectionEnd.offset = 0; 21580 break; 21581 } else { 21582 auto cfd = at; 21583 cfd.text = null; // delete the whole thing 21584 21585 at = at.getNextInlineElement(); 21586 21587 if(cfd.text.length == 0) { 21588 // and remove cfd 21589 for(size_t a = 0; a < cfd.containingBlock.parts.length; a++) { 21590 if(cfd.containingBlock.parts[a] is cfd) { 21591 for(size_t i = a; i < cfd.containingBlock.parts.length - 1; i++) 21592 cfd.containingBlock.parts[i] = cfd.containingBlock.parts[i + 1]; 21593 cfd.containingBlock.parts = cfd.containingBlock.parts[0 .. $-1]; 21594 21595 } 21596 } 21597 } 21598 } 21599 } 21600 } 21601 21602 caret = selectionEnd; 21603 selectNone(); 21604 21605 invalidateLayout(); 21606 21607 } 21608 21609 /// Plain text editing api. These work at the current caret inside the selected inline element. 21610 void insert(in char[] text) { 21611 foreach(dchar ch; text) 21612 insert(ch); 21613 } 21614 /// ditto 21615 void insert(dchar ch) { 21616 21617 bool selectionDeleted = false; 21618 if(selectionStart !is selectionEnd) { 21619 deleteSelection(); 21620 selectionDeleted = true; 21621 } 21622 21623 if(ch == 127) { 21624 delete_(); 21625 return; 21626 } 21627 if(ch == 8) { 21628 if(!selectionDeleted) 21629 backspace(); 21630 return; 21631 } 21632 21633 invalidateLayout(); 21634 21635 if(ch == 13) ch = 10; 21636 auto e = caret.inlineElement; 21637 if(e is null) { 21638 addText("" ~ cast(char) ch) ; // FIXME 21639 return; 21640 } 21641 21642 if(caret.offset == e.text.length) { 21643 e.text ~= cast(char) ch; // FIXME 21644 caret.offset++; 21645 if(ch == 10) { 21646 auto c = caret.inlineElement.clone; 21647 c.text = null; 21648 c.letterXs = null; 21649 insertPartAfter(c,e); 21650 caret = Caret(this, c, 0); 21651 } 21652 } else { 21653 // FIXME cast char sucks 21654 if(ch == 10) { 21655 auto c = caret.inlineElement.clone; 21656 c.text = e.text[caret.offset .. $]; 21657 if(caret.offset < c.letterXs.length) 21658 c.letterXs = e.letterXs[caret.offset .. $]; // FIXME boundingBox 21659 e.text = e.text[0 .. caret.offset] ~ cast(char) ch; 21660 if(caret.offset <= e.letterXs.length) { 21661 e.letterXs = e.letterXs[0 .. caret.offset] ~ 0; // FIXME bounding box 21662 } 21663 insertPartAfter(c,e); 21664 caret = Caret(this, c, 0); 21665 } else { 21666 e.text = e.text[0 .. caret.offset] ~ cast(char) ch ~ e.text[caret.offset .. $]; 21667 caret.offset++; 21668 } 21669 } 21670 } 21671 21672 void insertPartAfter(InlineElement what, InlineElement where) { 21673 foreach(idx, p; where.containingBlock.parts) { 21674 if(p is where) { 21675 if(idx + 1 == where.containingBlock.parts.length) 21676 where.containingBlock.parts ~= what; 21677 else 21678 where.containingBlock.parts = where.containingBlock.parts[0 .. idx + 1] ~ what ~ where.containingBlock.parts[idx + 1 .. $]; 21679 return; 21680 } 21681 } 21682 } 21683 21684 void cleanupStructures() { 21685 for(size_t i = 0; i < blocks.length; i++) { 21686 auto block = blocks[i]; 21687 for(size_t a = 0; a < block.parts.length; a++) { 21688 auto part = block.parts[a]; 21689 if(part.text.length == 0) { 21690 for(size_t b = a; b < block.parts.length - 1; b++) 21691 block.parts[b] = block.parts[b+1]; 21692 block.parts = block.parts[0 .. $-1]; 21693 } 21694 } 21695 if(block.parts.length == 0) { 21696 for(size_t a = i; a < blocks.length - 1; a++) 21697 blocks[a] = blocks[a+1]; 21698 blocks = blocks[0 .. $-1]; 21699 } 21700 } 21701 } 21702 21703 void backspace() { 21704 try_again: 21705 auto e = caret.inlineElement; 21706 if(e is null) 21707 return; 21708 if(caret.offset == 0) { 21709 auto prev = e.getPreviousInlineElement(); 21710 if(prev is null) 21711 return; 21712 auto newOffset = cast(int) prev.text.length; 21713 tryMerge(prev, e); 21714 caret.inlineElement = prev; 21715 caret.offset = prev is null ? 0 : newOffset; 21716 21717 goto try_again; 21718 } else if(caret.offset == e.text.length) { 21719 e.text = e.text[0 .. $-1]; 21720 caret.offset--; 21721 } else { 21722 e.text = e.text[0 .. caret.offset - 1] ~ e.text[caret.offset .. $]; 21723 caret.offset--; 21724 } 21725 //cleanupStructures(); 21726 21727 invalidateLayout(); 21728 } 21729 void delete_() { 21730 if(selectionStart !is selectionEnd) 21731 deleteSelection(); 21732 else { 21733 auto before = caret; 21734 moveRight(); 21735 if(caret != before) { 21736 backspace(); 21737 } 21738 } 21739 21740 invalidateLayout(); 21741 } 21742 void overstrike() {} 21743 21744 /// Selection API. See also: caret movement. 21745 void selectAll() { 21746 moveDocumentStart(selectionStart); 21747 moveDocumentEnd(selectionEnd); 21748 } 21749 bool selectNone() { 21750 if(selectionStart != selectionEnd) { 21751 selectionStart = selectionEnd = Caret.init; 21752 return true; 21753 } 21754 return false; 21755 } 21756 21757 /// Rich text editing api. These allow you to manipulate the meta data of the current element and add new elements. 21758 /// They will modify the current selection if there is one and will splice one in if needed. 21759 void changeAttributes() {} 21760 21761 21762 /// Text search api. They manipulate the selection and/or caret. 21763 void findText(string text) {} 21764 void findIndex(size_t textIndex) {} 21765 21766 // sample event handlers 21767 21768 void handleEvent(KeyEvent event) { 21769 //if(event.type == KeyEvent.Type.KeyPressed) { 21770 21771 //} 21772 } 21773 21774 void handleEvent(dchar ch) { 21775 21776 } 21777 21778 void handleEvent(MouseEvent event) { 21779 21780 } 21781 21782 bool contentEditable; // can it be edited? 21783 bool contentCaretable; // is there a caret/cursor that moves around in there? 21784 bool contentSelectable; // selectable? 21785 21786 Caret caret; 21787 Caret selectionStart; 21788 Caret selectionEnd; 21789 21790 bool insertMode; 21791 } 21792 21793 struct Caret { 21794 TextLayout layout; 21795 InlineElement inlineElement; 21796 int offset; 21797 } 21798 21799 enum TextFormat : ushort { 21800 // decorations 21801 underline = 1, 21802 strikethrough = 2, 21803 21804 // font selectors 21805 21806 bold = 0x4000 | 1, // weight 700 21807 light = 0x4000 | 2, // weight 300 21808 veryBoldOrLight = 0x4000 | 4, // weight 100 with light, weight 900 with bold 21809 // bold | light is really invalid but should give weight 500 21810 // veryBoldOrLight without one of the others should just give the default for the font; it should be ignored. 21811 21812 italic = 0x4000 | 8, 21813 smallcaps = 0x4000 | 16, 21814 } 21815 21816 void* findFont(string family, int weight, TextFormat formats) { 21817 return null; 21818 } 21819 21820 } 21821 21822 /++ 21823 $(PITFALL This is not yet stable and may break in future versions without notice.) 21824 21825 History: 21826 Added February 19, 2021 21827 +/ 21828 /// Group: drag_and_drop 21829 interface DropHandler { 21830 /++ 21831 Called when the drag enters the handler's area. 21832 +/ 21833 DragAndDropAction dragEnter(DropPackage*); 21834 /++ 21835 Called when the drag leaves the handler's area or is 21836 cancelled. You should free your resources when this is called. 21837 +/ 21838 void dragLeave(); 21839 /++ 21840 Called continually as the drag moves over the handler's area. 21841 21842 Returns: feedback to the dragger 21843 +/ 21844 DropParameters dragOver(Point pt); 21845 /++ 21846 The user dropped the data and you should process it now. You can 21847 access the data through the given [DropPackage]. 21848 +/ 21849 void drop(scope DropPackage*); 21850 /++ 21851 Called when the drop is complete. You should free whatever temporary 21852 resources you were using. It is often reasonable to simply forward 21853 this call to [dragLeave]. 21854 +/ 21855 void finish(); 21856 21857 /++ 21858 Parameters returned by [DropHandler.drop]. 21859 +/ 21860 static struct DropParameters { 21861 /++ 21862 Acceptable action over this area. 21863 +/ 21864 DragAndDropAction action; 21865 /++ 21866 Rectangle, in client coordinates, where the dragger can expect the same result during this drag session and thus need not ask again. 21867 21868 If you leave this as Rectangle.init, the dragger will continue to ask and this can waste resources. 21869 +/ 21870 Rectangle consistentWithin; 21871 } 21872 } 21873 21874 /++ 21875 History: 21876 Added February 19, 2021 21877 +/ 21878 /// Group: drag_and_drop 21879 enum DragAndDropAction { 21880 none = 0, 21881 copy, 21882 move, 21883 link, 21884 ask, 21885 custom 21886 } 21887 21888 /++ 21889 An opaque structure representing dropped data. It contains 21890 private, platform-specific data that your `drop` function 21891 should simply forward. 21892 21893 $(PITFALL This is not yet stable and may break in future versions without notice.) 21894 21895 History: 21896 Added February 19, 2021 21897 +/ 21898 /// Group: drag_and_drop 21899 struct DropPackage { 21900 /++ 21901 Lists the available formats as magic numbers. You should compare these 21902 against looked-up formats (see [DraggableData.getFormatId]) you know you support and can 21903 understand the passed data. 21904 +/ 21905 DraggableData.FormatId[] availableFormats() { 21906 version(X11) { 21907 return xFormats; 21908 } else version(Windows) { 21909 if(pDataObj is null) 21910 return null; 21911 21912 typeof(return) ret; 21913 21914 IEnumFORMATETC ef; 21915 if(pDataObj.EnumFormatEtc(DATADIR.DATADIR_GET, &ef) == S_OK) { 21916 FORMATETC fmt; 21917 ULONG fetched; 21918 while(ef.Next(1, &fmt, &fetched) == S_OK) { 21919 if(fetched == 0) 21920 break; 21921 21922 if(fmt.lindex != -1) 21923 continue; 21924 if(fmt.dwAspect != DVASPECT.DVASPECT_CONTENT) 21925 continue; 21926 if(!(fmt.tymed & TYMED.TYMED_HGLOBAL)) 21927 continue; 21928 21929 ret ~= fmt.cfFormat; 21930 } 21931 } 21932 21933 return ret; 21934 } else throw new NotYetImplementedException(); 21935 } 21936 21937 /++ 21938 Gets data from the drop and optionally accepts it. 21939 21940 Returns: 21941 void because the data is fed asynchronously through the `dg` parameter. 21942 21943 Params: 21944 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. 21945 21946 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. 21947 21948 Calling `getData` again after accepting a drop is not permitted. 21949 21950 format = the format you want, from [availableFormats]. Use [DraggableData.getFormatId] to convert from a MIME string or well-known standard format. 21951 21952 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. 21953 21954 Throws: 21955 if `format` was not compatible with the [availableFormats] or if the drop has already been accepted. 21956 21957 History: 21958 Included in first release of [DropPackage]. 21959 +/ 21960 void getData(DragAndDropAction acceptedAction, DraggableData.FormatId format, void delegate(scope ubyte[] data) dg) { 21961 version(X11) { 21962 21963 auto display = XDisplayConnection.get(); 21964 auto selectionAtom = GetAtom!"XdndSelection"(display); 21965 auto best = format; 21966 21967 static class X11GetSelectionHandler_Drop : X11GetSelectionHandler { 21968 21969 XDisplay* display; 21970 Atom selectionAtom; 21971 DraggableData.FormatId best; 21972 DraggableData.FormatId format; 21973 void delegate(scope ubyte[] data) dg; 21974 DragAndDropAction acceptedAction; 21975 Window sourceWindow; 21976 SimpleWindow win; 21977 this(XDisplay* display, SimpleWindow win, Window sourceWindow, DraggableData.FormatId format, Atom selectionAtom, DraggableData.FormatId best, void delegate(scope ubyte[] data) dg, DragAndDropAction acceptedAction) { 21978 this.display = display; 21979 this.win = win; 21980 this.sourceWindow = sourceWindow; 21981 this.format = format; 21982 this.selectionAtom = selectionAtom; 21983 this.best = best; 21984 this.dg = dg; 21985 this.acceptedAction = acceptedAction; 21986 } 21987 21988 21989 mixin X11GetSelectionHandler_Basics; 21990 21991 void handleData(Atom target, in ubyte[] data) { 21992 //if(target == GetAtom!"UTF8_STRING"(XDisplayConnection.get) || target == XA_STRING || target == GetAtom!"text/plain"(XDisplayConnection.get)) 21993 21994 dg(cast(ubyte[]) data); 21995 21996 if(acceptedAction != DragAndDropAction.none) { 21997 auto display = XDisplayConnection.get; 21998 21999 XClientMessageEvent xclient; 22000 22001 xclient.type = EventType.ClientMessage; 22002 xclient.window = sourceWindow; 22003 xclient.message_type = GetAtom!"XdndFinished"(display); 22004 xclient.format = 32; 22005 xclient.data.l[0] = win.impl.window; 22006 xclient.data.l[1] = 1; // drop successful 22007 xclient.data.l[2] = dndActionAtom(display, acceptedAction); 22008 22009 XSendEvent( 22010 display, 22011 sourceWindow, 22012 false, 22013 EventMask.NoEventMask, 22014 cast(XEvent*) &xclient 22015 ); 22016 22017 XFlush(display); 22018 } 22019 } 22020 22021 Atom findBestFormat(Atom[] answer) { 22022 Atom best = None; 22023 foreach(option; answer) { 22024 if(option == format) { 22025 best = option; 22026 break; 22027 } 22028 /* 22029 if(option == GetAtom!"UTF8_STRING"(XDisplayConnection.get)) { 22030 best = option; 22031 break; 22032 } else if(option == XA_STRING) { 22033 best = option; 22034 } else if(option == GetAtom!"text/plain"(XDisplayConnection.get)) { 22035 best = option; 22036 } 22037 */ 22038 } 22039 return best; 22040 } 22041 } 22042 22043 win.impl.getSelectionHandlers[selectionAtom] = new X11GetSelectionHandler_Drop(display, win, sourceWindow, format, selectionAtom, best, dg, acceptedAction); 22044 22045 XConvertSelection(display, selectionAtom, best, GetAtom!("SDD_DATA", true)(display), win.impl.window, dataTimestamp); 22046 22047 } else version(Windows) { 22048 22049 // clean up like DragLeave 22050 // pass effect back up 22051 22052 FORMATETC t; 22053 assert(format >= 0 && format <= ushort.max); 22054 t.cfFormat = cast(ushort) format; 22055 t.lindex = -1; 22056 t.dwAspect = DVASPECT.DVASPECT_CONTENT; 22057 t.tymed = TYMED.TYMED_HGLOBAL; 22058 22059 STGMEDIUM m; 22060 22061 if(pDataObj.GetData(&t, &m) != S_OK) { 22062 // fail 22063 } else { 22064 // succeed, take the data and clean up 22065 22066 // FIXME: ensure it is legit HGLOBAL 22067 auto handle = m.hGlobal; 22068 22069 if(handle) { 22070 auto sz = GlobalSize(handle); 22071 if(auto ptr = cast(ubyte*) GlobalLock(handle)) { 22072 scope(exit) GlobalUnlock(handle); 22073 scope(exit) GlobalFree(handle); 22074 22075 auto data = ptr[0 .. sz]; 22076 22077 dg(data); 22078 } 22079 } 22080 } 22081 } 22082 } 22083 22084 private: 22085 22086 version(X11) { 22087 SimpleWindow win; 22088 Window sourceWindow; 22089 Time dataTimestamp; 22090 22091 Atom[] xFormats; 22092 } 22093 version(Windows) { 22094 IDataObject pDataObj; 22095 } 22096 } 22097 22098 /++ 22099 A generic helper base class for making a drop handler with a preference list of custom types. 22100 This is the base for [TextDropHandler] and [FilesDropHandler] and you can use it for your own 22101 droppers too. 22102 22103 It assumes the whole window it used, but you can subclass to change that. 22104 22105 $(PITFALL This is not yet stable and may break in future versions without notice.) 22106 22107 History: 22108 Added February 19, 2021 22109 +/ 22110 /// Group: drag_and_drop 22111 class GenericDropHandlerBase : DropHandler { 22112 // no fancy state here so no need to do anything here 22113 void finish() { } 22114 void dragLeave() { } 22115 22116 private DragAndDropAction acceptedAction; 22117 private DraggableData.FormatId acceptedFormat; 22118 private void delegate(scope ubyte[]) acceptedHandler; 22119 22120 struct FormatHandler { 22121 DraggableData.FormatId format; 22122 void delegate(scope ubyte[]) handler; 22123 } 22124 22125 protected abstract FormatHandler[] formatHandlers(); 22126 22127 DragAndDropAction dragEnter(DropPackage* pkg) { 22128 debug(sdpy_dnd) { foreach(fmt; pkg.availableFormats()) writeln(fmt, " ", DraggableData.getFormatName(fmt)); } 22129 foreach(fmt; formatHandlers()) 22130 foreach(f; pkg.availableFormats()) 22131 if(f == fmt.format) { 22132 acceptedFormat = f; 22133 acceptedHandler = fmt.handler; 22134 return acceptedAction = DragAndDropAction.copy; 22135 } 22136 return acceptedAction = DragAndDropAction.none; 22137 } 22138 DropParameters dragOver(Point pt) { 22139 return DropParameters(acceptedAction); 22140 } 22141 22142 void drop(scope DropPackage* dropPackage) { 22143 if(!acceptedFormat || acceptedHandler is null) { 22144 debug(sdpy_dnd) { writeln("drop called w/ handler ", acceptedHandler, " and format ", acceptedFormat); } 22145 return; // prolly shouldn't happen anyway... 22146 } 22147 22148 dropPackage.getData(acceptedAction, acceptedFormat, acceptedHandler); 22149 } 22150 } 22151 22152 /++ 22153 A simple handler for making your window accept drops of plain text. 22154 22155 $(PITFALL This is not yet stable and may break in future versions without notice.) 22156 22157 History: 22158 Added February 22, 2021 22159 +/ 22160 /// Group: drag_and_drop 22161 class TextDropHandler : GenericDropHandlerBase { 22162 private void delegate(in char[] text) dg; 22163 22164 /++ 22165 22166 +/ 22167 this(void delegate(in char[] text) dg) { 22168 this.dg = dg; 22169 } 22170 22171 protected override FormatHandler[] formatHandlers() { 22172 version(X11) 22173 return [ 22174 FormatHandler(GetAtom!"UTF8_STRING"(XDisplayConnection.get), &translator), 22175 FormatHandler(GetAtom!"text/plain;charset=utf-8"(XDisplayConnection.get), &translator), 22176 ]; 22177 else version(Windows) 22178 return [ 22179 FormatHandler(CF_UNICODETEXT, &translator), 22180 ]; 22181 else throw new NotYetImplementedException(); 22182 } 22183 22184 private void translator(scope ubyte[] data) { 22185 version(X11) 22186 dg(cast(char[]) data); 22187 else version(Windows) 22188 dg(makeUtf8StringFromWindowsString(cast(wchar[]) data)); 22189 } 22190 } 22191 22192 /++ 22193 A simple handler for making your window accept drops of files, issued to you as file names. 22194 22195 $(PITFALL This is not yet stable and may break in future versions without notice.) 22196 22197 History: 22198 Added February 22, 2021 22199 +/ 22200 /// Group: drag_and_drop 22201 22202 class FilesDropHandler : GenericDropHandlerBase { 22203 private void delegate(in char[][]) dg; 22204 22205 /++ 22206 22207 +/ 22208 this(void delegate(in char[][] fileNames) dg) { 22209 this.dg = dg; 22210 } 22211 22212 protected override FormatHandler[] formatHandlers() { 22213 version(X11) 22214 return [ 22215 FormatHandler(GetAtom!"text/uri-list"(XDisplayConnection.get), &translator), 22216 ]; 22217 else version(Windows) 22218 return [ 22219 FormatHandler(CF_HDROP, &translator), 22220 ]; 22221 else throw new NotYetImplementedException(); 22222 } 22223 22224 private void translator(scope ubyte[] data) @system { 22225 version(X11) { 22226 char[] listString = cast(char[]) data; 22227 char[][16] buffer; 22228 int count; 22229 char[][] result = buffer[]; 22230 22231 void commit(char[] s) { 22232 if(count == result.length) 22233 result.length += 16; 22234 if(s.length > 7 && s[0 ..7] == "file://") 22235 s = s[7 .. $]; // FIXME: also may need to trim out the host and do some entity decoding 22236 result[count++] = s; 22237 } 22238 22239 size_t last; 22240 foreach(idx, char c; listString) { 22241 if(c == '\n') { 22242 commit(listString[last .. idx - 1]); // a \r 22243 last = idx + 1; // a \n 22244 } 22245 } 22246 22247 if(last < listString.length) { 22248 commit(listString[last .. $]); 22249 } 22250 22251 // FIXME: they are uris now, should I translate it to local file names? 22252 // of course the host name is supposed to be there cuz of X rokking... 22253 22254 dg(result[0 .. count]); 22255 } else version(Windows) { 22256 22257 static struct DROPFILES { 22258 DWORD pFiles; 22259 POINT pt; 22260 BOOL fNC; 22261 BOOL fWide; 22262 } 22263 22264 22265 const(char)[][16] buffer; 22266 int count; 22267 const(char)[][] result = buffer[]; 22268 size_t last; 22269 22270 void commitA(in char[] stuff) { 22271 if(count == result.length) 22272 result.length += 16; 22273 result[count++] = stuff; 22274 } 22275 22276 void commitW(in wchar[] stuff) { 22277 commitA(makeUtf8StringFromWindowsString(stuff)); 22278 } 22279 22280 void magic(T)(T chars) { 22281 size_t idx; 22282 while(chars[idx]) { 22283 last = idx; 22284 while(chars[idx]) { 22285 idx++; 22286 } 22287 static if(is(T == char*)) 22288 commitA(chars[last .. idx]); 22289 else 22290 commitW(chars[last .. idx]); 22291 idx++; 22292 } 22293 } 22294 22295 auto df = cast(DROPFILES*) data.ptr; 22296 if(df.fWide) { 22297 wchar* chars = cast(wchar*) (data.ptr + df.pFiles); 22298 magic(chars); 22299 } else { 22300 char* chars = cast(char*) (data.ptr + df.pFiles); 22301 magic(chars); 22302 } 22303 dg(result[0 .. count]); 22304 } 22305 else throw new NotYetImplementedException(); 22306 } 22307 } 22308 22309 /++ 22310 Interface to describe data being dragged. See also [draggable] helper function. 22311 22312 $(PITFALL This is not yet stable and may break in future versions without notice.) 22313 22314 History: 22315 Added February 19, 2021 22316 +/ 22317 interface DraggableData { 22318 version(X11) 22319 alias FormatId = Atom; 22320 else 22321 alias FormatId = uint; 22322 /++ 22323 Gets the platform-specific FormatId associated with the given named format. 22324 22325 This may be a MIME type, but may also be other various strings defined by the 22326 programs you want to interoperate with. 22327 22328 FIXME: sdpy needs to offer data adapter things that look for compatible formats 22329 and convert it to some particular type for you. 22330 +/ 22331 static FormatId getFormatId(string name)() { 22332 version(X11) 22333 return GetAtom!name(XDisplayConnection.get); 22334 else version(Windows) { 22335 static UINT cache; 22336 if(!cache) 22337 cache = RegisterClipboardFormatA(name); 22338 return cache; 22339 } else 22340 throw new NotYetImplementedException(); 22341 } 22342 22343 /++ 22344 Looks up a string to represent the name for the given format, if there is one. 22345 22346 You should avoid using this function because it is slow. It is provided more for 22347 debugging than for primary use. 22348 +/ 22349 static string getFormatName(FormatId format) { 22350 version(X11) { 22351 if(format == 0) 22352 return "None"; 22353 else 22354 return getAtomName(format, XDisplayConnection.get); 22355 } else version(Windows) { 22356 switch(format) { 22357 case CF_UNICODETEXT: return "CF_UNICODETEXT"; 22358 case CF_DIBV5: return "CF_DIBV5"; 22359 case CF_RIFF: return "CF_RIFF"; 22360 case CF_WAVE: return "CF_WAVE"; 22361 case CF_HDROP: return "CF_HDROP"; 22362 default: 22363 char[1024] name; 22364 auto count = GetClipboardFormatNameA(format, name.ptr, name.length); 22365 return name[0 .. count].idup; 22366 } 22367 } else throw new NotYetImplementedException(); 22368 } 22369 22370 FormatId[] availableFormats(); 22371 // Return the slice of data you filled, empty slice if done. 22372 // this is to support the incremental thing 22373 ubyte[] getData(FormatId format, return scope ubyte[] data); 22374 22375 size_t dataLength(FormatId format); 22376 } 22377 22378 /++ 22379 $(PITFALL This is not yet stable and may break in future versions without notice.) 22380 22381 History: 22382 Added February 19, 2021 22383 +/ 22384 DraggableData draggable(string s) { 22385 version(X11) 22386 return new class X11SetSelectionHandler_Text, DraggableData { 22387 this() { 22388 super(s); 22389 } 22390 22391 override FormatId[] availableFormats() { 22392 return X11SetSelectionHandler_Text.availableFormats(); 22393 } 22394 22395 override ubyte[] getData(FormatId format, return scope ubyte[] data) { 22396 return X11SetSelectionHandler_Text.getData(format, data); 22397 } 22398 22399 size_t dataLength(FormatId format) { 22400 return s.length; 22401 } 22402 }; 22403 else version(Windows) 22404 return new class DraggableData { 22405 FormatId[] availableFormats() { 22406 return [CF_UNICODETEXT]; 22407 } 22408 22409 ubyte[] getData(FormatId format, return scope ubyte[] data) { 22410 return cast(ubyte[]) makeWindowsString(s, cast(wchar[]) data, WindowsStringConversionFlags.convertNewLines | WindowsStringConversionFlags.zeroTerminate); 22411 } 22412 22413 size_t dataLength(FormatId format) { 22414 return sizeOfConvertedWstring(s, WindowsStringConversionFlags.convertNewLines | WindowsStringConversionFlags.zeroTerminate) * wchar.sizeof; 22415 } 22416 }; 22417 else 22418 throw new NotYetImplementedException(); 22419 } 22420 22421 /++ 22422 $(PITFALL This is not yet stable and may break in future versions without notice.) 22423 22424 History: 22425 Added February 19, 2021 22426 +/ 22427 /// Group: drag_and_drop 22428 int doDragDrop(SimpleWindow window, DraggableData handler, DragAndDropAction action = DragAndDropAction.copy) 22429 in { 22430 assert(window !is null); 22431 assert(handler !is null); 22432 } 22433 do 22434 { 22435 version(X11) { 22436 auto sh = cast(X11SetSelectionHandler) handler; 22437 if(sh is null) { 22438 // gotta make my own adapter. 22439 sh = new class X11SetSelectionHandler { 22440 mixin X11SetSelectionHandler_Basics; 22441 22442 Atom[] availableFormats() { return handler.availableFormats(); } 22443 ubyte[] getData(Atom format, return scope ubyte[] data) { 22444 return handler.getData(format, data); 22445 } 22446 22447 // since the drop selection is only ever used once it isn't important 22448 // to reset it. 22449 void done() {} 22450 }; 22451 } 22452 return doDragDropX11(window, sh, action); 22453 } else version(Windows) { 22454 return doDragDropWindows(window, handler, action); 22455 } else throw new NotYetImplementedException(); 22456 } 22457 22458 version(Windows) 22459 private int doDragDropWindows(SimpleWindow window, DraggableData handler, DragAndDropAction action = DragAndDropAction.copy) { 22460 IDataObject obj = new class IDataObject { 22461 ULONG refCount; 22462 ULONG AddRef() { 22463 return ++refCount; 22464 } 22465 ULONG Release() { 22466 return --refCount; 22467 } 22468 HRESULT QueryInterface(const (IID)*riid, LPVOID *ppv) { 22469 if (IID_IUnknown == *riid) { 22470 *ppv = cast(void*) cast(IUnknown) this; 22471 } 22472 else if (IID_IDataObject == *riid) { 22473 *ppv = cast(void*) cast(IDataObject) this; 22474 } 22475 else { 22476 *ppv = null; 22477 return E_NOINTERFACE; 22478 } 22479 22480 AddRef(); 22481 return NOERROR; 22482 } 22483 22484 HRESULT DAdvise(FORMATETC* pformatetc, DWORD advf, IAdviseSink pAdvSink, DWORD* pdwConnection) { 22485 // writeln("Advise"); 22486 return E_NOTIMPL; 22487 } 22488 HRESULT DUnadvise(DWORD dwConnection) { 22489 return E_NOTIMPL; 22490 } 22491 HRESULT EnumDAdvise(IEnumSTATDATA* ppenumAdvise) { 22492 // writeln("EnumDAdvise"); 22493 return OLE_E_ADVISENOTSUPPORTED; 22494 } 22495 // tell what formats it supports 22496 22497 FORMATETC[] types; 22498 this() { 22499 FORMATETC t; 22500 foreach(ty; handler.availableFormats()) { 22501 assert(ty <= ushort.max && ty >= 0); 22502 t.cfFormat = cast(ushort) ty; 22503 t.lindex = -1; 22504 t.dwAspect = DVASPECT.DVASPECT_CONTENT; 22505 t.tymed = TYMED.TYMED_HGLOBAL; 22506 } 22507 types ~= t; 22508 } 22509 HRESULT EnumFormatEtc(DWORD dwDirection, IEnumFORMATETC* ppenumFormatEtc) { 22510 if(dwDirection == DATADIR.DATADIR_GET) { 22511 *ppenumFormatEtc = new class IEnumFORMATETC { 22512 ULONG refCount; 22513 ULONG AddRef() { 22514 return ++refCount; 22515 } 22516 ULONG Release() { 22517 return --refCount; 22518 } 22519 HRESULT QueryInterface(const (IID)*riid, LPVOID *ppv) { 22520 if (IID_IUnknown == *riid) { 22521 *ppv = cast(void*) cast(IUnknown) this; 22522 } 22523 else if (IID_IEnumFORMATETC == *riid) { 22524 *ppv = cast(void*) cast(IEnumFORMATETC) this; 22525 } 22526 else { 22527 *ppv = null; 22528 return E_NOINTERFACE; 22529 } 22530 22531 AddRef(); 22532 return NOERROR; 22533 } 22534 22535 22536 int pos; 22537 this() { 22538 pos = 0; 22539 } 22540 22541 HRESULT Clone(IEnumFORMATETC* ppenum) { 22542 // writeln("clone"); 22543 return E_NOTIMPL; // FIXME 22544 } 22545 22546 // Caller is responsible for freeing memory 22547 HRESULT Next(ULONG celt, FORMATETC* rgelt, ULONG* pceltFetched) { 22548 // fetched may be null if celt is one 22549 if(celt != 1) 22550 return E_NOTIMPL; // FIXME 22551 22552 if(celt + pos > types.length) 22553 return S_FALSE; 22554 22555 *rgelt = types[pos++]; 22556 22557 if(pceltFetched !is null) 22558 *pceltFetched = 1; 22559 22560 // writeln("ok celt ", celt); 22561 return S_OK; 22562 } 22563 22564 HRESULT Reset() { 22565 pos = 0; 22566 return S_OK; 22567 } 22568 22569 HRESULT Skip(ULONG celt) { 22570 if(celt + pos <= types.length) { 22571 pos += celt; 22572 return S_OK; 22573 } 22574 return S_FALSE; 22575 } 22576 }; 22577 22578 return S_OK; 22579 } else 22580 return E_NOTIMPL; 22581 } 22582 // given a format, return the format you'd prefer to use cuz it is identical 22583 HRESULT GetCanonicalFormatEtc(FORMATETC* pformatectIn, FORMATETC* pformatetcOut) { 22584 // FIXME: prolly could be better but meh 22585 // writeln("gcf: ", *pformatectIn); 22586 *pformatetcOut = *pformatectIn; 22587 return S_OK; 22588 } 22589 HRESULT GetData(FORMATETC* pformatetcIn, STGMEDIUM* pmedium) { 22590 foreach(ty; types) { 22591 if(ty == *pformatetcIn) { 22592 auto format = ty.cfFormat; 22593 // writeln("A: ", *pformatetcIn, "\nB: ", ty); 22594 STGMEDIUM medium; 22595 medium.tymed = TYMED.TYMED_HGLOBAL; 22596 22597 auto sz = handler.dataLength(format); 22598 auto handle = GlobalAlloc(GMEM_MOVEABLE, sz); 22599 if(handle is null) throw new WindowsApiException("GlobalAlloc", GetLastError()); 22600 if(auto data = cast(wchar*) GlobalLock(handle)) { 22601 auto slice = data[0 .. sz]; 22602 scope(exit) 22603 GlobalUnlock(handle); 22604 22605 handler.getData(format, cast(ubyte[]) slice[]); 22606 } 22607 22608 22609 medium.hGlobal = handle; // FIXME 22610 *pmedium = medium; 22611 return S_OK; 22612 } 22613 } 22614 return DV_E_FORMATETC; 22615 } 22616 HRESULT GetDataHere(FORMATETC* pformatetcIn, STGMEDIUM* pmedium) { 22617 // writeln("GDH: ", *pformatetcIn); 22618 return E_NOTIMPL; // FIXME 22619 } 22620 HRESULT QueryGetData(FORMATETC* pformatetc) { 22621 auto search = *pformatetc; 22622 search.tymed &= TYMED.TYMED_HGLOBAL; 22623 foreach(ty; types) 22624 if(ty == search) { 22625 // writeln("QueryGetData ", search, " ", types[0]); 22626 return S_OK; 22627 } 22628 if(pformatetc.cfFormat==CF_UNICODETEXT) { 22629 //writeln("QueryGetData FALSE ", search, " ", types[0]); 22630 } 22631 return S_FALSE; 22632 } 22633 HRESULT SetData(FORMATETC* pformatetc, STGMEDIUM* pmedium, BOOL fRelease) { 22634 // writeln("SetData: "); 22635 return E_NOTIMPL; 22636 } 22637 }; 22638 22639 22640 IDropSource src = new class IDropSource { 22641 ULONG refCount; 22642 ULONG AddRef() { 22643 return ++refCount; 22644 } 22645 ULONG Release() { 22646 return --refCount; 22647 } 22648 HRESULT QueryInterface(const (IID)*riid, LPVOID *ppv) { 22649 if (IID_IUnknown == *riid) { 22650 *ppv = cast(void*) cast(IUnknown) this; 22651 } 22652 else if (IID_IDropSource == *riid) { 22653 *ppv = cast(void*) cast(IDropSource) this; 22654 } 22655 else { 22656 *ppv = null; 22657 return E_NOINTERFACE; 22658 } 22659 22660 AddRef(); 22661 return NOERROR; 22662 } 22663 22664 int QueryContinueDrag(int fEscapePressed, uint grfKeyState) { 22665 if(fEscapePressed) 22666 return DRAGDROP_S_CANCEL; 22667 if(!(grfKeyState & MK_LBUTTON)) 22668 return DRAGDROP_S_DROP; 22669 return S_OK; 22670 } 22671 22672 int GiveFeedback(uint dwEffect) { 22673 return DRAGDROP_S_USEDEFAULTCURSORS; 22674 } 22675 }; 22676 22677 DWORD effect; 22678 22679 if(action == DragAndDropAction.none) assert(0, "Don't drag something with a none effect."); 22680 22681 DROPEFFECT de = win32DragAndDropAction(action); 22682 22683 // I'm not as concerned about the GC here since DoDragDrop blocks so the stack frame still sane the whole time 22684 // but still prolly a FIXME 22685 22686 auto ret = DoDragDrop(obj, src, de, &effect); 22687 /+ 22688 if(ret == DRAGDROP_S_DROP) 22689 writeln("drop ", effect); 22690 else if(ret == DRAGDROP_S_CANCEL) 22691 writeln("cancel"); 22692 else if(ret == S_OK) 22693 writeln("ok"); 22694 else writeln(ret); 22695 +/ 22696 22697 return ret; 22698 } 22699 22700 version(Windows) 22701 DROPEFFECT win32DragAndDropAction(DragAndDropAction action) { 22702 DROPEFFECT de; 22703 22704 with(DragAndDropAction) 22705 with(DROPEFFECT) 22706 final switch(action) { 22707 case none: de = DROPEFFECT_NONE; break; 22708 case copy: de = DROPEFFECT_COPY; break; 22709 case move: de = DROPEFFECT_MOVE; break; 22710 case link: de = DROPEFFECT_LINK; break; 22711 case ask: throw new Exception("ask not implemented yet"); 22712 case custom: throw new Exception("custom not implemented yet"); 22713 } 22714 22715 return de; 22716 } 22717 22718 22719 /++ 22720 History: 22721 Added February 19, 2021 22722 +/ 22723 /// Group: drag_and_drop 22724 void enableDragAndDrop(SimpleWindow window, DropHandler handler) { 22725 version(X11) { 22726 auto display = XDisplayConnection.get; 22727 22728 Atom atom = 5; // right??? 22729 22730 XChangeProperty( 22731 display, 22732 window.impl.window, 22733 GetAtom!"XdndAware"(display), 22734 XA_ATOM, 22735 32 /* bits */, 22736 PropModeReplace, 22737 &atom, 22738 1); 22739 22740 window.dropHandler = handler; 22741 } else version(Windows) { 22742 22743 initDnd(); 22744 22745 auto dropTarget = new class (handler) IDropTarget { 22746 DropHandler handler; 22747 this(DropHandler handler) { 22748 this.handler = handler; 22749 } 22750 ULONG refCount; 22751 ULONG AddRef() { 22752 return ++refCount; 22753 } 22754 ULONG Release() { 22755 return --refCount; 22756 } 22757 HRESULT QueryInterface(const (IID)*riid, LPVOID *ppv) { 22758 if (IID_IUnknown == *riid) { 22759 *ppv = cast(void*) cast(IUnknown) this; 22760 } 22761 else if (IID_IDropTarget == *riid) { 22762 *ppv = cast(void*) cast(IDropTarget) this; 22763 } 22764 else { 22765 *ppv = null; 22766 return E_NOINTERFACE; 22767 } 22768 22769 AddRef(); 22770 return NOERROR; 22771 } 22772 22773 22774 // /////////////////// 22775 22776 HRESULT DragEnter(IDataObject pDataObj, DWORD grfKeyState, POINTL pt, DWORD* pdwEffect) { 22777 DropPackage dropPackage = DropPackage(pDataObj); 22778 *pdwEffect = win32DragAndDropAction(handler.dragEnter(&dropPackage)); 22779 return S_OK; // https://docs.microsoft.com/en-us/windows/win32/api/oleidl/nf-oleidl-idroptarget-dragenter 22780 } 22781 22782 HRESULT DragLeave() { 22783 handler.dragLeave(); 22784 // release the IDataObject if needed 22785 return S_OK; 22786 } 22787 22788 HRESULT DragOver(DWORD grfKeyState, POINTL pt, DWORD* pdwEffect) { 22789 auto res = handler.dragOver(Point(pt.x, pt.y)); // FIXME: translate screen coordinates back to window coordinates 22790 22791 *pdwEffect = win32DragAndDropAction(res.action); 22792 // same as DragEnter basically 22793 return S_OK; 22794 } 22795 22796 HRESULT Drop(IDataObject pDataObj, DWORD grfKeyState, POINTL pt, DWORD* pdwEffect) { 22797 DropPackage pkg = DropPackage(pDataObj); 22798 handler.drop(&pkg); 22799 22800 return S_OK; 22801 } 22802 }; 22803 // Windows can hold on to the handler and try to call it 22804 // during which time the GC can't see it. so important to 22805 // manually manage this. At some point i'll FIXME and make 22806 // all my com instances manually managed since they supposed 22807 // to respect the refcount. 22808 import core.memory; 22809 GC.addRoot(cast(void*) dropTarget); 22810 22811 if(RegisterDragDrop(window.impl.hwnd, dropTarget) != S_OK) 22812 throw new WindowsApiException("RegisterDragDrop", GetLastError()); 22813 22814 window.dropHandler = handler; 22815 } else throw new NotYetImplementedException(); 22816 } 22817 22818 22819 22820 static if(UsingSimpledisplayX11) { 22821 22822 enum _NET_WM_STATE_ADD = 1; 22823 enum _NET_WM_STATE_REMOVE = 0; 22824 enum _NET_WM_STATE_TOGGLE = 2; 22825 22826 /// X-specific. Use [SimpleWindow.requestAttention] instead for most cases. 22827 void demandAttention(SimpleWindow window, bool needs = true) { 22828 demandAttention(window.impl.window, needs); 22829 } 22830 22831 /// ditto 22832 void demandAttention(Window window, bool needs = true) { 22833 setNetWmStateAtom(window, GetAtom!("_NET_WM_STATE_DEMANDS_ATTENTION", false)(XDisplayConnection.get), needs); 22834 } 22835 22836 void setNetWmStateAtom(Window window, Atom atom, bool set = true, Atom atom2 = None) { 22837 auto display = XDisplayConnection.get(); 22838 if(atom == None) 22839 return; // non-failure error 22840 //auto atom2 = GetAtom!"_NET_WM_STATE_SHADED"(display); 22841 22842 XClientMessageEvent xclient; 22843 22844 xclient.type = EventType.ClientMessage; 22845 xclient.window = window; 22846 xclient.message_type = GetAtom!"_NET_WM_STATE"(display); 22847 xclient.format = 32; 22848 xclient.data.l[0] = set ? _NET_WM_STATE_ADD : _NET_WM_STATE_REMOVE; 22849 xclient.data.l[1] = atom; 22850 xclient.data.l[2] = atom2; 22851 xclient.data.l[3] = 1; 22852 // [3] == source. 0 == unknown, 1 == app, 2 == else 22853 22854 XSendEvent( 22855 display, 22856 RootWindow(display, DefaultScreen(display)), 22857 false, 22858 EventMask.SubstructureRedirectMask | EventMask.SubstructureNotifyMask, 22859 cast(XEvent*) &xclient 22860 ); 22861 22862 /+ 22863 XChangeProperty( 22864 display, 22865 window.impl.window, 22866 GetAtom!"_NET_WM_STATE"(display), 22867 XA_ATOM, 22868 32 /* bits */, 22869 PropModeAppend, 22870 &atom, 22871 1); 22872 +/ 22873 } 22874 22875 private Atom dndActionAtom(Display* display, DragAndDropAction action) { 22876 Atom actionAtom; 22877 with(DragAndDropAction) 22878 final switch(action) { 22879 case none: actionAtom = None; break; 22880 case copy: actionAtom = GetAtom!"XdndActionCopy"(display); break; 22881 case move: actionAtom = GetAtom!"XdndActionMove"(display); break; 22882 case link: actionAtom = GetAtom!"XdndActionLink"(display); break; 22883 case ask: actionAtom = GetAtom!"XdndActionAsk"(display); break; 22884 case custom: actionAtom = GetAtom!"XdndActionCustom"(display); break; 22885 } 22886 22887 return actionAtom; 22888 } 22889 22890 private int doDragDropX11(SimpleWindow window, X11SetSelectionHandler handler, DragAndDropAction action) { 22891 // FIXME: I need to show user feedback somehow. 22892 auto display = XDisplayConnection.get; 22893 22894 auto actionAtom = dndActionAtom(display, action); 22895 assert(actionAtom, "Don't use action none to accept a drop"); 22896 22897 setX11Selection!"XdndSelection"(window, handler, null); 22898 22899 auto oldKeyHandler = window.handleKeyEvent; 22900 scope(exit) window.handleKeyEvent = oldKeyHandler; 22901 22902 auto oldCharHandler = window.handleCharEvent; 22903 scope(exit) window.handleCharEvent = oldCharHandler; 22904 22905 auto oldMouseHandler = window.handleMouseEvent; 22906 scope(exit) window.handleMouseEvent = oldMouseHandler; 22907 22908 Window[Window] eligibility; // 0 == not eligible, otherwise it is the window id of an eligible child 22909 22910 import core.sys.posix.sys.time; 22911 timeval tv; 22912 gettimeofday(&tv, null); 22913 22914 Time dataTimestamp = cast(Time) ( tv.tv_sec * 1000 + tv.tv_usec / 1000 ); 22915 22916 Time lastMouseTimestamp; 22917 22918 bool dnding = true; 22919 Window lastIn = None; 22920 22921 void leave() { 22922 if(lastIn == None) 22923 return; 22924 22925 XEvent ev; 22926 ev.xclient.type = EventType.ClientMessage; 22927 ev.xclient.window = lastIn; 22928 ev.xclient.message_type = GetAtom!("XdndLeave", true)(display); 22929 ev.xclient.format = 32; 22930 ev.xclient.data.l[0] = window.impl.window; 22931 22932 XSendEvent(XDisplayConnection.get, lastIn, false, EventMask.NoEventMask, &ev); 22933 XFlush(display); 22934 22935 lastIn = None; 22936 } 22937 22938 void enter(Window w) { 22939 assert(lastIn == None); 22940 22941 lastIn = w; 22942 22943 XEvent ev; 22944 ev.xclient.type = EventType.ClientMessage; 22945 ev.xclient.window = lastIn; 22946 ev.xclient.message_type = GetAtom!("XdndEnter", true)(display); 22947 ev.xclient.format = 32; 22948 ev.xclient.data.l[0] = window.impl.window; 22949 ev.xclient.data.l[1] = (5 << 24) | 0; // version 5, no more sources. FIXME source types 22950 22951 auto types = handler.availableFormats(); 22952 assert(types.length > 0); 22953 22954 ev.xclient.data.l[2] = types[0]; 22955 if(types.length > 1) 22956 ev.xclient.data.l[3] = types[1]; 22957 if(types.length > 2) 22958 ev.xclient.data.l[4] = types[2]; 22959 22960 // FIXME: other types?!?!? and make sure we skip TARGETS 22961 22962 XSendEvent(XDisplayConnection.get, lastIn, false, EventMask.NoEventMask, &ev); 22963 XFlush(display); 22964 } 22965 22966 void position(int rootX, int rootY) { 22967 assert(lastIn != None); 22968 22969 XEvent ev; 22970 ev.xclient.type = EventType.ClientMessage; 22971 ev.xclient.window = lastIn; 22972 ev.xclient.message_type = GetAtom!("XdndPosition", true)(display); 22973 ev.xclient.format = 32; 22974 ev.xclient.data.l[0] = window.impl.window; 22975 ev.xclient.data.l[1] = 0; // reserved 22976 ev.xclient.data.l[2] = (rootX << 16) | rootY; 22977 ev.xclient.data.l[3] = dataTimestamp; 22978 ev.xclient.data.l[4] = actionAtom; 22979 22980 XSendEvent(XDisplayConnection.get, lastIn, false, EventMask.NoEventMask, &ev); 22981 XFlush(display); 22982 22983 } 22984 22985 void drop() { 22986 XEvent ev; 22987 ev.xclient.type = EventType.ClientMessage; 22988 ev.xclient.window = lastIn; 22989 ev.xclient.message_type = GetAtom!("XdndDrop", true)(display); 22990 ev.xclient.format = 32; 22991 ev.xclient.data.l[0] = window.impl.window; 22992 ev.xclient.data.l[1] = 0; // reserved 22993 ev.xclient.data.l[2] = dataTimestamp; 22994 22995 XSendEvent(XDisplayConnection.get, lastIn, false, EventMask.NoEventMask, &ev); 22996 XFlush(display); 22997 22998 lastIn = None; 22999 dnding = false; 23000 } 23001 23002 // fyi nativeEventHandler can return 0 if it handles it, or otherwise it goes back to the normal handler 23003 // but idk if i should... 23004 23005 window.setEventHandlers( 23006 delegate(KeyEvent ev) { 23007 if(ev.pressed == true && ev.key == Key.Escape) { 23008 // cancel 23009 dnding = false; 23010 } 23011 }, 23012 delegate(MouseEvent ev) { 23013 if(ev.timestamp < lastMouseTimestamp) 23014 return; 23015 23016 lastMouseTimestamp = ev.timestamp; 23017 23018 if(ev.type == MouseEventType.motion) { 23019 auto display = XDisplayConnection.get; 23020 auto root = RootWindow(display, DefaultScreen(display)); 23021 23022 Window topWindow; 23023 int rootX, rootY; 23024 23025 XTranslateCoordinates(display, window.impl.window, root, ev.x, ev.y, &rootX, &rootY, &topWindow); 23026 23027 if(topWindow == None) 23028 return; 23029 23030 top: 23031 if(auto result = topWindow in eligibility) { 23032 auto dropWindow = *result; 23033 if(dropWindow == None) { 23034 leave(); 23035 return; 23036 } 23037 23038 if(dropWindow != lastIn) { 23039 leave(); 23040 enter(dropWindow); 23041 position(rootX, rootY); 23042 } else { 23043 position(rootX, rootY); 23044 } 23045 } else { 23046 // determine eligibility 23047 auto data = cast(Atom[]) getX11PropertyData(topWindow, GetAtom!"XdndAware"(display), XA_ATOM); 23048 if(data.length == 1) { 23049 // in case there is no WM or it isn't reparenting 23050 eligibility[topWindow] = (data[0] == 5) ? topWindow : None; // FIXME I'm supposed to handle older versions too but meh 23051 } else { 23052 23053 Window tryScanChildren(Window search, int maxRecurse) { 23054 // could be reparenting window manager, so gotta check the next few children too 23055 Window child; 23056 int x; 23057 int y; 23058 XTranslateCoordinates(display, window.impl.window, search, ev.x, ev.y, &x, &y, &child); 23059 23060 if(child == None) 23061 return None; 23062 auto data = cast(Atom[]) getX11PropertyData(child, GetAtom!"XdndAware"(display), XA_ATOM); 23063 if(data.length == 1) { 23064 return (data[0] == 5) ? child : None; // FIXME I'm supposed to handle older versions too but meh 23065 } else { 23066 if(maxRecurse) 23067 return tryScanChildren(child, maxRecurse - 1); 23068 else 23069 return None; 23070 } 23071 23072 } 23073 23074 // if a WM puts more than 3 layers on it, like wtf is it doing, screw that. 23075 auto topResult = tryScanChildren(topWindow, 3); 23076 // it is easy to have a false negative due to the mouse going over a WM 23077 // child window like the close button if separate from the frame... so I 23078 // can't really cache negatives, :( 23079 if(topResult != None) { 23080 eligibility[topWindow] = topResult; 23081 goto top; // reload to do the positioning iff eligibility changed lest we endless loop 23082 } 23083 } 23084 23085 } 23086 23087 } else if(ev.type == MouseEventType.buttonReleased) { 23088 drop(); 23089 dnding = false; 23090 } 23091 } 23092 ); 23093 23094 window.grabInput(); 23095 scope(exit) 23096 window.releaseInputGrab(); 23097 23098 23099 EventLoop.get.run(() => dnding); 23100 23101 return 0; 23102 } 23103 23104 /// X-specific 23105 TrueColorImage getWindowNetWmIcon(Window window) { 23106 try { 23107 auto display = XDisplayConnection.get; 23108 23109 auto data = getX11PropertyData (window, GetAtom!"_NET_WM_ICON"(display), XA_CARDINAL); 23110 23111 if (data.length > arch_ulong.sizeof * 2) { 23112 auto meta = cast(arch_ulong[]) (data[0 .. arch_ulong.sizeof * 2]); 23113 // these are an array of rgba images that we have to convert into pixmaps ourself 23114 23115 int width = cast(int) meta[0]; 23116 int height = cast(int) meta[1]; 23117 23118 auto bytes = cast(ubyte[]) (data[arch_ulong.sizeof * 2 .. $]); 23119 23120 static if(arch_ulong.sizeof == 4) { 23121 bytes = bytes[0 .. width * height * 4]; 23122 alias imageData = bytes; 23123 } else static if(arch_ulong.sizeof == 8) { 23124 bytes = bytes[0 .. width * height * 8]; 23125 auto imageData = new ubyte[](4 * width * height); 23126 } else static assert(0); 23127 23128 23129 23130 // this returns ARGB. Remember it is little-endian so 23131 // we have BGRA 23132 // our thing uses RGBA, which in little endian, is ABGR 23133 for(int idx = 0, idx2 = 0; idx < bytes.length; idx += arch_ulong.sizeof, idx2 += 4) { 23134 auto r = bytes[idx + 2]; 23135 auto g = bytes[idx + 1]; 23136 auto b = bytes[idx + 0]; 23137 auto a = bytes[idx + 3]; 23138 23139 imageData[idx2 + 0] = r; 23140 imageData[idx2 + 1] = g; 23141 imageData[idx2 + 2] = b; 23142 imageData[idx2 + 3] = a; 23143 } 23144 23145 return new TrueColorImage(width, height, imageData); 23146 } 23147 23148 return null; 23149 } catch(Exception e) { 23150 return null; 23151 } 23152 } 23153 23154 } /* UsingSimpledisplayX11 */ 23155 23156 23157 void loadBinNameToWindowClassName () { 23158 import core.stdc.stdlib : realloc; 23159 version(linux) { 23160 // args[0] MAY be empty, so we'll just use this 23161 import core.sys.posix.unistd : readlink; 23162 char[1024] ebuf = void; // 1KB should be enough for everyone! 23163 auto len = readlink("/proc/self/exe", ebuf.ptr, ebuf.length); 23164 if (len < 1) return; 23165 } else /*version(Windows)*/ { 23166 import core.runtime : Runtime; 23167 if (Runtime.args.length == 0 || Runtime.args[0].length == 0) return; 23168 auto ebuf = Runtime.args[0]; 23169 auto len = ebuf.length; 23170 } 23171 auto pos = len; 23172 while (pos > 0 && ebuf[pos-1] != '/') --pos; 23173 sdpyWindowClassStr = cast(char*)realloc(sdpyWindowClassStr, len-pos+1); 23174 if (sdpyWindowClassStr is null) return; // oops 23175 sdpyWindowClassStr[0..len-pos+1] = 0; // just in case 23176 sdpyWindowClassStr[0..len-pos] = ebuf[pos..len]; 23177 } 23178 23179 /++ 23180 An interface representing a font that is drawn with custom facilities. 23181 23182 You might want [OperatingSystemFont] instead, which represents 23183 a font loaded and drawn by functions native to the operating system. 23184 23185 WARNING: I might still change this. 23186 +/ 23187 interface DrawableFont : MeasurableFont { 23188 /++ 23189 Please note the point is upperLeft, NOT baseline! This is the point of a bounding box of the string. 23190 23191 Implementations must use the painter's fillColor to draw a rectangle behind the string, 23192 then use the outlineColor to draw the string. It might alpha composite if there's a transparent 23193 fill color, but that's up to the implementation. 23194 +/ 23195 void drawString(ScreenPainter painter, Point upperLeft, in char[] text); 23196 23197 /++ 23198 Requests that the given string is added to the image cache. You should only do this rarely, but 23199 if you have a string that you know will be used over and over again, adding it to a cache can 23200 improve things (assuming the implementation actually has a cache; it is also valid for an implementation 23201 to implement this as a do-nothing method). 23202 +/ 23203 void cacheString(SimpleWindow window, Color foreground, Color background, string text); 23204 } 23205 23206 /++ 23207 Loads a true type font using [arsd.ttf] that can be drawn as images on windows 23208 through a [ScreenPainter]. That module must be compiled in if you choose to use this function. 23209 23210 You should also consider [OperatingSystemFont], which loads and draws a font with 23211 facilities native to the user's operating system. You might also consider 23212 [arsd.ttf.OpenGlLimitedFont] or using [arsd.nanovega] if you are making some kind 23213 of game, as they have their own ways to draw text too. 23214 23215 Be warned: this can be slow, especially on remote connections to the X server, since 23216 it needs to create and transfer bitmaps instead of just text. The [DrawableFont] interface 23217 offers [DrawableFont.cacheString] which can help with this, sometimes. You might want to 23218 experiment in your specific case. 23219 23220 Please note that the return type of [DrawableFont] also includes an implementation of 23221 [MeasurableFont]. 23222 +/ 23223 DrawableFont arsdTtfFont()(in ubyte[] data, int size) { 23224 import arsd.ttf; 23225 static class ArsdTtfFont : DrawableFont { 23226 TtfFont font; 23227 int size; 23228 this(in ubyte[] data, int size) { 23229 font = TtfFont(data); 23230 this.size = size; 23231 23232 23233 auto scale = stbtt_ScaleForPixelHeight(&font.font, size); 23234 int ascent_, descent_, line_gap; 23235 stbtt_GetFontVMetrics(&font.font, &ascent_, &descent_, &line_gap); 23236 23237 int advance, lsb; 23238 stbtt_GetCodepointHMetrics(&font.font, 'x', &advance, &lsb); 23239 xWidth = cast(int) (advance * scale); 23240 stbtt_GetCodepointHMetrics(&font.font, 'M', &advance, &lsb); 23241 MWidth = cast(int) (advance * scale); 23242 } 23243 23244 private int ascent_; 23245 private int descent_; 23246 private int xWidth; 23247 private int MWidth; 23248 23249 bool isMonospace() { 23250 return xWidth == MWidth; 23251 } 23252 int averageWidth() { 23253 return xWidth; 23254 } 23255 int height() { 23256 return size; 23257 } 23258 int ascent() { 23259 return ascent_; 23260 } 23261 int descent() { 23262 return descent_; 23263 } 23264 23265 int stringWidth(scope const(char)[] s, SimpleWindow window = null) { 23266 int width, height; 23267 font.getStringSize(s, size, width, height); 23268 return width; 23269 } 23270 23271 23272 23273 Sprite[string] cache; 23274 23275 void cacheString(SimpleWindow window, Color foreground, Color background, string text) { 23276 auto sprite = new Sprite(window, stringToImage(foreground, background, text)); 23277 cache[text] = sprite; 23278 } 23279 23280 Image stringToImage(Color fg, Color bg, in char[] text) { 23281 int width, height; 23282 auto data = font.renderString(text, size, width, height); 23283 auto image = new TrueColorImage(width, height); 23284 int pos = 0; 23285 foreach(y; 0 .. height) 23286 foreach(x; 0 .. width) { 23287 fg.a = data[0]; 23288 bg.a = 255; 23289 auto color = alphaBlend(fg, bg); 23290 image.imageData.bytes[pos++] = color.r; 23291 image.imageData.bytes[pos++] = color.g; 23292 image.imageData.bytes[pos++] = color.b; 23293 image.imageData.bytes[pos++] = data[0]; 23294 data = data[1 .. $]; 23295 } 23296 assert(data.length == 0); 23297 23298 return Image.fromMemoryImage(image); 23299 } 23300 23301 void drawString(ScreenPainter painter, Point upperLeft, in char[] text) { 23302 Sprite sprite = (text in cache) ? *(text in cache) : null; 23303 23304 auto fg = painter.impl._outlineColor; 23305 auto bg = painter.impl._fillColor; 23306 23307 if(sprite !is null) { 23308 auto w = cast(SimpleWindow) painter.window; 23309 assert(w !is null); 23310 23311 sprite.drawAt(painter, upperLeft); 23312 } else { 23313 painter.drawImage(upperLeft, stringToImage(fg, bg, text)); 23314 } 23315 } 23316 } 23317 23318 return new ArsdTtfFont(data, size); 23319 } 23320 23321 class NotYetImplementedException : Exception { 23322 this(string file = __FILE__, size_t line = __LINE__) { 23323 super("Not yet implemented", file, line); 23324 } 23325 } 23326 23327 /// 23328 __gshared bool librariesSuccessfullyLoaded = true; 23329 /// 23330 __gshared bool openGlLibrariesSuccessfullyLoaded = true; 23331 23332 private mixin template DynamicLoadSupplementalOpenGL(Iface) { 23333 // mixin(staticForeachReplacement!Iface); 23334 static foreach(name; __traits(derivedMembers, Iface)) 23335 mixin("__gshared typeof(&__traits(getMember, Iface, name)) " ~ name ~ ";"); 23336 23337 void loadDynamicLibrary() @nogc { 23338 (cast(void function() @nogc) &loadDynamicLibraryForReal)(); 23339 } 23340 23341 void loadDynamicLibraryForReal() { 23342 foreach(name; __traits(derivedMembers, Iface)) { 23343 mixin("alias tmp = " ~ name ~ ";"); 23344 tmp = cast(typeof(tmp)) glbindGetProcAddress(name); 23345 if(tmp is null) throw new Exception("load failure of function " ~ name ~ " from supplemental OpenGL"); 23346 } 23347 } 23348 } 23349 23350 /+ 23351 private const(char)[] staticForeachReplacement(Iface)() pure { 23352 /* 23353 // just this for gdc 9.... 23354 // when i drop support for it and switch to gdc10, we can put this original back for a slight compile time ram decrease 23355 23356 static foreach(name; __traits(derivedMembers, Iface)) 23357 mixin("__gshared typeof(&__traits(getMember, Iface, name)) " ~ name ~ ";"); 23358 */ 23359 23360 char[] code = new char[](__traits(derivedMembers, Iface).length * 64); 23361 size_t pos; 23362 23363 void append(in char[] what) { 23364 if(pos + what.length > code.length) 23365 code.length = (code.length * 3) / 2; 23366 code[pos .. pos + what.length] = what[]; 23367 pos += what.length; 23368 } 23369 23370 foreach(name; __traits(derivedMembers, Iface)) { 23371 append(`__gshared typeof(&__traits(getMember, Iface, "`); 23372 append(name); 23373 append(`")) `); 23374 append(name); 23375 append(";"); 23376 } 23377 23378 return code[0 .. pos]; 23379 } 23380 +/ 23381 23382 private mixin template DynamicLoad(Iface, string library, int majorVersion, alias success) { 23383 //mixin(staticForeachReplacement!Iface); 23384 static foreach(name; __traits(derivedMembers, Iface)) 23385 mixin("__gshared typeof(&__traits(getMember, Iface, name)) " ~ name ~ ";"); 23386 23387 private __gshared void* libHandle; 23388 private __gshared bool attempted; 23389 23390 void loadDynamicLibrary() @nogc { 23391 (cast(void function() @nogc) &loadDynamicLibraryForReal)(); 23392 } 23393 23394 bool loadAttempted() { 23395 return attempted; 23396 } 23397 bool loadSuccessful() { 23398 return libHandle !is null; 23399 } 23400 23401 void loadDynamicLibraryForReal() { 23402 attempted = true; 23403 version(Posix) { 23404 import core.sys.posix.dlfcn; 23405 version(OSX) { 23406 version(X11) 23407 libHandle = dlopen("/usr/X11/lib/lib" ~ library ~ ".dylib", RTLD_NOW); 23408 else 23409 libHandle = dlopen(library ~ ".dylib", RTLD_NOW); 23410 } else { 23411 version(apitrace) { 23412 if(library == "GL" || library == "GLX") { 23413 libHandle = dlopen("glxtrace.so", RTLD_NOW); 23414 if(libHandle is null) { 23415 assert(false, "Failed to load `glxtrace.so`."); 23416 } 23417 } 23418 else { 23419 libHandle = dlopen("lib" ~ library ~ ".so", RTLD_NOW); 23420 } 23421 } 23422 else { 23423 libHandle = dlopen("lib" ~ library ~ ".so", RTLD_NOW); 23424 } 23425 if(libHandle is null) { 23426 libHandle = dlopen(("lib" ~ library ~ ".so." ~ toInternal!string(majorVersion) ~ "\0").ptr, RTLD_NOW); 23427 } 23428 } 23429 23430 static void* loadsym(void* l, const char* name) { 23431 import core.stdc.stdlib; 23432 if(l is null) 23433 return &abort; 23434 return dlsym(l, name); 23435 } 23436 } else version(Windows) { 23437 import core.sys.windows.winbase; 23438 libHandle = LoadLibrary(library ~ ".dll"); 23439 static void* loadsym(void* l, const char* name) { 23440 import core.stdc.stdlib; 23441 if(l is null) 23442 return &abort; 23443 return GetProcAddress(l, name); 23444 } 23445 } 23446 if(libHandle is null) { 23447 success = false; 23448 //throw new Exception("load failure of library " ~ library); 23449 } 23450 foreach(name; __traits(derivedMembers, Iface)) { 23451 mixin("alias tmp = " ~ name ~ ";"); 23452 tmp = cast(typeof(tmp)) loadsym(libHandle, name); 23453 if(tmp is null) throw new Exception("load failure of function " ~ name ~ " from " ~ library); 23454 } 23455 } 23456 23457 void unloadDynamicLibrary() { 23458 version(Posix) { 23459 import core.sys.posix.dlfcn; 23460 dlclose(libHandle); 23461 } else version(Windows) { 23462 import core.sys.windows.winbase; 23463 FreeLibrary(libHandle); 23464 } 23465 foreach(name; __traits(derivedMembers, Iface)) 23466 mixin(name ~ " = null;"); 23467 } 23468 } 23469 23470 // version(X11) 23471 /++ 23472 Returns the custom scaling factor read out of environment["ARSD_SCALING_FACTOR"]. 23473 23474 $(WARNING 23475 This function is exempted from stability guarantees. 23476 ) 23477 +/ 23478 float customScalingFactorForMonitor(int monitorNumber) @system { 23479 import core.stdc.stdlib; 23480 auto val = getenv("ARSD_SCALING_FACTOR"); 23481 23482 // FIXME: maybe we should assume a default nbased on the dpi thing if this isn't given 23483 if(val is null) 23484 return 1.0; 23485 23486 char[16] buffer = 0; 23487 int pos; 23488 23489 const(char)* at = val; 23490 23491 foreach(item; 0 .. monitorNumber + 1) { 23492 if(*at == 0) 23493 break; // reuse the last number when we at the end of the string 23494 pos = 0; 23495 while(pos + 1 < buffer.length && *at && *at != ';') { 23496 buffer[pos++] = *at; 23497 at++; 23498 } 23499 if(*at) 23500 at++; // skip the semicolon 23501 buffer[pos] = 0; 23502 } 23503 23504 //sdpyPrintDebugString(buffer[0 .. pos]); 23505 23506 import core.stdc.math; 23507 auto f = atof(buffer.ptr); 23508 23509 if(f <= 0.0 || isnan(f) || isinf(f)) 23510 return 1.0; 23511 23512 return f; 23513 } 23514 23515 void guiAbortProcess(string msg) { 23516 import core.stdc.stdlib; 23517 version(Windows) { 23518 WCharzBuffer t = WCharzBuffer(msg); 23519 MessageBoxW(null, t.ptr, "Program Termination"w.ptr, 0); 23520 } else { 23521 import core.stdc.stdio; 23522 fwrite(msg.ptr, 1, msg.length, stderr); 23523 msg = "\n"; 23524 fwrite(msg.ptr, 1, msg.length, stderr); 23525 fflush(stderr); 23526 } 23527 23528 abort(); 23529 } 23530 23531 private int minInternal(int a, int b) { 23532 return (a < b) ? a : b; 23533 } 23534 23535 private alias scriptable = arsd_jsvar_compatible;