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 Windows_tips: 588 589 You can add icons or manifest files to your exe using a resource file. 590 591 To create a Windows .ico file, use the gimp or something. I'll write a helper 592 program later. 593 594 Create `yourapp.rc`: 595 596 ```rc 597 1 ICON filename.ico 598 CREATEPROCESS_MANIFEST_RESOURCE_ID RT_MANIFEST "YourApp.exe.manifest" 599 ``` 600 601 And `yourapp.exe.manifest`: 602 603 ```xml 604 <?xml version="1.0" encoding="UTF-8" standalone="yes"?> 605 <assembly xmlns="urn:schemas-microsoft-com:asm.v1" manifestVersion="1.0"> 606 <assemblyIdentity 607 version="1.0.0.0" 608 processorArchitecture="*" 609 name="CompanyName.ProductName.YourApplication" 610 type="win32" 611 /> 612 <description>Your application description here.</description> 613 <dependency> 614 <dependentAssembly> 615 <assemblyIdentity 616 type="win32" 617 name="Microsoft.Windows.Common-Controls" 618 version="6.0.0.0" 619 processorArchitecture="*" 620 publicKeyToken="6595b64144ccf1df" 621 language="*" 622 /> 623 </dependentAssembly> 624 </dependency> 625 <application xmlns="urn:schemas-microsoft-com:asm.v3"> 626 <windowsSettings> 627 <dpiAware xmlns="http://schemas.microsoft.com/SMI/2005/WindowsSettings">true/pm</dpiAware> <!-- old style --> 628 <dpiAwareness xmlns="http://schemas.microsoft.com/SMI/2016/WindowsSettings">PerMonitorV2</dpiAwareness> <!-- new style --> 629 <!-- Un-comment the line below to enable GDI-scaling in this project. This will enable text --> 630 <!-- to render crisply in DPI-unaware contexts --> 631 <!--<gdiScaling xmlns="http://schemas.microsoft.com/SMI/2017/WindowsSettings">true</gdiScaling>--> 632 </windowsSettings> 633 </application> 634 </assembly> 635 ``` 636 637 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`. 638 639 Doing this lets you opt into various new things since Windows XP. 640 641 See: https://docs.microsoft.com/en-us/windows/win32/SbsCs/application-manifests 642 643 $(H2 Tips) 644 645 $(H3 Name conflicts) 646 647 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: 648 649 --- 650 static import sdpy = arsd.simpledisplay; 651 import arsd.simpledisplay : SimpleWindow; 652 653 void main() { 654 auto window = new SimpleWindow(); 655 sdpy.EventLoop.get.run(); 656 } 657 --- 658 659 $(H2 $(ID developer-notes) Developer notes) 660 661 I don't have a Mac, so that code isn't maintained. I would like to have a Cocoa 662 implementation though. 663 664 The NativeSimpleWindowImplementation and NativeScreenPainterImplementation both 665 suck. If I was rewriting it, I wouldn't do it that way again. 666 667 This file must not have any more required dependencies. If you need bindings, add 668 them right to this file. Once it gets into druntime and is there for a while, remove 669 bindings from here to avoid conflicts (or put them in an appropriate version block 670 so it continues to just work on old dmd), but wait a couple releases before making the 671 transition so this module remains usable with older versions of dmd. 672 673 You may have optional dependencies if needed by putting them in version blocks or 674 template functions. You may also extend the module with other modules with UFCS without 675 actually editing this - that is nice to do if you can. 676 677 Try to make functions work the same way across operating systems. I typically make 678 it thinly wrap Windows, then emulate that on Linux. 679 680 A goal of this is to keep a gui hello world to less than 250 KB. This means avoiding 681 Phobos! So try to avoid it. 682 683 See more comments throughout the source. 684 685 I realize this file is fairly large, but over half that is just bindings at the bottom 686 or documentation at the top. Some of the classes are a bit big too, but hopefully easy 687 to understand. I suggest you jump around the source by looking for a particular 688 declaration you're interested in, like `class SimpleWindow` using your editor's search 689 function, then look at one piece at a time. 690 691 Authors: Adam D. Ruppe with the help of others. If you need help, please email me with 692 destructionator@gmail.com or find me on IRC. Our channel is #d on Freenode and you can 693 ping me, adam_d_ruppe, and I'll usually see it if I'm around. 694 695 I live in the eastern United States, so I will most likely not be around at night in 696 that US east timezone. 697 698 License: Copyright Adam D. Ruppe, 2011-2021. Released under the Boost Software License. 699 700 Building documentation: use my adrdox generator, `dub run adrdox`. 701 702 Examples: 703 704 $(DIV $(ID Event-example)) 705 $(H3 $(ID event-example) Event example) 706 This program creates a window and draws events inside them as they 707 happen, scrolling the text in the window as needed. Run this program 708 and experiment to get a feel for where basic input events take place 709 in the library. 710 711 --- 712 // dmd example.d simpledisplay.d color.d 713 import arsd.simpledisplay; 714 import std.conv; 715 716 void main() { 717 auto window = new SimpleWindow(Size(500, 500), "Event example - simpledisplay.d"); 718 719 int y = 0; 720 721 void addLine(string text) { 722 auto painter = window.draw(); 723 724 if(y + painter.fontHeight >= window.height) { 725 painter.scrollArea(Point(0, 0), window.width, window.height, 0, painter.fontHeight); 726 y -= painter.fontHeight; 727 } 728 729 painter.outlineColor = Color.red; 730 painter.fillColor = Color.black; 731 painter.drawRectangle(Point(0, y), window.width, painter.fontHeight); 732 733 painter.outlineColor = Color.white; 734 735 painter.drawText(Point(10, y), text); 736 737 y += painter.fontHeight; 738 } 739 740 window.eventLoop(1000, 741 () { 742 addLine("Timer went off!"); 743 }, 744 (KeyEvent event) { 745 addLine(to!string(event)); 746 }, 747 (MouseEvent event) { 748 addLine(to!string(event)); 749 }, 750 (dchar ch) { 751 addLine(to!string(ch)); 752 } 753 ); 754 } 755 --- 756 757 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. 758 759 $(COMMENT 760 This program displays a pie chart. Clicking on a color will increase its share of the pie. 761 762 --- 763 764 --- 765 ) 766 767 History: 768 Initial release in April 2011. 769 770 simpledisplay was stand alone until about 2015. It then added a dependency on [arsd.color] and changed its name to `arsd.simpledisplay`. 771 772 On March 4, 2023 (dub v11.0), it started importing [arsd.core] as well, making that a build-time requirement. 773 +/ 774 module arsd.simpledisplay; 775 776 import arsd.core; 777 778 // FIXME: tetris demo 779 // FIXME: space invaders demo 780 // FIXME: asteroids demo 781 782 /++ $(ID Pong-example) 783 $(H3 Pong) 784 785 This program creates a little Pong-like game. Player one is controlled 786 with the keyboard. Player two is controlled with the mouse. It demos 787 the pulse timer, event handling, and some basic drawing. 788 +/ 789 version(demos) 790 unittest { 791 // dmd example.d simpledisplay.d color.d 792 import arsd.simpledisplay; 793 794 enum paddleMovementSpeed = 8; 795 enum paddleHeight = 48; 796 797 void main() { 798 auto window = new SimpleWindow(600, 400, "Pong game!"); 799 800 int playerOnePosition, playerTwoPosition; 801 int playerOneMovement, playerTwoMovement; 802 int playerOneScore, playerTwoScore; 803 804 int ballX, ballY; 805 int ballDx, ballDy; 806 807 void serve() { 808 import std.random; 809 810 ballX = window.width / 2; 811 ballY = window.height / 2; 812 ballDx = uniform(-4, 4) * 3; 813 ballDy = uniform(-4, 4) * 3; 814 if(ballDx == 0) 815 ballDx = uniform(0, 2) == 0 ? 3 : -3; 816 } 817 818 serve(); 819 820 window.eventLoop(50, // set a 50 ms timer pulls 821 // This runs once per timer pulse 822 delegate () { 823 auto painter = window.draw(); 824 825 painter.clear(); 826 827 // Update everyone's motion 828 playerOnePosition += playerOneMovement; 829 playerTwoPosition += playerTwoMovement; 830 831 ballX += ballDx; 832 ballY += ballDy; 833 834 // Bounce off the top and bottom edges of the window 835 if(ballY + 7 >= window.height) 836 ballDy = -ballDy; 837 if(ballY - 8 <= 0) 838 ballDy = -ballDy; 839 840 // Bounce off the paddle, if it is in position 841 if(ballX - 8 <= 16) { 842 if(ballY + 7 > playerOnePosition && ballY - 8 < playerOnePosition + paddleHeight) { 843 ballDx = -ballDx + 1; // add some speed to keep it interesting 844 ballDy += playerOneMovement; // and y movement based on your controls too 845 ballX = 24; // move it past the paddle so it doesn't wiggle inside 846 } else { 847 // Missed it 848 playerTwoScore ++; 849 serve(); 850 } 851 } 852 853 if(ballX + 7 >= window.width - 16) { // do the same thing but for player 1 854 if(ballY + 7 > playerTwoPosition && ballY - 8 < playerTwoPosition + paddleHeight) { 855 ballDx = -ballDx - 1; 856 ballDy += playerTwoMovement; 857 ballX = window.width - 24; 858 } else { 859 // Missed it 860 playerOneScore ++; 861 serve(); 862 } 863 } 864 865 // Draw the paddles 866 painter.outlineColor = Color.black; 867 painter.drawLine(Point(16, playerOnePosition), Point(16, playerOnePosition + paddleHeight)); 868 painter.drawLine(Point(window.width - 16, playerTwoPosition), Point(window.width - 16, playerTwoPosition + paddleHeight)); 869 870 // Draw the ball 871 painter.fillColor = Color.red; 872 painter.outlineColor = Color.yellow; 873 painter.drawEllipse(Point(ballX - 8, ballY - 8), Point(ballX + 7, ballY + 7)); 874 875 // Draw the score 876 painter.outlineColor = Color.blue; 877 import std.conv; 878 painter.drawText(Point(64, 4), to!string(playerOneScore)); 879 painter.drawText(Point(window.width - 64, 4), to!string(playerTwoScore)); 880 881 }, 882 delegate (KeyEvent event) { 883 // Player 1's controls are the arrow keys on the keyboard 884 if(event.key == Key.Down) 885 playerOneMovement = event.pressed ? paddleMovementSpeed : 0; 886 if(event.key == Key.Up) 887 playerOneMovement = event.pressed ? -paddleMovementSpeed : 0; 888 889 }, 890 delegate (MouseEvent event) { 891 // Player 2's controls are mouse movement while the left button is held down 892 if(event.type == MouseEventType.motion && (event.modifierState & ModifierState.leftButtonDown)) { 893 if(event.dy > 0) 894 playerTwoMovement = paddleMovementSpeed; 895 else if(event.dy < 0) 896 playerTwoMovement = -paddleMovementSpeed; 897 } else { 898 playerTwoMovement = 0; 899 } 900 } 901 ); 902 } 903 } 904 905 /++ $(H3 $(ID example-minesweeper) Minesweeper) 906 907 This minesweeper demo shows how we can implement another classic 908 game with simpledisplay and shows some mouse input and basic output 909 code. 910 +/ 911 version(demos) 912 unittest { 913 import arsd.simpledisplay; 914 915 enum GameSquare { 916 mine = 0, 917 clear, 918 m1, m2, m3, m4, m5, m6, m7, m8 919 } 920 921 enum UserSquare { 922 unknown, 923 revealed, 924 flagged, 925 questioned 926 } 927 928 enum GameState { 929 inProgress, 930 lose, 931 win 932 } 933 934 GameSquare[] board; 935 UserSquare[] userState; 936 GameState gameState; 937 int boardWidth; 938 int boardHeight; 939 940 bool isMine(int x, int y) { 941 if(x < 0 || y < 0 || x >= boardWidth || y >= boardHeight) 942 return false; 943 return board[y * boardWidth + x] == GameSquare.mine; 944 } 945 946 GameState reveal(int x, int y) { 947 if(board[y * boardWidth + x] == GameSquare.clear) { 948 floodFill(userState, boardWidth, boardHeight, 949 UserSquare.unknown, UserSquare.revealed, 950 x, y, 951 (x, y) { 952 if(board[y * boardWidth + x] == GameSquare.clear) 953 return true; 954 else { 955 userState[y * boardWidth + x] = UserSquare.revealed; 956 return false; 957 } 958 }); 959 } else { 960 userState[y * boardWidth + x] = UserSquare.revealed; 961 if(isMine(x, y)) 962 return GameState.lose; 963 } 964 965 foreach(state; userState) { 966 if(state == UserSquare.unknown || state == UserSquare.questioned) 967 return GameState.inProgress; 968 } 969 970 return GameState.win; 971 } 972 973 void initializeBoard(int width, int height, int numberOfMines) { 974 boardWidth = width; 975 boardHeight = height; 976 board.length = width * height; 977 978 userState.length = width * height; 979 userState[] = UserSquare.unknown; 980 981 import std.algorithm, std.random, std.range; 982 983 board[] = GameSquare.clear; 984 985 foreach(minePosition; randomSample(iota(0, board.length), numberOfMines)) 986 board[minePosition] = GameSquare.mine; 987 988 int x; 989 int y; 990 foreach(idx, ref square; board) { 991 if(square == GameSquare.clear) { 992 int danger = 0; 993 danger += isMine(x-1, y-1)?1:0; 994 danger += isMine(x-1, y)?1:0; 995 danger += isMine(x-1, y+1)?1:0; 996 danger += isMine(x, y-1)?1:0; 997 danger += isMine(x, y+1)?1:0; 998 danger += isMine(x+1, y-1)?1:0; 999 danger += isMine(x+1, y)?1:0; 1000 danger += isMine(x+1, y+1)?1:0; 1001 1002 square = cast(GameSquare) (danger + 1); 1003 } 1004 1005 x++; 1006 if(x == width) { 1007 x = 0; 1008 y++; 1009 } 1010 } 1011 } 1012 1013 void redraw(SimpleWindow window) { 1014 import std.conv; 1015 1016 auto painter = window.draw(); 1017 1018 painter.clear(); 1019 1020 final switch(gameState) with(GameState) { 1021 case inProgress: 1022 break; 1023 case win: 1024 painter.fillColor = Color.green; 1025 painter.drawRectangle(Point(0, 0), window.width, window.height); 1026 return; 1027 case lose: 1028 painter.fillColor = Color.red; 1029 painter.drawRectangle(Point(0, 0), window.width, window.height); 1030 return; 1031 } 1032 1033 int x = 0; 1034 int y = 0; 1035 1036 foreach(idx, square; board) { 1037 auto state = userState[idx]; 1038 1039 final switch(state) with(UserSquare) { 1040 case unknown: 1041 painter.outlineColor = Color.black; 1042 painter.fillColor = Color(128,128,128); 1043 1044 painter.drawRectangle( 1045 Point(x * 20, y * 20), 1046 20, 20 1047 ); 1048 break; 1049 case revealed: 1050 if(square == GameSquare.clear) { 1051 painter.outlineColor = Color.white; 1052 painter.fillColor = Color.white; 1053 1054 painter.drawRectangle( 1055 Point(x * 20, y * 20), 1056 20, 20 1057 ); 1058 } else { 1059 painter.outlineColor = Color.black; 1060 painter.fillColor = Color.white; 1061 1062 painter.drawText( 1063 Point(x * 20, y * 20), 1064 to!string(square)[1..2], 1065 Point(x * 20 + 20, y * 20 + 20), 1066 TextAlignment.Center | TextAlignment.VerticalCenter); 1067 } 1068 break; 1069 case flagged: 1070 painter.outlineColor = Color.black; 1071 painter.fillColor = Color.red; 1072 painter.drawRectangle( 1073 Point(x * 20, y * 20), 1074 20, 20 1075 ); 1076 break; 1077 case questioned: 1078 painter.outlineColor = Color.black; 1079 painter.fillColor = Color.yellow; 1080 painter.drawRectangle( 1081 Point(x * 20, y * 20), 1082 20, 20 1083 ); 1084 break; 1085 } 1086 1087 x++; 1088 if(x == boardWidth) { 1089 x = 0; 1090 y++; 1091 } 1092 } 1093 1094 } 1095 1096 void main() { 1097 auto window = new SimpleWindow(200, 200); 1098 1099 initializeBoard(10, 10, 10); 1100 1101 redraw(window); 1102 window.eventLoop(0, 1103 delegate (MouseEvent me) { 1104 if(me.type != MouseEventType.buttonPressed) 1105 return; 1106 auto x = me.x / 20; 1107 auto y = me.y / 20; 1108 if(x >= 0 && x < boardWidth && y >= 0 && y < boardHeight) { 1109 if(me.button == MouseButton.left) { 1110 gameState = reveal(x, y); 1111 } else { 1112 userState[y*boardWidth+x] = UserSquare.flagged; 1113 } 1114 redraw(window); 1115 } 1116 } 1117 ); 1118 } 1119 } 1120 1121 // FIXME: tetris demo 1122 // FIXME: space invaders demo 1123 // FIXME: asteroids demo 1124 1125 version(OSX) version(DigitalMars) version=OSXCocoa; 1126 1127 1128 version(OSXCocoa) { 1129 version=without_opengl; 1130 version=allow_unimplemented_features; 1131 // version=OSXCocoa; 1132 // pragma(linkerDirective, "-framework Cocoa"); 1133 } 1134 1135 version(without_opengl) { 1136 enum SdpyIsUsingIVGLBinds = false; 1137 } else /*version(Posix)*/ { 1138 static if (__traits(compiles, (){import iv.glbinds;})) { 1139 enum SdpyIsUsingIVGLBinds = true; 1140 public import iv.glbinds; 1141 //pragma(msg, "SDPY: using iv.glbinds"); 1142 } else { 1143 enum SdpyIsUsingIVGLBinds = false; 1144 } 1145 //} else { 1146 // enum SdpyIsUsingIVGLBinds = false; 1147 } 1148 1149 1150 version(Windows) { 1151 //import core.sys.windows.windows; 1152 import core.sys.windows.winnls; 1153 import core.sys.windows.windef; 1154 import core.sys.windows.basetyps; 1155 import core.sys.windows.winbase; 1156 import core.sys.windows.winuser; 1157 import core.sys.windows.shellapi; 1158 import core.sys.windows.wingdi; 1159 static import gdi = core.sys.windows.wingdi; // so i 1160 1161 pragma(lib, "gdi32"); 1162 pragma(lib, "user32"); 1163 1164 // for AlphaBlend... a breaking change.... 1165 version(CRuntime_DigitalMars) { } else 1166 pragma(lib, "msimg32"); 1167 1168 // core.sys.windows.dwmapi 1169 private { 1170 /++ 1171 See_also: 1172 https://learn.microsoft.com/en-us/windows/win32/api/dwmapi/nf-dwmapi-dwmgetwindowattribute 1173 +/ 1174 extern extern(Windows) HRESULT DwmGetWindowAttribute( 1175 HWND hwnd, 1176 DWORD dwAttribute, 1177 PVOID pvAttribute, 1178 DWORD cbAttribute 1179 ) nothrow @nogc; 1180 1181 /++ 1182 See_also: 1183 https://learn.microsoft.com/en-us/windows/win32/api/dwmapi/nf-dwmapi-dwmsetwindowattribute 1184 +/ 1185 extern extern(Windows) HRESULT DwmSetWindowAttribute( 1186 HWND hwnd, 1187 DWORD dwAttribute, 1188 LPCVOID pvAttribute, 1189 DWORD cbAttribute 1190 ) nothrow @nogc; 1191 1192 /++ 1193 See_also: 1194 https://learn.microsoft.com/en-us/windows/win32/api/dwmapi/ne-dwmapi-dwmwindowattribute 1195 +/ 1196 enum DWMWINDOWATTRIBUTE { 1197 // incomplete, only declare what we need 1198 1199 /++ 1200 Usage: 1201 pvAttribute → `DWM_WINDOW_CORNER_PREFERENCE*` 1202 1203 $(NOTE 1204 Requires Windows 11 or later. 1205 ) 1206 +/ 1207 DWMWA_WINDOW_CORNER_PREFERENCE = 33, 1208 } 1209 1210 /++ 1211 See_also: 1212 https://learn.microsoft.com/en-us/windows/win32/api/dwmapi/ne-dwmapi-dwm_window_corner_preference 1213 +/ 1214 enum DWM_WINDOW_CORNER_PREFERENCE { 1215 /// System decision 1216 DWMWCP_DEFAULT = 0, 1217 1218 /// Never 1219 DWMWCP_DONOTROUND = 1, 1220 1221 // If "appropriate" 1222 DWMWCP_ROUND = 2, 1223 1224 // If "appropriate", but smaller radius 1225 DWMWCP_ROUNDSMALL = 3 1226 } 1227 1228 bool fromDWM( 1229 DWM_WINDOW_CORNER_PREFERENCE value, 1230 out CornerStyle result, 1231 ) @safe pure nothrow @nogc { 1232 switch (value) with (DWM_WINDOW_CORNER_PREFERENCE) { 1233 case DWMWCP_DEFAULT: 1234 result = CornerStyle.automatic; 1235 return true; 1236 case DWMWCP_DONOTROUND: 1237 result = CornerStyle.rectangular; 1238 return true; 1239 case DWMWCP_ROUND: 1240 result = CornerStyle.rounded; 1241 return true; 1242 case DWMWCP_ROUNDSMALL: 1243 result = CornerStyle.roundedSlightly; 1244 return true; 1245 default: 1246 return false; 1247 } 1248 } 1249 1250 bool toDWM( 1251 CornerStyle value, 1252 out DWM_WINDOW_CORNER_PREFERENCE result, 1253 ) @safe pure nothrow @nogc { 1254 final switch (value) with (DWM_WINDOW_CORNER_PREFERENCE) { 1255 case CornerStyle.automatic: 1256 result = DWMWCP_DEFAULT; 1257 return true; 1258 case CornerStyle.rectangular: 1259 result = DWMWCP_DONOTROUND; 1260 return true; 1261 case CornerStyle.rounded: 1262 result = DWMWCP_ROUND; 1263 return true; 1264 case CornerStyle.roundedSlightly: 1265 result = DWMWCP_ROUNDSMALL; 1266 return true; 1267 } 1268 } 1269 1270 pragma(lib, "dwmapi"); 1271 } 1272 } else version (linux) { 1273 //k8: this is hack for rdmd. sorry. 1274 static import core.sys.linux.epoll; 1275 static import core.sys.linux.timerfd; 1276 } 1277 1278 1279 // FIXME: icons on Windows don't look quite right, I think the transparency mask is off. 1280 1281 // http://wiki.dlang.org/Simpledisplay.d 1282 1283 // see : http://www.sbin.org/doc/Xlib/chapt_09.html section on Keyboard Preferences re: scroll lock led 1284 1285 // Cool stuff: I want right alt and scroll lock to do different stuff for personal use. maybe even right ctrl 1286 // but can i control the scroll lock led 1287 1288 1289 // Note: if you are using Image on X, you might want to do: 1290 /* 1291 static if(UsingSimpledisplayX11) { 1292 if(!Image.impl.xshmAvailable) { 1293 // the images will use the slower XPutImage, you might 1294 // want to consider an alternative method to get better speed 1295 } 1296 } 1297 1298 If the shared memory extension is available though, simpledisplay uses it 1299 for a significant speed boost whenever you draw large Images. 1300 */ 1301 1302 // 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. 1303 1304 // WARNING: if you are using with_eventloop, don't forget to call XFlush(XDisplayConnection.get()); before calling loop()! 1305 1306 /* 1307 Biggest FIXME: 1308 make sure the key event numbers match between X and Windows OR provide symbolic constants on each system 1309 1310 clean up opengl contexts when their windows close 1311 1312 fix resizing the bitmaps/pixmaps 1313 */ 1314 1315 // BTW on Windows: 1316 // -L/SUBSYSTEM:WINDOWS:5.0 1317 // to dmd will make a nice windows binary w/o a console if you want that. 1318 1319 /* 1320 Stuff to add: 1321 1322 use multibyte functions everywhere we can 1323 1324 OpenGL windows 1325 more event stuff 1326 extremely basic windows w/ no decoration for tooltips, splash screens, etc. 1327 1328 1329 resizeEvent 1330 and make the windows non-resizable by default, 1331 or perhaps stretched (if I can find something in X like StretchBlt) 1332 1333 take a screenshot function! 1334 1335 Pens and brushes? 1336 Maybe a global event loop? 1337 1338 Mouse deltas 1339 Key items 1340 */ 1341 1342 /* 1343 From MSDN: 1344 1345 You can also use the GET_X_LPARAM or GET_Y_LPARAM macro to extract the x- or y-coordinate. 1346 1347 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. 1348 1349 */ 1350 1351 version(linux) { 1352 version = X11; 1353 version(without_libnotify) { 1354 // we cool 1355 } 1356 else 1357 version = libnotify; 1358 } 1359 1360 version(libnotify) { 1361 pragma(lib, "dl"); 1362 import core.sys.posix.dlfcn; 1363 1364 void delegate()[int] libnotify_action_delegates; 1365 int libnotify_action_delegates_count; 1366 extern(C) static void libnotify_action_callback_sdpy(void* notification, char* action, void* user_data) { 1367 auto idx = cast(int) user_data; 1368 if(auto dgptr = idx in libnotify_action_delegates) { 1369 (*dgptr)(); 1370 libnotify_action_delegates.remove(idx); 1371 } 1372 } 1373 1374 struct C_DynamicLibrary { 1375 void* handle; 1376 this(string name) { 1377 handle = dlopen((name ~ "\0").ptr, RTLD_NOW); 1378 if(handle is null) 1379 throw new Exception("dlopen"); 1380 } 1381 1382 void close() { 1383 dlclose(handle); 1384 } 1385 1386 ~this() { 1387 // close 1388 } 1389 1390 // FIXME: this looks up by name every time.... 1391 template call(string func, Ret, Args...) { 1392 extern(C) Ret function(Args) fptr; 1393 typeof(fptr) call() { 1394 fptr = cast(typeof(fptr)) dlsym(handle, func); 1395 return fptr; 1396 } 1397 } 1398 } 1399 1400 C_DynamicLibrary* libnotify; 1401 } 1402 1403 version(OSX) { 1404 version(OSXCocoa) {} 1405 else { version = X11; } 1406 } 1407 //version = OSXCocoa; // this was written by KennyTM 1408 version(FreeBSD) 1409 version = X11; 1410 version(Solaris) 1411 version = X11; 1412 1413 version(X11) { 1414 version(without_xft) {} 1415 else version=with_xft; 1416 } 1417 1418 void featureNotImplemented()() { 1419 version(allow_unimplemented_features) 1420 throw new NotYetImplementedException(); 1421 else 1422 static assert(0); 1423 } 1424 1425 // these are so the static asserts don't trigger unless you want to 1426 // add support to it for an OS 1427 version(Windows) 1428 version = with_timer; 1429 version(linux) 1430 version = with_timer; 1431 version(OSXCocoa) 1432 version = with_timer; 1433 1434 version(with_timer) 1435 enum bool SimpledisplayTimerAvailable = true; 1436 else 1437 enum bool SimpledisplayTimerAvailable = false; 1438 1439 /// 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. 1440 version(Windows) 1441 enum bool UsingSimpledisplayWindows = true; 1442 else 1443 enum bool UsingSimpledisplayWindows = false; 1444 1445 /// 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. 1446 version(X11) 1447 enum bool UsingSimpledisplayX11 = true; 1448 else 1449 enum bool UsingSimpledisplayX11 = false; 1450 1451 /// 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. 1452 version(OSXCocoa) 1453 enum bool UsingSimpledisplayCocoa = true; 1454 else 1455 enum bool UsingSimpledisplayCocoa = false; 1456 1457 /// Does this platform support multiple windows? If not, trying to create another will cause it to throw an exception. 1458 version(Windows) 1459 enum multipleWindowsSupported = true; 1460 else version(X11) 1461 enum multipleWindowsSupported = true; 1462 else version(OSXCocoa) 1463 enum multipleWindowsSupported = true; 1464 else 1465 static assert(0); 1466 1467 version(without_opengl) 1468 enum bool OpenGlEnabled = false; 1469 else 1470 enum bool OpenGlEnabled = true; 1471 1472 /++ 1473 Adds the necessary pragmas to your application to use the Windows gui subsystem. 1474 If you mix this in above your `main` function, you no longer need to use the linker 1475 flags explicitly. It does the necessary version blocks for various compilers and runtimes. 1476 1477 It does nothing if not compiling for Windows, so you need not version it out yourself. 1478 1479 Please note that Windows gui subsystem applications must NOT use std.stdio's stdout and 1480 stderr writeln. It will fail and throw an exception. 1481 1482 This will NOT work with plain `dmd` on Windows; you must use `dmd -m32mscoff` or `dmd -m64`. 1483 1484 History: 1485 Added November 24, 2021 (dub v10.4) 1486 +/ 1487 mixin template EnableWindowsSubsystem() { 1488 version(Windows) 1489 version(CRuntime_Microsoft) { 1490 pragma(linkerDirective, "/subsystem:windows"); 1491 version(LDC) 1492 pragma(linkerDirective, "/entry:wmainCRTStartup"); 1493 else 1494 pragma(linkerDirective, "/entry:mainCRTStartup"); 1495 } 1496 } 1497 1498 1499 /++ 1500 After selecting a type from [WindowTypes], you may further customize 1501 its behavior by setting one or more of these flags. 1502 1503 1504 The different window types have different meanings of `normal`. If the 1505 window type already is a good match for what you want to do, you should 1506 just use [WindowFlags.normal], the default, which will do the right thing 1507 for your users. 1508 1509 The window flags will not always be honored by the operating system 1510 and window managers; they are hints, not commands. 1511 +/ 1512 enum WindowFlags : int { 1513 normal = 0, /// 1514 skipTaskbar = 1, /// 1515 alwaysOnTop = 2, /// 1516 alwaysOnBottom = 4, /// 1517 cannotBeActivated = 8, /// 1518 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. 1519 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. 1520 /++ 1521 Sets the window as a short-lived child of its parent, but unlike an ordinary child, 1522 it is still a top-level window. This should NOT be set separately for most window types. 1523 1524 A transient window will not keep the application open if its main window closes. 1525 1526 $(PITFALL This may not be correctly implemented and its behavior is subject to change.) 1527 1528 1529 From the ICCM: 1530 1531 $(BLOCKQUOTE 1532 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. 1533 1534 $(CITE https://tronche.com/gui/x/icccm/sec-4.html) 1535 ) 1536 1537 So if you are using a window type that already describes this like [WindowTypes.dropdownMenu] etc., you should not use this flag. 1538 1539 History: 1540 Added February 23, 2021 but not yet stabilized. 1541 +/ 1542 transient = 64, 1543 /++ 1544 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. 1545 1546 This is currently only used for the WM_TAKE_FOCUS protocol on X11 at this time. 1547 1548 History: 1549 Added April 1, 2022 1550 +/ 1551 managesChildWindowFocus = 128, 1552 1553 dontAutoShow = 0x1000_0000, /// Don't automatically show window after creation; you will have to call `show()` manually. 1554 } 1555 1556 /++ 1557 When creating a window, you can pass a type to SimpleWindow's constructor, 1558 then further customize the window by changing `WindowFlags`. 1559 1560 1561 You should mostly only need [normal], [undecorated], and [eventOnly] for normal 1562 use. The others are there to build a foundation for a higher level GUI toolkit, 1563 but are themselves not as high level as you might think from their names. 1564 1565 This list is based on the EMWH spec for X11. 1566 http://standards.freedesktop.org/wm-spec/1.4/ar01s05.html#idm139704063786896 1567 +/ 1568 enum WindowTypes : int { 1569 /// An ordinary application window. 1570 normal, 1571 /// 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. 1572 undecorated, 1573 /// 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. 1574 eventOnly, 1575 /// A drop down menu, such as from a menu bar 1576 dropdownMenu, 1577 /// A popup menu, such as from a right click 1578 popupMenu, 1579 /// A popup bubble notification 1580 notification, 1581 /* 1582 menu, /// a tearable menu bar 1583 splashScreen, /// a loading splash screen for your application 1584 tooltip, /// A tiny window showing temporary help text or something. 1585 comboBoxDropdown, 1586 dialog, 1587 toolbar 1588 */ 1589 /// a child nested inside the parent. You must pass a parent window to the ctor 1590 nestedChild, 1591 1592 /++ 1593 The type you get when you pass in an existing browser handle, which means most 1594 of simpledisplay's fancy things will not be done since they were never set up. 1595 1596 Using this to the main SimpleWindow constructor explicitly will trigger an assertion 1597 failure; you should use the existing handle constructor. 1598 1599 History: 1600 Added November 17, 2022 (previously it would have type `normal`) 1601 +/ 1602 minimallyWrapped 1603 } 1604 1605 1606 private __gshared ushort sdpyOpenGLContextVersion = 0; // default: use legacy call 1607 private __gshared bool sdpyOpenGLContextCompatible = true; // default: allow "deprecated" features 1608 private __gshared char* sdpyWindowClassStr = null; 1609 private __gshared bool sdpyOpenGLContextAllowFallback = false; 1610 1611 /** 1612 Set OpenGL context version to use. This has no effect on non-OpenGL windows. 1613 You may want to change context version if you want to use advanced shaders or 1614 other modern OpenGL techinques. This setting doesn't affect already created 1615 windows. You may use version 2.1 as your default, which should be supported 1616 by any box since 2006, so seems to be a reasonable choice. 1617 1618 Note that by default version is set to `0`, which forces SimpleDisplay to use 1619 old context creation code without any version specified. This is the safest 1620 way to init OpenGL, but it may not give you access to advanced features. 1621 1622 See available OpenGL versions here: https://en.wikipedia.org/wiki/OpenGL 1623 */ 1624 void setOpenGLContextVersion() (ubyte hi, ubyte lo) { sdpyOpenGLContextVersion = cast(ushort)(hi<<8|lo); } 1625 1626 /** 1627 Set OpenGL context mode. Modern (3.0+) OpenGL versions deprecated old fixed 1628 pipeline functions, and without "compatible" mode you won't be able to use 1629 your old non-shader-based code with such contexts. By default SimpleDisplay 1630 creates compatible context, so you can gradually upgrade your OpenGL code if 1631 you want to (or leave it as is, as it should "just work"). 1632 */ 1633 @property void openGLContextCompatible() (bool v) { sdpyOpenGLContextCompatible = v; } 1634 1635 /** 1636 Set to `true` to allow creating OpenGL context with lower version than requested 1637 instead of throwing. If fallback was activated (or legacy OpenGL was requested), 1638 `openGLContextFallbackActivated()` will return `true`. 1639 */ 1640 @property void openGLContextAllowFallback() (bool v) { sdpyOpenGLContextAllowFallback = v; } 1641 1642 /** 1643 After creating OpenGL window, you can check this to see if you got only "legacy" OpenGL context. 1644 */ 1645 @property bool openGLContextFallbackActivated() () { return (sdpyOpenGLContextVersion == 0); } 1646 1647 /++ 1648 History: 1649 Added April 24, 2023 (dub v11.0) 1650 +/ 1651 version(without_opengl) {} else 1652 auto openGLCurrentContext() { 1653 version(Windows) 1654 return wglGetCurrentContext(); 1655 else 1656 return glXGetCurrentContext(); 1657 } 1658 1659 1660 /** 1661 Set window class name for all following `new SimpleWindow()` calls. 1662 1663 WARNING! For Windows, you should set your class name before creating any 1664 window, and NEVER change it after that! 1665 */ 1666 void sdpyWindowClass (const(char)[] v) { 1667 import core.stdc.stdlib : realloc; 1668 if (v.length == 0) v = "SimpleDisplayWindow"; 1669 sdpyWindowClassStr = cast(char*)realloc(sdpyWindowClassStr, v.length+1); 1670 if (sdpyWindowClassStr is null) return; // oops 1671 sdpyWindowClassStr[0..v.length+1] = 0; 1672 sdpyWindowClassStr[0..v.length] = v[]; 1673 } 1674 1675 /** 1676 Get current window class name. 1677 */ 1678 string sdpyWindowClass () @trusted { 1679 if (sdpyWindowClassStr is null) return null; 1680 foreach (immutable idx; 0..size_t.max-1) { 1681 if (sdpyWindowClassStr[idx] == 0) return sdpyWindowClassStr[0..idx].idup; 1682 } 1683 return null; 1684 } 1685 1686 /++ 1687 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. 1688 1689 If you want per-monitor dpi values, check [SimpleWindow.actualDpi], but you can fall back to this if it returns 0. 1690 +/ 1691 float[2] getDpi() { 1692 float[2] dpi; 1693 version(Windows) { 1694 HDC screen = GetDC(null); 1695 dpi[0] = GetDeviceCaps(screen, LOGPIXELSX); 1696 dpi[1] = GetDeviceCaps(screen, LOGPIXELSY); 1697 } else version(X11) { 1698 auto display = XDisplayConnection.get; 1699 auto screen = DefaultScreen(display); 1700 1701 void fallback() { 1702 /+ 1703 // 25.4 millimeters in an inch... 1704 dpi[0] = cast(float) DisplayWidth(display, screen) / DisplayWidthMM(display, screen) * 25.4; 1705 dpi[1] = cast(float) DisplayHeight(display, screen) / DisplayHeightMM(display, screen) * 25.4; 1706 +/ 1707 1708 // the physical size isn't actually as important as the logical size since this is 1709 // all about scaling really 1710 dpi[0] = 96; 1711 dpi[1] = 96; 1712 } 1713 1714 auto xft = getXftDpi(); 1715 if(xft is float.init) 1716 fallback(); 1717 else { 1718 dpi[0] = xft; 1719 dpi[1] = xft; 1720 } 1721 } 1722 1723 return dpi; 1724 } 1725 1726 version(X11) 1727 float getXftDpi() { 1728 auto display = XDisplayConnection.get; 1729 1730 char* resourceString = XResourceManagerString(display); 1731 XrmInitialize(); 1732 1733 if (resourceString) { 1734 auto db = XrmGetStringDatabase(resourceString); 1735 XrmValue value; 1736 char* type; 1737 if (XrmGetResource(db, "Xft.dpi", "String", &type, &value) == true) { 1738 if (value.addr) { 1739 import core.stdc.stdlib; 1740 return atof(cast(char*) value.addr); 1741 } 1742 } 1743 } 1744 1745 return float.init; 1746 } 1747 1748 /++ 1749 Implementation used by [SimpleWindow.takeScreenshot]. 1750 1751 Params: 1752 handle = the native window handle. If `NativeWindowHandle.init`, it will attempt to get the whole screen. 1753 width = the width of the image you wish to capture. If 0, it will attempt to capture the full width of the target. 1754 height = the height of the image you wish to capture. If 0, it will attempt to capture the full height of the target. 1755 x = the x-offset of the image to capture, from the left. 1756 y = the y-offset of the image to capture, from the top. 1757 1758 History: 1759 Added on March 14, 2021 1760 1761 Documented public on September 23, 2021 with full support for null params (dub 10.3) 1762 1763 +/ 1764 TrueColorImage trueColorImageFromNativeHandle(PaintingHandle handle, int width = 0, int height = 0, int x = 0, int y = 0) { 1765 TrueColorImage got; 1766 version(X11) { 1767 auto display = XDisplayConnection.get; 1768 if(handle == 0) 1769 handle = RootWindow(display, DefaultScreen(display)); 1770 1771 if(width == 0 || height == 0) { 1772 Window root; 1773 int xpos, ypos; 1774 uint widthret, heightret, borderret, depthret; 1775 XGetGeometry(display, handle, &root, &xpos, &ypos, &widthret, &heightret, &borderret, &depthret); 1776 1777 if(width == 0) 1778 width = widthret; 1779 if(height == 0) 1780 height = heightret; 1781 } 1782 1783 auto image = XGetImage(display, handle, x, y, width, height, (cast(c_ulong) ~0) /*AllPlanes*/, ImageFormat.ZPixmap); 1784 1785 // https://github.com/adamdruppe/arsd/issues/98 1786 1787 auto i = new Image(image); 1788 got = i.toTrueColorImage(); 1789 1790 XDestroyImage(image); 1791 } else version(Windows) { 1792 auto hdc = GetDC(handle); 1793 scope(exit) ReleaseDC(handle, hdc); 1794 1795 if(width == 0 || height == 0) { 1796 BITMAP bmHeader; 1797 auto bm = GetCurrentObject(hdc, OBJ_BITMAP); 1798 GetObject(bm, BITMAP.sizeof, &bmHeader); 1799 if(width == 0) 1800 width = bmHeader.bmWidth; 1801 if(height == 0) 1802 height = bmHeader.bmHeight; 1803 } 1804 1805 auto i = new Image(width, height); 1806 HDC hdcMem = CreateCompatibleDC(hdc); 1807 HBITMAP hbmOld = SelectObject(hdcMem, i.handle); 1808 BitBlt(hdcMem, x, y, width, height, hdc, 0, 0, SRCCOPY); 1809 SelectObject(hdcMem, hbmOld); 1810 DeleteDC(hdcMem); 1811 1812 got = i.toTrueColorImage(); 1813 } else featureNotImplemented(); 1814 1815 return got; 1816 } 1817 1818 version(Windows) extern(Windows) private alias SetProcessDpiAwarenessContext_t = BOOL function(HANDLE); 1819 version(Windows) extern(Windows) private __gshared UINT function(HWND) GetDpiForWindow; 1820 version(Windows) extern(Windows) private __gshared BOOL function(UINT, UINT, PVOID, UINT, UINT) SystemParametersInfoForDpi; 1821 1822 version(Windows) 1823 shared static this() { 1824 auto lib = LoadLibrary("User32.dll"); 1825 if(lib is null) 1826 return; 1827 //scope(exit) 1828 //FreeLibrary(lib); 1829 1830 SetProcessDpiAwarenessContext_t SetProcessDpiAwarenessContext = cast(SetProcessDpiAwarenessContext_t) GetProcAddress(lib, "SetProcessDpiAwarenessContext"); 1831 1832 if(SetProcessDpiAwarenessContext is null) 1833 return; 1834 1835 enum DPI_AWARENESS_CONTEXT_PER_MONITOR_AWARE_V2 = cast(HANDLE) -4; 1836 if(!SetProcessDpiAwarenessContext(DPI_AWARENESS_CONTEXT_PER_MONITOR_AWARE_V2)) { 1837 //writeln(GetLastError()); 1838 } 1839 1840 GetDpiForWindow = cast(typeof(GetDpiForWindow)) GetProcAddress(lib, "GetDpiForWindow"); 1841 SystemParametersInfoForDpi = cast(typeof(SystemParametersInfoForDpi)) GetProcAddress(lib, "SystemParametersInfoForDpi"); 1842 } 1843 1844 /++ 1845 Blocking mode for event loop calls associated with a window instance. 1846 1847 History: 1848 Added December 8, 2021 (dub v10.5). Prior to that, all calls to 1849 `window.eventLoop` were the same as calls to `EventLoop.get.run`; that 1850 is, all would block until the application quit. 1851 1852 That behavior can still be achieved here with `untilApplicationQuits`, 1853 or explicitly calling the top-level `EventLoop.get.run` function. 1854 +/ 1855 enum BlockingMode { 1856 /++ 1857 The event loop call will block until the whole application is ready 1858 to quit if it is the only one running, but if it is nested inside 1859 another one, it will only block until the window you're calling it on 1860 closes. 1861 +/ 1862 automatic = 0x00, 1863 /++ 1864 The event loop call will only return when the whole application 1865 is ready to quit. This usually means all windows have been closed. 1866 1867 This is appropriate for your main application event loop. 1868 +/ 1869 untilApplicationQuits = 0x01, 1870 /++ 1871 The event loop will return when the window you're calling it on 1872 closes. If there are other windows still open, they may be destroyed 1873 unless you have another event loop running later. 1874 1875 This might be appropriate for a modal dialog box loop. Remember that 1876 other windows are still processing input though, so you can end up 1877 with a lengthy call stack if this happens in a loop, similar to a 1878 recursive function (well, it literally is a recursive function, just 1879 not an obvious looking one). 1880 +/ 1881 untilWindowCloses = 0x02, 1882 /++ 1883 If an event loop is already running, this call will immediately 1884 return, allowing the existing loop to handle it. If not, this call 1885 will block until the condition you bitwise-or into the flag. 1886 1887 The default is to block until the application quits, same as with 1888 the `automatic` setting (since if it were nested, which triggers until 1889 window closes in automatic, this flag would instead not block at all), 1890 but if you used `BlockingMode.onlyIfNotNested | BlockingMode.untilWindowCloses`, 1891 it will only nest until the window closes. You might want that if you are 1892 going to open two windows simultaneously and want closing just one of them 1893 to trigger the event loop return. 1894 +/ 1895 onlyIfNotNested = 0x10, 1896 } 1897 1898 /++ 1899 Window corner visuals preference 1900 +/ 1901 enum CornerStyle { 1902 /++ 1903 Use the default style automatically applied by the system or its window manager/compositor. 1904 +/ 1905 automatic, 1906 1907 /++ 1908 Prefer rectangular window corners 1909 +/ 1910 rectangular, 1911 1912 /++ 1913 Prefer rounded window corners 1914 +/ 1915 rounded, 1916 1917 /++ 1918 Prefer slightly-rounded window corners 1919 +/ 1920 roundedSlightly, 1921 } 1922 1923 /++ 1924 The flagship window class. 1925 1926 1927 SimpleWindow tries to make ordinary windows very easy to create and use without locking you 1928 out of more advanced or complex features of the underlying windowing system. 1929 1930 For many applications, you can simply call `new SimpleWindow(some_width, some_height, "some title")` 1931 and get a suitable window to work with. 1932 1933 From there, you can opt into additional features, like custom resizability and OpenGL support 1934 with the next two constructor arguments. Or, if you need even more, you can set a window type 1935 and customization flags with the final two constructor arguments. 1936 1937 If none of that works for you, you can also create a window using native function calls, then 1938 wrap the window in a SimpleWindow instance by calling `new SimpleWindow(native_handle)`. Remember, 1939 though, if you do this, managing the window is still your own responsibility! Notably, you 1940 will need to destroy it yourself. 1941 +/ 1942 class SimpleWindow : CapableOfHandlingNativeEvent, CapableOfBeingDrawnUpon { 1943 1944 /++ 1945 Copies the window's current state into a [TrueColorImage]. 1946 1947 Be warned: this can be a very slow operation 1948 1949 History: 1950 Actually implemented on March 14, 2021 1951 +/ 1952 TrueColorImage takeScreenshot() { 1953 version(Windows) 1954 return trueColorImageFromNativeHandle(impl.hwnd, _width, _height); 1955 else version(OSXCocoa) 1956 throw new NotYetImplementedException(); 1957 else 1958 return trueColorImageFromNativeHandle(impl.window, _width, _height); 1959 } 1960 1961 /++ 1962 Returns the actual logical DPI for the window on its current display monitor. If the window 1963 straddles monitors, it will return the value of one or the other in a platform-defined manner. 1964 1965 Please note this function may return zero if it doesn't know the answer! 1966 1967 1968 On Windows, it returns the dpi per monitor if the operating system supports it (Windows 10), 1969 or a system dpi value if not, which will live-update if the OS supports it (Windows 8 and up). 1970 1971 On X, it reads the xrandr extension to determine monitor positions and sizes. On some systems, 1972 this is not provided, meaning it will return 0. Otherwise, it will determine which monitor the 1973 window primarily resides on by checking the center point of the window against the monitor map. 1974 1975 Returns: 1976 0 if unknown. Otherwise, a rounded value of dots per inch reported by the monitor. It 1977 assumes the X and Y dpi are the same. 1978 1979 History: 1980 Added November 26, 2021 (dub v10.4) 1981 1982 It said "physical dpi" in the description prior to July 29, 2022, but the behavior was 1983 always a logical value on Windows and usually one on Linux too, so now the docs reflect 1984 that. 1985 1986 Bugs: 1987 Probably plenty. I haven't done a lot of tests on this. I know it doesn't automatically 1988 just work on linux; you need to set ARSD_SCALING_FACTOR as an environment variable to 1989 set it. Set ARSD_SCALING_FACTOR=1;1.5 for example to set it to 1x on the primary monitor 1990 and 1.5 on the secondary monitor. 1991 1992 The local dpi is not necessarily related to the physical dpi of the monitor. The name 1993 is a historical misnomer - the real thing of interest is the scale factor and due to 1994 compatibility concerns the scale would modify dpi values to trick applications. But since 1995 that's the terminology common out there, I used it too. 1996 1997 See_Also: 1998 [getDpi] gives the value provided for the default monitor. Not necessarily the same 1999 as this since the window many be on a different monitor, but it is a reasonable fallback 2000 to use if `actualDpi` returns 0. 2001 2002 [onDpiChanged] is changed when `actualDpi` has changed. 2003 +/ 2004 int actualDpi() { 2005 version(X11) bool useFallbackDpi = false; 2006 if(!actualDpiLoadAttempted) { 2007 // FIXME: do the actual monitor we are on 2008 // and on X this is a good chance to load the monitor map. 2009 version(Windows) { 2010 if(GetDpiForWindow) 2011 actualDpi_ = GetDpiForWindow(impl.hwnd); 2012 } else version(X11) { 2013 if(!xRandrInfoLoadAttemped) { 2014 xRandrInfoLoadAttemped = true; 2015 if(!XRandrLibrary.attempted) { 2016 XRandrLibrary.loadDynamicLibrary(); 2017 } 2018 2019 if(XRandrLibrary.loadSuccessful) { 2020 auto display = XDisplayConnection.get; 2021 int scratch; 2022 int major, minor; 2023 if(!XRRQueryExtension(display, &xrrEventBase, &scratch)) 2024 goto fallback; 2025 2026 XRRQueryVersion(display, &major, &minor); 2027 if(major <= 1 && minor < 5) 2028 goto fallback; 2029 2030 int count; 2031 XRRMonitorInfo *monitors = XRRGetMonitors(display, RootWindow(display, DefaultScreen(display)), true, &count); 2032 if(monitors is null) 2033 goto fallback; 2034 scope(exit) XRRFreeMonitors(monitors); 2035 2036 MonitorInfo.info = MonitorInfo.info[0 .. 0]; 2037 MonitorInfo.info.assumeSafeAppend(); 2038 foreach(idx, monitor; monitors[0 .. count]) { 2039 MonitorInfo.info ~= MonitorInfo( 2040 Rectangle(Point(monitor.x, monitor.y), Size(monitor.width, monitor.height)), 2041 Size(monitor.mwidth, monitor.mheight), 2042 cast(int) (customScalingFactorForMonitor(cast(int) idx) * getDpi()[0]) 2043 ); 2044 2045 /+ 2046 if(monitor.mwidth == 0 || monitor.mheight == 0) 2047 // unknown physical size, just guess 96 to avoid divide by zero 2048 MonitorInfo.info ~= MonitorInfo( 2049 Rectangle(Point(monitor.x, monitor.y), Size(monitor.width, monitor.height)), 2050 Size(monitor.mwidth, monitor.mheight), 2051 96 2052 ); 2053 else 2054 // and actual thing 2055 MonitorInfo.info ~= MonitorInfo( 2056 Rectangle(Point(monitor.x, monitor.y), Size(monitor.width, monitor.height)), 2057 Size(monitor.mwidth, monitor.mheight), 2058 minInternal( 2059 // millimeter to int then rounding up. 2060 cast(int)(monitor.width * 25.4 / monitor.mwidth + 0.5), 2061 cast(int)(monitor.height * 25.4 / monitor.mheight + 0.5) 2062 ) 2063 ); 2064 +/ 2065 } 2066 // writeln("Here", MonitorInfo.info); 2067 } 2068 } 2069 2070 if(XRandrLibrary.loadSuccessful) { 2071 updateActualDpi(true); 2072 // writeln("updated"); 2073 2074 if(!requestedInput) { 2075 // this is what requests live updates should the configuration change 2076 // each time you select input, it sends an initial event, so very important 2077 // to not get into a loop of selecting input, getting event, updating data, 2078 // and reselecting input... 2079 requestedInput = true; 2080 XRRSelectInput(display, impl.window, RRScreenChangeNotifyMask); 2081 // writeln("requested input"); 2082 } 2083 } else { 2084 fallback: 2085 // make sure we disable events that aren't coming 2086 xrrEventBase = -1; 2087 // best guess... respect the custom scaling user command to some extent at least though 2088 useFallbackDpi = true; 2089 } 2090 } else version(OSXCocoa) { 2091 actualDpi_ = cast(int)(96 * customScalingFactorForMonitor(0)); // FIXME 2092 } 2093 actualDpiLoadAttempted = true; 2094 } else version(X11) if(MonitorInfo.info.length == 0) { 2095 useFallbackDpi = true; 2096 } 2097 2098 version(X11) 2099 if(useFallbackDpi) 2100 actualDpi_ = cast(int) (getDpi()[0] * customScalingFactorForMonitor(0)); 2101 2102 return actualDpi_; 2103 } 2104 2105 private int actualDpi_; 2106 private bool actualDpiLoadAttempted; 2107 2108 version(X11) private { 2109 bool requestedInput; 2110 static bool xRandrInfoLoadAttemped; 2111 struct MonitorInfo { 2112 Rectangle position; 2113 Size size; 2114 int dpi; 2115 2116 static MonitorInfo[] info; 2117 } 2118 bool screenPositionKnown; 2119 int screenPositionX; 2120 int screenPositionY; 2121 void updateActualDpi(bool loadingNow = false) { 2122 if(!loadingNow && !actualDpiLoadAttempted) 2123 actualDpi(); // just to make it do the load 2124 foreach(idx, m; MonitorInfo.info) { 2125 if(m.position.contains(Point(screenPositionX + this.width / 2, screenPositionY + this.height / 2))) { 2126 bool changed = actualDpi_ && actualDpi_ != m.dpi; 2127 actualDpi_ = m.dpi; 2128 // writeln("monitor ", idx); 2129 if(changed && onDpiChanged) 2130 onDpiChanged(); 2131 break; 2132 } 2133 } 2134 } 2135 } 2136 2137 /++ 2138 Sent when the window is moved to a new DPI context, for example, when it is dragged between monitors 2139 or if the window is moved to a new remote connection or a monitor is hot-swapped. 2140 2141 History: 2142 Added November 26, 2021 (dub v10.4) 2143 2144 See_Also: 2145 [actualDpi] 2146 +/ 2147 void delegate() onDpiChanged; 2148 2149 version(X11) { 2150 void recreateAfterDisconnect() { 2151 if(!stateDiscarded) return; 2152 2153 if(_parent !is null && _parent.stateDiscarded) 2154 _parent.recreateAfterDisconnect(); 2155 2156 bool wasHidden = hidden; 2157 2158 activeScreenPainter = null; // should already be done but just to confirm 2159 2160 actualDpi_ = 0; 2161 actualDpiLoadAttempted = false; 2162 xRandrInfoLoadAttemped = false; 2163 2164 impl.createWindow(_width, _height, _title, openglMode, _parent); 2165 2166 if(auto dh = dropHandler) { 2167 dropHandler = null; 2168 enableDragAndDrop(this, dh); 2169 } 2170 2171 if(recreateAdditionalConnectionState) 2172 recreateAdditionalConnectionState(); 2173 2174 hidden = wasHidden; 2175 stateDiscarded = false; 2176 } 2177 2178 bool stateDiscarded; 2179 void discardConnectionState() { 2180 if(XDisplayConnection.display) 2181 impl.dispose(); // if display is already null, it is hopeless to try to destroy stuff on it anyway 2182 if(discardAdditionalConnectionState) 2183 discardAdditionalConnectionState(); 2184 stateDiscarded = true; 2185 } 2186 2187 void delegate() discardAdditionalConnectionState; 2188 void delegate() recreateAdditionalConnectionState; 2189 2190 } 2191 2192 private DropHandler dropHandler; 2193 2194 SimpleWindow _parent; 2195 bool beingOpenKeepsAppOpen = true; 2196 /++ 2197 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. 2198 2199 The constructor tries to have sane default arguments, so for many cases, you only need to provide a few of them. 2200 2201 Params: 2202 2203 width = the width of the window's client area, in pixels 2204 height = the height of the window's client area, in pixels 2205 title = the title of the window (seen in the title bar, taskbar, etc.). You can change it after construction with the [SimpleWindow.title] property. 2206 opengl = [OpenGlOptions] are yes and no. If yes, it creates an OpenGL context on the window. 2207 resizable = [Resizability] has three options: 2208 $(P `allowResizing`, which allows the window to be resized by the user. The `windowResized` delegate will be called when the size is changed.) 2209 $(P `fixedSize` will not allow the user to resize the window.) 2210 $(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.) 2211 windowType = The type of window you want to make. 2212 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. 2213 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". 2214 +/ 2215 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) { 2216 claimGuiThread(); 2217 version(sdpy_thread_checks) assert(thisIsGuiThread); 2218 this._width = this._virtualWidth = width; 2219 this._height = this._virtualHeight = height; 2220 this.openglMode = opengl; 2221 version(X11) { 2222 // auto scale not implemented except with opengl and even there it is kinda weird 2223 if(resizable == Resizability.automaticallyScaleIfPossible && opengl == OpenGlOptions.no) 2224 resizable = Resizability.fixedSize; 2225 } 2226 this.resizability = resizable; 2227 this.windowType = windowType; 2228 this.customizationFlags = customizationFlags; 2229 this._title = (title is null ? "D Application" : title); 2230 this._parent = parent; 2231 impl.createWindow(width, height, this._title, opengl, parent); 2232 2233 if(windowType == WindowTypes.dropdownMenu || windowType == WindowTypes.popupMenu || windowType == WindowTypes.nestedChild || (customizationFlags & WindowFlags.transient)) 2234 beingOpenKeepsAppOpen = false; 2235 } 2236 2237 /// ditto 2238 this(int width, int height, string title, Resizability resizable, OpenGlOptions opengl = OpenGlOptions.no, WindowTypes windowType = WindowTypes.normal, int customizationFlags = WindowFlags.normal, SimpleWindow parent = null) { 2239 this(width, height, title, opengl, resizable, windowType, customizationFlags, parent); 2240 } 2241 2242 /// Same as above, except using the `Size` struct instead of separate width and height. 2243 this(Size size, string title = null, OpenGlOptions opengl = OpenGlOptions.no, Resizability resizable = Resizability.automaticallyScaleIfPossible) { 2244 this(size.width, size.height, title, opengl, resizable); 2245 } 2246 2247 /// ditto 2248 this(Size size, string title, Resizability resizable, OpenGlOptions opengl = OpenGlOptions.no) { 2249 this(size, title, opengl, resizable); 2250 } 2251 2252 2253 /++ 2254 Creates a window based on the given [Image]. It's client area 2255 width and height is equal to the image. (A window's client area 2256 is the drawable space inside; it excludes the title bar, etc.) 2257 2258 Windows based on images will not be resizable and do not use OpenGL. 2259 2260 It will draw the image in upon creation, but this will be overwritten 2261 upon any draws, including the initial window visible event. 2262 2263 You probably do not want to use this and it may be removed from 2264 the library eventually, or I might change it to be a "permanent" 2265 background image; one that is automatically drawn on it before any 2266 other drawing event. idk. 2267 +/ 2268 this(Image image, string title = null) { 2269 this(image.width, image.height, title); 2270 this.image = image; 2271 } 2272 2273 /++ 2274 Wraps a native window handle with very little additional processing - notably no destruction 2275 this is incomplete so don't use it for much right now. The purpose of this is to make native 2276 windows created through the low level API (so you can use platform-specific options and 2277 other details SimpleWindow does not expose) available to the event loop wrappers. 2278 +/ 2279 this(NativeWindowHandle nativeWindow) { 2280 windowType = WindowTypes.minimallyWrapped; 2281 version(Windows) 2282 impl.hwnd = nativeWindow; 2283 else version(X11) { 2284 impl.window = nativeWindow; 2285 if(nativeWindow) 2286 display = XDisplayConnection.get(); // get initial display to not segfault 2287 } else version(OSXCocoa) { 2288 if(nativeWindow !is NullWindow) throw new NotYetImplementedException(); 2289 } else featureNotImplemented(); 2290 // FIXME: set the size correctly 2291 _width = 1; 2292 _height = 1; 2293 if(nativeWindow) 2294 nativeMapping[cast(void*) nativeWindow] = this; 2295 2296 beingOpenKeepsAppOpen = false; 2297 2298 if(nativeWindow) 2299 CapableOfHandlingNativeEvent.nativeHandleMapping[nativeWindow] = this; 2300 _suppressDestruction = true; // so it doesn't try to close 2301 } 2302 2303 /++ 2304 Used iff [WindowFlags.managesChildWindowFocus] is set when the window is created. 2305 The delegate will be called when the window manager asks you to take focus. 2306 2307 This is currently only used for the WM_TAKE_FOCUS protocol on X11 at this time. 2308 2309 History: 2310 Added April 1, 2022 (dub v10.8) 2311 +/ 2312 SimpleWindow delegate() setRequestedInputFocus; 2313 2314 /// Experimental, do not use yet 2315 /++ 2316 Grabs exclusive input from the user until you release it with 2317 [releaseInputGrab]. 2318 2319 2320 Note: it is extremely rude to do this without good reason. 2321 Reasons may include doing some kind of mouse drag operation 2322 or popping up a temporary menu that should get events and will 2323 be dismissed at ease by the user clicking away. 2324 2325 Params: 2326 keyboard = do you want to grab keyboard input? 2327 mouse = grab mouse input? 2328 confine = confine the mouse cursor to inside this window? 2329 2330 History: 2331 Prior to March 11, 2021, grabbing the keyboard would always also 2332 set the X input focus. Now, it only focuses if it is a non-transient 2333 window and otherwise manages the input direction internally. 2334 2335 This means spurious focus/blur events will no longer be sent and the 2336 application will not steal focus from other applications (which the 2337 window manager may have rejected anyway). 2338 +/ 2339 void grabInput(bool keyboard = true, bool mouse = true, bool confine = false) { 2340 static if(UsingSimpledisplayX11) { 2341 XSync(XDisplayConnection.get, 0); 2342 if(keyboard) { 2343 if(isTransient && _parent) { 2344 /* 2345 FIXME: 2346 setting the keyboard focus is not actually that helpful, what I more likely want 2347 is the events from the parent window to be sent over here if we're transient. 2348 */ 2349 2350 _parent.inputProxy = this; 2351 } else { 2352 2353 SimpleWindow setTo; 2354 if(setRequestedInputFocus !is null) 2355 setTo = setRequestedInputFocus(); 2356 if(setTo is null) 2357 setTo = this; 2358 XSetInputFocus(XDisplayConnection.get, setTo.impl.window, RevertToParent, CurrentTime); 2359 } 2360 } 2361 if(mouse) { 2362 if(auto res = XGrabPointer(XDisplayConnection.get, this.impl.window, false /* owner_events */, 2363 EventMask.PointerMotionMask // FIXME: not efficient 2364 | EventMask.ButtonPressMask 2365 | EventMask.ButtonReleaseMask 2366 /* event mask */, GrabMode.GrabModeAsync, GrabMode.GrabModeAsync, confine ? this.impl.window : None, None, CurrentTime) 2367 ) 2368 { 2369 XSync(XDisplayConnection.get, 0); 2370 import core.stdc.stdio; 2371 printf("Grab input failed %d\n", res); 2372 //throw new Exception("Grab input failed"); 2373 } else { 2374 // cool 2375 } 2376 } 2377 2378 } else version(Windows) { 2379 // FIXME: keyboard? 2380 SetCapture(impl.hwnd); 2381 if(confine) { 2382 RECT rcClip; 2383 //RECT rcOldClip; 2384 //GetClipCursor(&rcOldClip); 2385 GetWindowRect(hwnd, &rcClip); 2386 ClipCursor(&rcClip); 2387 } 2388 } else version(OSXCocoa) { 2389 // throw new NotYetImplementedException(); 2390 } else static assert(0); 2391 } 2392 2393 private Point imePopupLocation = Point(0, 0); 2394 2395 /++ 2396 Sets the location for the IME (input method editor) to pop up when the user activates it. 2397 2398 Bugs: 2399 Not implemented outside X11. 2400 +/ 2401 void setIMEPopupLocation(Point location) { 2402 static if(UsingSimpledisplayX11) { 2403 imePopupLocation = location; 2404 updateIMEPopupLocation(); 2405 } else { 2406 // this is non-fatal at this point... but still wanna find it when i search for NotYetImplementedException at least 2407 // throw new NotYetImplementedException(); 2408 } 2409 } 2410 2411 /// ditto 2412 void setIMEPopupLocation(int x, int y) { 2413 return setIMEPopupLocation(Point(x, y)); 2414 } 2415 2416 // we need to remind XIM of where we wanted to place the IME whenever the window moves 2417 // so this function gets called in setIMEPopupLocation as well as whenever the window 2418 // receives a ConfigureNotify event 2419 private void updateIMEPopupLocation() { 2420 static if(UsingSimpledisplayX11) { 2421 if (xic is null) { 2422 return; 2423 } 2424 2425 XPoint nspot; 2426 nspot.x = cast(short) imePopupLocation.x; 2427 nspot.y = cast(short) imePopupLocation.y; 2428 XVaNestedList preeditAttr = XVaCreateNestedList(0, /*XNSpotLocation*/"spotLocation".ptr, &nspot, null); 2429 XSetICValues(xic, /*XNPreeditAttributes*/"preeditAttributes".ptr, preeditAttr, null); 2430 XFree(preeditAttr); 2431 } 2432 } 2433 2434 private bool imeFocused = true; 2435 2436 /++ 2437 Tells the IME whether or not an input field is currently focused in the window. 2438 2439 Bugs: 2440 Not implemented outside X11. 2441 +/ 2442 void setIMEFocused(bool value) { 2443 imeFocused = value; 2444 updateIMEFocused(); 2445 } 2446 2447 // used to focus/unfocus the IC if necessary when the window gains/loses focus 2448 private void updateIMEFocused() { 2449 static if(UsingSimpledisplayX11) { 2450 if (xic is null) { 2451 return; 2452 } 2453 2454 if (focused && imeFocused) { 2455 XSetICFocus(xic); 2456 } else { 2457 XUnsetICFocus(xic); 2458 } 2459 } 2460 } 2461 2462 /++ 2463 Returns the native window. 2464 2465 History: 2466 Added November 5, 2021 (dub v10.4). Prior to that, you'd have 2467 to access it through the `impl` member (which is semi-supported 2468 but platform specific and here it is simple enough to offer an accessor). 2469 2470 Bugs: 2471 Not implemented outside Windows or X11. 2472 +/ 2473 NativeWindowHandle nativeWindowHandle() { 2474 version(X11) 2475 return impl.window; 2476 else version(Windows) 2477 return impl.hwnd; 2478 else 2479 throw new NotYetImplementedException(); 2480 } 2481 2482 private bool isTransient() { 2483 with(WindowTypes) 2484 final switch(windowType) { 2485 case normal, undecorated, eventOnly: 2486 case nestedChild, minimallyWrapped: 2487 return (customizationFlags & WindowFlags.transient) ? true : false; 2488 case dropdownMenu, popupMenu, notification: 2489 return true; 2490 } 2491 } 2492 2493 private SimpleWindow inputProxy; 2494 2495 /++ 2496 Releases the grab acquired by [grabInput]. 2497 +/ 2498 void releaseInputGrab() { 2499 static if(UsingSimpledisplayX11) { 2500 XUngrabPointer(XDisplayConnection.get, CurrentTime); 2501 if(_parent) 2502 _parent.inputProxy = null; 2503 } else version(Windows) { 2504 ReleaseCapture(); 2505 ClipCursor(null); 2506 } else version(OSXCocoa) { 2507 // throw new NotYetImplementedException(); 2508 } else static assert(0); 2509 } 2510 2511 /++ 2512 Sets the input focus to this window. 2513 2514 You shouldn't call this very often - please let the user control the input focus. 2515 +/ 2516 void focus() { 2517 static if(UsingSimpledisplayX11) { 2518 SimpleWindow setTo; 2519 if(setRequestedInputFocus !is null) 2520 setTo = setRequestedInputFocus(); 2521 if(setTo is null) 2522 setTo = this; 2523 XSetInputFocus(XDisplayConnection.get, setTo.impl.window, RevertToParent, CurrentTime); 2524 } else version(Windows) { 2525 SetFocus(this.impl.hwnd); 2526 } else version(OSXCocoa) { 2527 throw new NotYetImplementedException(); 2528 } else static assert(0); 2529 } 2530 2531 /++ 2532 Requests attention from the user for this window. 2533 2534 2535 The typical result of this function is to change the color 2536 of the taskbar icon, though it may be tweaked on specific 2537 platforms. 2538 2539 It is meant to unobtrusively tell the user that something 2540 relevant to them happened in the background and they should 2541 check the window when they get a chance. Upon receiving the 2542 keyboard focus, the window will automatically return to its 2543 natural state. 2544 2545 If the window already has the keyboard focus, this function 2546 may do nothing, because the user is presumed to already be 2547 giving the window attention. 2548 2549 Implementation_note: 2550 2551 `requestAttention` uses the _NET_WM_STATE_DEMANDS_ATTENTION 2552 atom on X11 and the FlashWindow function on Windows. 2553 +/ 2554 void requestAttention() { 2555 if(_focused) 2556 return; 2557 2558 version(Windows) { 2559 FLASHWINFO info; 2560 info.cbSize = info.sizeof; 2561 info.hwnd = impl.hwnd; 2562 info.dwFlags = FLASHW_TRAY; 2563 info.uCount = 1; 2564 2565 FlashWindowEx(&info); 2566 2567 } else version(X11) { 2568 demandingAttention = true; 2569 demandAttention(this, true); 2570 } else version(OSXCocoa) { 2571 throw new NotYetImplementedException(); 2572 } else static assert(0); 2573 } 2574 2575 private bool _focused; 2576 2577 version(X11) private bool demandingAttention; 2578 2579 /// This will be called when WM wants to close your window (i.e. user clicked "close" icon, for example). 2580 /// You'll have to call `close()` manually if you set this delegate. 2581 void delegate () closeQuery; 2582 2583 /// This will be called when window visibility was changed. 2584 void delegate (bool becomesVisible) visibilityChanged; 2585 2586 /// This will be called when window becomes visible for the first time. 2587 /// You can do OpenGL initialization here. Note that in X11 you can't call 2588 /// [setAsCurrentOpenGlContext] right after window creation, or X11 may 2589 /// fail to send reparent and map events (hit that with proprietary NVidia drivers). 2590 /// So you need to wait until this is called and call setAsCurrentOpenGlContext in there, then do the OpenGL initialization. 2591 private bool _visibleForTheFirstTimeCalled; 2592 void delegate () visibleForTheFirstTime; 2593 2594 /// Returns true if the window has been closed. 2595 final @property bool closed() { return _closed; } 2596 2597 private final @property bool notClosed() { return !_closed; } 2598 2599 /// Returns true if the window is focused. 2600 final @property bool focused() { return _focused; } 2601 2602 private bool _visible; 2603 /// Returns true if the window is visible (mapped). 2604 final @property bool visible() { return _visible; } 2605 2606 /// Closes the window. If there are no more open windows, the event loop will terminate. 2607 void close() { 2608 if (!_closed) { 2609 runInGuiThread( { 2610 if(_closed) return; // another thread got to it first. this isn't a big deal, it just means our message was queued 2611 if (onClosing !is null) onClosing(); 2612 impl.closeWindow(); 2613 _closed = true; 2614 } ); 2615 } 2616 } 2617 2618 /++ 2619 `close` is one of the few methods that can be called from other threads. This `shared` overload reflects that. 2620 2621 History: 2622 Overload added on March 7, 2021. 2623 +/ 2624 void close() shared { 2625 (cast() this).close(); 2626 } 2627 2628 /++ 2629 2630 +/ 2631 void maximize() { 2632 version(Windows) 2633 ShowWindow(impl.hwnd, SW_MAXIMIZE); 2634 else version(X11) { 2635 setNetWmStateAtom(this.impl.window, GetAtom!("_NET_WM_STATE_MAXIMIZED_VERT", false)(XDisplayConnection.get), true, GetAtom!("_NET_WM_STATE_MAXIMIZED_HORZ", false)(XDisplayConnection.get)); 2636 2637 // also note _NET_WM_STATE_FULLSCREEN 2638 } 2639 2640 } 2641 2642 private bool _fullscreen; 2643 version(Windows) 2644 private WINDOWPLACEMENT g_wpPrev; 2645 2646 /// not fully implemented but planned for a future release 2647 void fullscreen(bool yes) { 2648 version(Windows) { 2649 g_wpPrev.length = WINDOWPLACEMENT.sizeof; 2650 DWORD dwStyle = GetWindowLong(hwnd, GWL_STYLE); 2651 if (dwStyle & WS_OVERLAPPEDWINDOW) { 2652 MONITORINFO mi; 2653 mi.cbSize = MONITORINFO.sizeof; 2654 if (GetWindowPlacement(hwnd, &g_wpPrev) && 2655 GetMonitorInfo(MonitorFromWindow(hwnd, 2656 MONITOR_DEFAULTTOPRIMARY), &mi)) { 2657 SetWindowLong(hwnd, GWL_STYLE, 2658 dwStyle & ~WS_OVERLAPPEDWINDOW); 2659 SetWindowPos(hwnd, HWND_TOP, 2660 mi.rcMonitor.left, mi.rcMonitor.top, 2661 mi.rcMonitor.right - mi.rcMonitor.left, 2662 mi.rcMonitor.bottom - mi.rcMonitor.top, 2663 SWP_NOOWNERZORDER | SWP_FRAMECHANGED); 2664 } 2665 } else { 2666 SetWindowLong(hwnd, GWL_STYLE, 2667 dwStyle | WS_OVERLAPPEDWINDOW); 2668 SetWindowPlacement(hwnd, &g_wpPrev); 2669 SetWindowPos(hwnd, null, 0, 0, 0, 0, 2670 SWP_NOMOVE | SWP_NOSIZE | SWP_NOZORDER | 2671 SWP_NOOWNERZORDER | SWP_FRAMECHANGED); 2672 } 2673 2674 } else version(X11) { 2675 setNetWmStateAtom(this.impl.window, GetAtom!("_NET_WM_STATE_FULLSCREEN", false)(XDisplayConnection.get), yes); 2676 } 2677 2678 _fullscreen = yes; 2679 2680 } 2681 2682 bool fullscreen() { 2683 return _fullscreen; 2684 } 2685 2686 /++ 2687 Note: only implemented on Windows. No-op on other platforms. You may want to use [hide] instead. 2688 2689 +/ 2690 void minimize() { 2691 version(Windows) 2692 ShowWindow(impl.hwnd, SW_MINIMIZE); 2693 //else version(X11) 2694 //setNetWmStateAtom(this, GetAtom!("_NET_WM_STATE_MINIMIZED", false)(XDisplayConnection.get), true); 2695 } 2696 2697 /// Alias for `hidden = false` 2698 void show() { 2699 hidden = false; 2700 } 2701 2702 /// Alias for `hidden = true` 2703 void hide() { 2704 hidden = true; 2705 } 2706 2707 /// Hide cursor when it enters the window. 2708 void hideCursor() { 2709 version(OSXCocoa) throw new NotYetImplementedException(); else 2710 if (!_closed) impl.hideCursor(); 2711 } 2712 2713 /// Don't hide cursor when it enters the window. 2714 void showCursor() { 2715 version(OSXCocoa) throw new NotYetImplementedException(); else 2716 if (!_closed) impl.showCursor(); 2717 } 2718 2719 /** "Warp" mouse pointer to coordinates relative to window top-left corner. Return "success" flag. 2720 * 2721 * Please remember that the cursor is a shared resource that should usually be left to the user's 2722 * control. Try to think for other approaches before using this function. 2723 * 2724 * Note: "warping" pointer will not send any synthesised mouse events, so you probably doesn't want 2725 * to use it to move mouse pointer to some active GUI area, for example, as your window won't 2726 * receive "mouse moved here" event. 2727 */ 2728 bool warpMouse (int x, int y) { 2729 version(X11) { 2730 if (!_closed) { impl.warpMouse(x, y); return true; } 2731 } else version(Windows) { 2732 if (!_closed) { 2733 POINT point; 2734 point.x = x; 2735 point.y = y; 2736 if(ClientToScreen(impl.hwnd, &point)) { 2737 SetCursorPos(point.x, point.y); 2738 return true; 2739 } 2740 } 2741 } 2742 return false; 2743 } 2744 2745 /// Send dummy window event to ping event loop. Required to process NotificationIcon on X11, for example. 2746 void sendDummyEvent () { 2747 version(X11) { 2748 if (!_closed) { impl.sendDummyEvent(); } 2749 } 2750 } 2751 2752 /// Set window minimal size. 2753 void setMinSize (int minwidth, int minheight) { 2754 version(OSXCocoa) throw new NotYetImplementedException(); else 2755 if (!_closed) impl.setMinSize(minwidth, minheight); 2756 } 2757 2758 /// Set window maximal size. 2759 void setMaxSize (int maxwidth, int maxheight) { 2760 version(OSXCocoa) throw new NotYetImplementedException(); else 2761 if (!_closed) impl.setMaxSize(maxwidth, maxheight); 2762 } 2763 2764 /// Set window resize step (window size will be changed with the given granularity on supported platforms). 2765 /// Currently only supported on X11. 2766 void setResizeGranularity (int granx, int grany) { 2767 version(OSXCocoa) throw new NotYetImplementedException(); else 2768 if (!_closed) impl.setResizeGranularity(granx, grany); 2769 } 2770 2771 /// Move window. 2772 void move(int x, int y) { 2773 version(OSXCocoa) throw new NotYetImplementedException(); else 2774 if (!_closed) impl.move(x, y); 2775 } 2776 2777 /// ditto 2778 void move(Point p) { 2779 version(OSXCocoa) throw new NotYetImplementedException(); else 2780 if (!_closed) impl.move(p.x, p.y); 2781 } 2782 2783 /++ 2784 Resize window. 2785 2786 Note that the width and height of the window are NOT instantly 2787 updated - it waits for the window manager to approve the resize 2788 request, which means you must return to the event loop before the 2789 width and height are actually changed. 2790 +/ 2791 void resize(int w, int h) { 2792 if(!_closed && _fullscreen) fullscreen = false; 2793 version(OSXCocoa) throw new NotYetImplementedException(); else 2794 if (!_closed) impl.resize(w, h); 2795 } 2796 2797 /// Move and resize window (this can be faster and more visually pleasant than doing it separately). 2798 void moveResize (int x, int y, int w, int h) { 2799 if(!_closed && _fullscreen) fullscreen = false; 2800 version(OSXCocoa) throw new NotYetImplementedException(); else 2801 if (!_closed) impl.moveResize(x, y, w, h); 2802 } 2803 2804 private bool _hidden; 2805 2806 /// Returns true if the window is hidden. 2807 final @property bool hidden() { 2808 return _hidden; 2809 } 2810 2811 /// Shows or hides the window based on the bool argument. 2812 final @property void hidden(bool b) { 2813 _hidden = b; 2814 version(Windows) { 2815 ShowWindow(impl.hwnd, b ? SW_HIDE : SW_SHOW); 2816 } else version(X11) { 2817 if(b) 2818 //XUnmapWindow(impl.display, impl.window); 2819 XWithdrawWindow(impl.display, impl.window, DefaultScreen(impl.display)); 2820 else 2821 XMapWindow(impl.display, impl.window); 2822 } else version(OSXCocoa) { 2823 // throw new NotYetImplementedException(); 2824 } else static assert(0); 2825 } 2826 2827 /// Sets the window opacity. On X11 this requires a compositor to be running. On windows the WindowFlags.extraComposite must be set at window creation. 2828 void opacity(double opacity) @property 2829 in { 2830 assert(opacity >= 0 && opacity <= 1); 2831 } do { 2832 version (Windows) { 2833 impl.setOpacity(cast(ubyte)(255 * opacity)); 2834 } else version (X11) { 2835 impl.setOpacity(cast(uint)(uint.max * opacity)); 2836 } else throw new NotYetImplementedException(); 2837 } 2838 2839 /++ 2840 Sets your event handlers, without entering the event loop. Useful if you 2841 have multiple windows - set the handlers on each window, then only do 2842 [eventLoop] on your main window or call `EventLoop.get.run();`. 2843 2844 This assigns the given handlers to [handleKeyEvent], [handleCharEvent], 2845 [handlePulse], and [handleMouseEvent] automatically based on the provide 2846 delegate signatures. 2847 +/ 2848 void setEventHandlers(T...)(T eventHandlers) { 2849 // FIXME: add more events 2850 foreach(handler; eventHandlers) { 2851 static if(__traits(compiles, handleKeyEvent = handler)) { 2852 handleKeyEvent = handler; 2853 } else static if(__traits(compiles, handleCharEvent = handler)) { 2854 handleCharEvent = handler; 2855 } else static if(__traits(compiles, handlePulse = handler)) { 2856 handlePulse = handler; 2857 } else static if(__traits(compiles, handleMouseEvent = handler)) { 2858 handleMouseEvent = handler; 2859 } else static assert(0, "I can't use this event handler " ~ typeof(handler).stringof ~ "\nHave you tried using the delegate keyword?"); 2860 } 2861 } 2862 2863 /++ 2864 The event loop automatically returns when the window is closed 2865 pulseTimeout is given in milliseconds. If pulseTimeout == 0, no 2866 pulse timer is created. The event loop will block until an event 2867 arrives or the pulse timer goes off. 2868 2869 The given `eventHandlers` are passed to [setEventHandlers], which in turn 2870 assigns them to [handleKeyEvent], [handleCharEvent], [handlePulse], and 2871 [handleMouseEvent], based on the signature of delegates you provide. 2872 2873 Give one with no parameters to set a timer pulse handler. Give one that 2874 takes [KeyEvent] for a key handler, [MouseEvent], for a mouse handler, 2875 and one that takes `dchar` for a char event handler. You can use as many 2876 or as few handlers as you need for your application. 2877 2878 Bugs: 2879 2880 $(PITFALL 2881 You should always have one event loop live for your application. 2882 If you make two windows in sequence, the second call to eventLoop 2883 might fail: 2884 2885 --- 2886 // don't do this! 2887 auto window = new SimpleWindow(); 2888 window.eventLoop(0); 2889 2890 auto window2 = new SimpleWindow(); 2891 window2.eventLoop(0); // problematic! might crash 2892 --- 2893 2894 simpledisplay's current implementation assumes that final cleanup is 2895 done when the event loop refcount reaches zero. So after the first 2896 eventLoop returns, when there isn't already another one active, it assumes 2897 the program will exit soon and cleans up. 2898 2899 This is arguably a bug that it doesn't reinitialize, and I'll probably change 2900 it eventually, but in the mean time, there's an easy solution: 2901 2902 --- 2903 // do this 2904 EventLoop mainEventLoop = EventLoop.get; // just add this line 2905 2906 auto window = new SimpleWindow(); 2907 window.eventLoop(0); 2908 2909 auto window2 = new SimpleWindow(); 2910 window2.eventLoop(0); // perfectly fine since mainEventLoop still alive 2911 --- 2912 2913 By adding a top-level reference to the event loop, it ensures the final cleanup 2914 is not performed until it goes out of scope too, letting the individual window loops 2915 work without trouble despite the bug. 2916 ) 2917 2918 History: 2919 The overload without `pulseTimeout` was added on December 8, 2021. 2920 2921 On December 9, 2021, the default blocking mode (which is now configurable 2922 because [eventLoopWithBlockingMode] was added) switched from 2923 [BlockingMode.untilApplicationQuits] over to [BlockingMode.automatic]. This 2924 should almost never be noticeable to you since the typical simpledisplay 2925 paradigm has been (and I still recommend) to have one `eventLoop` call. 2926 2927 See_Also: 2928 [eventLoopWithBlockingMode] 2929 +/ 2930 final int eventLoop(T...)( 2931 long pulseTimeout, /// set to zero if you don't want a pulse. 2932 T eventHandlers) /// delegate list like std.concurrency.receive 2933 { 2934 return eventLoopWithBlockingMode(BlockingMode.automatic, pulseTimeout, eventHandlers); 2935 } 2936 2937 /// ditto 2938 final int eventLoop(T...)(T eventHandlers) if(T.length == 0 || is(T[0] == delegate)) 2939 { 2940 return eventLoopWithBlockingMode(BlockingMode.automatic, 0, eventHandlers); 2941 } 2942 2943 /++ 2944 This is the function [eventLoop] forwards to. It, in turn, forwards to `EventLoop.get.run`. 2945 2946 History: 2947 Added December 8, 2021 (dub v10.5) 2948 2949 Previously, this implementation was right inside [eventLoop], but when I wanted 2950 to add the new [BlockingMode] parameter, the compiler got in a trouble loop so I 2951 just renamed it instead of adding as an overload. Besides, the new name makes it 2952 easier to remember the order and avoids ambiguity between two int-like params anyway. 2953 2954 See_Also: 2955 [SimpleWindow.eventLoop], [EventLoop] 2956 2957 Bugs: 2958 The blocking mode is not implemented on OSX Cocoa nor on the (deprecated) arsd.eventloop. 2959 +/ 2960 final int eventLoopWithBlockingMode(T...)( 2961 BlockingMode blockingMode, /// when you want this function to block until 2962 long pulseTimeout, /// set to zero if you don't want a pulse. 2963 T eventHandlers) /// delegate list like std.concurrency.receive 2964 { 2965 setEventHandlers(eventHandlers); 2966 2967 version(with_eventloop) { 2968 // delegates event loop to my other module 2969 version(X11) 2970 XFlush(display); 2971 2972 import arsd.eventloop; 2973 auto handle = setInterval(handlePulse, cast(int) pulseTimeout); 2974 scope(exit) clearInterval(handle); 2975 2976 loop(); 2977 return 0; 2978 } else version(OSXCocoa) { 2979 // FIXME 2980 if (handlePulse !is null && pulseTimeout != 0) { 2981 timer = NSTimer.schedule(pulseTimeout*1e-3, 2982 cast(NSid) view, sel_registerName("simpledisplay_pulse:"), 2983 null, true); 2984 } 2985 2986 view.setNeedsDisplay(true); 2987 2988 NSApp.run(); 2989 return 0; 2990 } else { 2991 EventLoop el = EventLoop(pulseTimeout, handlePulse); 2992 2993 if((blockingMode & BlockingMode.onlyIfNotNested) && el.impl.refcount > 1) 2994 return 0; 2995 2996 return el.run( 2997 ((blockingMode & 0x0f) == BlockingMode.untilApplicationQuits) ? 2998 null : 2999 &this.notClosed 3000 ); 3001 } 3002 } 3003 3004 /++ 3005 This lets you draw on the window (or its backing buffer) using basic 3006 2D primitives. 3007 3008 Be sure to call this in a limited scope because your changes will not 3009 actually appear on the window until ScreenPainter's destructor runs. 3010 3011 Returns: an instance of [ScreenPainter], which has the drawing methods 3012 on it to draw on this window. 3013 3014 Params: 3015 manualInvalidations = if you set this to true, you will need to 3016 set the invalid rectangle on the painter yourself. If false, it 3017 assumes the whole window has been redrawn each time you draw. 3018 3019 Only invalidated rectangles are blitted back to the window when 3020 the destructor runs. Doing this yourself can reduce flickering 3021 of child windows. 3022 3023 History: 3024 The `manualInvalidations` parameter overload was added on 3025 December 30, 2021 (dub v10.5) 3026 +/ 3027 ScreenPainter draw() { 3028 return draw(false); 3029 } 3030 /// ditto 3031 ScreenPainter draw(bool manualInvalidations) { 3032 return impl.getPainter(manualInvalidations); 3033 } 3034 3035 // This is here to implement the interface we use for various native handlers. 3036 NativeEventHandler getNativeEventHandler() { return handleNativeEvent; } 3037 3038 // maps native window handles to SimpleWindow instances, if there are any 3039 // you shouldn't need this, but it is public in case you do in a native event handler or something 3040 // mac uses void* cuz NSObject opHash won't pick up in typeinfo 3041 version(OSXCocoa) 3042 public __gshared SimpleWindow[void*] nativeMapping; 3043 else 3044 public __gshared SimpleWindow[NativeWindowHandle] nativeMapping; 3045 3046 // the size the user requested in the constructor, in automatic scale modes it always pretends to be this size 3047 private int _virtualWidth; 3048 private int _virtualHeight; 3049 3050 /// Width of the window's drawable client area, in pixels. 3051 @scriptable 3052 final @property int width() const pure nothrow @safe @nogc { 3053 if(resizability == Resizability.automaticallyScaleIfPossible) 3054 return _virtualWidth; 3055 else 3056 return _width; 3057 } 3058 3059 /// Height of the window's drawable client area, in pixels. 3060 @scriptable 3061 final @property int height() const pure nothrow @safe @nogc { 3062 if(resizability == Resizability.automaticallyScaleIfPossible) 3063 return _virtualHeight; 3064 else 3065 return _height; 3066 } 3067 3068 /++ 3069 Returns the actual size of the window, bypassing the logical 3070 illusions of [Resizability.automaticallyScaleIfPossible]. 3071 3072 History: 3073 Added November 11, 2022 (dub v10.10) 3074 +/ 3075 final @property Size actualWindowSize() const pure nothrow @safe @nogc { 3076 return Size(_width, _height); 3077 } 3078 3079 3080 private int _width; 3081 private int _height; 3082 3083 // HACK: making the best of some copy constructor woes with refcounting 3084 private ScreenPainterImplementation* activeScreenPainter_; 3085 3086 protected ScreenPainterImplementation* activeScreenPainter() { return activeScreenPainter_; } 3087 protected void activeScreenPainter(ScreenPainterImplementation* i) { activeScreenPainter_ = i; } 3088 3089 private OpenGlOptions openglMode; 3090 private Resizability resizability; 3091 private WindowTypes windowType; 3092 private int customizationFlags; 3093 3094 /// `true` if OpenGL was initialized for this window. 3095 @property bool isOpenGL () const pure nothrow @safe @nogc { 3096 version(without_opengl) 3097 return false; 3098 else 3099 return (openglMode == OpenGlOptions.yes); 3100 } 3101 @property Resizability resizingMode () const pure nothrow @safe @nogc { return resizability; } /// Original resizability. 3102 @property WindowTypes type () const pure nothrow @safe @nogc { return windowType; } /// Original window type. 3103 @property int customFlags () const pure nothrow @safe @nogc { return customizationFlags; } /// Original customization flags. 3104 3105 /// "Lock" this window handle, to do multithreaded synchronization. You probably won't need 3106 /// to call this, as it's not recommended to share window between threads. 3107 void mtLock () { 3108 version(X11) { 3109 XLockDisplay(this.display); 3110 } 3111 } 3112 3113 /// "Unlock" this window handle, to do multithreaded synchronization. You probably won't need 3114 /// to call this, as it's not recommended to share window between threads. 3115 void mtUnlock () { 3116 version(X11) { 3117 XUnlockDisplay(this.display); 3118 } 3119 } 3120 3121 /// Emit a beep to get user's attention. 3122 void beep () { 3123 version(X11) { 3124 XBell(this.display, 100); 3125 } else version(Windows) { 3126 MessageBeep(0xFFFFFFFF); 3127 } 3128 } 3129 3130 3131 3132 version(without_opengl) {} else { 3133 3134 /// 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`. 3135 void delegate() redrawOpenGlScene; 3136 3137 /// This will allow you to change OpenGL vsync state. 3138 final @property void vsync (bool wait) { 3139 if (this._closed) return; // window may be closed, but timer is still firing; avoid GLXBadDrawable error 3140 version(X11) { 3141 setAsCurrentOpenGlContext(); 3142 glxSetVSync(display, impl.window, wait); 3143 } else version(Windows) { 3144 setAsCurrentOpenGlContext(); 3145 wglSetVSync(wait); 3146 } 3147 } 3148 3149 /// Set this to `false` if you don't need to do `glFinish()` after `swapOpenGlBuffers()`. 3150 /// Note that at least NVidia proprietary driver may segfault if you will modify texture fast 3151 /// enough without waiting 'em to finish their frame business. 3152 bool useGLFinish = true; 3153 3154 // FIXME: it should schedule it for the end of the current iteration of the event loop... 3155 /// 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. 3156 void redrawOpenGlSceneNow() { 3157 version(X11) if (!this._visible) return; // no need to do this if window is invisible 3158 if (this._closed) return; // window may be closed, but timer is still firing; avoid GLXBadDrawable error 3159 if(redrawOpenGlScene is null) 3160 return; 3161 3162 this.mtLock(); 3163 scope(exit) this.mtUnlock(); 3164 3165 this.setAsCurrentOpenGlContext(); 3166 3167 redrawOpenGlScene(); 3168 3169 this.swapOpenGlBuffers(); 3170 // 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. 3171 if (useGLFinish) glFinish(); 3172 } 3173 3174 private bool redrawOpenGlSceneSoonSet = false; 3175 private static class RedrawOpenGlSceneEvent { 3176 SimpleWindow w; 3177 this(SimpleWindow w) { this.w = w; } 3178 } 3179 private RedrawOpenGlSceneEvent redrawOpenGlSceneEvent; 3180 /++ 3181 Queues an opengl redraw as soon as the other pending events are cleared. 3182 +/ 3183 void redrawOpenGlSceneSoon() { 3184 if(redrawOpenGlScene is null) 3185 return; 3186 3187 if(!redrawOpenGlSceneSoonSet) { 3188 redrawOpenGlSceneEvent = new RedrawOpenGlSceneEvent(this); 3189 this.addEventListener((RedrawOpenGlSceneEvent e) { e.w.redrawOpenGlSceneNow(); }); 3190 redrawOpenGlSceneSoonSet = true; 3191 } 3192 this.postEvent(redrawOpenGlSceneEvent, true); 3193 } 3194 3195 3196 /// Makes all gl* functions target this window until changed. This is only valid if you passed `OpenGlOptions.yes` to the constructor. 3197 void setAsCurrentOpenGlContext() { 3198 assert(openglMode == OpenGlOptions.yes); 3199 version(X11) { 3200 if(glXMakeCurrent(display, impl.window, impl.glc) == 0) 3201 throw new Exception("glXMakeCurrent"); 3202 } else version(Windows) { 3203 static if (SdpyIsUsingIVGLBinds) import iv.glbinds; // override druntime windows imports 3204 if (!wglMakeCurrent(ghDC, ghRC)) 3205 throw new Exception("wglMakeCurrent " ~ toInternal!int(GetLastError())); // let windows users suffer too 3206 } 3207 } 3208 3209 /// Makes all gl* functions target this window until changed. This is only valid if you passed `OpenGlOptions.yes` to the constructor. 3210 /// This doesn't throw, returning success flag instead. 3211 bool setAsCurrentOpenGlContextNT() nothrow { 3212 assert(openglMode == OpenGlOptions.yes); 3213 version(X11) { 3214 return (glXMakeCurrent(display, impl.window, impl.glc) != 0); 3215 } else version(Windows) { 3216 static if (SdpyIsUsingIVGLBinds) import iv.glbinds; // override druntime windows imports 3217 return wglMakeCurrent(ghDC, ghRC) ? true : false; 3218 } 3219 } 3220 3221 /// 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. 3222 /// This doesn't throw, returning success flag instead. 3223 bool releaseCurrentOpenGlContext() nothrow { 3224 assert(openglMode == OpenGlOptions.yes); 3225 version(X11) { 3226 return (glXMakeCurrent(display, 0, null) != 0); 3227 } else version(Windows) { 3228 static if (SdpyIsUsingIVGLBinds) import iv.glbinds; // override druntime windows imports 3229 return wglMakeCurrent(ghDC, null) ? true : false; 3230 } 3231 } 3232 3233 /++ 3234 simpledisplay always uses double buffering, usually automatically. This 3235 manually swaps the OpenGL buffers. 3236 3237 3238 You should not need to call this yourself because simpledisplay will do it 3239 for you after calling your `redrawOpenGlScene`. 3240 3241 Remember that this may throw an exception, which you can catch in a multithreaded 3242 application to keep your thread from dying from an unhandled exception. 3243 +/ 3244 void swapOpenGlBuffers() { 3245 assert(openglMode == OpenGlOptions.yes); 3246 version(X11) { 3247 if (!this._visible) return; // no need to do this if window is invisible 3248 if (this._closed) return; // window may be closed, but timer is still firing; avoid GLXBadDrawable error 3249 glXSwapBuffers(display, impl.window); 3250 } else version(Windows) { 3251 SwapBuffers(ghDC); 3252 } 3253 } 3254 } 3255 3256 /++ 3257 Set the window title, which is visible on the window manager title bar, operating system taskbar, etc. 3258 3259 3260 --- 3261 auto window = new SimpleWindow(100, 100, "First title"); 3262 window.title = "A new title"; 3263 --- 3264 3265 You may call this function at any time. 3266 +/ 3267 @property void title(string title) { 3268 _title = title; 3269 version(OSXCocoa) throw new NotYetImplementedException(); else 3270 impl.setTitle(title); 3271 } 3272 3273 private string _title; 3274 3275 /// Gets the title 3276 @property string title() { 3277 if(_title is null) 3278 _title = getRealTitle(); 3279 return _title; 3280 } 3281 3282 /++ 3283 Get the title as set by the window manager. 3284 May not match what you attempted to set. 3285 +/ 3286 string getRealTitle() { 3287 static if(is(typeof(impl.getTitle()))) 3288 return impl.getTitle(); 3289 else 3290 return null; 3291 } 3292 3293 // don't use this generally it is not yet really released 3294 version(X11) 3295 @property Image secret_icon() { 3296 return secret_icon_inner; 3297 } 3298 private Image secret_icon_inner; 3299 3300 3301 /// Set the icon that is seen in the title bar or taskbar, etc., for the user. If passed `null`, does nothing. 3302 @property void icon(MemoryImage icon) { 3303 if(icon is null) 3304 return; 3305 auto tci = icon.getAsTrueColorImage(); 3306 version(Windows) { 3307 winIcon = new WindowsIcon(icon); 3308 SendMessageA(impl.hwnd, 0x0080 /*WM_SETICON*/, 0 /*ICON_SMALL*/, cast(LPARAM) winIcon.hIcon); // there is also 1 == ICON_BIG 3309 } else version(X11) { 3310 secret_icon_inner = Image.fromMemoryImage(icon); 3311 // FIXME: ensure this is correct 3312 auto display = XDisplayConnection.get; 3313 arch_ulong[] buffer; 3314 buffer ~= icon.width; 3315 buffer ~= icon.height; 3316 foreach(c; tci.imageData.colors) { 3317 arch_ulong b; 3318 b |= c.a << 24; 3319 b |= c.r << 16; 3320 b |= c.g << 8; 3321 b |= c.b; 3322 buffer ~= b; 3323 } 3324 3325 XChangeProperty( 3326 display, 3327 impl.window, 3328 GetAtom!("_NET_WM_ICON", true)(display), 3329 GetAtom!"CARDINAL"(display), 3330 32 /* bits */, 3331 0 /*PropModeReplace*/, 3332 buffer.ptr, 3333 cast(int) buffer.length); 3334 } else version(OSXCocoa) { 3335 throw new NotYetImplementedException(); 3336 } else static assert(0); 3337 } 3338 3339 version(Windows) 3340 private WindowsIcon winIcon; 3341 3342 bool _suppressDestruction; 3343 3344 ~this() { 3345 if(!thisIsGuiThread) return; // FIXME: leaks if multithreaded gc 3346 if(_suppressDestruction) 3347 return; 3348 impl.dispose(); 3349 } 3350 3351 private bool _closed; 3352 3353 // the idea here is to draw something temporary on top of the main picture e.g. a blinking cursor 3354 /* 3355 ScreenPainter drawTransiently() { 3356 return impl.getPainter(); 3357 } 3358 */ 3359 3360 /// Draws an image on the window. This is meant to provide quick look 3361 /// of a static image generated elsewhere. 3362 @property void image(Image i) { 3363 /+ 3364 version(Windows) { 3365 BITMAP bm; 3366 HDC hdc = GetDC(hwnd); 3367 HDC hdcMem = CreateCompatibleDC(hdc); 3368 HBITMAP hbmOld = SelectObject(hdcMem, i.handle); 3369 3370 GetObject(i.handle, bm.sizeof, &bm); 3371 3372 BitBlt(hdc, 0, 0, bm.bmWidth, bm.bmHeight, hdcMem, 0, 0, SRCCOPY); 3373 3374 SelectObject(hdcMem, hbmOld); 3375 DeleteDC(hdcMem); 3376 ReleaseDC(hwnd, hdc); 3377 3378 /* 3379 RECT r; 3380 r.right = i.width; 3381 r.bottom = i.height; 3382 InvalidateRect(hwnd, &r, false); 3383 */ 3384 } else 3385 version(X11) { 3386 if(!destroyed) { 3387 if(i.usingXshm) 3388 XShmPutImage(display, cast(Drawable) window, gc, i.handle, 0, 0, 0, 0, i.width, i.height, false); 3389 else 3390 XPutImage(display, cast(Drawable) window, gc, i.handle, 0, 0, 0, 0, i.width, i.height); 3391 } 3392 } else 3393 version(OSXCocoa) { 3394 draw().drawImage(Point(0, 0), i); 3395 setNeedsDisplay(view, true); 3396 } else static assert(0); 3397 +/ 3398 auto painter = this.draw; 3399 painter.drawImage(Point(0, 0), i); 3400 } 3401 3402 /++ 3403 Changes the cursor for the window. If the cursor is hidden via [hideCursor], this has no effect. 3404 3405 --- 3406 window.cursor = GenericCursor.Help; 3407 // now the window mouse cursor is set to a generic help 3408 --- 3409 3410 +/ 3411 @property void cursor(MouseCursor cursor) { 3412 version(OSXCocoa) 3413 {} // featureNotImplemented(); 3414 else 3415 if(this.impl.curHidden <= 0) { 3416 static if(UsingSimpledisplayX11) { 3417 auto ch = cursor.cursorHandle; 3418 XDefineCursor(XDisplayConnection.get(), this.impl.window, ch); 3419 } else version(Windows) { 3420 auto ch = cursor.cursorHandle; 3421 impl.currentCursor = ch; 3422 SetCursor(ch); // redraw without waiting for mouse movement to update 3423 } else featureNotImplemented(); 3424 } 3425 3426 } 3427 3428 /// What follows are the event handlers. These are set automatically 3429 /// by the eventLoop function, but are still public so you can change 3430 /// them later. wasPressed == true means key down. false == key up. 3431 3432 /// Handles a low-level keyboard event. Settable through setEventHandlers. 3433 void delegate(KeyEvent ke) handleKeyEvent; 3434 3435 /// Handles a higher level keyboard event - c is the character just pressed. Settable through setEventHandlers. 3436 void delegate(dchar c) handleCharEvent; 3437 3438 /// Handles a timer pulse. Settable through setEventHandlers. 3439 void delegate() handlePulse; 3440 3441 /// Called when the focus changes, param is if we have it (true) or are losing it (false). 3442 void delegate(bool) onFocusChange; 3443 3444 /** Called inside `close()` method. Our window is still alive, and we can free various resources. 3445 * Sometimes it is easier to setup the delegate instead of subclassing. */ 3446 void delegate() onClosing; 3447 3448 /** Called when we received destroy notification. At this stage we cannot do much with our window 3449 * (as it is already dead, and it's native handle cannot be used), but we still can do some 3450 * last minute cleanup. */ 3451 void delegate() onDestroyed; 3452 3453 static if (UsingSimpledisplayX11) 3454 /** Called when Expose event comes. See Xlib manual to understand the arguments. 3455 * Return `false` if you want Simpledisplay to copy backbuffer, or `true` if you did it yourself. 3456 * You will probably never need to setup this handler, it is for very low-level stuff. 3457 * 3458 * WARNING! Xlib is multithread-locked when this handles is called! */ 3459 bool delegate(int x, int y, int width, int height, int eventsLeft) handleExpose; 3460 3461 //version(Windows) 3462 //bool delegate(WPARAM wParam, LPARAM lParam) handleWM_PAINT; 3463 3464 private { 3465 int lastMouseX = int.min; 3466 int lastMouseY = int.min; 3467 void mdx(ref MouseEvent ev) { 3468 if(lastMouseX == int.min || lastMouseY == int.min) { 3469 ev.dx = 0; 3470 ev.dy = 0; 3471 } else { 3472 ev.dx = ev.x - lastMouseX; 3473 ev.dy = ev.y - lastMouseY; 3474 } 3475 3476 lastMouseX = ev.x; 3477 lastMouseY = ev.y; 3478 } 3479 } 3480 3481 /// Mouse event handler. Settable through setEventHandlers. 3482 void delegate(MouseEvent) handleMouseEvent; 3483 3484 /// use to redraw child widgets if you use system apis to add stuff 3485 void delegate() paintingFinished; 3486 3487 void delegate() paintingFinishedDg() { 3488 return paintingFinished; 3489 } 3490 3491 /// handle a resize, after it happens. You must construct the window with Resizability.allowResizing 3492 /// for this to ever happen. 3493 void delegate(int width, int height) windowResized; 3494 3495 /++ 3496 Platform specific - handle any native message this window gets. 3497 3498 Note: this is called *in addition to* other event handlers, unless you either: 3499 3500 1) On X11, return 0 indicating that you handled it. Any other return value is simply discarded. 3501 3502 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. 3503 3504 On Windows, your delegate takes the form of `int delegate(HWND hwnd, UINT message, WPARAM wParam, LPARAM lParam, out int mustReturn)`. 3505 3506 On X, it takes the form of `int delegate(XEvent)`. 3507 3508 History: 3509 In ancient versions, this was `static`. If you want a global hook, use [handleNativeGlobalEvent] instead. 3510 3511 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. 3512 +/ 3513 NativeEventHandler handleNativeEvent_; 3514 3515 @property NativeEventHandler handleNativeEvent() nothrow pure @nogc const @safe { 3516 return handleNativeEvent_; 3517 } 3518 @property void handleNativeEvent(NativeEventHandler neh) nothrow pure @nogc @safe { 3519 handleNativeEvent_ = neh; 3520 } 3521 3522 version(Windows) 3523 // compatibility shim with the old deprecated way 3524 // in this one, if you return 0, it means you must return. otherwise the ret value is ignored. 3525 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) { 3526 handleNativeEvent_ = delegate int(HWND h, UINT m, WPARAM w, LPARAM l, out int r) { 3527 auto ret = dg(h, m, w, l); 3528 if(ret == 0) 3529 r = 1; 3530 return ret; 3531 }; 3532 } 3533 3534 /// This is the same as handleNativeEvent, but static so it can hook ALL events in the loop. 3535 /// If you used to use handleNativeEvent depending on it being static, just change it to use 3536 /// this instead and it will work the same way. 3537 __gshared NativeEventHandler handleNativeGlobalEvent; 3538 3539 // private: 3540 /// The native implementation is available, but you shouldn't use it unless you are 3541 /// familiar with the underlying operating system, don't mind depending on it, and 3542 /// know simpledisplay.d's internals too. It is virtually private; you can hopefully 3543 /// do what you need to do with handleNativeEvent instead. 3544 /// 3545 /// This is likely to eventually change to be just a struct holding platform-specific 3546 /// handles instead of a template mixin at some point because I'm not happy with the 3547 /// code duplication here (ironically). 3548 mixin NativeSimpleWindowImplementation!() impl; 3549 3550 /** 3551 This is in-process one-way (from anything to window) event sending mechanics. 3552 It is thread-safe, so it can be used in multi-threaded applications to send, 3553 for example, "wake up and repaint" events when thread completed some operation. 3554 This will allow to avoid using timer pulse to check events with synchronization, 3555 'cause event handler will be called in UI thread. You can stop guessing which 3556 pulse frequency will be enough for your app. 3557 Note that events handlers may be called in arbitrary order, i.e. last registered 3558 handler can be called first, and vice versa. 3559 */ 3560 public: 3561 /** Is our custom event queue empty? Can be used in simple cases to prevent 3562 * "spamming" window with events it can't cope with. 3563 * It is safe to call this from non-UI threads. 3564 */ 3565 @property bool eventQueueEmpty() () { 3566 synchronized(this) { 3567 foreach (const ref o; eventQueue[0..eventQueueUsed]) if (!o.doProcess) return false; 3568 } 3569 return true; 3570 } 3571 3572 /** Does our custom event queue contains at least one with the given type? 3573 * Can be used in simple cases to prevent "spamming" window with events 3574 * it can't cope with. 3575 * It is safe to call this from non-UI threads. 3576 */ 3577 @property bool eventQueued(ET:Object) () { 3578 synchronized(this) { 3579 foreach (const ref o; eventQueue[0..eventQueueUsed]) { 3580 if (!o.doProcess) { 3581 if (cast(ET)(o.evt)) return true; 3582 } 3583 } 3584 } 3585 return false; 3586 } 3587 3588 /++ 3589 Event listeners added with [addEventListener] have their exceptions swallowed by the event loop. This delegate can handle them again before it proceeds. 3590 3591 History: 3592 Added May 12, 2021 3593 +/ 3594 void delegate(Exception e) nothrow eventUncaughtException; 3595 3596 /** Add listener for custom event. Can be used like this: 3597 * 3598 * --------------------- 3599 * auto eid = win.addEventListener((MyStruct evt) { ... }); 3600 * ... 3601 * win.removeEventListener(eid); 3602 * --------------------- 3603 * 3604 * Returns: 0 on failure (should never happen, so ignore it) 3605 * 3606 * $(WARNING Don't use this method in object destructors!) 3607 * 3608 * $(WARNING It is better to register all event handlers and don't remove 'em, 3609 * 'cause if event handler id counter will overflow, you won't be able 3610 * to register any more events.) 3611 */ 3612 uint addEventListener(ET:Object) (void delegate (ET) dg) { 3613 if (dg is null) return 0; // ignore empty handlers 3614 synchronized(this) { 3615 //FIXME: abort on overflow? 3616 if (++lastUsedHandlerId == 0) { --lastUsedHandlerId; return 0; } // alas, can't register more events. at all. 3617 EventHandlerEntry e; 3618 e.dg = delegate (Object o) { 3619 if (auto co = cast(ET)o) { 3620 try { 3621 dg(co); 3622 } catch (Exception e) { 3623 // sorry! 3624 if(eventUncaughtException) 3625 eventUncaughtException(e); 3626 } 3627 return true; 3628 } 3629 return false; 3630 }; 3631 e.id = lastUsedHandlerId; 3632 auto optr = eventHandlers.ptr; 3633 eventHandlers ~= e; 3634 if (eventHandlers.ptr !is optr) { 3635 import core.memory : GC; 3636 if (eventHandlers.ptr is GC.addrOf(eventHandlers.ptr)) GC.setAttr(eventHandlers.ptr, GC.BlkAttr.NO_INTERIOR); 3637 } 3638 return lastUsedHandlerId; 3639 } 3640 } 3641 3642 /// Remove event listener. It is safe to pass invalid event id here. 3643 /// $(WARNING Don't use this method in object destructors!) 3644 void removeEventListener() (uint id) { 3645 if (id == 0 || id > lastUsedHandlerId) return; 3646 synchronized(this) { 3647 foreach (immutable idx; 0..eventHandlers.length) { 3648 if (eventHandlers[idx].id == id) { 3649 foreach (immutable c; idx+1..eventHandlers.length) eventHandlers[c-1] = eventHandlers[c]; 3650 eventHandlers[$-1].dg = null; 3651 eventHandlers.length -= 1; 3652 eventHandlers.assumeSafeAppend; 3653 return; 3654 } 3655 } 3656 } 3657 } 3658 3659 /// Post event to queue. It is safe to call this from non-UI threads. 3660 /// If `timeoutmsecs` is greater than zero, the event will be delayed for at least `timeoutmsecs` milliseconds. 3661 /// if `replace` is `true`, replace all existing events typed `ET` with the new one (if `evt` is empty, remove 'em all) 3662 /// Returns `true` if event was queued. Always returns `false` if `evt` is null. 3663 bool postTimeout(ET:Object) (ET evt, uint timeoutmsecs, bool replace=false) { 3664 if (this.closed) return false; // closed windows can't handle events 3665 3666 // remove all events of type `ET` 3667 void removeAllET () { 3668 uint eidx = 0, ec = eventQueueUsed; 3669 auto eptr = eventQueue.ptr; 3670 while (eidx < ec) { 3671 if (eptr.doProcess) { ++eidx; ++eptr; continue; } 3672 if (cast(ET)eptr.evt !is null) { 3673 // i found her! 3674 if (inCustomEventProcessor) { 3675 // if we're in custom event processing loop, processor will clear it for us 3676 eptr.evt = null; 3677 ++eidx; 3678 ++eptr; 3679 } else { 3680 foreach (immutable c; eidx+1..ec) eventQueue.ptr[c-1] = eventQueue.ptr[c]; 3681 ec = --eventQueueUsed; 3682 // clear last event (it is already copied) 3683 eventQueue.ptr[ec].evt = null; 3684 } 3685 } else { 3686 ++eidx; 3687 ++eptr; 3688 } 3689 } 3690 } 3691 3692 if (evt is null) { 3693 if (replace) { synchronized(this) removeAllET(); } 3694 // ignore empty events, they can't be handled anyway 3695 return false; 3696 } 3697 3698 // add events even if no event FD/event object created yet 3699 synchronized(this) { 3700 if (replace) removeAllET(); 3701 if (eventQueueUsed == uint.max) return false; // just in case 3702 if (eventQueueUsed < eventQueue.length) { 3703 eventQueue[eventQueueUsed++] = QueuedEvent(evt, timeoutmsecs); 3704 } else { 3705 if (eventQueue.capacity == eventQueue.length) { 3706 // need to reallocate; do a trick to ensure that old array is cleared 3707 auto oarr = eventQueue; 3708 eventQueue ~= QueuedEvent(evt, timeoutmsecs); 3709 // just in case, do yet another check 3710 if (oarr.length != 0 && oarr.ptr !is eventQueue.ptr) foreach (ref e; oarr[0..eventQueueUsed]) e.evt = null; 3711 import core.memory : GC; 3712 if (eventQueue.ptr is GC.addrOf(eventQueue.ptr)) GC.setAttr(eventQueue.ptr, GC.BlkAttr.NO_INTERIOR); 3713 } else { 3714 auto optr = eventQueue.ptr; 3715 eventQueue ~= QueuedEvent(evt, timeoutmsecs); 3716 assert(eventQueue.ptr is optr); 3717 } 3718 ++eventQueueUsed; 3719 assert(eventQueueUsed == eventQueue.length); 3720 } 3721 if (!eventWakeUp()) { 3722 // can't wake up event processor, so there is no reason to keep the event 3723 assert(eventQueueUsed > 0); 3724 eventQueue[--eventQueueUsed].evt = null; 3725 return false; 3726 } 3727 return true; 3728 } 3729 } 3730 3731 /// Post event to queue. It is safe to call this from non-UI threads. 3732 /// if `replace` is `true`, replace all existing events typed `ET` with the new one (if `evt` is empty, remove 'em all) 3733 /// Returns `true` if event was queued. Always returns `false` if `evt` is null. 3734 bool postEvent(ET:Object) (ET evt, bool replace=false) { 3735 return postTimeout!ET(evt, 0, replace); 3736 } 3737 3738 private: 3739 private import core.time : MonoTime; 3740 3741 version(Posix) { 3742 __gshared int customEventFDRead = -1; 3743 __gshared int customEventFDWrite = -1; 3744 __gshared int customSignalFD = -1; 3745 } else version(Windows) { 3746 __gshared HANDLE customEventH = null; 3747 } 3748 3749 // wake up event processor 3750 static bool eventWakeUp () { 3751 version(X11) { 3752 import core.sys.posix.unistd : write; 3753 ulong n = 1; 3754 if (customEventFDWrite >= 0) write(customEventFDWrite, &n, n.sizeof); 3755 return true; 3756 } else version(Windows) { 3757 if (customEventH !is null) SetEvent(customEventH); 3758 return true; 3759 } else version(OSXCocoa) { 3760 if(globalAppDelegate) 3761 globalAppDelegate.performSelectorOnMainThread(sel_registerName("sdpyCustomEventWakeup:"), null, false); 3762 return true; 3763 } else { 3764 // not implemented for other OSes 3765 return false; 3766 } 3767 } 3768 3769 static struct QueuedEvent { 3770 Object evt; 3771 bool timed = false; 3772 MonoTime hittime = MonoTime.zero; 3773 bool doProcess = false; // process event at the current iteration (internal flag) 3774 3775 this (Object aevt, uint toutmsecs) { 3776 evt = aevt; 3777 if (toutmsecs > 0) { 3778 import core.time : msecs; 3779 timed = true; 3780 hittime = MonoTime.currTime+toutmsecs.msecs; 3781 } 3782 } 3783 } 3784 3785 alias CustomEventHandler = bool delegate (Object o) nothrow; 3786 static struct EventHandlerEntry { 3787 CustomEventHandler dg; 3788 uint id; 3789 } 3790 3791 uint lastUsedHandlerId; 3792 EventHandlerEntry[] eventHandlers; 3793 QueuedEvent[] eventQueue = null; 3794 uint eventQueueUsed = 0; // to avoid `.assumeSafeAppend` and length changes 3795 bool inCustomEventProcessor = false; // required to properly remove events 3796 3797 // process queued events and call custom event handlers 3798 // this will not process events posted from called handlers (such events are postponed for the next iteration) 3799 void processCustomEvents () @system { 3800 bool hasSomethingToDo = false; 3801 uint ecount; 3802 bool ocep; 3803 synchronized(this) { 3804 ocep = inCustomEventProcessor; 3805 inCustomEventProcessor = true; 3806 ecount = eventQueueUsed; // user may want to post new events from an event handler; process 'em on next iteration 3807 auto ctt = MonoTime.currTime; 3808 bool hasEmpty = false; 3809 // mark events to process (this is required for `eventQueued()`) 3810 foreach (ref qe; eventQueue[0..ecount]) { 3811 if (qe.evt is null) { hasEmpty = true; continue; } 3812 if (qe.timed) { 3813 qe.doProcess = (qe.hittime <= ctt); 3814 } else { 3815 qe.doProcess = true; 3816 } 3817 hasSomethingToDo = (hasSomethingToDo || qe.doProcess); 3818 } 3819 if (!hasSomethingToDo) { 3820 // remove empty events 3821 if (hasEmpty) { 3822 uint eidx = 0, ec = eventQueueUsed; 3823 auto eptr = eventQueue.ptr; 3824 while (eidx < ec) { 3825 if (eptr.evt is null) { 3826 foreach (immutable c; eidx+1..ec) eventQueue.ptr[c-1] = eventQueue.ptr[c]; 3827 ec = --eventQueueUsed; 3828 eventQueue.ptr[ec].evt = null; // make GC life easier 3829 } else { 3830 ++eidx; 3831 ++eptr; 3832 } 3833 } 3834 } 3835 inCustomEventProcessor = ocep; 3836 return; 3837 } 3838 } 3839 // process marked events 3840 uint efree = 0; // non-processed events will be put at this index 3841 EventHandlerEntry[] eh; 3842 Object evt; 3843 foreach (immutable eidx; 0..ecount) { 3844 synchronized(this) { 3845 if (!eventQueue[eidx].doProcess) { 3846 // skip this event 3847 assert(efree <= eidx); 3848 if (efree != eidx) { 3849 // copy this event to queue start 3850 eventQueue[efree] = eventQueue[eidx]; 3851 eventQueue[eidx].evt = null; // just in case 3852 } 3853 ++efree; 3854 continue; 3855 } 3856 evt = eventQueue[eidx].evt; 3857 eventQueue[eidx].evt = null; // in case event handler will hit GC 3858 if (evt is null) continue; // just in case 3859 // try all handlers; this can be slow, but meh... 3860 eh = eventHandlers; 3861 } 3862 foreach (ref evhan; eh) if (evhan.dg !is null) evhan.dg(evt); 3863 evt = null; 3864 eh = null; 3865 } 3866 synchronized(this) { 3867 // move all unprocessed events to queue top; efree holds first "free index" 3868 foreach (immutable eidx; ecount..eventQueueUsed) { 3869 assert(efree <= eidx); 3870 if (efree != eidx) eventQueue[efree] = eventQueue[eidx]; 3871 ++efree; 3872 } 3873 eventQueueUsed = efree; 3874 // wake up event processor on next event loop iteration if we have more queued events 3875 // also, remove empty events 3876 bool awaken = false; 3877 uint eidx = 0, ec = eventQueueUsed; 3878 auto eptr = eventQueue.ptr; 3879 while (eidx < ec) { 3880 if (eptr.evt is null) { 3881 foreach (immutable c; eidx+1..ec) eventQueue.ptr[c-1] = eventQueue.ptr[c]; 3882 ec = --eventQueueUsed; 3883 eventQueue.ptr[ec].evt = null; // make GC life easier 3884 } else { 3885 if (!awaken && !eptr.timed) { eventWakeUp(); awaken = true; } 3886 ++eidx; 3887 ++eptr; 3888 } 3889 } 3890 inCustomEventProcessor = ocep; 3891 } 3892 } 3893 3894 // for all windows in nativeMapping 3895 package static void processAllCustomEvents () @system { 3896 3897 cleanupQueue.process(); 3898 3899 justCommunication.processCustomEvents(); 3900 3901 foreach (SimpleWindow sw; SimpleWindow.nativeMapping.byValue) { 3902 if (sw is null || sw.closed) continue; 3903 sw.processCustomEvents(); 3904 } 3905 3906 runPendingRunInGuiThreadDelegates(); 3907 } 3908 3909 // 0: infinite (i.e. no scheduled events in queue) 3910 uint eventQueueTimeoutMSecs () { 3911 synchronized(this) { 3912 if (eventQueueUsed == 0) return 0; 3913 if (inCustomEventProcessor) assert(0, "WUTAFUUUUUUU..."); // the thing that should not be. ABSOLUTELY! (c) 3914 uint res = int.max; 3915 auto ctt = MonoTime.currTime; 3916 foreach (const ref qe; eventQueue[0..eventQueueUsed]) { 3917 if (qe.evt is null) assert(0, "WUTAFUUUUUUU..."); // the thing that should not be. ABSOLUTELY! (c) 3918 if (qe.doProcess) continue; // just in case 3919 if (!qe.timed) return 1; // minimal 3920 if (qe.hittime <= ctt) return 1; // minimal 3921 auto tms = (qe.hittime-ctt).total!"msecs"; 3922 if (tms < 1) tms = 1; // safety net 3923 if (tms >= int.max) tms = int.max-1; // and another safety net 3924 if (res > tms) res = cast(uint)tms; 3925 } 3926 return (res >= int.max ? 0 : res); 3927 } 3928 } 3929 3930 // for all windows in nativeMapping 3931 static uint eventAllQueueTimeoutMSecs () { 3932 uint res = uint.max; 3933 foreach (SimpleWindow sw; SimpleWindow.nativeMapping.byValue) { 3934 if (sw is null || sw.closed) continue; 3935 uint to = sw.eventQueueTimeoutMSecs(); 3936 if (to && to < res) { 3937 res = to; 3938 if (to == 1) break; // can't have less than this 3939 } 3940 } 3941 return (res >= int.max ? 0 : res); 3942 } 3943 3944 version(X11) { 3945 ResizeEvent pendingResizeEvent; 3946 } 3947 3948 /++ 3949 When in opengl mode and automatically resizing, it will set the opengl viewport to stretch. 3950 3951 If you work with multiple opengl contexts and/or threads, this might be more trouble than it is 3952 worth so you can disable it by setting this to `true`. 3953 3954 History: 3955 Added November 13, 2022. 3956 +/ 3957 public bool suppressAutoOpenglViewport = false; 3958 private void updateOpenglViewportIfNeeded(int width, int height) { 3959 if(suppressAutoOpenglViewport) return; 3960 3961 version(without_opengl) {} else 3962 if(openglMode == OpenGlOptions.yes && resizability == Resizability.automaticallyScaleIfPossible) { 3963 // writeln(width, " ", height); 3964 setAsCurrentOpenGlContextNT(); 3965 glViewport(0, 0, width, height); 3966 } 3967 } 3968 3969 // TODO: Implement on non-Windows platforms (where available). 3970 private CornerStyle _fauxCornerStyle = CornerStyle.automatic; 3971 3972 /++ 3973 Style of the window's corners 3974 3975 $(WARNING 3976 Currently only implemented on Windows targets. 3977 Has no visual effect elsewhere. 3978 3979 Windows: Requires Windows 11 or later. 3980 ) 3981 3982 History: 3983 Added September 09, 2024. 3984 +/ 3985 public CornerStyle cornerStyle() @trusted { 3986 version(Windows) { 3987 DWM_WINDOW_CORNER_PREFERENCE dwmCorner; 3988 const apiResult = DwmGetWindowAttribute( 3989 this.hwnd, 3990 DWMWINDOWATTRIBUTE.DWMWA_WINDOW_CORNER_PREFERENCE, 3991 &dwmCorner, 3992 typeof(dwmCorner).sizeof 3993 ); 3994 3995 if (apiResult != S_OK) { 3996 // Unsupported? 3997 if (apiResult == E_INVALIDARG) { 3998 // Feature unsupported; Windows version probably too old. 3999 // Requires Windows 11 (build 22000) or later. 4000 return _fauxCornerStyle; 4001 } 4002 4003 throw new WindowsApiException("DwmGetWindowAttribute", apiResult); 4004 } 4005 4006 CornerStyle corner; 4007 if (!dwmCorner.fromDWM(corner)) { 4008 throw ArsdException!"DwmGetWindowAttribute unfamiliar corner preference"(dwmCorner); 4009 } 4010 return corner; 4011 } else { 4012 return _fauxCornerStyle; 4013 } 4014 } 4015 4016 /// ditto 4017 public void cornerStyle(const CornerStyle corner) @trusted { 4018 version(Windows) { 4019 DWM_WINDOW_CORNER_PREFERENCE dwmCorner; 4020 if (!corner.toDWM(dwmCorner)) { 4021 assert(false, "This should have been impossible because of a final switch."); 4022 } 4023 4024 const apiResult = DwmSetWindowAttribute( 4025 this.hwnd, 4026 DWMWINDOWATTRIBUTE.DWMWA_WINDOW_CORNER_PREFERENCE, 4027 &dwmCorner, 4028 typeof(dwmCorner).sizeof 4029 ); 4030 4031 if (apiResult != S_OK) { 4032 // Unsupported? 4033 if (apiResult == E_INVALIDARG) { 4034 // Feature unsupported; Windows version probably too old. 4035 // Requires Windows 11 (build 22000) or later. 4036 _fauxCornerStyle = corner; 4037 return; 4038 } 4039 4040 throw new WindowsApiException("DwmSetWindowAttribute", apiResult); 4041 } 4042 } else { 4043 _fauxCornerStyle = corner; 4044 } 4045 } 4046 } 4047 4048 version(OSXCocoa) 4049 enum NSWindow NullWindow = null; 4050 else 4051 enum NullWindow = NativeWindowHandle.init; 4052 4053 /++ 4054 Magic pseudo-window for just posting events to a global queue. 4055 4056 Not entirely supported, I might delete it at any time. 4057 4058 Added Nov 5, 2021. 4059 +/ 4060 __gshared SimpleWindow justCommunication = new SimpleWindow(NullWindow); 4061 4062 /* Drag and drop support { */ 4063 version(X11) { 4064 4065 } else version(Windows) { 4066 import core.sys.windows.uuid; 4067 import core.sys.windows.ole2; 4068 import core.sys.windows.oleidl; 4069 import core.sys.windows.objidl; 4070 import core.sys.windows.wtypes; 4071 4072 pragma(lib, "ole32"); 4073 void initDnd() { 4074 auto err = OleInitialize(null); 4075 if(err != S_OK && err != S_FALSE) 4076 throw new Exception("init");//err); 4077 } 4078 } 4079 /* } End drag and drop support */ 4080 4081 4082 /// Represents a mouse cursor (aka the mouse pointer, the image seen on screen that indicates where the mouse is pointing). 4083 /// See [GenericCursor]. 4084 class MouseCursor { 4085 int osId; 4086 bool isStockCursor; 4087 private this(int osId) { 4088 this.osId = osId; 4089 this.isStockCursor = true; 4090 } 4091 4092 // https://msdn.microsoft.com/en-us/library/windows/desktop/ms648385(v=vs.85).aspx 4093 this(int xHotSpot, int yHotSpot, ubyte[] andMask, ubyte[] xorMask) {} 4094 4095 version(Windows) { 4096 HCURSOR cursor_; 4097 HCURSOR cursorHandle() { 4098 if(cursor_ is null) 4099 cursor_ = LoadCursor(null, MAKEINTRESOURCE(osId)); 4100 return cursor_; 4101 } 4102 4103 } else static if(UsingSimpledisplayX11) { 4104 Cursor cursor_ = None; 4105 int xDisplaySequence; 4106 4107 Cursor cursorHandle() { 4108 if(this.osId == None) 4109 return None; 4110 4111 // we need to reload if we on a new X connection 4112 if(cursor_ == None || XDisplayConnection.connectionSequenceNumber != xDisplaySequence) { 4113 cursor_ = XCreateFontCursor(XDisplayConnection.get(), this.osId); 4114 xDisplaySequence = XDisplayConnection.connectionSequenceNumber; 4115 } 4116 return cursor_; 4117 } 4118 } 4119 } 4120 4121 // https://developer.mozilla.org/en-US/docs/Web/CSS/cursor 4122 // https://tronche.com/gui/x/xlib/appendix/b/ 4123 // https://msdn.microsoft.com/en-us/library/windows/desktop/ms648391(v=vs.85).aspx 4124 /// 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. 4125 enum GenericCursorType { 4126 Default, /// The default arrow pointer. 4127 Wait, /// A cursor indicating something is loading and the user must wait. 4128 Hand, /// A pointing finger, like the one used hovering over hyperlinks in a web browser. 4129 Help, /// A cursor indicating the user can get help about the pointer location. 4130 Cross, /// A crosshair. 4131 Text, /// An i-beam shape, typically used to indicate text selection is possible. 4132 Move, /// Pointer indicating movement is possible. May also be used as SizeAll. 4133 UpArrow, /// An arrow pointing straight up. 4134 Progress, /// The hourglass and arrow, indicating the computer is working but the user can still work. Not great results on X11. 4135 NotAllowed, /// Indicates the current operation is not allowed. Not great results on X11. 4136 SizeNesw, /// Arrow pointing northeast and southwest (lower-left corner resize indicator). 4137 SizeNs, /// Arrow pointing north and south (upper/lower edge resize indicator). 4138 SizeNwse, /// Arrow pointing northwest and southeast (upper-left corner resize indicator). 4139 SizeWe, /// Arrow pointing west and east (left/right edge resize indicator). 4140 4141 } 4142 4143 /* 4144 X_plus == css cell == Windows ? 4145 */ 4146 4147 /// You get one by `GenericCursor.SomeTime`. See [GenericCursorType] for a list of types. 4148 static struct GenericCursor { 4149 static: 4150 /// 4151 MouseCursor opDispatch(string str)() if(__traits(hasMember, GenericCursorType, str)) { 4152 static MouseCursor mc; 4153 4154 auto type = __traits(getMember, GenericCursorType, str); 4155 4156 if(mc is null) { 4157 4158 version(Windows) { 4159 int osId; 4160 final switch(type) { 4161 case GenericCursorType.Default: osId = IDC_ARROW; break; 4162 case GenericCursorType.Wait: osId = IDC_WAIT; break; 4163 case GenericCursorType.Hand: osId = IDC_HAND; break; 4164 case GenericCursorType.Help: osId = IDC_HELP; break; 4165 case GenericCursorType.Cross: osId = IDC_CROSS; break; 4166 case GenericCursorType.Text: osId = IDC_IBEAM; break; 4167 case GenericCursorType.Move: osId = IDC_SIZEALL; break; 4168 case GenericCursorType.UpArrow: osId = IDC_UPARROW; break; 4169 case GenericCursorType.Progress: osId = IDC_APPSTARTING; break; 4170 case GenericCursorType.NotAllowed: osId = IDC_NO; break; 4171 case GenericCursorType.SizeNesw: osId = IDC_SIZENESW; break; 4172 case GenericCursorType.SizeNs: osId = IDC_SIZENS; break; 4173 case GenericCursorType.SizeNwse: osId = IDC_SIZENWSE; break; 4174 case GenericCursorType.SizeWe: osId = IDC_SIZEWE; break; 4175 } 4176 } else static if(UsingSimpledisplayX11) { 4177 int osId; 4178 final switch(type) { 4179 case GenericCursorType.Default: osId = None; break; 4180 case GenericCursorType.Wait: osId = 150 /* XC_watch */; break; 4181 case GenericCursorType.Hand: osId = 60 /* XC_hand2 */; break; 4182 case GenericCursorType.Help: osId = 92 /* XC_question_arrow */; break; 4183 case GenericCursorType.Cross: osId = 34 /* XC_crosshair */; break; 4184 case GenericCursorType.Text: osId = 152 /* XC_xterm */; break; 4185 case GenericCursorType.Move: osId = 52 /* XC_fleur */; break; 4186 case GenericCursorType.UpArrow: osId = 22 /* XC_center_ptr */; break; 4187 case GenericCursorType.Progress: osId = 150 /* XC_watch, best i can do i think */; break; 4188 4189 case GenericCursorType.NotAllowed: osId = 24 /* XC_circle. not great */; break; 4190 case GenericCursorType.SizeNesw: osId = 12 /* XC_bottom_left_corner */ ; break; 4191 case GenericCursorType.SizeNs: osId = 116 /* XC_sb_v_double_arrow */; break; 4192 case GenericCursorType.SizeNwse: osId = 14 /* XC_bottom_right_corner */; break; 4193 case GenericCursorType.SizeWe: osId = 108 /* XC_sb_h_double_arrow */; break; 4194 } 4195 4196 } else { 4197 int osId; 4198 // featureNotImplemented(); 4199 } 4200 4201 mc = new MouseCursor(osId); 4202 } 4203 return mc; 4204 } 4205 } 4206 4207 4208 /++ 4209 If you want to get more control over the event loop, you can use this. 4210 4211 Typically though, you can just call [SimpleWindow.eventLoop] which forwards 4212 to `EventLoop.get.run`. 4213 +/ 4214 struct EventLoop { 4215 @disable this(); 4216 4217 /// Gets a reference to an existing event loop 4218 static EventLoop get() { 4219 return EventLoop(0, null); 4220 } 4221 4222 static void quitApplication() { 4223 version(use_arsd_core) { 4224 import arsd.core; 4225 ICoreEventLoop.exitApplication(); 4226 } 4227 EventLoop.get().exit(); 4228 } 4229 4230 private __gshared static Object monitor = new Object(); // deliberate CTFE usage here fyi 4231 4232 /// Construct an application-global event loop for yourself 4233 /// See_Also: [SimpleWindow.setEventHandlers] 4234 this(long pulseTimeout, void delegate() handlePulse) { 4235 synchronized(monitor) { 4236 if(impl is null) { 4237 claimGuiThread(); 4238 version(sdpy_thread_checks) assert(thisIsGuiThread); 4239 impl = new EventLoopImpl(pulseTimeout, handlePulse); 4240 } else { 4241 if(pulseTimeout) { 4242 impl.pulseTimeout = pulseTimeout; 4243 impl.handlePulse = handlePulse; 4244 } 4245 } 4246 impl.refcount++; 4247 } 4248 } 4249 4250 ~this() { 4251 if(impl is null) 4252 return; 4253 impl.refcount--; 4254 if(impl.refcount == 0) { 4255 impl.dispose(); 4256 if(thisIsGuiThread) 4257 guiThreadFinalize(); 4258 } 4259 4260 } 4261 4262 this(this) { 4263 if(impl is null) 4264 return; 4265 impl.refcount++; 4266 } 4267 4268 /// Runs the event loop until the whileCondition, if present, returns false 4269 int run(bool delegate() whileCondition = null) { 4270 assert(impl !is null); 4271 impl.notExited = true; 4272 return impl.run(whileCondition); 4273 } 4274 4275 /// Exits the event loop, but allows you to reenter it again later (in contrast with quitApplication, which tries to terminate the program) 4276 void exit() { 4277 assert(impl !is null); 4278 impl.notExited = false; 4279 4280 version(use_arsd_core) { 4281 import arsd.core; 4282 ICoreEventLoop.exitApplication(); 4283 } 4284 } 4285 4286 version(linux) 4287 ref void delegate(int) signalHandler() { 4288 assert(impl !is null); 4289 return impl.signalHandler; 4290 } 4291 4292 __gshared static EventLoopImpl* impl; 4293 } 4294 4295 version(linux) 4296 void delegate(int, int) globalHupHandler; 4297 4298 version(Posix) 4299 void makeNonBlocking(int fd) { 4300 import fcntl = core.sys.posix.fcntl; 4301 auto flags = fcntl.fcntl(fd, fcntl.F_GETFL, 0); 4302 if(flags == -1) 4303 throw new Exception("fcntl get"); 4304 flags |= fcntl.O_NONBLOCK; 4305 auto s = fcntl.fcntl(fd, fcntl.F_SETFL, flags); 4306 if(s == -1) 4307 throw new Exception("fcntl set"); 4308 } 4309 4310 struct EventLoopImpl { 4311 int refcount; 4312 4313 bool notExited = true; 4314 4315 version(linux) { 4316 static import ep = core.sys.linux.epoll; 4317 static import unix = core.sys.posix.unistd; 4318 static import err = core.stdc.errno; 4319 import core.sys.linux.timerfd; 4320 4321 void delegate(int) signalHandler; 4322 } 4323 4324 version(X11) { 4325 int pulseFd = -1; 4326 version(linux) ep.epoll_event[16] events = void; 4327 } else version(Windows) { 4328 Timer pulser; 4329 HANDLE[] handles; 4330 } 4331 4332 4333 /// "Lock" this window handle, to do multithreaded synchronization. You probably won't need 4334 /// to call this, as it's not recommended to share window between threads. 4335 void mtLock () { 4336 version(X11) { 4337 XLockDisplay(this.display); 4338 } 4339 } 4340 4341 version(X11) 4342 auto display() { return XDisplayConnection.get; } 4343 4344 /// "Unlock" this window handle, to do multithreaded synchronization. You probably won't need 4345 /// to call this, as it's not recommended to share window between threads. 4346 void mtUnlock () { 4347 version(X11) { 4348 XUnlockDisplay(this.display); 4349 } 4350 } 4351 4352 version(with_eventloop) 4353 void initialize(long pulseTimeout) {} 4354 else 4355 void initialize(long pulseTimeout) @system { 4356 version(Windows) { 4357 if(pulseTimeout && handlePulse !is null) 4358 pulser = new Timer(cast(int) pulseTimeout, handlePulse); 4359 4360 if (customEventH is null) { 4361 customEventH = CreateEvent(null, FALSE/*autoreset*/, FALSE/*initial state*/, null); 4362 if (customEventH !is null) { 4363 handles ~= customEventH; 4364 } else { 4365 // this is something that should not be; better be safe than sorry 4366 throw new Exception("can't create eventfd for custom event processing"); 4367 } 4368 } 4369 4370 SimpleWindow.processAllCustomEvents(); // process events added before event object creation 4371 } 4372 4373 version(linux) { 4374 prepareEventLoop(); 4375 { 4376 auto display = XDisplayConnection.get; 4377 // adding Xlib file 4378 ep.epoll_event ev = void; 4379 { import core.stdc.string : memset; memset(&ev, 0, ev.sizeof); } // this makes valgrind happy 4380 ev.events = ep.EPOLLIN; 4381 ev.data.fd = display.fd; 4382 if(ep.epoll_ctl(epollFd, ep.EPOLL_CTL_ADD, display.fd, &ev) == -1) 4383 throw new Exception("add x fd");// ~ to!string(epollFd)); 4384 displayFd = display.fd; 4385 } 4386 4387 if(pulseTimeout && handlePulse !is null) { 4388 pulseFd = timerfd_create(CLOCK_MONOTONIC, 0); 4389 if(pulseFd == -1) 4390 throw new Exception("pulse timer create failed"); 4391 4392 itimerspec value; 4393 value.it_value.tv_sec = cast(int) (pulseTimeout / 1000); 4394 value.it_value.tv_nsec = (pulseTimeout % 1000) * 1000_000; 4395 4396 value.it_interval.tv_sec = cast(int) (pulseTimeout / 1000); 4397 value.it_interval.tv_nsec = (pulseTimeout % 1000) * 1000_000; 4398 4399 if(timerfd_settime(pulseFd, 0, &value, null) == -1) 4400 throw new Exception("couldn't make pulse timer"); 4401 4402 ep.epoll_event ev = void; 4403 { import core.stdc.string : memset; memset(&ev, 0, ev.sizeof); } // this makes valgrind happy 4404 ev.events = ep.EPOLLIN; 4405 ev.data.fd = pulseFd; 4406 ep.epoll_ctl(epollFd, ep.EPOLL_CTL_ADD, pulseFd, &ev); 4407 } 4408 4409 // eventfd for custom events 4410 if (customEventFDWrite == -1) { 4411 customEventFDWrite = eventfd(0, 0); 4412 customEventFDRead = customEventFDWrite; 4413 if (customEventFDRead >= 0) { 4414 ep.epoll_event ev = void; 4415 { import core.stdc.string : memset; memset(&ev, 0, ev.sizeof); } // this makes valgrind happy 4416 ev.events = ep.EPOLLIN; 4417 ev.data.fd = customEventFDRead; 4418 ep.epoll_ctl(epollFd, ep.EPOLL_CTL_ADD, customEventFDRead, &ev); 4419 } else { 4420 // this is something that should not be; better be safe than sorry 4421 throw new Exception("can't create eventfd for custom event processing"); 4422 } 4423 } 4424 4425 if (customSignalFD == -1) { 4426 import core.sys.linux.sys.signalfd; 4427 4428 sigset_t sigset; 4429 auto err = sigemptyset(&sigset); 4430 assert(!err); 4431 err = sigaddset(&sigset, SIGINT); 4432 assert(!err); 4433 err = sigaddset(&sigset, SIGHUP); 4434 assert(!err); 4435 err = sigprocmask(SIG_BLOCK, &sigset, null); 4436 assert(!err); 4437 4438 customSignalFD = signalfd(-1, &sigset, SFD_NONBLOCK); 4439 assert(customSignalFD != -1); 4440 4441 ep.epoll_event ev = void; 4442 { import core.stdc.string : memset; memset(&ev, 0, ev.sizeof); } // this makes valgrind happy 4443 ev.events = ep.EPOLLIN; 4444 ev.data.fd = customSignalFD; 4445 ep.epoll_ctl(epollFd, ep.EPOLL_CTL_ADD, customSignalFD, &ev); 4446 } 4447 } else version(Posix) { 4448 prepareEventLoop(); 4449 if (customEventFDRead == -1) { 4450 int[2] bfr; 4451 import core.sys.posix.unistd; 4452 auto ret = pipe(bfr); 4453 if(ret == -1) throw new Exception("pipe"); 4454 customEventFDRead = bfr[0]; 4455 customEventFDWrite = bfr[1]; 4456 } 4457 4458 } 4459 4460 SimpleWindow.processAllCustomEvents(); // process events added before event FD creation 4461 4462 version(linux) { 4463 this.mtLock(); 4464 scope(exit) this.mtUnlock(); 4465 XPending(display); // no, really 4466 } 4467 4468 disposed = false; 4469 } 4470 4471 bool disposed = true; 4472 version(X11) 4473 int displayFd = -1; 4474 4475 version(with_eventloop) 4476 void dispose() {} 4477 else 4478 void dispose() @system { 4479 disposed = true; 4480 version(X11) { 4481 if(pulseFd != -1) { 4482 import unix = core.sys.posix.unistd; 4483 unix.close(pulseFd); 4484 pulseFd = -1; 4485 } 4486 4487 version(linux) 4488 if(displayFd != -1) { 4489 // 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 4490 ep.epoll_event ev = void; 4491 { import core.stdc.string : memset; memset(&ev, 0, ev.sizeof); } // this makes valgrind happy 4492 ev.events = ep.EPOLLIN; 4493 ev.data.fd = displayFd; 4494 ep.epoll_ctl(epollFd, ep.EPOLL_CTL_DEL, displayFd, &ev); 4495 displayFd = -1; 4496 } 4497 4498 } else version(Windows) { 4499 if(pulser !is null) { 4500 pulser.destroy(); 4501 pulser = null; 4502 } 4503 if (customEventH !is null) { 4504 CloseHandle(customEventH); 4505 customEventH = null; 4506 } 4507 } 4508 } 4509 4510 this(long pulseTimeout, void delegate() handlePulse) { 4511 this.pulseTimeout = pulseTimeout; 4512 this.handlePulse = handlePulse; 4513 initialize(pulseTimeout); 4514 } 4515 4516 private long pulseTimeout; 4517 void delegate() handlePulse; 4518 4519 ~this() { 4520 dispose(); 4521 } 4522 4523 version(Posix) 4524 ref int customEventFDRead() { return SimpleWindow.customEventFDRead; } 4525 version(Posix) 4526 ref int customEventFDWrite() { return SimpleWindow.customEventFDWrite; } 4527 version(linux) 4528 ref int customSignalFD() { return SimpleWindow.customSignalFD; } 4529 version(Windows) 4530 ref auto customEventH() { return SimpleWindow.customEventH; } 4531 4532 version(X11) { 4533 bool doXPending() { 4534 bool done = false; 4535 4536 this.mtLock(); 4537 scope(exit) this.mtUnlock(); 4538 //{ import core.stdc.stdio; printf("*** queued: %d\n", XEventsQueued(this.display, QueueMode.QueuedAlready)); } 4539 while(!done && XPending(display)) { 4540 done = doXNextEvent(this.display); 4541 } 4542 4543 return done; 4544 } 4545 void doXNextEventVoid() { 4546 doXPending(); 4547 } 4548 } 4549 4550 version(with_eventloop) { 4551 int loopHelper(bool delegate() whileCondition) { 4552 // FIXME: whileCondition 4553 import arsd.eventloop; 4554 loop(); 4555 return 0; 4556 } 4557 } else 4558 int loopHelper(bool delegate() whileCondition) { 4559 version(X11) { 4560 bool done = false; 4561 4562 XFlush(display); 4563 insideXEventLoop = true; 4564 scope(exit) insideXEventLoop = false; 4565 4566 version(use_arsd_core) { 4567 import arsd.core; 4568 auto el = getThisThreadEventLoop(EventLoopType.Ui); 4569 4570 static bool loopInitialized = false; 4571 if(!loopInitialized) { 4572 el.addDelegateOnLoopIteration(&doXNextEventVoid, 0); 4573 el.addDelegateOnLoopIteration(&SimpleWindow.processAllCustomEvents, 0); 4574 4575 if(customSignalFD != -1) 4576 cast(void) el.addCallbackOnFdReadable(customSignalFD, new CallbackHelper(() { 4577 version(linux) { 4578 import core.sys.linux.sys.signalfd; 4579 import core.sys.posix.unistd : read; 4580 signalfd_siginfo info; 4581 read(customSignalFD, &info, info.sizeof); 4582 4583 auto sig = info.ssi_signo; 4584 4585 if(EventLoop.get.signalHandler !is null) { 4586 EventLoop.get.signalHandler()(sig); 4587 } else { 4588 EventLoop.get.exit(); 4589 } 4590 } 4591 })); 4592 4593 if(display.fd != -1) 4594 cast(void) el.addCallbackOnFdReadable(display.fd, new CallbackHelper(() { 4595 this.mtLock(); 4596 scope(exit) this.mtUnlock(); 4597 while(!done && XPending(display)) { 4598 done = doXNextEvent(this.display); 4599 } 4600 })); 4601 4602 if(pulseFd != -1) 4603 cast(void) el.addCallbackOnFdReadable(pulseFd, new CallbackHelper(() { 4604 long expirationCount; 4605 // 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... 4606 4607 handlePulse(); 4608 4609 // read just to clear the buffer so poll doesn't trigger again 4610 // BTW I read AFTER the pulse because if the pulse handler takes 4611 // a lot of time to execute, we don't want the app to get stuck 4612 // in a loop of timer hits without a chance to do anything else 4613 // 4614 // IOW handlePulse happens at most once per pulse interval. 4615 unix.read(pulseFd, &expirationCount, expirationCount.sizeof); 4616 })); 4617 4618 if(customEventFDRead != -1) 4619 cast(void) el.addCallbackOnFdReadable(customEventFDRead, new CallbackHelper(() { 4620 // we have some custom events; process 'em 4621 import core.sys.posix.unistd : read; 4622 ulong n; 4623 read(customEventFDRead, &n, n.sizeof); // reset counter value to zero again 4624 //{ import core.stdc.stdio; printf("custom event! count=%u\n", eventQueueUsed); } 4625 //SimpleWindow.processAllCustomEvents(); 4626 })); 4627 4628 // FIXME: posix fds 4629 // FIXME up? 4630 4631 4632 loopInitialized = true; 4633 } 4634 4635 el.run(() => !whileCondition()); 4636 } else version(linux) { 4637 while(!done && (whileCondition is null || whileCondition() == true) && notExited) { 4638 bool forceXPending = false; 4639 auto wto = SimpleWindow.eventAllQueueTimeoutMSecs(); 4640 // eh... some events may be queued for "squashing" (or "late delivery"), so we have to do the following magic 4641 { 4642 this.mtLock(); 4643 scope(exit) this.mtUnlock(); 4644 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 4645 } 4646 //{ import core.stdc.stdio; printf("*** wto=%d; force=%d\n", wto, (forceXPending ? 1 : 0)); } 4647 auto nfds = ep.epoll_wait(epollFd, events.ptr, events.length, (wto == 0 || wto >= int.max ? -1 : cast(int)wto)); 4648 if(nfds == -1) { 4649 if(err.errno == err.EINTR) { 4650 //if(forceXPending) goto xpending; 4651 continue; // interrupted by signal, just try again 4652 } 4653 throw new Exception("epoll wait failure"); 4654 } 4655 // writeln(nfds, " ", events[0].data.fd); 4656 4657 SimpleWindow.processAllCustomEvents(); // anyway 4658 //version(sdddd) { writeln("nfds=", nfds, "; [0]=", events[0].data.fd); } 4659 foreach(idx; 0 .. nfds) { 4660 if(done) break; 4661 auto fd = events[idx].data.fd; 4662 assert(fd != -1); // should never happen cuz the api doesn't do that but better to assert than assume. 4663 auto flags = events[idx].events; 4664 if(flags & ep.EPOLLIN) { 4665 if (fd == customSignalFD) { 4666 version(linux) { 4667 import core.sys.linux.sys.signalfd; 4668 import core.sys.posix.unistd : read; 4669 signalfd_siginfo info; 4670 read(customSignalFD, &info, info.sizeof); 4671 4672 auto sig = info.ssi_signo; 4673 4674 if(EventLoop.get.signalHandler !is null) { 4675 EventLoop.get.signalHandler()(sig); 4676 } else { 4677 EventLoop.get.exit(); 4678 } 4679 } 4680 } else if(fd == display.fd) { 4681 version(sdddd) { writeln("X EVENT PENDING!"); } 4682 this.mtLock(); 4683 scope(exit) this.mtUnlock(); 4684 while(!done && XPending(display)) { 4685 done = doXNextEvent(this.display); 4686 } 4687 forceXPending = false; 4688 } else if(fd == pulseFd) { 4689 long expirationCount; 4690 // 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... 4691 4692 handlePulse(); 4693 4694 // read just to clear the buffer so poll doesn't trigger again 4695 // BTW I read AFTER the pulse because if the pulse handler takes 4696 // a lot of time to execute, we don't want the app to get stuck 4697 // in a loop of timer hits without a chance to do anything else 4698 // 4699 // IOW handlePulse happens at most once per pulse interval. 4700 unix.read(pulseFd, &expirationCount, expirationCount.sizeof); 4701 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 4702 } else if (fd == customEventFDRead) { 4703 // we have some custom events; process 'em 4704 import core.sys.posix.unistd : read; 4705 ulong n; 4706 read(customEventFDRead, &n, n.sizeof); // reset counter value to zero again 4707 //{ import core.stdc.stdio; printf("custom event! count=%u\n", eventQueueUsed); } 4708 //SimpleWindow.processAllCustomEvents(); 4709 4710 forceXPending = true; 4711 } else { 4712 // some other timer 4713 version(sdddd) { writeln("unknown fd: ", fd); } 4714 4715 if(Timer* t = fd in Timer.mapping) 4716 (*t).trigger(); 4717 4718 if(PosixFdReader* pfr = fd in PosixFdReader.mapping) 4719 (*pfr).ready(flags); 4720 4721 // we don't know what the user did in this timer, so we need to assume that 4722 // there's X data to be flushed and potentially processed 4723 forceXPending = true; 4724 4725 // or i might add support for other FDs too 4726 // but for now it is just timer 4727 // (if you want other fds, use arsd.eventloop and compile with -version=with_eventloop), it offers a fuller api for arbitrary stuff. 4728 } 4729 } 4730 if(flags & ep.EPOLLHUP) { 4731 if(PosixFdReader* pfr = fd in PosixFdReader.mapping) 4732 (*pfr).hup(flags); 4733 if(globalHupHandler) 4734 globalHupHandler(fd, flags); 4735 } 4736 /+ 4737 } else { 4738 // not interested in OUT, we are just reading here. 4739 // 4740 // error or hup might also be reported 4741 // but it shouldn't here since we are only 4742 // using a few types of FD and Xlib will report 4743 // if it dies. 4744 // so instead of thoughtfully handling it, I'll 4745 // just throw. for now at least 4746 4747 throw new Exception("epoll did something else"); 4748 } 4749 +/ 4750 } 4751 // if we won't call `XPending()` here, libX may delay some internal event delivery. 4752 // i.e. we HAVE to repeatedly call `XPending()` even if libX fd wasn't signalled! 4753 xpending: 4754 if (!done && forceXPending) { 4755 done = doXPending(); 4756 } 4757 } 4758 } else { 4759 // Generic fallback: yes to simple pulse support, 4760 // but NO timer support! 4761 4762 // FIXME: we could probably support the POSIX timer_create 4763 // signal-based option, but I'm in no rush to write it since 4764 // I prefer the fd-based functions. 4765 while (!done && (whileCondition is null || whileCondition() == true) && notExited) { 4766 4767 import core.sys.posix.poll; 4768 4769 pollfd[] pfds; 4770 pollfd[32] pfdsBuffer; 4771 auto len = PosixFdReader.mapping.length + 2; 4772 // FIXME: i should just reuse the buffer 4773 if(len < pfdsBuffer.length) 4774 pfds = pfdsBuffer[0 .. len]; 4775 else 4776 pfds = new pollfd[](len); 4777 4778 pfds[0].fd = display.fd; 4779 pfds[0].events = POLLIN; 4780 pfds[0].revents = 0; 4781 4782 int slot = 1; 4783 4784 if(customEventFDRead != -1) { 4785 pfds[slot].fd = customEventFDRead; 4786 pfds[slot].events = POLLIN; 4787 pfds[slot].revents = 0; 4788 4789 slot++; 4790 } 4791 4792 foreach(fd, obj; PosixFdReader.mapping) { 4793 if(!obj.enabled) continue; 4794 pfds[slot].fd = fd; 4795 pfds[slot].events = POLLIN; 4796 pfds[slot].revents = 0; 4797 4798 slot++; 4799 } 4800 4801 auto ret = poll(pfds.ptr, slot, pulseTimeout > 0 ? cast(int) pulseTimeout : -1); 4802 if(ret == -1) throw new Exception("poll"); 4803 4804 if(ret == 0) { 4805 // FIXME it may not necessarily time out if events keep coming 4806 if(handlePulse !is null) 4807 handlePulse(); 4808 } else { 4809 foreach(s; 0 .. slot) { 4810 if(pfds[s].revents == 0) continue; 4811 4812 if(pfds[s].fd == display.fd) { 4813 while(!done && XPending(display)) { 4814 this.mtLock(); 4815 scope(exit) this.mtUnlock(); 4816 done = doXNextEvent(this.display); 4817 } 4818 } else if(customEventFDRead != -1 && pfds[s].fd == customEventFDRead) { 4819 4820 import core.sys.posix.unistd : read; 4821 ulong n; 4822 read(customEventFDRead, &n, n.sizeof); 4823 SimpleWindow.processAllCustomEvents(); 4824 } else { 4825 auto obj = PosixFdReader.mapping[pfds[s].fd]; 4826 if(pfds[s].revents & POLLNVAL) { 4827 obj.dispose(); 4828 } else { 4829 obj.ready(pfds[s].revents); 4830 } 4831 } 4832 4833 ret--; 4834 if(ret == 0) break; 4835 } 4836 } 4837 } 4838 } 4839 } 4840 4841 version(Windows) { 4842 4843 version(use_arsd_core) { 4844 import arsd.core; 4845 auto el = getThisThreadEventLoop(EventLoopType.Ui); 4846 static bool loopInitialized = false; 4847 if(!loopInitialized) { 4848 el.addDelegateOnLoopIteration(&SimpleWindow.processAllCustomEvents, 0); 4849 el.addDelegateOnLoopIteration(function() { eventLoopRound++; }, 0); 4850 loopInitialized = true; 4851 } 4852 el.run(() => !whileCondition()); 4853 } else { 4854 int ret = -1; 4855 MSG message; 4856 while(ret != 0 && (whileCondition is null || whileCondition() == true) && notExited) { 4857 eventLoopRound++; 4858 auto wto = SimpleWindow.eventAllQueueTimeoutMSecs(); 4859 auto waitResult = MsgWaitForMultipleObjectsEx( 4860 cast(int) handles.length, handles.ptr, 4861 (wto == 0 ? INFINITE : wto), /* timeout */ 4862 0x04FF, /* QS_ALLINPUT */ 4863 0x0002 /* MWMO_ALERTABLE */ | 0x0004 /* MWMO_INPUTAVAILABLE */); 4864 4865 SimpleWindow.processAllCustomEvents(); // anyway 4866 enum WAIT_OBJECT_0 = 0; 4867 if(waitResult >= WAIT_OBJECT_0 && waitResult < handles.length + WAIT_OBJECT_0) { 4868 auto h = handles[waitResult - WAIT_OBJECT_0]; 4869 if(auto e = h in WindowsHandleReader.mapping) { 4870 (*e).ready(); 4871 } 4872 } else if(waitResult == handles.length + WAIT_OBJECT_0) { 4873 // message ready 4874 int count; 4875 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 4876 ret = GetMessage(&message, null, 0, 0); 4877 if(ret == -1) 4878 throw new WindowsApiException("GetMessage", GetLastError()); 4879 TranslateMessage(&message); 4880 DispatchMessage(&message); 4881 4882 count++; 4883 if(count > 10) 4884 break; // take the opportunity to catch up on other events 4885 4886 if(ret == 0) { // WM_QUIT 4887 EventLoop.quitApplication(); 4888 break; 4889 } 4890 } 4891 } else if(waitResult == 0x000000C0L /* WAIT_IO_COMPLETION */) { 4892 SleepEx(0, true); // I call this to give it a chance to do stuff like async io 4893 } else if(waitResult == 258L /* WAIT_TIMEOUT */) { 4894 // timeout, should never happen since we aren't using it 4895 } else if(waitResult == 0xFFFFFFFF) { 4896 // failed 4897 throw new WindowsApiException("MsgWaitForMultipleObjectsEx", GetLastError()); 4898 } else { 4899 // idk.... 4900 } 4901 } 4902 } 4903 4904 // return message.wParam; 4905 return 0; 4906 } else { 4907 return 0; 4908 } 4909 } 4910 4911 int run(bool delegate() whileCondition = null) { 4912 if(disposed) 4913 initialize(this.pulseTimeout); 4914 4915 version(X11) { 4916 try { 4917 return loopHelper(whileCondition); 4918 } catch(XDisconnectException e) { 4919 if(e.userRequested) { 4920 foreach(item; CapableOfHandlingNativeEvent.nativeHandleMapping) 4921 item.discardConnectionState(); 4922 XCloseDisplay(XDisplayConnection.display); 4923 } 4924 4925 XDisplayConnection.display = null; 4926 4927 this.dispose(); 4928 4929 throw e; 4930 } 4931 } else { 4932 return loopHelper(whileCondition); 4933 } 4934 } 4935 } 4936 4937 4938 /++ 4939 Provides an icon on the system notification area (also known as the system tray). 4940 4941 4942 If a notification area is not available with the NotificationIcon object is created, 4943 it will silently succeed and simply attempt to create one when an area becomes available. 4944 4945 4946 NotificationAreaIcon on Windows assumes you are on Windows Vista or later. Support for 4947 Windows XP was dropped on October 31, 2023. On the other hand, support for 32 bit transparency 4948 with true color was added at that time. I was just too lazy to write the fallback. 4949 4950 If this is an issue, let me know, it'd take about an hour to get it back in there, but I suggest 4951 you use arsd 10.x when targeting Windows XP. 4952 +/ 4953 version(OSXCocoa) {} else // NotYetImplementedException 4954 class NotificationAreaIcon : CapableOfHandlingNativeEvent { 4955 4956 version(X11) { 4957 void recreateAfterDisconnect() { 4958 stateDiscarded = false; 4959 clippixmap = None; 4960 throw new Exception("NOT IMPLEMENTED"); 4961 } 4962 4963 bool stateDiscarded; 4964 void discardConnectionState() { 4965 stateDiscarded = true; 4966 } 4967 } 4968 4969 4970 version(X11) { 4971 Image img; 4972 4973 NativeEventHandler getNativeEventHandler() { 4974 return delegate int(XEvent e) { 4975 switch(e.type) { 4976 case EventType.Expose: 4977 //case EventType.VisibilityNotify: 4978 redraw(); 4979 break; 4980 case EventType.ClientMessage: 4981 version(sddddd) { 4982 writeln("\t", e.xclient.message_type == GetAtom!("_XEMBED")(XDisplayConnection.get)); 4983 writeln("\t", e.xclient.format); 4984 writeln("\t", e.xclient.data.l); 4985 } 4986 break; 4987 case EventType.ButtonPress: 4988 auto event = e.xbutton; 4989 if (onClick !is null || onClickEx !is null) { 4990 MouseButton mb = cast(MouseButton)0; 4991 switch (event.button) { 4992 case 1: mb = MouseButton.left; break; // left 4993 case 2: mb = MouseButton.middle; break; // middle 4994 case 3: mb = MouseButton.right; break; // right 4995 case 4: mb = MouseButton.wheelUp; break; // scroll up 4996 case 5: mb = MouseButton.wheelDown; break; // scroll down 4997 case 6: break; // scroll left... 4998 case 7: break; // scroll right... 4999 case 8: mb = MouseButton.backButton; break; 5000 case 9: mb = MouseButton.forwardButton; break; 5001 default: 5002 } 5003 if (mb) { 5004 try { onClick()(mb); } catch (Exception) {} 5005 if (onClickEx !is null) try { onClickEx(event.x_root, event.y_root, mb, cast(ModifierState)event.state); } catch (Exception) {} 5006 } 5007 } 5008 break; 5009 case EventType.EnterNotify: 5010 if (onEnter !is null) { 5011 onEnter(e.xcrossing.x_root, e.xcrossing.y_root, cast(ModifierState)e.xcrossing.state); 5012 } 5013 break; 5014 case EventType.LeaveNotify: 5015 if (onLeave !is null) try { onLeave(); } catch (Exception) {} 5016 break; 5017 case EventType.DestroyNotify: 5018 active = false; 5019 CapableOfHandlingNativeEvent.nativeHandleMapping.remove(nativeHandle); 5020 break; 5021 case EventType.ConfigureNotify: 5022 auto event = e.xconfigure; 5023 this.width = event.width; 5024 this.height = event.height; 5025 // writeln(width, " x " , height, " @ ", event.x, " ", event.y); 5026 redraw(); 5027 break; 5028 default: return 1; 5029 } 5030 return 1; 5031 }; 5032 } 5033 5034 /* private */ void hideBalloon() { 5035 balloon.close(); 5036 version(with_timer) 5037 timer.destroy(); 5038 balloon = null; 5039 version(with_timer) 5040 timer = null; 5041 } 5042 5043 void redraw() { 5044 if (!active) return; 5045 5046 auto display = XDisplayConnection.get; 5047 GC gc; 5048 5049 // from https://stackoverflow.com/questions/10492275/how-to-upload-32-bit-image-to-server-side-pixmap 5050 5051 int gc_depth(int depth, Display *dpy, Window root, GC *gc) { 5052 Visual *visual; 5053 XVisualInfo vis_info; 5054 XSetWindowAttributes win_attr; 5055 c_ulong win_mask; 5056 5057 if(!XMatchVisualInfo(dpy, 0, depth, 4 /*TrueColor*/, &vis_info)) { 5058 assert(0); 5059 // return 1; 5060 } 5061 5062 visual = vis_info.visual; 5063 5064 win_attr.colormap = XCreateColormap(dpy, root, visual, AllocNone); 5065 win_attr.background_pixel = 0; 5066 win_attr.border_pixel = 0; 5067 5068 win_mask = CWBackPixel | CWColormap | CWBorderPixel; 5069 5070 *gc = XCreateGC(dpy, nativeHandle, 0, null); 5071 5072 return 0; 5073 } 5074 5075 if(useAlpha) 5076 gc_depth(32, display, RootWindow(display, DefaultScreen(display)), &gc); 5077 else 5078 gc = DefaultGC(display, DefaultScreen(display)); 5079 5080 XClearWindow(display, nativeHandle); 5081 5082 if(!useAlpha && img !is null) 5083 XSetClipMask(display, gc, clippixmap); 5084 5085 /+ 5086 XSetForeground(display, gc, 5087 cast(uint) 0 << 16 | 5088 cast(uint) 0 << 8 | 5089 cast(uint) 0); 5090 XFillRectangle(display, nativeHandle, gc, 0, 0, width, height); 5091 +/ 5092 5093 if (img is null) { 5094 XSetForeground(display, gc, 5095 cast(uint) 0 << 16 | 5096 cast(uint) 127 << 8 | 5097 cast(uint) 0); 5098 XFillArc(display, nativeHandle, 5099 gc, width / 4, height / 4, width * 2 / 4, height * 2 / 4, 0 * 64, 360 * 64); 5100 } else { 5101 int dx = 0; 5102 int dy = 0; 5103 if(width > img.width) 5104 dx = (width - img.width) / 2; 5105 if(height > img.height) 5106 dy = (height - img.height) / 2; 5107 // writeln(img.width, " ", img.height, " vs ", width, " ", height); 5108 XSetClipOrigin(display, gc, dx, dy); 5109 5110 int max(int a, int b) { 5111 if(a > b) return a; else return b; 5112 } 5113 5114 if (img.usingXshm) 5115 XShmPutImage(display, cast(Drawable)nativeHandle, gc, img.handle, 0, 0, dx, dy, max(img.width, width), max(img.height, height), false); 5116 else 5117 XPutImage(display, cast(Drawable)nativeHandle, gc, img.handle, 0, 0, dx, dy, max(img.width, width), max(img.height, height)); 5118 } 5119 XSetClipMask(display, gc, None); 5120 flushGui(); 5121 } 5122 5123 static Window getTrayOwner() { 5124 auto display = XDisplayConnection.get; 5125 auto i = cast(int) DefaultScreen(display); 5126 if(i < 10 && i >= 0) { 5127 static Atom atom; 5128 if(atom == None) 5129 atom = XInternAtom(display, cast(char*) ("_NET_SYSTEM_TRAY_S"~(cast(char) (i + '0')) ~ '\0').ptr, false); 5130 return XGetSelectionOwner(display, atom); 5131 } 5132 return None; 5133 } 5134 5135 static void sendTrayMessage(arch_long message, arch_long d1, arch_long d2, arch_long d3) { 5136 auto to = getTrayOwner(); 5137 auto display = XDisplayConnection.get; 5138 XEvent ev; 5139 ev.xclient.type = EventType.ClientMessage; 5140 ev.xclient.window = to; 5141 ev.xclient.message_type = GetAtom!("_NET_SYSTEM_TRAY_OPCODE", true)(display); 5142 ev.xclient.format = 32; 5143 ev.xclient.data.l[0] = CurrentTime; 5144 ev.xclient.data.l[1] = message; 5145 ev.xclient.data.l[2] = d1; 5146 ev.xclient.data.l[3] = d2; 5147 ev.xclient.data.l[4] = d3; 5148 5149 XSendEvent(XDisplayConnection.get, to, false, EventMask.NoEventMask, &ev); 5150 } 5151 5152 private static NotificationAreaIcon[] activeIcons; 5153 5154 // FIXME: possible leak with this stuff, should be able to clear it and stuff. 5155 private void newManager() { 5156 close(); 5157 createXWin(); 5158 5159 if(this.clippixmap) 5160 XFreePixmap(XDisplayConnection.get, clippixmap); 5161 if(this.originalMemoryImage) 5162 this.icon = this.originalMemoryImage; 5163 else if(this.img) 5164 this.icon = this.img; 5165 } 5166 5167 private bool useAlpha = false; 5168 5169 private void createXWin () { 5170 // create window 5171 auto display = XDisplayConnection.get; 5172 5173 // to check for MANAGER on root window to catch new/changed tray owners 5174 XDisplayConnection.addRootInput(EventMask.StructureNotifyMask); 5175 // so if a thing does appear, we can handle it 5176 foreach(ai; activeIcons) 5177 if(ai is this) 5178 goto alreadythere; 5179 activeIcons ~= this; 5180 alreadythere: 5181 5182 // and check for an existing tray 5183 auto trayOwner = getTrayOwner(); 5184 if(trayOwner == None) 5185 return; 5186 //throw new Exception("No notification area found"); 5187 5188 Visual* v = cast(Visual*) CopyFromParent; 5189 5190 // GNOME's default is 22x22 and KDE assumes all icons are going to match that then bitmap scales 5191 // from there. It is ugly and stupid but this gives the fewest artifacts. Good environments will send 5192 // a resize event later. 5193 width = 22; 5194 height = 22; 5195 5196 // if they system gave us a 32 bit visual we need to switch to it too 5197 int depth = 24; 5198 5199 auto visualProp = getX11PropertyData(trayOwner, GetAtom!("_NET_SYSTEM_TRAY_VISUAL", true)(display)); 5200 if(visualProp !is null) { 5201 c_ulong[] info = cast(c_ulong[]) visualProp; 5202 if(info.length == 1) { 5203 auto vid = info[0]; 5204 int returned; 5205 XVisualInfo t; 5206 t.visualid = vid; 5207 auto got = XGetVisualInfo(display, VisualIDMask, &t, &returned); 5208 if(got !is null) { 5209 if(returned == 1) { 5210 v = got.visual; 5211 depth = got.depth; 5212 // writeln("using special visual ", got.depth); 5213 // writeln(depth); 5214 } 5215 XFree(got); 5216 } 5217 } 5218 } 5219 5220 int CWFlags = CWBackPixel | CWBorderPixel | CWOverrideRedirect; 5221 XSetWindowAttributes attr; 5222 attr.background_pixel = 0; 5223 attr.border_pixel = 0; 5224 attr.override_redirect = 0; 5225 if(v !is cast(Visual*) CopyFromParent) { 5226 attr.colormap = XCreateColormap(display, RootWindow(display, DefaultScreen(display)), v, AllocNone); 5227 CWFlags |= CWColormap; 5228 if(depth == 32) 5229 useAlpha = true; 5230 else 5231 goto plain; 5232 } else { 5233 plain: 5234 attr.background_pixmap = 1 /* ParentRelative */; 5235 CWFlags |= CWBackPixmap; 5236 } 5237 5238 auto nativeWindow = XCreateWindow(display, RootWindow(display, DefaultScreen(display)), 0, 0, width, height, 0, depth, InputOutput, v, CWFlags, &attr); 5239 5240 assert(nativeWindow); 5241 5242 if(!useAlpha) 5243 XSetWindowBackgroundPixmap(display, nativeWindow, 1 /* ParentRelative */); 5244 5245 nativeHandle = nativeWindow; 5246 5247 ///+ 5248 arch_ulong[2] info; 5249 info[0] = 0; 5250 info[1] = 1; 5251 5252 string title = this.name is null ? "simpledisplay.d program" : this.name; 5253 auto XA_UTF8 = XInternAtom(display, "UTF8_STRING".ptr, false); 5254 auto XA_NETWM_NAME = XInternAtom(display, "_NET_WM_NAME".ptr, false); 5255 XChangeProperty(display, nativeWindow, XA_NETWM_NAME, XA_UTF8, 8, PropModeReplace, title.ptr, cast(uint)title.length); 5256 5257 XChangeProperty( 5258 display, 5259 nativeWindow, 5260 GetAtom!("_XEMBED_INFO", true)(display), 5261 GetAtom!("_XEMBED_INFO", true)(display), 5262 32 /* bits */, 5263 0 /*PropModeReplace*/, 5264 info.ptr, 5265 2); 5266 5267 import core.sys.posix.unistd; 5268 arch_ulong pid = getpid(); 5269 5270 XChangeProperty( 5271 display, 5272 nativeWindow, 5273 GetAtom!("_NET_WM_PID", true)(display), 5274 XA_CARDINAL, 5275 32 /* bits */, 5276 0 /*PropModeReplace*/, 5277 &pid, 5278 1); 5279 5280 updateNetWmIcon(); 5281 5282 if (sdpyWindowClassStr !is null && sdpyWindowClassStr[0]) { 5283 //{ import core.stdc.stdio; printf("winclass: [%s]\n", sdpyWindowClassStr); } 5284 XClassHint klass; 5285 XWMHints wh; 5286 XSizeHints size; 5287 klass.res_name = sdpyWindowClassStr; 5288 klass.res_class = sdpyWindowClassStr; 5289 XSetWMProperties(display, nativeWindow, null, null, null, 0, &size, &wh, &klass); 5290 } 5291 5292 // believe it or not, THIS is what xfce needed for the 9999 issue 5293 XSizeHints sh; 5294 c_long spr; 5295 XGetWMNormalHints(display, nativeWindow, &sh, &spr); 5296 sh.flags |= PMaxSize | PMinSize; 5297 // FIXME maybe nicer resizing 5298 sh.min_width = 16; 5299 sh.min_height = 16; 5300 sh.max_width = 22; 5301 sh.max_height = 22; 5302 XSetWMNormalHints(display, nativeWindow, &sh); 5303 5304 5305 //+/ 5306 5307 5308 XSelectInput(display, nativeWindow, 5309 EventMask.ButtonPressMask | EventMask.ExposureMask | EventMask.StructureNotifyMask | EventMask.VisibilityChangeMask | 5310 EventMask.EnterWindowMask | EventMask.LeaveWindowMask); 5311 5312 sendTrayMessage(SYSTEM_TRAY_REQUEST_DOCK, nativeWindow, 0, 0); 5313 // XMapWindow(display, nativeWindow); // to demo it w/o a tray 5314 5315 CapableOfHandlingNativeEvent.nativeHandleMapping[nativeWindow] = this; 5316 active = true; 5317 } 5318 5319 void updateNetWmIcon() { 5320 if(img is null) return; 5321 auto display = XDisplayConnection.get; 5322 // FIXME: ensure this is correct 5323 arch_ulong[] buffer; 5324 auto imgMi = img.toTrueColorImage; 5325 buffer ~= imgMi.width; 5326 buffer ~= imgMi.height; 5327 foreach(c; imgMi.imageData.colors) { 5328 arch_ulong b; 5329 b |= c.a << 24; 5330 b |= c.r << 16; 5331 b |= c.g << 8; 5332 b |= c.b; 5333 buffer ~= b; 5334 } 5335 5336 XChangeProperty( 5337 display, 5338 nativeHandle, 5339 GetAtom!"_NET_WM_ICON"(display), 5340 GetAtom!"CARDINAL"(display), 5341 32 /* bits */, 5342 0 /*PropModeReplace*/, 5343 buffer.ptr, 5344 cast(int) buffer.length); 5345 } 5346 5347 5348 5349 private SimpleWindow balloon; 5350 version(with_timer) 5351 private Timer timer; 5352 5353 private Window nativeHandle; 5354 private Pixmap clippixmap = None; 5355 private int width = 16; 5356 private int height = 16; 5357 private bool active = false; 5358 5359 void delegate (int x, int y, MouseButton button, ModifierState mods) onClickEx; /// x and y are globals (relative to root window). X11 only. 5360 void delegate (int x, int y, ModifierState mods) onEnter; /// x and y are global window coordinates. X11 only. 5361 void delegate () onLeave; /// X11 only. 5362 5363 @property bool closed () const pure nothrow @safe @nogc { return !active; } /// 5364 5365 /// X11 only. Get global window coordinates and size. This can be used to show various notifications. 5366 void getWindowRect (out int x, out int y, out int width, out int height) { 5367 if (!active) { width = 1; height = 1; return; } // 1: just in case 5368 Window dummyw; 5369 auto dpy = XDisplayConnection.get; 5370 //XWindowAttributes xwa; 5371 //XGetWindowAttributes(dpy, nativeHandle, &xwa); 5372 //XTranslateCoordinates(dpy, nativeHandle, RootWindow(dpy, DefaultScreen(dpy)), xwa.x, xwa.y, &x, &y, &dummyw); 5373 XTranslateCoordinates(dpy, nativeHandle, RootWindow(dpy, DefaultScreen(dpy)), x, y, &x, &y, &dummyw); 5374 width = this.width; 5375 height = this.height; 5376 } 5377 } 5378 5379 /+ 5380 What I actually want from this: 5381 5382 * set / change: icon, tooltip 5383 * handle: mouse click, right click 5384 * show: notification bubble. 5385 +/ 5386 5387 version(Windows) { 5388 WindowsIcon win32Icon; 5389 HWND hwnd; 5390 5391 NOTIFYICONDATAW data; 5392 5393 NativeEventHandler getNativeEventHandler() { 5394 return delegate int(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam, out int mustReturn) { 5395 if(msg == WM_USER) { 5396 auto event = LOWORD(lParam); 5397 auto iconId = HIWORD(lParam); 5398 //auto x = GET_X_LPARAM(wParam); 5399 //auto y = GET_Y_LPARAM(wParam); 5400 switch(event) { 5401 case WM_LBUTTONDOWN: 5402 onClick()(MouseButton.left); 5403 break; 5404 case WM_RBUTTONDOWN: 5405 onClick()(MouseButton.right); 5406 break; 5407 case WM_MBUTTONDOWN: 5408 onClick()(MouseButton.middle); 5409 break; 5410 case WM_MOUSEMOVE: 5411 // sent, we could use it. 5412 break; 5413 case WM_MOUSEWHEEL: 5414 // NOT SENT 5415 break; 5416 //case NIN_KEYSELECT: 5417 //case NIN_SELECT: 5418 //break; 5419 default: {} 5420 } 5421 } 5422 return 0; 5423 }; 5424 } 5425 5426 enum NIF_SHOWTIP = 0x00000080; 5427 5428 private static struct NOTIFYICONDATAW { 5429 DWORD cbSize; 5430 HWND hWnd; 5431 UINT uID; 5432 UINT uFlags; 5433 UINT uCallbackMessage; 5434 HICON hIcon; 5435 WCHAR[128] szTip; 5436 DWORD dwState; 5437 DWORD dwStateMask; 5438 WCHAR[256] szInfo; 5439 union { 5440 UINT uTimeout; 5441 UINT uVersion; 5442 } 5443 WCHAR[64] szInfoTitle; 5444 DWORD dwInfoFlags; 5445 GUID guidItem; 5446 HICON hBalloonIcon; 5447 } 5448 5449 } 5450 5451 /++ 5452 Note that on Windows, only left, right, and middle buttons are sent. 5453 Mouse wheel buttons are NOT set, so don't rely on those events if your 5454 program is meant to be used on Windows too. 5455 +/ 5456 this(string name, MemoryImage icon, void delegate(MouseButton button) onClick) { 5457 // The canonical constructor for Windows needs the MemoryImage, so it is here, 5458 // but on X, we need an Image, so its canonical ctor is there. They should 5459 // forward to each other though. 5460 version(X11) { 5461 this.name = name; 5462 this.onClick = onClick; 5463 createXWin(); 5464 this.icon = icon; 5465 } else version(Windows) { 5466 this.onClick = onClick; 5467 this.win32Icon = new WindowsIcon(icon); 5468 5469 HINSTANCE hInstance = cast(HINSTANCE) GetModuleHandle(null); 5470 5471 static bool registered = false; 5472 if(!registered) { 5473 WNDCLASSEX wc; 5474 wc.cbSize = wc.sizeof; 5475 wc.hInstance = hInstance; 5476 wc.lpfnWndProc = &WndProc; 5477 wc.lpszClassName = "arsd_simpledisplay_notification_icon"w.ptr; 5478 if(!RegisterClassExW(&wc)) 5479 throw new WindowsApiException("RegisterClass", GetLastError()); 5480 registered = true; 5481 } 5482 5483 this.hwnd = CreateWindowW("arsd_simpledisplay_notification_icon"w.ptr, "test"w.ptr /* name */, 0 /* dwStyle */, 0, 0, 0, 0, HWND_MESSAGE, null, hInstance, null); 5484 if(hwnd is null) 5485 throw new WindowsApiException("CreateWindow", GetLastError()); 5486 5487 data.cbSize = data.sizeof; 5488 data.hWnd = hwnd; 5489 data.uID = cast(uint) cast(void*) this; 5490 data.uFlags = NIF_MESSAGE | NIF_ICON | NIF_TIP | NIF_STATE | NIF_SHOWTIP /* use default tooltip, for now. */; 5491 // NIF_INFO means show balloon 5492 data.uCallbackMessage = WM_USER; 5493 data.hIcon = this.win32Icon.hIcon; 5494 data.szTip = ""; // FIXME 5495 data.dwState = 0; // NIS_HIDDEN; // windows vista 5496 data.dwStateMask = NIS_HIDDEN; // windows vista 5497 5498 data.uVersion = 4; // NOTIFYICON_VERSION_4; // Windows Vista and up 5499 5500 5501 Shell_NotifyIcon(NIM_ADD, cast(NOTIFYICONDATA*) &data); 5502 5503 CapableOfHandlingNativeEvent.nativeHandleMapping[this.hwnd] = this; 5504 } else version(OSXCocoa) { 5505 throw new NotYetImplementedException(); 5506 } else static assert(0); 5507 } 5508 5509 /// ditto 5510 this(string name, Image icon, void delegate(MouseButton button) onClick) { 5511 version(X11) { 5512 this.onClick = onClick; 5513 this.name = name; 5514 createXWin(); 5515 this.icon = icon; 5516 } else version(Windows) { 5517 this(name, icon is null ? null : icon.toTrueColorImage(), onClick); 5518 } else version(OSXCocoa) { 5519 throw new NotYetImplementedException(); 5520 } else static assert(0); 5521 } 5522 5523 version(X11) { 5524 /++ 5525 X-specific extension (for now at least) 5526 +/ 5527 this(string name, MemoryImage icon, void delegate(int x, int y, MouseButton button, ModifierState mods) onClickEx) { 5528 this.onClickEx = onClickEx; 5529 createXWin(); 5530 if (icon !is null) this.icon = icon; 5531 } 5532 5533 /// ditto 5534 this(string name, Image icon, void delegate(int x, int y, MouseButton button, ModifierState mods) onClickEx) { 5535 this.onClickEx = onClickEx; 5536 createXWin(); 5537 this.icon = icon; 5538 } 5539 } 5540 5541 private void delegate (MouseButton button) onClick_; 5542 5543 /// 5544 @property final void delegate(MouseButton) onClick() { 5545 if(onClick_ is null) 5546 onClick_ = delegate void(MouseButton) {}; 5547 return onClick_; 5548 } 5549 5550 /// ditto 5551 @property final void onClick(void delegate(MouseButton) handler) { 5552 // I made this a property setter so we can wrap smaller arg 5553 // delegates and just forward all to onClickEx or something. 5554 onClick_ = handler; 5555 } 5556 5557 5558 string name_; 5559 @property void name(string n) { 5560 name_ = n; 5561 } 5562 5563 @property string name() { 5564 return name_; 5565 } 5566 5567 private MemoryImage originalMemoryImage; 5568 5569 /// 5570 @property void icon(MemoryImage i) { 5571 version(X11) { 5572 this.originalMemoryImage = i; 5573 if (!active) return; 5574 if (i !is null) { 5575 this.img = Image.fromMemoryImage(i, useAlpha, false); 5576 if(!useAlpha) 5577 this.clippixmap = transparencyMaskFromMemoryImage(i, nativeHandle); 5578 // writeln("using pixmap ", clippixmap); 5579 updateNetWmIcon(); 5580 redraw(); 5581 } else { 5582 if (this.img !is null) { 5583 this.img = null; 5584 redraw(); 5585 } 5586 } 5587 } else version(Windows) { 5588 this.win32Icon = new WindowsIcon(i); 5589 5590 data.uFlags = NIF_ICON; 5591 data.hIcon = this.win32Icon.hIcon; 5592 5593 Shell_NotifyIcon(NIM_MODIFY, cast(NOTIFYICONDATA*) &data); 5594 } else version(OSXCocoa) { 5595 throw new NotYetImplementedException(); 5596 } else static assert(0); 5597 } 5598 5599 /// ditto 5600 @property void icon (Image i) { 5601 version(X11) { 5602 if (!active) return; 5603 if (i !is img) { 5604 originalMemoryImage = null; 5605 img = i; 5606 redraw(); 5607 } 5608 } else version(Windows) { 5609 this.icon(i is null ? null : i.toTrueColorImage()); 5610 } else version(OSXCocoa) { 5611 throw new NotYetImplementedException(); 5612 } else static assert(0); 5613 } 5614 5615 /++ 5616 Shows a balloon notification. You can only show one balloon at a time, if you call 5617 it twice while one is already up, the first balloon will be replaced. 5618 5619 5620 The user is free to block notifications and they will automatically disappear after 5621 a timeout period. 5622 5623 Params: 5624 title = Title of the notification. Must be 40 chars or less or the OS may truncate it. 5625 message = The message to pop up. Must be 220 chars or less or the OS may truncate it. 5626 icon = the icon to display with the notification. If null, it uses your existing icon. 5627 onclick = delegate called if the user clicks the balloon. (not yet implemented) 5628 timeout = your suggested timeout period. The operating system is free to ignore your suggestion. 5629 +/ 5630 void showBalloon(string title, string message, MemoryImage icon = null, void delegate() onclick = null, int timeout = 2_500) { 5631 bool useCustom = true; 5632 version(libnotify) { 5633 if(onclick is null) // libnotify impl doesn't support callbacks yet because it doesn't do a dbus message loop 5634 try { 5635 if(!active) return; 5636 5637 if(libnotify is null) { 5638 libnotify = new C_DynamicLibrary("libnotify.so"); 5639 libnotify.call!("notify_init", int, const char*)()((ApplicationName ~ "\0").ptr); 5640 } 5641 5642 auto n = libnotify.call!("notify_notification_new", void*, const char*, const char*, const char*)()((title~"\0").ptr, (message~"\0").ptr, null /* icon */); 5643 5644 libnotify.call!("notify_notification_set_timeout", void, void*, int)()(n, timeout); 5645 5646 if(onclick) { 5647 libnotify_action_delegates[libnotify_action_delegates_count] = onclick; 5648 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); 5649 libnotify_action_delegates_count++; 5650 } 5651 5652 // FIXME icon 5653 5654 // set hint image-data 5655 // set default action for onclick 5656 5657 void* error; 5658 libnotify.call!("notify_notification_show", bool, void*, void**)()(n, &error); 5659 5660 useCustom = false; 5661 } catch(Exception e) { 5662 5663 } 5664 } 5665 5666 version(X11) { 5667 if(useCustom) { 5668 if(!active) return; 5669 if(balloon) { 5670 hideBalloon(); 5671 } 5672 // I know there are two specs for this, but one is never 5673 // implemented by any window manager I have ever seen, and 5674 // the other is a bloated mess and too complicated for simpledisplay... 5675 // so doing my own little window instead. 5676 balloon = new SimpleWindow(380, 120, null, OpenGlOptions.no, Resizability.fixedSize, WindowTypes.notification, WindowFlags.dontAutoShow/*, window*/); 5677 5678 int x, y, width, height; 5679 getWindowRect(x, y, width, height); 5680 5681 int bx = x - balloon.width; 5682 int by = y - balloon.height; 5683 if(bx < 0) 5684 bx = x + width + balloon.width; 5685 if(by < 0) 5686 by = y + height; 5687 5688 // just in case, make sure it is actually on scren 5689 if(bx < 0) 5690 bx = 0; 5691 if(by < 0) 5692 by = 0; 5693 5694 balloon.move(bx, by); 5695 auto painter = balloon.draw(); 5696 painter.fillColor = Color(220, 220, 220); 5697 painter.outlineColor = Color.black; 5698 painter.drawRectangle(Point(0, 0), balloon.width, balloon.height); 5699 auto iconWidth = icon is null ? 0 : icon.width; 5700 if(icon) 5701 painter.drawImage(Point(4, 4), Image.fromMemoryImage(icon)); 5702 iconWidth += 6; // margin around the icon 5703 5704 // draw a close button 5705 painter.outlineColor = Color(44, 44, 44); 5706 painter.fillColor = Color(255, 255, 255); 5707 painter.drawRectangle(Point(balloon.width - 15, 3), 13, 13); 5708 painter.pen = Pen(Color.black, 3); 5709 painter.drawLine(Point(balloon.width - 14, 4), Point(balloon.width - 4, 14)); 5710 painter.drawLine(Point(balloon.width - 4, 4), Point(balloon.width - 14, 13)); 5711 painter.pen = Pen(Color.black, 1); 5712 painter.fillColor = Color(220, 220, 220); 5713 5714 // Draw the title and message 5715 painter.drawText(Point(4 + iconWidth, 4), title); 5716 painter.drawLine( 5717 Point(4 + iconWidth, 4 + painter.fontHeight + 1), 5718 Point(balloon.width - 4, 4 + painter.fontHeight + 1), 5719 ); 5720 painter.drawText(Point(4 + iconWidth, 4 + painter.fontHeight + 4), message); 5721 5722 balloon.setEventHandlers( 5723 (MouseEvent ev) { 5724 if(ev.type == MouseEventType.buttonPressed) { 5725 if(ev.x > balloon.width - 16 && ev.y < 16) 5726 hideBalloon(); 5727 else if(onclick) 5728 onclick(); 5729 } 5730 } 5731 ); 5732 balloon.show(); 5733 5734 version(with_timer) 5735 timer = new Timer(timeout, &hideBalloon); 5736 else {} // FIXME 5737 } 5738 } else version(Windows) { 5739 enum NIF_INFO = 0x00000010; 5740 5741 data.uFlags = NIF_INFO; 5742 5743 // FIXME: go back to the last valid unicode code point 5744 if(title.length > 40) 5745 title = title[0 .. 40]; 5746 if(message.length > 220) 5747 message = message[0 .. 220]; 5748 5749 enum NIIF_RESPECT_QUIET_TIME = 0x00000080; 5750 enum NIIF_LARGE_ICON = 0x00000020; 5751 enum NIIF_NOSOUND = 0x00000010; 5752 enum NIIF_USER = 0x00000004; 5753 enum NIIF_ERROR = 0x00000003; 5754 enum NIIF_WARNING = 0x00000002; 5755 enum NIIF_INFO = 0x00000001; 5756 enum NIIF_NONE = 0; 5757 5758 WCharzBuffer t = WCharzBuffer(title); 5759 WCharzBuffer m = WCharzBuffer(message); 5760 5761 t.copyInto(data.szInfoTitle); 5762 m.copyInto(data.szInfo); 5763 data.dwInfoFlags = NIIF_RESPECT_QUIET_TIME; 5764 5765 if(icon !is null) { 5766 auto i = new WindowsIcon(icon); 5767 data.hBalloonIcon = i.hIcon; 5768 data.dwInfoFlags |= NIIF_USER; 5769 } 5770 5771 Shell_NotifyIcon(NIM_MODIFY, cast(NOTIFYICONDATA*) &data); 5772 } else version(OSXCocoa) { 5773 throw new NotYetImplementedException(); 5774 } else static assert(0); 5775 } 5776 5777 /// 5778 //version(Windows) 5779 void show() { 5780 version(X11) { 5781 if(!hidden) 5782 return; 5783 sendTrayMessage(SYSTEM_TRAY_REQUEST_DOCK, nativeHandle, 0, 0); 5784 hidden = false; 5785 } else version(Windows) { 5786 data.uFlags = NIF_STATE; 5787 data.dwState = 0; // NIS_HIDDEN; // windows vista 5788 data.dwStateMask = NIS_HIDDEN; // windows vista 5789 Shell_NotifyIcon(NIM_MODIFY, cast(NOTIFYICONDATA*) &data); 5790 } else version(OSXCocoa) { 5791 throw new NotYetImplementedException(); 5792 } else static assert(0); 5793 } 5794 5795 version(X11) 5796 bool hidden = false; 5797 5798 /// 5799 //version(Windows) 5800 void hide() { 5801 version(X11) { 5802 if(hidden) 5803 return; 5804 hidden = true; 5805 XUnmapWindow(XDisplayConnection.get, nativeHandle); 5806 } else version(Windows) { 5807 data.uFlags = NIF_STATE; 5808 data.dwState = NIS_HIDDEN; // windows vista 5809 data.dwStateMask = NIS_HIDDEN; // windows vista 5810 Shell_NotifyIcon(NIM_MODIFY, cast(NOTIFYICONDATA*) &data); 5811 } else version(OSXCocoa) { 5812 throw new NotYetImplementedException(); 5813 } else static assert(0); 5814 } 5815 5816 /// 5817 void close () { 5818 version(X11) { 5819 if (active) { 5820 active = false; // event handler will set this too, but meh 5821 XUnmapWindow(XDisplayConnection.get, nativeHandle); // 'cause why not; let's be polite 5822 XDestroyWindow(XDisplayConnection.get, nativeHandle); 5823 flushGui(); 5824 } 5825 } else version(Windows) { 5826 Shell_NotifyIcon(NIM_DELETE, cast(NOTIFYICONDATA*) &data); 5827 } else version(OSXCocoa) { 5828 throw new NotYetImplementedException(); 5829 } else static assert(0); 5830 } 5831 5832 ~this() { 5833 if(!thisIsGuiThread) return; // FIXME: leaks if multithreaded gc 5834 version(X11) 5835 if(clippixmap != None) 5836 XFreePixmap(XDisplayConnection.get, clippixmap); 5837 close(); 5838 } 5839 } 5840 5841 version(X11) 5842 /// Call `XFreePixmap` on the return value. 5843 Pixmap transparencyMaskFromMemoryImage(MemoryImage i, Window window) { 5844 char[] data = new char[](i.width * i.height / 8 + 2); 5845 data[] = 0; 5846 5847 int bitOffset = 0; 5848 foreach(c; i.getAsTrueColorImage().imageData.colors) { // FIXME inefficient unnecessary conversion in palette cases 5849 ubyte v = c.a > 128 ? 1 : 0; 5850 data[bitOffset / 8] |= v << (bitOffset%8); 5851 bitOffset++; 5852 } 5853 auto handle = XCreateBitmapFromData(XDisplayConnection.get, cast(Drawable) window, data.ptr, i.width, i.height); 5854 return handle; 5855 } 5856 5857 5858 // basic functions to make timers 5859 /** 5860 A timer that will trigger your function on a given interval. 5861 5862 5863 You create a timer with an interval and a callback. It will continue 5864 to fire on the interval until it is destroyed. 5865 5866 There are currently no one-off timers (instead, just create one and 5867 destroy it when it is triggered) nor are there pause/resume functions - 5868 the timer must again be destroyed and recreated if you want to pause it. 5869 5870 --- 5871 auto timer = new Timer(50, { it happened!; }); 5872 timer.destroy(); 5873 --- 5874 5875 Timers can only be expected to fire when the event loop is running and only 5876 once per iteration through the event loop. 5877 5878 History: 5879 Prior to December 9, 2020, a timer pulse set too high with a handler too 5880 slow could lock up the event loop. It now guarantees other things will 5881 get a chance to run between timer calls, even if that means not keeping up 5882 with the requested interval. 5883 */ 5884 version(with_timer) { 5885 version(use_arsd_core) 5886 alias Timer = arsd.core.Timer; // FIXME should probably wrap it for a stable api 5887 else 5888 class Timer { 5889 // FIXME: needs pause and unpause 5890 // FIXME: I might add overloads for ones that take a count of 5891 // how many elapsed since last time (on Windows, it will divide 5892 // the ticks thing given, on Linux it is just available) and 5893 // maybe one that takes an instance of the Timer itself too 5894 /// Create a timer with a callback when it triggers. 5895 this(int intervalInMilliseconds, void delegate() onPulse) @trusted { 5896 assert(onPulse !is null); 5897 5898 this.intervalInMilliseconds = intervalInMilliseconds; 5899 this.onPulse = onPulse; 5900 5901 version(Windows) { 5902 /* 5903 handle = SetTimer(null, handle, intervalInMilliseconds, &timerCallback); 5904 if(handle == 0) 5905 throw new WindowsApiException("SetTimer", GetLastError()); 5906 */ 5907 5908 // thanks to Archival 998 for the WaitableTimer blocks 5909 handle = CreateWaitableTimer(null, false, null); 5910 long initialTime = -intervalInMilliseconds; 5911 if(handle is null || !SetWaitableTimer(handle, cast(LARGE_INTEGER*)&initialTime, intervalInMilliseconds, &timerCallback, handle, false)) 5912 throw new WindowsApiException("SetWaitableTimer", GetLastError()); 5913 5914 mapping[handle] = this; 5915 5916 } else version(linux) { 5917 static import ep = core.sys.linux.epoll; 5918 5919 import core.sys.linux.timerfd; 5920 5921 fd = timerfd_create(CLOCK_MONOTONIC, 0); 5922 if(fd == -1) 5923 throw new Exception("timer create failed"); 5924 5925 mapping[fd] = this; 5926 5927 itimerspec value = makeItimerspec(intervalInMilliseconds); 5928 5929 if(timerfd_settime(fd, 0, &value, null) == -1) 5930 throw new Exception("couldn't make pulse timer"); 5931 5932 version(with_eventloop) { 5933 import arsd.eventloop; 5934 addFileEventListeners(fd, &trigger, null, null); 5935 } else { 5936 prepareEventLoop(); 5937 5938 ep.epoll_event ev = void; 5939 ev.events = ep.EPOLLIN; 5940 ev.data.fd = fd; 5941 ep.epoll_ctl(epollFd, ep.EPOLL_CTL_ADD, fd, &ev); 5942 } 5943 } else featureNotImplemented(); 5944 } 5945 5946 private int intervalInMilliseconds; 5947 5948 // just cuz I sometimes call it this. 5949 alias dispose = destroy; 5950 5951 /// Stop and destroy the timer object. 5952 void destroy() { 5953 version(Windows) { 5954 staticDestroy(handle); 5955 handle = null; 5956 } else version(linux) { 5957 staticDestroy(fd); 5958 fd = -1; 5959 } else featureNotImplemented(); 5960 } 5961 5962 version(Windows) 5963 static void staticDestroy(HANDLE handle) { 5964 if(handle) { 5965 // KillTimer(null, handle); 5966 CancelWaitableTimer(cast(void*)handle); 5967 mapping.remove(handle); 5968 CloseHandle(handle); 5969 } 5970 } 5971 else version(linux) 5972 static void staticDestroy(int fd) @system { 5973 if(fd != -1) { 5974 import unix = core.sys.posix.unistd; 5975 static import ep = core.sys.linux.epoll; 5976 5977 version(with_eventloop) { 5978 import arsd.eventloop; 5979 removeFileEventListeners(fd); 5980 } else { 5981 ep.epoll_event ev = void; 5982 ev.events = ep.EPOLLIN; 5983 ev.data.fd = fd; 5984 5985 ep.epoll_ctl(epollFd, ep.EPOLL_CTL_DEL, fd, &ev); 5986 } 5987 unix.close(fd); 5988 mapping.remove(fd); 5989 } 5990 } 5991 5992 ~this() { 5993 version(Windows) { if(handle) 5994 cleanupQueue.queue!staticDestroy(handle); 5995 } else version(linux) { if(fd != -1) 5996 cleanupQueue.queue!staticDestroy(fd); 5997 } 5998 } 5999 6000 void changeTime(int intervalInMilliseconds) 6001 { 6002 this.intervalInMilliseconds = intervalInMilliseconds; 6003 version(Windows) 6004 { 6005 if(handle) 6006 { 6007 //handle = SetTimer(null, handle, intervalInMilliseconds, &timerCallback); 6008 long initialTime = -intervalInMilliseconds; 6009 if(handle is null || !SetWaitableTimer(handle, cast(LARGE_INTEGER*)&initialTime, intervalInMilliseconds, &timerCallback, handle, false)) 6010 throw new WindowsApiException("couldn't change pulse timer", GetLastError()); 6011 } 6012 } else version(linux) { 6013 import core.sys.linux.timerfd; 6014 6015 itimerspec value = makeItimerspec(intervalInMilliseconds); 6016 if(timerfd_settime(fd, 0, &value, null) == -1) { 6017 throw new Exception("couldn't change pulse timer"); 6018 } 6019 } else { 6020 assert(false, "Timer.changeTime(int) is not implemented for this platform"); 6021 } 6022 } 6023 6024 6025 private: 6026 6027 void delegate() onPulse; 6028 6029 int lastEventLoopRoundTriggered; 6030 6031 version(linux) { 6032 static auto makeItimerspec(int intervalInMilliseconds) { 6033 import core.sys.linux.timerfd; 6034 6035 itimerspec value; 6036 value.it_value.tv_sec = cast(int) (intervalInMilliseconds / 1000); 6037 value.it_value.tv_nsec = (intervalInMilliseconds % 1000) * 1000_000; 6038 6039 value.it_interval.tv_sec = cast(int) (intervalInMilliseconds / 1000); 6040 value.it_interval.tv_nsec = (intervalInMilliseconds % 1000) * 1000_000; 6041 6042 return value; 6043 } 6044 } 6045 6046 void trigger() { 6047 version(linux) { 6048 import unix = core.sys.posix.unistd; 6049 long val; 6050 unix.read(fd, &val, val.sizeof); // gotta clear the pipe 6051 } else version(Windows) { 6052 if(this.lastEventLoopRoundTriggered == eventLoopRound) 6053 return; // never try to actually run faster than the event loop 6054 lastEventLoopRoundTriggered = eventLoopRound; 6055 } else featureNotImplemented(); 6056 6057 onPulse(); 6058 } 6059 6060 version(Windows) 6061 void rearm() { 6062 6063 } 6064 6065 version(Windows) 6066 extern(Windows) 6067 //static void timerCallback(HWND, UINT, UINT_PTR timer, DWORD dwTime) nothrow { 6068 static void timerCallback(HANDLE timer, DWORD lowTime, DWORD hiTime) nothrow { 6069 if(Timer* t = timer in mapping) { 6070 try 6071 (*t).trigger(); 6072 catch(Exception e) { sdpy_abort(e); assert(0); } 6073 } 6074 } 6075 6076 version(Windows) { 6077 //UINT_PTR handle; 6078 //static Timer[UINT_PTR] mapping; 6079 HANDLE handle; 6080 __gshared Timer[HANDLE] mapping; 6081 } else version(linux) { 6082 int fd = -1; 6083 __gshared Timer[int] mapping; 6084 } else version(OSXCocoa) { 6085 } else static assert(0, "timer not supported"); 6086 } 6087 } 6088 6089 version(Windows) 6090 private int eventLoopRound; 6091 6092 version(Windows) 6093 /// 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 6094 class WindowsHandleReader { 6095 /// 6096 this(void delegate() onReady, HANDLE handle) { 6097 this.onReady = onReady; 6098 this.handle = handle; 6099 6100 mapping[handle] = this; 6101 6102 enable(); 6103 } 6104 6105 version(use_arsd_core) 6106 ICoreEventLoop.UnregisterToken unregisterToken; 6107 6108 /// 6109 void enable() { 6110 version(use_arsd_core) { 6111 unregisterToken = getThisThreadEventLoop(EventLoopType.Ui).addCallbackOnHandleReady(handle, new CallbackHelper(&ready)); 6112 } else { 6113 auto el = EventLoop.get().impl; 6114 el.handles ~= handle; 6115 } 6116 } 6117 6118 /// 6119 void disable() { 6120 version(use_arsd_core) { 6121 unregisterToken.unregister(); 6122 } else { 6123 auto el = EventLoop.get().impl; 6124 for(int i = 0; i < el.handles.length; i++) { 6125 if(el.handles[i] is handle) { 6126 el.handles[i] = el.handles[$-1]; 6127 el.handles = el.handles[0 .. $-1]; 6128 return; 6129 } 6130 } 6131 } 6132 } 6133 6134 void dispose() { 6135 disable(); 6136 if(handle) 6137 mapping.remove(handle); 6138 handle = null; 6139 } 6140 6141 void ready() { 6142 if(onReady) 6143 onReady(); 6144 } 6145 6146 HANDLE handle; 6147 void delegate() onReady; 6148 6149 __gshared WindowsHandleReader[HANDLE] mapping; 6150 } 6151 6152 version(Posix) 6153 /// Lets you add files to the event loop for reading. Use at your own risk. 6154 class PosixFdReader { 6155 /// 6156 this(void delegate() onReady, int fd, bool captureReads = true, bool captureWrites = false) { 6157 this((int, bool, bool) { onReady(); }, fd, captureReads, captureWrites); 6158 } 6159 6160 /// 6161 this(void delegate(int) onReady, int fd, bool captureReads = true, bool captureWrites = false) { 6162 this((int fd, bool, bool) { onReady(fd); }, fd, captureReads, captureWrites); 6163 } 6164 6165 /// 6166 this(void delegate(int fd, bool read, bool write) onReady, int fd, bool captureReads = true, bool captureWrites = false) { 6167 this.onReady = onReady; 6168 this.fd = fd; 6169 this.captureWrites = captureWrites; 6170 this.captureReads = captureReads; 6171 6172 mapping[fd] = this; 6173 6174 version(with_eventloop) { 6175 import arsd.eventloop; 6176 addFileEventListeners(fd, &readyel); 6177 } else { 6178 enable(); 6179 } 6180 } 6181 6182 bool captureReads; 6183 bool captureWrites; 6184 6185 version(use_arsd_core) { 6186 import arsd.core; 6187 ICoreEventLoop.UnregisterToken unregisterToken; 6188 } 6189 6190 version(with_eventloop) {} else 6191 /// 6192 void enable() @system { 6193 enabled = true; 6194 6195 version(use_arsd_core) { 6196 unregisterToken = getThisThreadEventLoop(EventLoopType.Ui).addCallbackOnFdReadable(fd, new CallbackHelper( 6197 () { onReady(fd, true, false); } 6198 )); 6199 // FIXME: what if it is writeable? 6200 6201 } else version(linux) { 6202 prepareEventLoop(); 6203 static import ep = core.sys.linux.epoll; 6204 ep.epoll_event ev = void; 6205 ev.events = (captureReads ? ep.EPOLLIN : 0) | (captureWrites ? ep.EPOLLOUT : 0); 6206 // writeln("enable ", fd, " ", captureReads, " ", captureWrites); 6207 ev.data.fd = fd; 6208 ep.epoll_ctl(epollFd, ep.EPOLL_CTL_ADD, fd, &ev); 6209 } else { 6210 6211 } 6212 } 6213 6214 version(with_eventloop) {} else 6215 /// 6216 void disable() @system { 6217 enabled = false; 6218 6219 version(use_arsd_core) { 6220 unregisterToken.unregister(); 6221 } else 6222 version(linux) { 6223 prepareEventLoop(); 6224 static import ep = core.sys.linux.epoll; 6225 ep.epoll_event ev = void; 6226 ev.events = (captureReads ? ep.EPOLLIN : 0) | (captureWrites ? ep.EPOLLOUT : 0); 6227 // writeln("disable ", fd, " ", captureReads, " ", captureWrites); 6228 ev.data.fd = fd; 6229 ep.epoll_ctl(epollFd, ep.EPOLL_CTL_DEL, fd, &ev); 6230 } 6231 } 6232 6233 version(with_eventloop) {} else 6234 /// 6235 void dispose() { 6236 if(enabled) 6237 disable(); 6238 if(fd != -1) 6239 mapping.remove(fd); 6240 fd = -1; 6241 } 6242 6243 void delegate(int, bool, bool) onReady; 6244 6245 version(with_eventloop) 6246 void readyel() { 6247 onReady(fd, true, true); 6248 } 6249 6250 void ready(uint flags) { 6251 version(linux) { 6252 static import ep = core.sys.linux.epoll; 6253 onReady(fd, (flags & ep.EPOLLIN) ? true : false, (flags & ep.EPOLLOUT) ? true : false); 6254 } else { 6255 import core.sys.posix.poll; 6256 onReady(fd, (flags & POLLIN) ? true : false, (flags & POLLOUT) ? true : false); 6257 } 6258 } 6259 6260 void hup(uint flags) { 6261 if(onHup) 6262 onHup(); 6263 } 6264 6265 void delegate() onHup; 6266 6267 int fd = -1; 6268 private bool enabled; 6269 __gshared PosixFdReader[int] mapping; 6270 } 6271 6272 // basic functions to access the clipboard 6273 /+ 6274 6275 6276 http://msdn.microsoft.com/en-us/library/windows/desktop/ff729168%28v=vs.85%29.aspx 6277 http://msdn.microsoft.com/en-us/library/windows/desktop/ms649039%28v=vs.85%29.aspx 6278 http://msdn.microsoft.com/en-us/library/windows/desktop/ms649035%28v=vs.85%29.aspx 6279 http://msdn.microsoft.com/en-us/library/windows/desktop/ms649051%28v=vs.85%29.aspx 6280 http://msdn.microsoft.com/en-us/library/windows/desktop/ms649037%28v=vs.85%29.aspx 6281 http://msdn.microsoft.com/en-us/library/windows/desktop/ms649035%28v=vs.85%29.aspx 6282 http://msdn.microsoft.com/en-us/library/windows/desktop/ms649016%28v=vs.85%29.aspx 6283 6284 +/ 6285 6286 /++ 6287 this does a delegate because it is actually an async call on X... 6288 the receiver may never be called if the clipboard is empty or unavailable 6289 gets plain text from the clipboard. 6290 +/ 6291 void getClipboardText(SimpleWindow clipboardOwner, void delegate(in char[]) receiver) @system { 6292 version(Windows) { 6293 HWND hwndOwner = clipboardOwner ? clipboardOwner.impl.hwnd : null; 6294 if(OpenClipboard(hwndOwner) == 0) 6295 throw new WindowsApiException("OpenClipboard", GetLastError()); 6296 scope(exit) 6297 CloseClipboard(); 6298 // see: https://docs.microsoft.com/en-us/windows/win32/api/winuser/nf-winuser-getpriorityclipboardformat 6299 if(auto dataHandle = GetClipboardData(CF_UNICODETEXT)) { 6300 6301 if(auto data = cast(wchar*) GlobalLock(dataHandle)) { 6302 scope(exit) 6303 GlobalUnlock(dataHandle); 6304 6305 // FIXME: CR/LF conversions 6306 // FIXME: I might not have to copy it now that the receiver is in char[] instead of string 6307 int len = 0; 6308 auto d = data; 6309 while(*d) { 6310 d++; 6311 len++; 6312 } 6313 string s; 6314 s.reserve(len); 6315 foreach(dchar ch; data[0 .. len]) { 6316 s ~= ch; 6317 } 6318 receiver(s); 6319 } 6320 } 6321 } else version(X11) { 6322 getX11Selection!"CLIPBOARD"(clipboardOwner, receiver); 6323 } else version(OSXCocoa) { 6324 throw new NotYetImplementedException(); 6325 } else static assert(0); 6326 } 6327 6328 // FIXME: a clipboard listener might be cool btw 6329 6330 /++ 6331 this does a delegate because it is actually an async call on X... 6332 the receiver may never be called if the clipboard is empty or unavailable 6333 gets image from the clipboard. 6334 6335 templated because it introduces an optional dependency on arsd.bmp 6336 +/ 6337 void getClipboardImage()(SimpleWindow clipboardOwner, void delegate(MemoryImage) receiver) { 6338 version(Windows) { 6339 HWND hwndOwner = clipboardOwner ? clipboardOwner.impl.hwnd : null; 6340 if(OpenClipboard(hwndOwner) == 0) 6341 throw new WindowsApiException("OpenClipboard", GetLastError()); 6342 scope(exit) 6343 CloseClipboard(); 6344 if(auto dataHandle = GetClipboardData(CF_DIBV5)) { 6345 if(auto data = cast(ubyte*) GlobalLock(dataHandle)) { 6346 scope(exit) 6347 GlobalUnlock(dataHandle); 6348 6349 auto len = GlobalSize(dataHandle); 6350 6351 import arsd.bmp; 6352 auto img = readBmp(data[0 .. len], false); 6353 receiver(img); 6354 } 6355 } 6356 } else version(X11) { 6357 getX11Selection!"CLIPBOARD"(clipboardOwner, receiver); 6358 } else version(OSXCocoa) { 6359 throw new NotYetImplementedException(); 6360 } else static assert(0); 6361 } 6362 6363 /// Copies some text to the clipboard. 6364 void setClipboardText(SimpleWindow clipboardOwner, string text) { 6365 assert(clipboardOwner !is null); 6366 version(Windows) { 6367 if(OpenClipboard(clipboardOwner.impl.hwnd) == 0) 6368 throw new WindowsApiException("OpenClipboard", GetLastError()); 6369 scope(exit) 6370 CloseClipboard(); 6371 EmptyClipboard(); 6372 auto sz = sizeOfConvertedWstring(text, WindowsStringConversionFlags.convertNewLines | WindowsStringConversionFlags.zeroTerminate); 6373 auto handle = GlobalAlloc(GMEM_MOVEABLE, sz * 2); // zero terminated wchars 6374 if(handle is null) throw new WindowsApiException("GlobalAlloc", GetLastError()); 6375 if(auto data = cast(wchar*) GlobalLock(handle)) { 6376 auto slice = data[0 .. sz]; 6377 scope(failure) 6378 GlobalUnlock(handle); 6379 6380 auto str = makeWindowsString(text, slice, WindowsStringConversionFlags.convertNewLines | WindowsStringConversionFlags.zeroTerminate); 6381 6382 GlobalUnlock(handle); 6383 SetClipboardData(CF_UNICODETEXT, handle); 6384 } 6385 } else version(X11) { 6386 setX11Selection!"CLIPBOARD"(clipboardOwner, text); 6387 } else version(OSXCocoa) { 6388 throw new NotYetImplementedException(); 6389 } else static assert(0); 6390 } 6391 6392 void setClipboardImage()(SimpleWindow clipboardOwner, MemoryImage img) { 6393 assert(clipboardOwner !is null); 6394 version(Windows) { 6395 if(OpenClipboard(clipboardOwner.impl.hwnd) == 0) 6396 throw new WindowsApiException("OpenClipboard", GetLastError()); 6397 scope(exit) 6398 CloseClipboard(); 6399 EmptyClipboard(); 6400 6401 6402 import arsd.bmp; 6403 ubyte[] mdata; 6404 mdata.reserve(img.width * img.height); 6405 void sink(ubyte b) { 6406 mdata ~= b; 6407 } 6408 writeBmpIndirect(img, &sink, false); 6409 6410 auto handle = GlobalAlloc(GMEM_MOVEABLE, mdata.length); 6411 if(handle is null) throw new WindowsApiException("GlobalAlloc", GetLastError()); 6412 if(auto data = cast(ubyte*) GlobalLock(handle)) { 6413 auto slice = data[0 .. mdata.length]; 6414 scope(failure) 6415 GlobalUnlock(handle); 6416 6417 slice[] = mdata[]; 6418 6419 GlobalUnlock(handle); 6420 SetClipboardData(CF_DIB, handle); 6421 } 6422 } else version(X11) { 6423 static class X11SetSelectionHandler_Image : X11SetSelectionHandler { 6424 mixin X11SetSelectionHandler_Basics; 6425 private const(ubyte)[] mdata; 6426 private const(ubyte)[] mdata_original; 6427 this(MemoryImage img) { 6428 import arsd.bmp; 6429 6430 mdata.reserve(img.width * img.height); 6431 void sink(ubyte b) { 6432 mdata ~= b; 6433 } 6434 writeBmpIndirect(img, &sink, true); 6435 6436 mdata_original = mdata; 6437 } 6438 6439 Atom[] availableFormats() { 6440 auto display = XDisplayConnection.get; 6441 return [ 6442 GetAtom!"image/bmp"(display), 6443 GetAtom!"TARGETS"(display) 6444 ]; 6445 } 6446 6447 ubyte[] getData(Atom format, return scope ubyte[] data) { 6448 if(mdata.length < data.length) { 6449 data[0 .. mdata.length] = mdata[]; 6450 auto ret = data[0 .. mdata.length]; 6451 mdata = mdata[$..$]; 6452 return ret; 6453 } else { 6454 data[] = mdata[0 .. data.length]; 6455 mdata = mdata[data.length .. $]; 6456 return data[]; 6457 } 6458 } 6459 6460 void done() { 6461 mdata = mdata_original; 6462 } 6463 } 6464 6465 setX11Selection!"CLIPBOARD"(clipboardOwner, new X11SetSelectionHandler_Image(img)); 6466 } else version(OSXCocoa) { 6467 throw new NotYetImplementedException(); 6468 } else static assert(0); 6469 } 6470 6471 6472 version(X11) { 6473 // and the PRIMARY on X, be sure to put these in static if(UsingSimpledisplayX11) 6474 6475 private __gshared Atom*[] interredAtoms; // for discardAndRecreate 6476 6477 // FIXME: do a GetAtomUpfront too that just queues all at CT and combines it all. 6478 /// Platform-specific for X11. 6479 /// History: On February 21, 2021, I changed the default value of `create` to be true. 6480 @property Atom GetAtom(string name, bool create = true)(Display* display) { 6481 __gshared static Atom a; 6482 if(!a) { 6483 a = XInternAtom(display, name, !create); 6484 // FIXME: might need to synchronize this and attach it to the actual object 6485 interredAtoms ~= &a; 6486 } 6487 if(a == None) 6488 throw new Exception("XInternAtom " ~ name ~ " " ~ (create ? "true":"false")); 6489 return a; 6490 } 6491 6492 /// Platform-specific for X11 - gets atom names as a string. 6493 string getAtomName(Atom atom, Display* display) { 6494 auto got = XGetAtomName(display, atom); 6495 scope(exit) XFree(got); 6496 import core.stdc.string; 6497 string s = got[0 .. strlen(got)].idup; 6498 return s; 6499 } 6500 6501 /// Asserts ownership of PRIMARY and copies the text into a buffer that clients can request later. 6502 void setPrimarySelection(SimpleWindow window, string text) { 6503 setX11Selection!"PRIMARY"(window, text); 6504 } 6505 6506 /// Asserts ownership of SECONDARY and copies the text into a buffer that clients can request later. 6507 void setSecondarySelection(SimpleWindow window, string text) { 6508 setX11Selection!"SECONDARY"(window, text); 6509 } 6510 6511 interface X11SetSelectionHandler { 6512 // should include TARGETS right now 6513 Atom[] availableFormats(); 6514 // Return the slice of data you filled, empty slice if done. 6515 // this is to support the incremental thing 6516 ubyte[] getData(Atom format, return scope ubyte[] data); 6517 6518 void done(); 6519 6520 void handleRequest(XEvent); 6521 6522 bool matchesIncr(Window, Atom); 6523 void sendMoreIncr(XPropertyEvent*); 6524 } 6525 6526 mixin template X11SetSelectionHandler_Basics() { 6527 Window incrWindow; 6528 Atom incrAtom; 6529 Atom selectionAtom; 6530 Atom formatAtom; 6531 ubyte[] toSend; 6532 bool matchesIncr(Window w, Atom a) { 6533 return incrAtom && incrAtom == a && w == incrWindow; 6534 } 6535 void sendMoreIncr(XPropertyEvent* event) { 6536 auto display = XDisplayConnection.get; 6537 6538 XChangeProperty (display, 6539 incrWindow, 6540 incrAtom, 6541 formatAtom, 6542 8 /* bits */, PropModeReplace, 6543 toSend.ptr, cast(int) toSend.length); 6544 6545 if(toSend.length != 0) { 6546 toSend = this.getData(formatAtom, toSend[]); 6547 } else { 6548 this.done(); 6549 incrWindow = None; 6550 incrAtom = None; 6551 selectionAtom = None; 6552 formatAtom = None; 6553 toSend = null; 6554 } 6555 } 6556 void handleRequest(XEvent ev) { 6557 6558 auto display = XDisplayConnection.get; 6559 6560 XSelectionRequestEvent* event = &ev.xselectionrequest; 6561 XSelectionEvent selectionEvent; 6562 selectionEvent.type = EventType.SelectionNotify; 6563 selectionEvent.display = event.display; 6564 selectionEvent.requestor = event.requestor; 6565 selectionEvent.selection = event.selection; 6566 selectionEvent.time = event.time; 6567 selectionEvent.target = event.target; 6568 6569 bool supportedType() { 6570 foreach(t; this.availableFormats()) 6571 if(t == event.target) 6572 return true; 6573 return false; 6574 } 6575 6576 if(event.property == None) { 6577 selectionEvent.property = event.target; 6578 6579 XSendEvent(display, selectionEvent.requestor, false, 0, cast(XEvent*) &selectionEvent); 6580 XFlush(display); 6581 } if(event.target == GetAtom!"TARGETS"(display)) { 6582 /* respond with the supported types */ 6583 auto tlist = this.availableFormats(); 6584 XChangeProperty(display, event.requestor, event.property, XA_ATOM, 32, PropModeReplace, cast(void*)tlist.ptr, cast(int) tlist.length); 6585 selectionEvent.property = event.property; 6586 6587 XSendEvent(display, selectionEvent.requestor, false, 0, cast(XEvent*) &selectionEvent); 6588 XFlush(display); 6589 } else if(supportedType()) { 6590 auto buffer = new ubyte[](1024 * 64); 6591 auto toSend = this.getData(event.target, buffer[]); 6592 6593 if(toSend.length < 32 * 1024) { 6594 // small enough to send directly... 6595 selectionEvent.property = event.property; 6596 XChangeProperty (display, 6597 selectionEvent.requestor, 6598 selectionEvent.property, 6599 event.target, 6600 8 /* bits */, 0 /* PropModeReplace */, 6601 toSend.ptr, cast(int) toSend.length); 6602 6603 XSendEvent(display, selectionEvent.requestor, false, 0, cast(XEvent*) &selectionEvent); 6604 XFlush(display); 6605 } else { 6606 // large, let's send incrementally 6607 arch_ulong l = toSend.length; 6608 6609 // if I wanted other events from this window don't want to clear that out.... 6610 XWindowAttributes xwa; 6611 XGetWindowAttributes(display, selectionEvent.requestor, &xwa); 6612 6613 XSelectInput(display, selectionEvent.requestor, cast(EventMask) (xwa.your_event_mask | EventMask.PropertyChangeMask)); 6614 6615 incrWindow = event.requestor; 6616 incrAtom = event.property; 6617 formatAtom = event.target; 6618 selectionAtom = event.selection; 6619 this.toSend = toSend; 6620 6621 selectionEvent.property = event.property; 6622 XChangeProperty (display, 6623 selectionEvent.requestor, 6624 selectionEvent.property, 6625 GetAtom!"INCR"(display), 6626 32 /* bits */, PropModeReplace, 6627 &l, 1); 6628 6629 XSendEvent(display, selectionEvent.requestor, false, 0, cast(XEvent*) &selectionEvent); 6630 XFlush(display); 6631 } 6632 //if(after) 6633 //after(); 6634 } else { 6635 debug(sdpy_clip) { 6636 writeln("Unsupported data ", getAtomName(event.target, display)); 6637 } 6638 selectionEvent.property = None; // I don't know how to handle this type... 6639 XSendEvent(display, selectionEvent.requestor, false, EventMask.NoEventMask, cast(XEvent*) &selectionEvent); 6640 XFlush(display); 6641 } 6642 } 6643 } 6644 6645 class X11SetSelectionHandler_Text : X11SetSelectionHandler { 6646 mixin X11SetSelectionHandler_Basics; 6647 private const(ubyte)[] text; 6648 private const(ubyte)[] text_original; 6649 this(string text) { 6650 this.text = cast(const ubyte[]) text; 6651 this.text_original = this.text; 6652 } 6653 Atom[] availableFormats() { 6654 auto display = XDisplayConnection.get; 6655 return [ 6656 GetAtom!"UTF8_STRING"(display), 6657 GetAtom!"text/plain"(display), 6658 XA_STRING, 6659 GetAtom!"TARGETS"(display) 6660 ]; 6661 } 6662 6663 ubyte[] getData(Atom format, return scope ubyte[] data) { 6664 if(text.length < data.length) { 6665 data[0 .. text.length] = text[]; 6666 return data[0 .. text.length]; 6667 } else { 6668 data[] = text[0 .. data.length]; 6669 text = text[data.length .. $]; 6670 return data[]; 6671 } 6672 } 6673 6674 void done() { 6675 text = text_original; 6676 } 6677 } 6678 6679 /// 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?!) 6680 void setX11Selection(string atomName)(SimpleWindow window, string text, void delegate() after = null) { 6681 setX11Selection!atomName(window, new X11SetSelectionHandler_Text(text), after); 6682 } 6683 6684 void setX11Selection(string atomName)(SimpleWindow window, X11SetSelectionHandler data, void delegate() after = null) { 6685 assert(window !is null); 6686 6687 auto display = XDisplayConnection.get(); 6688 static if (atomName == "PRIMARY") Atom a = XA_PRIMARY; 6689 else static if (atomName == "SECONDARY") Atom a = XA_SECONDARY; 6690 else Atom a = GetAtom!atomName(display); 6691 6692 XSetSelectionOwner(display, a, window.impl.window, 0 /* CurrentTime */); 6693 6694 window.impl.setSelectionHandlers[a] = data; 6695 } 6696 6697 /// 6698 void getPrimarySelection(SimpleWindow window, void delegate(in char[]) handler) { 6699 getX11Selection!"PRIMARY"(window, handler); 6700 } 6701 6702 // added July 28, 2020 6703 // undocumented as experimental tho 6704 interface X11GetSelectionHandler { 6705 void handleData(Atom target, in ubyte[] data); 6706 Atom findBestFormat(Atom[] answer); 6707 6708 void prepareIncremental(Window, Atom); 6709 bool matchesIncr(Window, Atom); 6710 void handleIncrData(Atom, in ubyte[] data); 6711 } 6712 6713 mixin template X11GetSelectionHandler_Basics() { 6714 Window incrWindow; 6715 Atom incrAtom; 6716 6717 void prepareIncremental(Window w, Atom a) { 6718 incrWindow = w; 6719 incrAtom = a; 6720 } 6721 bool matchesIncr(Window w, Atom a) { 6722 return incrWindow == w && incrAtom == a; 6723 } 6724 6725 Atom incrFormatAtom; 6726 ubyte[] incrData; 6727 void handleIncrData(Atom format, in ubyte[] data) { 6728 incrFormatAtom = format; 6729 6730 if(data.length) 6731 incrData ~= data; 6732 else 6733 handleData(incrFormatAtom, incrData); 6734 6735 } 6736 } 6737 6738 /// 6739 void getX11Selection(string atomName)(SimpleWindow window, void delegate(in char[]) handler, Time timestamp = 0 /* CurrentTime */) { 6740 assert(window !is null); 6741 6742 auto display = XDisplayConnection.get(); 6743 auto atom = GetAtom!atomName(display); 6744 6745 static class X11GetSelectionHandler_Text : X11GetSelectionHandler { 6746 this(void delegate(in char[]) handler) { 6747 this.handler = handler; 6748 } 6749 6750 mixin X11GetSelectionHandler_Basics; 6751 6752 void delegate(in char[]) handler; 6753 6754 void handleData(Atom target, in ubyte[] data) { 6755 // import std.stdio; writeln(target, " ", data); 6756 if(target == GetAtom!"UTF8_STRING"(XDisplayConnection.get) || target == XA_STRING || target == GetAtom!"text/plain"(XDisplayConnection.get)) 6757 handler(cast(const char[]) data); 6758 else if(target == None && data is null) 6759 handler(null); // no suitable selection exists 6760 } 6761 6762 Atom findBestFormat(Atom[] answer) { 6763 Atom best = None; 6764 foreach(option; answer) { 6765 if(option == GetAtom!"UTF8_STRING"(XDisplayConnection.get)) { 6766 best = option; 6767 break; 6768 } else if(option == XA_STRING) { 6769 best = option; 6770 } else if(option == GetAtom!"text/plain"(XDisplayConnection.get)) { 6771 best = option; 6772 } 6773 } 6774 return best; 6775 } 6776 } 6777 6778 window.impl.getSelectionHandlers[atom] = new X11GetSelectionHandler_Text(handler); 6779 6780 auto target = GetAtom!"TARGETS"(display); 6781 6782 // SDD_DATA is "simpledisplay.d data" 6783 XConvertSelection(display, atom, target, GetAtom!("SDD_DATA", true)(display), window.impl.window, timestamp); 6784 } 6785 6786 /// Gets the image on the clipboard, if there is one. Added July 2020. 6787 void getX11Selection(string atomName)(SimpleWindow window, void delegate(MemoryImage) handler) { 6788 assert(window !is null); 6789 6790 auto display = XDisplayConnection.get(); 6791 auto atom = GetAtom!atomName(display); 6792 6793 static class X11GetSelectionHandler_Image : X11GetSelectionHandler { 6794 this(void delegate(MemoryImage) handler) { 6795 this.handler = handler; 6796 } 6797 6798 mixin X11GetSelectionHandler_Basics; 6799 6800 void delegate(MemoryImage) handler; 6801 6802 void handleData(Atom target, in ubyte[] data) { 6803 if(target == GetAtom!"image/bmp"(XDisplayConnection.get)) { 6804 import arsd.bmp; 6805 handler(readBmp(data)); 6806 } 6807 } 6808 6809 Atom findBestFormat(Atom[] answer) { 6810 Atom best = None; 6811 foreach(option; answer) { 6812 if(option == GetAtom!"image/bmp"(XDisplayConnection.get)) { 6813 best = option; 6814 } 6815 } 6816 return best; 6817 } 6818 6819 } 6820 6821 6822 window.impl.getSelectionHandlers[atom] = new X11GetSelectionHandler_Image(handler); 6823 6824 auto target = GetAtom!"TARGETS"(display); 6825 6826 // SDD_DATA is "simpledisplay.d data" 6827 XConvertSelection(display, atom, target, GetAtom!("SDD_DATA", true)(display), window.impl.window, 0 /*CurrentTime*/); 6828 } 6829 6830 6831 /// 6832 void[] getX11PropertyData(Window window, Atom property, Atom type = AnyPropertyType) { 6833 Atom actualType; 6834 int actualFormat; 6835 arch_ulong actualItems; 6836 arch_ulong bytesRemaining; 6837 void* data; 6838 6839 auto display = XDisplayConnection.get(); 6840 if(XGetWindowProperty(display, window, property, 0, 0x7fffffff, false, type, &actualType, &actualFormat, &actualItems, &bytesRemaining, &data) == Success) { 6841 if(actualFormat == 0) 6842 return null; 6843 else { 6844 int byteLength; 6845 if(actualFormat == 32) { 6846 // 32 means it is a C long... which is variable length 6847 actualFormat = cast(int) arch_long.sizeof * 8; 6848 } 6849 6850 // then it is just a bit count 6851 byteLength = cast(int) (actualItems * actualFormat / 8); 6852 6853 auto d = new ubyte[](byteLength); 6854 d[] = cast(ubyte[]) data[0 .. byteLength]; 6855 XFree(data); 6856 return d; 6857 } 6858 } 6859 return null; 6860 } 6861 6862 /* defined in the systray spec */ 6863 enum SYSTEM_TRAY_REQUEST_DOCK = 0; 6864 enum SYSTEM_TRAY_BEGIN_MESSAGE = 1; 6865 enum SYSTEM_TRAY_CANCEL_MESSAGE = 2; 6866 6867 6868 /** Global hotkey handler. Simpledisplay will usually create one for you, but if you want to use subclassing 6869 * instead of delegates, you can subclass this, and override `doHandle()` method. */ 6870 public class GlobalHotkey { 6871 KeyEvent key; 6872 void delegate () handler; 6873 6874 void doHandle () { if (handler !is null) handler(); } /// this will be called by hotkey manager 6875 6876 /// Create from initialzed KeyEvent object 6877 this (KeyEvent akey, void delegate () ahandler=null) { 6878 if (akey.key == 0 || !GlobalHotkeyManager.isGoodModifierMask(akey.modifierState)) throw new Exception("invalid global hotkey"); 6879 key = akey; 6880 handler = ahandler; 6881 } 6882 6883 /// Create from emacs-like key name ("C-M-Y", etc.) 6884 this (const(char)[] akey, void delegate () ahandler=null) { 6885 key = KeyEvent.parse(akey); 6886 if (key.key == 0 || !GlobalHotkeyManager.isGoodModifierMask(key.modifierState)) throw new Exception("invalid global hotkey"); 6887 handler = ahandler; 6888 } 6889 6890 } 6891 6892 private extern(C) int XGrabErrorHandler (Display* dpy, XErrorEvent* evt) nothrow @nogc { 6893 //conwriteln("failed to grab key"); 6894 GlobalHotkeyManager.ghfailed = true; 6895 return 0; 6896 } 6897 6898 private extern(C) int XShmErrorHandler (Display* dpy, XErrorEvent* evt) nothrow @nogc { 6899 Image.impl.xshmfailed = true; 6900 return 0; 6901 } 6902 6903 private __gshared int errorHappened; 6904 private extern(C) int adrlogger (Display* dpy, XErrorEvent* evt) nothrow @nogc { 6905 import core.stdc.stdio; 6906 char[265] buffer; 6907 XGetErrorText(dpy, evt.error_code, buffer.ptr, cast(int) buffer.length); 6908 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); 6909 errorHappened = true; 6910 return 0; 6911 } 6912 6913 /++ 6914 Global hotkey manager. It contains static methods to manage global hotkeys. 6915 6916 --- 6917 try { 6918 GlobalHotkeyManager.register("M-H-A", delegate () { hideShowWindows(); }); 6919 } catch (Exception e) { 6920 conwriteln("ERROR registering hotkey!"); 6921 } 6922 EventLoop.get.run(); 6923 --- 6924 6925 The key strings are based on Emacs. In practical terms, 6926 `M` means `alt` and `H` means the Windows logo key. `C` 6927 is `ctrl`. 6928 6929 $(WARNING 6930 This is X-specific right now. If you are on 6931 Windows, try [registerHotKey] instead. 6932 6933 We will probably merge these into a single 6934 interface later. 6935 ) 6936 +/ 6937 public class GlobalHotkeyManager : CapableOfHandlingNativeEvent { 6938 version(X11) { 6939 void recreateAfterDisconnect() { 6940 throw new Exception("NOT IMPLEMENTED"); 6941 } 6942 void discardConnectionState() { 6943 throw new Exception("NOT IMPLEMENTED"); 6944 } 6945 } 6946 6947 private static immutable uint[8] masklist = [ 0, 6948 KeyOrButtonMask.LockMask, 6949 KeyOrButtonMask.Mod2Mask, 6950 KeyOrButtonMask.Mod3Mask, 6951 KeyOrButtonMask.LockMask|KeyOrButtonMask.Mod2Mask, 6952 KeyOrButtonMask.LockMask|KeyOrButtonMask.Mod3Mask, 6953 KeyOrButtonMask.LockMask|KeyOrButtonMask.Mod2Mask|KeyOrButtonMask.Mod3Mask, 6954 KeyOrButtonMask.Mod2Mask|KeyOrButtonMask.Mod3Mask, 6955 ]; 6956 private __gshared GlobalHotkeyManager ghmanager; 6957 private __gshared bool ghfailed = false; 6958 6959 private static bool isGoodModifierMask (uint modmask) pure nothrow @safe @nogc { 6960 if (modmask == 0) return false; 6961 if (modmask&(KeyOrButtonMask.LockMask|KeyOrButtonMask.Mod2Mask|KeyOrButtonMask.Mod3Mask)) return false; 6962 if (modmask&~(KeyOrButtonMask.Mod5Mask-1)) return false; 6963 return true; 6964 } 6965 6966 private static uint cleanupModifiers (uint modmask) pure nothrow @safe @nogc { 6967 modmask &= ~(KeyOrButtonMask.LockMask|KeyOrButtonMask.Mod2Mask|KeyOrButtonMask.Mod3Mask); // remove caps, num, scroll 6968 modmask &= (KeyOrButtonMask.Mod5Mask-1); // and other modifiers 6969 return modmask; 6970 } 6971 6972 private static uint keyEvent2KeyCode() (scope auto ref const KeyEvent ke) { 6973 uint keycode = cast(uint)ke.key; 6974 auto dpy = XDisplayConnection.get; 6975 return XKeysymToKeycode(dpy, keycode); 6976 } 6977 6978 private static ulong keyCode2Hash() (uint keycode, uint modstate) pure nothrow @safe @nogc { return ((cast(ulong)modstate)<<32)|keycode; } 6979 6980 private __gshared GlobalHotkey[ulong] globalHotkeyList; 6981 6982 NativeEventHandler getNativeEventHandler () { 6983 return delegate int (XEvent e) { 6984 if (e.type != EventType.KeyPress) return 1; 6985 auto kev = cast(const(XKeyEvent)*)&e; 6986 auto hash = keyCode2Hash(e.xkey.keycode, cleanupModifiers(e.xkey.state)); 6987 if (auto ghkp = hash in globalHotkeyList) { 6988 try { 6989 ghkp.doHandle(); 6990 } catch (Exception e) { 6991 import core.stdc.stdio : stderr, fprintf; 6992 stderr.fprintf("HOTKEY HANDLER EXCEPTION: %.*s", cast(uint)e.msg.length, e.msg.ptr); 6993 } 6994 } 6995 return 1; 6996 }; 6997 } 6998 6999 private this () { 7000 auto dpy = XDisplayConnection.get; 7001 auto root = RootWindow(dpy, DefaultScreen(dpy)); 7002 CapableOfHandlingNativeEvent.nativeHandleMapping[root] = this; 7003 XDisplayConnection.addRootInput(EventMask.KeyPressMask); 7004 } 7005 7006 /// Register new global hotkey with initialized `GlobalHotkey` object. 7007 /// This function will throw if it failed to register hotkey (i.e. hotkey is invalid or already taken). 7008 static void register (GlobalHotkey gh) { 7009 if (gh is null) return; 7010 if (gh.key.key == 0 || !isGoodModifierMask(gh.key.modifierState)) throw new Exception("invalid global hotkey"); 7011 7012 auto dpy = XDisplayConnection.get; 7013 immutable keycode = keyEvent2KeyCode(gh.key); 7014 7015 auto hash = keyCode2Hash(keycode, gh.key.modifierState); 7016 if (hash in globalHotkeyList) throw new Exception("duplicate global hotkey"); 7017 if (ghmanager is null) ghmanager = new GlobalHotkeyManager(); 7018 XSync(dpy, 0/*False*/); 7019 7020 Window root = RootWindow(dpy, DefaultScreen(dpy)); 7021 XErrorHandler savedErrorHandler = XSetErrorHandler(&XGrabErrorHandler); 7022 ghfailed = false; 7023 foreach (immutable uint ormask; masklist[]) { 7024 XGrabKey(dpy, keycode, gh.key.modifierState|ormask, /*grab_window*/root, /*owner_events*/0/*False*/, GrabMode.GrabModeAsync, GrabMode.GrabModeAsync); 7025 } 7026 XSync(dpy, 0/*False*/); 7027 XSetErrorHandler(savedErrorHandler); 7028 7029 if (ghfailed) { 7030 savedErrorHandler = XSetErrorHandler(&XGrabErrorHandler); 7031 foreach (immutable uint ormask; masklist[]) XUngrabKey(dpy, keycode, gh.key.modifierState|ormask, /*grab_window*/root); 7032 XSync(dpy, 0/*False*/); 7033 XSetErrorHandler(savedErrorHandler); 7034 throw new Exception("cannot register global hotkey"); 7035 } 7036 7037 globalHotkeyList[hash] = gh; 7038 } 7039 7040 /// Ditto 7041 static void register (const(char)[] akey, void delegate () ahandler) { 7042 register(new GlobalHotkey(akey, ahandler)); 7043 } 7044 7045 private static void removeByHash (ulong hash) { 7046 if (auto ghp = hash in globalHotkeyList) { 7047 auto dpy = XDisplayConnection.get; 7048 immutable keycode = keyEvent2KeyCode(ghp.key); 7049 Window root = RootWindow(dpy, DefaultScreen(dpy)); 7050 XSync(dpy, 0/*False*/); 7051 XErrorHandler savedErrorHandler = XSetErrorHandler(&XGrabErrorHandler); 7052 foreach (immutable uint ormask; masklist[]) XUngrabKey(dpy, keycode, ghp.key.modifierState|ormask, /*grab_window*/root); 7053 XSync(dpy, 0/*False*/); 7054 XSetErrorHandler(savedErrorHandler); 7055 globalHotkeyList.remove(hash); 7056 } 7057 } 7058 7059 /// Register new global hotkey with previously used `GlobalHotkey` object. 7060 /// It is safe to unregister unknown or invalid hotkey. 7061 static void unregister (GlobalHotkey gh) { 7062 //TODO: add second AA for faster search? prolly doesn't worth it. 7063 if (gh is null) return; 7064 foreach (const ref kv; globalHotkeyList.byKeyValue) { 7065 if (kv.value is gh) { 7066 removeByHash(kv.key); 7067 return; 7068 } 7069 } 7070 } 7071 7072 /// Ditto. 7073 static void unregister (const(char)[] key) { 7074 auto kev = KeyEvent.parse(key); 7075 immutable keycode = keyEvent2KeyCode(kev); 7076 removeByHash(keyCode2Hash(keycode, kev.modifierState)); 7077 } 7078 } 7079 } 7080 7081 version(Windows) { 7082 /++ 7083 See [SyntheticInput.sendSyntheticInput] instead for cross-platform applications. 7084 7085 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). 7086 +/ 7087 void sendSyntheticInput(wstring s) { 7088 INPUT[] inputs; 7089 inputs.reserve(s.length * 2); 7090 7091 foreach(wchar c; s) { 7092 INPUT input; 7093 input.type = INPUT_KEYBOARD; 7094 input.ki.wScan = c; 7095 input.ki.dwFlags = KEYEVENTF_UNICODE; 7096 inputs ~= input; 7097 7098 input.ki.dwFlags |= KEYEVENTF_KEYUP; 7099 inputs ~= input; 7100 } 7101 7102 if(SendInput(cast(int) inputs.length, inputs.ptr, INPUT.sizeof) != inputs.length) { 7103 throw new WindowsApiException("SendInput", GetLastError()); 7104 } 7105 7106 } 7107 7108 7109 // global hotkey helper function 7110 7111 /// Platform-specific for Windows. Registers a global hotkey. Returns a registration ID. See [GlobalHotkeyManager] for Linux. Maybe some day I will merge these. 7112 int registerHotKey(SimpleWindow window, UINT modifiers, UINT vk, void delegate() handler) @system { 7113 __gshared int hotkeyId = 0; 7114 int id = ++hotkeyId; 7115 if(!RegisterHotKey(window.impl.hwnd, id, modifiers, vk)) 7116 throw new Exception("RegisterHotKey"); 7117 7118 __gshared void delegate()[WPARAM][HWND] handlers; 7119 7120 handlers[window.impl.hwnd][id] = handler; 7121 7122 int delegate(HWND, UINT, WPARAM, LPARAM, out int) oldHandler; 7123 7124 auto nativeEventHandler = delegate int(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam, out int mustReturn) { 7125 switch(msg) { 7126 // http://msdn.microsoft.com/en-us/library/windows/desktop/ms646279%28v=vs.85%29.aspx 7127 case WM_HOTKEY: 7128 if(auto list = hwnd in handlers) { 7129 if(auto h = wParam in *list) { 7130 (*h)(); 7131 return 0; 7132 } 7133 } 7134 goto default; 7135 default: 7136 } 7137 if(oldHandler) 7138 return oldHandler(hwnd, msg, wParam, lParam, mustReturn); 7139 return 1; // pass it on 7140 }; 7141 7142 if(window.handleNativeEvent.funcptr !is nativeEventHandler.funcptr) { 7143 oldHandler = window.handleNativeEvent; 7144 window.handleNativeEvent = nativeEventHandler; 7145 } 7146 7147 return id; 7148 } 7149 7150 /// Platform-specific for Windows. Unregisters a key. The id is the value returned by [registerHotKey]. 7151 void unregisterHotKey(SimpleWindow window, int id) { 7152 if(!UnregisterHotKey(window.impl.hwnd, id)) 7153 throw new WindowsApiException("UnregisterHotKey", GetLastError()); 7154 } 7155 } 7156 7157 version (X11) { 7158 pragma(lib, "dl"); 7159 import core.sys.posix.dlfcn; 7160 } 7161 7162 /++ 7163 Allows for sending synthetic input to the X server via the Xtst 7164 extension or on Windows using SendInput. 7165 7166 Please remember user input is meant to be user - don't use this 7167 if you have some other alternative! 7168 7169 History: 7170 Added May 17, 2020 with the X implementation. 7171 7172 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.) 7173 Bugs: 7174 All methods on OSX Cocoa will throw not yet implemented exceptions. 7175 +/ 7176 struct SyntheticInput { 7177 @disable this(); 7178 7179 private int* refcount; 7180 7181 version(X11) { 7182 private void* lib; 7183 7184 private extern(C) { 7185 void function(Display*, uint keycode, bool press, arch_ulong delay) XTestFakeKeyEvent; 7186 void function(Display*, uint button, bool press, arch_ulong delay) XTestFakeButtonEvent; 7187 } 7188 } 7189 7190 /// The dummy param must be 0. 7191 this(int dummy) { 7192 version(X11) { 7193 lib = dlopen("libXtst.so", RTLD_NOW); 7194 if(lib is null) 7195 throw new Exception("cannot load xtest lib extension"); 7196 scope(failure) 7197 dlclose(lib); 7198 7199 XTestFakeButtonEvent = cast(typeof(XTestFakeButtonEvent)) dlsym(lib, "XTestFakeButtonEvent"); 7200 XTestFakeKeyEvent = cast(typeof(XTestFakeKeyEvent)) dlsym(lib, "XTestFakeKeyEvent"); 7201 7202 if(XTestFakeKeyEvent is null) 7203 throw new Exception("No XTestFakeKeyEvent"); 7204 if(XTestFakeButtonEvent is null) 7205 throw new Exception("No XTestFakeButtonEvent"); 7206 } 7207 7208 refcount = new int; 7209 *refcount = 1; 7210 } 7211 7212 this(this) { 7213 if(refcount) 7214 *refcount += 1; 7215 } 7216 7217 ~this() { 7218 if(refcount) { 7219 *refcount -= 1; 7220 if(*refcount == 0) 7221 // I commented this because if I close the lib before 7222 // XCloseDisplay, it is liable to segfault... so just 7223 // gonna keep it loaded if it is loaded, no big deal 7224 // anyway. 7225 {} // dlclose(lib); 7226 } 7227 } 7228 7229 /++ 7230 Simulates typing a string into the keyboard. 7231 7232 Bugs: 7233 On X11, this ONLY works with basic ascii! On Windows, it can handle more. 7234 7235 Not implemented except on Windows and X11. 7236 +/ 7237 void sendSyntheticInput(string s) { 7238 version(Windows) { 7239 INPUT[] inputs; 7240 inputs.reserve(s.length * 2); 7241 7242 auto ei = GetMessageExtraInfo(); 7243 7244 foreach(wchar c; s) { 7245 INPUT input; 7246 input.type = INPUT_KEYBOARD; 7247 input.ki.wScan = c; 7248 input.ki.dwFlags = KEYEVENTF_UNICODE; 7249 input.ki.dwExtraInfo = ei; 7250 inputs ~= input; 7251 7252 input.ki.dwFlags |= KEYEVENTF_KEYUP; 7253 inputs ~= input; 7254 } 7255 7256 if(SendInput(cast(int) inputs.length, inputs.ptr, INPUT.sizeof) != inputs.length) { 7257 throw new WindowsApiException("SendInput", GetLastError()); 7258 } 7259 } else version(X11) { 7260 int delay = 0; 7261 foreach(ch; s) { 7262 pressKey(cast(Key) ch, true, delay); 7263 pressKey(cast(Key) ch, false, delay); 7264 delay += 5; 7265 } 7266 } else throw new NotYetImplementedException(); 7267 } 7268 7269 /++ 7270 Sends a fake press or release key event. 7271 7272 Please note you need to call [flushGui] or return to the event loop for this to actually be sent on X11. 7273 7274 Bugs: 7275 The `delay` parameter is not implemented yet on Windows. 7276 7277 Not implemented except on Windows and X11. 7278 +/ 7279 void pressKey(Key key, bool pressed, int delay = 0) { 7280 version(Windows) { 7281 INPUT input; 7282 input.type = INPUT_KEYBOARD; 7283 input.ki.wVk = cast(ushort) key; 7284 7285 input.ki.dwFlags = pressed ? 0 : KEYEVENTF_KEYUP; 7286 input.ki.dwExtraInfo = GetMessageExtraInfo(); 7287 7288 if(SendInput(1, &input, INPUT.sizeof) != 1) { 7289 throw new WindowsApiException("SendInput", GetLastError()); 7290 } 7291 } else version(X11) { 7292 XTestFakeKeyEvent(XDisplayConnection.get, XKeysymToKeycode(XDisplayConnection.get, key), pressed, delay + pressed ? 0 : 5); 7293 } else throw new NotYetImplementedException(); 7294 } 7295 7296 /++ 7297 Sends a fake mouse button press or release event. 7298 7299 Please note you need to call [flushGui] or return to the event loop for this to actually be sent on X11. 7300 7301 `pressed` param must be `true` if button is `wheelUp` or `wheelDown`. 7302 7303 Bugs: 7304 The `delay` parameter is not implemented yet on Windows. 7305 7306 The backButton and forwardButton will throw NotYetImplementedException on Windows. 7307 7308 All arguments will throw NotYetImplementedException on OSX Cocoa. 7309 +/ 7310 void pressMouseButton(MouseButton button, bool pressed, int delay = 0) { 7311 version(Windows) { 7312 INPUT input; 7313 input.type = INPUT_MOUSE; 7314 input.mi.dwExtraInfo = GetMessageExtraInfo(); 7315 7316 // input.mi.mouseData for a wheel event 7317 7318 switch(button) { 7319 case MouseButton.left: input.mi.dwFlags = pressed ? MOUSEEVENTF_LEFTDOWN : MOUSEEVENTF_LEFTUP; break; 7320 case MouseButton.middle: input.mi.dwFlags = pressed ? MOUSEEVENTF_MIDDLEDOWN : MOUSEEVENTF_MIDDLEUP; break; 7321 case MouseButton.right: input.mi.dwFlags = pressed ? MOUSEEVENTF_RIGHTDOWN : MOUSEEVENTF_RIGHTUP; break; 7322 case MouseButton.wheelUp: 7323 case MouseButton.wheelDown: 7324 input.mi.dwFlags = MOUSEEVENTF_WHEEL; 7325 input.mi.mouseData = button == MouseButton.wheelUp ? 120 : -120; 7326 break; 7327 case MouseButton.backButton: throw new NotYetImplementedException(); 7328 case MouseButton.forwardButton: throw new NotYetImplementedException(); 7329 default: 7330 } 7331 7332 if(SendInput(1, &input, INPUT.sizeof) != 1) { 7333 throw new WindowsApiException("SendInput", GetLastError()); 7334 } 7335 } else version(X11) { 7336 int btn; 7337 7338 switch(button) { 7339 case MouseButton.left: btn = 1; break; 7340 case MouseButton.middle: btn = 2; break; 7341 case MouseButton.right: btn = 3; break; 7342 case MouseButton.wheelUp: btn = 4; break; 7343 case MouseButton.wheelDown: btn = 5; break; 7344 case MouseButton.backButton: btn = 8; break; 7345 case MouseButton.forwardButton: btn = 9; break; 7346 default: 7347 } 7348 7349 assert(btn); 7350 7351 XTestFakeButtonEvent(XDisplayConnection.get, btn, pressed, delay); 7352 } else throw new NotYetImplementedException(); 7353 } 7354 7355 /// 7356 static void moveMouseArrowBy(int dx, int dy) { 7357 version(Windows) { 7358 INPUT input; 7359 input.type = INPUT_MOUSE; 7360 input.mi.dwExtraInfo = GetMessageExtraInfo(); 7361 input.mi.dx = dx; 7362 input.mi.dy = dy; 7363 input.mi.dwFlags = MOUSEEVENTF_MOVE; 7364 7365 if(SendInput(1, &input, INPUT.sizeof) != 1) { 7366 throw new WindowsApiException("SendInput", GetLastError()); 7367 } 7368 } else version(X11) { 7369 auto disp = XDisplayConnection.get(); 7370 XWarpPointer(disp, None, None, 0, 0, 0, 0, dx, dy); 7371 XFlush(disp); 7372 } else throw new NotYetImplementedException(); 7373 } 7374 7375 /// 7376 static void moveMouseArrowTo(int x, int y) { 7377 version(Windows) { 7378 INPUT input; 7379 input.type = INPUT_MOUSE; 7380 input.mi.dwExtraInfo = GetMessageExtraInfo(); 7381 input.mi.dx = x; 7382 input.mi.dy = y; 7383 input.mi.dwFlags = MOUSEEVENTF_MOVE | MOUSEEVENTF_ABSOLUTE; 7384 7385 if(SendInput(1, &input, INPUT.sizeof) != 1) { 7386 throw new WindowsApiException("SendInput", GetLastError()); 7387 } 7388 } else version(X11) { 7389 auto disp = XDisplayConnection.get(); 7390 auto root = RootWindow(disp, DefaultScreen(disp)); 7391 XWarpPointer(disp, None, root, 0, 0, 0, 0, x, y); 7392 XFlush(disp); 7393 } else throw new NotYetImplementedException(); 7394 } 7395 } 7396 7397 7398 7399 /++ 7400 [ScreenPainter] operations can use different operations to combine the color with the color on screen. 7401 7402 See_Also: 7403 $(LIST 7404 *[ScreenPainter] 7405 *[ScreenPainter.rasterOp] 7406 ) 7407 +/ 7408 enum RasterOp { 7409 normal, /// Replaces the pixel. 7410 xor, /// Uses bitwise xor to draw. 7411 } 7412 7413 // being phobos-free keeps the size WAY down 7414 private const(char)* toStringz(string s) { return (s ~ '\0').ptr; } 7415 package(arsd) const(wchar)* toWStringz(wstring s) { return (s ~ '\0').ptr; } 7416 package(arsd) const(wchar)* toWStringz(string s) { 7417 wstring r; 7418 foreach(dchar c; s) 7419 r ~= c; 7420 r ~= '\0'; 7421 return r.ptr; 7422 } 7423 private string[] split(in void[] a, char c) { 7424 string[] ret; 7425 size_t previous = 0; 7426 foreach(i, char ch; cast(ubyte[]) a) { 7427 if(ch == c) { 7428 ret ~= cast(string) a[previous .. i]; 7429 previous = i + 1; 7430 } 7431 } 7432 if(previous != a.length) 7433 ret ~= cast(string) a[previous .. $]; 7434 return ret; 7435 } 7436 7437 version(without_opengl) { 7438 enum OpenGlOptions { 7439 no, 7440 } 7441 } else { 7442 /++ 7443 Determines if you want an OpenGL context created on the new window. 7444 7445 7446 See more: [#topics-3d|in the 3d topic]. 7447 7448 --- 7449 import arsd.simpledisplay; 7450 void main() { 7451 auto window = new SimpleWindow(500, 500, "OpenGL Test", OpenGlOptions.yes); 7452 7453 // Set up the matrix 7454 window.setAsCurrentOpenGlContext(); // make this window active 7455 7456 // This is called on each frame, we will draw our scene 7457 window.redrawOpenGlScene = delegate() { 7458 7459 }; 7460 7461 window.eventLoop(0); 7462 } 7463 --- 7464 +/ 7465 enum OpenGlOptions { 7466 no, /// No OpenGL context is created 7467 yes, /// Yes, create an OpenGL context 7468 } 7469 7470 version(X11) { 7471 static if (!SdpyIsUsingIVGLBinds) { 7472 7473 7474 struct __GLXFBConfigRec {} 7475 alias GLXFBConfig = __GLXFBConfigRec*; 7476 7477 //pragma(lib, "GL"); 7478 //pragma(lib, "GLU"); 7479 interface GLX { 7480 extern(C) nothrow @nogc { 7481 XVisualInfo* glXChooseVisual(Display *dpy, int screen, 7482 const int *attrib_list); 7483 7484 void glXCopyContext(Display *dpy, GLXContext src, 7485 GLXContext dst, arch_ulong mask); 7486 7487 GLXContext glXCreateContext(Display *dpy, XVisualInfo *vis, 7488 GLXContext share_list, Bool direct); 7489 7490 GLXPixmap glXCreateGLXPixmap(Display *dpy, XVisualInfo *vis, 7491 Pixmap pixmap); 7492 7493 void glXDestroyContext(Display *dpy, GLXContext ctx); 7494 7495 void glXDestroyGLXPixmap(Display *dpy, GLXPixmap pix); 7496 7497 int glXGetConfig(Display *dpy, XVisualInfo *vis, 7498 int attrib, int *value); 7499 7500 GLXContext glXGetCurrentContext(); 7501 7502 GLXDrawable glXGetCurrentDrawable(); 7503 7504 Bool glXIsDirect(Display *dpy, GLXContext ctx); 7505 7506 Bool glXMakeCurrent(Display *dpy, GLXDrawable drawable, 7507 GLXContext ctx); 7508 7509 Bool glXQueryExtension(Display *dpy, int *error_base, int *event_base); 7510 7511 Bool glXQueryVersion(Display *dpy, int *major, int *minor); 7512 7513 void glXSwapBuffers(Display *dpy, GLXDrawable drawable); 7514 7515 void glXUseXFont(Font font, int first, int count, int list_base); 7516 7517 void glXWaitGL(); 7518 7519 void glXWaitX(); 7520 7521 7522 GLXFBConfig* glXChooseFBConfig (Display*, int, int*, int*); 7523 int glXGetFBConfigAttrib (Display*, GLXFBConfig, int, int*); 7524 XVisualInfo* glXGetVisualFromFBConfig (Display*, GLXFBConfig); 7525 7526 char* glXQueryExtensionsString (Display*, int); 7527 void* glXGetProcAddress (const(char)*); 7528 7529 } 7530 } 7531 7532 version(OSX) 7533 mixin DynamicLoad!(GLX, "GL", 0, openGlLibrariesSuccessfullyLoaded) glx; 7534 else 7535 mixin DynamicLoad!(GLX, "GLX", 0, openGlLibrariesSuccessfullyLoaded) glx; 7536 shared static this() { 7537 glx.loadDynamicLibrary(); 7538 } 7539 7540 alias glbindGetProcAddress = glXGetProcAddress; 7541 } 7542 } else version(Windows) { 7543 /* it is done below by interface GL */ 7544 } else 7545 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."); 7546 } 7547 7548 deprecated("Sorry, I misspelled it in the first version! Use `Resizability` instead.") 7549 alias Resizablity = Resizability; 7550 7551 /// When you create a SimpleWindow, you can see its resizability to be one of these via the constructor... 7552 enum Resizability { 7553 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. 7554 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. 7555 /++ 7556 $(PITFALL 7557 Planned for the future but not implemented. 7558 ) 7559 7560 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. 7561 7562 History: 7563 Added November 11, 2022, but not yet implemented and may not be for some time. 7564 +/ 7565 /*@__future*/ allowResizingMaintainingAspectRatio, 7566 /++ 7567 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. 7568 7569 History: 7570 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. 7571 7572 Your programs should not be affected, as they will continue to function as if the user simply never resized the window at all. 7573 +/ 7574 automaticallyScaleIfPossible, 7575 } 7576 /// ditto 7577 alias Resizeability = Resizability; 7578 7579 7580 /++ 7581 Alignment for [ScreenPainter.drawText]. Left, Center, or Right may be combined with VerticalTop, VerticalCenter, or VerticalBottom via bitwise or. 7582 +/ 7583 enum TextAlignment : uint { 7584 Left = 0, /// 7585 Center = 1, /// 7586 Right = 2, /// 7587 7588 VerticalTop = 0, /// 7589 VerticalCenter = 4, /// 7590 VerticalBottom = 8, /// 7591 } 7592 7593 public import arsd.color; // no longer stand alone... :-( but i need a common type for this to work with images easily. 7594 alias Rectangle = arsd.color.Rectangle; 7595 7596 7597 /++ 7598 Keyboard press and release events. 7599 +/ 7600 struct KeyEvent { 7601 /// see table below. Always use the symbolic names, even for ASCII characters, since the actual numbers vary across platforms. See [Key] 7602 Key key; 7603 ubyte hardwareCode; /// A platform and hardware specific code for the key 7604 bool pressed; /// true if the key was just pressed, false if it was just released. note: released events aren't always sent... 7605 7606 deprecated("This never actually worked anyway, you should do a character event handler instead.") dchar character; 7607 7608 uint modifierState; /// see enum [ModifierState]. They are bitwise combined together. 7609 7610 SimpleWindow window; /// associated Window 7611 7612 /++ 7613 A view into the upcoming buffer holding coming character events that are sent if and only if neither 7614 the alt or super modifier keys are pressed (check this with `!(modifierState & (ModifierState.window | ModifierState.alt))` 7615 to predict if char events are actually coming.. 7616 7617 Only available on X systems since this information is not given ahead of time elsewhere. 7618 (Well, you COULD probably dig it up, but as far as I know right now, it isn't terribly pretty.) 7619 7620 I'm adding this because it is useful to the terminal emulator, but given its platform specificness 7621 and potential quirks I'd recommend avoiding it. 7622 7623 History: 7624 Added April 26, 2021 (dub v9.5) 7625 +/ 7626 version(X11) 7627 dchar[] charsPossible; 7628 7629 // convert key event to simplified string representation a-la emacs 7630 const(char)[] toStrBuf(bool growdest=false) (char[] dest) const nothrow @trusted { 7631 uint dpos = 0; 7632 void put (const(char)[] s...) nothrow @trusted { 7633 static if (growdest) { 7634 foreach (char ch; s) if (dpos < dest.length) dest.ptr[dpos++] = ch; else { dest ~= ch; ++dpos; } 7635 } else { 7636 foreach (char ch; s) if (dpos < dest.length) dest.ptr[dpos++] = ch; 7637 } 7638 } 7639 7640 void putMod (ModifierState mod, Key key, string text) nothrow @trusted { 7641 if ((this.modifierState&mod) != 0 && (this.pressed || this.key != key)) put(text); 7642 } 7643 7644 if (!this.key && !(this.modifierState&(ModifierState.ctrl|ModifierState.alt|ModifierState.shift|ModifierState.windows))) return null; 7645 7646 // put modifiers 7647 // releasing modifier keys can produce bizarre things like "Ctrl+Ctrl", so hack around it 7648 putMod(ModifierState.ctrl, Key.Ctrl, "Ctrl+"); 7649 putMod(ModifierState.alt, Key.Alt, "Alt+"); 7650 putMod(ModifierState.windows, Key.Shift, "Windows+"); 7651 putMod(ModifierState.shift, Key.Shift, "Shift+"); 7652 7653 if (this.key) { 7654 foreach (string kn; __traits(allMembers, Key)) { 7655 if (this.key == __traits(getMember, Key, kn)) { 7656 // HACK! 7657 static if (kn == "N0") put("0"); 7658 else static if (kn == "N1") put("1"); 7659 else static if (kn == "N2") put("2"); 7660 else static if (kn == "N3") put("3"); 7661 else static if (kn == "N4") put("4"); 7662 else static if (kn == "N5") put("5"); 7663 else static if (kn == "N6") put("6"); 7664 else static if (kn == "N7") put("7"); 7665 else static if (kn == "N8") put("8"); 7666 else static if (kn == "N9") put("9"); 7667 else put(kn); 7668 return dest[0..dpos]; 7669 } 7670 } 7671 put("Unknown"); 7672 } else { 7673 if (dpos && dest[dpos-1] == '+') --dpos; 7674 } 7675 return dest[0..dpos]; 7676 } 7677 7678 string toStr() () { return cast(string)toStrBuf!true(null); } // it is safe to cast here 7679 7680 /** Parse string into key name with modifiers. It accepts things like: 7681 * 7682 * C-H-1 -- emacs style (ctrl, and windows, and 1) 7683 * 7684 * Ctrl+Win+1 -- windows style 7685 * 7686 * Ctrl-Win-1 -- '-' is a valid delimiter too 7687 * 7688 * Ctrl Win 1 -- and space 7689 * 7690 * and even "Win + 1 + Ctrl". 7691 */ 7692 static KeyEvent parse (const(char)[] name, bool* ignoreModsOut=null, int* updown=null) nothrow @trusted @nogc { 7693 auto nanchor = name; // keep it anchored, 'cause `name` may have NO_INTERIOR set 7694 7695 // remove trailing spaces 7696 while (name.length && name[$-1] <= ' ') name = name[0..$-1]; 7697 7698 // tokens delimited by blank, '+', or '-' 7699 // null on eol 7700 const(char)[] getToken () nothrow @trusted @nogc { 7701 // remove leading spaces and delimiters 7702 while (name.length && (name[0] <= ' ' || name[0] == '+' || name[0] == '-')) name = name[1..$]; 7703 if (name.length == 0) return null; // oops, no more tokens 7704 // get token 7705 size_t epos = 0; 7706 while (epos < name.length && name[epos] > ' ' && name[epos] != '+' && name[epos] != '-') ++epos; 7707 assert(epos > 0 && epos <= name.length); 7708 auto res = name[0..epos]; 7709 name = name[epos..$]; 7710 return res; 7711 } 7712 7713 static bool strEquCI (const(char)[] s0, const(char)[] s1) pure nothrow @trusted @nogc { 7714 if (s0.length != s1.length) return false; 7715 foreach (immutable ci, char c0; s0) { 7716 if (c0 >= 'A' && c0 <= 'Z') c0 += 32; // poor man's tolower 7717 char c1 = s1[ci]; 7718 if (c1 >= 'A' && c1 <= 'Z') c1 += 32; // poor man's tolower 7719 if (c0 != c1) return false; 7720 } 7721 return true; 7722 } 7723 7724 if (ignoreModsOut !is null) *ignoreModsOut = false; 7725 if (updown !is null) *updown = -1; 7726 KeyEvent res; 7727 res.key = cast(Key)0; // just in case 7728 const(char)[] tk, tkn; // last token 7729 bool allowEmascStyle = true; 7730 bool ignoreModifiers = false; 7731 tokenloop: for (;;) { 7732 tk = tkn; 7733 tkn = getToken(); 7734 //k8: yay, i took "Bloody Mess" trait from Fallout! 7735 if (tkn.length != 0 && tk.length == 0) { tk = tkn; continue tokenloop; } 7736 if (tkn.length == 0 && tk.length == 0) break; // no more tokens 7737 if (allowEmascStyle && tkn.length != 0) { 7738 if (tk.length == 1) { 7739 char mdc = tk[0]; 7740 if (mdc >= 'a' && mdc <= 'z') mdc -= 32; // poor man's toupper() 7741 if (mdc == 'C' && (res.modifierState&ModifierState.ctrl) == 0) {res.modifierState |= ModifierState.ctrl; continue tokenloop; } 7742 if (mdc == 'M' && (res.modifierState&ModifierState.alt) == 0) { res.modifierState |= ModifierState.alt; continue tokenloop; } 7743 if (mdc == 'H' && (res.modifierState&ModifierState.windows) == 0) { res.modifierState |= ModifierState.windows; continue tokenloop; } 7744 if (mdc == 'S' && (res.modifierState&ModifierState.shift) == 0) { res.modifierState |= ModifierState.shift; continue tokenloop; } 7745 if (mdc == '*') { ignoreModifiers = true; continue tokenloop; } 7746 if (mdc == 'U' || mdc == 'R') { if (updown !is null) *updown = 0; continue tokenloop; } 7747 if (mdc == 'D' || mdc == 'P') { if (updown !is null) *updown = 1; continue tokenloop; } 7748 } 7749 } 7750 allowEmascStyle = false; 7751 if (strEquCI(tk, "Ctrl")) { res.modifierState |= ModifierState.ctrl; continue tokenloop; } 7752 if (strEquCI(tk, "Alt")) { res.modifierState |= ModifierState.alt; continue tokenloop; } 7753 if (strEquCI(tk, "Win") || strEquCI(tk, "Windows")) { res.modifierState |= ModifierState.windows; continue tokenloop; } 7754 if (strEquCI(tk, "Shift")) { res.modifierState |= ModifierState.shift; continue tokenloop; } 7755 if (strEquCI(tk, "Release")) { if (updown !is null) *updown = 0; continue tokenloop; } 7756 if (strEquCI(tk, "Press")) { if (updown !is null) *updown = 1; continue tokenloop; } 7757 if (tk == "*") { ignoreModifiers = true; continue tokenloop; } 7758 if (tk.length == 0) continue; 7759 // try key name 7760 if (res.key == 0) { 7761 // little hack 7762 if (tk.length == 1 && tk[0] >= '0' && tk[0] <= '9') { 7763 final switch (tk[0]) { 7764 case '0': tk = "N0"; break; 7765 case '1': tk = "N1"; break; 7766 case '2': tk = "N2"; break; 7767 case '3': tk = "N3"; break; 7768 case '4': tk = "N4"; break; 7769 case '5': tk = "N5"; break; 7770 case '6': tk = "N6"; break; 7771 case '7': tk = "N7"; break; 7772 case '8': tk = "N8"; break; 7773 case '9': tk = "N9"; break; 7774 } 7775 } 7776 foreach (string kn; __traits(allMembers, Key)) { 7777 if (strEquCI(tk, kn)) { res.key = __traits(getMember, Key, kn); continue tokenloop; } 7778 } 7779 } 7780 // unknown or duplicate key name, get out of here 7781 break; 7782 } 7783 if (ignoreModsOut !is null) *ignoreModsOut = ignoreModifiers; 7784 return res; // something 7785 } 7786 7787 bool opEquals() (const(char)[] name) const nothrow @trusted @nogc { 7788 enum modmask = (ModifierState.ctrl|ModifierState.alt|ModifierState.shift|ModifierState.windows); 7789 void doModKey (ref uint mask, ref Key kk, Key k, ModifierState mst) { 7790 if (kk == k) { mask |= mst; kk = cast(Key)0; } 7791 } 7792 bool ignoreMods; 7793 int updown; 7794 auto ke = KeyEvent.parse(name, &ignoreMods, &updown); 7795 if ((updown == 0 && this.pressed) || (updown == 1 && !this.pressed)) return false; 7796 if (this.key != ke.key) { 7797 // things like "ctrl+alt" are complicated 7798 uint tkm = this.modifierState&modmask; 7799 uint kkm = ke.modifierState&modmask; 7800 Key tk = this.key; 7801 // ke 7802 doModKey(kkm, ke.key, Key.Ctrl, ModifierState.ctrl); 7803 doModKey(kkm, ke.key, Key.Alt, ModifierState.alt); 7804 doModKey(kkm, ke.key, Key.Windows, ModifierState.windows); 7805 doModKey(kkm, ke.key, Key.Shift, ModifierState.shift); 7806 // this 7807 doModKey(tkm, tk, Key.Ctrl, ModifierState.ctrl); 7808 doModKey(tkm, tk, Key.Alt, ModifierState.alt); 7809 doModKey(tkm, tk, Key.Windows, ModifierState.windows); 7810 doModKey(tkm, tk, Key.Shift, ModifierState.shift); 7811 return (tk == ke.key && tkm == kkm); 7812 } 7813 return (ignoreMods || ((this.modifierState&modmask) == (ke.modifierState&modmask))); 7814 } 7815 } 7816 7817 /// Sets the application name. 7818 @property string ApplicationName(string name) { 7819 return _applicationName = name; 7820 } 7821 7822 string _applicationName; 7823 7824 /// ditto 7825 @property string ApplicationName() { 7826 if(_applicationName is null) { 7827 import core.runtime; 7828 return Runtime.args[0]; 7829 } 7830 return _applicationName; 7831 } 7832 7833 7834 /// Type of a [MouseEvent]. 7835 enum MouseEventType : int { 7836 motion = 0, /// The mouse moved inside the window 7837 buttonPressed = 1, /// A mouse button was pressed or the wheel was spun 7838 buttonReleased = 2, /// A mouse button was released 7839 } 7840 7841 // FIXME: mouse move should be distinct from presses+releases, so we can avoid subscribing to those events in X unnecessarily 7842 /++ 7843 Listen for this on your event listeners if you are interested in mouse action. 7844 7845 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. 7846 7847 Examples: 7848 7849 This will draw boxes on the window with the mouse as you hold the left button. 7850 --- 7851 import arsd.simpledisplay; 7852 7853 void main() { 7854 auto window = new SimpleWindow(); 7855 7856 window.eventLoop(0, 7857 (MouseEvent ev) { 7858 if(ev.modifierState & ModifierState.leftButtonDown) { 7859 auto painter = window.draw(); 7860 painter.fillColor = Color.red; 7861 painter.outlineColor = Color.black; 7862 painter.drawRectangle(Point(ev.x / 16 * 16, ev.y / 16 * 16), 16, 16); 7863 } 7864 } 7865 ); 7866 } 7867 --- 7868 +/ 7869 struct MouseEvent { 7870 MouseEventType type; /// movement, press, release, double click. See [MouseEventType] 7871 7872 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. 7873 int y; /// Current Y position of the cursor when the event fired. 7874 7875 int dx; /// Change in X position since last report 7876 int dy; /// Change in Y position since last report 7877 7878 MouseButton button; /// See [MouseButton] 7879 int modifierState; /// See [ModifierState] 7880 7881 version(X11) 7882 private Time timestamp; 7883 7884 /// Returns a linear representation of mouse button, 7885 /// for use with static arrays. Guaranteed to be >= 0 && <= 15 7886 /// 7887 /// Its implementation is based on range-limiting `core.bitop.bsf(button) + 1`. 7888 @property ubyte buttonLinear() const { 7889 import core.bitop; 7890 if(button == 0) 7891 return 0; 7892 return (bsf(button) + 1) & 0b1111; 7893 } 7894 7895 bool doubleClick; /// was it a double click? Only set on type == [MouseEventType.buttonPressed] 7896 7897 SimpleWindow window; /// The window in which the event happened. 7898 7899 Point globalCoordinates() { 7900 Point p; 7901 if(window is null) 7902 throw new Exception("wtf"); 7903 static if(UsingSimpledisplayX11) { 7904 Window child; 7905 XTranslateCoordinates( 7906 XDisplayConnection.get, 7907 window.impl.window, 7908 RootWindow(XDisplayConnection.get, DefaultScreen(XDisplayConnection.get)), 7909 x, y, &p.x, &p.y, &child); 7910 return p; 7911 } else version(Windows) { 7912 POINT[1] points; 7913 points[0].x = x; 7914 points[0].y = y; 7915 MapWindowPoints( 7916 window.impl.hwnd, 7917 null, 7918 points.ptr, 7919 points.length 7920 ); 7921 p.x = points[0].x; 7922 p.y = points[0].y; 7923 7924 return p; 7925 } else version(OSXCocoa) { 7926 throw new NotYetImplementedException(); 7927 } else static assert(0); 7928 } 7929 7930 bool opEquals() (const(char)[] str) pure nothrow @trusted @nogc { return equStr(this, str); } 7931 7932 /** 7933 can contain emacs-like modifier prefix 7934 case-insensitive names: 7935 lmbX/leftX 7936 rmbX/rightX 7937 mmbX/middleX 7938 wheelX 7939 motion (no prefix allowed) 7940 'X' is either "up" or "down" (or "-up"/"-down"); if omited, means "down" 7941 */ 7942 static bool equStr() (scope auto ref const MouseEvent event, const(char)[] str) pure nothrow @trusted @nogc { 7943 if (str.length == 0) return false; // just in case 7944 debug(arsd_mevent_strcmp) { import iv.cmdcon; conwriteln("str=<", str, ">"); } 7945 enum Flag : uint { Up = 0x8000_0000U, Down = 0x4000_0000U, Any = 0x1000_0000U } 7946 auto anchor = str; 7947 uint mods = 0; // uint.max == any 7948 // interesting bits in kmod 7949 uint kmodmask = 7950 ModifierState.shift| 7951 ModifierState.ctrl| 7952 ModifierState.alt| 7953 ModifierState.windows| 7954 ModifierState.leftButtonDown| 7955 ModifierState.middleButtonDown| 7956 ModifierState.rightButtonDown| 7957 0; 7958 uint lastButt = uint.max; // otherwise, bit 31 means "down" 7959 bool wasButtons = false; 7960 while (str.length) { 7961 if (str.ptr[0] <= ' ') { 7962 while (str.length && str.ptr[0] <= ' ') str = str[1..$]; 7963 continue; 7964 } 7965 // one-letter modifier? 7966 if (str.length >= 2 && str.ptr[1] == '-') { 7967 switch (str.ptr[0]) { 7968 case '*': // "any" modifier (cannot be undone) 7969 mods = mods.max; 7970 break; 7971 case 'C': case 'c': // emacs "ctrl" 7972 if (mods != mods.max) mods |= ModifierState.ctrl; 7973 break; 7974 case 'M': case 'm': // emacs "meta" 7975 if (mods != mods.max) mods |= ModifierState.alt; 7976 break; 7977 case 'S': case 's': // emacs "shift" 7978 if (mods != mods.max) mods |= ModifierState.shift; 7979 break; 7980 case 'H': case 'h': // emacs "hyper" (aka winkey) 7981 if (mods != mods.max) mods |= ModifierState.windows; 7982 break; 7983 default: 7984 return false; // unknown modifier 7985 } 7986 str = str[2..$]; 7987 continue; 7988 } 7989 // word 7990 char[16] buf = void; // locased 7991 auto wep = 0; 7992 while (str.length) { 7993 immutable char ch = str.ptr[0]; 7994 if (ch <= ' ' || ch == '-') break; 7995 str = str[1..$]; 7996 if (wep > buf.length) return false; // too long 7997 if (ch >= 'A' && ch <= 'Z') buf.ptr[wep++] = cast(char)(ch+32); // poor man tolower 7998 else if (ch >= 'a' && ch <= 'z') buf.ptr[wep++] = ch; 7999 else return false; // invalid char 8000 } 8001 if (wep == 0) return false; // just in case 8002 uint bnum; 8003 enum UpDown { None = -1, Up, Down, Any } 8004 auto updown = UpDown.None; // 0: up; 1: down 8005 switch (buf[0..wep]) { 8006 // left button 8007 case "lmbup": case "leftup": updown = UpDown.Up; goto case "lmb"; 8008 case "lmbdown": case "leftdown": updown = UpDown.Down; goto case "lmb"; 8009 case "lmbany": case "leftany": updown = UpDown.Any; goto case "lmb"; 8010 case "lmb": case "left": bnum = 0; break; 8011 // middle button 8012 case "mmbup": case "middleup": updown = UpDown.Up; goto case "mmb"; 8013 case "mmbdown": case "middledown": updown = UpDown.Down; goto case "mmb"; 8014 case "mmbany": case "middleany": updown = UpDown.Any; goto case "mmb"; 8015 case "mmb": case "middle": bnum = 1; break; 8016 // right button 8017 case "rmbup": case "rightup": updown = UpDown.Up; goto case "rmb"; 8018 case "rmbdown": case "rightdown": updown = UpDown.Down; goto case "rmb"; 8019 case "rmbany": case "rightany": updown = UpDown.Any; goto case "rmb"; 8020 case "rmb": case "right": bnum = 2; break; 8021 // wheel 8022 case "wheelup": updown = UpDown.Up; goto case "wheel"; 8023 case "wheeldown": updown = UpDown.Down; goto case "wheel"; 8024 case "wheelany": updown = UpDown.Any; goto case "wheel"; 8025 case "wheel": bnum = 3; break; 8026 // motion 8027 case "motion": bnum = 7; break; 8028 // unknown 8029 default: return false; 8030 } 8031 debug(arsd_mevent_strcmp) { import iv.cmdcon; conprintfln(" 0: mods=0x%08x; bnum=%u; updown=%s [%s]", mods, bnum, updown, str); } 8032 // parse possible "-up" or "-down" 8033 if (updown == UpDown.None && bnum < 7 && str.length > 0 && str.ptr[0] == '-') { 8034 wep = 0; 8035 foreach (immutable idx, immutable char ch; str[1..$]) { 8036 if (ch <= ' ' || ch == '-') break; 8037 assert(idx == wep); // for now; trick 8038 if (wep > buf.length) { wep = 0; break; } // too long 8039 if (ch >= 'A' && ch <= 'Z') buf.ptr[wep++] = cast(char)(ch+32); // poor man tolower 8040 else if (ch >= 'a' && ch <= 'z') buf.ptr[wep++] = ch; 8041 else { wep = 0; break; } // invalid char 8042 } 8043 if (wep == 2 && buf[0..wep] == "up") updown = UpDown.Up; 8044 else if (wep == 4 && buf[0..wep] == "down") updown = UpDown.Down; 8045 else if (wep == 3 && buf[0..wep] == "any") updown = UpDown.Any; 8046 // remove parsed part 8047 if (updown != UpDown.None) str = str[wep+1..$]; 8048 } 8049 if (updown == UpDown.None) { 8050 updown = UpDown.Down; 8051 } 8052 wasButtons = wasButtons || (bnum <= 2); 8053 //assert(updown != UpDown.None); 8054 debug(arsd_mevent_strcmp) { import iv.cmdcon; conprintfln(" 1: mods=0x%08x; bnum=%u; updown=%s [%s]", mods, bnum, updown, str); } 8055 // if we have a previous button, it goes to modifiers (unless it is a wheel or motion) 8056 if (lastButt != lastButt.max) { 8057 if ((lastButt&0xff) >= 3) return false; // wheel or motion 8058 if (mods != mods.max) { 8059 uint butbit = 0; 8060 final switch (lastButt&0x03) { 8061 case 0: butbit = ModifierState.leftButtonDown; break; 8062 case 1: butbit = ModifierState.middleButtonDown; break; 8063 case 2: butbit = ModifierState.rightButtonDown; break; 8064 } 8065 if (lastButt&Flag.Down) mods |= butbit; 8066 else if (lastButt&Flag.Up) mods &= ~butbit; 8067 else if (lastButt&Flag.Any) kmodmask &= ~butbit; 8068 } 8069 } 8070 // remember last button 8071 lastButt = bnum|(updown == UpDown.Up ? Flag.Up : updown == UpDown.Any ? Flag.Any : Flag.Down); 8072 } 8073 // no button -- nothing to do 8074 if (lastButt == lastButt.max) return false; 8075 // done parsing, check if something's left 8076 foreach (immutable char ch; str) if (ch > ' ') return false; // oops 8077 // remove action button from mask 8078 if ((lastButt&0xff) < 3) { 8079 final switch (lastButt&0x03) { 8080 case 0: kmodmask &= ~cast(uint)ModifierState.leftButtonDown; break; 8081 case 1: kmodmask &= ~cast(uint)ModifierState.middleButtonDown; break; 8082 case 2: kmodmask &= ~cast(uint)ModifierState.rightButtonDown; break; 8083 } 8084 } 8085 // special case: "Motion" means "ignore buttons" 8086 if ((lastButt&0xff) == 7 && !wasButtons) { 8087 debug(arsd_mevent_strcmp) { import iv.cmdcon; conwriteln(" *: special motion"); } 8088 kmodmask &= ~cast(uint)(ModifierState.leftButtonDown|ModifierState.middleButtonDown|ModifierState.rightButtonDown); 8089 } 8090 uint kmod = event.modifierState&kmodmask; 8091 debug(arsd_mevent_strcmp) { import iv.cmdcon; conprintfln(" *: mods=0x%08x; lastButt=0x%08x; kmod=0x%08x; type=%s", mods, lastButt, kmod, event.type); } 8092 // check modifier state 8093 if (mods != mods.max) { 8094 if (kmod != mods) return false; 8095 } 8096 // now check type 8097 if ((lastButt&0xff) == 7) { 8098 // motion 8099 if (event.type != MouseEventType.motion) return false; 8100 } else if ((lastButt&0xff) == 3) { 8101 // wheel 8102 if (lastButt&Flag.Up) return (event.type == MouseEventType.buttonPressed && event.button == MouseButton.wheelUp); 8103 if (lastButt&Flag.Down) return (event.type == MouseEventType.buttonPressed && event.button == MouseButton.wheelDown); 8104 if (lastButt&Flag.Any) return (event.type == MouseEventType.buttonPressed && (event.button == MouseButton.wheelUp || event.button == MouseButton.wheelUp)); 8105 return false; 8106 } else { 8107 // buttons 8108 if (((lastButt&Flag.Down) != 0 && event.type != MouseEventType.buttonPressed) || 8109 ((lastButt&Flag.Up) != 0 && event.type != MouseEventType.buttonReleased)) 8110 { 8111 return false; 8112 } 8113 // button number 8114 switch (lastButt&0x03) { 8115 case 0: if (event.button != MouseButton.left) return false; break; 8116 case 1: if (event.button != MouseButton.middle) return false; break; 8117 case 2: if (event.button != MouseButton.right) return false; break; 8118 default: return false; 8119 } 8120 } 8121 return true; 8122 } 8123 } 8124 8125 version(arsd_mevent_strcmp_test) unittest { 8126 MouseEvent event; 8127 event.type = MouseEventType.buttonPressed; 8128 event.button = MouseButton.left; 8129 event.modifierState = ModifierState.ctrl; 8130 assert(event == "C-LMB"); 8131 assert(event != "C-LMBUP"); 8132 assert(event != "C-LMB-UP"); 8133 assert(event != "C-S-LMB"); 8134 assert(event == "*-LMB"); 8135 assert(event != "*-LMB-UP"); 8136 8137 event.type = MouseEventType.buttonReleased; 8138 assert(event != "C-LMB"); 8139 assert(event == "C-LMBUP"); 8140 assert(event == "C-LMB-UP"); 8141 assert(event != "C-S-LMB"); 8142 assert(event != "*-LMB"); 8143 assert(event == "*-LMB-UP"); 8144 8145 event.button = MouseButton.right; 8146 event.modifierState |= ModifierState.shift; 8147 event.type = MouseEventType.buttonPressed; 8148 assert(event != "C-LMB"); 8149 assert(event != "C-LMBUP"); 8150 assert(event != "C-LMB-UP"); 8151 assert(event != "C-S-LMB"); 8152 assert(event != "*-LMB"); 8153 assert(event != "*-LMB-UP"); 8154 8155 assert(event != "C-RMB"); 8156 assert(event != "C-RMBUP"); 8157 assert(event != "C-RMB-UP"); 8158 assert(event == "C-S-RMB"); 8159 assert(event == "*-RMB"); 8160 assert(event != "*-RMB-UP"); 8161 } 8162 8163 /// This gives a few more options to drawing lines and such 8164 struct Pen { 8165 Color color; /// the foreground color 8166 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. 8167 Style style; /// See [Style] 8168 /+ 8169 // From X.h 8170 8171 #define LineSolid 0 8172 #define LineOnOffDash 1 8173 #define LineDoubleDash 2 8174 LineDou- The full path of the line is drawn, but the 8175 bleDash even dashes are filled differently from the 8176 odd dashes (see fill-style) with CapButt 8177 style used where even and odd dashes meet. 8178 8179 8180 8181 /* capStyle */ 8182 8183 #define CapNotLast 0 8184 #define CapButt 1 8185 #define CapRound 2 8186 #define CapProjecting 3 8187 8188 /* joinStyle */ 8189 8190 #define JoinMiter 0 8191 #define JoinRound 1 8192 #define JoinBevel 2 8193 8194 /* fillStyle */ 8195 8196 #define FillSolid 0 8197 #define FillTiled 1 8198 #define FillStippled 2 8199 #define FillOpaqueStippled 3 8200 8201 8202 +/ 8203 /// Style of lines drawn 8204 enum Style { 8205 Solid, /// a solid line 8206 Dashed, /// a dashed line 8207 Dotted, /// a dotted line 8208 } 8209 } 8210 8211 8212 /++ 8213 Represents an in-memory image in the format that the GUI expects, but with its raw data available to your program. 8214 8215 8216 On Windows, this means a device-independent bitmap. On X11, it is an XImage. 8217 8218 $(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.) 8219 8220 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. 8221 8222 If you intend to draw an image to screen several times, you will want to convert it into a [Sprite]. 8223 8224 $(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. 8225 8226 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! 8227 8228 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!) 8229 8230 Please call `destroy(image);` when you are done with it. The easiest way to do this is with scope: 8231 8232 --- 8233 auto image = new Image(256, 256); 8234 scope(exit) destroy(image); 8235 --- 8236 8237 As long as you don't hold on to it outside the scope. 8238 8239 I might change it to be an owned pointer at some point in the future. 8240 8241 ) 8242 8243 Drawing pixels on the image may be simple, using the `opIndexAssign` function, but 8244 you can also often get a fair amount of speedup by getting the raw data format and 8245 writing some custom code. 8246 8247 FIXME INSERT EXAMPLES HERE 8248 8249 8250 +/ 8251 final class Image { 8252 /// 8253 this(int width, int height, bool forcexshm=false, bool enableAlpha = false) { 8254 this.width = width; 8255 this.height = height; 8256 this.enableAlpha = enableAlpha; 8257 8258 impl.createImage(width, height, forcexshm, enableAlpha); 8259 } 8260 8261 /// 8262 this(Size size, bool forcexshm=false, bool enableAlpha = false) { 8263 this(size.width, size.height, forcexshm, enableAlpha); 8264 } 8265 8266 private bool suppressDestruction; 8267 8268 version(X11) 8269 this(XImage* handle) { 8270 this.handle = handle; 8271 this.rawData = cast(ubyte*) handle.data; 8272 this.width = handle.width; 8273 this.height = handle.height; 8274 this.enableAlpha = handle.depth == 32; 8275 suppressDestruction = true; 8276 } 8277 8278 ~this() { 8279 if(suppressDestruction) return; 8280 impl.dispose(); 8281 } 8282 8283 // these numbers are used for working with rawData itself, skipping putPixel and getPixel 8284 /// if you do the math yourself you might be able to optimize it. Call these functions only once and cache the value. 8285 pure const @system nothrow { 8286 /* 8287 To use these to draw a blue rectangle with size WxH at position X,Y... 8288 8289 // make certain that it will fit before we proceed 8290 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! 8291 8292 // gather all the values you'll need up front. These can be kept until the image changes size if you want 8293 // (though calculating them isn't really that expensive). 8294 auto nextLineAdjustment = img.adjustmentForNextLine(); 8295 auto offR = img.redByteOffset(); 8296 auto offB = img.blueByteOffset(); 8297 auto offG = img.greenByteOffset(); 8298 auto bpp = img.bytesPerPixel(); 8299 8300 auto data = img.getDataPointer(); 8301 8302 // figure out the starting byte offset 8303 auto offset = img.offsetForTopLeftPixel() + nextLineAdjustment*Y + bpp * X; 8304 8305 auto startOfLine = data + offset; // get our pointer lined up on the first pixel 8306 8307 // and now our drawing loop for the rectangle 8308 foreach(y; 0 .. H) { 8309 auto data = startOfLine; // we keep the start of line separately so moving to the next line is simple and portable 8310 foreach(x; 0 .. W) { 8311 // write our color 8312 data[offR] = 0; 8313 data[offG] = 0; 8314 data[offB] = 255; 8315 8316 data += bpp; // moving to the next pixel is just an addition... 8317 } 8318 startOfLine += nextLineAdjustment; 8319 } 8320 8321 8322 As you can see, the loop itself was very simple thanks to the calculations being moved outside. 8323 8324 FIXME: I wonder if I can make the pixel formats consistently 32 bit across platforms, so the color offsets 8325 can be made into a bitmask or something so we can write them as *uint... 8326 */ 8327 8328 /// 8329 int offsetForTopLeftPixel() { 8330 version(X11) { 8331 return 0; 8332 } else version(Windows) { 8333 if(enableAlpha) { 8334 return (width * 4) * (height - 1); 8335 } else { 8336 return (((cast(int) width * 3 + 3) / 4) * 4) * (height - 1); 8337 } 8338 } else version(OSXCocoa) { 8339 return 0 ; //throw new NotYetImplementedException(); 8340 } else static assert(0, "fill in this info for other OSes"); 8341 } 8342 8343 /// 8344 int offsetForPixel(int x, int y) { 8345 version(X11) { 8346 auto offset = (y * width + x) * 4; 8347 return offset; 8348 } else version(Windows) { 8349 if(enableAlpha) { 8350 auto itemsPerLine = width * 4; 8351 // remember, bmps are upside down 8352 auto offset = itemsPerLine * (height - y - 1) + x * 4; 8353 return offset; 8354 } else { 8355 auto itemsPerLine = ((cast(int) width * 3 + 3) / 4) * 4; 8356 // remember, bmps are upside down 8357 auto offset = itemsPerLine * (height - y - 1) + x * 3; 8358 return offset; 8359 } 8360 } else version(OSXCocoa) { 8361 return 0 ; //throw new NotYetImplementedException(); 8362 } else static assert(0, "fill in this info for other OSes"); 8363 } 8364 8365 /// 8366 int adjustmentForNextLine() { 8367 version(X11) { 8368 return width * 4; 8369 } else version(Windows) { 8370 // windows bmps are upside down, so the adjustment is actually negative 8371 if(enableAlpha) 8372 return - (cast(int) width * 4); 8373 else 8374 return -((cast(int) width * 3 + 3) / 4) * 4; 8375 } else version(OSXCocoa) { 8376 return 0 ; //throw new NotYetImplementedException(); 8377 } else static assert(0, "fill in this info for other OSes"); 8378 } 8379 8380 /// once you have the position of a pixel, use these to get to the proper color 8381 int redByteOffset() { 8382 version(X11) { 8383 return 2; 8384 } else version(Windows) { 8385 return 2; 8386 } else version(OSXCocoa) { 8387 return 0 ; //throw new NotYetImplementedException(); 8388 } else static assert(0, "fill in this info for other OSes"); 8389 } 8390 8391 /// 8392 int greenByteOffset() { 8393 version(X11) { 8394 return 1; 8395 } else version(Windows) { 8396 return 1; 8397 } else version(OSXCocoa) { 8398 return 0 ; //throw new NotYetImplementedException(); 8399 } else static assert(0, "fill in this info for other OSes"); 8400 } 8401 8402 /// 8403 int blueByteOffset() { 8404 version(X11) { 8405 return 0; 8406 } else version(Windows) { 8407 return 0; 8408 } else version(OSXCocoa) { 8409 return 0 ; //throw new NotYetImplementedException(); 8410 } else static assert(0, "fill in this info for other OSes"); 8411 } 8412 8413 /// Only valid if [enableAlpha] is true 8414 int alphaByteOffset() { 8415 version(X11) { 8416 return 3; 8417 } else version(Windows) { 8418 return 3; 8419 } else version(OSXCocoa) { 8420 return 3; //throw new NotYetImplementedException(); 8421 } else static assert(0, "fill in this info for other OSes"); 8422 } 8423 } 8424 8425 /// 8426 final void putPixel(int x, int y, Color c) { 8427 if(x < 0 || x >= width) 8428 return; 8429 if(y < 0 || y >= height) 8430 return; 8431 8432 impl.setPixel(x, y, c); 8433 } 8434 8435 /// 8436 final Color getPixel(int x, int y) { 8437 if(x < 0 || x >= width) 8438 return Color.transparent; 8439 if(y < 0 || y >= height) 8440 return Color.transparent; 8441 8442 version(OSXCocoa) throw new NotYetImplementedException(); else 8443 return impl.getPixel(x, y); 8444 } 8445 8446 /// 8447 final void opIndexAssign(Color c, int x, int y) { 8448 putPixel(x, y, c); 8449 } 8450 8451 /// 8452 TrueColorImage toTrueColorImage() { 8453 auto tci = new TrueColorImage(width, height); 8454 convertToRgbaBytes(tci.imageData.bytes); 8455 return tci; 8456 } 8457 8458 /// 8459 static Image fromMemoryImage(MemoryImage i, bool enableAlpha = false, bool premultiply = true) { 8460 auto tci = i.getAsTrueColorImage(); 8461 auto img = new Image(tci.width, tci.height, false, enableAlpha); 8462 static if(UsingSimpledisplayX11) 8463 img.premultiply = premultiply; 8464 img.setRgbaBytes(tci.imageData.bytes); 8465 return img; 8466 } 8467 8468 /// this is here for interop with arsd.image. where can be a TrueColorImage's data member 8469 /// if you pass in a buffer, it will put it right there. length must be width*height*4 already 8470 /// if you pass null, it will allocate a new one. 8471 ubyte[] getRgbaBytes(ubyte[] where = null) { 8472 if(where is null) 8473 where = new ubyte[this.width*this.height*4]; 8474 convertToRgbaBytes(where); 8475 return where; 8476 } 8477 8478 /// this is here for interop with arsd.image. from can be a TrueColorImage's data member 8479 void setRgbaBytes(in ubyte[] from ) { 8480 assert(from.length == this.width * this.height * 4); 8481 setFromRgbaBytes(from); 8482 } 8483 8484 // FIXME: make properly cross platform by getting rgba right 8485 8486 /// warning: this is not portable across platforms because the data format can change 8487 ubyte* getDataPointer() { 8488 return impl.rawData; 8489 } 8490 8491 /// for use with getDataPointer 8492 final int bytesPerLine() const pure @safe nothrow { 8493 version(Windows) 8494 return enableAlpha ? (width * 4) : (((cast(int) width * 3 + 3) / 4) * 4); 8495 else version(X11) 8496 return 4 * width; 8497 else version(OSXCocoa) 8498 return 4 * width; 8499 else static assert(0); 8500 } 8501 8502 /// for use with getDataPointer 8503 final int bytesPerPixel() const pure @safe nothrow { 8504 version(Windows) 8505 return enableAlpha ? 4 : 3; 8506 else version(X11) 8507 return 4; 8508 else version(OSXCocoa) 8509 return 4; 8510 else static assert(0); 8511 } 8512 8513 /// 8514 immutable int width; 8515 8516 /// 8517 immutable int height; 8518 8519 /// 8520 immutable bool enableAlpha; 8521 //private: 8522 mixin NativeImageImplementation!() impl; 8523 } 8524 8525 /++ 8526 A convenience function to pop up a window displaying the image. 8527 If you pass a win, it will draw the image in it. Otherwise, it will 8528 create a window with the size of the image and run its event loop, closing 8529 when a key is pressed. 8530 8531 History: 8532 `BlockingMode` parameter added on December 8, 2021. Previously, it would 8533 always block until the application quit which could cause bizarre behavior 8534 inside a more complex application. Now, the default is to block until 8535 this window closes if it is the only event loop running, and otherwise, 8536 not to block at all and just pop up the display window asynchronously. 8537 +/ 8538 void displayImage(Image image, SimpleWindow win = null, BlockingMode bm = BlockingMode.untilWindowCloses | BlockingMode.onlyIfNotNested) { 8539 if(win is null) { 8540 win = new SimpleWindow(image); 8541 { 8542 auto p = win.draw; 8543 p.drawImage(Point(0, 0), image); 8544 } 8545 win.eventLoopWithBlockingMode( 8546 bm, 0, 8547 (KeyEvent ev) { 8548 if (ev.pressed && (ev.key == Key.Escape || ev.key == Key.Space)) win.close(); 8549 } ); 8550 } else { 8551 win.image = image; 8552 } 8553 } 8554 8555 enum FontWeight : int { 8556 dontcare = 0, 8557 thin = 100, 8558 extralight = 200, 8559 light = 300, 8560 regular = 400, 8561 medium = 500, 8562 semibold = 600, 8563 bold = 700, 8564 extrabold = 800, 8565 heavy = 900 8566 } 8567 8568 /++ 8569 Interface with the common functionality for font measurements between [OperatingSystemFont] and [DrawableFont]. 8570 8571 History: 8572 Added October 24, 2022. The methods were already on [OperatingSystemFont] before that. 8573 +/ 8574 interface MeasurableFont { 8575 /++ 8576 Returns true if it is a monospace font, meaning each of the 8577 glyphs (at least the ascii characters) have matching width 8578 and no kerning, so you can determine the display width of some 8579 strings by simply multiplying the string width by [averageWidth]. 8580 8581 (Please note that multiply doesn't $(I actually) work in general, 8582 consider characters like tab and newline, but it does sometimes.) 8583 +/ 8584 bool isMonospace(); 8585 8586 /++ 8587 The average width of glyphs in the font, traditionally equal to the 8588 width of the lowercase x. Can be used to estimate bounding boxes, 8589 especially if the font [isMonospace]. 8590 8591 Given in pixels. 8592 +/ 8593 int averageWidth(); 8594 /++ 8595 The height of the bounding box of a line. 8596 +/ 8597 int height(); 8598 /++ 8599 The maximum ascent of a glyph above the baseline. 8600 8601 Given in pixels. 8602 +/ 8603 int ascent(); 8604 /++ 8605 The maximum descent of a glyph below the baseline. For example, how low the g might go. 8606 8607 Given in pixels. 8608 +/ 8609 int descent(); 8610 /++ 8611 The display width of the given string, and if you provide a window, it will use it to 8612 make the pixel count on screen more accurate too, but this shouldn't generally be necessary. 8613 8614 Given in pixels. 8615 +/ 8616 int stringWidth(scope const(char)[] s, SimpleWindow window = null); 8617 8618 } 8619 8620 // FIXME: i need a font cache and it needs to handle disconnects. 8621 8622 /++ 8623 Represents a font loaded off the operating system or the X server. 8624 8625 8626 While the api here is unified cross platform, the fonts are not necessarily 8627 available, even across machines of the same platform, so be sure to always check 8628 for null (using [isNull]) and have a fallback plan. 8629 8630 When you have a font you like, use [ScreenPainter.setFont] to load it for drawing. 8631 8632 Worst case, a null font will automatically fall back to the default font loaded 8633 for your system. 8634 +/ 8635 class OperatingSystemFont : MeasurableFont { 8636 // FIXME: when the X Connection is lost, these need to be invalidated! 8637 // that means I need to store the original stuff again to reconstruct it too. 8638 8639 version(X11) { 8640 XFontStruct* font; 8641 XFontSet fontset; 8642 8643 version(with_xft) { 8644 XftFont* xftFont; 8645 bool isXft; 8646 } 8647 } else version(Windows) { 8648 HFONT font; 8649 int width_; 8650 int height_; 8651 } else version(OSXCocoa) { 8652 NSFont font; 8653 } else static assert(0); 8654 8655 /++ 8656 Constructs the class and immediately calls [load]. 8657 +/ 8658 this(string name, int size = 0, FontWeight weight = FontWeight.dontcare, bool italic = false) { 8659 load(name, size, weight, italic); 8660 } 8661 8662 /++ 8663 Constructs the object, but does nothing. Call one of [load] or [loadDefault] to populate the object. 8664 8665 You can also call the platform-specific [loadXft], [loadCoreX], or [loadWin32] functions if appropriate for you. 8666 8667 History: 8668 Added January 24, 2021. 8669 +/ 8670 this() { 8671 // this space intentionally left blank 8672 } 8673 8674 /++ 8675 Constructs a copy of the given font object. 8676 8677 History: 8678 Added January 7, 2023. 8679 +/ 8680 this(OperatingSystemFont font) { 8681 if(font is null || font.loadedInfo is LoadedInfo.init) 8682 loadDefault(); 8683 else 8684 load(font.loadedInfo.tupleof); 8685 } 8686 8687 /++ 8688 Loads specifically with the Xft library - a freetype font from a fontconfig string. 8689 8690 History: 8691 Added November 13, 2020. 8692 +/ 8693 version(with_xft) 8694 bool loadXft(string name, int size = 0, FontWeight weight = FontWeight.dontcare, bool italic = false) { 8695 unload(); 8696 8697 if(!XftLibrary.attempted) { 8698 XftLibrary.loadDynamicLibrary(); 8699 } 8700 8701 if(!XftLibrary.loadSuccessful) 8702 return false; 8703 8704 auto display = XDisplayConnection.get; 8705 8706 char[256] nameBuffer = void; 8707 int nbp = 0; 8708 8709 void add(in char[] a) { 8710 nameBuffer[nbp .. nbp + a.length] = a[]; 8711 nbp += a.length; 8712 } 8713 add(name); 8714 8715 if(size) { 8716 add(":size="); 8717 add(toInternal!string(size)); 8718 } 8719 if(weight != FontWeight.dontcare && weight != 400) { 8720 if(weight < 400) 8721 add(":style=Light"); 8722 else 8723 add(":style=Bold"); 8724 add(":weight="); 8725 add(weightToString(weight)); 8726 } 8727 if(italic) { 8728 if(weight == FontWeight.dontcare) 8729 add(":style=Italic"); 8730 add(":slant=100"); 8731 } 8732 8733 nameBuffer[nbp] = 0; 8734 8735 this.xftFont = XftFontOpenName( 8736 display, 8737 DefaultScreen(display), 8738 nameBuffer.ptr 8739 ); 8740 8741 this.isXft = true; 8742 8743 if(xftFont !is null) { 8744 isMonospace_ = stringWidth("x") == stringWidth("M"); 8745 ascent_ = xftFont.ascent; 8746 descent_ = xftFont.descent; 8747 } 8748 8749 return !isNull(); 8750 } 8751 8752 /++ 8753 Lists available fonts from the system that match the given pattern, finding names that are suitable for passing to [OperatingSystemFont]'s constructor. 8754 8755 8756 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. 8757 8758 If `pattern` is null, it returns all available font families. 8759 8760 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. 8761 8762 The format of the pattern is platform-specific. 8763 8764 History: 8765 Added May 1, 2021 (dub v9.5) 8766 +/ 8767 static void listFonts(string pattern, bool delegate(in char[] name) handler) { 8768 version(Windows) { 8769 auto hdc = GetDC(null); 8770 scope(exit) ReleaseDC(null, hdc); 8771 LOGFONT logfont; 8772 static extern(Windows) int proc(const LOGFONT* lf, const TEXTMETRIC* tm, DWORD type, LPARAM p) { 8773 auto localHandler = *(cast(typeof(handler)*) p); 8774 return localHandler(lf.lfFaceName[].sliceCString) ? 1 : 0; 8775 } 8776 EnumFontFamiliesEx(hdc, &logfont, &proc, cast(LPARAM) &handler, 0); 8777 } else version(X11) { 8778 //import core.stdc.stdio; 8779 bool done = false; 8780 version(with_xft) { 8781 if(!XftLibrary.attempted) { 8782 XftLibrary.loadDynamicLibrary(); 8783 } 8784 8785 if(!XftLibrary.loadSuccessful) 8786 goto skipXft; 8787 8788 if(!FontConfigLibrary.attempted) 8789 FontConfigLibrary.loadDynamicLibrary(); 8790 if(!FontConfigLibrary.loadSuccessful) 8791 goto skipXft; 8792 8793 { 8794 auto got = XftListFonts(XDisplayConnection.get, 0, null, "family".ptr, "style".ptr, null); 8795 if(got is null) 8796 goto skipXft; 8797 scope(exit) FcFontSetDestroy(got); 8798 8799 auto fontPatterns = got.fonts[0 .. got.nfont]; 8800 foreach(candidate; fontPatterns) { 8801 char* where, whereStyle; 8802 8803 char* pmg = FcNameUnparse(candidate); 8804 8805 //FcPatternGetString(candidate, "family", 0, &where); 8806 //FcPatternGetString(candidate, "style", 0, &whereStyle); 8807 //if(where && whereStyle) { 8808 if(pmg) { 8809 if(!handler(pmg.sliceCString)) 8810 return; 8811 //printf("%s || %s %s\n", pmg, where, whereStyle); 8812 } 8813 } 8814 } 8815 } 8816 8817 skipXft: 8818 8819 if(pattern is null) 8820 pattern = "*"; 8821 8822 int count; 8823 auto coreFontsRaw = XListFonts(XDisplayConnection.get, pattern.toStringz, 10000 /* max return */, &count); 8824 scope(exit) XFreeFontNames(coreFontsRaw); 8825 8826 auto coreFonts = coreFontsRaw[0 .. count]; 8827 8828 foreach(font; coreFonts) { 8829 char[128] tmp; 8830 tmp[0 ..5] = "core:"; 8831 auto cf = font.sliceCString; 8832 if(5 + cf.length > tmp.length) 8833 assert(0, "a font name was too long, sorry i didn't bother implementing a fallback"); 8834 tmp[5 .. 5 + cf.length] = cf; 8835 if(!handler(tmp[0 .. 5 + cf.length])) 8836 return; 8837 } 8838 } 8839 } 8840 8841 /++ 8842 Returns the raw content of the ttf file, if possible. This allows you to use OperatingSystemFont 8843 to look up fonts that you then pass to things like [arsd.ttf.OpenGlLimitedFont] or [arsd.nanovega]. 8844 8845 Returns null if impossible. It is impossible if the loaded font is not a local TTF file or if the 8846 underlying system doesn't support returning the raw bytes. 8847 8848 History: 8849 Added September 10, 2021 (dub v10.3) 8850 +/ 8851 ubyte[] getTtfBytes() { 8852 if(isNull) 8853 return null; 8854 8855 version(Windows) { 8856 auto dc = GetDC(null); 8857 auto orig = SelectObject(dc, font); 8858 8859 scope(exit) { 8860 SelectObject(dc, orig); 8861 ReleaseDC(null, dc); 8862 } 8863 8864 auto res = GetFontData(dc, 0 /* whole file */, 0 /* offset */, null, 0); 8865 if(res == GDI_ERROR) 8866 return null; 8867 8868 ubyte[] buffer = new ubyte[](res); 8869 res = GetFontData(dc, 0 /* whole file */, 0 /* offset */, buffer.ptr, cast(DWORD) buffer.length); 8870 if(res == GDI_ERROR) 8871 return null; // wtf really tbh 8872 8873 return buffer; 8874 } else version(with_xft) { 8875 if(isXft && xftFont) { 8876 if(!FontConfigLibrary.attempted) 8877 FontConfigLibrary.loadDynamicLibrary(); 8878 if(!FontConfigLibrary.loadSuccessful) 8879 return null; 8880 8881 char* file; 8882 if (FcPatternGetString(xftFont.pattern, "file", 0, &file) == 0 /*FcResultMatch*/) { 8883 if (file !is null && file[0]) { 8884 import core.stdc.stdio; 8885 auto fp = fopen(file, "rb"); 8886 if(fp is null) 8887 return null; 8888 scope(exit) 8889 fclose(fp); 8890 fseek(fp, 0, SEEK_END); 8891 ubyte[] buffer = new ubyte[](ftell(fp)); 8892 fseek(fp, 0, SEEK_SET); 8893 8894 auto got = fread(buffer.ptr, 1, buffer.length, fp); 8895 if(got != buffer.length) 8896 return null; 8897 8898 return buffer; 8899 } 8900 } 8901 } 8902 return null; 8903 } else throw new NotYetImplementedException(); 8904 } 8905 8906 // see also: XftLockFace(font) which gives a FT_Face. from /usr/include/X11/Xft/Xft.h line 352 8907 8908 private string weightToString(FontWeight weight) { 8909 with(FontWeight) 8910 final switch(weight) { 8911 case dontcare: return "*"; 8912 case thin: return "extralight"; 8913 case extralight: return "extralight"; 8914 case light: return "light"; 8915 case regular: return "regular"; 8916 case medium: return "medium"; 8917 case semibold: return "demibold"; 8918 case bold: return "bold"; 8919 case extrabold: return "demibold"; 8920 case heavy: return "black"; 8921 } 8922 } 8923 8924 /++ 8925 Loads specifically a Core X font - rendered on the X server without antialiasing. Best performance. 8926 8927 History: 8928 Added November 13, 2020. Before then, this code was integrated in the [load] function. 8929 +/ 8930 version(X11) 8931 bool loadCoreX(string name, int size = 0, FontWeight weight = FontWeight.dontcare, bool italic = false) { 8932 unload(); 8933 8934 string xfontstr; 8935 8936 if(name.length > 3 && name[0 .. 3] == "-*-") { 8937 // this is kinda a disgusting hack but if the user sends an exact 8938 // string I'd like to honor it... 8939 xfontstr = name; 8940 } else { 8941 string weightstr = weightToString(weight); 8942 string sizestr; 8943 if(size == 0) 8944 sizestr = "*"; 8945 else 8946 sizestr = toInternal!string(size); 8947 xfontstr = "-*-"~name~"-"~weightstr~"-"~(italic ? "i" : "r")~"-*-*-"~sizestr~"-*-*-*-*-*-*-*\0"; 8948 } 8949 8950 // writeln(xfontstr); 8951 8952 auto display = XDisplayConnection.get; 8953 8954 font = XLoadQueryFont(display, xfontstr.ptr); 8955 if(font is null) 8956 return false; 8957 8958 char** lol; 8959 int lol2; 8960 char* lol3; 8961 fontset = XCreateFontSet(display, xfontstr.ptr, &lol, &lol2, &lol3); 8962 8963 prepareFontInfo(); 8964 8965 return !isNull(); 8966 } 8967 8968 version(X11) 8969 private void prepareFontInfo() { 8970 if(font !is null) { 8971 isMonospace_ = stringWidth("l") == stringWidth("M"); 8972 ascent_ = font.max_bounds.ascent; 8973 descent_ = font.max_bounds.descent; 8974 } 8975 } 8976 8977 version(OSXCocoa) 8978 private void prepareFontInfo() { 8979 if(font !is null) { 8980 isMonospace_ = font.isFixedPitch; 8981 ascent_ = cast(int) font.ascender; 8982 descent_ = cast(int) - font.descender; 8983 } 8984 } 8985 8986 8987 /++ 8988 Loads a Windows font. You probably want to use [load] instead to be more generic. 8989 8990 History: 8991 Added November 13, 2020. Before then, this code was integrated in the [load] function. 8992 +/ 8993 version(Windows) 8994 bool loadWin32(string name, int size = 0, FontWeight weight = FontWeight.dontcare, bool italic = false, HDC hdc = null) { 8995 unload(); 8996 8997 WCharzBuffer buffer = WCharzBuffer(name); 8998 font = CreateFont(size, 0, 0, 0, cast(int) weight, italic, 0, 0, 0, 0, 0, 0, 0, buffer.ptr); 8999 9000 prepareFontInfo(hdc); 9001 9002 return !isNull(); 9003 } 9004 9005 version(Windows) 9006 void prepareFontInfo(HDC hdc = null) { 9007 if(font is null) 9008 return; 9009 9010 TEXTMETRIC tm; 9011 auto dc = hdc ? hdc : GetDC(null); 9012 auto orig = SelectObject(dc, font); 9013 GetTextMetrics(dc, &tm); 9014 SelectObject(dc, orig); 9015 if(hdc is null) 9016 ReleaseDC(null, dc); 9017 9018 width_ = tm.tmAveCharWidth; 9019 height_ = tm.tmHeight; 9020 ascent_ = tm.tmAscent; 9021 descent_ = tm.tmDescent; 9022 // 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. 9023 isMonospace_ = (tm.tmPitchAndFamily & TMPF_FIXED_PITCH) == 0; 9024 } 9025 9026 9027 /++ 9028 `name` is a font name, but it can also be a more complicated string parsed in an OS-specific way. 9029 9030 On X, you may prefix a name with `core:` to bypass the freetype engine causing this function to forward to [loadCoreX]. Otherwise, 9031 it calls [loadXft] if the library is available. If the library or font is not available on Xft, it falls back on [loadCoreX]. 9032 9033 On Windows, it forwards directly to [loadWin32]. 9034 9035 Params: 9036 name = font name. This is looked up by the operating system and may be interpreted differently across platforms or user machines and their preferences. 9037 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. 9038 weight = approximate boldness, results may vary. 9039 italic = try to get a slanted version of the given font. 9040 9041 History: 9042 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. 9043 +/ 9044 bool load(string name, int size = 0, FontWeight weight = FontWeight.dontcare, bool italic = false) { 9045 this.loadedInfo = LoadedInfo(name, size, weight, italic); 9046 version(X11) { 9047 version(with_xft) { 9048 if(name.length > 5 && name[0 .. 5] == "core:") { 9049 goto core; 9050 } 9051 9052 if(loadXft(name, size, weight, italic)) 9053 return true; 9054 // if xft fails, fallback to core to avoid breaking 9055 // code that already depended on this. 9056 } 9057 9058 core: 9059 9060 if(name.length > 5 && name[0 .. 5] == "core:") { 9061 name = name[5 .. $]; 9062 } 9063 9064 return loadCoreX(name, size, weight, italic); 9065 } else version(Windows) { 9066 return loadWin32(name, size, weight, italic); 9067 } else version(OSXCocoa) { 9068 return loadCocoa(name, size, weight, italic); 9069 } else static assert(0); 9070 } 9071 9072 version(OSXCocoa) 9073 bool loadCocoa(string name, int size, FontWeight weight, bool italic) { 9074 unload(); 9075 9076 font = NSFont.fontWithName(MacString(name).borrow, size); // FIXME: weight and italic? 9077 prepareFontInfo(); 9078 9079 return !isNull(); 9080 } 9081 9082 private struct LoadedInfo { 9083 string name; 9084 int size; 9085 FontWeight weight; 9086 bool italic; 9087 } 9088 private LoadedInfo loadedInfo; 9089 9090 /// 9091 void unload() { 9092 if(isNull()) 9093 return; 9094 9095 version(X11) { 9096 auto display = XDisplayConnection.display; 9097 9098 if(display is null) 9099 return; 9100 9101 version(with_xft) { 9102 if(isXft) { 9103 if(xftFont) 9104 XftFontClose(display, xftFont); 9105 isXft = false; 9106 xftFont = null; 9107 return; 9108 } 9109 } 9110 9111 if(font && font !is ScreenPainterImplementation.defaultfont) 9112 XFreeFont(display, font); 9113 if(fontset && fontset !is ScreenPainterImplementation.defaultfontset) 9114 XFreeFontSet(display, fontset); 9115 9116 font = null; 9117 fontset = null; 9118 } else version(Windows) { 9119 DeleteObject(font); 9120 font = null; 9121 } else version(OSXCocoa) { 9122 font.release(); 9123 font = null; 9124 } else static assert(0); 9125 } 9126 9127 private bool isMonospace_; 9128 9129 /++ 9130 History: 9131 Added January 16, 2021 9132 +/ 9133 bool isMonospace() { 9134 return isMonospace_; 9135 } 9136 9137 /++ 9138 Returns the average width of the font, conventionally defined as the width of the lowercase 'x' character. 9139 9140 History: 9141 Added March 26, 2020 9142 Documented January 16, 2021 9143 +/ 9144 int averageWidth() { 9145 version(X11) { 9146 return stringWidth("x"); 9147 } version(OSXCocoa) { 9148 return stringWidth("x"); 9149 } else version(Windows) 9150 return width_; 9151 else assert(0); 9152 } 9153 9154 /++ 9155 Returns the width of the string as drawn on the specified window, or the default screen if the window is null. 9156 9157 History: 9158 Added January 16, 2021 9159 +/ 9160 int stringWidth(scope const(char)[] s, SimpleWindow window = null) { 9161 // FIXME: what about tab? 9162 if(isNull) 9163 return 0; 9164 9165 version(X11) { 9166 version(with_xft) 9167 if(isXft && xftFont !is null) { 9168 //return xftFont.max_advance_width; 9169 XGlyphInfo extents; 9170 XftTextExtentsUtf8(XDisplayConnection.get, xftFont, s.ptr, cast(int) s.length, &extents); 9171 // writeln(extents); 9172 return extents.xOff; 9173 } 9174 if(font is null) 9175 return 0; 9176 else if(fontset) { 9177 XRectangle rect; 9178 Xutf8TextExtents(fontset, s.ptr, cast(int) s.length, null, &rect); 9179 9180 return rect.width; 9181 } else { 9182 return XTextWidth(font, s.ptr, cast(int) s.length); 9183 } 9184 } else version(Windows) { 9185 WCharzBuffer buffer = WCharzBuffer(s); 9186 9187 return stringWidth(buffer.slice, window); 9188 } else version(OSXCocoa) { 9189 /+ 9190 int charCount = [string length]; 9191 CGGlyph glyphs[charCount]; 9192 CGRect rects[charCount]; 9193 9194 CTFontGetGlyphsForCharacters(theCTFont, (const unichar*)[string cStringUsingEncoding:NSUnicodeStringEncoding], glyphs, charCount); 9195 CTFontGetBoundingRectsForGlyphs(theCTFont, kCTFontDefaultOrientation, glyphs, rects, charCount); 9196 9197 int totalwidth = 0, maxheight = 0; 9198 for (int i=0; i < charCount; i++) 9199 { 9200 totalwidth += rects[i].size.width; 9201 maxheight = maxheight < rects[i].size.height ? rects[i].size.height : maxheight; 9202 } 9203 9204 dim = CGSizeMake(totalwidth, maxheight); 9205 +/ 9206 9207 return 16; // FIXME 9208 } 9209 else assert(0); 9210 } 9211 9212 version(Windows) 9213 /// ditto 9214 int stringWidth(scope const(wchar)[] s, SimpleWindow window = null) { 9215 if(isNull) 9216 return 0; 9217 version(Windows) { 9218 SIZE size; 9219 9220 prepareContext(window); 9221 scope(exit) releaseContext(); 9222 9223 GetTextExtentPoint32W(dc, s.ptr, cast(int) s.length, &size); 9224 9225 return size.cx; 9226 } else { 9227 // std.conv can do this easily but it is slow to import and i don't think it is worth it 9228 static assert(0, "not implemented yet"); 9229 //return stringWidth(s, window); 9230 } 9231 } 9232 9233 private { 9234 int prepRefcount; 9235 9236 version(Windows) { 9237 HDC dc; 9238 HANDLE orig; 9239 HWND hwnd; 9240 } 9241 } 9242 /++ 9243 [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. 9244 9245 History: 9246 Added January 23, 2021 9247 +/ 9248 void prepareContext(SimpleWindow window = null) { 9249 prepRefcount++; 9250 if(prepRefcount == 1) { 9251 version(Windows) { 9252 hwnd = window is null ? null : window.impl.hwnd; 9253 dc = GetDC(hwnd); 9254 orig = SelectObject(dc, font); 9255 } 9256 } 9257 } 9258 /// ditto 9259 void releaseContext() { 9260 prepRefcount--; 9261 if(prepRefcount == 0) { 9262 version(Windows) { 9263 SelectObject(dc, orig); 9264 ReleaseDC(hwnd, dc); 9265 hwnd = null; 9266 dc = null; 9267 orig = null; 9268 } 9269 } 9270 } 9271 9272 /+ 9273 FIXME: I think I need advance and kerning pair 9274 9275 int advance(dchar from, dchar to) { } // use dchar.init for first item in string 9276 +/ 9277 9278 /++ 9279 Returns the height of the font. 9280 9281 History: 9282 Added March 26, 2020 9283 Documented January 16, 2021 9284 +/ 9285 int height() { 9286 version(X11) { 9287 version(with_xft) 9288 if(isXft && xftFont !is null) { 9289 return xftFont.ascent + xftFont.descent; // i don't use height here because it doesn't include the baseline pixel 9290 } 9291 if(font is null) 9292 return 0; 9293 return font.max_bounds.ascent + font.max_bounds.descent; 9294 } else version(Windows) { 9295 return height_; 9296 } else version(OSXCocoa) { 9297 if(font is null) 9298 return 0; 9299 return cast(int) (font.ascender + font.descender + 0.9 /* to round up */); // font.capHeight 9300 } 9301 else assert(0); 9302 } 9303 9304 private int ascent_; 9305 private int descent_; 9306 9307 /++ 9308 Max ascent above the baseline. 9309 9310 History: 9311 Added January 22, 2021 9312 +/ 9313 int ascent() { 9314 return ascent_; 9315 } 9316 9317 /++ 9318 Max descent below the baseline. 9319 9320 History: 9321 Added January 22, 2021 9322 +/ 9323 int descent() { 9324 return descent_; 9325 } 9326 9327 /++ 9328 Loads the default font used by [ScreenPainter] if none others are loaded. 9329 9330 Returns: 9331 This method mutates the `this` object, but then returns `this` for 9332 easy chaining like: 9333 9334 --- 9335 auto font = foo.isNull ? foo : foo.loadDefault 9336 --- 9337 9338 History: 9339 Added previously, but left unimplemented until January 24, 2021. 9340 +/ 9341 OperatingSystemFont loadDefault() { 9342 unload(); 9343 9344 loadedInfo = LoadedInfo.init; 9345 9346 version(X11) { 9347 // another option would be https://tronche.com/gui/x/xlib/graphics/font-metrics/XQueryFont.html 9348 // but meh since sdpy does its own thing, this should be ok too 9349 9350 ScreenPainterImplementation.ensureDefaultFontLoaded(); 9351 this.font = ScreenPainterImplementation.defaultfont; 9352 this.fontset = ScreenPainterImplementation.defaultfontset; 9353 9354 prepareFontInfo(); 9355 return this; 9356 } else version(Windows) { 9357 ScreenPainterImplementation.ensureDefaultFontLoaded(); 9358 this.font = ScreenPainterImplementation.defaultGuiFont; 9359 9360 prepareFontInfo(); 9361 return this; 9362 } else version(OSXCocoa) { 9363 this.font = NSFont.systemFontOfSize(15); 9364 9365 prepareFontInfo(); 9366 9367 // import std.stdio; writeln("Load default: ", this.height()); 9368 return this; 9369 } else throw new NotYetImplementedException(); 9370 } 9371 9372 /// 9373 bool isNull() { 9374 version(with_xft) 9375 if(isXft) 9376 return xftFont is null; 9377 return font is null; 9378 } 9379 9380 /* Metrics */ 9381 /+ 9382 GetABCWidth 9383 GetKerningPairs 9384 9385 if I do it right, I can size it all here, and match 9386 what happens when I draw the full string with the OS functions. 9387 9388 subclasses might do the same thing while getting the glyphs on images 9389 struct GlyphInfo { 9390 int glyph; 9391 9392 size_t stringIdxStart; 9393 size_t stringIdxEnd; 9394 9395 Rectangle boundingBox; 9396 } 9397 GlyphInfo[] getCharBoxes() { 9398 // XftTextExtentsUtf8 9399 return null; 9400 9401 } 9402 +/ 9403 9404 ~this() { 9405 if(!thisIsGuiThread) return; // FIXME: leaks if multithreaded gc 9406 unload(); 9407 } 9408 } 9409 9410 version(Windows) 9411 private string sliceCString(const(wchar)[] w) { 9412 return makeUtf8StringFromWindowsString(cast(wchar*) w.ptr); 9413 } 9414 9415 private inout(char)[] sliceCString(inout(char)* s) { 9416 import core.stdc.string; 9417 auto len = strlen(s); 9418 return s[0 .. len]; 9419 } 9420 9421 version(OSXCocoa) 9422 alias PaintingHandle = NSObject; 9423 else 9424 alias PaintingHandle = NativeWindowHandle; 9425 9426 /** 9427 The 2D drawing proxy. You acquire one of these with [SimpleWindow.draw] rather 9428 than constructing it directly. Then, it is reference counted so you can pass it 9429 at around and when the last ref goes out of scope, the buffered drawing activities 9430 are all carried out. 9431 9432 9433 Most functions use the outlineColor instead of taking a color themselves. 9434 ScreenPainter is reference counted and draws its buffer to the screen when its 9435 final reference goes out of scope. 9436 */ 9437 struct ScreenPainter { 9438 CapableOfBeingDrawnUpon window; 9439 this(CapableOfBeingDrawnUpon window, PaintingHandle handle, bool manualInvalidations) { 9440 this.window = window; 9441 if(window.closed) 9442 return; // null painter is now allowed so no need to throw anymore, this likely happens at the end of a program anyway 9443 //currentClipRectangle = arsd.color.Rectangle(0, 0, window.width, window.height); 9444 currentClipRectangle = arsd.color.Rectangle(short.min, short.min, short.max, short.max); 9445 if(window.activeScreenPainter !is null) { 9446 impl = window.activeScreenPainter; 9447 if(impl.referenceCount == 0) { 9448 impl.window = window; 9449 impl.create(handle); 9450 } 9451 impl.manualInvalidations = manualInvalidations; 9452 impl.referenceCount++; 9453 // writeln("refcount ++ ", impl.referenceCount); 9454 } else { 9455 impl = new ScreenPainterImplementation; 9456 impl.window = window; 9457 impl.create(handle); 9458 impl.referenceCount = 1; 9459 impl.manualInvalidations = manualInvalidations; 9460 window.activeScreenPainter = impl; 9461 // writeln("constructed"); 9462 } 9463 9464 copyActiveOriginals(); 9465 } 9466 9467 /++ 9468 EXPERIMENTAL. subject to change. 9469 9470 When you draw a cursor, you can draw this to notify your window of where it is, 9471 for IME systems to use. 9472 +/ 9473 void notifyCursorPosition(int x, int y, int width, int height) { 9474 if(auto w = cast(SimpleWindow) window) { 9475 w.setIMEPopupLocation(x + _originX + width, y + _originY + height); 9476 } 9477 } 9478 9479 /++ 9480 If you are using manual invalidations, this informs the 9481 window system that a section needs to be redrawn. 9482 9483 If you didn't opt into manual invalidation, you don't 9484 have to call this. 9485 9486 History: 9487 Added December 30, 2021 (dub v10.5) 9488 +/ 9489 void invalidateRect(Rectangle rect) { 9490 if(impl is null) return; 9491 9492 // transform(rect) 9493 rect.left += _originX; 9494 rect.right += _originX; 9495 rect.top += _originY; 9496 rect.bottom += _originY; 9497 9498 impl.invalidateRect(rect); 9499 } 9500 9501 private Pen originalPen; 9502 private Color originalFillColor; 9503 private arsd.color.Rectangle originalClipRectangle; 9504 private OperatingSystemFont originalFont; 9505 void copyActiveOriginals() { 9506 if(impl is null) return; 9507 originalPen = impl._activePen; 9508 originalFillColor = impl._fillColor; 9509 originalClipRectangle = impl._clipRectangle; 9510 version(OSXCocoa) {} else 9511 originalFont = impl._activeFont; 9512 } 9513 9514 ~this() { 9515 if(impl is null) return; 9516 impl.referenceCount--; 9517 //writeln("refcount -- ", impl.referenceCount); 9518 if(impl.referenceCount == 0) { 9519 // writeln("destructed"); 9520 impl.dispose(); 9521 *window.activeScreenPainter = ScreenPainterImplementation.init; 9522 // writeln("paint finished"); 9523 } else { 9524 // there is still an active reference, reset stuff so the 9525 // next user doesn't get weirdness via the reference 9526 this.rasterOp = RasterOp.normal; 9527 pen = originalPen; 9528 fillColor = originalFillColor; 9529 if(originalFont) 9530 setFont(originalFont); 9531 impl.setClipRectangle(originalClipRectangle.left, originalClipRectangle.top, originalClipRectangle.width, originalClipRectangle.height); 9532 } 9533 } 9534 9535 this(this) { 9536 if(impl is null) return; 9537 impl.referenceCount++; 9538 //writeln("refcount ++ ", impl.referenceCount); 9539 9540 copyActiveOriginals(); 9541 } 9542 9543 private int _originX; 9544 private int _originY; 9545 @property int originX() { return _originX; } 9546 @property int originY() { return _originY; } 9547 @property int originX(int a) { 9548 _originX = a; 9549 return _originX; 9550 } 9551 @property int originY(int a) { 9552 _originY = a; 9553 return _originY; 9554 } 9555 arsd.color.Rectangle currentClipRectangle; // set BEFORE doing any transformations 9556 private void transform(ref Point p) { 9557 if(impl is null) return; 9558 p.x += _originX; 9559 p.y += _originY; 9560 } 9561 9562 // this needs to be checked BEFORE the originX/Y transformation 9563 private bool isClipped(Point p) { 9564 return !currentClipRectangle.contains(p); 9565 } 9566 private bool isClipped(Point p, int width, int height) { 9567 return !currentClipRectangle.overlaps(arsd.color.Rectangle(p, Size(width + 1, height + 1))); 9568 } 9569 private bool isClipped(Point p, Size s) { 9570 return !currentClipRectangle.overlaps(arsd.color.Rectangle(p, Size(s.width + 1, s.height + 1))); 9571 } 9572 private bool isClipped(Point p, Point p2) { 9573 // need to ensure the end points are actually included inside, so the +1 does that 9574 return !currentClipRectangle.overlaps(arsd.color.Rectangle(p, p2 + Point(1, 1))); 9575 } 9576 9577 9578 /++ 9579 Sets the clipping region for drawing. If width == 0 && height == 0, disabled clipping. 9580 9581 Returns: 9582 The old clip rectangle. 9583 9584 History: 9585 Return value was `void` prior to May 10, 2021. 9586 9587 +/ 9588 arsd.color.Rectangle setClipRectangle(Point pt, int width, int height) { 9589 if(impl is null) return currentClipRectangle; 9590 if(pt == currentClipRectangle.upperLeft && width == currentClipRectangle.width && height == currentClipRectangle.height) 9591 return currentClipRectangle; // no need to do anything 9592 auto old = currentClipRectangle; 9593 currentClipRectangle = arsd.color.Rectangle(pt, Size(width, height)); 9594 transform(pt); 9595 9596 impl.setClipRectangle(pt.x, pt.y, width, height); 9597 9598 return old; 9599 } 9600 9601 /// ditto 9602 arsd.color.Rectangle setClipRectangle(arsd.color.Rectangle rect) { 9603 if(impl is null) return currentClipRectangle; 9604 return setClipRectangle(rect.upperLeft, rect.width, rect.height); 9605 } 9606 9607 /// 9608 void setFont(OperatingSystemFont font) { 9609 if(impl is null) return; 9610 impl.setFont(font); 9611 } 9612 9613 /// 9614 int fontHeight() { 9615 if(impl is null) return 0; 9616 return impl.fontHeight(); 9617 } 9618 9619 private Pen activePen; 9620 9621 /// 9622 @property void pen(Pen p) { 9623 if(impl is null) return; 9624 activePen = p; 9625 impl.pen(p); 9626 } 9627 9628 /// 9629 @scriptable 9630 @property void outlineColor(Color c) { 9631 if(impl is null) return; 9632 if(activePen.color == c) 9633 return; 9634 activePen.color = c; 9635 impl.pen(activePen); 9636 } 9637 9638 /// 9639 @scriptable 9640 @property void fillColor(Color c) { 9641 if(impl is null) return; 9642 impl.fillColor(c); 9643 } 9644 9645 /// 9646 @property void rasterOp(RasterOp op) { 9647 if(impl is null) return; 9648 impl.rasterOp(op); 9649 } 9650 9651 9652 void updateDisplay() { 9653 // FIXME this should do what the dtor does 9654 } 9655 9656 /// 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) 9657 void scrollArea(Point upperLeft, int width, int height, int dx, int dy) { 9658 if(impl is null) return; 9659 if(isClipped(upperLeft, width, height)) return; 9660 transform(upperLeft); 9661 version(Windows) { 9662 // http://msdn.microsoft.com/en-us/library/windows/desktop/bb787589%28v=vs.85%29.aspx 9663 RECT scroll = RECT(upperLeft.x, upperLeft.y, upperLeft.x + width, upperLeft.y + height); 9664 RECT clip = scroll; 9665 RECT uncovered; 9666 HRGN hrgn; 9667 if(!ScrollDC(impl.hdc, -dx, -dy, &scroll, &clip, hrgn, &uncovered)) 9668 throw new WindowsApiException("ScrollDC", GetLastError()); 9669 9670 } else version(X11) { 9671 // FIXME: clip stuff outside this rectangle 9672 XCopyArea(impl.display, impl.d, impl.d, impl.gc, upperLeft.x, upperLeft.y, width, height, upperLeft.x - dx, upperLeft.y - dy); 9673 } else version(OSXCocoa) { 9674 throw new NotYetImplementedException(); 9675 } else static assert(0); 9676 } 9677 9678 /// 9679 void clear(Color color = Color.white()) { 9680 if(impl is null) return; 9681 fillColor = color; 9682 outlineColor = color; 9683 drawRectangle(Point(0, 0), window.width, window.height); 9684 } 9685 9686 /++ 9687 Draws a pixmap (represented by the [Sprite] class) on the drawable. 9688 9689 Params: 9690 upperLeft = point on the window where the upper left corner of the image will be drawn 9691 imageUpperLeft = point on the image to start the slice to draw 9692 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. 9693 History: 9694 The `imageUpperLeft` and `sliceSize` parameters were added on March 11, 2021 (dub v9.3.0) 9695 +/ 9696 void drawPixmap(Sprite s, Point upperLeft, Point imageUpperLeft = Point(0, 0), Size sliceSize = Size(0, 0)) { 9697 if(impl is null) return; 9698 if(isClipped(upperLeft, s.width, s.height)) return; 9699 transform(upperLeft); 9700 impl.drawPixmap(s, upperLeft.x, upperLeft.y, imageUpperLeft.x, imageUpperLeft.y, sliceSize.width, sliceSize.height); 9701 } 9702 9703 /// 9704 void drawImage(Point upperLeft, Image i, Point upperLeftOfImage = Point(0, 0), int w = 0, int h = 0) { 9705 if(impl is null) return; 9706 //if(isClipped(upperLeft, w, h)) return; // FIXME 9707 transform(upperLeft); 9708 if(w == 0 || w > i.width) 9709 w = i.width; 9710 if(h == 0 || h > i.height) 9711 h = i.height; 9712 if(upperLeftOfImage.x < 0) 9713 upperLeftOfImage.x = 0; 9714 if(upperLeftOfImage.y < 0) 9715 upperLeftOfImage.y = 0; 9716 9717 impl.drawImage(upperLeft.x, upperLeft.y, i, upperLeftOfImage.x, upperLeftOfImage.y, w, h); 9718 } 9719 9720 /// 9721 Size textSize(in char[] text) { 9722 if(impl is null) return Size(0, 0); 9723 return impl.textSize(text); 9724 } 9725 9726 /++ 9727 Draws a string in the window with the set font (see [setFont] to change it). 9728 9729 Params: 9730 upperLeft = the upper left point of the bounding box of the text 9731 text = the string to draw 9732 lowerRight = the lower right point of the bounding box of the text. If 0, 0, there is no lower right bound. 9733 alignment = A [arsd.docs.general_concepts#bitflags|combination] of [TextAlignment] flags 9734 +/ 9735 @scriptable 9736 void drawText(Point upperLeft, in char[] text, Point lowerRight = Point(0, 0), uint alignment = 0) { 9737 if(impl is null) return; 9738 if(lowerRight.x != 0 || lowerRight.y != 0) { 9739 if(isClipped(upperLeft, lowerRight)) return; 9740 transform(lowerRight); 9741 } else { 9742 if(isClipped(upperLeft, textSize(text))) return; 9743 } 9744 transform(upperLeft); 9745 impl.drawText(upperLeft.x, upperLeft.y, lowerRight.x, lowerRight.y, text, alignment); 9746 } 9747 9748 /++ 9749 Draws text using a custom font. 9750 9751 This is still MAJOR work in progress. 9752 9753 Creating a [DrawableFont] can be tricky and require additional dependencies. 9754 +/ 9755 void drawText(DrawableFont font, Point upperLeft, in char[] text) { 9756 if(impl is null) return; 9757 if(isClipped(upperLeft, Point(int.max, int.max))) return; 9758 transform(upperLeft); 9759 font.drawString(this, upperLeft, text); 9760 } 9761 9762 version(Windows) 9763 void drawText(Point upperLeft, scope const(wchar)[] text) { 9764 if(impl is null) return; 9765 if(isClipped(upperLeft, Point(int.max, int.max))) return; 9766 transform(upperLeft); 9767 9768 if(text.length && text[$-1] == '\n') 9769 text = text[0 .. $-1]; // tailing newlines are weird on windows... 9770 9771 TextOutW(impl.hdc, upperLeft.x, upperLeft.y, text.ptr, cast(int) text.length); 9772 } 9773 9774 static struct TextDrawingContext { 9775 Point boundingBoxUpperLeft; 9776 Point boundingBoxLowerRight; 9777 9778 Point currentLocation; 9779 9780 Point lastDrewUpperLeft; 9781 Point lastDrewLowerRight; 9782 9783 // how do i do right aligned rich text? 9784 // i kinda want to do a pre-made drawing then right align 9785 // draw the whole block. 9786 // 9787 // That's exactly the diff: inline vs block stuff. 9788 9789 // I need to get coordinates of an inline section out too, 9790 // not just a bounding box, but a series of bounding boxes 9791 // should be ok. Consider what's needed to detect a click 9792 // on a link in the middle of a paragraph breaking a line. 9793 // 9794 // Generally, we should be able to get the rectangles of 9795 // any portion we draw. 9796 // 9797 // It also needs to tell what text is left if it overflows 9798 // out of the box, so we can do stuff like float images around 9799 // it. It should not attempt to draw a letter that would be 9800 // clipped. 9801 // 9802 // I might also turn off word wrap stuff. 9803 } 9804 9805 void drawText(TextDrawingContext context, in char[] text, uint alignment = 0) { 9806 if(impl is null) return; 9807 // FIXME 9808 } 9809 9810 /// Drawing an individual pixel is slow. Avoid it if possible. 9811 void drawPixel(Point where) { 9812 if(impl is null) return; 9813 if(isClipped(where)) return; 9814 transform(where); 9815 impl.drawPixel(where.x, where.y); 9816 } 9817 9818 9819 /// Draws a pen using the current pen / outlineColor 9820 @scriptable 9821 void drawLine(Point starting, Point ending) { 9822 if(impl is null) return; 9823 if(isClipped(starting, ending)) return; 9824 transform(starting); 9825 transform(ending); 9826 impl.drawLine(starting.x, starting.y, ending.x, ending.y); 9827 } 9828 9829 /// Draws a rectangle using the current pen/outline color for the border and brush/fill color for the insides 9830 /// The outer lines, inclusive of x = 0, y = 0, x = width - 1, and y = height - 1 are drawn with the outlineColor 9831 /// The rest of the pixels are drawn with the fillColor. If fillColor is transparent, those pixels are not drawn. 9832 @scriptable 9833 void drawRectangle(Point upperLeft, int width, int height) { 9834 if(impl is null) return; 9835 if(isClipped(upperLeft, width, height)) return; 9836 transform(upperLeft); 9837 impl.drawRectangle(upperLeft.x, upperLeft.y, width, height); 9838 } 9839 9840 /// ditto 9841 void drawRectangle(Point upperLeft, Size size) { 9842 if(impl is null) return; 9843 if(isClipped(upperLeft, size.width, size.height)) return; 9844 transform(upperLeft); 9845 impl.drawRectangle(upperLeft.x, upperLeft.y, size.width, size.height); 9846 } 9847 9848 /// ditto 9849 void drawRectangle(Point upperLeft, Point lowerRightInclusive) { 9850 if(impl is null) return; 9851 if(isClipped(upperLeft, lowerRightInclusive + Point(1, 1))) return; 9852 transform(upperLeft); 9853 transform(lowerRightInclusive); 9854 impl.drawRectangle(upperLeft.x, upperLeft.y, 9855 lowerRightInclusive.x - upperLeft.x + 1, lowerRightInclusive.y - upperLeft.y + 1); 9856 } 9857 9858 // overload added on May 12, 2021 9859 /// ditto 9860 void drawRectangle(Rectangle rect) { 9861 drawRectangle(rect.upperLeft, rect.size); 9862 } 9863 9864 /// Arguments are the points of the bounding rectangle 9865 void drawEllipse(Point upperLeft, Point lowerRight) { 9866 if(impl is null) return; 9867 if(isClipped(upperLeft, lowerRight)) return; 9868 transform(upperLeft); 9869 transform(lowerRight); 9870 impl.drawEllipse(upperLeft.x, upperLeft.y, lowerRight.x, lowerRight.y); 9871 } 9872 9873 /++ 9874 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. 9875 9876 9877 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. 9878 9879 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. 9880 9881 Bugs: 9882 They still don't exactly match in outlining the arc with straight lines (Windows does, Linux doesn't for now). 9883 9884 The arc outline on Linux sometimes goes over the target. 9885 9886 The fill on Windows sometimes stops short. 9887 9888 History: 9889 This function was broken af, totally inconsistent on platforms until September 24, 2021. 9890 9891 The interpretation of the final argument was incorrectly documented and implemented until August 2, 2024. 9892 +/ 9893 void drawArc(Point upperLeft, int width, int height, int start, int length) { 9894 if(impl is null) return; 9895 // FIXME: not actually implemented 9896 if(isClipped(upperLeft, width, height)) return; 9897 transform(upperLeft); 9898 impl.drawArc(upperLeft.x, upperLeft.y, width, height, start, length); 9899 } 9900 9901 /// this function draws a circle with the drawEllipse() function above, it requires the upper left point and the radius 9902 void drawCircle(Point upperLeft, int diameter) { 9903 drawEllipse(upperLeft, Point(upperLeft.x + diameter, upperLeft.y + diameter)); 9904 } 9905 9906 /++ 9907 Draws a rectangle with rounded corners. It is outlined with the current foreground pen and filled with the current background brush. 9908 9909 9910 Bugs: 9911 Not implemented on Mac; it will instead draw a non-rounded rectangle for now. 9912 9913 History: 9914 Added August 3, 2024 9915 +/ 9916 void drawRectangleRounded(Rectangle rect, int borderRadius) { 9917 drawRectangleRounded(rect.upperLeft, rect.lowerRight, borderRadius); 9918 } 9919 9920 /// ditto 9921 void drawRectangleRounded(Point upperLeft, Size size, int borderRadius) { 9922 drawRectangleRounded(upperLeft, upperLeft + Point(size.width, size.height), borderRadius); 9923 } 9924 9925 /// ditto 9926 void drawRectangleRounded(Point upperLeft, Point lowerRight, int borderRadius) { 9927 if(borderRadius <= 0) { 9928 drawRectangle(upperLeft, lowerRight); 9929 return; 9930 } 9931 9932 transform(upperLeft); 9933 transform(lowerRight); 9934 9935 impl.drawRectangleRounded(upperLeft, lowerRight, borderRadius); 9936 } 9937 9938 /// . 9939 void drawPolygon(Point[] vertexes) { 9940 if(impl is null) return; 9941 assert(vertexes.length); 9942 int minX = int.max, minY = int.max, maxX = int.min, maxY = int.min; 9943 foreach(ref vertex; vertexes) { 9944 if(vertex.x < minX) 9945 minX = vertex.x; 9946 if(vertex.y < minY) 9947 minY = vertex.y; 9948 if(vertex.x > maxX) 9949 maxX = vertex.x; 9950 if(vertex.y > maxY) 9951 maxY = vertex.y; 9952 transform(vertex); 9953 } 9954 if(isClipped(Point(minX, maxY), Point(maxX + 1, maxY + 1))) return; 9955 impl.drawPolygon(vertexes); 9956 } 9957 9958 /// ditto 9959 void drawPolygon(Point[] vertexes...) { 9960 if(impl is null) return; 9961 drawPolygon(vertexes); 9962 } 9963 9964 9965 // and do a draw/fill in a single call maybe. Windows can do it... but X can't, though it could do two calls. 9966 9967 //mixin NativeScreenPainterImplementation!() impl; 9968 9969 9970 // HACK: if I mixin the impl directly, it won't let me override the copy 9971 // constructor! The linker complains about there being multiple definitions. 9972 // I'll make the best of it and reference count it though. 9973 ScreenPainterImplementation* impl; 9974 } 9975 9976 // HACK: I need a pointer to the implementation so it's separate 9977 struct ScreenPainterImplementation { 9978 CapableOfBeingDrawnUpon window; 9979 int referenceCount; 9980 mixin NativeScreenPainterImplementation!(); 9981 } 9982 9983 // FIXME: i haven't actually tested the sprite class on MS Windows 9984 9985 /** 9986 Sprites are optimized for fast drawing on the screen, but slow for direct pixel 9987 access. They are best for drawing a relatively unchanging image repeatedly on the screen. 9988 9989 9990 On X11, this corresponds to an `XPixmap`. On Windows, it still uses a bitmap, 9991 though I'm not sure that's ideal and the implementation might change. 9992 9993 You create one by giving a window and an image. It optimizes for that window, 9994 and copies the image into it to use as the initial picture. Creating a sprite 9995 can be quite slow (especially over a network connection) so you should do it 9996 as little as possible and just hold on to your sprite handles after making them. 9997 simpledisplay does try to do its best though, using the XSHM extension if available, 9998 but you should still write your code as if it will always be slow. 9999 10000 Then you can use `sprite.drawAt(painter, point);` to draw it, which should be 10001 a fast operation - much faster than drawing the Image itself every time. 10002 10003 `Sprite` represents a scarce resource which should be freed when you 10004 are done with it. Use the `dispose` method to do this. Do not use a `Sprite` 10005 after it has been disposed. If you are unsure about this, don't take chances, 10006 just let the garbage collector do it for you. But ideally, you can manage its 10007 lifetime more efficiently. 10008 10009 $(NOTE `Sprite`, like the rest of simpledisplay's `ScreenPainter`, does not 10010 support alpha blending in its drawing at this time. That might change in the 10011 future, but if you need alpha blending right now, use OpenGL instead. See 10012 `gamehelpers.d` for a similar class to `Sprite` that uses OpenGL: `OpenGlTexture`.) 10013 10014 Update: on April 23, 2021, I finally added alpha blending support. You must opt 10015 in by setting the enableAlpha = true in the constructor. 10016 */ 10017 class Sprite : CapableOfBeingDrawnUpon { 10018 10019 /// 10020 ScreenPainter draw() { 10021 return ScreenPainter(this, handle, false); 10022 } 10023 10024 /++ 10025 Copies the sprite's current state into a [TrueColorImage]. 10026 10027 Be warned: this can be a very slow operation 10028 10029 History: 10030 Actually implemented on March 14, 2021 10031 +/ 10032 TrueColorImage takeScreenshot() { 10033 return trueColorImageFromNativeHandle(handle, width, height); 10034 } 10035 10036 void delegate() paintingFinishedDg() { return null; } 10037 bool closed() { return false; } 10038 ScreenPainterImplementation* activeScreenPainter_; 10039 protected ScreenPainterImplementation* activeScreenPainter() { return activeScreenPainter_; } 10040 protected void activeScreenPainter(ScreenPainterImplementation* i) { activeScreenPainter_ = i; } 10041 10042 version(Windows) 10043 private ubyte* rawData; 10044 // FIXME: sprites are lost when disconnecting from X! We need some way to invalidate them... 10045 // ditto on the XPicture stuff 10046 10047 version(X11) { 10048 private static XRenderPictFormat* RGB24; 10049 private static XRenderPictFormat* ARGB32; 10050 10051 private Picture xrenderPicture; 10052 } 10053 10054 version(X11) 10055 private static void requireXRender() { 10056 if(!XRenderLibrary.loadAttempted) { 10057 XRenderLibrary.loadDynamicLibrary(); 10058 } 10059 10060 if(!XRenderLibrary.loadSuccessful) 10061 throw new Exception("XRender library load failure"); 10062 10063 auto display = XDisplayConnection.get; 10064 10065 // FIXME: if we migrate X displays, these need to be changed 10066 if(RGB24 is null) 10067 RGB24 = XRenderFindStandardFormat(display, PictStandardRGB24); 10068 if(ARGB32 is null) 10069 ARGB32 = XRenderFindStandardFormat(display, PictStandardARGB32); 10070 } 10071 10072 protected this() {} 10073 10074 this(SimpleWindow win, int width, int height, bool enableAlpha = false) { 10075 this._width = width; 10076 this._height = height; 10077 this.enableAlpha = enableAlpha; 10078 10079 version(X11) { 10080 auto display = XDisplayConnection.get(); 10081 10082 if(enableAlpha) { 10083 requireXRender(); 10084 } 10085 10086 handle = XCreatePixmap(display, cast(Drawable) win.window, width, height, enableAlpha ? 32 : DefaultDepthOfDisplay(display)); 10087 10088 if(enableAlpha) { 10089 XRenderPictureAttributes attrs; 10090 xrenderPicture = XRenderCreatePicture(display, handle, ARGB32, 0, &attrs); 10091 } 10092 } else version(Windows) { 10093 version(CRuntime_DigitalMars) { 10094 //if(enableAlpha) 10095 //throw new Exception("Alpha support not available, try recompiling with -m32mscoff"); 10096 } 10097 10098 BITMAPINFO infoheader; 10099 infoheader.bmiHeader.biSize = infoheader.bmiHeader.sizeof; 10100 infoheader.bmiHeader.biWidth = width; 10101 infoheader.bmiHeader.biHeight = height; 10102 infoheader.bmiHeader.biPlanes = 1; 10103 infoheader.bmiHeader.biBitCount = enableAlpha ? 32 : 24; 10104 infoheader.bmiHeader.biCompression = BI_RGB; 10105 10106 // FIXME: this should prolly be a device dependent bitmap... 10107 handle = CreateDIBSection( 10108 null, 10109 &infoheader, 10110 DIB_RGB_COLORS, 10111 cast(void**) &rawData, 10112 null, 10113 0); 10114 10115 if(handle is null) 10116 throw new WindowsApiException("couldn't create pixmap", GetLastError()); 10117 } 10118 } 10119 10120 /// Makes a sprite based on the image with the initial contents from the Image 10121 this(SimpleWindow win, Image i) { 10122 this(win, i.width, i.height, i.enableAlpha); 10123 10124 version(X11) { 10125 auto display = XDisplayConnection.get(); 10126 auto gc = XCreateGC(display, this.handle, 0, null); 10127 scope(exit) XFreeGC(display, gc); 10128 if(i.usingXshm) 10129 XShmPutImage(display, cast(Drawable) handle, gc, i.handle, 0, 0, 0, 0, i.width, i.height, false); 10130 else 10131 XPutImage(display, cast(Drawable) handle, gc, i.handle, 0, 0, 0, 0, i.width, i.height); 10132 } else version(Windows) { 10133 auto itemsPerLine = enableAlpha ? (4 * width) : (((cast(int) width * 3 + 3) / 4) * 4); 10134 auto arrLength = itemsPerLine * height; 10135 rawData[0..arrLength] = i.rawData[0..arrLength]; 10136 } else version(OSXCocoa) { 10137 // FIXME: I have no idea if this is even any good 10138 auto colorSpace = CGColorSpaceCreateDeviceRGB(); 10139 handle = CGBitmapContextCreate(null, width, height, 8, 4*width, 10140 colorSpace, 10141 kCGImageAlphaPremultipliedLast 10142 |kCGBitmapByteOrder32Big); 10143 CGColorSpaceRelease(colorSpace); 10144 auto rawData = CGBitmapContextGetData(handle); 10145 10146 auto rdl = (width * height * 4); 10147 rawData[0 .. rdl] = i.rawData[0 .. rdl]; 10148 } else static assert(0); 10149 } 10150 10151 /++ 10152 Draws the image on the specified painter at the specified point. The point is the upper-left point where the image will be drawn. 10153 10154 Params: 10155 where = point on the window where the upper left corner of the image will be drawn 10156 imageUpperLeft = point on the image to start the slice to draw 10157 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. 10158 History: 10159 The `imageUpperLeft` and `sliceSize` parameters were added on March 11, 2021 (dub v9.3.0) 10160 +/ 10161 void drawAt(ScreenPainter painter, Point where, Point imageUpperLeft = Point(0, 0), Size sliceSize = Size(0, 0)) { 10162 painter.drawPixmap(this, where, imageUpperLeft, sliceSize); 10163 } 10164 10165 /// Call this when you're ready to get rid of it 10166 void dispose() { 10167 version(X11) { 10168 staticDispose(xrenderPicture, handle); 10169 xrenderPicture = None; 10170 handle = None; 10171 } else version(Windows) { 10172 staticDispose(handle); 10173 handle = null; 10174 } else version(OSXCocoa) { 10175 staticDispose(handle); 10176 handle = null; 10177 } else static assert(0); 10178 10179 } 10180 10181 version(X11) 10182 static void staticDispose(Picture xrenderPicture, Pixmap handle) { 10183 if(xrenderPicture) 10184 XRenderFreePicture(XDisplayConnection.get, xrenderPicture); 10185 if(handle) 10186 XFreePixmap(XDisplayConnection.get(), handle); 10187 } 10188 else version(Windows) 10189 static void staticDispose(HBITMAP handle) { 10190 if(handle) 10191 DeleteObject(handle); 10192 } 10193 else version(OSXCocoa) 10194 static void staticDispose(CGContextRef context) { 10195 if(context) 10196 CGContextRelease(context); 10197 } 10198 10199 ~this() { 10200 version(X11) { if(xrenderPicture || handle) 10201 cleanupQueue.queue!staticDispose(xrenderPicture, handle); 10202 } else version(Windows) { if(handle) 10203 cleanupQueue.queue!staticDispose(handle); 10204 } else version(OSXCocoa) { if(handle) 10205 cleanupQueue.queue!staticDispose(handle); 10206 } else static assert(0); 10207 } 10208 10209 /// 10210 final @property int width() { return _width; } 10211 10212 /// 10213 final @property int height() { return _height; } 10214 10215 /// 10216 static Sprite fromMemoryImage(SimpleWindow win, MemoryImage img, bool enableAlpha = false) { 10217 return new Sprite(win, Image.fromMemoryImage(img, enableAlpha)); 10218 } 10219 10220 auto nativeHandle() { 10221 return handle; 10222 } 10223 10224 private: 10225 10226 int _width; 10227 int _height; 10228 bool enableAlpha; 10229 version(X11) 10230 Pixmap handle; 10231 else version(Windows) 10232 HBITMAP handle; 10233 else version(OSXCocoa) 10234 CGContextRef handle; 10235 else static assert(0); 10236 } 10237 10238 /++ 10239 Represents a display-side gradient pseudo-image. Actually construct it with [LinearGradient], [RadialGradient], or [ConicalGradient]. 10240 10241 History: 10242 Added November 20, 2021 (dub v10.4) 10243 +/ 10244 version(OSXCocoa) {} else // NotYetImplementedException 10245 abstract class Gradient : Sprite { 10246 protected this(int w, int h) { 10247 version(X11) { 10248 Sprite.requireXRender(); 10249 10250 super(); 10251 enableAlpha = true; 10252 _width = w; 10253 _height = h; 10254 } else version(Windows) { 10255 super(null, w, h, true); // on Windows i'm just making a bitmap myself 10256 } 10257 } 10258 10259 version(Windows) 10260 final void forEachPixel(scope Color delegate(int x, int y) dg) @system { 10261 auto ptr = rawData; 10262 foreach(j; 0 .. _height) 10263 foreach(i; 0 .. _width) { 10264 auto color = dg(i, _height - j - 1); // cuz of upside down bitmap 10265 *rawData = (color.a * color.b) / 255; rawData++; 10266 *rawData = (color.a * color.g) / 255; rawData++; 10267 *rawData = (color.a * color.r) / 255; rawData++; 10268 *rawData = color.a; rawData++; 10269 } 10270 } 10271 10272 version(X11) 10273 protected void helper(scope Stop[] stops, scope Picture delegate(scope XFixed[] stopsPositions, scope XRenderColor[] colors) dg) { 10274 assert(stops.length > 0); 10275 assert(stops.length <= 16, "I got lazy with buffers"); 10276 10277 XFixed[16] stopsPositions = void; 10278 XRenderColor[16] colors = void; 10279 10280 foreach(idx, stop; stops) { 10281 stopsPositions[idx] = cast(int)(stop.percentage * ushort.max); 10282 auto c = stop.c; 10283 colors[idx] = XRenderColor( 10284 cast(ushort)(c.r * ushort.max / 255), 10285 cast(ushort)(c.g * ushort.max / 255), 10286 cast(ushort)(c.b * ushort.max / 255), 10287 cast(ushort)(c.a * ubyte.max) // max value here is fractional 10288 ); 10289 } 10290 10291 xrenderPicture = dg(stopsPositions, colors); 10292 } 10293 10294 /// 10295 static struct Stop { 10296 float percentage; /// between 0 and 1.0 10297 Color c; 10298 } 10299 } 10300 10301 /++ 10302 Creates a linear gradient between p1 and p2. 10303 10304 X ONLY RIGHT NOW 10305 10306 History: 10307 Added November 20, 2021 (dub v10.4) 10308 10309 Bugs: 10310 Not yet implemented on Windows. 10311 +/ 10312 version(OSXCocoa) {} else // NotYetImplementedException 10313 class LinearGradient : Gradient { 10314 /++ 10315 10316 +/ 10317 this(Point p1, Point p2, Stop[] stops...) { 10318 super(p2.x, p2.y); 10319 10320 version(X11) { 10321 XLinearGradient gradient; 10322 gradient.p1 = XPointFixed(p1.x * ushort.max, p1.y * ushort.max); 10323 gradient.p2 = XPointFixed(p2.x * ushort.max, p2.y * ushort.max); 10324 10325 helper(stops, (scope XFixed[] stopsPositions, scope XRenderColor[] colors) { 10326 return XRenderCreateLinearGradient( 10327 XDisplayConnection.get, 10328 &gradient, 10329 stopsPositions.ptr, 10330 colors.ptr, 10331 cast(int) stops.length); 10332 }); 10333 } else version(Windows) { 10334 // FIXME 10335 forEachPixel((int x, int y) { 10336 import core.stdc.math; 10337 10338 //sqrtf( 10339 10340 return Color.transparent; 10341 // return Color(x * 2, y * 2, 0, 128); // this result is so beautiful 10342 }); 10343 } 10344 } 10345 } 10346 10347 /++ 10348 A conical gradient goes from color to color around a circumference from a center point. 10349 10350 X ONLY RIGHT NOW 10351 10352 History: 10353 Added November 20, 2021 (dub v10.4) 10354 10355 Bugs: 10356 Not yet implemented on Windows. 10357 +/ 10358 version(OSXCocoa) {} else // NotYetImplementedException 10359 class ConicalGradient : Gradient { 10360 /++ 10361 10362 +/ 10363 this(Point center, float angleInDegrees, Stop[] stops...) { 10364 super(center.x * 2, center.y * 2); 10365 10366 version(X11) { 10367 XConicalGradient gradient; 10368 gradient.center = XPointFixed(center.x * ushort.max, center.y * ushort.max); 10369 gradient.angle = cast(int)(angleInDegrees * ushort.max); 10370 10371 helper(stops, (scope XFixed[] stopsPositions, scope XRenderColor[] colors) { 10372 return XRenderCreateConicalGradient( 10373 XDisplayConnection.get, 10374 &gradient, 10375 stopsPositions.ptr, 10376 colors.ptr, 10377 cast(int) stops.length); 10378 }); 10379 } else version(Windows) { 10380 // FIXME 10381 forEachPixel((int x, int y) { 10382 import core.stdc.math; 10383 10384 //sqrtf( 10385 10386 return Color.transparent; 10387 // return Color(x * 2, y * 2, 0, 128); // this result is so beautiful 10388 }); 10389 10390 } 10391 } 10392 } 10393 10394 /++ 10395 A radial gradient goes from color to color based on distance from the center. 10396 It is like rings of color. 10397 10398 X ONLY RIGHT NOW 10399 10400 10401 More specifically, you create two circles: an inner circle and an outer circle. 10402 The gradient is only drawn in the area outside the inner circle but inside the outer 10403 circle. The closest line between those two circles forms the line for the gradient 10404 and the stops are calculated the same as the [LinearGradient]. Then it just sweeps around. 10405 10406 History: 10407 Added November 20, 2021 (dub v10.4) 10408 10409 Bugs: 10410 Not yet implemented on Windows. 10411 +/ 10412 version(OSXCocoa) {} else // NotYetImplementedException 10413 class RadialGradient : Gradient { 10414 /++ 10415 10416 +/ 10417 this(Point innerCenter, float innerRadius, Point outerCenter, float outerRadius, Stop[] stops...) { 10418 super(cast(int)(outerCenter.x + outerRadius + 0.5), cast(int)(outerCenter.y + outerRadius + 0.5)); 10419 10420 version(X11) { 10421 XRadialGradient gradient; 10422 gradient.inner = XCircle(innerCenter.x * ushort.max, innerCenter.y * ushort.max, cast(int) (innerRadius * ushort.max)); 10423 gradient.outer = XCircle(outerCenter.x * ushort.max, outerCenter.y * ushort.max, cast(int) (outerRadius * ushort.max)); 10424 10425 helper(stops, (scope XFixed[] stopsPositions, scope XRenderColor[] colors) { 10426 return XRenderCreateRadialGradient( 10427 XDisplayConnection.get, 10428 &gradient, 10429 stopsPositions.ptr, 10430 colors.ptr, 10431 cast(int) stops.length); 10432 }); 10433 } else version(Windows) { 10434 // FIXME 10435 forEachPixel((int x, int y) { 10436 import core.stdc.math; 10437 10438 //sqrtf( 10439 10440 return Color.transparent; 10441 // return Color(x * 2, y * 2, 0, 128); // this result is so beautiful 10442 }); 10443 } 10444 } 10445 } 10446 10447 10448 10449 /+ 10450 NOT IMPLEMENTED 10451 10452 A display-stored image optimized for relatively quick drawing, like 10453 [Sprite], but this one supports alpha channel blending and does NOT 10454 support direct drawing upon it with a [ScreenPainter]. 10455 10456 You can think of it as an [arsd.game.OpenGlTexture] for usage with a 10457 plain [ScreenPainter]... sort of. 10458 10459 On X11, it requires the Xrender extension and library. This is available 10460 almost everywhere though. 10461 10462 History: 10463 Added November 14, 2020 but NOT ACTUALLY IMPLEMENTED 10464 +/ 10465 version(none) 10466 class AlphaSprite { 10467 /++ 10468 Copies the given image into it. 10469 +/ 10470 this(MemoryImage img) { 10471 10472 if(!XRenderLibrary.loadAttempted) { 10473 XRenderLibrary.loadDynamicLibrary(); 10474 10475 // FIXME: this needs to be reconstructed when the X server changes 10476 repopulateX(); 10477 } 10478 if(!XRenderLibrary.loadSuccessful) 10479 throw new Exception("XRender library load failure"); 10480 10481 // I probably need to put the alpha mask in a separate Picture 10482 // ugh 10483 // maybe the Sprite itself can have an alpha bitmask anyway 10484 10485 10486 auto display = XDisplayConnection.get(); 10487 pixmap = XCreatePixmap(display, cast(Drawable) win.window, width, height, DefaultDepthOfDisplay(display)); 10488 10489 10490 XRenderPictureAttributes attrs; 10491 10492 handle = XRenderCreatePicture( 10493 XDisplayConnection.get, 10494 pixmap, 10495 RGBA, 10496 0, 10497 &attrs 10498 ); 10499 10500 } 10501 10502 // maybe i'll use the create gradient functions too with static factories.. 10503 10504 void drawAt(ScreenPainter painter, Point where) { 10505 //painter.drawPixmap(this, where); 10506 10507 XRenderPictureAttributes attrs; 10508 10509 auto pic = XRenderCreatePicture( 10510 XDisplayConnection.get, 10511 painter.impl.d, 10512 RGB, 10513 0, 10514 &attrs 10515 ); 10516 10517 XRenderComposite( 10518 XDisplayConnection.get, 10519 3, // PictOpOver 10520 handle, 10521 None, 10522 pic, 10523 0, // src 10524 0, 10525 0, // mask 10526 0, 10527 10, // dest 10528 10, 10529 100, // width 10530 100 10531 ); 10532 10533 /+ 10534 XRenderFreePicture( 10535 XDisplayConnection.get, 10536 pic 10537 ); 10538 10539 XRenderFreePicture( 10540 XDisplayConnection.get, 10541 fill 10542 ); 10543 +/ 10544 // on Windows you can stretch but Xrender still can't :( 10545 } 10546 10547 static XRenderPictFormat* RGB; 10548 static XRenderPictFormat* RGBA; 10549 static void repopulateX() { 10550 auto display = XDisplayConnection.get; 10551 RGB = XRenderFindStandardFormat(display, PictStandardRGB24); 10552 RGBA = XRenderFindStandardFormat(display, PictStandardARGB32); 10553 } 10554 10555 XPixmap pixmap; 10556 Picture handle; 10557 } 10558 10559 /// 10560 interface CapableOfBeingDrawnUpon { 10561 /// 10562 ScreenPainter draw(); 10563 /// 10564 int width(); 10565 /// 10566 int height(); 10567 protected ScreenPainterImplementation* activeScreenPainter(); 10568 protected void activeScreenPainter(ScreenPainterImplementation*); 10569 bool closed(); 10570 10571 void delegate() paintingFinishedDg(); 10572 10573 /// Be warned: this can be a very slow operation 10574 TrueColorImage takeScreenshot(); 10575 } 10576 10577 /// 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]. 10578 void flushGui() { 10579 version(X11) { 10580 auto dpy = XDisplayConnection.get(); 10581 XLockDisplay(dpy); 10582 scope(exit) XUnlockDisplay(dpy); 10583 XFlush(dpy); 10584 } 10585 } 10586 10587 /++ 10588 Runs the given code in the GUI thread when its event loop 10589 is available, blocking until it completes. This allows you 10590 to create and manipulate windows from another thread without 10591 invoking undefined behavior. 10592 10593 If this is the gui thread, it runs the code immediately. 10594 10595 If no gui thread exists yet, the current thread is assumed 10596 to be it. Attempting to create windows or run the event loop 10597 in any other thread will cause an assertion failure. 10598 10599 10600 $(TIP 10601 Did you know you can use UFCS on delegate literals? 10602 10603 () { 10604 // code here 10605 }.runInGuiThread; 10606 ) 10607 10608 Returns: 10609 `true` if the function was called, `false` if it was not. 10610 The function may not be called because the gui thread had 10611 already terminated by the time you called this. 10612 10613 History: 10614 Added April 10, 2020 (v7.2.0) 10615 10616 Return value added and implementation tweaked to avoid locking 10617 at program termination on February 24, 2021 (v9.2.1). 10618 +/ 10619 bool runInGuiThread(scope void delegate() dg) @trusted { 10620 claimGuiThread(); 10621 10622 if(thisIsGuiThread) { 10623 dg(); 10624 return true; 10625 } 10626 10627 if(guiThreadTerminating) 10628 return false; 10629 10630 import core.sync.semaphore; 10631 static Semaphore sc; 10632 if(sc is null) 10633 sc = new Semaphore(); 10634 10635 static RunQueueMember* rqm; 10636 if(rqm is null) 10637 rqm = new RunQueueMember; 10638 rqm.dg = cast(typeof(rqm.dg)) dg; 10639 rqm.signal = sc; 10640 rqm.thrown = null; 10641 10642 synchronized(runInGuiThreadLock) { 10643 runInGuiThreadQueue ~= rqm; 10644 } 10645 10646 if(!SimpleWindow.eventWakeUp()) 10647 throw new Error("runInGuiThread impossible; eventWakeUp failed"); 10648 10649 rqm.signal.wait(); 10650 auto t = rqm.thrown; 10651 10652 if(t) 10653 throw t; 10654 10655 return true; 10656 } 10657 10658 // note it runs sync if this is the gui thread.... 10659 void runInGuiThreadAsync(void delegate() dg, void delegate(Exception) nothrow handleError = null) nothrow { 10660 claimGuiThread(); 10661 10662 try { 10663 10664 if(thisIsGuiThread) { 10665 dg(); 10666 return; 10667 } 10668 10669 if(guiThreadTerminating) 10670 return; 10671 10672 RunQueueMember* rqm = new RunQueueMember; 10673 rqm.dg = cast(typeof(rqm.dg)) dg; 10674 rqm.signal = null; 10675 rqm.thrown = null; 10676 10677 synchronized(runInGuiThreadLock) { 10678 runInGuiThreadQueue ~= rqm; 10679 } 10680 10681 if(!SimpleWindow.eventWakeUp()) 10682 throw new Error("runInGuiThread impossible; eventWakeUp failed"); 10683 } catch(Exception e) { 10684 if(handleError) 10685 handleError(e); 10686 } 10687 } 10688 10689 private void runPendingRunInGuiThreadDelegates() { 10690 more: 10691 RunQueueMember* next; 10692 synchronized(runInGuiThreadLock) { 10693 if(runInGuiThreadQueue.length) { 10694 next = runInGuiThreadQueue[0]; 10695 runInGuiThreadQueue = runInGuiThreadQueue[1 .. $]; 10696 } else { 10697 next = null; 10698 } 10699 } 10700 10701 if(next) { 10702 try { 10703 next.dg(); 10704 next.thrown = null; 10705 } catch(Throwable t) { 10706 next.thrown = t; 10707 } 10708 10709 if(next.signal) 10710 next.signal.notify(); 10711 10712 goto more; 10713 } 10714 } 10715 10716 private void claimGuiThread() nothrow { 10717 import core.atomic; 10718 if(cas(&guiThreadExists_, false, true)) 10719 thisIsGuiThread = true; 10720 } 10721 10722 private struct RunQueueMember { 10723 void delegate() dg; 10724 import core.sync.semaphore; 10725 Semaphore signal; 10726 Throwable thrown; 10727 } 10728 10729 private __gshared RunQueueMember*[] runInGuiThreadQueue; 10730 private __gshared Object runInGuiThreadLock = new Object; // intentional CTFE 10731 private bool thisIsGuiThread = false; 10732 private shared bool guiThreadExists_ = false; 10733 private shared bool guiThreadTerminating = false; 10734 10735 /++ 10736 Returns `true` if a gui thread exists, that is, a thread running the simpledisplay.d 10737 event loop. All windows must be exclusively created and managed by a single thread. 10738 10739 If no gui thread exists, simpledisplay.d will automatically adopt the current thread 10740 when you call one of its constructors. 10741 10742 If a gui thread exists, you should check [thisThreadRunningGui] to see if it is this 10743 one. If so, you can run gui functions on it. If not, don't. The helper functions 10744 [runInGuiThread] and [runInGuiThreadAsync] can be used to help you with this automatically. 10745 10746 The reason this function is available is in case you want to message pass between a gui 10747 thread and your current thread. If no gui thread exists or if this is the gui thread, 10748 you're liable to deadlock when trying to communicate since you'd end up talking to yourself. 10749 10750 History: 10751 Added December 3, 2021 (dub v10.5) 10752 +/ 10753 public bool guiThreadExists() { 10754 return guiThreadExists_; 10755 } 10756 10757 /++ 10758 Returns `true` if this thread is either running or set to be running the 10759 simpledisplay.d gui core event loop because it owns windows. 10760 10761 It is important to keep gui-related functionality in the right thread, so you will 10762 want to `runInGuiThread` when you call them (with some specific exceptions called 10763 out in those specific functions' documentation). Notably, all windows must be 10764 created and managed only from the gui thread. 10765 10766 Will return false if simpledisplay's other functions haven't been called 10767 yet; check [guiThreadExists] in addition to this. 10768 10769 History: 10770 Added December 3, 2021 (dub v10.5) 10771 +/ 10772 public bool thisThreadRunningGui() { 10773 return thisIsGuiThread; 10774 } 10775 10776 /++ 10777 Function to help temporarily print debugging info. It will bypass any stdout/err redirection 10778 and go to the controlling tty or console (attaching to the parent and/or allocating one as 10779 needed on Windows. Please note it may overwrite output from other programs in the parent and the 10780 allocated one will not survive if your program crashes. Use the `fileOverride` to print to a log 10781 file instead if you are in one of those situations). 10782 10783 It does not support outputting very many types; just strings and ints are likely to actually work. 10784 10785 It will perform very slowly and swallows any errors that may occur. Moreover, the specific output 10786 is unspecified meaning I can change it at any time. The only point of this function is to help 10787 in temporary use for printf-style debugging. It is NOT nogc, but you can use the `debug` keyword 10788 and the compiler will cheat for you. It is, however, formally nothrow and trusted to ease its use 10789 in those contexts. 10790 10791 $(WARNING 10792 I reserve the right to change this function at any time. You can use it if it helps you 10793 but do not rely on it for anything permanent. 10794 ) 10795 10796 History: 10797 Added December 3, 2021. Not formally supported under any stable tag. 10798 +/ 10799 void sdpyPrintDebugString(string fileOverride = null, T...)(T t) nothrow @trusted { 10800 try { 10801 version(Windows) { 10802 import core.sys.windows.wincon; 10803 if(!AttachConsole(ATTACH_PARENT_PROCESS)) 10804 AllocConsole(); 10805 const(char)* fn = "CONOUT$"; 10806 } else version(Posix) { 10807 const(char)* fn = "/dev/tty"; 10808 } else static assert(0, "Function not implemented for your system"); 10809 10810 if(fileOverride.length) 10811 fn = fileOverride.ptr; 10812 10813 import core.stdc.stdio; 10814 auto fp = fopen(fn, "wt"); 10815 if(fp is null) return; 10816 scope(exit) fclose(fp); 10817 10818 string str; 10819 foreach(item; t) { 10820 static if(is(typeof(item) : const(char)[])) 10821 str ~= item; 10822 else 10823 str ~= toInternal!string(item); 10824 str ~= " "; 10825 } 10826 str ~= "\n"; 10827 10828 fwrite(str.ptr, 1, str.length, fp); 10829 fflush(fp); 10830 } catch(Exception e) { 10831 // sorry no hope 10832 } 10833 } 10834 10835 private void guiThreadFinalize() { 10836 assert(thisIsGuiThread); 10837 10838 guiThreadTerminating = true; // don't add any more from this point on 10839 runPendingRunInGuiThreadDelegates(); 10840 } 10841 10842 /+ 10843 interface IPromise { 10844 void reportProgress(int current, int max, string message); 10845 10846 /+ // not formally in cuz of templates but still 10847 IPromise Then(); 10848 IPromise Catch(); 10849 IPromise Finally(); 10850 +/ 10851 } 10852 10853 /+ 10854 auto promise = async({ ... }); 10855 promise.Then(whatever). 10856 Then(whateverelse). 10857 Catch((exception) { }); 10858 10859 10860 A promise is run inside a fiber and it looks something like: 10861 10862 try { 10863 auto res = whatever(); 10864 auto res2 = whateverelse(res); 10865 } catch(Exception e) { 10866 { }(e); 10867 } 10868 10869 When a thing succeeds, it is passed as an arg to the next 10870 +/ 10871 class Promise(T) : IPromise { 10872 auto Then() { return null; } 10873 auto Catch() { return null; } 10874 auto Finally() { return null; } 10875 10876 // wait for it to resolve and return the value, or rethrow the error if that occurred. 10877 // cannot be called from the gui thread, but this is caught at runtime instead of compile time. 10878 T await(); 10879 } 10880 10881 interface Task { 10882 } 10883 10884 interface Resolvable(T) : Task { 10885 void run(); 10886 10887 void resolve(T); 10888 10889 Resolvable!T then(void delegate(T)); // returns a new promise 10890 Resolvable!T error(Throwable); // js catch 10891 Resolvable!T completed(); // js finally 10892 10893 } 10894 10895 /++ 10896 Runs `work` in a helper thread and sends its return value back to the main gui 10897 thread as the argument to `uponCompletion`. If `work` throws, the exception is 10898 sent to the `uponThrown` if given, or if null, rethrown from the event loop to 10899 kill the program. 10900 10901 You can call reportProgress(position, max, message) to update your parent window 10902 on your progress. 10903 10904 I should also use `shared` methods. FIXME 10905 10906 History: 10907 Added March 6, 2021 (dub version 9.3). 10908 +/ 10909 void runInWorkerThread(T)(T delegate(Task) work, void delegate(T) uponCompletion) { 10910 uponCompletion(work(null)); 10911 } 10912 10913 +/ 10914 10915 /// Used internal to dispatch events to various classes. 10916 interface CapableOfHandlingNativeEvent { 10917 NativeEventHandler getNativeEventHandler(); 10918 10919 /*private*//*protected*/ __gshared CapableOfHandlingNativeEvent[NativeWindowHandle] nativeHandleMapping; 10920 10921 version(X11) { 10922 // if this is impossible, you are allowed to just throw from it 10923 // Note: if you call it from another object, set a flag cuz the manger will call you again 10924 void recreateAfterDisconnect(); 10925 // discard any *connection specific* state, but keep enough that you 10926 // can be recreated if possible. discardConnectionState() is always called immediately 10927 // before recreateAfterDisconnect(), so you can set a flag there to decide if 10928 // you need initialization order 10929 void discardConnectionState(); 10930 } 10931 } 10932 10933 version(X11) 10934 /++ 10935 State of keys on mouse events, especially motion. 10936 10937 Do not trust the actual integer values in this, they are platform-specific. Always use the names. 10938 +/ 10939 enum ModifierState : uint { 10940 shift = 1, /// 10941 capsLock = 2, /// 10942 ctrl = 4, /// 10943 alt = 8, /// Not always available on Windows 10944 windows = 64, /// ditto 10945 numLock = 16, /// 10946 10947 leftButtonDown = 256, /// these aren't available on Windows for key events, so don't use them for that unless your app is X only. 10948 middleButtonDown = 512, /// ditto 10949 rightButtonDown = 1024, /// ditto 10950 } 10951 else version(Windows) 10952 /// ditto 10953 enum ModifierState : uint { 10954 shift = 4, /// 10955 ctrl = 8, /// 10956 10957 // i'm not sure if the next two are available 10958 alt = 256, /// not always available on Windows 10959 windows = 512, /// ditto 10960 10961 capsLock = 1024, /// 10962 numLock = 2048, /// 10963 10964 leftButtonDown = 1, /// not available on key events 10965 middleButtonDown = 16, /// ditto 10966 rightButtonDown = 2, /// ditto 10967 10968 backButtonDown = 0x20, /// not available on X 10969 forwardButtonDown = 0x40, /// ditto 10970 } 10971 else version(OSXCocoa) 10972 // FIXME FIXME NotYetImplementedException 10973 enum ModifierState : uint { 10974 shift = 1, /// 10975 capsLock = 2, /// 10976 ctrl = 4, /// 10977 alt = 8, /// Not always available on Windows 10978 windows = 64, /// ditto 10979 numLock = 16, /// 10980 10981 leftButtonDown = 256, /// these aren't available on Windows for key events, so don't use them for that unless your app is X only. 10982 middleButtonDown = 512, /// ditto 10983 rightButtonDown = 1024, /// ditto 10984 } 10985 10986 /// The names assume a right-handed mouse. These are bitwise combined on the events that use them. 10987 enum MouseButton : int { 10988 none = 0, 10989 left = 1, /// 10990 right = 2, /// 10991 middle = 4, /// 10992 wheelUp = 8, /// 10993 wheelDown = 16, /// 10994 backButton = 32, /// often found on the thumb and used for back in browsers 10995 forwardButton = 64, /// often found on the thumb and used for forward in browsers 10996 } 10997 10998 /// Corresponds to the values found in MouseEvent.buttonLinear, being equal to `core.bitop.bsf(button) + 1` 10999 enum MouseButtonLinear : ubyte { 11000 left = 1, /// 11001 right, /// 11002 middle, /// 11003 wheelUp, /// 11004 wheelDown, /// 11005 backButton, /// often found on the thumb and used for back in browsers 11006 forwardButton, /// often found on the thumb and used for forward in browsers 11007 } 11008 11009 version(X11) { 11010 // FIXME: match ASCII whenever we can. Most of it is already there, 11011 // but there's a few exceptions and mismatches with Windows 11012 11013 /// Do not trust the numeric values as they are platform-specific. Always use the symbolic name. 11014 enum Key { 11015 Escape = 0xff1b, /// 11016 F1 = 0xffbe, /// 11017 F2 = 0xffbf, /// 11018 F3 = 0xffc0, /// 11019 F4 = 0xffc1, /// 11020 F5 = 0xffc2, /// 11021 F6 = 0xffc3, /// 11022 F7 = 0xffc4, /// 11023 F8 = 0xffc5, /// 11024 F9 = 0xffc6, /// 11025 F10 = 0xffc7, /// 11026 F11 = 0xffc8, /// 11027 F12 = 0xffc9, /// 11028 PrintScreen = 0xff61, /// 11029 ScrollLock = 0xff14, /// 11030 Pause = 0xff13, /// 11031 Grave = 0x60, /// The $(BACKTICK) ~ key 11032 // number keys across the top of the keyboard 11033 N1 = 0x31, /// Number key atop the keyboard 11034 N2 = 0x32, /// 11035 N3 = 0x33, /// 11036 N4 = 0x34, /// 11037 N5 = 0x35, /// 11038 N6 = 0x36, /// 11039 N7 = 0x37, /// 11040 N8 = 0x38, /// 11041 N9 = 0x39, /// 11042 N0 = 0x30, /// 11043 Dash = 0x2d, /// 11044 Equals = 0x3d, /// 11045 Backslash = 0x5c, /// The \ | key 11046 Backspace = 0xff08, /// 11047 Insert = 0xff63, /// 11048 Home = 0xff50, /// 11049 PageUp = 0xff55, /// 11050 Delete = 0xffff, /// 11051 End = 0xff57, /// 11052 PageDown = 0xff56, /// 11053 Up = 0xff52, /// 11054 Down = 0xff54, /// 11055 Left = 0xff51, /// 11056 Right = 0xff53, /// 11057 11058 Tab = 0xff09, /// 11059 Q = 0x71, /// 11060 W = 0x77, /// 11061 E = 0x65, /// 11062 R = 0x72, /// 11063 T = 0x74, /// 11064 Y = 0x79, /// 11065 U = 0x75, /// 11066 I = 0x69, /// 11067 O = 0x6f, /// 11068 P = 0x70, /// 11069 LeftBracket = 0x5b, /// the [ { key 11070 RightBracket = 0x5d, /// the ] } key 11071 CapsLock = 0xffe5, /// 11072 A = 0x61, /// 11073 S = 0x73, /// 11074 D = 0x64, /// 11075 F = 0x66, /// 11076 G = 0x67, /// 11077 H = 0x68, /// 11078 J = 0x6a, /// 11079 K = 0x6b, /// 11080 L = 0x6c, /// 11081 Semicolon = 0x3b, /// 11082 Apostrophe = 0x27, /// 11083 Enter = 0xff0d, /// 11084 Shift = 0xffe1, /// 11085 Z = 0x7a, /// 11086 X = 0x78, /// 11087 C = 0x63, /// 11088 V = 0x76, /// 11089 B = 0x62, /// 11090 N = 0x6e, /// 11091 M = 0x6d, /// 11092 Comma = 0x2c, /// 11093 Period = 0x2e, /// 11094 Slash = 0x2f, /// the / ? key 11095 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 11096 Ctrl = 0xffe3, /// 11097 Windows = 0xffeb, /// 11098 Alt = 0xffe9, /// 11099 Space = 0x20, /// 11100 Alt_r = 0xffea, /// ditto of shift_r 11101 Windows_r = 0xffec, /// 11102 Menu = 0xff67, /// 11103 Ctrl_r = 0xffe4, /// 11104 11105 NumLock = 0xff7f, /// 11106 Divide = 0xffaf, /// The / key on the number pad 11107 Multiply = 0xffaa, /// The * key on the number pad 11108 Minus = 0xffad, /// The - key on the number pad 11109 Plus = 0xffab, /// The + key on the number pad 11110 PadEnter = 0xff8d, /// Numberpad enter key 11111 Pad1 = 0xff9c, /// Numberpad keys 11112 Pad2 = 0xff99, /// 11113 Pad3 = 0xff9b, /// 11114 Pad4 = 0xff96, /// 11115 Pad5 = 0xff9d, /// 11116 Pad6 = 0xff98, /// 11117 Pad7 = 0xff95, /// 11118 Pad8 = 0xff97, /// 11119 Pad9 = 0xff9a, /// 11120 Pad0 = 0xff9e, /// 11121 PadDot = 0xff9f, /// 11122 } 11123 } else version(Windows) { 11124 // the character here is for en-us layouts and for illustration only 11125 // if you actually want to get characters, wait for character events 11126 // (the argument to your event handler is simply a dchar) 11127 // those will be converted by the OS for the right locale. 11128 11129 enum Key { 11130 Escape = 0x1b, 11131 F1 = 0x70, 11132 F2 = 0x71, 11133 F3 = 0x72, 11134 F4 = 0x73, 11135 F5 = 0x74, 11136 F6 = 0x75, 11137 F7 = 0x76, 11138 F8 = 0x77, 11139 F9 = 0x78, 11140 F10 = 0x79, 11141 F11 = 0x7a, 11142 F12 = 0x7b, 11143 PrintScreen = 0x2c, 11144 ScrollLock = 0x91, 11145 Pause = 0x13, 11146 Grave = 0xc0, 11147 // number keys across the top of the keyboard 11148 N1 = 0x31, 11149 N2 = 0x32, 11150 N3 = 0x33, 11151 N4 = 0x34, 11152 N5 = 0x35, 11153 N6 = 0x36, 11154 N7 = 0x37, 11155 N8 = 0x38, 11156 N9 = 0x39, 11157 N0 = 0x30, 11158 Dash = 0xbd, 11159 Equals = 0xbb, 11160 Backslash = 0xdc, 11161 Backspace = 0x08, 11162 Insert = 0x2d, 11163 Home = 0x24, 11164 PageUp = 0x21, 11165 Delete = 0x2e, 11166 End = 0x23, 11167 PageDown = 0x22, 11168 Up = 0x26, 11169 Down = 0x28, 11170 Left = 0x25, 11171 Right = 0x27, 11172 11173 Tab = 0x09, 11174 Q = 0x51, 11175 W = 0x57, 11176 E = 0x45, 11177 R = 0x52, 11178 T = 0x54, 11179 Y = 0x59, 11180 U = 0x55, 11181 I = 0x49, 11182 O = 0x4f, 11183 P = 0x50, 11184 LeftBracket = 0xdb, 11185 RightBracket = 0xdd, 11186 CapsLock = 0x14, 11187 A = 0x41, 11188 S = 0x53, 11189 D = 0x44, 11190 F = 0x46, 11191 G = 0x47, 11192 H = 0x48, 11193 J = 0x4a, 11194 K = 0x4b, 11195 L = 0x4c, 11196 Semicolon = 0xba, 11197 Apostrophe = 0xde, 11198 Enter = 0x0d, 11199 Shift = 0x10, 11200 Z = 0x5a, 11201 X = 0x58, 11202 C = 0x43, 11203 V = 0x56, 11204 B = 0x42, 11205 N = 0x4e, 11206 M = 0x4d, 11207 Comma = 0xbc, 11208 Period = 0xbe, 11209 Slash = 0xbf, 11210 Shift_r = 0xa1, // FIXME Note: this isn't sent on all computers, sometimes it just sends Shift, so don't rely on it 11211 Ctrl = 0x11, 11212 Windows = 0x5b, 11213 Alt = -5, // FIXME 11214 Space = 0x20, 11215 Alt_r = 0xffea, // ditto of shift_r 11216 Windows_r = 0x5c, // ditto of shift_r 11217 Menu = 0x5d, 11218 Ctrl_r = 0xa3, // ditto of shift_r 11219 11220 NumLock = 0x90, 11221 Divide = 0x6f, 11222 Multiply = 0x6a, 11223 Minus = 0x6d, 11224 Plus = 0x6b, 11225 PadEnter = -8, // FIXME 11226 Pad1 = 0x61, 11227 Pad2 = 0x62, 11228 Pad3 = 0x63, 11229 Pad4 = 0x64, 11230 Pad5 = 0x65, 11231 Pad6 = 0x66, 11232 Pad7 = 0x67, 11233 Pad8 = 0x68, 11234 Pad9 = 0x69, 11235 Pad0 = 0x60, 11236 PadDot = 0x6e, 11237 } 11238 11239 // I'm keeping this around for reference purposes 11240 // ideally all these buttons will be listed for all platforms, 11241 // but now now I'm just focusing on my US keyboard 11242 version(none) 11243 enum Key { 11244 LBUTTON = 0x01, 11245 RBUTTON = 0x02, 11246 CANCEL = 0x03, 11247 MBUTTON = 0x04, 11248 //static if (_WIN32_WINNT > = 0x500) { 11249 XBUTTON1 = 0x05, 11250 XBUTTON2 = 0x06, 11251 //} 11252 BACK = 0x08, 11253 TAB = 0x09, 11254 CLEAR = 0x0C, 11255 RETURN = 0x0D, 11256 SHIFT = 0x10, 11257 CONTROL = 0x11, 11258 MENU = 0x12, 11259 PAUSE = 0x13, 11260 CAPITAL = 0x14, 11261 KANA = 0x15, 11262 HANGEUL = 0x15, 11263 HANGUL = 0x15, 11264 JUNJA = 0x17, 11265 FINAL = 0x18, 11266 HANJA = 0x19, 11267 KANJI = 0x19, 11268 ESCAPE = 0x1B, 11269 CONVERT = 0x1C, 11270 NONCONVERT = 0x1D, 11271 ACCEPT = 0x1E, 11272 MODECHANGE = 0x1F, 11273 SPACE = 0x20, 11274 PRIOR = 0x21, 11275 NEXT = 0x22, 11276 END = 0x23, 11277 HOME = 0x24, 11278 LEFT = 0x25, 11279 UP = 0x26, 11280 RIGHT = 0x27, 11281 DOWN = 0x28, 11282 SELECT = 0x29, 11283 PRINT = 0x2A, 11284 EXECUTE = 0x2B, 11285 SNAPSHOT = 0x2C, 11286 INSERT = 0x2D, 11287 DELETE = 0x2E, 11288 HELP = 0x2F, 11289 LWIN = 0x5B, 11290 RWIN = 0x5C, 11291 APPS = 0x5D, 11292 SLEEP = 0x5F, 11293 NUMPAD0 = 0x60, 11294 NUMPAD1 = 0x61, 11295 NUMPAD2 = 0x62, 11296 NUMPAD3 = 0x63, 11297 NUMPAD4 = 0x64, 11298 NUMPAD5 = 0x65, 11299 NUMPAD6 = 0x66, 11300 NUMPAD7 = 0x67, 11301 NUMPAD8 = 0x68, 11302 NUMPAD9 = 0x69, 11303 MULTIPLY = 0x6A, 11304 ADD = 0x6B, 11305 SEPARATOR = 0x6C, 11306 SUBTRACT = 0x6D, 11307 DECIMAL = 0x6E, 11308 DIVIDE = 0x6F, 11309 F1 = 0x70, 11310 F2 = 0x71, 11311 F3 = 0x72, 11312 F4 = 0x73, 11313 F5 = 0x74, 11314 F6 = 0x75, 11315 F7 = 0x76, 11316 F8 = 0x77, 11317 F9 = 0x78, 11318 F10 = 0x79, 11319 F11 = 0x7A, 11320 F12 = 0x7B, 11321 F13 = 0x7C, 11322 F14 = 0x7D, 11323 F15 = 0x7E, 11324 F16 = 0x7F, 11325 F17 = 0x80, 11326 F18 = 0x81, 11327 F19 = 0x82, 11328 F20 = 0x83, 11329 F21 = 0x84, 11330 F22 = 0x85, 11331 F23 = 0x86, 11332 F24 = 0x87, 11333 NUMLOCK = 0x90, 11334 SCROLL = 0x91, 11335 LSHIFT = 0xA0, 11336 RSHIFT = 0xA1, 11337 LCONTROL = 0xA2, 11338 RCONTROL = 0xA3, 11339 LMENU = 0xA4, 11340 RMENU = 0xA5, 11341 //static if (_WIN32_WINNT > = 0x500) { 11342 BROWSER_BACK = 0xA6, 11343 BROWSER_FORWARD = 0xA7, 11344 BROWSER_REFRESH = 0xA8, 11345 BROWSER_STOP = 0xA9, 11346 BROWSER_SEARCH = 0xAA, 11347 BROWSER_FAVORITES = 0xAB, 11348 BROWSER_HOME = 0xAC, 11349 VOLUME_MUTE = 0xAD, 11350 VOLUME_DOWN = 0xAE, 11351 VOLUME_UP = 0xAF, 11352 MEDIA_NEXT_TRACK = 0xB0, 11353 MEDIA_PREV_TRACK = 0xB1, 11354 MEDIA_STOP = 0xB2, 11355 MEDIA_PLAY_PAUSE = 0xB3, 11356 LAUNCH_MAIL = 0xB4, 11357 LAUNCH_MEDIA_SELECT = 0xB5, 11358 LAUNCH_APP1 = 0xB6, 11359 LAUNCH_APP2 = 0xB7, 11360 //} 11361 OEM_1 = 0xBA, 11362 //static if (_WIN32_WINNT > = 0x500) { 11363 OEM_PLUS = 0xBB, 11364 OEM_COMMA = 0xBC, 11365 OEM_MINUS = 0xBD, 11366 OEM_PERIOD = 0xBE, 11367 //} 11368 OEM_2 = 0xBF, 11369 OEM_3 = 0xC0, 11370 OEM_4 = 0xDB, 11371 OEM_5 = 0xDC, 11372 OEM_6 = 0xDD, 11373 OEM_7 = 0xDE, 11374 OEM_8 = 0xDF, 11375 //static if (_WIN32_WINNT > = 0x500) { 11376 OEM_102 = 0xE2, 11377 //} 11378 PROCESSKEY = 0xE5, 11379 //static if (_WIN32_WINNT > = 0x500) { 11380 PACKET = 0xE7, 11381 //} 11382 ATTN = 0xF6, 11383 CRSEL = 0xF7, 11384 EXSEL = 0xF8, 11385 EREOF = 0xF9, 11386 PLAY = 0xFA, 11387 ZOOM = 0xFB, 11388 NONAME = 0xFC, 11389 PA1 = 0xFD, 11390 OEM_CLEAR = 0xFE, 11391 } 11392 11393 } else version(OSXCocoa) { 11394 enum Key { 11395 Escape = 53, 11396 F1 = 122, 11397 F2 = 120, 11398 F3 = 99, 11399 F4 = 118, 11400 F5 = 96, 11401 F6 = 97, 11402 F7 = 98, 11403 F8 = 100, 11404 F9 = 101, 11405 F10 = 109, 11406 F11 = 103, 11407 F12 = 111, 11408 PrintScreen = 105, 11409 ScrollLock = 107, 11410 Pause = 113, 11411 Grave = 50, 11412 // number keys across the top of the keyboard 11413 N1 = 18, 11414 N2 = 19, 11415 N3 = 20, 11416 N4 = 21, 11417 N5 = 23, 11418 N6 = 22, 11419 N7 = 26, 11420 N8 = 28, 11421 N9 = 25, 11422 N0 = 29, 11423 Dash = 27, 11424 Equals = 24, 11425 Backslash = 42, 11426 Backspace = 51, 11427 Insert = 114, 11428 Home = 115, 11429 PageUp = 116, 11430 Delete = 117, 11431 End = 119, 11432 PageDown = 121, 11433 Up = 126, 11434 Down = 125, 11435 Left = 123, 11436 Right = 124, 11437 11438 Tab = 48, 11439 Q = 12, 11440 W = 13, 11441 E = 14, 11442 R = 15, 11443 T = 17, 11444 Y = 16, 11445 U = 32, 11446 I = 34, 11447 O = 31, 11448 P = 35, 11449 LeftBracket = 33, 11450 RightBracket = 30, 11451 CapsLock = 57, 11452 A = 0, 11453 S = 1, 11454 D = 2, 11455 F = 3, 11456 G = 5, 11457 H = 4, 11458 J = 38, 11459 K = 40, 11460 L = 37, 11461 Semicolon = 41, 11462 Apostrophe = 39, 11463 Enter = 36, 11464 Shift = 56, 11465 Z = 6, 11466 X = 7, 11467 C = 8, 11468 V = 9, 11469 B = 11, 11470 N = 45, 11471 M = 46, 11472 Comma = 43, 11473 Period = 47, 11474 Slash = 44, 11475 Shift_r = -4, // FIXME Note: this isn't sent on all computers, sometimes it just sends Shift, so don't rely on it 11476 Ctrl = 59, 11477 Windows = 55, 11478 Alt = 58, 11479 Space = 49, 11480 Alt_r = -3, // ditto of shift_r 11481 Windows_r = -2, 11482 Menu = 110, 11483 Ctrl_r = -1, 11484 11485 NumLock = 1, 11486 Divide = 75, 11487 Multiply = 67, 11488 Minus = 78, 11489 Plus = 69, 11490 PadEnter = 76, 11491 Pad1 = 83, 11492 Pad2 = 84, 11493 Pad3 = 85, 11494 Pad4 = 86, 11495 Pad5 = 87, 11496 Pad6 = 88, 11497 Pad7 = 89, 11498 Pad8 = 91, 11499 Pad9 = 92, 11500 Pad0 = 82, 11501 PadDot = 65, 11502 } 11503 11504 } 11505 11506 /* Additional utilities */ 11507 11508 11509 Color fromHsl(real h, real s, real l) { 11510 return arsd.color.fromHsl([h,s,l]); 11511 } 11512 11513 11514 11515 /* ********** What follows is the system-specific implementations *********/ 11516 version(Windows) { 11517 11518 11519 // helpers for making HICONs from MemoryImages 11520 class WindowsIcon { 11521 struct Win32Icon { 11522 align(1): 11523 uint biSize; 11524 int biWidth; 11525 int biHeight; 11526 ushort biPlanes; 11527 ushort biBitCount; 11528 uint biCompression; 11529 uint biSizeImage; 11530 int biXPelsPerMeter; 11531 int biYPelsPerMeter; 11532 uint biClrUsed; 11533 uint biClrImportant; 11534 // RGBQUAD[colorCount] biColors; 11535 /* Pixels: 11536 Uint8 pixels[] 11537 */ 11538 /* Mask: 11539 Uint8 mask[] 11540 */ 11541 } 11542 11543 ubyte[] fromMemoryImage(MemoryImage mi, out int icon_len, out int width, out int height) { 11544 11545 assert(mi.width <= 256, "image too wide"); 11546 assert(mi.height <= 256, "image too tall"); 11547 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 11548 assert(mi.height % 4 == 0, "image not multiple of 4 height"); 11549 11550 int icon_plen = mi.width * mi.height * 4; 11551 int icon_mlen = mi.width * mi.height / 8; 11552 11553 int colorCount = 0; 11554 icon_len = 40 + icon_plen + icon_mlen + cast(int) RGBQUAD.sizeof * colorCount; 11555 11556 ubyte[] memory = new ubyte[](Win32Icon.sizeof + icon_plen + icon_mlen); 11557 Win32Icon* icon_win32 = cast(Win32Icon*) memory.ptr; 11558 11559 auto data = memory[Win32Icon.sizeof .. $]; 11560 11561 width = mi.width; 11562 height = mi.height; 11563 11564 auto trueColorImage = mi.getAsTrueColorImage(); 11565 11566 icon_win32.biSize = 40; 11567 icon_win32.biWidth = mi.width; 11568 icon_win32.biHeight = mi.height*2; 11569 icon_win32.biPlanes = 1; 11570 icon_win32.biBitCount = 32; 11571 icon_win32.biSizeImage = icon_plen + icon_mlen; 11572 11573 int offset = 0; 11574 int andOff = icon_plen * 8; // the and offset is in bits 11575 11576 // leaving the and mask as the default 0 so the rgba alpha blend 11577 // does its thing instead 11578 for(int y = height - 1; y >= 0; y--) { 11579 int off2 = y * width * 4; 11580 foreach(x; 0 .. width) { 11581 data[offset + 2] = trueColorImage.imageData.bytes[off2 + 0]; 11582 data[offset + 1] = trueColorImage.imageData.bytes[off2 + 1]; 11583 data[offset + 0] = trueColorImage.imageData.bytes[off2 + 2]; 11584 data[offset + 3] = trueColorImage.imageData.bytes[off2 + 3]; 11585 11586 offset += 4; 11587 off2 += 4; 11588 } 11589 } 11590 11591 return memory; 11592 } 11593 11594 this(MemoryImage mi) { 11595 int icon_len, width, height; 11596 11597 auto icon_win32 = fromMemoryImage(mi, icon_len, width, height); 11598 11599 /* 11600 PNG* png = readPnpngData); 11601 PNGHeader pngh = getHeader(png); 11602 void* icon_win32; 11603 if(pngh.depth == 4) { 11604 auto i = new Win32Icon!(16); 11605 i.fromPNG(png, pngh, icon_len, width, height); 11606 icon_win32 = i; 11607 } 11608 else if(pngh.depth == 8) { 11609 auto i = new Win32Icon!(256); 11610 i.fromPNG(png, pngh, icon_len, width, height); 11611 icon_win32 = i; 11612 } else assert(0); 11613 */ 11614 11615 hIcon = CreateIconFromResourceEx(icon_win32.ptr, icon_len, true, 0x00030000, width, height, 0); 11616 11617 if(hIcon is null) throw new WindowsApiException("CreateIconFromResourceEx", GetLastError()); 11618 } 11619 11620 ~this() { 11621 if(!thisIsGuiThread) return; // FIXME: leaks if multithreaded gc 11622 DestroyIcon(hIcon); 11623 } 11624 11625 HICON hIcon; 11626 } 11627 11628 11629 11630 11631 11632 11633 alias int delegate(HWND, UINT, WPARAM, LPARAM, out int) NativeEventHandler; 11634 alias HWND NativeWindowHandle; 11635 11636 extern(Windows) 11637 LRESULT WndProc(HWND hWnd, UINT iMessage, WPARAM wParam, LPARAM lParam) nothrow { 11638 try { 11639 if(SimpleWindow.handleNativeGlobalEvent !is null) { 11640 // it returns zero if the message is handled, so we won't do anything more there 11641 // do I like that though? 11642 int mustReturn; 11643 auto ret = SimpleWindow.handleNativeGlobalEvent(hWnd, iMessage, wParam, lParam, mustReturn); 11644 if(mustReturn) 11645 return ret; 11646 } 11647 11648 if(auto window = hWnd in CapableOfHandlingNativeEvent.nativeHandleMapping) { 11649 if(window.getNativeEventHandler !is null) { 11650 int mustReturn; 11651 auto ret = window.getNativeEventHandler()(hWnd, iMessage, wParam, lParam, mustReturn); 11652 if(mustReturn) 11653 return ret; 11654 } 11655 if(auto w = cast(SimpleWindow) (*window)) 11656 return w.windowProcedure(hWnd, iMessage, wParam, lParam); 11657 else 11658 return DefWindowProc(hWnd, iMessage, wParam, lParam); 11659 } else { 11660 return DefWindowProc(hWnd, iMessage, wParam, lParam); 11661 } 11662 } catch (Exception e) { 11663 try { 11664 sdpy_abort(e); 11665 return 0; 11666 } catch(Exception e) { assert(0); } 11667 } 11668 } 11669 11670 void sdpy_abort(Throwable e) nothrow { 11671 try 11672 MessageBoxA(null, (e.toString() ~ "\0").ptr, "Exception caught in WndProc", 0); 11673 catch(Exception e) 11674 MessageBoxA(null, "Exception.toString threw too!", "Exception caught in WndProc", 0); 11675 ExitProcess(1); 11676 } 11677 11678 mixin template NativeScreenPainterImplementation() { 11679 HDC hdc; 11680 HWND hwnd; 11681 //HDC windowHdc; 11682 HBITMAP oldBmp; 11683 11684 void create(PaintingHandle window) { 11685 hwnd = window; 11686 11687 if(auto sw = cast(SimpleWindow) this.window) { 11688 // drawing on a window, double buffer 11689 auto windowHdc = GetDC(hwnd); 11690 11691 auto buffer = sw.impl.buffer; 11692 if(buffer is null) { 11693 hdc = windowHdc; 11694 windowDc = true; 11695 } else { 11696 hdc = CreateCompatibleDC(windowHdc); 11697 11698 ReleaseDC(hwnd, windowHdc); 11699 11700 oldBmp = SelectObject(hdc, buffer); 11701 } 11702 } else { 11703 // drawing on something else, draw directly 11704 hdc = CreateCompatibleDC(null); 11705 SelectObject(hdc, window); 11706 } 11707 11708 // X doesn't draw a text background, so neither should we 11709 SetBkMode(hdc, TRANSPARENT); 11710 11711 ensureDefaultFontLoaded(); 11712 11713 if(defaultGuiFont) { 11714 SelectObject(hdc, defaultGuiFont); 11715 // DeleteObject(defaultGuiFont); 11716 } 11717 } 11718 11719 static HFONT defaultGuiFont; 11720 static void ensureDefaultFontLoaded() { 11721 static bool triedDefaultGuiFont = false; 11722 if(!triedDefaultGuiFont) { 11723 NONCLIENTMETRICS params; 11724 params.cbSize = params.sizeof; 11725 if(SystemParametersInfo(SPI_GETNONCLIENTMETRICS, params.sizeof, ¶ms, 0)) { 11726 defaultGuiFont = CreateFontIndirect(¶ms.lfMessageFont); 11727 } 11728 triedDefaultGuiFont = true; 11729 } 11730 } 11731 11732 private OperatingSystemFont _activeFont; 11733 11734 void setFont(OperatingSystemFont font) { 11735 _activeFont = font; 11736 if(font && font.font) { 11737 if(SelectObject(hdc, font.font) == HGDI_ERROR) { 11738 // error... how to handle tho? 11739 } else { 11740 11741 } 11742 } 11743 else if(defaultGuiFont) 11744 SelectObject(hdc, defaultGuiFont); 11745 } 11746 11747 arsd.color.Rectangle _clipRectangle; 11748 11749 void setClipRectangle(int x, int y, int width, int height) { 11750 auto old = _clipRectangle; 11751 _clipRectangle = arsd.color.Rectangle(Point(x, y), Size(width, height)); 11752 if(old == _clipRectangle) 11753 return; 11754 11755 if(width == 0 || height == 0) { 11756 SelectClipRgn(hdc, null); 11757 } else { 11758 auto region = CreateRectRgn(x, y, x + width, y + height); 11759 SelectClipRgn(hdc, region); 11760 DeleteObject(region); 11761 } 11762 } 11763 11764 11765 // just because we can on Windows... 11766 //void create(Image image); 11767 11768 void invalidateRect(Rectangle invalidRect) { 11769 RECT rect; 11770 rect.left = invalidRect.left; 11771 rect.right = invalidRect.right; 11772 rect.top = invalidRect.top; 11773 rect.bottom = invalidRect.bottom; 11774 InvalidateRect(hwnd, &rect, false); 11775 } 11776 bool manualInvalidations; 11777 11778 void dispose() { 11779 // FIXME: this.window.width/height is probably wrong 11780 // BitBlt(windowHdc, 0, 0, this.window.width, this.window.height, hdc, 0, 0, SRCCOPY); 11781 // ReleaseDC(hwnd, windowHdc); 11782 11783 // FIXME: it shouldn't invalidate the whole thing in all cases... it would be ideal to do this right 11784 if(cast(SimpleWindow) this.window) { 11785 if(!manualInvalidations) 11786 InvalidateRect(hwnd, cast(RECT*)null, false); // no need to erase bg as the whole thing gets bitblt'd ove 11787 } 11788 11789 if(originalPen !is null) 11790 SelectObject(hdc, originalPen); 11791 if(currentPen !is null) 11792 DeleteObject(currentPen); 11793 if(originalBrush !is null) 11794 SelectObject(hdc, originalBrush); 11795 if(currentBrush !is null) 11796 DeleteObject(currentBrush); 11797 11798 SelectObject(hdc, oldBmp); 11799 11800 if(windowDc) 11801 ReleaseDC(hwnd, hdc); 11802 else 11803 DeleteDC(hdc); 11804 11805 if(window.paintingFinishedDg !is null) 11806 window.paintingFinishedDg()(); 11807 } 11808 11809 bool windowDc; 11810 HPEN originalPen; 11811 HPEN currentPen; 11812 11813 Pen _activePen; 11814 11815 Color _outlineColor; 11816 11817 @property void pen(Pen p) { 11818 _activePen = p; 11819 _outlineColor = p.color; 11820 11821 HPEN pen; 11822 if(p.color.a == 0) { 11823 pen = GetStockObject(NULL_PEN); 11824 } else { 11825 int style = PS_SOLID; 11826 final switch(p.style) { 11827 case Pen.Style.Solid: 11828 style = PS_SOLID; 11829 break; 11830 case Pen.Style.Dashed: 11831 style = PS_DASH; 11832 break; 11833 case Pen.Style.Dotted: 11834 style = PS_DOT; 11835 break; 11836 } 11837 pen = CreatePen(style, p.width, RGB(p.color.r, p.color.g, p.color.b)); 11838 } 11839 auto orig = SelectObject(hdc, pen); 11840 if(originalPen is null) 11841 originalPen = orig; 11842 11843 if(currentPen !is null) 11844 DeleteObject(currentPen); 11845 11846 currentPen = pen; 11847 11848 // the outline is like a foreground since it's done that way on X 11849 SetTextColor(hdc, RGB(p.color.r, p.color.g, p.color.b)); 11850 11851 } 11852 11853 @property void rasterOp(RasterOp op) { 11854 int mode; 11855 final switch(op) { 11856 case RasterOp.normal: 11857 mode = R2_COPYPEN; 11858 break; 11859 case RasterOp.xor: 11860 mode = R2_XORPEN; 11861 break; 11862 } 11863 SetROP2(hdc, mode); 11864 } 11865 11866 HBRUSH originalBrush; 11867 HBRUSH currentBrush; 11868 Color _fillColor = Color(1, 1, 1, 1); // what are the odds that they'd set this?? 11869 @property void fillColor(Color c) { 11870 if(c == _fillColor) 11871 return; 11872 _fillColor = c; 11873 HBRUSH brush; 11874 if(c.a == 0) { 11875 brush = GetStockObject(HOLLOW_BRUSH); 11876 } else { 11877 brush = CreateSolidBrush(RGB(c.r, c.g, c.b)); 11878 } 11879 auto orig = SelectObject(hdc, brush); 11880 if(originalBrush is null) 11881 originalBrush = orig; 11882 11883 if(currentBrush !is null) 11884 DeleteObject(currentBrush); 11885 11886 currentBrush = brush; 11887 11888 // background color is NOT set because X doesn't draw text backgrounds 11889 // SetBkColor(hdc, RGB(255, 255, 255)); 11890 } 11891 11892 void drawImage(int x, int y, Image i, int ix, int iy, int w, int h) { 11893 BITMAP bm; 11894 11895 HDC hdcMem = CreateCompatibleDC(hdc); 11896 HBITMAP hbmOld = SelectObject(hdcMem, i.handle); 11897 11898 GetObject(i.handle, bm.sizeof, &bm); 11899 11900 // or should I AlphaBlend!??!?! 11901 BitBlt(hdc, x, y, w /* bm.bmWidth */, /*bm.bmHeight*/ h, hdcMem, ix, iy, SRCCOPY); 11902 11903 SelectObject(hdcMem, hbmOld); 11904 DeleteDC(hdcMem); 11905 } 11906 11907 void drawPixmap(Sprite s, int x, int y, int ix, int iy, int w, int h) { 11908 BITMAP bm; 11909 11910 HDC hdcMem = CreateCompatibleDC(hdc); 11911 HBITMAP hbmOld = SelectObject(hdcMem, s.handle); 11912 11913 GetObject(s.handle, bm.sizeof, &bm); 11914 11915 version(CRuntime_DigitalMars) goto noalpha; 11916 11917 // or should I AlphaBlend!??!?! note it is supposed to be premultiplied http://www.fengyuan.com/article/alphablend.html 11918 if(s.enableAlpha) { 11919 auto dw = w ? w : bm.bmWidth; 11920 auto dh = h ? h : bm.bmHeight; 11921 BLENDFUNCTION bf; 11922 bf.BlendOp = AC_SRC_OVER; 11923 bf.SourceConstantAlpha = 255; 11924 bf.AlphaFormat = AC_SRC_ALPHA; 11925 AlphaBlend(hdc, x, y, dw, dh, hdcMem, ix, iy, dw, dh, bf); 11926 } else { 11927 noalpha: 11928 BitBlt(hdc, x, y, w ? w : bm.bmWidth, h ? h : bm.bmHeight, hdcMem, ix, iy, SRCCOPY); 11929 } 11930 11931 SelectObject(hdcMem, hbmOld); 11932 DeleteDC(hdcMem); 11933 } 11934 11935 Size textSize(scope const(char)[] text) { 11936 bool dummyX; 11937 if(text.length == 0) { 11938 text = " "; 11939 dummyX = true; 11940 } 11941 RECT rect; 11942 WCharzBuffer buffer = WCharzBuffer(text); 11943 DrawTextW(hdc, buffer.ptr, cast(int) buffer.length, &rect, DT_CALCRECT | DT_NOPREFIX); 11944 return Size(dummyX ? 0 : rect.right, rect.bottom); 11945 } 11946 11947 void drawText(int x, int y, int x2, int y2, scope const(char)[] text, uint alignment) { 11948 if(text.length && text[$-1] == '\n') 11949 text = text[0 .. $-1]; // tailing newlines are weird on windows... 11950 if(text.length && text[$-1] == '\r') 11951 text = text[0 .. $-1]; 11952 11953 WCharzBuffer buffer = WCharzBuffer(text, WindowsStringConversionFlags.convertNewLines); 11954 if(x2 == 0 && y2 == 0) { 11955 TextOutW(hdc, x, y, buffer.ptr, cast(int) buffer.length); 11956 } else { 11957 RECT rect; 11958 rect.left = x; 11959 rect.top = y; 11960 rect.right = x2; 11961 rect.bottom = y2; 11962 11963 uint mode = DT_LEFT; 11964 if(alignment & TextAlignment.Right) 11965 mode = DT_RIGHT; 11966 else if(alignment & TextAlignment.Center) 11967 mode = DT_CENTER; 11968 11969 // FIXME: vcenter on windows only works with single line, but I want it to work in all cases 11970 if(alignment & TextAlignment.VerticalCenter) 11971 mode |= DT_VCENTER | DT_SINGLELINE; 11972 11973 DrawTextW(hdc, buffer.ptr, cast(int) buffer.length, &rect, mode | DT_NOPREFIX); 11974 } 11975 11976 /* 11977 uint mode; 11978 11979 if(alignment & TextAlignment.Center) 11980 mode = TA_CENTER; 11981 11982 SetTextAlign(hdc, mode); 11983 */ 11984 } 11985 11986 int fontHeight() { 11987 TEXTMETRIC metric; 11988 if(GetTextMetricsW(hdc, &metric)) { 11989 return metric.tmHeight; 11990 } 11991 11992 return 16; // idk just guessing here, maybe we should throw 11993 } 11994 11995 void drawPixel(int x, int y) { 11996 SetPixel(hdc, x, y, RGB(_activePen.color.r, _activePen.color.g, _activePen.color.b)); 11997 } 11998 11999 // The basic shapes, outlined 12000 12001 void drawLine(int x1, int y1, int x2, int y2) { 12002 MoveToEx(hdc, x1, y1, null); 12003 LineTo(hdc, x2, y2); 12004 } 12005 12006 void drawRectangle(int x, int y, int width, int height) { 12007 // FIXME: with a wider pen this might not draw quite right. im not sure. 12008 gdi.Rectangle(hdc, x, y, x + width, y + height); 12009 } 12010 12011 void drawRectangleRounded(Point upperLeft, Point lowerRight, int borderRadius) { 12012 RoundRect( 12013 hdc, 12014 upperLeft.x, upperLeft.y, 12015 lowerRight.x, lowerRight.y, 12016 borderRadius, borderRadius 12017 ); 12018 } 12019 12020 /// Arguments are the points of the bounding rectangle 12021 void drawEllipse(int x1, int y1, int x2, int y2) { 12022 Ellipse(hdc, x1, y1, x2, y2); 12023 } 12024 12025 void drawArc(int x1, int y1, int width, int height, int start, int length) { 12026 //if(length > 360*64) 12027 //length = 360*64; 12028 12029 if((start == 0 && length == 360*64)) { 12030 drawEllipse(x1, y1, x1 + width, y1 + height); 12031 } else { 12032 import core.stdc.math; 12033 12034 bool clockwise = false; 12035 if(length < 0) { 12036 clockwise = true; 12037 length = -length; 12038 } 12039 12040 double startAngle = cast(double) start / 64.0 / 180.0 * 3.14159265358979323; 12041 double endAngle = cast(double) (start + length) / 64.0 / 180.0 * 3.14159265358979323; 12042 12043 auto c1 = cast(int) (cos(startAngle) * width / 2.0 + double(x1) + double(width) / 2.0); 12044 auto c2 = cast(int) (-sin(startAngle) * height / 2.0 + double(y1) + double(height) / 2.0); 12045 auto c3 = cast(int) (cos(endAngle) * width / 2.0 + double(x1) + double(width) / 2.0); 12046 auto c4 = cast(int) (-sin(endAngle) * height / 2.0 + double(y1) + double(height) / 2.0); 12047 12048 if(clockwise) { 12049 auto t1 = c1; 12050 auto t2 = c2; 12051 c1 = c3; 12052 c2 = c4; 12053 c3 = t1; 12054 c4 = t2; 12055 } 12056 12057 //if(_activePen.color.a) 12058 //Arc(hdc, x1, y1, x1 + width + 0, y1 + height + 0, c1, c2, c3, c4); 12059 //if(_fillColor.a) 12060 12061 Pie(hdc, x1, y1, x1 + width + 0, y1 + height + 0, c1, c2, c3, c4); 12062 } 12063 } 12064 12065 void drawPolygon(Point[] vertexes) { 12066 POINT[] points; 12067 points.length = vertexes.length; 12068 12069 foreach(i, p; vertexes) { 12070 points[i].x = p.x; 12071 points[i].y = p.y; 12072 } 12073 12074 Polygon(hdc, points.ptr, cast(int) points.length); 12075 } 12076 } 12077 12078 12079 // Mix this into the SimpleWindow class 12080 mixin template NativeSimpleWindowImplementation() { 12081 int curHidden = 0; // counter 12082 __gshared static bool[string] knownWinClasses; 12083 static bool altPressed = false; 12084 12085 HANDLE oldCursor; 12086 12087 void hideCursor () { 12088 if(curHidden == 0) 12089 oldCursor = SetCursor(null); 12090 ++curHidden; 12091 } 12092 12093 void showCursor () { 12094 --curHidden; 12095 if(curHidden == 0) { 12096 SetCursor(currentCursor is null ? oldCursor : currentCursor); // show it immediately without waiting for mouse movement 12097 } 12098 } 12099 12100 12101 int minWidth = 0, minHeight = 0, maxWidth = int.max, maxHeight = int.max; 12102 12103 void setMinSize (int minwidth, int minheight) { 12104 minWidth = minwidth; 12105 minHeight = minheight; 12106 } 12107 void setMaxSize (int maxwidth, int maxheight) { 12108 maxWidth = maxwidth; 12109 maxHeight = maxheight; 12110 } 12111 12112 // FIXME i'm not sure that Windows has this functionality 12113 // though it is nonessential anyway. 12114 void setResizeGranularity (int granx, int grany) {} 12115 12116 ScreenPainter getPainter(bool manualInvalidations) { 12117 return ScreenPainter(this, hwnd, manualInvalidations); 12118 } 12119 12120 HBITMAP buffer; 12121 12122 void setTitle(string title) { 12123 WCharzBuffer bfr = WCharzBuffer(title); 12124 SetWindowTextW(hwnd, bfr.ptr); 12125 } 12126 12127 string getTitle() { 12128 auto len = GetWindowTextLengthW(hwnd); 12129 if (!len) 12130 return null; 12131 wchar[256] tmpBuffer; 12132 wchar[] buffer = (len <= tmpBuffer.length) ? tmpBuffer[] : new wchar[len]; 12133 auto len2 = GetWindowTextW(hwnd, buffer.ptr, cast(int) buffer.length); 12134 auto str = buffer[0 .. len2]; 12135 return makeUtf8StringFromWindowsString(str); 12136 } 12137 12138 void move(int x, int y) { 12139 RECT rect; 12140 GetWindowRect(hwnd, &rect); 12141 // move it while maintaining the same size... 12142 MoveWindow(hwnd, x, y, rect.right - rect.left, rect.bottom - rect.top, true); 12143 } 12144 12145 void resize(int w, int h) { 12146 RECT rect; 12147 GetWindowRect(hwnd, &rect); 12148 12149 RECT client; 12150 GetClientRect(hwnd, &client); 12151 12152 rect.right = rect.right - client.right + w; 12153 rect.bottom = rect.bottom - client.bottom + h; 12154 12155 // same position, new size for the client rectangle 12156 MoveWindow(hwnd, rect.left, rect.top, rect.right, rect.bottom, true); 12157 12158 updateOpenglViewportIfNeeded(w, h); 12159 } 12160 12161 void moveResize (int x, int y, int w, int h) { 12162 // what's given is the client rectangle, we need to adjust 12163 12164 RECT rect; 12165 rect.left = x; 12166 rect.top = y; 12167 rect.right = w + x; 12168 rect.bottom = h + y; 12169 if(!AdjustWindowRect(&rect, GetWindowLong(hwnd, GWL_STYLE), GetMenu(hwnd) !is null)) 12170 throw new WindowsApiException("AdjustWindowRect", GetLastError()); 12171 12172 MoveWindow(hwnd, rect.left, rect.top, rect.right - rect.left, rect.bottom - rect.top, true); 12173 updateOpenglViewportIfNeeded(w, h); 12174 if (windowResized !is null) windowResized(w, h); 12175 } 12176 12177 version(without_opengl) {} else { 12178 HGLRC ghRC; 12179 HDC ghDC; 12180 } 12181 12182 void createWindow(int width, int height, string title, OpenGlOptions opengl, SimpleWindow parent) { 12183 string cnamec; 12184 if (sdpyWindowClassStr is null) loadBinNameToWindowClassName(); 12185 if (sdpyWindowClassStr is null || sdpyWindowClassStr[0] == 0) { 12186 cnamec = "DSimpleWindow"; 12187 } else { 12188 cnamec = sdpyWindowClass; 12189 } 12190 12191 WCharzBuffer cn = WCharzBuffer(cnamec); 12192 12193 HINSTANCE hInstance = cast(HINSTANCE) GetModuleHandle(null); 12194 12195 if(cnamec !in knownWinClasses) { 12196 WNDCLASSEX wc; 12197 12198 // FIXME: I might be able to use cbWndExtra to hold the pointer back 12199 // to the object. Maybe. 12200 wc.cbSize = wc.sizeof; 12201 wc.cbClsExtra = 0; 12202 wc.cbWndExtra = 0; 12203 wc.hbrBackground = cast(HBRUSH) (COLOR_WINDOW+1); // GetStockObject(WHITE_BRUSH); 12204 wc.hCursor = LoadCursorW(null, IDC_ARROW); 12205 wc.hIcon = LoadIcon(hInstance, null); 12206 wc.hInstance = hInstance; 12207 wc.lpfnWndProc = &WndProc; 12208 wc.lpszClassName = cn.ptr; 12209 wc.hIconSm = null; 12210 wc.style = CS_HREDRAW | CS_VREDRAW | CS_DBLCLKS; 12211 if(!RegisterClassExW(&wc)) 12212 throw new WindowsApiException("RegisterClassExW", GetLastError()); 12213 knownWinClasses[cnamec] = true; 12214 } 12215 12216 int style; 12217 uint flags = WS_EX_ACCEPTFILES; // accept drag-drop files 12218 12219 // FIXME: windowType and customizationFlags 12220 final switch(windowType) { 12221 case WindowTypes.normal: 12222 if(resizability == Resizability.fixedSize) { 12223 style = WS_SYSMENU | WS_OVERLAPPED | WS_CAPTION; 12224 } else { 12225 style = WS_OVERLAPPEDWINDOW; 12226 } 12227 break; 12228 case WindowTypes.undecorated: 12229 style = WS_POPUP | WS_SYSMENU; 12230 break; 12231 case WindowTypes.eventOnly: 12232 _hidden = true; 12233 break; 12234 case WindowTypes.dropdownMenu: 12235 case WindowTypes.popupMenu: 12236 case WindowTypes.notification: 12237 style = WS_POPUP; 12238 flags |= WS_EX_NOACTIVATE; 12239 break; 12240 case WindowTypes.nestedChild: 12241 style = WS_CHILD; 12242 break; 12243 case WindowTypes.minimallyWrapped: 12244 assert(0, "construct minimally wrapped through the other ctor overlad"); 12245 } 12246 12247 if ((customizationFlags & WindowFlags.extraComposite) != 0) 12248 flags |= WS_EX_LAYERED; // composite window for better performance and effects support 12249 12250 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 12251 CW_USEDEFAULT, CW_USEDEFAULT, width, height, 12252 parent is null ? null : parent.impl.hwnd, null, hInstance, null); 12253 12254 if(!hwnd) 12255 throw new WindowsApiException("CreateWindowEx", GetLastError()); 12256 12257 if ((customizationFlags & WindowFlags.extraComposite) != 0) 12258 setOpacity(255); 12259 12260 SimpleWindow.nativeMapping[hwnd] = this; 12261 CapableOfHandlingNativeEvent.nativeHandleMapping[hwnd] = this; 12262 12263 if(windowType == WindowTypes.eventOnly) 12264 return; 12265 12266 HDC hdc = GetDC(hwnd); 12267 12268 if(!hdc) 12269 throw new WindowsApiException("GetDC", GetLastError()); 12270 12271 version(without_opengl) {} 12272 else { 12273 if(opengl == OpenGlOptions.yes) { 12274 if(!openGlLibrariesSuccessfullyLoaded) throw new Exception("OpenGL libraries did not load"); 12275 static if (SdpyIsUsingIVGLBinds) {if (glbindGetProcAddress("glHint") is null) assert(0, "GL: error loading OpenGL"); } // loads all necessary functions 12276 static if (SdpyIsUsingIVGLBinds) import iv.glbinds; // override druntime windows imports 12277 ghDC = hdc; 12278 PIXELFORMATDESCRIPTOR pfd; 12279 12280 pfd.nSize = PIXELFORMATDESCRIPTOR.sizeof; 12281 pfd.nVersion = 1; 12282 pfd.dwFlags = PFD_DRAW_TO_WINDOW | PFD_SUPPORT_OPENGL | PFD_DOUBLEBUFFER; 12283 pfd.dwLayerMask = PFD_MAIN_PLANE; 12284 pfd.iPixelType = PFD_TYPE_RGBA; 12285 pfd.cColorBits = 24; 12286 pfd.cDepthBits = 24; 12287 pfd.cAccumBits = 0; 12288 pfd.cStencilBits = 8; // any reasonable OpenGL implementation should support this anyway 12289 12290 auto pixelformat = ChoosePixelFormat(hdc, &pfd); 12291 12292 if (pixelformat == 0) 12293 throw new WindowsApiException("ChoosePixelFormat", GetLastError()); 12294 12295 if (SetPixelFormat(hdc, pixelformat, &pfd) == 0) 12296 throw new WindowsApiException("SetPixelFormat", GetLastError()); 12297 12298 if (sdpyOpenGLContextVersion && wglCreateContextAttribsARB is null) { 12299 // windoze is idiotic: we have to have OpenGL context to get function addresses 12300 // so we will create fake context to get that stupid address 12301 auto tmpcc = wglCreateContext(ghDC); 12302 if (tmpcc !is null) { 12303 scope(exit) { wglMakeCurrent(ghDC, null); wglDeleteContext(tmpcc); } 12304 wglMakeCurrent(ghDC, tmpcc); 12305 wglInitOtherFunctions(); 12306 } 12307 } 12308 12309 if (wglCreateContextAttribsARB !is null && sdpyOpenGLContextVersion) { 12310 int[9] contextAttribs = [ 12311 WGL_CONTEXT_MAJOR_VERSION_ARB, (sdpyOpenGLContextVersion>>8), 12312 WGL_CONTEXT_MINOR_VERSION_ARB, (sdpyOpenGLContextVersion&0xff), 12313 WGL_CONTEXT_PROFILE_MASK_ARB, (sdpyOpenGLContextCompatible ? WGL_CONTEXT_COMPATIBILITY_PROFILE_BIT_ARB : WGL_CONTEXT_CORE_PROFILE_BIT_ARB), 12314 // for modern context, set "forward compatibility" flag too 12315 (sdpyOpenGLContextCompatible ? 0/*None*/ : WGL_CONTEXT_FLAGS_ARB), WGL_CONTEXT_FORWARD_COMPATIBLE_BIT_ARB, 12316 0/*None*/, 12317 ]; 12318 ghRC = wglCreateContextAttribsARB(ghDC, null, contextAttribs.ptr); 12319 if (ghRC is null && sdpyOpenGLContextAllowFallback) { 12320 // activate fallback mode 12321 // 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; 12322 ghRC = wglCreateContext(ghDC); 12323 } 12324 if (ghRC is null) 12325 throw new WindowsApiException("wglCreateContextAttribsARB", GetLastError()); 12326 } else { 12327 // try to do at least something 12328 if (sdpyOpenGLContextAllowFallback || sdpyOpenGLContextVersion == 0) { 12329 sdpyOpenGLContextVersion = 0; 12330 ghRC = wglCreateContext(ghDC); 12331 } 12332 if (ghRC is null) 12333 throw new WindowsApiException("wglCreateContext", GetLastError()); 12334 } 12335 } 12336 } 12337 12338 if(opengl == OpenGlOptions.no) { 12339 buffer = CreateCompatibleBitmap(hdc, width, height); 12340 12341 auto hdcBmp = CreateCompatibleDC(hdc); 12342 // make sure it's filled with a blank slate 12343 auto oldBmp = SelectObject(hdcBmp, buffer); 12344 auto oldBrush = SelectObject(hdcBmp, GetStockObject(WHITE_BRUSH)); 12345 auto oldPen = SelectObject(hdcBmp, GetStockObject(WHITE_PEN)); 12346 gdi.Rectangle(hdcBmp, 0, 0, width, height); 12347 SelectObject(hdcBmp, oldBmp); 12348 SelectObject(hdcBmp, oldBrush); 12349 SelectObject(hdcBmp, oldPen); 12350 DeleteDC(hdcBmp); 12351 12352 bmpWidth = width; 12353 bmpHeight = height; 12354 12355 ReleaseDC(hwnd, hdc); // we keep this in opengl mode since it is a class member now 12356 } 12357 12358 // We want the window's client area to match the image size 12359 RECT rcClient, rcWindow; 12360 POINT ptDiff; 12361 GetClientRect(hwnd, &rcClient); 12362 GetWindowRect(hwnd, &rcWindow); 12363 ptDiff.x = (rcWindow.right - rcWindow.left) - rcClient.right; 12364 ptDiff.y = (rcWindow.bottom - rcWindow.top) - rcClient.bottom; 12365 MoveWindow(hwnd,rcWindow.left, rcWindow.top, width + ptDiff.x, height + ptDiff.y, true); 12366 12367 if ((customizationFlags&WindowFlags.dontAutoShow) == 0) { 12368 ShowWindow(hwnd, SW_SHOWNORMAL); 12369 } else { 12370 _hidden = true; 12371 } 12372 this._visibleForTheFirstTimeCalled = false; // hack! 12373 } 12374 12375 12376 void dispose() { 12377 if(buffer) 12378 DeleteObject(buffer); 12379 } 12380 12381 void closeWindow() { 12382 if(ghRC) { 12383 wglDeleteContext(ghRC); 12384 ghRC = null; 12385 } 12386 DestroyWindow(hwnd); 12387 } 12388 12389 bool setOpacity(ubyte alpha) { 12390 return SetLayeredWindowAttributes(hwnd, 0, alpha, LWA_ALPHA) == TRUE; 12391 } 12392 12393 HANDLE currentCursor; 12394 12395 // returns zero if it recognized the event 12396 static int triggerEvents(HWND hwnd, uint msg, WPARAM wParam, LPARAM lParam, int offsetX, int offsetY, SimpleWindow wind) { 12397 MouseEvent mouse; 12398 12399 void mouseEvent(bool isScreen, ulong mods) { 12400 auto x = LOWORD(lParam); 12401 auto y = HIWORD(lParam); 12402 if(isScreen) { 12403 POINT p; 12404 p.x = x; 12405 p.y = y; 12406 ScreenToClient(hwnd, &p); 12407 x = cast(ushort) p.x; 12408 y = cast(ushort) p.y; 12409 } 12410 12411 if(wind.resizability == Resizability.automaticallyScaleIfPossible) { 12412 x = cast(ushort)( x * wind._virtualWidth / wind._width ); 12413 y = cast(ushort)( y * wind._virtualHeight / wind._height ); 12414 } 12415 12416 mouse.x = x + offsetX; 12417 mouse.y = y + offsetY; 12418 12419 wind.mdx(mouse); 12420 mouse.modifierState = cast(int) mods; 12421 mouse.window = wind; 12422 12423 if(wind.handleMouseEvent) 12424 wind.handleMouseEvent(mouse); 12425 } 12426 12427 switch(msg) { 12428 case WM_GETMINMAXINFO: 12429 MINMAXINFO* mmi = cast(MINMAXINFO*) lParam; 12430 12431 if(wind.minWidth > 0) { 12432 RECT rect; 12433 rect.left = 100; 12434 rect.top = 100; 12435 rect.right = wind.minWidth + 100; 12436 rect.bottom = wind.minHeight + 100; 12437 if(!AdjustWindowRect(&rect, GetWindowLong(wind.hwnd, GWL_STYLE), GetMenu(wind.hwnd) !is null)) 12438 throw new WindowsApiException("AdjustWindowRect", GetLastError()); 12439 12440 mmi.ptMinTrackSize.x = rect.right - rect.left; 12441 mmi.ptMinTrackSize.y = rect.bottom - rect.top; 12442 } 12443 12444 if(wind.maxWidth < int.max) { 12445 RECT rect; 12446 rect.left = 100; 12447 rect.top = 100; 12448 rect.right = wind.maxWidth + 100; 12449 rect.bottom = wind.maxHeight + 100; 12450 if(!AdjustWindowRect(&rect, GetWindowLong(wind.hwnd, GWL_STYLE), GetMenu(wind.hwnd) !is null)) 12451 throw new WindowsApiException("AdjustWindowRect", GetLastError()); 12452 12453 mmi.ptMaxTrackSize.x = rect.right - rect.left; 12454 mmi.ptMaxTrackSize.y = rect.bottom - rect.top; 12455 } 12456 break; 12457 case WM_CHAR: 12458 wchar c = cast(wchar) wParam; 12459 if(wind.handleCharEvent) 12460 wind.handleCharEvent(cast(dchar) c); 12461 break; 12462 case WM_SETFOCUS: 12463 case WM_KILLFOCUS: 12464 wind._focused = (msg == WM_SETFOCUS); 12465 if (msg == WM_SETFOCUS) altPressed = false; //k8: reset alt state on defocus (it is better than nothing...) 12466 if(wind.onFocusChange) 12467 wind.onFocusChange(msg == WM_SETFOCUS); 12468 break; 12469 12470 case WM_SYSKEYDOWN: 12471 goto case; 12472 case WM_SYSKEYUP: 12473 if(lParam & (1 << 29)) { 12474 goto case; 12475 } else { 12476 // no window has keyboard focus 12477 goto default; 12478 } 12479 case WM_KEYDOWN: 12480 case WM_KEYUP: 12481 KeyEvent ev; 12482 ev.key = cast(Key) wParam; 12483 ev.pressed = (msg == WM_KEYDOWN || msg == WM_SYSKEYDOWN); 12484 if (wParam == 0x12) ev.key = Key.Alt; // windows does it this way 12485 12486 ev.hardwareCode = (lParam & 0xff0000) >> 16; 12487 12488 if(GetKeyState(Key.Shift)&0x8000 || GetKeyState(Key.Shift_r)&0x8000) 12489 ev.modifierState |= ModifierState.shift; 12490 //k8: this doesn't work; thanks for nothing, windows 12491 /*if(GetKeyState(Key.Alt)&0x8000 || GetKeyState(Key.Alt_r)&0x8000) 12492 ev.modifierState |= ModifierState.alt;*/ 12493 // this never seems to actually be set 12494 // if (lParam & 0x2000 /* KF_ALTDOWN */) ev.modifierState |= ModifierState.alt; else ev.modifierState &= ~ModifierState.alt; 12495 12496 if (wParam == 0x12) { 12497 altPressed = (msg == WM_SYSKEYDOWN); 12498 } 12499 12500 if(msg == WM_KEYDOWN || msg == WM_KEYUP) { 12501 altPressed = false; 12502 } 12503 // sdpyPrintDebugString(altPressed ? "alt down" : " up "); 12504 12505 if (altPressed) ev.modifierState |= ModifierState.alt; else ev.modifierState &= ~ModifierState.alt; 12506 if(GetKeyState(Key.Ctrl)&0x8000 || GetKeyState(Key.Ctrl_r)&0x8000) 12507 ev.modifierState |= ModifierState.ctrl; 12508 if(GetKeyState(Key.Windows)&0x8000 || GetKeyState(Key.Windows_r)&0x8000) 12509 ev.modifierState |= ModifierState.windows; 12510 if(GetKeyState(Key.NumLock)) 12511 ev.modifierState |= ModifierState.numLock; 12512 if(GetKeyState(Key.CapsLock)) 12513 ev.modifierState |= ModifierState.capsLock; 12514 12515 /+ 12516 // we always want to send the character too, so let's convert it 12517 ubyte[256] state; 12518 wchar[16] buffer; 12519 GetKeyboardState(state.ptr); 12520 ToUnicodeEx(wParam, lParam, state.ptr, buffer.ptr, buffer.length, 0, null); 12521 12522 foreach(dchar d; buffer) { 12523 ev.character = d; 12524 break; 12525 } 12526 +/ 12527 12528 ev.window = wind; 12529 if(wind.handleKeyEvent) 12530 wind.handleKeyEvent(ev); 12531 break; 12532 case 0x020a /*WM_MOUSEWHEEL*/: 12533 // send click 12534 mouse.type = cast(MouseEventType) 1; 12535 mouse.button = ((GET_WHEEL_DELTA_WPARAM(wParam) > 0) ? MouseButton.wheelUp : MouseButton.wheelDown); 12536 mouseEvent(true, LOWORD(wParam)); 12537 12538 // also send release 12539 mouse.type = cast(MouseEventType) 2; 12540 mouse.button = ((GET_WHEEL_DELTA_WPARAM(wParam) > 0) ? MouseButton.wheelUp : MouseButton.wheelDown); 12541 mouseEvent(true, LOWORD(wParam)); 12542 break; 12543 case WM_MOUSEMOVE: 12544 mouse.type = cast(MouseEventType) 0; 12545 mouseEvent(false, wParam); 12546 break; 12547 case WM_LBUTTONDOWN: 12548 case WM_LBUTTONDBLCLK: 12549 mouse.type = cast(MouseEventType) 1; 12550 mouse.button = MouseButton.left; 12551 mouse.doubleClick = msg == WM_LBUTTONDBLCLK; 12552 mouseEvent(false, wParam); 12553 break; 12554 case WM_LBUTTONUP: 12555 mouse.type = cast(MouseEventType) 2; 12556 mouse.button = MouseButton.left; 12557 mouseEvent(false, wParam); 12558 break; 12559 case WM_RBUTTONDOWN: 12560 case WM_RBUTTONDBLCLK: 12561 mouse.type = cast(MouseEventType) 1; 12562 mouse.button = MouseButton.right; 12563 mouse.doubleClick = msg == WM_RBUTTONDBLCLK; 12564 mouseEvent(false, wParam); 12565 break; 12566 case WM_RBUTTONUP: 12567 mouse.type = cast(MouseEventType) 2; 12568 mouse.button = MouseButton.right; 12569 mouseEvent(false, wParam); 12570 break; 12571 case WM_MBUTTONDOWN: 12572 case WM_MBUTTONDBLCLK: 12573 mouse.type = cast(MouseEventType) 1; 12574 mouse.button = MouseButton.middle; 12575 mouse.doubleClick = msg == WM_MBUTTONDBLCLK; 12576 mouseEvent(false, wParam); 12577 break; 12578 case WM_MBUTTONUP: 12579 mouse.type = cast(MouseEventType) 2; 12580 mouse.button = MouseButton.middle; 12581 mouseEvent(false, wParam); 12582 break; 12583 case WM_XBUTTONDOWN: 12584 case WM_XBUTTONDBLCLK: 12585 mouse.type = cast(MouseEventType) 1; 12586 mouse.button = HIWORD(wParam) == 1 ? MouseButton.backButton : MouseButton.forwardButton; 12587 mouse.doubleClick = msg == WM_XBUTTONDBLCLK; 12588 mouseEvent(false, wParam); 12589 return 1; // MSDN says special treatment here, return TRUE to bypass simulation programs 12590 case WM_XBUTTONUP: 12591 mouse.type = cast(MouseEventType) 2; 12592 mouse.button = HIWORD(wParam) == 1 ? MouseButton.backButton : MouseButton.forwardButton; 12593 mouseEvent(false, wParam); 12594 return 1; // see: https://msdn.microsoft.com/en-us/library/windows/desktop/ms646246(v=vs.85).aspx 12595 12596 default: return 1; 12597 } 12598 return 0; 12599 } 12600 12601 HWND hwnd; 12602 private int oldWidth; 12603 private int oldHeight; 12604 private bool inSizeMove; 12605 12606 /++ 12607 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. 12608 12609 History: 12610 Added November 23, 2021 12611 12612 Not fully stable, may be moved out of the impl struct. 12613 12614 Default value changed to `true` on February 15, 2021 12615 +/ 12616 bool doLiveResizing = true; 12617 12618 package int bmpWidth; 12619 package int bmpHeight; 12620 12621 // the extern(Windows) wndproc should just forward to this 12622 LRESULT windowProcedure(HWND hwnd, uint msg, WPARAM wParam, LPARAM lParam) { 12623 try { 12624 assert(hwnd is this.hwnd); 12625 12626 if(triggerEvents(hwnd, msg, wParam, lParam, 0, 0, this)) 12627 switch(msg) { 12628 case WM_MENUCHAR: // menu active but key not associated with a thing. 12629 // you would ideally use this for like a search function but sdpy not that ideally designed. alas. 12630 // The main things we can do are select, execute, close, or ignore 12631 // the default is ignore, but it doesn't *just* ignore it - it also dings an audio alert to 12632 // the user. This can be a bit annoying for sdpy things so instead im overriding and setting it 12633 // to close, which can be really annoying when you hit the wrong button. but meh i think for sdpy 12634 // that's the lesser bad choice rn. Can always override by returning true in triggerEvents.... 12635 12636 // returns the value in the *high order word* of the return value 12637 // hence the << 16 12638 return 1 << 16; // MNC_CLOSE, close the menu without dinging at the user 12639 case WM_SETCURSOR: 12640 if(cast(HWND) wParam !is hwnd) 12641 return 0; // further processing elsewhere 12642 12643 if(LOWORD(lParam) == HTCLIENT && (this.curHidden > 0 || currentCursor !is null)) { 12644 SetCursor(this.curHidden > 0 ? null : currentCursor); 12645 return 1; 12646 } else { 12647 return DefWindowProc(hwnd, msg, wParam, lParam); 12648 } 12649 //break; 12650 12651 case WM_CLOSE: 12652 if (this.closeQuery !is null) this.closeQuery(); else this.close(); 12653 break; 12654 case WM_DESTROY: 12655 if (this.onDestroyed !is null) try { this.onDestroyed(); } catch (Exception e) {} // sorry 12656 SimpleWindow.nativeMapping.remove(hwnd); 12657 CapableOfHandlingNativeEvent.nativeHandleMapping.remove(hwnd); 12658 12659 bool anyImportant = false; 12660 foreach(SimpleWindow w; SimpleWindow.nativeMapping) 12661 if(w.beingOpenKeepsAppOpen) { 12662 anyImportant = true; 12663 break; 12664 } 12665 if(!anyImportant) { 12666 PostQuitMessage(0); 12667 } 12668 break; 12669 case 0x02E0 /*WM_DPICHANGED*/: 12670 this.actualDpi_ = LOWORD(wParam); // hiword is the y param but it is the same per docs 12671 12672 RECT* prcNewWindow = cast(RECT*)lParam; 12673 // docs say this is the recommended position and we should honor it 12674 SetWindowPos(hwnd, 12675 null, 12676 prcNewWindow.left, 12677 prcNewWindow.top, 12678 prcNewWindow.right - prcNewWindow.left, 12679 prcNewWindow.bottom - prcNewWindow.top, 12680 SWP_NOZORDER | SWP_NOACTIVATE); 12681 12682 // doing this because of https://github.com/microsoft/Windows-classic-samples/blob/main/Samples/DPIAwarenessPerWindow/client/DpiAwarenessContext.cpp 12683 // im not sure it is completely correct 12684 // but without it the tabs and such do look weird as things change. 12685 if(SystemParametersInfoForDpi) { 12686 LOGFONT lfText; 12687 SystemParametersInfoForDpi(SPI_GETICONTITLELOGFONT, lfText.sizeof, &lfText, FALSE, this.actualDpi_); 12688 HFONT hFontNew = CreateFontIndirect(&lfText); 12689 if (hFontNew) 12690 { 12691 //DeleteObject(hFontOld); 12692 static extern(Windows) BOOL helper(HWND hWnd, LPARAM lParam) { 12693 SendMessage(hWnd, WM_SETFONT, cast(WPARAM)lParam, MAKELPARAM(TRUE, 0)); 12694 return TRUE; 12695 } 12696 EnumChildWindows(hwnd, &helper, cast(LPARAM) hFontNew); 12697 } 12698 } 12699 12700 if(this.onDpiChanged) 12701 this.onDpiChanged(); 12702 break; 12703 case WM_ENTERIDLE: 12704 // when a menu is up, it stops normal event processing (modal message loop) 12705 // but this at least gives us a chance to SOMETIMES catch up 12706 // FIXME: I can use SetTimer while idle to keep working i think... but idk when i'd destroy it. 12707 SimpleWindow.processAllCustomEvents; 12708 SimpleWindow.processAllCustomEvents; 12709 SleepEx(0, true); 12710 break; 12711 case WM_SIZE: 12712 if(wParam == 1 /* SIZE_MINIMIZED */) 12713 break; 12714 _width = LOWORD(lParam); 12715 _height = HIWORD(lParam); 12716 12717 // I want to avoid tearing in the windows (my code is inefficient 12718 // so this is a hack around that) so while sizing, we don't trigger, 12719 // but we do want to trigger on events like mazimize. 12720 if(!inSizeMove || doLiveResizing) 12721 goto size_changed; 12722 break; 12723 /+ 12724 case WM_SIZING: 12725 writeln("size"); 12726 break; 12727 +/ 12728 // I don't like the tearing I get when redrawing on WM_SIZE 12729 // (I know there's other ways to fix that but I don't like that behavior anyway) 12730 // so instead it is going to redraw only at the end of a size. 12731 case 0x0231: /* WM_ENTERSIZEMOVE */ 12732 inSizeMove = true; 12733 break; 12734 case 0x0232: /* WM_EXITSIZEMOVE */ 12735 inSizeMove = false; 12736 12737 size_changed: 12738 12739 // nothing relevant changed, don't bother redrawing 12740 if(oldWidth == _width && oldHeight == _height) { 12741 if(msg == 0x0232) 12742 goto finalize_resize; 12743 break; 12744 } 12745 12746 // note: OpenGL windows don't use a backing bmp, so no need to change them 12747 // if resizability is anything other than allowResizing, it is meant to either stretch the one image or just do nothing 12748 if(openglMode == OpenGlOptions.no) { // && resizability == Resizability.allowResizing) { 12749 // gotta get the double buffer bmp to match the window 12750 // FIXME: could this be more efficient? it never relinquishes a large bitmap 12751 12752 // if it is auto-scaled, we keep the backing bitmap the same size all the time 12753 if(resizability != Resizability.automaticallyScaleIfPossible) 12754 if(_width > bmpWidth || _height > bmpHeight) { 12755 auto hdc = GetDC(hwnd); 12756 auto oldBuffer = buffer; 12757 buffer = CreateCompatibleBitmap(hdc, _width, _height); 12758 12759 auto hdcBmp = CreateCompatibleDC(hdc); 12760 auto oldBmp = SelectObject(hdcBmp, buffer); 12761 12762 auto hdcOldBmp = CreateCompatibleDC(hdc); 12763 auto oldOldBmp = SelectObject(hdcOldBmp, oldBuffer); 12764 12765 /+ 12766 RECT r; 12767 r.left = 0; 12768 r.top = 0; 12769 r.right = width; 12770 r.bottom = height; 12771 auto c = Color.green; 12772 auto brush = CreateSolidBrush(RGB(c.r, c.g, c.b)); 12773 FillRect(hdcBmp, &r, brush); 12774 DeleteObject(brush); 12775 +/ 12776 12777 BitBlt(hdcBmp, 0, 0, bmpWidth, bmpHeight, hdcOldBmp, 0, 0, SRCCOPY); 12778 12779 bmpWidth = _width; 12780 bmpHeight = _height; 12781 12782 SelectObject(hdcOldBmp, oldOldBmp); 12783 DeleteDC(hdcOldBmp); 12784 12785 SelectObject(hdcBmp, oldBmp); 12786 DeleteDC(hdcBmp); 12787 12788 ReleaseDC(hwnd, hdc); 12789 12790 DeleteObject(oldBuffer); 12791 } 12792 } 12793 12794 updateOpenglViewportIfNeeded(_width, _height); 12795 12796 if(resizability != Resizability.automaticallyScaleIfPossible) 12797 if(windowResized !is null) 12798 windowResized(_width, _height); 12799 12800 /+ 12801 if(inSizeMove) { 12802 // SimpleWindow.processAllCustomEvents(); 12803 // SimpleWindow.processAllCustomEvents(); 12804 12805 //RedrawWindow(hwnd, null, null, RDW_ERASE | RDW_INVALIDATE | RDW_ALLCHILDREN); 12806 //sdpyPrintDebugString("redraw b"); 12807 } else { 12808 +/ { 12809 finalize_resize: 12810 // when it is all done, make sure everything is freshly drawn or there might be 12811 // weird bugs left. 12812 SimpleWindow.processAllCustomEvents(); 12813 SimpleWindow.processAllCustomEvents(); 12814 12815 RedrawWindow(hwnd, null, null, RDW_ERASE | RDW_INVALIDATE | RDW_ALLCHILDREN); 12816 // sdpyPrintDebugString("redraw"); 12817 } 12818 12819 oldWidth = this._width; 12820 oldHeight = this._height; 12821 break; 12822 case WM_ERASEBKGND: 12823 // call `visibleForTheFirstTime` here, so we can do initialization as early as possible 12824 if (!this._visibleForTheFirstTimeCalled) { 12825 this._visibleForTheFirstTimeCalled = true; 12826 if (this.visibleForTheFirstTime !is null) { 12827 this.visibleForTheFirstTime(); 12828 } 12829 } 12830 // block it in OpenGL mode, 'cause no sane person will (or should) draw windows controls over OpenGL scene 12831 version(without_opengl) {} else { 12832 if (openglMode == OpenGlOptions.yes) return 1; 12833 } 12834 // call windows default handler, so it can paint standard controls 12835 goto default; 12836 case WM_CTLCOLORBTN: 12837 case WM_CTLCOLORSTATIC: 12838 SetBkMode(cast(HDC) wParam, TRANSPARENT); 12839 return cast(typeof(return)) //GetStockObject(NULL_BRUSH); 12840 GetSysColorBrush(COLOR_3DFACE); 12841 //break; 12842 case WM_SHOWWINDOW: 12843 this._visible = (wParam != 0); 12844 if (!this._visibleForTheFirstTimeCalled && this._visible) { 12845 this._visibleForTheFirstTimeCalled = true; 12846 if (this.visibleForTheFirstTime !is null) { 12847 this.visibleForTheFirstTime(); 12848 } 12849 } 12850 if (this.visibilityChanged !is null) this.visibilityChanged(this._visible); 12851 break; 12852 case WM_PAINT: { 12853 if (!this._visibleForTheFirstTimeCalled) { 12854 this._visibleForTheFirstTimeCalled = true; 12855 if (this.visibleForTheFirstTime !is null) { 12856 this.visibleForTheFirstTime(); 12857 } 12858 } 12859 12860 BITMAP bm; 12861 PAINTSTRUCT ps; 12862 12863 HDC hdc = BeginPaint(hwnd, &ps); 12864 12865 if(openglMode == OpenGlOptions.no) { 12866 12867 HDC hdcMem = CreateCompatibleDC(hdc); 12868 HBITMAP hbmOld = SelectObject(hdcMem, buffer); 12869 12870 GetObject(buffer, bm.sizeof, &bm); 12871 12872 // FIXME: only BitBlt the invalidated rectangle, not the whole thing 12873 if(resizability == Resizability.automaticallyScaleIfPossible) 12874 StretchBlt(hdc, 0, 0, this._width, this._height, hdcMem, 0, 0, bm.bmWidth, bm.bmHeight, SRCCOPY); 12875 else 12876 BitBlt(hdc, 0, 0, bm.bmWidth, bm.bmHeight, hdcMem, 0, 0, SRCCOPY); 12877 //BitBlt(hdc, ps.rcPaint.left, ps.rcPaint.top, ps.rcPaint.right - ps.rcPaint.left, ps.rcPaint.top - ps.rcPaint.bottom, hdcMem, 0, 0, SRCCOPY); 12878 12879 SelectObject(hdcMem, hbmOld); 12880 DeleteDC(hdcMem); 12881 EndPaint(hwnd, &ps); 12882 } else { 12883 EndPaint(hwnd, &ps); 12884 version(without_opengl) {} else 12885 redrawOpenGlSceneSoon(); 12886 } 12887 } break; 12888 default: 12889 return DefWindowProc(hwnd, msg, wParam, lParam); 12890 } 12891 return 0; 12892 12893 } 12894 catch(Throwable t) { 12895 sdpyPrintDebugString(t.toString); 12896 return 0; 12897 } 12898 } 12899 } 12900 12901 mixin template NativeImageImplementation() { 12902 HBITMAP handle; 12903 ubyte* rawData; 12904 12905 final: 12906 12907 Color getPixel(int x, int y) @system { 12908 auto itemsPerLine = ((cast(int) width * 3 + 3) / 4) * 4; 12909 // remember, bmps are upside down 12910 auto offset = itemsPerLine * (height - y - 1) + x * 3; 12911 12912 Color c; 12913 if(enableAlpha) 12914 c.a = rawData[offset + 3]; 12915 else 12916 c.a = 255; 12917 c.b = rawData[offset + 0]; 12918 c.g = rawData[offset + 1]; 12919 c.r = rawData[offset + 2]; 12920 c.unPremultiply(); 12921 return c; 12922 } 12923 12924 void setPixel(int x, int y, Color c) @system { 12925 auto itemsPerLine = ((cast(int) width * 3 + 3) / 4) * 4; 12926 // remember, bmps are upside down 12927 auto offset = itemsPerLine * (height - y - 1) + x * 3; 12928 12929 if(enableAlpha) 12930 c.premultiply(); 12931 12932 rawData[offset + 0] = c.b; 12933 rawData[offset + 1] = c.g; 12934 rawData[offset + 2] = c.r; 12935 if(enableAlpha) 12936 rawData[offset + 3] = c.a; 12937 } 12938 12939 void convertToRgbaBytes(ubyte[] where) @system { 12940 assert(where.length == this.width * this.height * 4); 12941 12942 auto itemsPerLine = ((cast(int) width * 3 + 3) / 4) * 4; 12943 int idx = 0; 12944 int offset = itemsPerLine * (height - 1); 12945 // remember, bmps are upside down 12946 for(int y = height - 1; y >= 0; y--) { 12947 auto offsetStart = offset; 12948 for(int x = 0; x < width; x++) { 12949 where[idx + 0] = rawData[offset + 2]; // r 12950 where[idx + 1] = rawData[offset + 1]; // g 12951 where[idx + 2] = rawData[offset + 0]; // b 12952 if(enableAlpha) { 12953 where[idx + 3] = rawData[offset + 3]; // a 12954 unPremultiplyRgba(where[idx .. idx + 4]); 12955 offset++; 12956 } else 12957 where[idx + 3] = 255; // a 12958 idx += 4; 12959 offset += 3; 12960 } 12961 12962 offset = offsetStart - itemsPerLine; 12963 } 12964 } 12965 12966 void setFromRgbaBytes(in ubyte[] what) @system { 12967 assert(what.length == this.width * this.height * 4); 12968 12969 auto itemsPerLine = enableAlpha ? (width * 4) : (((cast(int) width * 3 + 3) / 4) * 4); 12970 int idx = 0; 12971 int offset = itemsPerLine * (height - 1); 12972 // remember, bmps are upside down 12973 for(int y = height - 1; y >= 0; y--) { 12974 auto offsetStart = offset; 12975 for(int x = 0; x < width; x++) { 12976 if(enableAlpha) { 12977 auto a = what[idx + 3]; 12978 12979 rawData[offset + 2] = (a * what[idx + 0]) / 255; // r 12980 rawData[offset + 1] = (a * what[idx + 1]) / 255; // g 12981 rawData[offset + 0] = (a * what[idx + 2]) / 255; // b 12982 rawData[offset + 3] = a; // a 12983 //premultiplyBgra(rawData[offset .. offset + 4]); 12984 offset++; 12985 } else { 12986 rawData[offset + 2] = what[idx + 0]; // r 12987 rawData[offset + 1] = what[idx + 1]; // g 12988 rawData[offset + 0] = what[idx + 2]; // b 12989 } 12990 idx += 4; 12991 offset += 3; 12992 } 12993 12994 offset = offsetStart - itemsPerLine; 12995 } 12996 } 12997 12998 12999 void createImage(int width, int height, bool forcexshm=false, bool enableAlpha = false) { 13000 BITMAPINFO infoheader; 13001 infoheader.bmiHeader.biSize = infoheader.bmiHeader.sizeof; 13002 infoheader.bmiHeader.biWidth = width; 13003 infoheader.bmiHeader.biHeight = height; 13004 infoheader.bmiHeader.biPlanes = 1; 13005 infoheader.bmiHeader.biBitCount = enableAlpha ? 32: 24; 13006 infoheader.bmiHeader.biCompression = BI_RGB; 13007 13008 handle = CreateDIBSection( 13009 null, 13010 &infoheader, 13011 DIB_RGB_COLORS, 13012 cast(void**) &rawData, 13013 null, 13014 0); 13015 if(handle is null) 13016 throw new WindowsApiException("create image failed", GetLastError()); 13017 13018 } 13019 13020 void dispose() { 13021 DeleteObject(handle); 13022 } 13023 } 13024 13025 enum KEY_ESCAPE = 27; 13026 } 13027 version(X11) { 13028 /// This is the default font used. You might change this before doing anything else with 13029 /// the library if you want to try something else. Surround that in `static if(UsingSimpledisplayX11)` 13030 /// for cross-platform compatibility. 13031 //__gshared string xfontstr = "-*-dejavu sans-medium-r-*-*-12-*-*-*-*-*-*-*"; 13032 //__gshared string xfontstr = "-*-dejavu sans-medium-r-*-*-12-*-*-*-*-*-*-*"; 13033 __gshared string xfontstr = "-*-lucida-medium-r-normal-sans-12-*-*-*-*-*-*-*"; 13034 //__gshared string xfontstr = "-*-fixed-medium-r-*-*-14-*-*-*-*-*-*-*"; 13035 13036 alias int delegate(XEvent) NativeEventHandler; 13037 alias Window NativeWindowHandle; 13038 13039 enum KEY_ESCAPE = 9; 13040 13041 mixin template NativeScreenPainterImplementation() { 13042 Display* display; 13043 Drawable d; 13044 Drawable destiny; 13045 13046 // FIXME: should the gc be static too so it isn't recreated every time draw is called? 13047 GC gc; 13048 13049 __gshared bool fontAttempted; 13050 13051 __gshared XFontStruct* defaultfont; 13052 __gshared XFontSet defaultfontset; 13053 13054 XFontStruct* font; 13055 XFontSet fontset; 13056 13057 void create(PaintingHandle window) { 13058 this.display = XDisplayConnection.get(); 13059 13060 Drawable buffer = None; 13061 if(auto sw = cast(SimpleWindow) this.window) { 13062 buffer = sw.impl.buffer; 13063 this.destiny = cast(Drawable) window; 13064 } else { 13065 buffer = cast(Drawable) window; 13066 this.destiny = None; 13067 } 13068 13069 this.d = cast(Drawable) buffer; 13070 13071 auto dgc = DefaultGC(display, DefaultScreen(display)); 13072 13073 this.gc = XCreateGC(display, d, 0, null); 13074 13075 XCopyGC(display, dgc, 0xffffffff, this.gc); 13076 13077 ensureDefaultFontLoaded(); 13078 13079 font = defaultfont; 13080 fontset = defaultfontset; 13081 13082 if(font) { 13083 XSetFont(display, gc, font.fid); 13084 } 13085 } 13086 13087 static void ensureDefaultFontLoaded() { 13088 if(!fontAttempted) { 13089 auto display = XDisplayConnection.get; 13090 auto font = XLoadQueryFont(display, xfontstr.ptr); 13091 // if the user font choice fails, fixed is pretty reliable (required by X to start!) and not bad either 13092 if(font is null) { 13093 xfontstr = "-*-fixed-medium-r-*-*-13-*-*-*-*-*-*-*"; 13094 font = XLoadQueryFont(display, xfontstr.ptr); 13095 } 13096 13097 char** lol; 13098 int lol2; 13099 char* lol3; 13100 auto fontset = XCreateFontSet(display, xfontstr.ptr, &lol, &lol2, &lol3); 13101 13102 fontAttempted = true; 13103 13104 defaultfont = font; 13105 defaultfontset = fontset; 13106 } 13107 } 13108 13109 arsd.color.Rectangle _clipRectangle; 13110 void setClipRectangle(int x, int y, int width, int height) { 13111 auto old = _clipRectangle; 13112 _clipRectangle = arsd.color.Rectangle(Point(x, y), Size(width, height)); 13113 if(old == _clipRectangle) 13114 return; 13115 13116 if(width == 0 || height == 0) { 13117 XSetClipMask(display, gc, None); 13118 13119 if(xrenderPicturePainter) { 13120 13121 XRectangle[1] rects; 13122 rects[0] = XRectangle(short.min, short.min, short.max, short.max); 13123 XRenderSetPictureClipRectangles(display, xrenderPicturePainter, 0, 0, rects.ptr, cast(int) rects.length); 13124 } 13125 13126 version(with_xft) { 13127 if(xftFont is null || xftDraw is null) 13128 return; 13129 XftDrawSetClip(xftDraw, null); 13130 } 13131 } else { 13132 XRectangle[1] rects; 13133 rects[0] = XRectangle(cast(short)(x), cast(short)(y), cast(short) width, cast(short) height); 13134 XSetClipRectangles(XDisplayConnection.get, gc, 0, 0, rects.ptr, 1, 0); 13135 13136 if(xrenderPicturePainter) 13137 XRenderSetPictureClipRectangles(display, xrenderPicturePainter, 0, 0, rects.ptr, cast(int) rects.length); 13138 13139 version(with_xft) { 13140 if(xftFont is null || xftDraw is null) 13141 return; 13142 XftDrawSetClipRectangles(xftDraw, 0, 0, rects.ptr, 1); 13143 } 13144 } 13145 } 13146 13147 version(with_xft) { 13148 XftFont* xftFont; 13149 XftDraw* xftDraw; 13150 13151 XftColor xftColor; 13152 13153 void updateXftColor() { 13154 if(xftFont is null) 13155 return; 13156 13157 // not bothering with XftColorFree since p sure i don't need it on 24 bit displays.... 13158 XRenderColor colorIn = XRenderColor(_outlineColor.r * 255, _outlineColor.g * 255, _outlineColor.b * 255, _outlineColor.a * 255); 13159 13160 XftColorAllocValue( 13161 display, 13162 DefaultVisual(display, DefaultScreen(display)), 13163 DefaultColormap(display, 0), 13164 &colorIn, 13165 &xftColor 13166 ); 13167 } 13168 } 13169 13170 private OperatingSystemFont _activeFont; 13171 void setFont(OperatingSystemFont font) { 13172 _activeFont = font; 13173 version(with_xft) { 13174 if(font && font.isXft && font.xftFont) 13175 this.xftFont = font.xftFont; 13176 else 13177 this.xftFont = null; 13178 13179 if(this.xftFont) { 13180 if(xftDraw is null) { 13181 xftDraw = XftDrawCreate( 13182 display, 13183 d, 13184 DefaultVisual(display, DefaultScreen(display)), 13185 DefaultColormap(display, 0) 13186 ); 13187 13188 updateXftColor(); 13189 } 13190 13191 return; 13192 } 13193 } 13194 13195 if(font && font.font) { 13196 this.font = font.font; 13197 this.fontset = font.fontset; 13198 XSetFont(display, gc, font.font.fid); 13199 } else { 13200 this.font = defaultfont; 13201 this.fontset = defaultfontset; 13202 } 13203 13204 } 13205 13206 private Picture xrenderPicturePainter; 13207 13208 bool manualInvalidations; 13209 void invalidateRect(Rectangle invalidRect) { 13210 // FIXME if manualInvalidations 13211 } 13212 13213 void dispose() { 13214 this.rasterOp = RasterOp.normal; 13215 13216 if(xrenderPicturePainter) { 13217 XRenderFreePicture(display, xrenderPicturePainter); 13218 xrenderPicturePainter = None; 13219 } 13220 13221 // FIXME: this.window.width/height is probably wrong 13222 13223 // src x,y then dest x, y 13224 if(destiny != None) { 13225 // FIXME: if manual invalidations we can actually only copy some of the area. 13226 // if(manualInvalidations) 13227 XSetClipMask(display, gc, None); 13228 XCopyArea(display, d, destiny, gc, 0, 0, this.window.width, this.window.height, 0, 0); 13229 } 13230 13231 XFreeGC(display, gc); 13232 13233 version(with_xft) 13234 if(xftDraw) { 13235 XftDrawDestroy(xftDraw); 13236 xftDraw = null; 13237 } 13238 13239 /+ 13240 // this should prolly legit never be used since if it destroys the font handle from a OperatingSystemFont, it also ruins a reusable resource. 13241 if(font && font !is defaultfont) { 13242 XFreeFont(display, font); 13243 font = null; 13244 } 13245 if(fontset && fontset !is defaultfontset) { 13246 XFreeFontSet(display, fontset); 13247 fontset = null; 13248 } 13249 +/ 13250 XFlush(display); 13251 13252 if(window.paintingFinishedDg !is null) 13253 window.paintingFinishedDg()(); 13254 } 13255 13256 bool backgroundIsNotTransparent = true; 13257 bool foregroundIsNotTransparent = true; 13258 13259 bool _penInitialized = false; 13260 Pen _activePen; 13261 13262 Color _outlineColor; 13263 Color _fillColor; 13264 13265 @property void pen(Pen p) { 13266 if(_penInitialized && p == _activePen) { 13267 return; 13268 } 13269 _penInitialized = true; 13270 _activePen = p; 13271 _outlineColor = p.color; 13272 13273 int style; 13274 13275 byte dashLength; 13276 13277 final switch(p.style) { 13278 case Pen.Style.Solid: 13279 style = 0 /*LineSolid*/; 13280 break; 13281 case Pen.Style.Dashed: 13282 style = 1 /*LineOnOffDash*/; 13283 dashLength = 4; 13284 break; 13285 case Pen.Style.Dotted: 13286 style = 1 /*LineOnOffDash*/; 13287 dashLength = 1; 13288 break; 13289 } 13290 13291 XSetLineAttributes(display, gc, p.width, style, style == 0 ? 3 : 0, 0); 13292 if(dashLength) 13293 XSetDashes(display, gc, 0, &dashLength, 1); 13294 13295 if(p.color.a == 0) { 13296 foregroundIsNotTransparent = false; 13297 return; 13298 } 13299 13300 foregroundIsNotTransparent = true; 13301 13302 XSetForeground(display, gc, colorToX(p.color, display)); 13303 13304 version(with_xft) 13305 updateXftColor(); 13306 } 13307 13308 RasterOp _currentRasterOp; 13309 bool _currentRasterOpInitialized = false; 13310 @property void rasterOp(RasterOp op) { 13311 if(_currentRasterOpInitialized && _currentRasterOp == op) 13312 return; 13313 _currentRasterOp = op; 13314 _currentRasterOpInitialized = true; 13315 int mode; 13316 final switch(op) { 13317 case RasterOp.normal: 13318 mode = GXcopy; 13319 break; 13320 case RasterOp.xor: 13321 mode = GXxor; 13322 break; 13323 } 13324 XSetFunction(display, gc, mode); 13325 } 13326 13327 13328 bool _fillColorInitialized = false; 13329 13330 @property void fillColor(Color c) { 13331 if(_fillColorInitialized && _fillColor == c) 13332 return; // already good, no need to waste time calling it 13333 _fillColor = c; 13334 _fillColorInitialized = true; 13335 if(c.a == 0) { 13336 backgroundIsNotTransparent = false; 13337 return; 13338 } 13339 13340 backgroundIsNotTransparent = true; 13341 13342 XSetBackground(display, gc, colorToX(c, display)); 13343 13344 } 13345 13346 void swapColors() { 13347 auto tmp = _fillColor; 13348 fillColor = _outlineColor; 13349 auto newPen = _activePen; 13350 newPen.color = tmp; 13351 pen(newPen); 13352 } 13353 13354 uint colorToX(Color c, Display* display) { 13355 auto visual = DefaultVisual(display, DefaultScreen(display)); 13356 import core.bitop; 13357 uint color = 0; 13358 { 13359 auto startBit = bsf(visual.red_mask); 13360 auto lastBit = bsr(visual.red_mask); 13361 auto r = cast(uint) c.r; 13362 r >>= 7 - (lastBit - startBit); 13363 r <<= startBit; 13364 color |= r; 13365 } 13366 { 13367 auto startBit = bsf(visual.green_mask); 13368 auto lastBit = bsr(visual.green_mask); 13369 auto g = cast(uint) c.g; 13370 g >>= 7 - (lastBit - startBit); 13371 g <<= startBit; 13372 color |= g; 13373 } 13374 { 13375 auto startBit = bsf(visual.blue_mask); 13376 auto lastBit = bsr(visual.blue_mask); 13377 auto b = cast(uint) c.b; 13378 b >>= 7 - (lastBit - startBit); 13379 b <<= startBit; 13380 color |= b; 13381 } 13382 13383 13384 13385 return color; 13386 } 13387 13388 void drawImage(int x, int y, Image i, int ix, int iy, int w, int h) { 13389 // source x, source y 13390 if(ix >= i.width) return; 13391 if(iy >= i.height) return; 13392 if(ix + w > i.width) w = i.width - ix; 13393 if(iy + h > i.height) h = i.height - iy; 13394 if(i.usingXshm) 13395 XShmPutImage(display, d, gc, i.handle, ix, iy, x, y, w, h, false); 13396 else 13397 XPutImage(display, d, gc, i.handle, ix, iy, x, y, w, h); 13398 } 13399 13400 void drawPixmap(Sprite s, int x, int y, int ix, int iy, int w, int h) { 13401 if(s.enableAlpha) { 13402 // the Sprite must be created first, meaning if we're here, XRender is already loaded 13403 if(this.xrenderPicturePainter == None) { 13404 XRenderPictureAttributes attrs; 13405 // FIXME: I can prolly reuse this as long as the pixmap itself is valid. 13406 xrenderPicturePainter = XRenderCreatePicture(display, d, Sprite.RGB24, 0, &attrs); 13407 13408 // need to initialize the clip 13409 XRectangle[1] rects; 13410 rects[0] = XRectangle(cast(short)(_clipRectangle.left), cast(short)(_clipRectangle.top), cast(short) _clipRectangle.width, cast(short) _clipRectangle.height); 13411 13412 if(_clipRectangle != Rectangle.init) 13413 XRenderSetPictureClipRectangles(display, xrenderPicturePainter, 0, 0, rects.ptr, cast(int) rects.length); 13414 } 13415 13416 XRenderComposite( 13417 display, 13418 3, // PicOpOver 13419 s.xrenderPicture, 13420 None, 13421 this.xrenderPicturePainter, 13422 ix, 13423 iy, 13424 0, 13425 0, 13426 x, 13427 y, 13428 w ? w : s.width, 13429 h ? h : s.height 13430 ); 13431 } else { 13432 XCopyArea(display, s.handle, d, gc, ix, iy, w ? w : s.width, h ? h : s.height, x, y); 13433 } 13434 } 13435 13436 int fontHeight() { 13437 version(with_xft) 13438 if(xftFont !is null) 13439 return xftFont.height; 13440 if(font) 13441 return font.max_bounds.ascent + font.max_bounds.descent; 13442 return 12; // pretty common default... 13443 } 13444 13445 int textWidth(in char[] line) { 13446 version(with_xft) 13447 if(xftFont) { 13448 if(line.length == 0) 13449 return 0; 13450 XGlyphInfo extents; 13451 XftTextExtentsUtf8(display, xftFont, line.ptr, cast(int) line.length, &extents); 13452 return extents.width; 13453 } 13454 13455 if(fontset) { 13456 if(line.length == 0) 13457 return 0; 13458 XRectangle rect; 13459 Xutf8TextExtents(fontset, line.ptr, cast(int) line.length, null, &rect); 13460 13461 return rect.width; 13462 } 13463 13464 if(font) 13465 // FIXME: unicode 13466 return XTextWidth( font, line.ptr, cast(int) line.length); 13467 else 13468 return fontHeight / 2 * cast(int) line.length; // if no font is loaded, it is prolly Fixed, which is a 2:1 ratio 13469 } 13470 13471 Size textSize(in char[] text) { 13472 auto maxWidth = 0; 13473 auto lineHeight = fontHeight; 13474 int h = text.length ? 0 : lineHeight + 4; // if text is empty, it still gives the line height 13475 foreach(line; text.split('\n')) { 13476 int textWidth = this.textWidth(line); 13477 if(textWidth > maxWidth) 13478 maxWidth = textWidth; 13479 h += lineHeight + 4; 13480 } 13481 return Size(maxWidth, h); 13482 } 13483 13484 void drawText(in int x, in int y, in int x2, in int y2, in char[] originalText, in uint alignment) { 13485 const(char)[] text; 13486 version(with_xft) 13487 if(xftFont) { 13488 text = originalText; 13489 goto loaded; 13490 } 13491 13492 if(fontset) 13493 text = originalText; 13494 else { 13495 text.reserve(originalText.length); 13496 // the first 256 unicode codepoints are the same as ascii and latin-1, which is what X expects, so we can keep all those 13497 // then strip the rest so there isn't garbage 13498 foreach(dchar ch; originalText) 13499 if(ch < 256) 13500 text ~= cast(ubyte) ch; 13501 else 13502 text ~= 191; // FIXME: using a random character (upside down question mark) to fill the space 13503 } 13504 loaded: 13505 if(text.length == 0) 13506 return; 13507 13508 // FIXME: should we clip it to the bounding box? 13509 int textHeight = fontHeight; 13510 13511 auto lines = text.split('\n'); 13512 13513 const lineHeight = textHeight; 13514 textHeight *= lines.length; 13515 13516 int cy = y; 13517 13518 if(alignment & TextAlignment.VerticalBottom) { 13519 if(y2 <= 0) 13520 return; 13521 auto h = y2 - y; 13522 if(h > textHeight) { 13523 cy += h - textHeight; 13524 cy -= lineHeight / 2; 13525 } 13526 } else if(alignment & TextAlignment.VerticalCenter) { 13527 if(y2 <= 0) 13528 return; 13529 auto h = y2 - y; 13530 if(textHeight < h) { 13531 cy += (h - textHeight) / 2; 13532 //cy -= lineHeight / 4; 13533 } 13534 } 13535 13536 foreach(line; text.split('\n')) { 13537 int textWidth = this.textWidth(line); 13538 13539 int px = x, py = cy; 13540 13541 if(alignment & TextAlignment.Center) { 13542 if(x2 <= 0) 13543 return; 13544 auto w = x2 - x; 13545 if(w > textWidth) 13546 px += (w - textWidth) / 2; 13547 } else if(alignment & TextAlignment.Right) { 13548 if(x2 <= 0) 13549 return; 13550 auto pos = x2 - textWidth; 13551 if(pos > x) 13552 px = pos; 13553 } 13554 13555 version(with_xft) 13556 if(xftFont) { 13557 XftDrawStringUtf8(xftDraw, &xftColor, xftFont, px, py + xftFont.ascent, line.ptr, cast(int) line.length); 13558 13559 goto carry_on; 13560 } 13561 13562 if(fontset) 13563 Xutf8DrawString(display, d, fontset, gc, px, py + (font ? font.max_bounds.ascent : lineHeight), line.ptr, cast(int) line.length); 13564 else 13565 XDrawString(display, d, gc, px, py + (font ? font.max_bounds.ascent : lineHeight), line.ptr, cast(int) line.length); 13566 carry_on: 13567 cy += lineHeight + 4; 13568 } 13569 } 13570 13571 void drawPixel(int x, int y) { 13572 XDrawPoint(display, d, gc, x, y); 13573 } 13574 13575 // The basic shapes, outlined 13576 13577 void drawLine(int x1, int y1, int x2, int y2) { 13578 if(foregroundIsNotTransparent) 13579 XDrawLine(display, d, gc, x1, y1, x2, y2); 13580 } 13581 13582 void drawRectangle(int x, int y, int width, int height) { 13583 if(backgroundIsNotTransparent) { 13584 swapColors(); 13585 XFillRectangle(display, d, gc, x+1, y+1, width-2, height-2); // Need to ensure pixels are only drawn once... 13586 swapColors(); 13587 } 13588 // 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 13589 if(foregroundIsNotTransparent) 13590 XDrawRectangle(display, d, gc, x + _activePen.width / 2, y + _activePen.width / 2, width - 1 - _activePen.width / 2, height - 1 - _activePen.width / 2); 13591 } 13592 13593 void drawRectangleRounded(Point upperLeft, Point lowerRight, int borderRadius) { 13594 int[4] radii = borderRadius; 13595 auto r = Rectangle(upperLeft, lowerRight); 13596 13597 if(backgroundIsNotTransparent) { 13598 swapColors(); 13599 // FIXME these overlap and thus draw the pixels multiple times 13600 XFillRectangle(display, d, gc, r.left, r.top + borderRadius/2, r.width, r.height - borderRadius); 13601 XFillRectangle(display, d, gc, r.left + borderRadius/2, r.top, r.width - borderRadius, r.height); 13602 swapColors(); 13603 } 13604 13605 drawLine(r.left + borderRadius / 2, r.top, r.right - borderRadius / 2, r.top); 13606 drawLine(r.left + borderRadius / 2, r.bottom-1, r.right - borderRadius / 2, r.bottom-1); 13607 drawLine(r.left, r.top + borderRadius / 2, r.left, r.bottom - borderRadius / 2); 13608 drawLine(r.right - 1, r.top + borderRadius / 2, r.right - 1, r.bottom - borderRadius / 2); 13609 13610 //drawRectangle(r.left + borderRadius/2, r.top, r.width - borderRadius, r.height); 13611 13612 drawArc(r.upperLeft.x, r.upperLeft.y, radii[0], radii[0], 90*64, 90*64); 13613 drawArc(r.upperRight.x - radii[1], r.upperRight.y, radii[1] - 1, radii[1], 0*64, 90*64); 13614 drawArc(r.lowerLeft.x, r.lowerLeft.y - radii[2], radii[2], radii[2] - 1, 180*64, 90*64); 13615 drawArc(r.lowerRight.x - radii[3], r.lowerRight.y - radii[3], radii[3] - 1, radii[3] - 1, 270*64, 90*64); 13616 } 13617 13618 13619 /// Arguments are the points of the bounding rectangle 13620 void drawEllipse(int x1, int y1, int x2, int y2) { 13621 drawArc(x1, y1, x2 - x1, y2 - y1, 0, 360 * 64); 13622 } 13623 13624 // NOTE: start and finish are in units of degrees * 64 13625 void drawArc(int x1, int y1, int width, int height, int start, int length) { 13626 if(backgroundIsNotTransparent) { 13627 swapColors(); 13628 XFillArc(display, d, gc, x1, y1, width, height, start, length); 13629 swapColors(); 13630 } 13631 if(foregroundIsNotTransparent) { 13632 XDrawArc(display, d, gc, x1, y1, width, height, start, length); 13633 13634 // Windows draws the straight lines on the edges too so FIXME sort of 13635 } 13636 } 13637 13638 void drawPolygon(Point[] vertexes) { 13639 XPoint[16] pointsBuffer; 13640 XPoint[] points; 13641 if(vertexes.length <= pointsBuffer.length) 13642 points = pointsBuffer[0 .. vertexes.length]; 13643 else 13644 points.length = vertexes.length; 13645 13646 foreach(i, p; vertexes) { 13647 points[i].x = cast(short) p.x; 13648 points[i].y = cast(short) p.y; 13649 } 13650 13651 if(backgroundIsNotTransparent) { 13652 swapColors(); 13653 XFillPolygon(display, d, gc, points.ptr, cast(int) points.length, PolygonShape.Complex, CoordMode.CoordModeOrigin); 13654 swapColors(); 13655 } 13656 if(foregroundIsNotTransparent) { 13657 XDrawLines(display, d, gc, points.ptr, cast(int) points.length, CoordMode.CoordModeOrigin); 13658 } 13659 } 13660 } 13661 13662 /* XRender { */ 13663 13664 struct XRenderColor { 13665 ushort red; 13666 ushort green; 13667 ushort blue; 13668 ushort alpha; 13669 } 13670 13671 alias Picture = XID; 13672 alias PictFormat = XID; 13673 13674 struct XGlyphInfo { 13675 ushort width; 13676 ushort height; 13677 short x; 13678 short y; 13679 short xOff; 13680 short yOff; 13681 } 13682 13683 struct XRenderDirectFormat { 13684 short red; 13685 short redMask; 13686 short green; 13687 short greenMask; 13688 short blue; 13689 short blueMask; 13690 short alpha; 13691 short alphaMask; 13692 } 13693 13694 struct XRenderPictFormat { 13695 PictFormat id; 13696 int type; 13697 int depth; 13698 XRenderDirectFormat direct; 13699 Colormap colormap; 13700 } 13701 13702 enum PictFormatID = (1 << 0); 13703 enum PictFormatType = (1 << 1); 13704 enum PictFormatDepth = (1 << 2); 13705 enum PictFormatRed = (1 << 3); 13706 enum PictFormatRedMask =(1 << 4); 13707 enum PictFormatGreen = (1 << 5); 13708 enum PictFormatGreenMask=(1 << 6); 13709 enum PictFormatBlue = (1 << 7); 13710 enum PictFormatBlueMask =(1 << 8); 13711 enum PictFormatAlpha = (1 << 9); 13712 enum PictFormatAlphaMask=(1 << 10); 13713 enum PictFormatColormap =(1 << 11); 13714 13715 struct XRenderPictureAttributes { 13716 int repeat; 13717 Picture alpha_map; 13718 int alpha_x_origin; 13719 int alpha_y_origin; 13720 int clip_x_origin; 13721 int clip_y_origin; 13722 Pixmap clip_mask; 13723 Bool graphics_exposures; 13724 int subwindow_mode; 13725 int poly_edge; 13726 int poly_mode; 13727 Atom dither; 13728 Bool component_alpha; 13729 } 13730 13731 alias int XFixed; 13732 13733 struct XPointFixed { 13734 XFixed x, y; 13735 } 13736 13737 struct XCircle { 13738 XFixed x; 13739 XFixed y; 13740 XFixed radius; 13741 } 13742 13743 struct XTransform { 13744 XFixed[3][3] matrix; 13745 } 13746 13747 struct XFilters { 13748 int nfilter; 13749 char **filter; 13750 int nalias; 13751 short *alias_; 13752 } 13753 13754 struct XIndexValue { 13755 c_ulong pixel; 13756 ushort red, green, blue, alpha; 13757 } 13758 13759 struct XAnimCursor { 13760 Cursor cursor; 13761 c_ulong delay; 13762 } 13763 13764 struct XLinearGradient { 13765 XPointFixed p1; 13766 XPointFixed p2; 13767 } 13768 13769 struct XRadialGradient { 13770 XCircle inner; 13771 XCircle outer; 13772 } 13773 13774 struct XConicalGradient { 13775 XPointFixed center; 13776 XFixed angle; /* in degrees */ 13777 } 13778 13779 enum PictStandardARGB32 = 0; 13780 enum PictStandardRGB24 = 1; 13781 enum PictStandardA8 = 2; 13782 enum PictStandardA4 = 3; 13783 enum PictStandardA1 = 4; 13784 enum PictStandardNUM = 5; 13785 13786 interface XRender { 13787 extern(C) @nogc: 13788 13789 Bool XRenderQueryExtension (Display *dpy, int *event_basep, int *error_basep); 13790 13791 Status XRenderQueryVersion (Display *dpy, 13792 int *major_versionp, 13793 int *minor_versionp); 13794 13795 Status XRenderQueryFormats (Display *dpy); 13796 13797 int XRenderQuerySubpixelOrder (Display *dpy, int screen); 13798 13799 Bool XRenderSetSubpixelOrder (Display *dpy, int screen, int subpixel); 13800 13801 XRenderPictFormat * 13802 XRenderFindVisualFormat (Display *dpy, const Visual *visual); 13803 13804 XRenderPictFormat * 13805 XRenderFindFormat (Display *dpy, 13806 c_ulong mask, 13807 const XRenderPictFormat *templ, 13808 int count); 13809 XRenderPictFormat * 13810 XRenderFindStandardFormat (Display *dpy, 13811 int format); 13812 13813 XIndexValue * 13814 XRenderQueryPictIndexValues(Display *dpy, 13815 const XRenderPictFormat *format, 13816 int *num); 13817 13818 Picture XRenderCreatePicture( 13819 Display *dpy, 13820 Drawable drawable, 13821 const XRenderPictFormat *format, 13822 c_ulong valuemask, 13823 const XRenderPictureAttributes *attributes); 13824 13825 void XRenderChangePicture (Display *dpy, 13826 Picture picture, 13827 c_ulong valuemask, 13828 const XRenderPictureAttributes *attributes); 13829 13830 void 13831 XRenderSetPictureClipRectangles (Display *dpy, 13832 Picture picture, 13833 int xOrigin, 13834 int yOrigin, 13835 const XRectangle *rects, 13836 int n); 13837 13838 void 13839 XRenderSetPictureClipRegion (Display *dpy, 13840 Picture picture, 13841 Region r); 13842 13843 void 13844 XRenderSetPictureTransform (Display *dpy, 13845 Picture picture, 13846 XTransform *transform); 13847 13848 void 13849 XRenderFreePicture (Display *dpy, 13850 Picture picture); 13851 13852 void 13853 XRenderComposite (Display *dpy, 13854 int op, 13855 Picture src, 13856 Picture mask, 13857 Picture dst, 13858 int src_x, 13859 int src_y, 13860 int mask_x, 13861 int mask_y, 13862 int dst_x, 13863 int dst_y, 13864 uint width, 13865 uint height); 13866 13867 13868 Picture XRenderCreateSolidFill (Display *dpy, 13869 const XRenderColor *color); 13870 13871 Picture XRenderCreateLinearGradient (Display *dpy, 13872 const XLinearGradient *gradient, 13873 const XFixed *stops, 13874 const XRenderColor *colors, 13875 int nstops); 13876 13877 Picture XRenderCreateRadialGradient (Display *dpy, 13878 const XRadialGradient *gradient, 13879 const XFixed *stops, 13880 const XRenderColor *colors, 13881 int nstops); 13882 13883 Picture XRenderCreateConicalGradient (Display *dpy, 13884 const XConicalGradient *gradient, 13885 const XFixed *stops, 13886 const XRenderColor *colors, 13887 int nstops); 13888 13889 13890 13891 Cursor 13892 XRenderCreateCursor (Display *dpy, 13893 Picture source, 13894 uint x, 13895 uint y); 13896 13897 XFilters * 13898 XRenderQueryFilters (Display *dpy, Drawable drawable); 13899 13900 void 13901 XRenderSetPictureFilter (Display *dpy, 13902 Picture picture, 13903 const char *filter, 13904 XFixed *params, 13905 int nparams); 13906 13907 Cursor 13908 XRenderCreateAnimCursor (Display *dpy, 13909 int ncursor, 13910 XAnimCursor *cursors); 13911 } 13912 13913 __gshared bool XRenderLibrarySuccessfullyLoaded = true; 13914 mixin DynamicLoad!(XRender, "Xrender", 1, XRenderLibrarySuccessfullyLoaded) XRenderLibrary; 13915 13916 /* XRender } */ 13917 13918 /* Xrandr { */ 13919 13920 struct XRRMonitorInfo { 13921 Atom name; 13922 Bool primary; 13923 Bool automatic; 13924 int noutput; 13925 int x; 13926 int y; 13927 int width; 13928 int height; 13929 int mwidth; 13930 int mheight; 13931 /*RROutput*/ void *outputs; 13932 } 13933 13934 struct XRRScreenChangeNotifyEvent { 13935 int type; /* event base */ 13936 c_ulong serial; /* # of last request processed by server */ 13937 Bool send_event; /* true if this came from a SendEvent request */ 13938 Display *display; /* Display the event was read from */ 13939 Window window; /* window which selected for this event */ 13940 Window root; /* Root window for changed screen */ 13941 Time timestamp; /* when the screen change occurred */ 13942 Time config_timestamp; /* when the last configuration change */ 13943 ushort/*SizeID*/ size_index; 13944 ushort/*SubpixelOrder*/ subpixel_order; 13945 ushort/*Rotation*/ rotation; 13946 int width; 13947 int height; 13948 int mwidth; 13949 int mheight; 13950 } 13951 13952 enum RRScreenChangeNotify = 0; 13953 13954 enum RRScreenChangeNotifyMask = 1; 13955 13956 __gshared int xrrEventBase = -1; 13957 13958 13959 interface XRandr { 13960 extern(C) @nogc: 13961 Bool XRRQueryExtension (Display *dpy, int *event_base_return, int *error_base_return); 13962 Status XRRQueryVersion (Display *dpy, int *major_version_return, int *minor_version_return); 13963 13964 XRRMonitorInfo * XRRGetMonitors(Display *dpy, Window window, Bool get_active, int *nmonitors); 13965 void XRRFreeMonitors(XRRMonitorInfo *monitors); 13966 13967 void XRRSelectInput(Display *dpy, Window window, int mask); 13968 } 13969 13970 __gshared bool XRandrLibrarySuccessfullyLoaded = true; 13971 mixin DynamicLoad!(XRandr, "Xrandr", 2, XRandrLibrarySuccessfullyLoaded) XRandrLibrary; 13972 /* Xrandr } */ 13973 13974 /* Xft { */ 13975 13976 // actually freetype 13977 alias void FT_Face; 13978 13979 // actually fontconfig 13980 private alias FcBool = int; 13981 alias void FcCharSet; 13982 alias void FcPattern; 13983 alias void FcResult; 13984 enum FcEndian { FcEndianBig, FcEndianLittle } 13985 struct FcFontSet { 13986 int nfont; 13987 int sfont; 13988 FcPattern** fonts; 13989 } 13990 13991 // actually XRegion 13992 struct BOX { 13993 short x1, x2, y1, y2; 13994 } 13995 struct _XRegion { 13996 c_long size; 13997 c_long numRects; 13998 BOX* rects; 13999 BOX extents; 14000 } 14001 14002 alias Region = _XRegion*; 14003 14004 // ok actually Xft 14005 14006 struct XftFontInfo; 14007 14008 struct XftFont { 14009 int ascent; 14010 int descent; 14011 int height; 14012 int max_advance_width; 14013 FcCharSet* charset; 14014 FcPattern* pattern; 14015 } 14016 14017 struct XftDraw; 14018 14019 struct XftColor { 14020 c_ulong pixel; 14021 XRenderColor color; 14022 } 14023 14024 struct XftCharSpec { 14025 dchar ucs4; 14026 short x; 14027 short y; 14028 } 14029 14030 struct XftCharFontSpec { 14031 XftFont *font; 14032 dchar ucs4; 14033 short x; 14034 short y; 14035 } 14036 14037 struct XftGlyphSpec { 14038 uint glyph; 14039 short x; 14040 short y; 14041 } 14042 14043 struct XftGlyphFontSpec { 14044 XftFont *font; 14045 uint glyph; 14046 short x; 14047 short y; 14048 } 14049 14050 interface Xft { 14051 extern(C) @nogc pure: 14052 14053 Bool XftColorAllocName (Display *dpy, 14054 const Visual *visual, 14055 Colormap cmap, 14056 const char *name, 14057 XftColor *result); 14058 14059 Bool XftColorAllocValue (Display *dpy, 14060 Visual *visual, 14061 Colormap cmap, 14062 const XRenderColor *color, 14063 XftColor *result); 14064 14065 void XftColorFree (Display *dpy, 14066 Visual *visual, 14067 Colormap cmap, 14068 XftColor *color); 14069 14070 Bool XftDefaultHasRender (Display *dpy); 14071 14072 Bool XftDefaultSet (Display *dpy, FcPattern *defaults); 14073 14074 void XftDefaultSubstitute (Display *dpy, int screen, FcPattern *pattern); 14075 14076 XftDraw * XftDrawCreate (Display *dpy, 14077 Drawable drawable, 14078 Visual *visual, 14079 Colormap colormap); 14080 14081 XftDraw * XftDrawCreateBitmap (Display *dpy, 14082 Pixmap bitmap); 14083 14084 XftDraw * XftDrawCreateAlpha (Display *dpy, 14085 Pixmap pixmap, 14086 int depth); 14087 14088 void XftDrawChange (XftDraw *draw, 14089 Drawable drawable); 14090 14091 Display * XftDrawDisplay (XftDraw *draw); 14092 14093 Drawable XftDrawDrawable (XftDraw *draw); 14094 14095 Colormap XftDrawColormap (XftDraw *draw); 14096 14097 Visual * XftDrawVisual (XftDraw *draw); 14098 14099 void XftDrawDestroy (XftDraw *draw); 14100 14101 Picture XftDrawPicture (XftDraw *draw); 14102 14103 Picture XftDrawSrcPicture (XftDraw *draw, const XftColor *color); 14104 14105 void XftDrawGlyphs (XftDraw *draw, 14106 const XftColor *color, 14107 XftFont *pub, 14108 int x, 14109 int y, 14110 const uint *glyphs, 14111 int nglyphs); 14112 14113 void XftDrawString8 (XftDraw *draw, 14114 const XftColor *color, 14115 XftFont *pub, 14116 int x, 14117 int y, 14118 const char *string, 14119 int len); 14120 14121 void XftDrawString16 (XftDraw *draw, 14122 const XftColor *color, 14123 XftFont *pub, 14124 int x, 14125 int y, 14126 const wchar *string, 14127 int len); 14128 14129 void XftDrawString32 (XftDraw *draw, 14130 const XftColor *color, 14131 XftFont *pub, 14132 int x, 14133 int y, 14134 const dchar *string, 14135 int len); 14136 14137 void XftDrawStringUtf8 (XftDraw *draw, 14138 const XftColor *color, 14139 XftFont *pub, 14140 int x, 14141 int y, 14142 const char *string, 14143 int len); 14144 void XftDrawStringUtf16 (XftDraw *draw, 14145 const XftColor *color, 14146 XftFont *pub, 14147 int x, 14148 int y, 14149 const char *string, 14150 FcEndian endian, 14151 int len); 14152 14153 void XftDrawCharSpec (XftDraw *draw, 14154 const XftColor *color, 14155 XftFont *pub, 14156 const XftCharSpec *chars, 14157 int len); 14158 14159 void XftDrawCharFontSpec (XftDraw *draw, 14160 const XftColor *color, 14161 const XftCharFontSpec *chars, 14162 int len); 14163 14164 void XftDrawGlyphSpec (XftDraw *draw, 14165 const XftColor *color, 14166 XftFont *pub, 14167 const XftGlyphSpec *glyphs, 14168 int len); 14169 14170 void XftDrawGlyphFontSpec (XftDraw *draw, 14171 const XftColor *color, 14172 const XftGlyphFontSpec *glyphs, 14173 int len); 14174 14175 void XftDrawRect (XftDraw *draw, 14176 const XftColor *color, 14177 int x, 14178 int y, 14179 uint width, 14180 uint height); 14181 14182 Bool XftDrawSetClip (XftDraw *draw, 14183 Region r); 14184 14185 14186 Bool XftDrawSetClipRectangles (XftDraw *draw, 14187 int xOrigin, 14188 int yOrigin, 14189 const XRectangle *rects, 14190 int n); 14191 14192 void XftDrawSetSubwindowMode (XftDraw *draw, 14193 int mode); 14194 14195 void XftGlyphExtents (Display *dpy, 14196 XftFont *pub, 14197 const uint *glyphs, 14198 int nglyphs, 14199 XGlyphInfo *extents); 14200 14201 void XftTextExtents8 (Display *dpy, 14202 XftFont *pub, 14203 const char *string, 14204 int len, 14205 XGlyphInfo *extents); 14206 14207 void XftTextExtents16 (Display *dpy, 14208 XftFont *pub, 14209 const wchar *string, 14210 int len, 14211 XGlyphInfo *extents); 14212 14213 void XftTextExtents32 (Display *dpy, 14214 XftFont *pub, 14215 const dchar *string, 14216 int len, 14217 XGlyphInfo *extents); 14218 14219 void XftTextExtentsUtf8 (Display *dpy, 14220 XftFont *pub, 14221 const char *string, 14222 int len, 14223 XGlyphInfo *extents); 14224 14225 void XftTextExtentsUtf16 (Display *dpy, 14226 XftFont *pub, 14227 const char *string, 14228 FcEndian endian, 14229 int len, 14230 XGlyphInfo *extents); 14231 14232 FcPattern * XftFontMatch (Display *dpy, 14233 int screen, 14234 const FcPattern *pattern, 14235 FcResult *result); 14236 14237 XftFont * XftFontOpen (Display *dpy, int screen, ...); 14238 14239 XftFont * XftFontOpenName (Display *dpy, int screen, const char *name); 14240 14241 XftFont * XftFontOpenXlfd (Display *dpy, int screen, const char *xlfd); 14242 14243 FT_Face XftLockFace (XftFont *pub); 14244 14245 void XftUnlockFace (XftFont *pub); 14246 14247 XftFontInfo * XftFontInfoCreate (Display *dpy, const FcPattern *pattern); 14248 14249 void XftFontInfoDestroy (Display *dpy, XftFontInfo *fi); 14250 14251 dchar XftFontInfoHash (const XftFontInfo *fi); 14252 14253 FcBool XftFontInfoEqual (const XftFontInfo *a, const XftFontInfo *b); 14254 14255 XftFont * XftFontOpenInfo (Display *dpy, 14256 FcPattern *pattern, 14257 XftFontInfo *fi); 14258 14259 XftFont * XftFontOpenPattern (Display *dpy, FcPattern *pattern); 14260 14261 XftFont * XftFontCopy (Display *dpy, XftFont *pub); 14262 14263 void XftFontClose (Display *dpy, XftFont *pub); 14264 14265 FcBool XftInitFtLibrary(); 14266 void XftFontLoadGlyphs (Display *dpy, 14267 XftFont *pub, 14268 FcBool need_bitmaps, 14269 const uint *glyphs, 14270 int nglyph); 14271 14272 void XftFontUnloadGlyphs (Display *dpy, 14273 XftFont *pub, 14274 const uint *glyphs, 14275 int nglyph); 14276 14277 FcBool XftFontCheckGlyph (Display *dpy, 14278 XftFont *pub, 14279 FcBool need_bitmaps, 14280 uint glyph, 14281 uint *missing, 14282 int *nmissing); 14283 14284 FcBool XftCharExists (Display *dpy, 14285 XftFont *pub, 14286 dchar ucs4); 14287 14288 uint XftCharIndex (Display *dpy, 14289 XftFont *pub, 14290 dchar ucs4); 14291 FcBool XftInit (const char *config); 14292 14293 int XftGetVersion (); 14294 14295 FcFontSet * XftListFonts (Display *dpy, 14296 int screen, 14297 ...); 14298 14299 FcPattern *XftNameParse (const char *name); 14300 14301 void XftGlyphRender (Display *dpy, 14302 int op, 14303 Picture src, 14304 XftFont *pub, 14305 Picture dst, 14306 int srcx, 14307 int srcy, 14308 int x, 14309 int y, 14310 const uint *glyphs, 14311 int nglyphs); 14312 14313 void XftGlyphSpecRender (Display *dpy, 14314 int op, 14315 Picture src, 14316 XftFont *pub, 14317 Picture dst, 14318 int srcx, 14319 int srcy, 14320 const XftGlyphSpec *glyphs, 14321 int nglyphs); 14322 14323 void XftCharSpecRender (Display *dpy, 14324 int op, 14325 Picture src, 14326 XftFont *pub, 14327 Picture dst, 14328 int srcx, 14329 int srcy, 14330 const XftCharSpec *chars, 14331 int len); 14332 void XftGlyphFontSpecRender (Display *dpy, 14333 int op, 14334 Picture src, 14335 Picture dst, 14336 int srcx, 14337 int srcy, 14338 const XftGlyphFontSpec *glyphs, 14339 int nglyphs); 14340 14341 void XftCharFontSpecRender (Display *dpy, 14342 int op, 14343 Picture src, 14344 Picture dst, 14345 int srcx, 14346 int srcy, 14347 const XftCharFontSpec *chars, 14348 int len); 14349 14350 void XftTextRender8 (Display *dpy, 14351 int op, 14352 Picture src, 14353 XftFont *pub, 14354 Picture dst, 14355 int srcx, 14356 int srcy, 14357 int x, 14358 int y, 14359 const char *string, 14360 int len); 14361 void XftTextRender16 (Display *dpy, 14362 int op, 14363 Picture src, 14364 XftFont *pub, 14365 Picture dst, 14366 int srcx, 14367 int srcy, 14368 int x, 14369 int y, 14370 const wchar *string, 14371 int len); 14372 14373 void XftTextRender16BE (Display *dpy, 14374 int op, 14375 Picture src, 14376 XftFont *pub, 14377 Picture dst, 14378 int srcx, 14379 int srcy, 14380 int x, 14381 int y, 14382 const char *string, 14383 int len); 14384 14385 void XftTextRender16LE (Display *dpy, 14386 int op, 14387 Picture src, 14388 XftFont *pub, 14389 Picture dst, 14390 int srcx, 14391 int srcy, 14392 int x, 14393 int y, 14394 const char *string, 14395 int len); 14396 14397 void XftTextRender32 (Display *dpy, 14398 int op, 14399 Picture src, 14400 XftFont *pub, 14401 Picture dst, 14402 int srcx, 14403 int srcy, 14404 int x, 14405 int y, 14406 const dchar *string, 14407 int len); 14408 14409 void XftTextRender32BE (Display *dpy, 14410 int op, 14411 Picture src, 14412 XftFont *pub, 14413 Picture dst, 14414 int srcx, 14415 int srcy, 14416 int x, 14417 int y, 14418 const char *string, 14419 int len); 14420 14421 void XftTextRender32LE (Display *dpy, 14422 int op, 14423 Picture src, 14424 XftFont *pub, 14425 Picture dst, 14426 int srcx, 14427 int srcy, 14428 int x, 14429 int y, 14430 const char *string, 14431 int len); 14432 14433 void XftTextRenderUtf8 (Display *dpy, 14434 int op, 14435 Picture src, 14436 XftFont *pub, 14437 Picture dst, 14438 int srcx, 14439 int srcy, 14440 int x, 14441 int y, 14442 const char *string, 14443 int len); 14444 14445 void XftTextRenderUtf16 (Display *dpy, 14446 int op, 14447 Picture src, 14448 XftFont *pub, 14449 Picture dst, 14450 int srcx, 14451 int srcy, 14452 int x, 14453 int y, 14454 const char *string, 14455 FcEndian endian, 14456 int len); 14457 FcPattern * XftXlfdParse (const char *xlfd_orig, Bool ignore_scalable, Bool complete); 14458 14459 } 14460 14461 interface FontConfig { 14462 extern(C) @nogc pure: 14463 int FcPatternGetString(const FcPattern *p, const char *object, int n, char ** s); 14464 void FcFontSetDestroy(FcFontSet*); 14465 char* FcNameUnparse(const FcPattern *); 14466 } 14467 14468 mixin DynamicLoad!(Xft, "Xft", 2, librariesSuccessfullyLoaded) XftLibrary; 14469 mixin DynamicLoad!(FontConfig, "fontconfig", 1, librariesSuccessfullyLoaded) FontConfigLibrary; 14470 14471 14472 /* Xft } */ 14473 14474 class XDisconnectException : Exception { 14475 bool userRequested; 14476 this(bool userRequested = true) { 14477 this.userRequested = userRequested; 14478 super("X disconnected"); 14479 } 14480 } 14481 14482 /++ 14483 Platform-specific for X11. Traps errors for the duration of `dg`. Avoid calling this from inside a call to this. 14484 14485 Please note that it returns 14486 +/ 14487 XErrorEvent[] trapXErrors(scope void delegate() dg) { 14488 14489 static XErrorEvent[] errorBuffer; 14490 14491 static extern(C) int handler (Display* dpy, XErrorEvent* evt) nothrow { 14492 errorBuffer ~= *evt; 14493 return 0; 14494 } 14495 14496 auto savedErrorHandler = XSetErrorHandler(&handler); 14497 14498 try { 14499 dg(); 14500 } finally { 14501 XSync(XDisplayConnection.get, 0/*False*/); 14502 XSetErrorHandler(savedErrorHandler); 14503 } 14504 14505 auto bfr = errorBuffer; 14506 errorBuffer = null; 14507 14508 return bfr; 14509 } 14510 14511 /// Platform-specific for X11. A singleton class (well, all its methods are actually static... so more like a namespace) wrapping a `Display*`. 14512 class XDisplayConnection { 14513 private __gshared Display* display; 14514 private __gshared XIM xim; 14515 private __gshared char* displayName; 14516 14517 private __gshared int connectionSequence_; 14518 private __gshared bool isLocal_; 14519 14520 /// use this for lazy caching when reconnection 14521 static int connectionSequenceNumber() { return connectionSequence_; } 14522 14523 /++ 14524 Guesses if the connection appears to be local. 14525 14526 History: 14527 Added June 3, 2021 14528 +/ 14529 static @property bool isLocal() nothrow @trusted @nogc { 14530 return isLocal_; 14531 } 14532 14533 /// Attempts recreation of state, may require application assistance 14534 /// You MUST call this OUTSIDE the event loop. Let the exception kill the loop, 14535 /// then call this, and if successful, reenter the loop. 14536 static void discardAndRecreate(string newDisplayString = null) { 14537 if(insideXEventLoop) 14538 throw new Error("You MUST call discardAndRecreate from OUTSIDE the event loop"); 14539 14540 // 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 14541 auto chnenhm = CapableOfHandlingNativeEvent.nativeHandleMapping.dup; 14542 14543 foreach(handle; chnenhm) { 14544 handle.discardConnectionState(); 14545 } 14546 14547 discardState(); 14548 14549 if(newDisplayString !is null) 14550 setDisplayName(newDisplayString); 14551 14552 auto display = get(); 14553 14554 foreach(handle; chnenhm) { 14555 handle.recreateAfterDisconnect(); 14556 } 14557 } 14558 14559 private __gshared EventMask rootEventMask; 14560 14561 /++ 14562 Requests the specified input from the root window on the connection, in addition to any other request. 14563 14564 14565 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. 14566 14567 $(WARNING it calls XSelectInput itself, which will override any other root window input you have!) 14568 +/ 14569 static void addRootInput(EventMask mask) { 14570 auto old = rootEventMask; 14571 rootEventMask |= mask; 14572 get(); // to ensure display connected 14573 if(display !is null && rootEventMask != old) 14574 XSelectInput(display, RootWindow(display, DefaultScreen(display)), rootEventMask); 14575 } 14576 14577 static void discardState() { 14578 freeImages(); 14579 14580 foreach(atomPtr; interredAtoms) 14581 *atomPtr = 0; 14582 interredAtoms = null; 14583 interredAtoms.assumeSafeAppend(); 14584 14585 ScreenPainterImplementation.fontAttempted = false; 14586 ScreenPainterImplementation.defaultfont = null; 14587 ScreenPainterImplementation.defaultfontset = null; 14588 14589 Image.impl.xshmQueryCompleted = false; 14590 Image.impl._xshmAvailable = false; 14591 14592 SimpleWindow.nativeMapping = null; 14593 CapableOfHandlingNativeEvent.nativeHandleMapping = null; 14594 // GlobalHotkeyManager 14595 14596 display = null; 14597 xim = null; 14598 } 14599 14600 // Do you want to know why do we need all this horrible-looking code? See comment at the bottom. 14601 private static void createXIM () { 14602 import core.stdc.locale : setlocale, LC_ALL; 14603 import core.stdc.stdio : stderr, fprintf; 14604 import core.stdc.stdlib : free; 14605 import core.stdc.string : strdup; 14606 14607 static immutable string[3] mtry = [ "", "@im=local", "@im=" ]; 14608 14609 auto olocale = strdup(setlocale(LC_ALL, null)); 14610 setlocale(LC_ALL, (sdx_isUTF8Locale ? "" : "en_US.UTF-8")); 14611 scope(exit) { setlocale(LC_ALL, olocale); free(olocale); } 14612 14613 //fprintf(stderr, "opening IM...\n"); 14614 foreach (string s; mtry) { 14615 XSetLocaleModifiers(s.ptr); // it's safe, as `s` is string literal 14616 if ((xim = XOpenIM(display, null, null, null)) !is null) return; 14617 } 14618 fprintf(stderr, "createXIM: XOpenIM failed!\n"); 14619 } 14620 14621 // for X11 we will keep all XShm-allocated images in this list, so we can free 'em on connection closing. 14622 // we'll use glibc malloc()/free(), 'cause `unregisterImage()` can be called from object dtor. 14623 static struct ImgList { 14624 size_t img; // class; hide it from GC 14625 ImgList* next; 14626 } 14627 14628 static __gshared ImgList* imglist = null; 14629 static __gshared bool imglistLocked = false; // true: don't register and unregister images 14630 14631 static void registerImage (Image img) { 14632 if (!imglistLocked && img !is null) { 14633 import core.stdc.stdlib : malloc; 14634 auto it = cast(ImgList*)malloc(ImgList.sizeof); 14635 assert(it !is null); // do proper checks 14636 it.img = cast(size_t)cast(void*)img; 14637 it.next = imglist; 14638 imglist = it; 14639 version(sdpy_debug_xshm) { import core.stdc.stdio : printf; printf("registering image %p\n", cast(void*)img); } 14640 } 14641 } 14642 14643 static void unregisterImage (Image img) { 14644 if (!imglistLocked && img !is null) { 14645 import core.stdc.stdlib : free; 14646 ImgList* prev = null; 14647 ImgList* cur = imglist; 14648 while (cur !is null) { 14649 if (cur.img == cast(size_t)cast(void*)img) break; // i found her! 14650 prev = cur; 14651 cur = cur.next; 14652 } 14653 if (cur !is null) { 14654 if (prev is null) imglist = cur.next; else prev.next = cur.next; 14655 free(cur); 14656 version(sdpy_debug_xshm) { import core.stdc.stdio : printf; printf("unregistering image %p\n", cast(void*)img); } 14657 } else { 14658 version(sdpy_debug_xshm) { import core.stdc.stdio : printf; printf("trying to unregister unknown image %p\n", cast(void*)img); } 14659 } 14660 } 14661 } 14662 14663 static void freeImages () { // needed for discardAndRecreate 14664 imglistLocked = true; 14665 scope(exit) imglistLocked = false; 14666 ImgList* cur = imglist; 14667 ImgList* next = null; 14668 while (cur !is null) { 14669 import core.stdc.stdlib : free; 14670 next = cur.next; 14671 version(sdpy_debug_xshm) { import core.stdc.stdio : printf; printf("disposing image %p\n", cast(void*)cur.img); } 14672 (cast(Image)cast(void*)cur.img).dispose(); 14673 free(cur); 14674 cur = next; 14675 } 14676 imglist = null; 14677 } 14678 14679 /// can be used to override normal handling of display name 14680 /// from environment and/or command line 14681 static setDisplayName(string newDisplayName) { 14682 displayName = cast(char*) (newDisplayName ~ '\0'); 14683 } 14684 14685 /// resets to the default display string 14686 static resetDisplayName() { 14687 displayName = null; 14688 } 14689 14690 /// 14691 static Display* get() { 14692 if(display is null) { 14693 if(!librariesSuccessfullyLoaded) 14694 throw new Exception("Unable to load X11 client libraries"); 14695 display = XOpenDisplay(displayName); 14696 14697 isLocal_ = false; 14698 14699 connectionSequence_++; 14700 if(display is null) 14701 throw new Exception("Unable to open X display"); 14702 14703 auto str = display.display_name; 14704 // this is a bit of a hack but like if it looks like a unix socket we assume it is local 14705 // and otherwise it probably isn't 14706 if(str is null || (str[0] != ':' && str[0] != '/')) 14707 isLocal_ = false; 14708 else 14709 isLocal_ = true; 14710 14711 debug(sdpy_x_errors) { 14712 XSetErrorHandler(&adrlogger); 14713 XSynchronize(display, true); 14714 14715 extern(C) int wtf() { 14716 if(errorHappened) { 14717 asm { int 3; } 14718 errorHappened = false; 14719 } 14720 return 0; 14721 } 14722 XSetAfterFunction(display, &wtf); 14723 } 14724 14725 14726 XSetIOErrorHandler(&x11ioerrCB); 14727 Bool sup; 14728 XkbSetDetectableAutoRepeat(display, 1, &sup); // so we will not receive KeyRelease until key is really released 14729 createXIM(); 14730 version(with_eventloop) { 14731 import arsd.eventloop; 14732 addFileEventListeners(display.fd, &eventListener, null, null); 14733 } 14734 } 14735 14736 return display; 14737 } 14738 14739 extern(C) 14740 static int x11ioerrCB(Display* dpy) { 14741 throw new XDisconnectException(false); 14742 } 14743 14744 version(with_eventloop) { 14745 import arsd.eventloop; 14746 static void eventListener(OsFileHandle fd) { 14747 //this.mtLock(); 14748 //scope(exit) this.mtUnlock(); 14749 while(XPending(display)) 14750 doXNextEvent(display); 14751 } 14752 } 14753 14754 // close connection on program exit -- we need this to properly free all images 14755 static ~this () { 14756 // the gui thread must clean up after itself or else Xlib might deadlock 14757 // using this flag on any thread destruction is the easiest way i know of 14758 // (shared static this is run by the LAST thread to exit, which may not be 14759 // the gui thread, and normal static this run by ALL threads, so we gotta check.) 14760 if(thisIsGuiThread) 14761 close(); 14762 } 14763 14764 /// 14765 static void close() { 14766 if(display is null) 14767 return; 14768 14769 version(with_eventloop) { 14770 import arsd.eventloop; 14771 removeFileEventListeners(display.fd); 14772 } 14773 14774 // now remove all registered images to prevent shared memory leaks 14775 freeImages(); 14776 14777 // tbh I don't know why it is doing this but like if this happens to run 14778 // from the other thread there's frequent hanging inside here. 14779 if(thisIsGuiThread) 14780 XCloseDisplay(display); 14781 display = null; 14782 } 14783 } 14784 14785 mixin template NativeImageImplementation() { 14786 XImage* handle; 14787 ubyte* rawData; 14788 14789 XShmSegmentInfo shminfo; 14790 bool premultiply = true; 14791 14792 __gshared bool xshmQueryCompleted; 14793 __gshared bool _xshmAvailable; 14794 public static @property bool xshmAvailable() { 14795 if(!xshmQueryCompleted) { 14796 int i1, i2, i3; 14797 xshmQueryCompleted = true; 14798 14799 if(!XDisplayConnection.isLocal) 14800 _xshmAvailable = false; 14801 else 14802 _xshmAvailable = XQueryExtension(XDisplayConnection.get(), "MIT-SHM", &i1, &i2, &i3) != 0; 14803 } 14804 return _xshmAvailable; 14805 } 14806 14807 bool usingXshm; 14808 final: 14809 14810 private __gshared bool xshmfailed; 14811 14812 void createImage(int width, int height, bool forcexshm=false, bool enableAlpha = false) { 14813 auto display = XDisplayConnection.get(); 14814 assert(display !is null); 14815 auto screen = DefaultScreen(display); 14816 14817 // it will only use shared memory for somewhat largish images, 14818 // since otherwise we risk wasting shared memory handles on a lot of little ones 14819 if (xshmAvailable && (forcexshm || (width > 100 && height > 100))) { 14820 14821 14822 // it is possible for the query extension to return true, the DISPLAY check to pass, yet 14823 // the actual use still fails. For example, if the program is in a container and permission denied 14824 // on shared memory, or if it is a local thing forwarded to a remote server, etc. 14825 // 14826 // If it does fail, we need to detect it now, abort the xshm and fall back to core protocol. 14827 14828 14829 // synchronize so preexisting buffers are clear 14830 XSync(display, false); 14831 xshmfailed = false; 14832 14833 auto oldErrorHandler = XSetErrorHandler(&XShmErrorHandler); 14834 14835 14836 usingXshm = true; 14837 handle = XShmCreateImage( 14838 display, 14839 DefaultVisual(display, screen), 14840 enableAlpha ? 32: 24, 14841 ImageFormat.ZPixmap, 14842 null, 14843 &shminfo, 14844 width, height); 14845 if(handle is null) 14846 goto abortXshm1; 14847 14848 if(handle.bytes_per_line != 4 * width) 14849 goto abortXshm2; 14850 14851 shminfo.shmid = shmget(IPC_PRIVATE, handle.bytes_per_line * height, IPC_CREAT | 511 /* 0777 */); 14852 if(shminfo.shmid < 0) 14853 goto abortXshm3; 14854 handle.data = shminfo.shmaddr = rawData = cast(ubyte*) shmat(shminfo.shmid, null, 0); 14855 if(rawData == cast(ubyte*) -1) 14856 goto abortXshm4; 14857 shminfo.readOnly = 0; 14858 XShmAttach(display, &shminfo); 14859 14860 // and now to the final error check to ensure it actually worked. 14861 XSync(display, false); 14862 if(xshmfailed) 14863 goto abortXshm5; 14864 14865 XSetErrorHandler(oldErrorHandler); 14866 14867 XDisplayConnection.registerImage(this); 14868 // if I don't flush here there's a chance the dtor will run before the 14869 // ctor and lead to a bad value X error. While this hurts the efficiency 14870 // it is local anyway so prolly better to keep it simple 14871 XFlush(display); 14872 14873 return; 14874 14875 abortXshm5: 14876 shmdt(shminfo.shmaddr); 14877 rawData = null; 14878 14879 abortXshm4: 14880 shmctl(shminfo.shmid, IPC_RMID, null); 14881 14882 abortXshm3: 14883 // nothing needed, the shmget failed so there's nothing to free 14884 14885 abortXshm2: 14886 XDestroyImage(handle); 14887 handle = null; 14888 14889 abortXshm1: 14890 XSetErrorHandler(oldErrorHandler); 14891 usingXshm = false; 14892 handle = null; 14893 14894 shminfo = typeof(shminfo).init; 14895 14896 _xshmAvailable = false; // don't try again in the future 14897 14898 // writeln("fallingback"); 14899 14900 goto fallback; 14901 14902 } else { 14903 fallback: 14904 14905 if (forcexshm) throw new Exception("can't create XShm Image"); 14906 // This actually needs to be malloc to avoid a double free error when XDestroyImage is called 14907 import core.stdc.stdlib : malloc; 14908 rawData = cast(ubyte*) malloc(width * height * 4); 14909 14910 handle = XCreateImage( 14911 display, 14912 DefaultVisual(display, screen), 14913 enableAlpha ? 32 : 24, // bpp 14914 ImageFormat.ZPixmap, 14915 0, // offset 14916 rawData, 14917 width, height, 14918 enableAlpha ? 32 : 8 /* FIXME */, 4 * width); // padding, bytes per line 14919 } 14920 } 14921 14922 void dispose() { 14923 // note: this calls free(rawData) for us 14924 if(handle) { 14925 if (usingXshm) { 14926 XDisplayConnection.unregisterImage(this); 14927 if (XDisplayConnection.get()) XShmDetach(XDisplayConnection.get(), &shminfo); 14928 } 14929 XDestroyImage(handle); 14930 if(usingXshm) { 14931 shmdt(shminfo.shmaddr); 14932 shmctl(shminfo.shmid, IPC_RMID, null); 14933 } 14934 handle = null; 14935 } 14936 } 14937 14938 Color getPixel(int x, int y) @system { 14939 auto offset = (y * width + x) * 4; 14940 Color c; 14941 c.a = enableAlpha ? rawData[offset + 3] : 255; 14942 c.b = rawData[offset + 0]; 14943 c.g = rawData[offset + 1]; 14944 c.r = rawData[offset + 2]; 14945 if(enableAlpha && premultiply) 14946 c.unPremultiply; 14947 return c; 14948 } 14949 14950 void setPixel(int x, int y, Color c) @system { 14951 if(enableAlpha && premultiply) 14952 c.premultiply(); 14953 auto offset = (y * width + x) * 4; 14954 rawData[offset + 0] = c.b; 14955 rawData[offset + 1] = c.g; 14956 rawData[offset + 2] = c.r; 14957 if(enableAlpha) 14958 rawData[offset + 3] = c.a; 14959 } 14960 14961 void convertToRgbaBytes(ubyte[] where) @system { 14962 assert(where.length == this.width * this.height * 4); 14963 14964 // if rawData had a length.... 14965 //assert(rawData.length == where.length); 14966 for(int idx = 0; idx < where.length; idx += 4) { 14967 where[idx + 0] = rawData[idx + 2]; // r 14968 where[idx + 1] = rawData[idx + 1]; // g 14969 where[idx + 2] = rawData[idx + 0]; // b 14970 where[idx + 3] = enableAlpha ? rawData[idx + 3] : 255; // a 14971 14972 if(enableAlpha && premultiply) 14973 unPremultiplyRgba(where[idx .. idx + 4]); 14974 } 14975 } 14976 14977 void setFromRgbaBytes(in ubyte[] where) @system { 14978 assert(where.length == this.width * this.height * 4); 14979 14980 // if rawData had a length.... 14981 //assert(rawData.length == where.length); 14982 for(int idx = 0; idx < where.length; idx += 4) { 14983 rawData[idx + 2] = where[idx + 0]; // r 14984 rawData[idx + 1] = where[idx + 1]; // g 14985 rawData[idx + 0] = where[idx + 2]; // b 14986 if(enableAlpha) { 14987 rawData[idx + 3] = where[idx + 3]; // a 14988 if(premultiply) 14989 premultiplyBgra(rawData[idx .. idx + 4]); 14990 } 14991 } 14992 } 14993 14994 } 14995 14996 mixin template NativeSimpleWindowImplementation() { 14997 GC gc; 14998 Window window; 14999 Display* display; 15000 15001 Pixmap buffer; 15002 int bufferw, bufferh; // size of the buffer; can be bigger than window 15003 XIC xic; // input context 15004 int curHidden = 0; // counter 15005 Cursor blankCurPtr = 0; 15006 int cursorSequenceNumber = 0; 15007 int warpEventCount = 0; // number of mouse movement events to eat 15008 15009 __gshared X11SetSelectionHandler[Atom] setSelectionHandlers; 15010 X11GetSelectionHandler[Atom] getSelectionHandlers; 15011 15012 version(without_opengl) {} else 15013 GLXContext glc; 15014 15015 private void fixFixedSize(bool forced=false) (int width, int height) { 15016 if (forced || this.resizability == Resizability.fixedSize) { 15017 //{ import core.stdc.stdio; printf("fixing size to: %dx%d\n", width, height); } 15018 XSizeHints sh; 15019 static if (!forced) { 15020 c_long spr; 15021 XGetWMNormalHints(display, window, &sh, &spr); 15022 sh.flags |= PMaxSize | PMinSize; 15023 } else { 15024 sh.flags = PMaxSize | PMinSize; 15025 } 15026 sh.min_width = width; 15027 sh.min_height = height; 15028 sh.max_width = width; 15029 sh.max_height = height; 15030 XSetWMNormalHints(display, window, &sh); 15031 //XFlush(display); 15032 } 15033 } 15034 15035 ScreenPainter getPainter(bool manualInvalidations) { 15036 return ScreenPainter(this, window, manualInvalidations); 15037 } 15038 15039 void move(int x, int y) { 15040 XMoveWindow(display, window, x, y); 15041 } 15042 15043 void resize(int w, int h) { 15044 if (w < 1) w = 1; 15045 if (h < 1) h = 1; 15046 XResizeWindow(display, window, w, h); 15047 15048 // calling this now to avoid waiting for the server to 15049 // acknowledge the resize; draws without returning to the 15050 // event loop will thus actually work. the server's event 15051 // btw might overrule this and resize it again 15052 recordX11Resize(display, this, w, h); 15053 15054 updateOpenglViewportIfNeeded(w, h); 15055 } 15056 15057 void moveResize (int x, int y, int w, int h) { 15058 if (w < 1) w = 1; 15059 if (h < 1) h = 1; 15060 XMoveResizeWindow(display, window, x, y, w, h); 15061 updateOpenglViewportIfNeeded(w, h); 15062 } 15063 15064 void hideCursor () { 15065 if (curHidden++ == 0) { 15066 if (!blankCurPtr || cursorSequenceNumber != XDisplayConnection.connectionSequenceNumber) { 15067 static const(char)[1] cmbmp = 0; 15068 XColor blackcolor = { 0, 0, 0, 0, 0, 0 }; 15069 Pixmap pm = XCreateBitmapFromData(display, window, cmbmp.ptr, 1, 1); 15070 blankCurPtr = XCreatePixmapCursor(display, pm, pm, &blackcolor, &blackcolor, 0, 0); 15071 cursorSequenceNumber = XDisplayConnection.connectionSequenceNumber; 15072 XFreePixmap(display, pm); 15073 } 15074 XDefineCursor(display, window, blankCurPtr); 15075 } 15076 } 15077 15078 void showCursor () { 15079 if (--curHidden == 0) XUndefineCursor(display, window); 15080 } 15081 15082 void warpMouse (int x, int y) { 15083 // here i will send dummy "ignore next mouse motion" event, 15084 // 'cause `XWarpPointer()` sends synthesised mouse motion, 15085 // and we don't need to report it to the user (as warping is 15086 // used when the user needs movement deltas). 15087 //XClientMessageEvent xclient; 15088 XEvent e; 15089 e.xclient.type = EventType.ClientMessage; 15090 e.xclient.window = window; 15091 e.xclient.message_type = GetAtom!("_X11SDPY_INSMME_FLAG_EVENT_", true)(display); // let's hope nobody else will use such stupid name ;-) 15092 e.xclient.format = 32; 15093 e.xclient.data.l[0] = 0; 15094 debug(x11sdpy_warp_debug) { import core.stdc.stdio : printf; printf("X11: sending \"INSMME\"...\n"); } 15095 //{ 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]); } 15096 XSendEvent(display, window, false, EventMask.NoEventMask, /*cast(XEvent*)&xclient*/&e); 15097 // now warp pointer... 15098 debug(x11sdpy_warp_debug) { import core.stdc.stdio : printf; printf("X11: sending \"warp\"...\n"); } 15099 XWarpPointer(display, None, window, 0, 0, 0, 0, x, y); 15100 // ...and flush 15101 debug(x11sdpy_warp_debug) { import core.stdc.stdio : printf; printf("X11: flushing...\n"); } 15102 XFlush(display); 15103 } 15104 15105 void sendDummyEvent () { 15106 // here i will send dummy event to ping event queue 15107 XEvent e; 15108 e.xclient.type = EventType.ClientMessage; 15109 e.xclient.window = window; 15110 e.xclient.message_type = GetAtom!("_X11SDPY_DUMMY_EVENT_", true)(display); // let's hope nobody else will use such stupid name ;-) 15111 e.xclient.format = 32; 15112 e.xclient.data.l[0] = 0; 15113 XSendEvent(display, window, false, EventMask.NoEventMask, /*cast(XEvent*)&xclient*/&e); 15114 XFlush(display); 15115 } 15116 15117 void setTitle(string title) { 15118 if (title.ptr is null) title = ""; 15119 auto XA_UTF8 = XInternAtom(display, "UTF8_STRING".ptr, false); 15120 auto XA_NETWM_NAME = XInternAtom(display, "_NET_WM_NAME".ptr, false); 15121 XTextProperty windowName; 15122 windowName.value = title.ptr; 15123 windowName.encoding = XA_UTF8; //XA_STRING; 15124 windowName.format = 8; 15125 windowName.nitems = cast(uint)title.length; 15126 XSetWMName(display, window, &windowName); 15127 char[1024] namebuf = 0; 15128 auto maxlen = namebuf.length-1; 15129 if (maxlen > title.length) maxlen = title.length; 15130 namebuf[0..maxlen] = title[0..maxlen]; 15131 XStoreName(display, window, namebuf.ptr); 15132 XChangeProperty(display, window, XA_NETWM_NAME, XA_UTF8, 8, PropModeReplace, title.ptr, cast(uint)title.length); 15133 flushGui(); // without this OpenGL windows has a LONG delay before changing title 15134 } 15135 15136 string[] getTitles() { 15137 auto XA_UTF8 = XInternAtom(display, "UTF8_STRING".ptr, false); 15138 auto XA_NETWM_NAME = XInternAtom(display, "_NET_WM_NAME".ptr, false); 15139 XTextProperty textProp; 15140 if (XGetTextProperty(display, window, &textProp, XA_NETWM_NAME) != 0 || XGetWMName(display, window, &textProp) != 0) { 15141 if ((textProp.encoding == XA_UTF8 || textProp.encoding == XA_STRING) && textProp.format == 8) { 15142 return textProp.value[0 .. textProp.nitems].idup.split('\0'); 15143 } else 15144 return []; 15145 } else 15146 return null; 15147 } 15148 15149 string getTitle() { 15150 auto titles = getTitles(); 15151 return titles.length ? titles[0] : null; 15152 } 15153 15154 void setMinSize (int minwidth, int minheight) { 15155 import core.stdc.config : c_long; 15156 if (minwidth < 1) minwidth = 1; 15157 if (minheight < 1) minheight = 1; 15158 XSizeHints sh; 15159 c_long spr; 15160 XGetWMNormalHints(display, window, &sh, &spr); 15161 sh.min_width = minwidth; 15162 sh.min_height = minheight; 15163 sh.flags |= PMinSize; 15164 XSetWMNormalHints(display, window, &sh); 15165 flushGui(); 15166 } 15167 15168 void setMaxSize (int maxwidth, int maxheight) { 15169 import core.stdc.config : c_long; 15170 if (maxwidth < 1) maxwidth = 1; 15171 if (maxheight < 1) maxheight = 1; 15172 XSizeHints sh; 15173 c_long spr; 15174 XGetWMNormalHints(display, window, &sh, &spr); 15175 sh.max_width = maxwidth; 15176 sh.max_height = maxheight; 15177 sh.flags |= PMaxSize; 15178 XSetWMNormalHints(display, window, &sh); 15179 flushGui(); 15180 } 15181 15182 void setResizeGranularity (int granx, int grany) { 15183 import core.stdc.config : c_long; 15184 if (granx < 1) granx = 1; 15185 if (grany < 1) grany = 1; 15186 XSizeHints sh; 15187 c_long spr; 15188 XGetWMNormalHints(display, window, &sh, &spr); 15189 sh.width_inc = granx; 15190 sh.height_inc = grany; 15191 sh.flags |= PResizeInc; 15192 XSetWMNormalHints(display, window, &sh); 15193 flushGui(); 15194 } 15195 15196 void setOpacity (uint opacity) { 15197 arch_ulong o = opacity; 15198 if (opacity == uint.max) 15199 XDeleteProperty(display, window, XInternAtom(display, "_NET_WM_WINDOW_OPACITY".ptr, false)); 15200 else 15201 XChangeProperty(display, window, XInternAtom(display, "_NET_WM_WINDOW_OPACITY".ptr, false), 15202 XA_CARDINAL, 32, PropModeReplace, &o, 1); 15203 } 15204 15205 void createWindow(int width, int height, string title, in OpenGlOptions opengl, SimpleWindow parent) @trusted { 15206 version(without_opengl) {} else if(opengl == OpenGlOptions.yes && !openGlLibrariesSuccessfullyLoaded) throw new Exception("OpenGL libraries did not load"); 15207 display = XDisplayConnection.get(); 15208 auto screen = DefaultScreen(display); 15209 15210 bool overrideRedirect = false; 15211 if(windowType == WindowTypes.dropdownMenu || windowType == WindowTypes.popupMenu || windowType == WindowTypes.notification)// || windowType == WindowTypes.nestedChild) 15212 overrideRedirect = true; 15213 15214 version(without_opengl) {} 15215 else { 15216 if(opengl == OpenGlOptions.yes) { 15217 GLXFBConfig fbconf = null; 15218 XVisualInfo* vi = null; 15219 bool useLegacy = false; 15220 static if (SdpyIsUsingIVGLBinds) {if (glbindGetProcAddress("glHint") is null) assert(0, "GL: error loading OpenGL"); } // loads all necessary functions 15221 if (sdpyOpenGLContextVersion != 0 && glXCreateContextAttribsARB_present()) { 15222 int[23] visualAttribs = [ 15223 GLX_X_RENDERABLE , 1/*True*/, 15224 GLX_DRAWABLE_TYPE, GLX_WINDOW_BIT, 15225 GLX_RENDER_TYPE , GLX_RGBA_BIT, 15226 GLX_X_VISUAL_TYPE, GLX_TRUE_COLOR, 15227 GLX_RED_SIZE , 8, 15228 GLX_GREEN_SIZE , 8, 15229 GLX_BLUE_SIZE , 8, 15230 GLX_ALPHA_SIZE , 8, 15231 GLX_DEPTH_SIZE , 24, 15232 GLX_STENCIL_SIZE , 8, 15233 GLX_DOUBLEBUFFER , 1/*True*/, 15234 0/*None*/, 15235 ]; 15236 int fbcount; 15237 GLXFBConfig* fbc = glXChooseFBConfig(display, screen, visualAttribs.ptr, &fbcount); 15238 if (fbcount == 0) { 15239 useLegacy = true; // try to do at least something 15240 } else { 15241 // pick the FB config/visual with the most samples per pixel 15242 int bestidx = -1, bestns = -1; 15243 foreach (int fbi; 0..fbcount) { 15244 int sb, samples; 15245 glXGetFBConfigAttrib(display, fbc[fbi], GLX_SAMPLE_BUFFERS, &sb); 15246 glXGetFBConfigAttrib(display, fbc[fbi], GLX_SAMPLES, &samples); 15247 if (bestidx < 0 || sb && samples > bestns) { bestidx = fbi; bestns = samples; } 15248 } 15249 //{ import core.stdc.stdio; printf("found gl visual with %d samples\n", bestns); } 15250 fbconf = fbc[bestidx]; 15251 // Be sure to free the FBConfig list allocated by glXChooseFBConfig() 15252 XFree(fbc); 15253 vi = cast(XVisualInfo*)glXGetVisualFromFBConfig(display, fbconf); 15254 } 15255 } 15256 if (vi is null || useLegacy) { 15257 static immutable GLint[5] attrs = [ GLX_RGBA, GLX_DEPTH_SIZE, 24, GLX_DOUBLEBUFFER, None ]; 15258 vi = cast(XVisualInfo*)glXChooseVisual(display, 0, attrs.ptr); 15259 useLegacy = true; 15260 } 15261 if (vi is null) throw new Exception("no open gl visual found"); 15262 15263 XSetWindowAttributes swa; 15264 auto root = RootWindow(display, screen); 15265 swa.colormap = XCreateColormap(display, root, vi.visual, AllocNone); 15266 15267 swa.override_redirect = overrideRedirect; 15268 15269 window = XCreateWindow(display, (windowType != WindowTypes.nestedChild || parent is null) ? root : parent.impl.window, 15270 0, 0, width, height, 15271 0, vi.depth, 1 /* InputOutput */, vi.visual, CWColormap | CWOverrideRedirect, &swa); 15272 15273 // now try to use `glXCreateContextAttribsARB()` if it's here 15274 if (!useLegacy) { 15275 // request fairly advanced context, even with stencil buffer! 15276 int[9] contextAttribs = [ 15277 GLX_CONTEXT_MAJOR_VERSION_ARB, (sdpyOpenGLContextVersion>>8), 15278 GLX_CONTEXT_MINOR_VERSION_ARB, (sdpyOpenGLContextVersion&0xff), 15279 /*GLX_CONTEXT_PROFILE_MASK_ARB*/0x9126, (sdpyOpenGLContextCompatible ? /*GLX_CONTEXT_COMPATIBILITY_PROFILE_BIT_ARB*/0x02 : /*GLX_CONTEXT_CORE_PROFILE_BIT_ARB*/ 0x01), 15280 // for modern context, set "forward compatibility" flag too 15281 (sdpyOpenGLContextCompatible ? None : /*GLX_CONTEXT_FLAGS_ARB*/ 0x2094), /*GLX_CONTEXT_FORWARD_COMPATIBLE_BIT_ARB*/ 0x02, 15282 0/*None*/, 15283 ]; 15284 glc = glXCreateContextAttribsARB(display, fbconf, null, 1/*True*/, contextAttribs.ptr); 15285 if (glc is null && sdpyOpenGLContextAllowFallback) { 15286 sdpyOpenGLContextVersion = 0; 15287 glc = glXCreateContext(display, vi, null, /*GL_TRUE*/1); 15288 } 15289 //{ import core.stdc.stdio; printf("using modern ogl v%d.%d\n", contextAttribs[1], contextAttribs[3]); } 15290 } else { 15291 // fallback to old GLX call 15292 if (sdpyOpenGLContextAllowFallback || sdpyOpenGLContextVersion == 0) { 15293 sdpyOpenGLContextVersion = 0; 15294 glc = glXCreateContext(display, vi, null, /*GL_TRUE*/1); 15295 } 15296 } 15297 // sync to ensure any errors generated are processed 15298 XSync(display, 0/*False*/); 15299 //{ import core.stdc.stdio; printf("ogl is here\n"); } 15300 if(glc is null) 15301 throw new Exception("glc"); 15302 } 15303 } 15304 15305 if(opengl == OpenGlOptions.no) { 15306 15307 XSetWindowAttributes swa; 15308 swa.background_pixel = WhitePixel(display, screen); 15309 swa.border_pixel = BlackPixel(display, screen); 15310 swa.override_redirect = overrideRedirect; 15311 auto root = RootWindow(display, screen); 15312 swa.colormap = XCreateColormap(display, root, DefaultVisual(display, screen), AllocNone); 15313 15314 window = XCreateWindow(display, (windowType != WindowTypes.nestedChild || parent is null) ? root : parent.impl.window, 15315 0, 0, width, height, 15316 // I'm commenting that CWBackPixel thing just because it actually causes flicker for no apparent benefit. 15317 0, CopyFromParent, 1 /* InputOutput */, cast(Visual*) CopyFromParent, CWColormap /*| CWBackPixel*/ | CWBorderPixel | CWOverrideRedirect, &swa); 15318 15319 15320 15321 /* 15322 window = XCreateSimpleWindow( 15323 display, 15324 parent is null ? RootWindow(display, screen) : parent.impl.window, 15325 0, 0, // x, y 15326 width, height, 15327 1, // border width 15328 BlackPixel(display, screen), // border 15329 WhitePixel(display, screen)); // background 15330 */ 15331 15332 buffer = XCreatePixmap(display, cast(Drawable) window, width, height, DefaultDepthOfDisplay(display)); 15333 bufferw = width; 15334 bufferh = height; 15335 15336 gc = DefaultGC(display, screen); 15337 15338 // clear out the buffer to get us started... 15339 XSetForeground(display, gc, WhitePixel(display, screen)); 15340 XFillRectangle(display, cast(Drawable) buffer, gc, 0, 0, width, height); 15341 XSetForeground(display, gc, BlackPixel(display, screen)); 15342 } 15343 15344 // input context 15345 //TODO: create this only for top-level windows, and reuse that? 15346 populateXic(); 15347 15348 if (sdpyWindowClassStr is null) loadBinNameToWindowClassName(); 15349 if (sdpyWindowClassStr is null) sdpyWindowClass = "DSimpleWindow"; 15350 // window class 15351 if (sdpyWindowClassStr !is null && sdpyWindowClassStr[0]) { 15352 //{ import core.stdc.stdio; printf("winclass: [%s]\n", sdpyWindowClassStr); } 15353 XClassHint klass; 15354 XWMHints wh; 15355 if(this.customizationFlags & WindowFlags.managesChildWindowFocus) { 15356 wh.input = true; 15357 wh.flags |= InputHint; 15358 } 15359 XSizeHints size; 15360 klass.res_name = sdpyWindowClassStr; 15361 klass.res_class = sdpyWindowClassStr; 15362 XSetWMProperties(display, window, null, null, null, 0, &size, &wh, &klass); 15363 } 15364 15365 setTitle(title); 15366 SimpleWindow.nativeMapping[window] = this; 15367 CapableOfHandlingNativeEvent.nativeHandleMapping[window] = this; 15368 15369 // This gives our window a close button 15370 if (windowType != WindowTypes.eventOnly) { 15371 Atom[2] atoms = [GetAtom!"WM_DELETE_WINDOW"(display), GetAtom!"WM_TAKE_FOCUS"(display)]; 15372 int useAtoms; 15373 if(this.customizationFlags & WindowFlags.managesChildWindowFocus) { 15374 useAtoms = 2; 15375 } else { 15376 useAtoms = 1; 15377 } 15378 assert(useAtoms <= atoms.length); 15379 XSetWMProtocols(display, window, atoms.ptr, useAtoms); 15380 } 15381 15382 // FIXME: windowType and customizationFlags 15383 Atom[8] wsatoms; // here, due to goto 15384 int wmsacount = 0; // here, due to goto 15385 15386 try 15387 final switch(windowType) { 15388 case WindowTypes.normal: 15389 setNetWMWindowType(GetAtom!"_NET_WM_WINDOW_TYPE_NORMAL"(display)); 15390 break; 15391 case WindowTypes.undecorated: 15392 motifHideDecorations(); 15393 setNetWMWindowType(GetAtom!"_NET_WM_WINDOW_TYPE_NORMAL"(display)); 15394 break; 15395 case WindowTypes.eventOnly: 15396 _hidden = true; 15397 XSelectInput(display, window, EventMask.StructureNotifyMask); // without this, we won't get destroy notification 15398 goto hiddenWindow; 15399 //break; 15400 case WindowTypes.nestedChild: 15401 // handled in XCreateWindow calls 15402 break; 15403 15404 case WindowTypes.dropdownMenu: 15405 motifHideDecorations(); 15406 setNetWMWindowType(GetAtom!"_NET_WM_WINDOW_TYPE_DROPDOWN_MENU"(display)); 15407 customizationFlags |= WindowFlags.skipTaskbar | WindowFlags.alwaysOnTop; 15408 break; 15409 case WindowTypes.popupMenu: 15410 motifHideDecorations(); 15411 setNetWMWindowType(GetAtom!"_NET_WM_WINDOW_TYPE_POPUP_MENU"(display)); 15412 customizationFlags |= WindowFlags.skipTaskbar | WindowFlags.alwaysOnTop; 15413 break; 15414 case WindowTypes.notification: 15415 motifHideDecorations(); 15416 setNetWMWindowType(GetAtom!"_NET_WM_WINDOW_TYPE_NOTIFICATION"(display)); 15417 customizationFlags |= WindowFlags.skipTaskbar | WindowFlags.alwaysOnTop; 15418 break; 15419 case WindowTypes.minimallyWrapped: 15420 assert(0, "don't create a minimallyWrapped thing explicitly!"); 15421 /+ 15422 case WindowTypes.menu: 15423 atoms[0] = GetAtom!"_NET_WM_WINDOW_TYPE_MENU"(display); 15424 motifHideDecorations(); 15425 break; 15426 case WindowTypes.desktop: 15427 atoms[0] = GetAtom!"_NET_WM_WINDOW_TYPE_DESKTOP"(display); 15428 break; 15429 case WindowTypes.dock: 15430 atoms[0] = GetAtom!"_NET_WM_WINDOW_TYPE_DOCK"(display); 15431 break; 15432 case WindowTypes.toolbar: 15433 atoms[0] = GetAtom!"_NET_WM_WINDOW_TYPE_TOOLBAR"(display); 15434 break; 15435 case WindowTypes.menu: 15436 atoms[0] = GetAtom!"_NET_WM_WINDOW_TYPE_MENU"(display); 15437 break; 15438 case WindowTypes.utility: 15439 atoms[0] = GetAtom!"_NET_WM_WINDOW_TYPE_UTILITY"(display); 15440 break; 15441 case WindowTypes.splash: 15442 atoms[0] = GetAtom!"_NET_WM_WINDOW_TYPE_SPLASH"(display); 15443 break; 15444 case WindowTypes.dialog: 15445 atoms[0] = GetAtom!"_NET_WM_WINDOW_TYPE_DIALOG"(display); 15446 break; 15447 case WindowTypes.tooltip: 15448 atoms[0] = GetAtom!"_NET_WM_WINDOW_TYPE_TOOLTIP"(display); 15449 break; 15450 case WindowTypes.notification: 15451 atoms[0] = GetAtom!"_NET_WM_WINDOW_TYPE_NOTIFICATION"(display); 15452 break; 15453 case WindowTypes.combo: 15454 atoms[0] = GetAtom!"_NET_WM_WINDOW_TYPE_COMBO"(display); 15455 break; 15456 case WindowTypes.dnd: 15457 atoms[0] = GetAtom!"_NET_WM_WINDOW_TYPE_DND"(display); 15458 break; 15459 +/ 15460 } 15461 catch(Exception e) { 15462 // XInternAtom failed, prolly a WM 15463 // that doesn't support these things 15464 } 15465 15466 if (customizationFlags&WindowFlags.skipTaskbar) wsatoms[wmsacount++] = GetAtom!("_NET_WM_STATE_SKIP_TASKBAR", true)(display); 15467 // the two following flags may be ignored by WM 15468 if (customizationFlags&WindowFlags.alwaysOnTop) wsatoms[wmsacount++] = GetAtom!("_NET_WM_STATE_ABOVE", true)(display); 15469 if (customizationFlags&WindowFlags.alwaysOnBottom) wsatoms[wmsacount++] = GetAtom!("_NET_WM_STATE_BELOW", true)(display); 15470 15471 if (wmsacount != 0) XChangeProperty(display, window, GetAtom!("_NET_WM_STATE", true)(display), XA_ATOM, 32 /* bits */,0 /*PropModeReplace*/, wsatoms.ptr, wmsacount); 15472 15473 if (this.resizability == Resizability.fixedSize || (opengl == OpenGlOptions.no && this.resizability != Resizability.allowResizing)) fixFixedSize!true(width, height); 15474 15475 // What would be ideal here is if they only were 15476 // selected if there was actually an event handler 15477 // for them... 15478 15479 selectDefaultInput((customizationFlags & WindowFlags.alwaysRequestMouseMotionEvents)?true:false); 15480 15481 hiddenWindow: 15482 15483 // set the pid property for lookup later by window managers 15484 // a standard convenience 15485 import core.sys.posix.unistd; 15486 arch_ulong pid = getpid(); 15487 15488 XChangeProperty( 15489 display, 15490 impl.window, 15491 GetAtom!("_NET_WM_PID", true)(display), 15492 XA_CARDINAL, 15493 32 /* bits */, 15494 0 /*PropModeReplace*/, 15495 &pid, 15496 1); 15497 15498 if(isTransient && parent) { // customizationFlags & WindowFlags.transient) { 15499 if(parent is null) assert(0); 15500 XChangeProperty( 15501 display, 15502 impl.window, 15503 GetAtom!("WM_TRANSIENT_FOR", true)(display), 15504 XA_WINDOW, 15505 32 /* bits */, 15506 0 /*PropModeReplace*/, 15507 &parent.impl.window, 15508 1); 15509 15510 } 15511 15512 if(windowType != WindowTypes.eventOnly && (customizationFlags&WindowFlags.dontAutoShow) == 0) { 15513 XMapWindow(display, window); 15514 } else { 15515 _hidden = true; 15516 } 15517 } 15518 15519 void populateXic() { 15520 if (XDisplayConnection.xim !is null) { 15521 xic = XCreateIC(XDisplayConnection.xim, 15522 /*XNInputStyle*/"inputStyle".ptr, XIMPreeditNothing|XIMStatusNothing, 15523 /*XNClientWindow*/"clientWindow".ptr, window, 15524 /*XNFocusWindow*/"focusWindow".ptr, window, 15525 null); 15526 if (xic is null) { 15527 import core.stdc.stdio : stderr, fprintf; 15528 fprintf(stderr, "XCreateIC failed for window %u\n", cast(uint)window); 15529 } 15530 } 15531 } 15532 15533 void selectDefaultInput(bool forceIncludeMouseMotion) { 15534 auto mask = EventMask.ExposureMask | 15535 EventMask.KeyPressMask | 15536 EventMask.KeyReleaseMask | 15537 EventMask.PropertyChangeMask | 15538 EventMask.FocusChangeMask | 15539 EventMask.StructureNotifyMask | 15540 EventMask.SubstructureNotifyMask | 15541 EventMask.VisibilityChangeMask 15542 | EventMask.ButtonPressMask 15543 | EventMask.ButtonReleaseMask 15544 ; 15545 15546 // xshm is our shortcut for local connections 15547 if(XDisplayConnection.isLocal || forceIncludeMouseMotion) 15548 mask |= EventMask.PointerMotionMask; 15549 else 15550 mask |= EventMask.ButtonMotionMask; 15551 15552 XSelectInput(display, window, mask); 15553 } 15554 15555 15556 void setNetWMWindowType(Atom type) { 15557 Atom[2] atoms; 15558 15559 atoms[0] = type; 15560 // generic fallback 15561 atoms[1] = GetAtom!"_NET_WM_WINDOW_TYPE_NORMAL"(display); 15562 15563 XChangeProperty( 15564 display, 15565 impl.window, 15566 GetAtom!"_NET_WM_WINDOW_TYPE"(display), 15567 XA_ATOM, 15568 32 /* bits */, 15569 0 /*PropModeReplace*/, 15570 atoms.ptr, 15571 cast(int) atoms.length); 15572 } 15573 15574 void motifHideDecorations(bool hide = true) { 15575 MwmHints hints; 15576 hints.flags = MWM_HINTS_DECORATIONS; 15577 hints.decorations = hide ? 0 : 1; 15578 15579 XChangeProperty( 15580 display, 15581 impl.window, 15582 GetAtom!"_MOTIF_WM_HINTS"(display), 15583 GetAtom!"_MOTIF_WM_HINTS"(display), 15584 32 /* bits */, 15585 0 /*PropModeReplace*/, 15586 &hints, 15587 hints.sizeof / 4); 15588 } 15589 15590 /*k8: unused 15591 void createOpenGlContext() { 15592 15593 } 15594 */ 15595 15596 void closeWindow() { 15597 // I can't close this or a child window closing will 15598 // break events for everyone. So I'm just leaking it right 15599 // now and that is probably perfectly fine... 15600 version(none) 15601 if (customEventFDRead != -1) { 15602 import core.sys.posix.unistd : close; 15603 auto same = customEventFDRead == customEventFDWrite; 15604 15605 close(customEventFDRead); 15606 if(!same) 15607 close(customEventFDWrite); 15608 customEventFDRead = -1; 15609 customEventFDWrite = -1; 15610 } 15611 15612 version(without_opengl) {} else 15613 if(glc !is null) { 15614 glXDestroyContext(display, glc); 15615 glc = null; 15616 } 15617 15618 if(buffer) 15619 XFreePixmap(display, buffer); 15620 bufferw = bufferh = 0; 15621 if (blankCurPtr && cursorSequenceNumber == XDisplayConnection.connectionSequenceNumber) XFreeCursor(display, blankCurPtr); 15622 XDestroyWindow(display, window); 15623 XFlush(display); 15624 } 15625 15626 void dispose() { 15627 } 15628 15629 bool destroyed = false; 15630 } 15631 15632 bool insideXEventLoop; 15633 } 15634 15635 version(X11) { 15636 15637 int mouseDoubleClickTimeout = 350; /// Double click timeout. X only, you probably shouldn't change this. 15638 15639 private class ResizeEvent { 15640 int width, height; 15641 } 15642 15643 void recordX11ResizeAsync(Display* display, SimpleWindow win, int width, int height) { 15644 if(win.windowType == WindowTypes.minimallyWrapped) 15645 return; 15646 15647 if(win.pendingResizeEvent is null) { 15648 win.pendingResizeEvent = new ResizeEvent(); 15649 win.addEventListener((ResizeEvent re) { 15650 recordX11Resize(XDisplayConnection.get, win, re.width, re.height); 15651 }); 15652 } 15653 win.pendingResizeEvent.width = width; 15654 win.pendingResizeEvent.height = height; 15655 if(!win.eventQueued!ResizeEvent) { 15656 win.postEvent(win.pendingResizeEvent); 15657 } 15658 } 15659 15660 void recordX11Resize(Display* display, SimpleWindow win, int width, int height) { 15661 if(win.windowType == WindowTypes.minimallyWrapped) 15662 return; 15663 if(win.closed) 15664 return; 15665 15666 if(width != win.width || height != win.height) { 15667 15668 // writeln("RESIZE: ", width, "x", height, " was ", win._width, "x", win._height, " window: ", win.windowType, "-", win.title, " ", win.window); 15669 win._width = width; 15670 win._height = height; 15671 15672 if(win.openglMode == OpenGlOptions.no) { 15673 // FIXME: could this be more efficient? 15674 15675 if (win.bufferw < width || win.bufferh < height) { 15676 //{ 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); } 15677 // grow the internal buffer to match the window... 15678 auto newPixmap = XCreatePixmap(display, cast(Drawable) win.window, width, height, DefaultDepthOfDisplay(display)); 15679 { 15680 GC xgc = XCreateGC(win.display, cast(Drawable)win.window, 0, null); 15681 XCopyGC(win.display, win.gc, 0xffffffff, xgc); 15682 scope(exit) XFreeGC(win.display, xgc); 15683 XSetClipMask(win.display, xgc, None); 15684 XSetForeground(win.display, xgc, 0); 15685 XFillRectangle(display, cast(Drawable)newPixmap, xgc, 0, 0, width, height); 15686 } 15687 XCopyArea(display, 15688 cast(Drawable) win.buffer, 15689 cast(Drawable) newPixmap, 15690 win.gc, 0, 0, 15691 win.bufferw < width ? win.bufferw : win.width, 15692 win.bufferh < height ? win.bufferh : win.height, 15693 0, 0); 15694 15695 XFreePixmap(display, win.buffer); 15696 win.buffer = newPixmap; 15697 win.bufferw = width; 15698 win.bufferh = height; 15699 } 15700 15701 // clear unused parts of the buffer 15702 if (win.bufferw > width || win.bufferh > height) { 15703 GC xgc = XCreateGC(win.display, cast(Drawable)win.window, 0, null); 15704 XCopyGC(win.display, win.gc, 0xffffffff, xgc); 15705 scope(exit) XFreeGC(win.display, xgc); 15706 XSetClipMask(win.display, xgc, None); 15707 XSetForeground(win.display, xgc, 0); 15708 immutable int maxw = (win.bufferw > width ? win.bufferw : width); 15709 immutable int maxh = (win.bufferh > height ? win.bufferh : height); 15710 XFillRectangle(win.display, cast(Drawable)win.buffer, xgc, width, 0, maxw, maxh); // let X11 do clipping 15711 XFillRectangle(win.display, cast(Drawable)win.buffer, xgc, 0, height, maxw, maxh); // let X11 do clipping 15712 } 15713 15714 } 15715 15716 win.updateOpenglViewportIfNeeded(width, height); 15717 15718 win.fixFixedSize(width, height); //k8: this does nothing on my FluxBox; wtf?! 15719 15720 if(win.resizability != Resizability.automaticallyScaleIfPossible) 15721 if(win.windowResized !is null) { 15722 XUnlockDisplay(display); 15723 scope(exit) XLockDisplay(display); 15724 win.windowResized(width, height); 15725 } 15726 } 15727 } 15728 15729 15730 /// Platform-specific, you might use it when doing a custom event loop. 15731 bool doXNextEvent(Display* display) { 15732 bool done; 15733 XEvent e; 15734 XNextEvent(display, &e); 15735 version(sddddd) { 15736 if(auto win = e.xany.window in CapableOfHandlingNativeEvent.nativeHandleMapping) { 15737 if(typeid(cast(Object) *win) == NotificationAreaIcon.classinfo) 15738 writeln("event for: ", e.xany.window, "; type is ", to!string(cast(EventType)e.type)); 15739 } 15740 } 15741 15742 // filter out compose events 15743 if (XFilterEvent(&e, None)) { 15744 //{ import core.stdc.stdio : printf; printf("XFilterEvent filtered!\n"); } 15745 //NOTE: we should ungrab keyboard here, but simpledisplay doesn't use keyboard grabbing (yet) 15746 return false; 15747 } 15748 // process keyboard mapping changes 15749 if (e.type == EventType.KeymapNotify) { 15750 //{ import core.stdc.stdio : printf; printf("KeymapNotify processed!\n"); } 15751 XRefreshKeyboardMapping(&e.xmapping); 15752 return false; 15753 } 15754 15755 version(with_eventloop) 15756 import arsd.eventloop; 15757 15758 if(SimpleWindow.handleNativeGlobalEvent !is null) { 15759 // see windows impl's comments 15760 XUnlockDisplay(display); 15761 scope(exit) XLockDisplay(display); 15762 auto ret = SimpleWindow.handleNativeGlobalEvent(e); 15763 if(ret == 0) 15764 return done; 15765 } 15766 15767 15768 if(auto win = e.xany.window in CapableOfHandlingNativeEvent.nativeHandleMapping) { 15769 if(win.getNativeEventHandler !is null) { 15770 XUnlockDisplay(display); 15771 scope(exit) XLockDisplay(display); 15772 auto ret = win.getNativeEventHandler()(e); 15773 if(ret == 0) 15774 return done; 15775 } 15776 } 15777 15778 if(xrrEventBase != -1 && e.type == xrrEventBase + RRScreenChangeNotify) { 15779 if(auto win = e.xany.window in SimpleWindow.nativeMapping) { 15780 // we get this because of the RRScreenChangeNotifyMask 15781 15782 // this isn't actually an ideal way to do it since it wastes time 15783 // but meh it is simple and it works. 15784 win.actualDpiLoadAttempted = false; 15785 SimpleWindow.xRandrInfoLoadAttemped = false; 15786 win.updateActualDpi(); // trigger a reload 15787 } 15788 } 15789 15790 switch(e.type) { 15791 case EventType.SelectionClear: 15792 if(auto win = e.xselectionclear.window in SimpleWindow.nativeMapping) { 15793 // FIXME so it is supposed to finish any in progress transfers... but idk... 15794 // writeln("SelectionClear"); 15795 SimpleWindow.impl.setSelectionHandlers.remove(e.xselectionclear.selection); 15796 } 15797 break; 15798 case EventType.SelectionRequest: 15799 if(auto win = e.xselectionrequest.owner in SimpleWindow.nativeMapping) 15800 if(auto ssh = e.xselectionrequest.selection in SimpleWindow.impl.setSelectionHandlers) { 15801 // printf("SelectionRequest %s\n", XGetAtomName(e.xselectionrequest.display, e.xselectionrequest.target)); 15802 XUnlockDisplay(display); 15803 scope(exit) XLockDisplay(display); 15804 (*ssh).handleRequest(e); 15805 } 15806 break; 15807 case EventType.PropertyNotify: 15808 // printf("PropertyNotify %s %d\n", XGetAtomName(e.xproperty.display, e.xproperty.atom), e.xproperty.state); 15809 15810 foreach(ssh; SimpleWindow.impl.setSelectionHandlers) { 15811 if(ssh.matchesIncr(e.xproperty.window, e.xproperty.atom) && e.xproperty.state == PropertyNotification.PropertyDelete) 15812 ssh.sendMoreIncr(&e.xproperty); 15813 } 15814 15815 15816 if(auto win = e.xproperty.window in SimpleWindow.nativeMapping) 15817 if(auto handler = e.xproperty.atom in win.getSelectionHandlers) { 15818 if(handler.matchesIncr(e.xproperty.window, e.xproperty.atom) && e.xproperty.state == PropertyNotification.PropertyNewValue) { 15819 Atom target; 15820 int format; 15821 arch_ulong bytesafter, length; 15822 void* value; 15823 15824 ubyte[] s; 15825 Atom targetToKeep; 15826 15827 XGetWindowProperty( 15828 e.xproperty.display, 15829 e.xproperty.window, 15830 e.xproperty.atom, 15831 0, 15832 100000 /* length */, 15833 true, /* erase it to signal we got it and want more */ 15834 0 /*AnyPropertyType*/, 15835 &target, &format, &length, &bytesafter, &value); 15836 15837 if(!targetToKeep) 15838 targetToKeep = target; 15839 15840 auto id = (cast(ubyte*) value)[0 .. length]; 15841 15842 handler.handleIncrData(targetToKeep, id); 15843 if(length == 0) { 15844 win.getSelectionHandlers.remove(e.xproperty.atom); 15845 } 15846 15847 XFree(value); 15848 } 15849 } 15850 break; 15851 case EventType.SelectionNotify: 15852 if(auto win = e.xselection.requestor in SimpleWindow.nativeMapping) 15853 if(auto handler = e.xproperty.atom in win.getSelectionHandlers) { 15854 if(e.xselection.property == None) { // || e.xselection.property == GetAtom!("NULL", true)(e.xselection.display)) { 15855 XUnlockDisplay(display); 15856 scope(exit) XLockDisplay(display); 15857 handler.handleData(None, null); 15858 win.getSelectionHandlers.remove(e.xproperty.atom); 15859 } else { 15860 Atom target; 15861 int format; 15862 arch_ulong bytesafter, length; 15863 void* value; 15864 XGetWindowProperty( 15865 e.xselection.display, 15866 e.xselection.requestor, 15867 e.xselection.property, 15868 0, 15869 100000 /* length */, 15870 //false, /* don't erase it */ 15871 true, /* do erase it lol */ 15872 0 /*AnyPropertyType*/, 15873 &target, &format, &length, &bytesafter, &value); 15874 15875 // FIXME: I don't have to copy it now since it is in char[] instead of string 15876 15877 { 15878 XUnlockDisplay(display); 15879 scope(exit) XLockDisplay(display); 15880 15881 if(target == XA_ATOM) { 15882 // initial request, see what they are able to work with and request the best one 15883 // we can handle, if available 15884 15885 Atom[] answer = (cast(Atom*) value)[0 .. length]; 15886 Atom best = handler.findBestFormat(answer); 15887 15888 /+ 15889 writeln("got ", answer); 15890 foreach(a; answer) 15891 writeln(XGetAtomName(display, a).stringz); 15892 writeln("best ", best); 15893 +/ 15894 15895 if(best != None) { 15896 // actually request the best format 15897 XConvertSelection(e.xselection.display, e.xselection.selection, best, GetAtom!("SDD_DATA", true)(display), e.xselection.requestor, 0 /*CurrentTime*/); 15898 } 15899 } else if(target == GetAtom!"INCR"(display)) { 15900 // incremental 15901 15902 handler.prepareIncremental(e.xselection.requestor, e.xselection.property); 15903 15904 // signal the sending program that we see 15905 // the incr and are ready to receive more. 15906 XDeleteProperty( 15907 e.xselection.display, 15908 e.xselection.requestor, 15909 e.xselection.property); 15910 } else { 15911 // unsupported type... maybe, forward, then we done with it 15912 if(target != None) { 15913 handler.handleData(target, cast(ubyte[]) value[0 .. length]); 15914 win.getSelectionHandlers.remove(e.xproperty.atom); 15915 } 15916 } 15917 } 15918 XFree(value); 15919 /* 15920 XDeleteProperty( 15921 e.xselection.display, 15922 e.xselection.requestor, 15923 e.xselection.property); 15924 */ 15925 } 15926 } 15927 break; 15928 case EventType.ConfigureNotify: 15929 auto event = e.xconfigure; 15930 if(auto win = event.window in SimpleWindow.nativeMapping) { 15931 if(win.windowType == WindowTypes.minimallyWrapped) 15932 break; 15933 //version(sdddd) { writeln(" w=", event.width, "; h=", event.height); } 15934 15935 /+ 15936 The ICCCM says window managers must send a synthetic event when the window 15937 is moved but NOT when it is resized. In the resize case, an event is sent 15938 with position (0, 0) which can be wrong and break the dpi calculations. 15939 15940 So we only consider the synthetic events from the WM and otherwise 15941 need to wait for some other event to get the position which... sucks. 15942 15943 I'd rather not have windows changing their layout on mouse motion after 15944 switching monitors... might be forced to but for now just ignoring it. 15945 15946 Easiest way to switch monitors without sending a size position is by 15947 maximize or fullscreen in a setup like mine, but on most setups those 15948 work on the monitor it is already living on, so it should be ok most the 15949 time. 15950 +/ 15951 if(event.send_event) { 15952 win.screenPositionKnown = true; 15953 win.screenPositionX = event.x; 15954 win.screenPositionY = event.y; 15955 win.updateActualDpi(); 15956 } 15957 15958 win.updateIMEPopupLocation(); 15959 recordX11ResizeAsync(display, *win, event.width, event.height); 15960 } 15961 break; 15962 case EventType.Expose: 15963 if(auto win = e.xexpose.window in SimpleWindow.nativeMapping) { 15964 if(win.windowType == WindowTypes.minimallyWrapped) 15965 break; 15966 // if it is closing from a popup menu, it can get 15967 // an Expose event right by the end and trigger a 15968 // BadDrawable error ... we'll just check 15969 // closed to handle that. 15970 if((*win).closed) break; 15971 if((*win).openglMode == OpenGlOptions.no) { 15972 bool doCopy = true;// e.xexpose.count == 0; // the count is better if we copy all area but meh 15973 if (win.handleExpose !is null) doCopy = !win.handleExpose(e.xexpose.x, e.xexpose.y, e.xexpose.width, e.xexpose.height, e.xexpose.count); 15974 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); 15975 } else { 15976 // need to redraw the scene somehow 15977 if(e.xexpose.count == 0) { // only do the last one since redrawOpenGlSceneNow always does it all 15978 XUnlockDisplay(display); 15979 scope(exit) XLockDisplay(display); 15980 version(without_opengl) {} else 15981 win.redrawOpenGlSceneSoon(); 15982 } 15983 } 15984 } 15985 break; 15986 case EventType.FocusIn: 15987 case EventType.FocusOut: 15988 15989 if(auto win = e.xfocus.window in SimpleWindow.nativeMapping) { 15990 /+ 15991 15992 void info(string detail) { 15993 string s; 15994 // import std.conv; 15995 // import std.datetime; 15996 s ~= to!string(Clock.currTime); 15997 s ~= " "; 15998 s ~= e.type == EventType.FocusIn ? "in " : "out"; 15999 s ~= " "; 16000 s ~= win.windowType == WindowTypes.nestedChild ? "child " : "main "; 16001 s ~= e.xfocus.mode == NotifyModes.NotifyNormal ? " normal ": " grabbed "; 16002 s ~= detail; 16003 s ~= " "; 16004 16005 sdpyPrintDebugString(s); 16006 16007 } 16008 16009 switch(e.xfocus.detail) { 16010 case NotifyDetail.NotifyAncestor: info("Ancestor"); break; 16011 case NotifyDetail.NotifyVirtual: info("Virtual"); break; 16012 case NotifyDetail.NotifyInferior: info("Inferior"); break; 16013 case NotifyDetail.NotifyNonlinear: info("Nonlinear"); break; 16014 case NotifyDetail.NotifyNonlinearVirtual: info("nlinearvirtual"); break; 16015 case NotifyDetail.NotifyPointer: info("pointer"); break; 16016 case NotifyDetail.NotifyPointerRoot: info("pointerroot"); break; 16017 case NotifyDetail.NotifyDetailNone: info("none"); break; 16018 default: 16019 16020 } 16021 +/ 16022 16023 16024 if(e.xfocus.detail == NotifyDetail.NotifyPointer) 16025 break; // just ignore these they seem irrelevant 16026 16027 auto old = win._focused; 16028 win._focused = e.type == EventType.FocusIn; 16029 16030 // yes, we are losing the focus, but to our own child. that's actually kinda keeping it. 16031 if(e.type == EventType.FocusOut && e.xfocus.detail == NotifyDetail.NotifyInferior) 16032 win._focused = true; 16033 16034 if(win.demandingAttention) 16035 demandAttention(*win, false); 16036 16037 win.updateIMEFocused(); 16038 16039 if(old != win._focused && win.onFocusChange) { 16040 XUnlockDisplay(display); 16041 scope(exit) XLockDisplay(display); 16042 win.onFocusChange(win._focused); 16043 } 16044 } 16045 break; 16046 case EventType.VisibilityNotify: 16047 if(auto win = e.xfocus.window in SimpleWindow.nativeMapping) { 16048 if (e.xvisibility.state == VisibilityNotify.VisibilityFullyObscured) { 16049 if (win.visibilityChanged !is null) { 16050 XUnlockDisplay(display); 16051 scope(exit) XLockDisplay(display); 16052 win.visibilityChanged(false); 16053 } 16054 } else { 16055 if (win.visibilityChanged !is null) { 16056 XUnlockDisplay(display); 16057 scope(exit) XLockDisplay(display); 16058 win.visibilityChanged(true); 16059 } 16060 } 16061 } 16062 break; 16063 case EventType.ClientMessage: 16064 if (e.xclient.message_type == GetAtom!("_X11SDPY_INSMME_FLAG_EVENT_", true)(e.xany.display)) { 16065 // "ignore next mouse motion" event, increment ignore counter for teh window 16066 if (auto win = e.xclient.window in SimpleWindow.nativeMapping) { 16067 ++(*win).warpEventCount; 16068 debug(x11sdpy_warp_debug) { import core.stdc.stdio : printf; printf("X11: got \"INSMME\" message, new count=%d\n", (*win).warpEventCount); } 16069 } else { 16070 debug(x11sdpy_warp_debug) { import core.stdc.stdio : printf; printf("X11: got \"INSMME\" WTF?!!\n"); } 16071 } 16072 } else if(e.xclient.data.l[0] == GetAtom!"WM_DELETE_WINDOW"(e.xany.display)) { 16073 // user clicked the close button on the window manager 16074 if(auto win = e.xclient.window in SimpleWindow.nativeMapping) { 16075 XUnlockDisplay(display); 16076 scope(exit) XLockDisplay(display); 16077 if ((*win).closeQuery !is null) (*win).closeQuery(); else (*win).close(); 16078 } 16079 16080 } else if(e.xclient.data.l[0] == GetAtom!"WM_TAKE_FOCUS"(e.xany.display)) { 16081 // writeln("HAPPENED"); 16082 // user clicked the close button on the window manager 16083 if(auto win = e.xclient.window in SimpleWindow.nativeMapping) { 16084 XUnlockDisplay(display); 16085 scope(exit) XLockDisplay(display); 16086 16087 auto setTo = *win; 16088 16089 if(win.setRequestedInputFocus !is null) { 16090 auto s = win.setRequestedInputFocus(); 16091 if(s !is null) { 16092 setTo = s; 16093 } 16094 } 16095 16096 assert(setTo !is null); 16097 16098 // FIXME: so this is actually supposed to focus to a relevant child window if appropriate 16099 16100 XSetInputFocus(display, setTo.impl.window, RevertToParent, e.xclient.data.l[1]); 16101 } 16102 } else if(e.xclient.message_type == GetAtom!"MANAGER"(e.xany.display)) { 16103 foreach(nai; NotificationAreaIcon.activeIcons) 16104 nai.newManager(); 16105 } else if(auto win = e.xclient.window in SimpleWindow.nativeMapping) { 16106 16107 bool xDragWindow = true; 16108 if(xDragWindow && e.xclient.message_type == GetAtom!"XdndStatus"(e.xany.display)) { 16109 //XDefineCursor(display, xDragWindow.impl.window, 16110 //writeln("XdndStatus ", e.xclient.data.l); 16111 } 16112 if(auto dh = win.dropHandler) { 16113 16114 static Atom[3] xFormatsBuffer; 16115 static Atom[] xFormats; 16116 16117 void resetXFormats() { 16118 xFormatsBuffer[] = 0; 16119 xFormats = xFormatsBuffer[]; 16120 } 16121 16122 if(e.xclient.message_type == GetAtom!"XdndEnter"(e.xany.display)) { 16123 // on Windows it is supposed to return the effect you actually do FIXME 16124 16125 auto sourceWindow = e.xclient.data.l[0]; 16126 16127 xFormatsBuffer[0] = e.xclient.data.l[2]; 16128 xFormatsBuffer[1] = e.xclient.data.l[3]; 16129 xFormatsBuffer[2] = e.xclient.data.l[4]; 16130 16131 if(e.xclient.data.l[1] & 1) { 16132 // can just grab it all but like we don't necessarily need them... 16133 xFormats = cast(Atom[]) getX11PropertyData(sourceWindow, GetAtom!"XdndTypeList"(display), XA_ATOM); 16134 } else { 16135 int len; 16136 foreach(fmt; xFormatsBuffer) 16137 if(fmt) len++; 16138 xFormats = xFormatsBuffer[0 .. len]; 16139 } 16140 16141 auto pkg = DropPackage(*win, e.xclient.data.l[0], 0, xFormats); 16142 16143 dh.dragEnter(&pkg); 16144 } else if(e.xclient.message_type == GetAtom!"XdndPosition"(e.xany.display)) { 16145 16146 auto pack = e.xclient.data.l[2]; 16147 16148 auto result = dh.dragOver(Point((pack & 0xffff0000) >> 16, pack & 0xffff)); // FIXME: translate screen coordinates back to window coords 16149 16150 16151 XClientMessageEvent xclient; 16152 16153 xclient.type = EventType.ClientMessage; 16154 xclient.window = e.xclient.data.l[0]; 16155 xclient.message_type = GetAtom!"XdndStatus"(display); 16156 xclient.format = 32; 16157 xclient.data.l[0] = win.impl.window; 16158 xclient.data.l[1] = (result.action != DragAndDropAction.none) ? 1 : 0; // will accept 16159 auto r = result.consistentWithin; 16160 xclient.data.l[2] = ((cast(short) r.left) << 16) | (cast(short) r.top); 16161 xclient.data.l[3] = ((cast(short) r.width) << 16) | (cast(short) r.height); 16162 xclient.data.l[4] = dndActionAtom(e.xany.display, result.action); 16163 16164 XSendEvent( 16165 display, 16166 e.xclient.data.l[0], 16167 false, 16168 EventMask.NoEventMask, 16169 cast(XEvent*) &xclient 16170 ); 16171 16172 16173 } else if(e.xclient.message_type == GetAtom!"XdndLeave"(e.xany.display)) { 16174 //writeln("XdndLeave"); 16175 // drop cancelled. 16176 // data.l[0] is the source window 16177 dh.dragLeave(); 16178 16179 resetXFormats(); 16180 } else if(e.xclient.message_type == GetAtom!"XdndDrop"(e.xany.display)) { 16181 // drop happening, should fetch data, then send finished 16182 // writeln("XdndDrop"); 16183 16184 auto pkg = DropPackage(*win, e.xclient.data.l[0], e.xclient.data.l[2], xFormats); 16185 16186 dh.drop(&pkg); 16187 16188 resetXFormats(); 16189 } else if(e.xclient.message_type == GetAtom!"XdndFinished"(e.xany.display)) { 16190 // writeln("XdndFinished"); 16191 16192 dh.finish(); 16193 } 16194 16195 } 16196 } 16197 break; 16198 case EventType.MapNotify: 16199 if(auto win = e.xmap.window in SimpleWindow.nativeMapping) { 16200 (*win)._visible = true; 16201 if (!(*win)._visibleForTheFirstTimeCalled) { 16202 (*win)._visibleForTheFirstTimeCalled = true; 16203 if ((*win).visibleForTheFirstTime !is null) { 16204 XUnlockDisplay(display); 16205 scope(exit) XLockDisplay(display); 16206 (*win).visibleForTheFirstTime(); 16207 } 16208 } 16209 if ((*win).visibilityChanged !is null) { 16210 XUnlockDisplay(display); 16211 scope(exit) XLockDisplay(display); 16212 (*win).visibilityChanged(true); 16213 } 16214 } 16215 break; 16216 case EventType.UnmapNotify: 16217 if(auto win = e.xunmap.window in SimpleWindow.nativeMapping) { 16218 win._visible = false; 16219 if (win.visibilityChanged !is null) { 16220 XUnlockDisplay(display); 16221 scope(exit) XLockDisplay(display); 16222 win.visibilityChanged(false); 16223 } 16224 } 16225 break; 16226 case EventType.DestroyNotify: 16227 if(auto win = e.xdestroywindow.window in SimpleWindow.nativeMapping) { 16228 if(win.destroyed) 16229 break; // might get a notification both for itself and from its parent 16230 if (win.onDestroyed !is null) try { win.onDestroyed(); } catch (Exception e) {} // sorry 16231 win._closed = true; // just in case 16232 win.destroyed = true; 16233 if (win.xic !is null) { 16234 XDestroyIC(win.xic); 16235 win.xic = null; // just in case 16236 } 16237 SimpleWindow.nativeMapping.remove(e.xdestroywindow.window); 16238 bool anyImportant = false; 16239 foreach(SimpleWindow w; SimpleWindow.nativeMapping) 16240 if(w.beingOpenKeepsAppOpen) { 16241 anyImportant = true; 16242 break; 16243 } 16244 if(!anyImportant) { 16245 EventLoop.quitApplication(); 16246 done = true; 16247 } 16248 } 16249 auto window = e.xdestroywindow.window; 16250 if(window in CapableOfHandlingNativeEvent.nativeHandleMapping) 16251 CapableOfHandlingNativeEvent.nativeHandleMapping.remove(window); 16252 16253 version(with_eventloop) { 16254 if(done) exit(); 16255 } 16256 break; 16257 16258 case EventType.MotionNotify: 16259 MouseEvent mouse; 16260 auto event = e.xmotion; 16261 16262 mouse.type = MouseEventType.motion; 16263 mouse.x = event.x; 16264 mouse.y = event.y; 16265 mouse.modifierState = event.state; 16266 16267 mouse.timestamp = event.time; 16268 16269 if(auto win = e.xmotion.window in SimpleWindow.nativeMapping) { 16270 mouse.window = *win; 16271 if (win.warpEventCount > 0) { 16272 debug(x11sdpy_warp_debug) { import core.stdc.stdio : printf; printf("X11: got \"warp motion\" message, current count=%d\n", (*win).warpEventCount); } 16273 --(*win).warpEventCount; 16274 (*win).mdx(mouse); // so deltas will be correctly updated 16275 } else { 16276 win.warpEventCount = 0; // just in case 16277 (*win).mdx(mouse); 16278 if((*win).handleMouseEvent) { 16279 XUnlockDisplay(display); 16280 scope(exit) XLockDisplay(display); 16281 (*win).handleMouseEvent(mouse); 16282 } 16283 } 16284 } 16285 16286 version(with_eventloop) 16287 send(mouse); 16288 break; 16289 case EventType.ButtonPress: 16290 case EventType.ButtonRelease: 16291 MouseEvent mouse; 16292 auto event = e.xbutton; 16293 16294 mouse.timestamp = event.time; 16295 16296 mouse.type = cast(MouseEventType) (e.type == EventType.ButtonPress ? 1 : 2); 16297 mouse.x = event.x; 16298 mouse.y = event.y; 16299 16300 static Time lastMouseDownTime = 0; 16301 static int lastMouseDownButton = -1; 16302 16303 mouse.doubleClick = e.type == EventType.ButtonPress && event.button == lastMouseDownButton && (event.time - lastMouseDownTime) < mouseDoubleClickTimeout; 16304 if(e.type == EventType.ButtonPress) { 16305 lastMouseDownTime = event.time; 16306 lastMouseDownButton = event.button; 16307 } 16308 16309 switch(event.button) { 16310 case 1: mouse.button = MouseButton.left; break; // left 16311 case 2: mouse.button = MouseButton.middle; break; // middle 16312 case 3: mouse.button = MouseButton.right; break; // right 16313 case 4: mouse.button = MouseButton.wheelUp; break; // scroll up 16314 case 5: mouse.button = MouseButton.wheelDown; break; // scroll down 16315 case 6: break; // idk 16316 case 7: break; // idk 16317 case 8: mouse.button = MouseButton.backButton; break; 16318 case 9: mouse.button = MouseButton.forwardButton; break; 16319 default: 16320 } 16321 16322 // FIXME: double check this 16323 mouse.modifierState = event.state; 16324 16325 //mouse.modifierState = event.detail; 16326 16327 if(auto win = e.xbutton.window in SimpleWindow.nativeMapping) { 16328 mouse.window = *win; 16329 (*win).mdx(mouse); 16330 if((*win).handleMouseEvent) { 16331 XUnlockDisplay(display); 16332 scope(exit) XLockDisplay(display); 16333 (*win).handleMouseEvent(mouse); 16334 } 16335 } 16336 version(with_eventloop) 16337 send(mouse); 16338 break; 16339 16340 case EventType.KeyPress: 16341 case EventType.KeyRelease: 16342 //if (e.type == EventType.KeyPress) { import core.stdc.stdio : stderr, fprintf; fprintf(stderr, "X11 keyboard event!\n"); } 16343 KeyEvent ke; 16344 ke.pressed = e.type == EventType.KeyPress; 16345 ke.hardwareCode = cast(ubyte) e.xkey.keycode; 16346 16347 auto sym = XKeycodeToKeysym( 16348 XDisplayConnection.get(), 16349 e.xkey.keycode, 16350 0); 16351 16352 ke.key = cast(Key) sym;//e.xkey.keycode; 16353 16354 ke.modifierState = e.xkey.state; 16355 16356 // writefln("%x", sym); 16357 wchar_t[128] charbuf = void; // buffer for XwcLookupString; composed value can consist of many chars! 16358 int charbuflen = 0; // return value of XwcLookupString 16359 if (ke.pressed) { 16360 auto win = e.xkey.window in SimpleWindow.nativeMapping; 16361 if (win !is null && win.xic !is null) { 16362 //{ import core.stdc.stdio : printf; printf("using xic!\n"); } 16363 Status status; 16364 charbuflen = XwcLookupString(win.xic, &e.xkey, charbuf.ptr, cast(int)charbuf.length, &sym, &status); 16365 //{ import core.stdc.stdio : printf; printf("charbuflen=%d\n", charbuflen); } 16366 } else { 16367 //{ import core.stdc.stdio : printf; printf("NOT using xic!\n"); } 16368 // If XIM initialization failed, don't process intl chars. Sorry, boys and girls. 16369 char[16] buffer; 16370 auto res = XLookupString(&e.xkey, buffer.ptr, buffer.length, null, null); 16371 if (res && buffer[0] < 128) charbuf[charbuflen++] = cast(wchar_t)buffer[0]; 16372 } 16373 } 16374 16375 // if there's no char, subst one 16376 if (charbuflen == 0) { 16377 switch (sym) { 16378 case 0xff09: charbuf[charbuflen++] = '\t'; break; 16379 case 0xff8d: // keypad enter 16380 case 0xff0d: charbuf[charbuflen++] = '\n'; break; 16381 default : // ignore 16382 } 16383 } 16384 16385 if (auto win = e.xkey.window in SimpleWindow.nativeMapping) { 16386 ke.window = *win; 16387 16388 16389 if(win.inputProxy) 16390 win = &win.inputProxy; 16391 16392 // char events are separate since they are on Windows too 16393 // also, xcompose can generate long char sequences 16394 // don't send char events if Meta and/or Hyper is pressed 16395 // TODO: ctrl+char should only send control chars; not yet 16396 if ((e.xkey.state&ModifierState.ctrl) != 0) { 16397 if (charbuflen > 1 || charbuf[0] >= ' ') charbuflen = 0; 16398 } 16399 16400 dchar[32] charsComingBuffer; 16401 int charsComingPosition; 16402 dchar[] charsComing = charsComingBuffer[]; 16403 16404 if (ke.pressed && charbuflen > 0) { 16405 // FIXME: I think Windows sends these on releases... we should try to match that, but idk about repeats. 16406 foreach (immutable dchar ch; charbuf[0..charbuflen]) { 16407 if(charsComingPosition >= charsComing.length) 16408 charsComing.length = charsComingPosition + 8; 16409 16410 charsComing[charsComingPosition++] = ch; 16411 } 16412 16413 charsComing = charsComing[0 .. charsComingPosition]; 16414 } else { 16415 charsComing = null; 16416 } 16417 16418 ke.charsPossible = charsComing; 16419 16420 if (win.handleKeyEvent) { 16421 XUnlockDisplay(display); 16422 scope(exit) XLockDisplay(display); 16423 win.handleKeyEvent(ke); 16424 } 16425 16426 // Super and alt modifier keys never actually send the chars, they are assumed to be special. 16427 if ((e.xkey.state&(ModifierState.alt|ModifierState.windows)) == 0 && win.handleCharEvent) { 16428 XUnlockDisplay(display); 16429 scope(exit) XLockDisplay(display); 16430 foreach(ch; charsComing) 16431 win.handleCharEvent(ch); 16432 } 16433 } 16434 16435 version(with_eventloop) 16436 send(ke); 16437 break; 16438 default: 16439 } 16440 16441 return done; 16442 } 16443 } 16444 16445 /* *************************************** */ 16446 /* Done with simpledisplay stuff */ 16447 /* *************************************** */ 16448 16449 // Necessary C library bindings follow 16450 version(Windows) {} else 16451 version(X11) { 16452 16453 extern(C) int eventfd (uint initval, int flags) nothrow @trusted @nogc; 16454 16455 // X11 bindings needed here 16456 /* 16457 A little of this is from the bindings project on 16458 D Source and some of it is copy/paste from the C 16459 header. 16460 16461 The DSource listing consistently used D's long 16462 where C used long. That's wrong - C long is 32 bit, so 16463 it should be int in D. I changed that here. 16464 16465 Note: 16466 This isn't complete, just took what I needed for myself. 16467 */ 16468 16469 import core.stdc.stddef : wchar_t; 16470 16471 interface XLib { 16472 extern(C) nothrow @nogc { 16473 char* XResourceManagerString(Display*); 16474 void XrmInitialize(); 16475 XrmDatabase XrmGetStringDatabase(char* data); 16476 bool XrmGetResource(XrmDatabase, const char*, const char*, char**, XrmValue*); 16477 16478 Cursor XCreateFontCursor(Display*, uint shape); 16479 int XDefineCursor(Display* display, Window w, Cursor cursor); 16480 int XUndefineCursor(Display* display, Window w); 16481 16482 Pixmap XCreateBitmapFromData(Display* display, Drawable d, const(char)* data, uint width, uint height); 16483 Cursor XCreatePixmapCursor(Display* display, Pixmap source, Pixmap mask, XColor* foreground_color, XColor* background_color, uint x, uint y); 16484 int XFreeCursor(Display* display, Cursor cursor); 16485 16486 int XLookupString(XKeyEvent *event_struct, char *buffer_return, int bytes_buffer, KeySym *keysym_return, void *status_in_out); 16487 16488 int XwcLookupString(XIC ic, XKeyPressedEvent* event, wchar_t* buffer_return, int wchars_buffer, KeySym* keysym_return, Status* status_return); 16489 16490 XVaNestedList XVaCreateNestedList(int unused, ...); 16491 16492 char *XKeysymToString(KeySym keysym); 16493 KeySym XKeycodeToKeysym( 16494 Display* /* display */, 16495 KeyCode /* keycode */, 16496 int /* index */ 16497 ); 16498 16499 int XConvertSelection(Display *display, Atom selection, Atom target, Atom property, Window requestor, Time time); 16500 16501 int XFree(void*); 16502 int XDeleteProperty(Display *display, Window w, Atom property); 16503 16504 int XChangeProperty(Display *display, Window w, Atom property, Atom type, int format, int mode, scope const void *data, int nelements); 16505 16506 int XGetWindowProperty(Display *display, Window w, Atom property, arch_long 16507 long_offset, arch_long long_length, Bool del, Atom req_type, Atom 16508 *actual_type_return, int *actual_format_return, arch_ulong 16509 *nitems_return, arch_ulong *bytes_after_return, void** prop_return); 16510 Atom* XListProperties(Display *display, Window w, int *num_prop_return); 16511 Status XGetTextProperty(Display *display, Window w, XTextProperty *text_prop_return, Atom property); 16512 Status XQueryTree(Display *display, Window w, Window *root_return, Window *parent_return, Window **children_return, uint *nchildren_return); 16513 16514 int XSetSelectionOwner(Display *display, Atom selection, Window owner, Time time); 16515 16516 Window XGetSelectionOwner(Display *display, Atom selection); 16517 16518 XVisualInfo* XGetVisualInfo(Display*, c_long, XVisualInfo*, int*); 16519 16520 char** XListFonts(Display*, const char*, int, int*); 16521 void XFreeFontNames(char**); 16522 16523 Display* XOpenDisplay(const char*); 16524 int XCloseDisplay(Display*); 16525 16526 int function() XSynchronize(Display*, bool); 16527 int function() XSetAfterFunction(Display*, int function() proc); 16528 16529 Bool XQueryExtension(Display*, const char*, int*, int*, int*); 16530 16531 Bool XSupportsLocale(); 16532 char* XSetLocaleModifiers(const(char)* modifier_list); 16533 XOM XOpenOM(Display* display, _XrmHashBucketRec* rdb, const(char)* res_name, const(char)* res_class); 16534 Status XCloseOM(XOM om); 16535 16536 XIM XOpenIM(Display* dpy, _XrmHashBucketRec* rdb, const(char)* res_name, const(char)* res_class); 16537 Status XCloseIM(XIM im); 16538 16539 char* XGetIMValues(XIM im, ...) /*_X_SENTINEL(0)*/; 16540 char* XSetIMValues(XIM im, ...) /*_X_SENTINEL(0)*/; 16541 Display* XDisplayOfIM(XIM im); 16542 char* XLocaleOfIM(XIM im); 16543 XIC XCreateIC(XIM im, ...) /*_X_SENTINEL(0)*/; 16544 void XDestroyIC(XIC ic); 16545 void XSetICFocus(XIC ic); 16546 void XUnsetICFocus(XIC ic); 16547 //wchar_t* XwcResetIC(XIC ic); 16548 char* XmbResetIC(XIC ic); 16549 char* Xutf8ResetIC(XIC ic); 16550 char* XSetICValues(XIC ic, ...) /*_X_SENTINEL(0)*/; 16551 char* XGetICValues(XIC ic, ...) /*_X_SENTINEL(0)*/; 16552 XIM XIMOfIC(XIC ic); 16553 16554 uint XSendEvent(Display* display, Window w, Bool propagate, arch_long event_mask, XEvent* event_send); 16555 16556 16557 XFontStruct *XLoadQueryFont(Display *display, scope const char *name); 16558 int XFreeFont(Display *display, XFontStruct *font_struct); 16559 int XSetFont(Display* display, GC gc, Font font); 16560 int XTextWidth(XFontStruct*, scope const char*, int); 16561 16562 int XSetLineAttributes(Display *display, GC gc, uint line_width, int line_style, int cap_style, int join_style); 16563 int XSetDashes(Display *display, GC gc, int dash_offset, scope const byte* dash_list, int n); 16564 16565 Window XCreateSimpleWindow( 16566 Display* /* display */, 16567 Window /* parent */, 16568 int /* x */, 16569 int /* y */, 16570 uint /* width */, 16571 uint /* height */, 16572 uint /* border_width */, 16573 uint /* border */, 16574 uint /* background */ 16575 ); 16576 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); 16577 16578 int XReparentWindow(Display*, Window, Window, int, int); 16579 int XClearWindow(Display*, Window); 16580 int XMoveResizeWindow(Display*, Window, int, int, uint, uint); 16581 int XMoveWindow(Display*, Window, int, int); 16582 int XResizeWindow(Display *display, Window w, uint width, uint height); 16583 16584 Colormap XCreateColormap(Display *display, Window w, Visual *visual, int alloc); 16585 16586 Status XMatchVisualInfo(Display *display, int screen, int depth, int class_, XVisualInfo *vinfo_return); 16587 16588 Status XGetWindowAttributes(Display*, Window, XWindowAttributes*); 16589 16590 XImage *XCreateImage( 16591 Display* /* display */, 16592 Visual* /* visual */, 16593 uint /* depth */, 16594 int /* format */, 16595 int /* offset */, 16596 ubyte* /* data */, 16597 uint /* width */, 16598 uint /* height */, 16599 int /* bitmap_pad */, 16600 int /* bytes_per_line */ 16601 ); 16602 16603 Status XInitImage (XImage* image); 16604 16605 Atom XInternAtom( 16606 Display* /* display */, 16607 const char* /* atom_name */, 16608 Bool /* only_if_exists */ 16609 ); 16610 16611 Status XInternAtoms(Display*, const char**, int, Bool, Atom*); 16612 char* XGetAtomName(Display*, Atom); 16613 Status XGetAtomNames(Display*, Atom*, int count, char**); 16614 16615 int XPutImage( 16616 Display* /* display */, 16617 Drawable /* d */, 16618 GC /* gc */, 16619 XImage* /* image */, 16620 int /* src_x */, 16621 int /* src_y */, 16622 int /* dest_x */, 16623 int /* dest_y */, 16624 uint /* width */, 16625 uint /* height */ 16626 ); 16627 16628 XImage *XGetImage(Display *display, Drawable d, int x, int y, uint width, uint height, c_ulong plane_mask, int format); 16629 16630 16631 int XDestroyWindow( 16632 Display* /* display */, 16633 Window /* w */ 16634 ); 16635 16636 int XDestroyImage(XImage*); 16637 16638 int XSelectInput( 16639 Display* /* display */, 16640 Window /* w */, 16641 EventMask /* event_mask */ 16642 ); 16643 16644 int XMapWindow( 16645 Display* /* display */, 16646 Window /* w */ 16647 ); 16648 16649 Status XIconifyWindow(Display*, Window, int); 16650 int XMapRaised(Display*, Window); 16651 int XMapSubwindows(Display*, Window); 16652 16653 int XNextEvent( 16654 Display* /* display */, 16655 XEvent* /* event_return */ 16656 ); 16657 16658 int XMaskEvent(Display*, arch_long, XEvent*); 16659 16660 Bool XFilterEvent(XEvent *event, Window window); 16661 int XRefreshKeyboardMapping(XMappingEvent *event_map); 16662 16663 Status XSetWMProtocols( 16664 Display* /* display */, 16665 Window /* w */, 16666 Atom* /* protocols */, 16667 int /* count */ 16668 ); 16669 16670 void XSetWMNormalHints(Display *display, Window w, XSizeHints *hints); 16671 Status XGetWMNormalHints(Display *display, Window w, XSizeHints *hints, c_long* supplied_return); 16672 16673 16674 Status XInitThreads(); 16675 void XLockDisplay (Display* display); 16676 void XUnlockDisplay (Display* display); 16677 16678 void XSetWMProperties(Display*, Window, XTextProperty*, XTextProperty*, char**, int, XSizeHints*, XWMHints*, XClassHint*); 16679 16680 int XSetWindowBackground (Display* display, Window w, c_ulong background_pixel); 16681 int XSetWindowBackgroundPixmap (Display* display, Window w, Pixmap background_pixmap); 16682 //int XSetWindowBorder (Display* display, Window w, c_ulong border_pixel); 16683 //int XSetWindowBorderPixmap (Display* display, Window w, Pixmap border_pixmap); 16684 //int XSetWindowBorderWidth (Display* display, Window w, uint width); 16685 16686 16687 // check out Xft too: http://www.keithp.com/~keithp/render/Xft.tutorial 16688 int XDrawString(Display*, Drawable, GC, int, int, scope const char*, int); 16689 int XDrawLine(Display*, Drawable, GC, int, int, int, int); 16690 int XDrawRectangle(Display*, Drawable, GC, int, int, uint, uint); 16691 int XDrawArc(Display*, Drawable, GC, int, int, uint, uint, int, int); 16692 int XFillRectangle(Display*, Drawable, GC, int, int, uint, uint); 16693 int XFillArc(Display*, Drawable, GC, int, int, uint, uint, int, int); 16694 int XDrawPoint(Display*, Drawable, GC, int, int); 16695 int XSetForeground(Display*, GC, uint); 16696 int XSetBackground(Display*, GC, uint); 16697 16698 XFontSet XCreateFontSet(Display*, const char*, char***, int*, char**); 16699 void XFreeFontSet(Display*, XFontSet); 16700 void Xutf8DrawString(Display*, Drawable, XFontSet, GC, int, int, scope const char*, int); 16701 void Xutf8DrawText(Display*, Drawable, GC, int, int, XmbTextItem*, int); 16702 16703 int Xutf8TextExtents(XFontSet font_set, const char *, int num_bytes, XRectangle *overall_ink_return, XRectangle *overall_logical_return); 16704 16705 16706 //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); 16707 16708 void XDrawText(Display*, Drawable, GC, int, int, XTextItem*, int); 16709 int XSetFunction(Display*, GC, int); 16710 16711 GC XCreateGC(Display*, Drawable, uint, void*); 16712 int XCopyGC(Display*, GC, uint, GC); 16713 int XFreeGC(Display*, GC); 16714 16715 bool XCheckWindowEvent(Display*, Window, int, XEvent*); 16716 bool XCheckMaskEvent(Display*, int, XEvent*); 16717 16718 int XPending(Display*); 16719 int XEventsQueued(Display* display, int mode); 16720 16721 Pixmap XCreatePixmap(Display*, Drawable, uint, uint, uint); 16722 int XFreePixmap(Display*, Pixmap); 16723 int XCopyArea(Display*, Drawable, Drawable, GC, int, int, uint, uint, int, int); 16724 int XFlush(Display*); 16725 int XBell(Display*, int); 16726 int XSync(Display*, bool); 16727 16728 int XGrabKey (Display* display, int keycode, uint modifiers, Window grab_window, Bool owner_events, int pointer_mode, int keyboard_mode); 16729 int XUngrabKey (Display* display, int keycode, uint modifiers, Window grab_window); 16730 16731 int XGrabKeyboard(Display*, Window, Bool, int, int, Time); 16732 int XUngrabKeyboard(Display*, Time); 16733 16734 KeyCode XKeysymToKeycode (Display* display, KeySym keysym); 16735 16736 KeySym XStringToKeysym(const char *string); 16737 16738 Bool XCheckTypedEvent(Display *display, int event_type, XEvent *event_return); 16739 16740 Window XDefaultRootWindow(Display*); 16741 16742 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); 16743 16744 int XUngrabButton(Display *display, uint button, uint modifiers, Window grab_window); 16745 16746 int XDrawLines(Display*, Drawable, GC, XPoint*, int, CoordMode); 16747 int XFillPolygon(Display*, Drawable, GC, XPoint*, int, PolygonShape, CoordMode); 16748 16749 Status XAllocColor(Display*, Colormap, XColor*); 16750 16751 int XWithdrawWindow(Display*, Window, int); 16752 int XUnmapWindow(Display*, Window); 16753 int XLowerWindow(Display*, Window); 16754 int XRaiseWindow(Display*, Window); 16755 16756 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); 16757 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); 16758 16759 int XGetInputFocus(Display*, Window*, int*); 16760 int XSetInputFocus(Display*, Window, int, Time); 16761 16762 XErrorHandler XSetErrorHandler(XErrorHandler); 16763 16764 int XGetErrorText(Display*, int, char*, int); 16765 16766 Bool XkbSetDetectableAutoRepeat(Display* dpy, Bool detectable, Bool* supported); 16767 16768 16769 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); 16770 int XUngrabPointer(Display *display, Time time); 16771 int XChangeActivePointerGrab(Display *display, uint event_mask, Cursor cursor, Time time); 16772 16773 int XCopyPlane(Display*, Drawable, Drawable, GC, int, int, uint, uint, int, int, arch_ulong); 16774 16775 Status XGetGeometry(Display*, Drawable, Window*, int*, int*, uint*, uint*, uint*, uint*); 16776 int XSetClipMask(Display*, GC, Pixmap); 16777 int XSetClipOrigin(Display*, GC, int, int); 16778 16779 void XSetClipRectangles(Display*, GC, int, int, XRectangle*, int, int); 16780 16781 void XSetWMName(Display*, Window, XTextProperty*); 16782 Status XGetWMName(Display*, Window, XTextProperty*); 16783 int XStoreName(Display* display, Window w, const(char)* window_name); 16784 16785 XIOErrorHandler XSetIOErrorHandler (XIOErrorHandler handler); 16786 16787 } 16788 } 16789 16790 interface Xext { 16791 extern(C) nothrow @nogc { 16792 Status XShmAttach(Display*, XShmSegmentInfo*); 16793 Status XShmDetach(Display*, XShmSegmentInfo*); 16794 Status XShmPutImage( 16795 Display* /* dpy */, 16796 Drawable /* d */, 16797 GC /* gc */, 16798 XImage* /* image */, 16799 int /* src_x */, 16800 int /* src_y */, 16801 int /* dst_x */, 16802 int /* dst_y */, 16803 uint /* src_width */, 16804 uint /* src_height */, 16805 Bool /* send_event */ 16806 ); 16807 16808 Status XShmQueryExtension(Display*); 16809 16810 XImage *XShmCreateImage( 16811 Display* /* dpy */, 16812 Visual* /* visual */, 16813 uint /* depth */, 16814 int /* format */, 16815 char* /* data */, 16816 XShmSegmentInfo* /* shminfo */, 16817 uint /* width */, 16818 uint /* height */ 16819 ); 16820 16821 Pixmap XShmCreatePixmap( 16822 Display* /* dpy */, 16823 Drawable /* d */, 16824 char* /* data */, 16825 XShmSegmentInfo* /* shminfo */, 16826 uint /* width */, 16827 uint /* height */, 16828 uint /* depth */ 16829 ); 16830 16831 } 16832 } 16833 16834 // this requires -lXpm 16835 //int XpmCreatePixmapFromData(Display*, Drawable, scope const char**, Pixmap*, Pixmap*, void*); // FIXME: void* should be XpmAttributes 16836 16837 16838 mixin DynamicLoad!(XLib, "X11", 6, librariesSuccessfullyLoaded) xlib; 16839 mixin DynamicLoad!(Xext, "Xext", 6, librariesSuccessfullyLoaded) xext; 16840 shared static this() { 16841 xlib.loadDynamicLibrary(); 16842 xext.loadDynamicLibrary(); 16843 } 16844 16845 16846 extern(C) nothrow @nogc { 16847 16848 alias XrmDatabase = void*; 16849 struct XrmValue { 16850 uint size; 16851 void* addr; 16852 } 16853 16854 struct XVisualInfo { 16855 Visual* visual; 16856 VisualID visualid; 16857 int screen; 16858 uint depth; 16859 int c_class; 16860 c_ulong red_mask; 16861 c_ulong green_mask; 16862 c_ulong blue_mask; 16863 int colormap_size; 16864 int bits_per_rgb; 16865 } 16866 16867 enum VisualNoMask= 0x0; 16868 enum VisualIDMask= 0x1; 16869 enum VisualScreenMask=0x2; 16870 enum VisualDepthMask= 0x4; 16871 enum VisualClassMask= 0x8; 16872 enum VisualRedMaskMask=0x10; 16873 enum VisualGreenMaskMask=0x20; 16874 enum VisualBlueMaskMask=0x40; 16875 enum VisualColormapSizeMask=0x80; 16876 enum VisualBitsPerRGBMask=0x100; 16877 enum VisualAllMask= 0x1FF; 16878 16879 enum AnyKey = 0; 16880 enum AnyModifier = 1 << 15; 16881 16882 // XIM and other crap 16883 struct _XOM {} 16884 struct _XIM {} 16885 struct _XIC {} 16886 alias XOM = _XOM*; 16887 alias XIM = _XIM*; 16888 alias XIC = _XIC*; 16889 16890 alias XVaNestedList = void*; 16891 16892 alias XIMStyle = arch_ulong; 16893 enum : arch_ulong { 16894 XIMPreeditArea = 0x0001, 16895 XIMPreeditCallbacks = 0x0002, 16896 XIMPreeditPosition = 0x0004, 16897 XIMPreeditNothing = 0x0008, 16898 XIMPreeditNone = 0x0010, 16899 XIMStatusArea = 0x0100, 16900 XIMStatusCallbacks = 0x0200, 16901 XIMStatusNothing = 0x0400, 16902 XIMStatusNone = 0x0800, 16903 } 16904 16905 16906 /* X Shared Memory Extension functions */ 16907 //pragma(lib, "Xshm"); 16908 alias arch_ulong ShmSeg; 16909 struct XShmSegmentInfo { 16910 ShmSeg shmseg; 16911 int shmid; 16912 ubyte* shmaddr; 16913 Bool readOnly; 16914 } 16915 16916 // and the necessary OS functions 16917 int shmget(int, size_t, int); 16918 void* shmat(int, scope const void*, int); 16919 int shmdt(scope const void*); 16920 int shmctl (int shmid, int cmd, void* ptr /*struct shmid_ds *buf*/); 16921 16922 enum IPC_PRIVATE = 0; 16923 enum IPC_CREAT = 512; 16924 enum IPC_RMID = 0; 16925 16926 /* MIT-SHM end */ 16927 16928 16929 enum MappingType:int { 16930 MappingModifier =0, 16931 MappingKeyboard =1, 16932 MappingPointer =2 16933 } 16934 16935 /* ImageFormat -- PutImage, GetImage */ 16936 enum ImageFormat:int { 16937 XYBitmap =0, /* depth 1, XYFormat */ 16938 XYPixmap =1, /* depth == drawable depth */ 16939 ZPixmap =2 /* depth == drawable depth */ 16940 } 16941 16942 enum ModifierName:int { 16943 ShiftMapIndex =0, 16944 LockMapIndex =1, 16945 ControlMapIndex =2, 16946 Mod1MapIndex =3, 16947 Mod2MapIndex =4, 16948 Mod3MapIndex =5, 16949 Mod4MapIndex =6, 16950 Mod5MapIndex =7 16951 } 16952 16953 enum ButtonMask:int { 16954 Button1Mask =1<<8, 16955 Button2Mask =1<<9, 16956 Button3Mask =1<<10, 16957 Button4Mask =1<<11, 16958 Button5Mask =1<<12, 16959 AnyModifier =1<<15/* used in GrabButton, GrabKey */ 16960 } 16961 16962 enum KeyOrButtonMask:uint { 16963 ShiftMask =1<<0, 16964 LockMask =1<<1, 16965 ControlMask =1<<2, 16966 Mod1Mask =1<<3, 16967 Mod2Mask =1<<4, 16968 Mod3Mask =1<<5, 16969 Mod4Mask =1<<6, 16970 Mod5Mask =1<<7, 16971 Button1Mask =1<<8, 16972 Button2Mask =1<<9, 16973 Button3Mask =1<<10, 16974 Button4Mask =1<<11, 16975 Button5Mask =1<<12, 16976 AnyModifier =1<<15/* used in GrabButton, GrabKey */ 16977 } 16978 16979 enum ButtonName:int { 16980 Button1 =1, 16981 Button2 =2, 16982 Button3 =3, 16983 Button4 =4, 16984 Button5 =5 16985 } 16986 16987 /* Notify modes */ 16988 enum NotifyModes:int 16989 { 16990 NotifyNormal =0, 16991 NotifyGrab =1, 16992 NotifyUngrab =2, 16993 NotifyWhileGrabbed =3 16994 } 16995 enum NotifyHint = 1; /* for MotionNotify events */ 16996 16997 /* Notify detail */ 16998 enum NotifyDetail:int 16999 { 17000 NotifyAncestor =0, 17001 NotifyVirtual =1, 17002 NotifyInferior =2, 17003 NotifyNonlinear =3, 17004 NotifyNonlinearVirtual =4, 17005 NotifyPointer =5, 17006 NotifyPointerRoot =6, 17007 NotifyDetailNone =7 17008 } 17009 17010 /* Visibility notify */ 17011 17012 enum VisibilityNotify:int 17013 { 17014 VisibilityUnobscured =0, 17015 VisibilityPartiallyObscured =1, 17016 VisibilityFullyObscured =2 17017 } 17018 17019 17020 enum WindowStackingMethod:int 17021 { 17022 Above =0, 17023 Below =1, 17024 TopIf =2, 17025 BottomIf =3, 17026 Opposite =4 17027 } 17028 17029 /* Circulation request */ 17030 enum CirculationRequest:int 17031 { 17032 PlaceOnTop =0, 17033 PlaceOnBottom =1 17034 } 17035 17036 enum PropertyNotification:int 17037 { 17038 PropertyNewValue =0, 17039 PropertyDelete =1 17040 } 17041 17042 enum ColorMapNotification:int 17043 { 17044 ColormapUninstalled =0, 17045 ColormapInstalled =1 17046 } 17047 17048 17049 struct _XPrivate {} 17050 struct _XrmHashBucketRec {} 17051 17052 alias void* XPointer; 17053 alias void* XExtData; 17054 17055 version( X86_64 ) { 17056 alias ulong XID; 17057 alias ulong arch_ulong; 17058 alias long arch_long; 17059 } else version (AArch64) { 17060 alias ulong XID; 17061 alias ulong arch_ulong; 17062 alias long arch_long; 17063 } else { 17064 alias uint XID; 17065 alias uint arch_ulong; 17066 alias int arch_long; 17067 } 17068 17069 alias XID Window; 17070 alias XID Drawable; 17071 alias XID Pixmap; 17072 17073 alias arch_ulong Atom; 17074 alias int Bool; 17075 alias Display XDisplay; 17076 17077 alias int ByteOrder; 17078 alias arch_ulong Time; 17079 alias void ScreenFormat; 17080 17081 struct XImage { 17082 int width, height; /* size of image */ 17083 int xoffset; /* number of pixels offset in X direction */ 17084 ImageFormat format; /* XYBitmap, XYPixmap, ZPixmap */ 17085 void *data; /* pointer to image data */ 17086 ByteOrder byte_order; /* data byte order, LSBFirst, MSBFirst */ 17087 int bitmap_unit; /* quant. of scanline 8, 16, 32 */ 17088 int bitmap_bit_order; /* LSBFirst, MSBFirst */ 17089 int bitmap_pad; /* 8, 16, 32 either XY or ZPixmap */ 17090 int depth; /* depth of image */ 17091 int bytes_per_line; /* accelarator to next line */ 17092 int bits_per_pixel; /* bits per pixel (ZPixmap) */ 17093 arch_ulong red_mask; /* bits in z arrangment */ 17094 arch_ulong green_mask; 17095 arch_ulong blue_mask; 17096 XPointer obdata; /* hook for the object routines to hang on */ 17097 static struct F { /* image manipulation routines */ 17098 XImage* function( 17099 XDisplay* /* display */, 17100 Visual* /* visual */, 17101 uint /* depth */, 17102 int /* format */, 17103 int /* offset */, 17104 ubyte* /* data */, 17105 uint /* width */, 17106 uint /* height */, 17107 int /* bitmap_pad */, 17108 int /* bytes_per_line */) create_image; 17109 int function(XImage *) destroy_image; 17110 arch_ulong function(XImage *, int, int) get_pixel; 17111 int function(XImage *, int, int, arch_ulong) put_pixel; 17112 XImage* function(XImage *, int, int, uint, uint) sub_image; 17113 int function(XImage *, arch_long) add_pixel; 17114 } 17115 F f; 17116 } 17117 version(X86_64) static assert(XImage.sizeof == 136); 17118 else version(X86) static assert(XImage.sizeof == 88); 17119 17120 struct XCharStruct { 17121 short lbearing; /* origin to left edge of raster */ 17122 short rbearing; /* origin to right edge of raster */ 17123 short width; /* advance to next char's origin */ 17124 short ascent; /* baseline to top edge of raster */ 17125 short descent; /* baseline to bottom edge of raster */ 17126 ushort attributes; /* per char flags (not predefined) */ 17127 } 17128 17129 /* 17130 * To allow arbitrary information with fonts, there are additional properties 17131 * returned. 17132 */ 17133 struct XFontProp { 17134 Atom name; 17135 arch_ulong card32; 17136 } 17137 17138 alias Atom Font; 17139 17140 struct XFontStruct { 17141 XExtData *ext_data; /* Hook for extension to hang data */ 17142 Font fid; /* Font ID for this font */ 17143 uint direction; /* Direction the font is painted */ 17144 uint min_char_or_byte2; /* First character */ 17145 uint max_char_or_byte2; /* Last character */ 17146 uint min_byte1; /* First row that exists (for two-byte fonts) */ 17147 uint max_byte1; /* Last row that exists (for two-byte fonts) */ 17148 Bool all_chars_exist; /* Flag if all characters have nonzero size */ 17149 uint default_char; /* Char to print for undefined character */ 17150 int n_properties; /* How many properties there are */ 17151 XFontProp *properties; /* Pointer to array of additional properties*/ 17152 XCharStruct min_bounds; /* Minimum bounds over all existing char*/ 17153 XCharStruct max_bounds; /* Maximum bounds over all existing char*/ 17154 XCharStruct *per_char; /* first_char to last_char information */ 17155 int ascent; /* Max extent above baseline for spacing */ 17156 int descent; /* Max descent below baseline for spacing */ 17157 } 17158 17159 17160 /* 17161 * Definitions of specific events. 17162 */ 17163 struct XKeyEvent 17164 { 17165 int type; /* of event */ 17166 arch_ulong serial; /* # of last request processed by server */ 17167 Bool send_event; /* true if this came from a SendEvent request */ 17168 Display *display; /* Display the event was read from */ 17169 Window window; /* "event" window it is reported relative to */ 17170 Window root; /* root window that the event occurred on */ 17171 Window subwindow; /* child window */ 17172 Time time; /* milliseconds */ 17173 int x, y; /* pointer x, y coordinates in event window */ 17174 int x_root, y_root; /* coordinates relative to root */ 17175 KeyOrButtonMask state; /* key or button mask */ 17176 uint keycode; /* detail */ 17177 Bool same_screen; /* same screen flag */ 17178 } 17179 version(X86_64) static assert(XKeyEvent.sizeof == 96); 17180 alias XKeyEvent XKeyPressedEvent; 17181 alias XKeyEvent XKeyReleasedEvent; 17182 17183 struct XButtonEvent 17184 { 17185 int type; /* of event */ 17186 arch_ulong serial; /* # of last request processed by server */ 17187 Bool send_event; /* true if this came from a SendEvent request */ 17188 Display *display; /* Display the event was read from */ 17189 Window window; /* "event" window it is reported relative to */ 17190 Window root; /* root window that the event occurred on */ 17191 Window subwindow; /* child window */ 17192 Time time; /* milliseconds */ 17193 int x, y; /* pointer x, y coordinates in event window */ 17194 int x_root, y_root; /* coordinates relative to root */ 17195 KeyOrButtonMask state; /* key or button mask */ 17196 uint button; /* detail */ 17197 Bool same_screen; /* same screen flag */ 17198 } 17199 alias XButtonEvent XButtonPressedEvent; 17200 alias XButtonEvent XButtonReleasedEvent; 17201 17202 struct XMotionEvent{ 17203 int type; /* of event */ 17204 arch_ulong serial; /* # of last request processed by server */ 17205 Bool send_event; /* true if this came from a SendEvent request */ 17206 Display *display; /* Display the event was read from */ 17207 Window window; /* "event" window reported relative to */ 17208 Window root; /* root window that the event occurred on */ 17209 Window subwindow; /* child window */ 17210 Time time; /* milliseconds */ 17211 int x, y; /* pointer x, y coordinates in event window */ 17212 int x_root, y_root; /* coordinates relative to root */ 17213 KeyOrButtonMask state; /* key or button mask */ 17214 byte is_hint; /* detail */ 17215 Bool same_screen; /* same screen flag */ 17216 } 17217 alias XMotionEvent XPointerMovedEvent; 17218 17219 struct XCrossingEvent{ 17220 int type; /* of event */ 17221 arch_ulong serial; /* # of last request processed by server */ 17222 Bool send_event; /* true if this came from a SendEvent request */ 17223 Display *display; /* Display the event was read from */ 17224 Window window; /* "event" window reported relative to */ 17225 Window root; /* root window that the event occurred on */ 17226 Window subwindow; /* child window */ 17227 Time time; /* milliseconds */ 17228 int x, y; /* pointer x, y coordinates in event window */ 17229 int x_root, y_root; /* coordinates relative to root */ 17230 NotifyModes mode; /* NotifyNormal, NotifyGrab, NotifyUngrab */ 17231 NotifyDetail detail; 17232 /* 17233 * NotifyAncestor, NotifyVirtual, NotifyInferior, 17234 * NotifyNonlinear,NotifyNonlinearVirtual 17235 */ 17236 Bool same_screen; /* same screen flag */ 17237 Bool focus; /* Boolean focus */ 17238 KeyOrButtonMask state; /* key or button mask */ 17239 } 17240 alias XCrossingEvent XEnterWindowEvent; 17241 alias XCrossingEvent XLeaveWindowEvent; 17242 17243 struct XFocusChangeEvent{ 17244 int type; /* FocusIn or FocusOut */ 17245 arch_ulong serial; /* # of last request processed by server */ 17246 Bool send_event; /* true if this came from a SendEvent request */ 17247 Display *display; /* Display the event was read from */ 17248 Window window; /* window of event */ 17249 NotifyModes mode; /* NotifyNormal, NotifyWhileGrabbed, 17250 NotifyGrab, NotifyUngrab */ 17251 NotifyDetail detail; 17252 /* 17253 * NotifyAncestor, NotifyVirtual, NotifyInferior, 17254 * NotifyNonlinear,NotifyNonlinearVirtual, NotifyPointer, 17255 * NotifyPointerRoot, NotifyDetailNone 17256 */ 17257 } 17258 alias XFocusChangeEvent XFocusInEvent; 17259 alias XFocusChangeEvent XFocusOutEvent; 17260 17261 enum CWBackPixmap = (1L<<0); 17262 enum CWBackPixel = (1L<<1); 17263 enum CWBorderPixmap = (1L<<2); 17264 enum CWBorderPixel = (1L<<3); 17265 enum CWBitGravity = (1L<<4); 17266 enum CWWinGravity = (1L<<5); 17267 enum CWBackingStore = (1L<<6); 17268 enum CWBackingPlanes = (1L<<7); 17269 enum CWBackingPixel = (1L<<8); 17270 enum CWOverrideRedirect = (1L<<9); 17271 enum CWSaveUnder = (1L<<10); 17272 enum CWEventMask = (1L<<11); 17273 enum CWDontPropagate = (1L<<12); 17274 enum CWColormap = (1L<<13); 17275 enum CWCursor = (1L<<14); 17276 17277 struct XWindowAttributes { 17278 int x, y; /* location of window */ 17279 int width, height; /* width and height of window */ 17280 int border_width; /* border width of window */ 17281 int depth; /* depth of window */ 17282 Visual *visual; /* the associated visual structure */ 17283 Window root; /* root of screen containing window */ 17284 int class_; /* InputOutput, InputOnly*/ 17285 int bit_gravity; /* one of the bit gravity values */ 17286 int win_gravity; /* one of the window gravity values */ 17287 int backing_store; /* NotUseful, WhenMapped, Always */ 17288 arch_ulong backing_planes; /* planes to be preserved if possible */ 17289 arch_ulong backing_pixel; /* value to be used when restoring planes */ 17290 Bool save_under; /* boolean, should bits under be saved? */ 17291 Colormap colormap; /* color map to be associated with window */ 17292 Bool map_installed; /* boolean, is color map currently installed*/ 17293 int map_state; /* IsUnmapped, IsUnviewable, IsViewable */ 17294 arch_long all_event_masks; /* set of events all people have interest in*/ 17295 arch_long your_event_mask; /* my event mask */ 17296 arch_long do_not_propagate_mask; /* set of events that should not propagate */ 17297 Bool override_redirect; /* boolean value for override-redirect */ 17298 Screen *screen; /* back pointer to correct screen */ 17299 } 17300 17301 enum IsUnmapped = 0; 17302 enum IsUnviewable = 1; 17303 enum IsViewable = 2; 17304 17305 struct XSetWindowAttributes { 17306 Pixmap background_pixmap;/* background, None, or ParentRelative */ 17307 arch_ulong background_pixel;/* background pixel */ 17308 Pixmap border_pixmap; /* border of the window or CopyFromParent */ 17309 arch_ulong border_pixel;/* border pixel value */ 17310 int bit_gravity; /* one of bit gravity values */ 17311 int win_gravity; /* one of the window gravity values */ 17312 int backing_store; /* NotUseful, WhenMapped, Always */ 17313 arch_ulong backing_planes;/* planes to be preserved if possible */ 17314 arch_ulong backing_pixel;/* value to use in restoring planes */ 17315 Bool save_under; /* should bits under be saved? (popups) */ 17316 arch_long event_mask; /* set of events that should be saved */ 17317 arch_long do_not_propagate_mask;/* set of events that should not propagate */ 17318 Bool override_redirect; /* boolean value for override_redirect */ 17319 Colormap colormap; /* color map to be associated with window */ 17320 Cursor cursor; /* cursor to be displayed (or None) */ 17321 } 17322 17323 17324 alias int Status; 17325 17326 17327 enum EventMask:int 17328 { 17329 NoEventMask =0, 17330 KeyPressMask =1<<0, 17331 KeyReleaseMask =1<<1, 17332 ButtonPressMask =1<<2, 17333 ButtonReleaseMask =1<<3, 17334 EnterWindowMask =1<<4, 17335 LeaveWindowMask =1<<5, 17336 PointerMotionMask =1<<6, 17337 PointerMotionHintMask =1<<7, 17338 Button1MotionMask =1<<8, 17339 Button2MotionMask =1<<9, 17340 Button3MotionMask =1<<10, 17341 Button4MotionMask =1<<11, 17342 Button5MotionMask =1<<12, 17343 ButtonMotionMask =1<<13, 17344 KeymapStateMask =1<<14, 17345 ExposureMask =1<<15, 17346 VisibilityChangeMask =1<<16, 17347 StructureNotifyMask =1<<17, 17348 ResizeRedirectMask =1<<18, 17349 SubstructureNotifyMask =1<<19, 17350 SubstructureRedirectMask=1<<20, 17351 FocusChangeMask =1<<21, 17352 PropertyChangeMask =1<<22, 17353 ColormapChangeMask =1<<23, 17354 OwnerGrabButtonMask =1<<24 17355 } 17356 17357 struct MwmHints { 17358 c_ulong flags; 17359 c_ulong functions; 17360 c_ulong decorations; 17361 c_long input_mode; 17362 c_ulong status; 17363 } 17364 17365 enum { 17366 MWM_HINTS_FUNCTIONS = (1L << 0), 17367 MWM_HINTS_DECORATIONS = (1L << 1), 17368 17369 MWM_FUNC_ALL = (1L << 0), 17370 MWM_FUNC_RESIZE = (1L << 1), 17371 MWM_FUNC_MOVE = (1L << 2), 17372 MWM_FUNC_MINIMIZE = (1L << 3), 17373 MWM_FUNC_MAXIMIZE = (1L << 4), 17374 MWM_FUNC_CLOSE = (1L << 5), 17375 17376 MWM_DECOR_ALL = (1L << 0), 17377 MWM_DECOR_BORDER = (1L << 1), 17378 MWM_DECOR_RESIZEH = (1L << 2), 17379 MWM_DECOR_TITLE = (1L << 3), 17380 MWM_DECOR_MENU = (1L << 4), 17381 MWM_DECOR_MINIMIZE = (1L << 5), 17382 MWM_DECOR_MAXIMIZE = (1L << 6), 17383 } 17384 17385 import core.stdc.config : c_long, c_ulong; 17386 17387 /* Size hints mask bits */ 17388 17389 enum USPosition = (1L << 0) /* user specified x, y */; 17390 enum USSize = (1L << 1) /* user specified width, height */; 17391 enum PPosition = (1L << 2) /* program specified position */; 17392 enum PSize = (1L << 3) /* program specified size */; 17393 enum PMinSize = (1L << 4) /* program specified minimum size */; 17394 enum PMaxSize = (1L << 5) /* program specified maximum size */; 17395 enum PResizeInc = (1L << 6) /* program specified resize increments */; 17396 enum PAspect = (1L << 7) /* program specified min and max aspect ratios */; 17397 enum PBaseSize = (1L << 8); 17398 enum PWinGravity = (1L << 9); 17399 enum PAllHints = (PPosition|PSize| PMinSize|PMaxSize| PResizeInc|PAspect); 17400 struct XSizeHints { 17401 arch_long flags; /* marks which fields in this structure are defined */ 17402 int x, y; /* Obsolete */ 17403 int width, height; /* Obsolete */ 17404 int min_width, min_height; 17405 int max_width, max_height; 17406 int width_inc, height_inc; 17407 struct Aspect { 17408 int x; /* numerator */ 17409 int y; /* denominator */ 17410 } 17411 17412 Aspect min_aspect; 17413 Aspect max_aspect; 17414 int base_width, base_height; 17415 int win_gravity; 17416 /* this structure may be extended in the future */ 17417 } 17418 17419 17420 17421 enum EventType:int 17422 { 17423 KeyPress =2, 17424 KeyRelease =3, 17425 ButtonPress =4, 17426 ButtonRelease =5, 17427 MotionNotify =6, 17428 EnterNotify =7, 17429 LeaveNotify =8, 17430 FocusIn =9, 17431 FocusOut =10, 17432 KeymapNotify =11, 17433 Expose =12, 17434 GraphicsExpose =13, 17435 NoExpose =14, 17436 VisibilityNotify =15, 17437 CreateNotify =16, 17438 DestroyNotify =17, 17439 UnmapNotify =18, 17440 MapNotify =19, 17441 MapRequest =20, 17442 ReparentNotify =21, 17443 ConfigureNotify =22, 17444 ConfigureRequest =23, 17445 GravityNotify =24, 17446 ResizeRequest =25, 17447 CirculateNotify =26, 17448 CirculateRequest =27, 17449 PropertyNotify =28, 17450 SelectionClear =29, 17451 SelectionRequest =30, 17452 SelectionNotify =31, 17453 ColormapNotify =32, 17454 ClientMessage =33, 17455 MappingNotify =34, 17456 LASTEvent =35 /* must be bigger than any event # */ 17457 } 17458 /* generated on EnterWindow and FocusIn when KeyMapState selected */ 17459 struct XKeymapEvent 17460 { 17461 int type; 17462 arch_ulong serial; /* # of last request processed by server */ 17463 Bool send_event; /* true if this came from a SendEvent request */ 17464 Display *display; /* Display the event was read from */ 17465 Window window; 17466 byte[32] key_vector; 17467 } 17468 17469 struct XExposeEvent 17470 { 17471 int type; 17472 arch_ulong serial; /* # of last request processed by server */ 17473 Bool send_event; /* true if this came from a SendEvent request */ 17474 Display *display; /* Display the event was read from */ 17475 Window window; 17476 int x, y; 17477 int width, height; 17478 int count; /* if non-zero, at least this many more */ 17479 } 17480 17481 struct XGraphicsExposeEvent{ 17482 int type; 17483 arch_ulong serial; /* # of last request processed by server */ 17484 Bool send_event; /* true if this came from a SendEvent request */ 17485 Display *display; /* Display the event was read from */ 17486 Drawable drawable; 17487 int x, y; 17488 int width, height; 17489 int count; /* if non-zero, at least this many more */ 17490 int major_code; /* core is CopyArea or CopyPlane */ 17491 int minor_code; /* not defined in the core */ 17492 } 17493 17494 struct XNoExposeEvent{ 17495 int type; 17496 arch_ulong serial; /* # of last request processed by server */ 17497 Bool send_event; /* true if this came from a SendEvent request */ 17498 Display *display; /* Display the event was read from */ 17499 Drawable drawable; 17500 int major_code; /* core is CopyArea or CopyPlane */ 17501 int minor_code; /* not defined in the core */ 17502 } 17503 17504 struct XVisibilityEvent{ 17505 int type; 17506 arch_ulong serial; /* # of last request processed by server */ 17507 Bool send_event; /* true if this came from a SendEvent request */ 17508 Display *display; /* Display the event was read from */ 17509 Window window; 17510 VisibilityNotify state; /* Visibility state */ 17511 } 17512 17513 struct XCreateWindowEvent{ 17514 int type; 17515 arch_ulong serial; /* # of last request processed by server */ 17516 Bool send_event; /* true if this came from a SendEvent request */ 17517 Display *display; /* Display the event was read from */ 17518 Window parent; /* parent of the window */ 17519 Window window; /* window id of window created */ 17520 int x, y; /* window location */ 17521 int width, height; /* size of window */ 17522 int border_width; /* border width */ 17523 Bool override_redirect; /* creation should be overridden */ 17524 } 17525 17526 struct XDestroyWindowEvent 17527 { 17528 int type; 17529 arch_ulong serial; /* # of last request processed by server */ 17530 Bool send_event; /* true if this came from a SendEvent request */ 17531 Display *display; /* Display the event was read from */ 17532 Window event; 17533 Window window; 17534 } 17535 17536 struct XUnmapEvent 17537 { 17538 int type; 17539 arch_ulong serial; /* # of last request processed by server */ 17540 Bool send_event; /* true if this came from a SendEvent request */ 17541 Display *display; /* Display the event was read from */ 17542 Window event; 17543 Window window; 17544 Bool from_configure; 17545 } 17546 17547 struct XMapEvent 17548 { 17549 int type; 17550 arch_ulong serial; /* # of last request processed by server */ 17551 Bool send_event; /* true if this came from a SendEvent request */ 17552 Display *display; /* Display the event was read from */ 17553 Window event; 17554 Window window; 17555 Bool override_redirect; /* Boolean, is override set... */ 17556 } 17557 17558 struct XMapRequestEvent 17559 { 17560 int type; 17561 arch_ulong serial; /* # of last request processed by server */ 17562 Bool send_event; /* true if this came from a SendEvent request */ 17563 Display *display; /* Display the event was read from */ 17564 Window parent; 17565 Window window; 17566 } 17567 17568 struct XReparentEvent 17569 { 17570 int type; 17571 arch_ulong serial; /* # of last request processed by server */ 17572 Bool send_event; /* true if this came from a SendEvent request */ 17573 Display *display; /* Display the event was read from */ 17574 Window event; 17575 Window window; 17576 Window parent; 17577 int x, y; 17578 Bool override_redirect; 17579 } 17580 17581 struct XConfigureEvent 17582 { 17583 int type; 17584 arch_ulong serial; /* # of last request processed by server */ 17585 Bool send_event; /* true if this came from a SendEvent request */ 17586 Display *display; /* Display the event was read from */ 17587 Window event; 17588 Window window; 17589 int x, y; 17590 int width, height; 17591 int border_width; 17592 Window above; 17593 Bool override_redirect; 17594 } 17595 17596 struct XGravityEvent 17597 { 17598 int type; 17599 arch_ulong serial; /* # of last request processed by server */ 17600 Bool send_event; /* true if this came from a SendEvent request */ 17601 Display *display; /* Display the event was read from */ 17602 Window event; 17603 Window window; 17604 int x, y; 17605 } 17606 17607 struct XResizeRequestEvent 17608 { 17609 int type; 17610 arch_ulong serial; /* # of last request processed by server */ 17611 Bool send_event; /* true if this came from a SendEvent request */ 17612 Display *display; /* Display the event was read from */ 17613 Window window; 17614 int width, height; 17615 } 17616 17617 struct XConfigureRequestEvent 17618 { 17619 int type; 17620 arch_ulong serial; /* # of last request processed by server */ 17621 Bool send_event; /* true if this came from a SendEvent request */ 17622 Display *display; /* Display the event was read from */ 17623 Window parent; 17624 Window window; 17625 int x, y; 17626 int width, height; 17627 int border_width; 17628 Window above; 17629 WindowStackingMethod detail; /* Above, Below, TopIf, BottomIf, Opposite */ 17630 arch_ulong value_mask; 17631 } 17632 17633 struct XCirculateEvent 17634 { 17635 int type; 17636 arch_ulong serial; /* # of last request processed by server */ 17637 Bool send_event; /* true if this came from a SendEvent request */ 17638 Display *display; /* Display the event was read from */ 17639 Window event; 17640 Window window; 17641 CirculationRequest place; /* PlaceOnTop, PlaceOnBottom */ 17642 } 17643 17644 struct XCirculateRequestEvent 17645 { 17646 int type; 17647 arch_ulong serial; /* # of last request processed by server */ 17648 Bool send_event; /* true if this came from a SendEvent request */ 17649 Display *display; /* Display the event was read from */ 17650 Window parent; 17651 Window window; 17652 CirculationRequest place; /* PlaceOnTop, PlaceOnBottom */ 17653 } 17654 17655 struct XPropertyEvent 17656 { 17657 int type; 17658 arch_ulong serial; /* # of last request processed by server */ 17659 Bool send_event; /* true if this came from a SendEvent request */ 17660 Display *display; /* Display the event was read from */ 17661 Window window; 17662 Atom atom; 17663 Time time; 17664 PropertyNotification state; /* NewValue, Deleted */ 17665 } 17666 17667 struct XSelectionClearEvent 17668 { 17669 int type; 17670 arch_ulong serial; /* # of last request processed by server */ 17671 Bool send_event; /* true if this came from a SendEvent request */ 17672 Display *display; /* Display the event was read from */ 17673 Window window; 17674 Atom selection; 17675 Time time; 17676 } 17677 17678 struct XSelectionRequestEvent 17679 { 17680 int type; 17681 arch_ulong serial; /* # of last request processed by server */ 17682 Bool send_event; /* true if this came from a SendEvent request */ 17683 Display *display; /* Display the event was read from */ 17684 Window owner; 17685 Window requestor; 17686 Atom selection; 17687 Atom target; 17688 Atom property; 17689 Time time; 17690 } 17691 17692 struct XSelectionEvent 17693 { 17694 int type; 17695 arch_ulong serial; /* # of last request processed by server */ 17696 Bool send_event; /* true if this came from a SendEvent request */ 17697 Display *display; /* Display the event was read from */ 17698 Window requestor; 17699 Atom selection; 17700 Atom target; 17701 Atom property; /* ATOM or None */ 17702 Time time; 17703 } 17704 version(X86_64) static assert(XSelectionClearEvent.sizeof == 56); 17705 17706 struct XColormapEvent 17707 { 17708 int type; 17709 arch_ulong serial; /* # of last request processed by server */ 17710 Bool send_event; /* true if this came from a SendEvent request */ 17711 Display *display; /* Display the event was read from */ 17712 Window window; 17713 Colormap colormap; /* COLORMAP or None */ 17714 Bool new_; /* C++ */ 17715 ColorMapNotification state; /* ColormapInstalled, ColormapUninstalled */ 17716 } 17717 version(X86_64) static assert(XColormapEvent.sizeof == 56); 17718 17719 struct XClientMessageEvent 17720 { 17721 int type; 17722 arch_ulong serial; /* # of last request processed by server */ 17723 Bool send_event; /* true if this came from a SendEvent request */ 17724 Display *display; /* Display the event was read from */ 17725 Window window; 17726 Atom message_type; 17727 int format; 17728 union Data{ 17729 byte[20] b; 17730 short[10] s; 17731 arch_ulong[5] l; 17732 } 17733 Data data; 17734 17735 } 17736 version(X86_64) static assert(XClientMessageEvent.sizeof == 96); 17737 17738 struct XMappingEvent 17739 { 17740 int type; 17741 arch_ulong serial; /* # of last request processed by server */ 17742 Bool send_event; /* true if this came from a SendEvent request */ 17743 Display *display; /* Display the event was read from */ 17744 Window window; /* unused */ 17745 MappingType request; /* one of MappingModifier, MappingKeyboard, 17746 MappingPointer */ 17747 int first_keycode; /* first keycode */ 17748 int count; /* defines range of change w. first_keycode*/ 17749 } 17750 17751 struct XErrorEvent 17752 { 17753 int type; 17754 Display *display; /* Display the event was read from */ 17755 XID resourceid; /* resource id */ 17756 arch_ulong serial; /* serial number of failed request */ 17757 ubyte error_code; /* error code of failed request */ 17758 ubyte request_code; /* Major op-code of failed request */ 17759 ubyte minor_code; /* Minor op-code of failed request */ 17760 } 17761 17762 struct XAnyEvent 17763 { 17764 int type; 17765 arch_ulong serial; /* # of last request processed by server */ 17766 Bool send_event; /* true if this came from a SendEvent request */ 17767 Display *display;/* Display the event was read from */ 17768 Window window; /* window on which event was requested in event mask */ 17769 } 17770 17771 union XEvent{ 17772 int type; /* must not be changed; first element */ 17773 XAnyEvent xany; 17774 XKeyEvent xkey; 17775 XButtonEvent xbutton; 17776 XMotionEvent xmotion; 17777 XCrossingEvent xcrossing; 17778 XFocusChangeEvent xfocus; 17779 XExposeEvent xexpose; 17780 XGraphicsExposeEvent xgraphicsexpose; 17781 XNoExposeEvent xnoexpose; 17782 XVisibilityEvent xvisibility; 17783 XCreateWindowEvent xcreatewindow; 17784 XDestroyWindowEvent xdestroywindow; 17785 XUnmapEvent xunmap; 17786 XMapEvent xmap; 17787 XMapRequestEvent xmaprequest; 17788 XReparentEvent xreparent; 17789 XConfigureEvent xconfigure; 17790 XGravityEvent xgravity; 17791 XResizeRequestEvent xresizerequest; 17792 XConfigureRequestEvent xconfigurerequest; 17793 XCirculateEvent xcirculate; 17794 XCirculateRequestEvent xcirculaterequest; 17795 XPropertyEvent xproperty; 17796 XSelectionClearEvent xselectionclear; 17797 XSelectionRequestEvent xselectionrequest; 17798 XSelectionEvent xselection; 17799 XColormapEvent xcolormap; 17800 XClientMessageEvent xclient; 17801 XMappingEvent xmapping; 17802 XErrorEvent xerror; 17803 XKeymapEvent xkeymap; 17804 arch_ulong[24] pad; 17805 } 17806 17807 17808 struct Display { 17809 XExtData *ext_data; /* hook for extension to hang data */ 17810 _XPrivate *private1; 17811 int fd; /* Network socket. */ 17812 int private2; 17813 int proto_major_version;/* major version of server's X protocol */ 17814 int proto_minor_version;/* minor version of servers X protocol */ 17815 char *vendor; /* vendor of the server hardware */ 17816 XID private3; 17817 XID private4; 17818 XID private5; 17819 int private6; 17820 XID function(Display*)resource_alloc;/* allocator function */ 17821 ByteOrder byte_order; /* screen byte order, LSBFirst, MSBFirst */ 17822 int bitmap_unit; /* padding and data requirements */ 17823 int bitmap_pad; /* padding requirements on bitmaps */ 17824 ByteOrder bitmap_bit_order; /* LeastSignificant or MostSignificant */ 17825 int nformats; /* number of pixmap formats in list */ 17826 ScreenFormat *pixmap_format; /* pixmap format list */ 17827 int private8; 17828 int release; /* release of the server */ 17829 _XPrivate *private9; 17830 _XPrivate *private10; 17831 int qlen; /* Length of input event queue */ 17832 arch_ulong last_request_read; /* seq number of last event read */ 17833 arch_ulong request; /* sequence number of last request. */ 17834 XPointer private11; 17835 XPointer private12; 17836 XPointer private13; 17837 XPointer private14; 17838 uint max_request_size; /* maximum number 32 bit words in request*/ 17839 _XrmHashBucketRec *db; 17840 int function (Display*)private15; 17841 char *display_name; /* "host:display" string used on this connect*/ 17842 int default_screen; /* default screen for operations */ 17843 int nscreens; /* number of screens on this server*/ 17844 Screen *screens; /* pointer to list of screens */ 17845 arch_ulong motion_buffer; /* size of motion buffer */ 17846 arch_ulong private16; 17847 int min_keycode; /* minimum defined keycode */ 17848 int max_keycode; /* maximum defined keycode */ 17849 XPointer private17; 17850 XPointer private18; 17851 int private19; 17852 byte *xdefaults; /* contents of defaults from server */ 17853 /* there is more to this structure, but it is private to Xlib */ 17854 } 17855 17856 // I got these numbers from a C program as a sanity test 17857 version(X86_64) { 17858 static assert(Display.sizeof == 296); 17859 static assert(XPointer.sizeof == 8); 17860 static assert(XErrorEvent.sizeof == 40); 17861 static assert(XAnyEvent.sizeof == 40); 17862 static assert(XMappingEvent.sizeof == 56); 17863 static assert(XEvent.sizeof == 192); 17864 } else version (AArch64) { 17865 // omit check for aarch64 17866 } else { 17867 static assert(Display.sizeof == 176); 17868 static assert(XPointer.sizeof == 4); 17869 static assert(XEvent.sizeof == 96); 17870 } 17871 17872 struct Depth 17873 { 17874 int depth; /* this depth (Z) of the depth */ 17875 int nvisuals; /* number of Visual types at this depth */ 17876 Visual *visuals; /* list of visuals possible at this depth */ 17877 } 17878 17879 alias void* GC; 17880 alias c_ulong VisualID; 17881 alias XID Colormap; 17882 alias XID Cursor; 17883 alias XID KeySym; 17884 alias uint KeyCode; 17885 enum None = 0; 17886 } 17887 17888 version(without_opengl) {} 17889 else { 17890 extern(C) nothrow @nogc { 17891 17892 17893 static if(!SdpyIsUsingIVGLBinds) { 17894 enum GLX_USE_GL= 1; /* support GLX rendering */ 17895 enum GLX_BUFFER_SIZE= 2; /* depth of the color buffer */ 17896 enum GLX_LEVEL= 3; /* level in plane stacking */ 17897 enum GLX_RGBA= 4; /* true if RGBA mode */ 17898 enum GLX_DOUBLEBUFFER= 5; /* double buffering supported */ 17899 enum GLX_STEREO= 6; /* stereo buffering supported */ 17900 enum GLX_AUX_BUFFERS= 7; /* number of aux buffers */ 17901 enum GLX_RED_SIZE= 8; /* number of red component bits */ 17902 enum GLX_GREEN_SIZE= 9; /* number of green component bits */ 17903 enum GLX_BLUE_SIZE= 10; /* number of blue component bits */ 17904 enum GLX_ALPHA_SIZE= 11; /* number of alpha component bits */ 17905 enum GLX_DEPTH_SIZE= 12; /* number of depth bits */ 17906 enum GLX_STENCIL_SIZE= 13; /* number of stencil bits */ 17907 enum GLX_ACCUM_RED_SIZE= 14; /* number of red accum bits */ 17908 enum GLX_ACCUM_GREEN_SIZE= 15; /* number of green accum bits */ 17909 enum GLX_ACCUM_BLUE_SIZE= 16; /* number of blue accum bits */ 17910 enum GLX_ACCUM_ALPHA_SIZE= 17; /* number of alpha accum bits */ 17911 17912 17913 //XVisualInfo* glXChooseVisual(Display *dpy, int screen, in int *attrib_list); 17914 17915 17916 17917 enum GL_TRUE = 1; 17918 enum GL_FALSE = 0; 17919 } 17920 17921 alias XID GLXContextID; 17922 alias XID GLXPixmap; 17923 alias XID GLXDrawable; 17924 alias XID GLXPbuffer; 17925 alias XID GLXWindow; 17926 alias XID GLXFBConfigID; 17927 alias void* GLXContext; 17928 17929 } 17930 } 17931 17932 enum AllocNone = 0; 17933 17934 extern(C) { 17935 /* WARNING, this type not in Xlib spec */ 17936 extern(C) alias XIOErrorHandler = int function (Display* display); 17937 } 17938 17939 extern(C) nothrow 17940 alias XErrorHandler = int function(Display*, XErrorEvent*); 17941 17942 extern(C) nothrow @nogc { 17943 struct Screen{ 17944 XExtData *ext_data; /* hook for extension to hang data */ 17945 Display *display; /* back pointer to display structure */ 17946 Window root; /* Root window id. */ 17947 int width, height; /* width and height of screen */ 17948 int mwidth, mheight; /* width and height of in millimeters */ 17949 int ndepths; /* number of depths possible */ 17950 Depth *depths; /* list of allowable depths on the screen */ 17951 int root_depth; /* bits per pixel */ 17952 Visual *root_visual; /* root visual */ 17953 GC default_gc; /* GC for the root root visual */ 17954 Colormap cmap; /* default color map */ 17955 uint white_pixel; 17956 uint black_pixel; /* White and Black pixel values */ 17957 int max_maps, min_maps; /* max and min color maps */ 17958 int backing_store; /* Never, WhenMapped, Always */ 17959 bool save_unders; 17960 int root_input_mask; /* initial root input mask */ 17961 } 17962 17963 struct Visual 17964 { 17965 XExtData *ext_data; /* hook for extension to hang data */ 17966 VisualID visualid; /* visual id of this visual */ 17967 int class_; /* class of screen (monochrome, etc.) */ 17968 c_ulong red_mask, green_mask, blue_mask; /* mask values */ 17969 int bits_per_rgb; /* log base 2 of distinct color values */ 17970 int map_entries; /* color map entries */ 17971 } 17972 17973 alias Display* _XPrivDisplay; 17974 17975 extern(D) Screen* ScreenOfDisplay(Display* dpy, int scr) @system { 17976 assert(dpy !is null); 17977 return &dpy.screens[scr]; 17978 } 17979 17980 extern(D) Window RootWindow(Display *dpy,int scr) { 17981 return ScreenOfDisplay(dpy,scr).root; 17982 } 17983 17984 struct XWMHints { 17985 arch_long flags; 17986 Bool input; 17987 int initial_state; 17988 Pixmap icon_pixmap; 17989 Window icon_window; 17990 int icon_x, icon_y; 17991 Pixmap icon_mask; 17992 XID window_group; 17993 } 17994 17995 struct XClassHint { 17996 char* res_name; 17997 char* res_class; 17998 } 17999 18000 extern(D) int DefaultScreen(Display *dpy) { 18001 return dpy.default_screen; 18002 } 18003 18004 extern(D) int DefaultDepth(Display* dpy, int scr) { return ScreenOfDisplay(dpy, scr).root_depth; } 18005 extern(D) int DisplayWidth(Display* dpy, int scr) { return ScreenOfDisplay(dpy, scr).width; } 18006 extern(D) int DisplayHeight(Display* dpy, int scr) { return ScreenOfDisplay(dpy, scr).height; } 18007 extern(D) int DisplayWidthMM(Display* dpy, int scr) { return ScreenOfDisplay(dpy, scr).mwidth; } 18008 extern(D) int DisplayHeightMM(Display* dpy, int scr) { return ScreenOfDisplay(dpy, scr).mheight; } 18009 extern(D) auto DefaultColormap(Display* dpy, int scr) { return ScreenOfDisplay(dpy, scr).cmap; } 18010 18011 extern(D) int ConnectionNumber(Display* dpy) { return dpy.fd; } 18012 18013 enum int AnyPropertyType = 0; 18014 enum int Success = 0; 18015 18016 enum int RevertToNone = None; 18017 enum int PointerRoot = 1; 18018 enum Time CurrentTime = 0; 18019 enum int RevertToPointerRoot = PointerRoot; 18020 enum int RevertToParent = 2; 18021 18022 extern(D) int DefaultDepthOfDisplay(Display* dpy) { 18023 return ScreenOfDisplay(dpy, DefaultScreen(dpy)).root_depth; 18024 } 18025 18026 extern(D) Visual* DefaultVisual(Display *dpy,int scr) { 18027 return ScreenOfDisplay(dpy,scr).root_visual; 18028 } 18029 18030 extern(D) GC DefaultGC(Display *dpy,int scr) { 18031 return ScreenOfDisplay(dpy,scr).default_gc; 18032 } 18033 18034 extern(D) uint BlackPixel(Display *dpy,int scr) { 18035 return ScreenOfDisplay(dpy,scr).black_pixel; 18036 } 18037 18038 extern(D) uint WhitePixel(Display *dpy,int scr) { 18039 return ScreenOfDisplay(dpy,scr).white_pixel; 18040 } 18041 18042 alias void* XFontSet; // i think 18043 struct XmbTextItem { 18044 char* chars; 18045 int nchars; 18046 int delta; 18047 XFontSet font_set; 18048 } 18049 18050 struct XTextItem { 18051 char* chars; 18052 int nchars; 18053 int delta; 18054 Font font; 18055 } 18056 18057 enum { 18058 GXclear = 0x0, /* 0 */ 18059 GXand = 0x1, /* src AND dst */ 18060 GXandReverse = 0x2, /* src AND NOT dst */ 18061 GXcopy = 0x3, /* src */ 18062 GXandInverted = 0x4, /* NOT src AND dst */ 18063 GXnoop = 0x5, /* dst */ 18064 GXxor = 0x6, /* src XOR dst */ 18065 GXor = 0x7, /* src OR dst */ 18066 GXnor = 0x8, /* NOT src AND NOT dst */ 18067 GXequiv = 0x9, /* NOT src XOR dst */ 18068 GXinvert = 0xa, /* NOT dst */ 18069 GXorReverse = 0xb, /* src OR NOT dst */ 18070 GXcopyInverted = 0xc, /* NOT src */ 18071 GXorInverted = 0xd, /* NOT src OR dst */ 18072 GXnand = 0xe, /* NOT src OR NOT dst */ 18073 GXset = 0xf, /* 1 */ 18074 } 18075 enum QueueMode : int { 18076 QueuedAlready, 18077 QueuedAfterReading, 18078 QueuedAfterFlush 18079 } 18080 18081 enum GrabMode { GrabModeSync = 0, GrabModeAsync = 1 } 18082 18083 struct XPoint { 18084 short x; 18085 short y; 18086 } 18087 18088 enum CoordMode:int { 18089 CoordModeOrigin = 0, 18090 CoordModePrevious = 1 18091 } 18092 18093 enum PolygonShape:int { 18094 Complex = 0, 18095 Nonconvex = 1, 18096 Convex = 2 18097 } 18098 18099 struct XTextProperty { 18100 const(char)* value; /* same as Property routines */ 18101 Atom encoding; /* prop type */ 18102 int format; /* prop data format: 8, 16, or 32 */ 18103 arch_ulong nitems; /* number of data items in value */ 18104 } 18105 18106 version( X86_64 ) { 18107 static assert(XTextProperty.sizeof == 32); 18108 } 18109 18110 18111 struct XGCValues { 18112 int function_; /* logical operation */ 18113 arch_ulong plane_mask;/* plane mask */ 18114 arch_ulong foreground;/* foreground pixel */ 18115 arch_ulong background;/* background pixel */ 18116 int line_width; /* line width */ 18117 int line_style; /* LineSolid, LineOnOffDash, LineDoubleDash */ 18118 int cap_style; /* CapNotLast, CapButt, 18119 CapRound, CapProjecting */ 18120 int join_style; /* JoinMiter, JoinRound, JoinBevel */ 18121 int fill_style; /* FillSolid, FillTiled, 18122 FillStippled, FillOpaeueStippled */ 18123 int fill_rule; /* EvenOddRule, WindingRule */ 18124 int arc_mode; /* ArcChord, ArcPieSlice */ 18125 Pixmap tile; /* tile pixmap for tiling operations */ 18126 Pixmap stipple; /* stipple 1 plane pixmap for stipping */ 18127 int ts_x_origin; /* offset for tile or stipple operations */ 18128 int ts_y_origin; 18129 Font font; /* default text font for text operations */ 18130 int subwindow_mode; /* ClipByChildren, IncludeInferiors */ 18131 Bool graphics_exposures;/* boolean, should exposures be generated */ 18132 int clip_x_origin; /* origin for clipping */ 18133 int clip_y_origin; 18134 Pixmap clip_mask; /* bitmap clipping; other calls for rects */ 18135 int dash_offset; /* patterned/dashed line information */ 18136 char dashes; 18137 } 18138 18139 struct XColor { 18140 arch_ulong pixel; 18141 ushort red, green, blue; 18142 byte flags; 18143 byte pad; 18144 } 18145 18146 struct XRectangle { 18147 short x; 18148 short y; 18149 ushort width; 18150 ushort height; 18151 } 18152 18153 enum ClipByChildren = 0; 18154 enum IncludeInferiors = 1; 18155 18156 enum Atom XA_PRIMARY = 1; 18157 enum Atom XA_SECONDARY = 2; 18158 enum Atom XA_STRING = 31; 18159 enum Atom XA_CARDINAL = 6; 18160 enum Atom XA_WM_NAME = 39; 18161 enum Atom XA_ATOM = 4; 18162 enum Atom XA_WINDOW = 33; 18163 enum Atom XA_WM_HINTS = 35; 18164 enum int PropModeAppend = 2; 18165 enum int PropModeReplace = 0; 18166 enum int PropModePrepend = 1; 18167 18168 enum int CopyFromParent = 0; 18169 enum int InputOutput = 1; 18170 18171 // XWMHints 18172 enum InputHint = 1 << 0; 18173 enum StateHint = 1 << 1; 18174 enum IconPixmapHint = (1L << 2); 18175 enum IconWindowHint = (1L << 3); 18176 enum IconPositionHint = (1L << 4); 18177 enum IconMaskHint = (1L << 5); 18178 enum WindowGroupHint = (1L << 6); 18179 enum AllHints = (InputHint|StateHint|IconPixmapHint|IconWindowHint|IconPositionHint|IconMaskHint|WindowGroupHint); 18180 enum XUrgencyHint = (1L << 8); 18181 18182 // GC Components 18183 enum GCFunction = (1L<<0); 18184 enum GCPlaneMask = (1L<<1); 18185 enum GCForeground = (1L<<2); 18186 enum GCBackground = (1L<<3); 18187 enum GCLineWidth = (1L<<4); 18188 enum GCLineStyle = (1L<<5); 18189 enum GCCapStyle = (1L<<6); 18190 enum GCJoinStyle = (1L<<7); 18191 enum GCFillStyle = (1L<<8); 18192 enum GCFillRule = (1L<<9); 18193 enum GCTile = (1L<<10); 18194 enum GCStipple = (1L<<11); 18195 enum GCTileStipXOrigin = (1L<<12); 18196 enum GCTileStipYOrigin = (1L<<13); 18197 enum GCFont = (1L<<14); 18198 enum GCSubwindowMode = (1L<<15); 18199 enum GCGraphicsExposures= (1L<<16); 18200 enum GCClipXOrigin = (1L<<17); 18201 enum GCClipYOrigin = (1L<<18); 18202 enum GCClipMask = (1L<<19); 18203 enum GCDashOffset = (1L<<20); 18204 enum GCDashList = (1L<<21); 18205 enum GCArcMode = (1L<<22); 18206 enum GCLastBit = 22; 18207 18208 18209 enum int WithdrawnState = 0; 18210 enum int NormalState = 1; 18211 enum int IconicState = 3; 18212 18213 } 18214 } else version (OSXCocoa) { 18215 18216 /+ 18217 DON'T FORGET TO MARK THE CLASSES `extern`!! can cause "unrecognized selector sent to class" errors if you do. 18218 +/ 18219 18220 private __gshared AppDelegate globalAppDelegate; 18221 18222 extern(Objective-C) 18223 class AppDelegate : NSObject, NSApplicationDelegate { 18224 override static AppDelegate alloc() @selector("alloc"); 18225 18226 18227 void sdpyCustomEventWakeup(NSid arg) @selector("sdpyCustomEventWakeup:") { 18228 SimpleWindow.processAllCustomEvents(); 18229 } 18230 18231 override void applicationWillFinishLaunching(NSNotification notification) @selector("applicationWillFinishLaunching:") { 18232 immutable style = NSWindowStyleMask.resizable | 18233 NSWindowStyleMask.closable | 18234 NSWindowStyleMask.miniaturizable | 18235 NSWindowStyleMask.titled; 18236 18237 NSMenu mainMenu = NSMenu.alloc.init(MacString("Main").borrow); 18238 18239 { 18240 auto item = mainMenu.addItem(MacString("Test").borrow, null, MacString("").borrow); 18241 auto menu = NSMenu.alloc.init(MacString("Test2").borrow); 18242 mainMenu.setSubmenu(menu, item); 18243 18244 auto newItem = menu.addItem(MacString("Quit").borrow, sel_registerName("terminate:"), MacString("q").borrow); 18245 newItem.target = NSApp; 18246 auto newItem2 = menu.addItem(MacString("Disabled").borrow, sel_registerName("doesnotexist:"), MacString("x").borrow); 18247 newItem2.target = NSApp; 18248 } 18249 18250 { 18251 auto item = mainMenu.addItem(MacString("Test3").borrow, null, MacString("").borrow); 18252 auto menu = NSMenu.alloc.init(MacString("Test4").borrow); // this is the title actually used 18253 mainMenu.setSubmenu(menu, item); 18254 18255 auto newItem = menu.addItem(MacString("Quit2").borrow, sel_registerName("stop:"), MacString("s").borrow); 18256 menu.addItem(MacString("Pulse").borrow, sel_registerName("simpledisplay_pulse:"), MacString("p").borrow); 18257 } 18258 18259 18260 NSApp.menu = mainMenu; 18261 18262 18263 // auto controller = ViewController.alloc.init; 18264 18265 // auto timer = NSTimer.schedule(1.0, cast(NSid) view, sel_registerName("simpledisplay_pulse:"), null, true); 18266 18267 /+ 18268 this.window = window; 18269 this.controller = controller; 18270 +/ 18271 } 18272 18273 override void applicationDidFinishLaunching(NSNotification notification) @selector("applicationDidFinishLaunching:") { 18274 NSApplication.shared_.activateIgnoringOtherApps(true); 18275 } 18276 override bool applicationShouldTerminateAfterLastWindowClosed(NSNotification notification) @selector("applicationShouldTerminateAfterLastWindowClosed:") { 18277 return true; 18278 } 18279 } 18280 18281 extern(Objective-C) 18282 class SDWindowDelegate : NSObject, NSWindowDelegate { 18283 override static SDWindowDelegate alloc() @selector("alloc"); 18284 override SDWindowDelegate init() @selector("init"); 18285 18286 SimpleWindow simpleWindow; 18287 18288 override void windowWillClose(NSNotification notification) @selector("windowWillClose:") { 18289 auto window = cast(void*) notification.object; 18290 18291 // FIXME: do i need to release it? 18292 SimpleWindow.nativeMapping.remove(window); 18293 } 18294 18295 override NSSize windowWillResize(NSWindow sender, NSSize frameSize) @selector("windowWillResize:toSize:") { 18296 if(simpleWindow.windowResized) { 18297 // FIXME: automaticallyScaleIfPossible behaviors 18298 18299 simpleWindow._width = cast(int) frameSize.width; 18300 simpleWindow._height = cast(int) frameSize.height; 18301 18302 simpleWindow.view.setFrameSize(frameSize); 18303 18304 /+ 18305 auto size = simpleWindow.view.frame.size; 18306 writeln(cast(int) size.width, "x", cast(int) size.height); 18307 +/ 18308 18309 simpleWindow.createNewDrawingContext(simpleWindow._width, simpleWindow._height); 18310 18311 simpleWindow.windowResized(simpleWindow._width, simpleWindow._height); 18312 18313 // simpleWindow.view.setNeedsDisplay(true); 18314 } 18315 18316 return frameSize; 18317 } 18318 18319 /+ 18320 override void windowDidResize(NSNotification notification) @selector("windowDidResize:") { 18321 if(simpleWindow.windowResized) { 18322 auto window = simpleWindow.window; 18323 auto rect = window.contentRectForFrameRect(window.frame); 18324 import std.stdio; writeln(window.frame.size); 18325 simpleWindow.windowResized(cast(int) rect.size.width, cast(int) rect.size.height); 18326 } 18327 } 18328 +/ 18329 } 18330 18331 extern(Objective-C) 18332 class SDGraphicsView : NSView { 18333 SimpleWindow simpleWindow; 18334 18335 override static SDGraphicsView alloc() @selector("alloc"); 18336 override SDGraphicsView init() @selector("init") { 18337 super.init(); 18338 return this; 18339 } 18340 18341 override void drawRect(NSRect rect) @selector("drawRect:") { 18342 auto curCtx = NSGraphicsContext.currentContext.graphicsPort; 18343 auto cgImage = CGBitmapContextCreateImage(simpleWindow.drawingContext); 18344 auto size = CGSize(CGBitmapContextGetWidth(simpleWindow.drawingContext), CGBitmapContextGetHeight(simpleWindow.drawingContext)); 18345 CGContextDrawImage(curCtx, CGRect(CGPoint(0, 0), size), cgImage); 18346 CGImageRelease(cgImage); 18347 } 18348 18349 private void mouseHelper(NSEvent event, MouseEventType type, MouseButton button) { 18350 MouseEvent me; 18351 me.type = type; 18352 18353 auto pos = event.locationInWindow; 18354 18355 me.x = cast(int) pos.x; 18356 me.y = cast(int) (simpleWindow.height - pos.y); 18357 18358 me.dx = 0; // FIXME 18359 me.dy = 0; // FIXME 18360 18361 me.button = button; 18362 me.modifierState = cast(uint) event.modifierFlags; 18363 me.window = simpleWindow; 18364 18365 me.doubleClick = false; 18366 18367 if(simpleWindow && simpleWindow.handleMouseEvent) 18368 simpleWindow.handleMouseEvent(me); 18369 } 18370 18371 override void mouseDown(NSEvent event) @selector("mouseDown:") { 18372 // writeln(event.pressedMouseButtons); 18373 18374 mouseHelper(event, MouseEventType.buttonPressed, MouseButton.left); 18375 } 18376 override void mouseDragged(NSEvent event) @selector("mouseDragged:") { 18377 mouseHelper(event, MouseEventType.motion, MouseButton.left); 18378 } 18379 override void mouseUp(NSEvent event) @selector("mouseUp:") { 18380 mouseHelper(event, MouseEventType.buttonReleased, MouseButton.left); 18381 } 18382 override void mouseMoved(NSEvent event) @selector("mouseMoved:") { 18383 mouseHelper(event, MouseEventType.motion, MouseButton.left); // button wrong prolly 18384 } 18385 /+ 18386 // FIXME 18387 override void mouseEntered(NSEvent event) @selector("mouseEntered:") { 18388 mouseHelper(event, MouseEventType.buttonPressed, MouseButton.left); 18389 } 18390 override void mouseExited(NSEvent event) @selector("mouseExited:") { 18391 mouseHelper(event, MouseEventType.buttonPressed, MouseButton.left); 18392 } 18393 +/ 18394 18395 override void rightMouseDown(NSEvent event) @selector("rightMouseDown:") { 18396 mouseHelper(event, MouseEventType.buttonPressed, MouseButton.right); 18397 } 18398 override void rightMouseDragged(NSEvent event) @selector("rightMouseDragged:") { 18399 mouseHelper(event, MouseEventType.motion, MouseButton.right); 18400 } 18401 override void rightMouseUp(NSEvent event) @selector("rightMouseUp:") { 18402 mouseHelper(event, MouseEventType.buttonReleased, MouseButton.right); 18403 } 18404 18405 override void otherMouseDown(NSEvent event) @selector("otherMouseDown:") { 18406 mouseHelper(event, MouseEventType.buttonPressed, MouseButton.middle); 18407 } 18408 override void otherMouseDragged(NSEvent event) @selector("otherMouseDragged:") { 18409 mouseHelper(event, MouseEventType.motion, MouseButton.middle); 18410 } 18411 override void otherMouseUp(NSEvent event) @selector("otherMouseUp:") { 18412 mouseHelper(event, MouseEventType.buttonReleased, MouseButton.middle); 18413 } 18414 18415 override void scrollWheel(NSEvent event) @selector("scrollWheel:") { 18416 import std.stdio; 18417 writeln(event.deltaY); 18418 } 18419 18420 override void keyDown(NSEvent event) @selector("keyDown:") { 18421 // the event may have multiple characters, and we send them all at once. 18422 if (simpleWindow.handleCharEvent) { 18423 auto chars = DeifiedNSString(event.characters); 18424 foreach (dchar dc; chars.str) 18425 simpleWindow.handleCharEvent(dc); 18426 } 18427 18428 keyHelper(event, true); 18429 } 18430 18431 override void keyUp(NSEvent event) @selector("keyUp:") { 18432 keyHelper(event, false); 18433 } 18434 18435 private void keyHelper(NSEvent event, bool pressed) { 18436 if(simpleWindow.handleKeyEvent) { 18437 KeyEvent ev; 18438 ev.key = cast(Key) event.keyCode;// (event.specialKey ? event.specialKey : event.keyCode); 18439 ev.pressed = pressed; 18440 ev.hardwareCode = cast(ubyte) event.keyCode; 18441 ev.modifierState = cast(uint) event.modifierFlags; 18442 ev.window = simpleWindow; 18443 18444 simpleWindow.handleKeyEvent(ev); 18445 } 18446 } 18447 18448 override bool isFlipped() @selector("isFlipped") { 18449 return true; 18450 } 18451 override bool acceptsFirstResponder() @selector("acceptsFirstResponder") { 18452 return true; 18453 } 18454 18455 void simpledisplay_pulse(NSTimer timer) @selector("simpledisplay_pulse:") { 18456 if(simpleWindow && simpleWindow.handlePulse) 18457 simpleWindow.handlePulse(); 18458 /+ 18459 setNeedsDisplay = true; 18460 +/ 18461 } 18462 } 18463 18464 private: 18465 alias const(void)* CFStringRef; 18466 alias const(void)* CFAllocatorRef; 18467 alias const(void)* CFTypeRef; 18468 alias const(void)* CGColorSpaceRef; 18469 alias const(void)* CGImageRef; 18470 alias ulong CGBitmapInfo; 18471 alias NSGraphicsContext CGContextRef; 18472 18473 alias NSPoint CGPoint; 18474 alias NSSize CGSize; 18475 alias NSRect CGRect; 18476 18477 struct CGAffineTransform { 18478 double a, b, c, d, tx, ty; 18479 } 18480 18481 enum NSApplicationActivationPolicyRegular = 0; 18482 enum NSBackingStoreBuffered = 2; 18483 enum kCFStringEncodingUTF8 = 0x08000100; 18484 18485 enum : size_t { 18486 NSBorderlessWindowMask = 0, 18487 NSTitledWindowMask = 1 << 0, 18488 NSClosableWindowMask = 1 << 1, 18489 NSMiniaturizableWindowMask = 1 << 2, 18490 NSResizableWindowMask = 1 << 3, 18491 NSTexturedBackgroundWindowMask = 1 << 8 18492 } 18493 18494 enum : ulong { 18495 kCGImageAlphaNone, 18496 kCGImageAlphaPremultipliedLast, 18497 kCGImageAlphaPremultipliedFirst, 18498 kCGImageAlphaLast, 18499 kCGImageAlphaFirst, 18500 kCGImageAlphaNoneSkipLast, 18501 kCGImageAlphaNoneSkipFirst 18502 } 18503 enum : ulong { 18504 kCGBitmapAlphaInfoMask = 0x1F, 18505 kCGBitmapFloatComponents = (1 << 8), 18506 kCGBitmapByteOrderMask = 0x7000, 18507 kCGBitmapByteOrderDefault = (0 << 12), 18508 kCGBitmapByteOrder16Little = (1 << 12), 18509 kCGBitmapByteOrder32Little = (2 << 12), 18510 kCGBitmapByteOrder16Big = (3 << 12), 18511 kCGBitmapByteOrder32Big = (4 << 12) 18512 } 18513 enum CGPathDrawingMode { 18514 kCGPathFill, 18515 kCGPathEOFill, 18516 kCGPathStroke, 18517 kCGPathFillStroke, 18518 kCGPathEOFillStroke 18519 } 18520 enum objc_AssociationPolicy : size_t { 18521 OBJC_ASSOCIATION_ASSIGN = 0, 18522 OBJC_ASSOCIATION_RETAIN_NONATOMIC = 1, 18523 OBJC_ASSOCIATION_COPY_NONATOMIC = 3, 18524 OBJC_ASSOCIATION_RETAIN = 0x301, //01401, 18525 OBJC_ASSOCIATION_COPY = 0x303 //01403 18526 } 18527 18528 extern(C) { 18529 CGContextRef CGBitmapContextCreate(void* data, size_t width, size_t height, size_t bitsPerComponent, size_t bytesPerRow, CGColorSpaceRef colorspace, CGBitmapInfo bitmapInfo); 18530 void CGContextRelease(CGContextRef c); 18531 ubyte* CGBitmapContextGetData(CGContextRef c); 18532 CGImageRef CGBitmapContextCreateImage(CGContextRef c); 18533 size_t CGBitmapContextGetWidth(CGContextRef c); 18534 size_t CGBitmapContextGetHeight(CGContextRef c); 18535 18536 CGColorSpaceRef CGColorSpaceCreateDeviceRGB(); 18537 void CGColorSpaceRelease(CGColorSpaceRef cs); 18538 18539 void CGContextSetRGBStrokeColor(CGContextRef c, double red, double green, double blue, double alpha); 18540 void CGContextSetRGBFillColor(CGContextRef c, double red, double green, double blue, double alpha); 18541 void CGContextDrawImage(CGContextRef c, CGRect rect, CGImageRef image); 18542 void CGContextShowTextAtPoint(CGContextRef c, double x, double y, const(char)* str, size_t length); 18543 void CGContextStrokeLineSegments(CGContextRef c, const(CGPoint)* points, size_t count); 18544 void CGContextSetLineDash(CGContextRef c, CGFloat phase, const CGFloat *lengths, size_t count); 18545 18546 void CGContextBeginPath(CGContextRef c); 18547 void CGContextDrawPath(CGContextRef c, CGPathDrawingMode mode); 18548 void CGContextAddEllipseInRect(CGContextRef c, CGRect rect); 18549 void CGContextAddArc(CGContextRef c, double x, double y, double radius, double startAngle, double endAngle, long clockwise); 18550 void CGContextAddRect(CGContextRef c, CGRect rect); 18551 void CGContextAddLines(CGContextRef c, const(CGPoint)* points, size_t count); 18552 void CGContextSaveGState(CGContextRef c); 18553 void CGContextRestoreGState(CGContextRef c); 18554 void CGContextSelectFont(CGContextRef c, const(char)* name, double size, ulong textEncoding); 18555 CGAffineTransform CGContextGetTextMatrix(CGContextRef c); 18556 void CGContextSetTextMatrix(CGContextRef c, CGAffineTransform t); 18557 18558 void CGImageRelease(CGImageRef image); 18559 } 18560 } else static assert(0, "Unsupported operating system"); 18561 18562 18563 version(OSXCocoa) { 18564 // I don't know anything about the Mac, but a couple years ago, KennyTM on the newsgroup wrote this for me 18565 // 18566 // http://forum.dlang.org/thread/innr0v$1deh$1@digitalmars.com?page=4#post-int88l:24uaf:241:40digitalmars.com 18567 // https://github.com/kennytm/simpledisplay.d/blob/osx/simpledisplay.d 18568 // 18569 // and it is about time I merged it in here. It is available with -version=OSXCocoa until someone tests it for me! 18570 // Probably won't even fully compile right now 18571 18572 private enum double PI = 3.14159265358979323; 18573 18574 alias NSWindow NativeWindowHandle; 18575 alias void delegate(NSid) NativeEventHandler; 18576 18577 enum KEY_ESCAPE = 27; 18578 18579 mixin template NativeImageImplementation() { 18580 CGContextRef context; 18581 ubyte* rawData; 18582 18583 final: 18584 18585 void convertToRgbaBytes(ubyte[] where) { 18586 assert(where.length == this.width * this.height * 4); 18587 18588 // if rawData had a length.... 18589 //assert(rawData.length == where.length); 18590 for(long idx = 0; idx < where.length; idx += 4) { 18591 auto alpha = rawData[idx + 3]; 18592 if(alpha == 255) { 18593 where[idx + 0] = rawData[idx + 0]; // r 18594 where[idx + 1] = rawData[idx + 1]; // g 18595 where[idx + 2] = rawData[idx + 2]; // b 18596 where[idx + 3] = rawData[idx + 3]; // a 18597 } else { 18598 where[idx + 0] = cast(ubyte)(rawData[idx + 0] * 255 / alpha); // r 18599 where[idx + 1] = cast(ubyte)(rawData[idx + 1] * 255 / alpha); // g 18600 where[idx + 2] = cast(ubyte)(rawData[idx + 2] * 255 / alpha); // b 18601 where[idx + 3] = rawData[idx + 3]; // a 18602 18603 } 18604 } 18605 } 18606 18607 void setFromRgbaBytes(in ubyte[] where) { 18608 // FIXME: this is probably wrong 18609 assert(where.length == this.width * this.height * 4); 18610 18611 // if rawData had a length.... 18612 //assert(rawData.length == where.length); 18613 for(long idx = 0; idx < where.length; idx += 4) { 18614 auto alpha = where[idx + 3]; 18615 if(alpha == 255) { 18616 rawData[idx + 0] = where[idx + 0]; // r 18617 rawData[idx + 1] = where[idx + 1]; // g 18618 rawData[idx + 2] = where[idx + 2]; // b 18619 rawData[idx + 3] = where[idx + 3]; // a 18620 } else if(alpha == 0) { 18621 rawData[idx + 0] = 0; 18622 rawData[idx + 1] = 0; 18623 rawData[idx + 2] = 0; 18624 rawData[idx + 3] = 0; 18625 } else { 18626 rawData[idx + 0] = cast(ubyte)(where[idx + 0] * 255 / alpha); // r 18627 rawData[idx + 1] = cast(ubyte)(where[idx + 1] * 255 / alpha); // g 18628 rawData[idx + 2] = cast(ubyte)(where[idx + 2] * 255 / alpha); // b 18629 rawData[idx + 3] = where[idx + 3]; // a 18630 } 18631 } 18632 } 18633 18634 18635 void createImage(int width, int height, bool forcexshm=false, bool ignored = false) { 18636 auto colorSpace = CGColorSpaceCreateDeviceRGB(); 18637 context = CGBitmapContextCreate(null, width, height, 8, 4*width, colorSpace, kCGImageAlphaPremultipliedLast |kCGBitmapByteOrder32Big); 18638 CGColorSpaceRelease(colorSpace); 18639 rawData = CGBitmapContextGetData(context); 18640 } 18641 void dispose() { 18642 CGContextRelease(context); 18643 } 18644 18645 void setPixel(int x, int y, Color c) { 18646 auto offset = (y * width + x) * 4; 18647 if (c.a == 255) { 18648 rawData[offset + 0] = c.r; 18649 rawData[offset + 1] = c.g; 18650 rawData[offset + 2] = c.b; 18651 rawData[offset + 3] = c.a; 18652 } else { 18653 rawData[offset + 0] = cast(ubyte)(c.r*c.a/255); 18654 rawData[offset + 1] = cast(ubyte)(c.g*c.a/255); 18655 rawData[offset + 2] = cast(ubyte)(c.b*c.a/255); 18656 rawData[offset + 3] = c.a; 18657 } 18658 } 18659 } 18660 18661 mixin template NativeScreenPainterImplementation() { 18662 CGContextRef context; 18663 ubyte[4] _outlineComponents; 18664 NSView view; 18665 18666 Pen _activePen; 18667 Color _fillColor; 18668 Rectangle _clipRectangle; 18669 OperatingSystemFont _font; 18670 18671 OperatingSystemFont getFont() { 18672 if(_font is null) { 18673 static OperatingSystemFont _defaultFont; 18674 if(_defaultFont is null) { 18675 _defaultFont = new OperatingSystemFont(); 18676 _defaultFont.loadDefault(); 18677 } 18678 _font = _defaultFont; 18679 } 18680 18681 return _font; 18682 } 18683 18684 void create(PaintingHandle window) { 18685 // this.destiny = window; 18686 if(auto sw = cast(SimpleWindow) this.window) { 18687 context = sw.drawingContext; 18688 view = sw.view; 18689 } else { 18690 throw new NotYetImplementedException(); 18691 } 18692 } 18693 18694 void dispose() { 18695 view.setNeedsDisplay(true); 18696 } 18697 18698 bool manualInvalidations; 18699 void invalidateRect(Rectangle invalidRect) { } 18700 18701 // NotYetImplementedException 18702 void rasterOp(RasterOp op) { 18703 } 18704 void setClipRectangle(int, int, int, int) { 18705 } 18706 Size textSize(in char[] txt) { 18707 auto font = getFont(); 18708 return Size(font.stringWidth(txt), font.height()); 18709 } 18710 18711 void setFont(OperatingSystemFont font) { 18712 _font = font; 18713 //font.font.setInContext(context); 18714 } 18715 int fontHeight() { 18716 auto font = getFont(); 18717 return font.height; 18718 } 18719 18720 // end 18721 18722 void pen(Pen pen) { 18723 _activePen = pen; 18724 auto color = pen.color; // FIXME 18725 double alphaComponent = color.a/255.0f; 18726 CGContextSetRGBStrokeColor(context, color.r/255.0f, color.g/255.0f, color.b/255.0f, alphaComponent); 18727 18728 double[2] patternBuffer; 18729 double[] pattern; 18730 final switch(pen.style) { 18731 case Pen.Style.Solid: 18732 pattern = null; 18733 break; 18734 case Pen.Style.Dashed: 18735 patternBuffer[0] = 4; 18736 patternBuffer[1] = 1; 18737 pattern = patternBuffer[]; 18738 break; 18739 case Pen.Style.Dotted: 18740 patternBuffer[0] = 1; 18741 patternBuffer[1] = 1; 18742 pattern = patternBuffer[]; 18743 break; 18744 } 18745 18746 CGContextSetLineDash(context, 0, pattern.ptr, pattern.length); 18747 18748 if (color.a != 255) { 18749 _outlineComponents[0] = cast(ubyte)(color.r*color.a/255); 18750 _outlineComponents[1] = cast(ubyte)(color.g*color.a/255); 18751 _outlineComponents[2] = cast(ubyte)(color.b*color.a/255); 18752 _outlineComponents[3] = color.a; 18753 } else { 18754 _outlineComponents[0] = color.r; 18755 _outlineComponents[1] = color.g; 18756 _outlineComponents[2] = color.b; 18757 _outlineComponents[3] = color.a; 18758 } 18759 } 18760 18761 @property void fillColor(Color color) { 18762 CGContextSetRGBFillColor(context, color.r/255.0f, color.g/255.0f, color.b/255.0f, color.a/255.0f); 18763 } 18764 18765 void drawImage(int x, int y, Image image, int ulx, int upy, int width, int height) { 18766 // NotYetImplementedException for upper left/width/height 18767 auto cgImage = CGBitmapContextCreateImage(image.context); 18768 auto size = CGSize(CGBitmapContextGetWidth(image.context), CGBitmapContextGetHeight(image.context)); 18769 CGContextDrawImage(context, CGRect(CGPoint(x, y), size), cgImage); 18770 CGImageRelease(cgImage); 18771 } 18772 18773 void drawPixmap(Sprite s, int x, int y, int ix, int iy, int w, int h) { 18774 // FIXME: is this efficient? 18775 auto cgImage = CGBitmapContextCreateImage(s.handle); 18776 auto size = CGSize(CGBitmapContextGetWidth(s.handle), CGBitmapContextGetHeight(s.handle)); 18777 CGContextDrawImage(context, CGRect(CGPoint(x, y), size), cgImage); 18778 CGImageRelease(cgImage); 18779 } 18780 18781 18782 void drawText(int x, int y, int x2, int y2, in char[] text, uint alignment) { 18783 // FIXME: alignment 18784 if (_outlineComponents[3] != 0) { 18785 CGContextSaveGState(context); 18786 auto invAlpha = 1.0f/_outlineComponents[3]; 18787 CGContextSetRGBFillColor(context, _outlineComponents[0]*invAlpha, 18788 _outlineComponents[1]*invAlpha, 18789 _outlineComponents[2]*invAlpha, 18790 _outlineComponents[3]/255.0f); 18791 18792 18793 18794 // FIXME: should we clip it to the bounding box? 18795 int textHeight = fontHeight; 18796 18797 auto lines = text.split('\n'); 18798 18799 const lineHeight = textHeight; 18800 textHeight *= lines.length; 18801 18802 int cy = y; 18803 18804 if(alignment & TextAlignment.VerticalBottom) { 18805 if(y2 <= 0) 18806 return; 18807 auto h = y2 - y; 18808 if(h > textHeight) { 18809 cy += h - textHeight; 18810 cy -= lineHeight / 2; 18811 } 18812 } else if(alignment & TextAlignment.VerticalCenter) { 18813 if(y2 <= 0) 18814 return; 18815 auto h = y2 - y; 18816 if(textHeight < h) { 18817 cy += (h - textHeight) / 2; 18818 //cy -= lineHeight / 4; 18819 } 18820 } 18821 18822 foreach(line; text.split('\n')) { 18823 int textWidth = this.textSize(line).width; 18824 18825 int px = x, py = cy; 18826 18827 if(alignment & TextAlignment.Center) { 18828 if(x2 <= 0) 18829 return; 18830 auto w = x2 - x; 18831 if(w > textWidth) 18832 px += (w - textWidth) / 2; 18833 } else if(alignment & TextAlignment.Right) { 18834 if(x2 <= 0) 18835 return; 18836 auto pos = x2 - textWidth; 18837 if(pos > x) 18838 px = pos; 18839 } 18840 18841 CGContextShowTextAtPoint(context, px, py + getFont.ascent /* this is cuz this picks baseline but i want bounding box */, line.ptr, line.length); 18842 18843 carry_on: 18844 cy += lineHeight + 4; 18845 } 18846 18847 // auto cfstr = cast(NSid)createCFString(text); 18848 // objc_msgSend(cfstr, sel_registerName("drawAtPoint:withAttributes:"), 18849 // NSPoint(x, y), null); 18850 // CFRelease(cfstr); 18851 CGContextRestoreGState(context); 18852 } 18853 } 18854 18855 void drawPixel(int x, int y) { 18856 auto rawData = CGBitmapContextGetData(context); 18857 auto width = CGBitmapContextGetWidth(context); 18858 auto height = CGBitmapContextGetHeight(context); 18859 auto offset = ((height - y - 1) * width + x) * 4; 18860 rawData[offset .. offset+4] = _outlineComponents; 18861 } 18862 18863 void drawLine(int x1, int y1, int x2, int y2) { 18864 CGPoint[2] linePoints; 18865 linePoints[0] = CGPoint(x1, y1); 18866 linePoints[1] = CGPoint(x2, y2); 18867 CGContextStrokeLineSegments(context, linePoints.ptr, linePoints.length); 18868 } 18869 18870 void drawRectangleRounded(Point upperLeft, Point lowerRight, int borderRadius) { 18871 drawRectangle(upperLeft.x, upperLeft.y, lowerRight.x - upperLeft.x, lowerRight.y - upperLeft.y); // FIXME not rounded 18872 } 18873 18874 void drawRectangle(int x, int y, int width, int height) { 18875 CGContextBeginPath(context); 18876 auto rect = CGRect(CGPoint(x, y), CGSize(width, height)); 18877 CGContextAddRect(context, rect); 18878 CGContextDrawPath(context, CGPathDrawingMode.kCGPathFillStroke); 18879 } 18880 18881 void drawEllipse(int x1, int y1, int x2, int y2) { 18882 CGContextBeginPath(context); 18883 auto rect = CGRect(CGPoint(x1, y1), CGSize(x2-x1, y2-y1)); 18884 CGContextAddEllipseInRect(context, rect); 18885 CGContextDrawPath(context, CGPathDrawingMode.kCGPathFillStroke); 18886 } 18887 18888 void drawArc(int x1, int y1, int width, int height, int start, int length) { 18889 // @@@BUG@@@ Does not support elliptic arc (width != height). 18890 CGContextBeginPath(context); 18891 int clockwise = 0; 18892 if(length < 0) { 18893 clockwise = 1; 18894 length = -length; 18895 } 18896 CGContextAddArc(context, x1+width*0.5f, y1+height*0.5f, width, 18897 start*PI/(180*64), (start+length)*PI/(180*64), clockwise); 18898 CGContextDrawPath(context, CGPathDrawingMode.kCGPathFillStroke); 18899 } 18900 18901 void drawPolygon(Point[] intPoints) { 18902 CGContextBeginPath(context); 18903 CGPoint[16] pointsBuffer; 18904 CGPoint[] points; 18905 if(intPoints.length <= pointsBuffer.length) 18906 points = pointsBuffer[0 .. intPoints.length]; 18907 else 18908 points = new CGPoint[](intPoints.length); 18909 18910 foreach(idx, pt; intPoints) 18911 points[idx] = CGPoint(pt.x, pt.y); 18912 18913 CGContextAddLines(context, points.ptr, points.length); 18914 CGContextDrawPath(context, CGPathDrawingMode.kCGPathFillStroke); 18915 } 18916 } 18917 18918 private bool appInitialized = false; 18919 void initializeApp() { 18920 if(appInitialized) 18921 return; 18922 synchronized { 18923 if(appInitialized) 18924 return; 18925 18926 auto app = NSApp(); // ensure the is initialized 18927 18928 auto dg = AppDelegate.alloc; 18929 globalAppDelegate = dg; 18930 NSApp.delegate_ = dg; 18931 18932 NSApp.setActivationPolicy(NSApplicationActivationPolicy.regular); 18933 18934 appInitialized = true; 18935 } 18936 } 18937 18938 mixin template NativeSimpleWindowImplementation() { 18939 void createWindow(int width, int height, string title, OpenGlOptions opengl, SimpleWindow parent) { 18940 initializeApp(); 18941 18942 auto contentRect = NSRect(NSPoint(0, 0), NSSize(width, height)); 18943 18944 auto window = NSWindow.alloc.initWithContentRect( 18945 contentRect, 18946 NSWindowStyleMask.resizable | NSWindowStyleMask.closable | NSWindowStyleMask.miniaturizable | NSWindowStyleMask.titled, 18947 NSBackingStoreType.buffered, 18948 true 18949 ); 18950 18951 SimpleWindow.nativeMapping[cast(void*) window] = this; 18952 18953 window.title = MacString(title).borrow; 18954 18955 auto dg = SDWindowDelegate.alloc.init; 18956 dg.simpleWindow = this; 18957 window.delegate_ = dg; 18958 18959 auto view = SDGraphicsView.alloc.init; 18960 assert(view !is null); 18961 window.contentView = view; 18962 this.view = view; 18963 view.simpleWindow = this; 18964 18965 window.center(); 18966 18967 window.makeKeyAndOrderFront(null); 18968 18969 // no need to make a bitmap on mac since everything is double buffered already 18970 18971 // create area to draw on. 18972 createNewDrawingContext(width, height); 18973 18974 window.setBackgroundColor(NSColor.whiteColor); 18975 } 18976 18977 void createNewDrawingContext(int width, int height) { 18978 // FIXME need to preserve info from the old context too i think... maybe. or at least setNeedsDisplay 18979 if(this.drawingContext) 18980 CGContextRelease(this.drawingContext); 18981 auto colorSpace = CGColorSpaceCreateDeviceRGB(); 18982 this.drawingContext = CGBitmapContextCreate(null, width, height, 8, 4*width, colorSpace, kCGImageAlphaPremultipliedLast |kCGBitmapByteOrder32Big); 18983 CGColorSpaceRelease(colorSpace); 18984 CGContextSelectFont(drawingContext, "Lucida Grande", 12.0f, 1); 18985 auto matrix = CGContextGetTextMatrix(drawingContext); 18986 matrix.c = -matrix.c; 18987 matrix.d = -matrix.d; 18988 CGContextSetTextMatrix(drawingContext, matrix); 18989 18990 } 18991 18992 void dispose() { 18993 closeWindow(); 18994 // window.release(); // closing the window does this automatically i think 18995 } 18996 void closeWindow() { 18997 if(timer) 18998 timer.invalidate(); 18999 window.close(); 19000 } 19001 19002 ScreenPainter getPainter(bool manualInvalidations) { 19003 return ScreenPainter(this, this.window, manualInvalidations); 19004 } 19005 19006 NSWindow window; 19007 NSTimer timer; 19008 NSView view; 19009 CGContextRef drawingContext; 19010 } 19011 } 19012 19013 version(without_opengl) {} else 19014 extern(System) nothrow @nogc { 19015 //enum uint GL_VERSION = 0x1F02; 19016 //const(char)* glGetString (/*GLenum*/uint); 19017 version(X11) { 19018 static if (!SdpyIsUsingIVGLBinds) { 19019 19020 enum GLX_X_RENDERABLE = 0x8012; 19021 enum GLX_DRAWABLE_TYPE = 0x8010; 19022 enum GLX_RENDER_TYPE = 0x8011; 19023 enum GLX_X_VISUAL_TYPE = 0x22; 19024 enum GLX_TRUE_COLOR = 0x8002; 19025 enum GLX_WINDOW_BIT = 0x00000001; 19026 enum GLX_RGBA_BIT = 0x00000001; 19027 enum GLX_COLOR_INDEX_BIT = 0x00000002; 19028 enum GLX_SAMPLE_BUFFERS = 0x186a0; 19029 enum GLX_SAMPLES = 0x186a1; 19030 enum GLX_CONTEXT_MAJOR_VERSION_ARB = 0x2091; 19031 enum GLX_CONTEXT_MINOR_VERSION_ARB = 0x2092; 19032 } 19033 19034 // GLX_EXT_swap_control 19035 alias glXSwapIntervalEXT = void function (Display* dpy, /*GLXDrawable*/Drawable drawable, int interval); 19036 private __gshared glXSwapIntervalEXT _glx_swapInterval_fn = null; 19037 19038 //k8: ugly code to prevent warnings when sdpy is compiled into .a 19039 extern(System) { 19040 alias glXCreateContextAttribsARB_fna = GLXContext function (Display *dpy, GLXFBConfig config, GLXContext share_context, /*Bool*/int direct, const(int)* attrib_list); 19041 } 19042 private __gshared /*glXCreateContextAttribsARB_fna*/void* glXCreateContextAttribsARBFn = cast(void*)1; //HACK! 19043 19044 // this made public so we don't have to get it again and again 19045 public bool glXCreateContextAttribsARB_present () @system { 19046 if (glXCreateContextAttribsARBFn is cast(void*)1) { 19047 // get it 19048 glXCreateContextAttribsARBFn = cast(void*)glbindGetProcAddress("glXCreateContextAttribsARB"); 19049 //{ import core.stdc.stdio; printf("checking glXCreateContextAttribsARB: %shere\n", (glXCreateContextAttribsARBFn !is null ? "".ptr : "not ".ptr)); } 19050 } 19051 return (glXCreateContextAttribsARBFn !is null); 19052 } 19053 19054 // this made public so we don't have to get it again and again 19055 public GLXContext glXCreateContextAttribsARB (Display *dpy, GLXFBConfig config, GLXContext share_context, /*Bool*/int direct, const(int)* attrib_list) @system { 19056 if (!glXCreateContextAttribsARB_present()) assert(0, "glXCreateContextAttribsARB is not present"); 19057 return (cast(glXCreateContextAttribsARB_fna)glXCreateContextAttribsARBFn)(dpy, config, share_context, direct, attrib_list); 19058 } 19059 19060 // extern(C) private __gshared int function(int) glXSwapIntervalSGI; // seems totally redundant to the tohers 19061 extern(C) private __gshared int function(int) glXSwapIntervalMESA; 19062 19063 void glxSetVSync (Display* dpy, /*GLXDrawable*/Drawable drawable, bool wait) { 19064 if (cast(void*)_glx_swapInterval_fn is cast(void*)1) return; 19065 if (_glx_swapInterval_fn is null) { 19066 _glx_swapInterval_fn = cast(glXSwapIntervalEXT)glXGetProcAddress("glXSwapIntervalEXT"); 19067 if (_glx_swapInterval_fn is null) { 19068 _glx_swapInterval_fn = cast(glXSwapIntervalEXT)1; 19069 return; 19070 } 19071 version(sdddd) { debug writeln("glXSwapIntervalEXT found!"); } 19072 } 19073 19074 if(glXSwapIntervalMESA is null) { 19075 // it seems to require both to actually take effect on many computers 19076 // idk why 19077 glXSwapIntervalMESA = cast(typeof(glXSwapIntervalMESA)) glXGetProcAddress("glXSwapIntervalMESA"); 19078 if(glXSwapIntervalMESA is null) 19079 glXSwapIntervalMESA = cast(typeof(glXSwapIntervalMESA)) 1; 19080 } 19081 19082 if(cast(void*) glXSwapIntervalMESA > cast(void*) 1) 19083 glXSwapIntervalMESA(wait ? 1 : 0); 19084 19085 _glx_swapInterval_fn(dpy, drawable, (wait ? 1 : 0)); 19086 } 19087 } else version(Windows) { 19088 static if (!SdpyIsUsingIVGLBinds) { 19089 enum GL_TRUE = 1; 19090 enum GL_FALSE = 0; 19091 19092 public void* glbindGetProcAddress (const(char)* name) { 19093 void* res = wglGetProcAddress(name); 19094 if (res is null) { 19095 /+ 19096 //{ import core.stdc.stdio; printf("GL: '%s' not found (0)\n", name); } 19097 import core.sys.windows.windef, core.sys.windows.winbase; 19098 __gshared HINSTANCE dll = null; 19099 if (dll is null) { 19100 dll = LoadLibraryA("opengl32.dll"); 19101 if (dll is null) return null; // <32, but idc 19102 } 19103 res = GetProcAddress(dll, name); 19104 +/ 19105 res = GetProcAddress(gl.libHandle, name); 19106 } 19107 //{ import core.stdc.stdio; printf(" GL: '%s' is 0x%08x\n", name, cast(uint)res); } 19108 return res; 19109 } 19110 } 19111 19112 19113 private __gshared extern(System) BOOL function(int) wglSwapIntervalEXT; 19114 void wglSetVSync(bool wait) { 19115 if(wglSwapIntervalEXT is null) { 19116 wglSwapIntervalEXT = cast(typeof(wglSwapIntervalEXT)) wglGetProcAddress("wglSwapIntervalEXT"); 19117 if(wglSwapIntervalEXT is null) 19118 wglSwapIntervalEXT = cast(typeof(wglSwapIntervalEXT)) 1; 19119 } 19120 if(cast(void*) wglSwapIntervalEXT is cast(void*) 1) 19121 return; 19122 19123 wglSwapIntervalEXT(wait ? 1 : 0); 19124 } 19125 19126 enum WGL_CONTEXT_MAJOR_VERSION_ARB = 0x2091; 19127 enum WGL_CONTEXT_MINOR_VERSION_ARB = 0x2092; 19128 enum WGL_CONTEXT_LAYER_PLANE_ARB = 0x2093; 19129 enum WGL_CONTEXT_FLAGS_ARB = 0x2094; 19130 enum WGL_CONTEXT_PROFILE_MASK_ARB = 0x9126; 19131 19132 enum WGL_CONTEXT_DEBUG_BIT_ARB = 0x0001; 19133 enum WGL_CONTEXT_FORWARD_COMPATIBLE_BIT_ARB = 0x0002; 19134 19135 enum WGL_CONTEXT_CORE_PROFILE_BIT_ARB = 0x00000001; 19136 enum WGL_CONTEXT_COMPATIBILITY_PROFILE_BIT_ARB = 0x00000002; 19137 19138 alias wglCreateContextAttribsARB_fna = HGLRC function (HDC hDC, HGLRC hShareContext, const(int)* attribList); 19139 __gshared wglCreateContextAttribsARB_fna wglCreateContextAttribsARB = null; 19140 19141 void wglInitOtherFunctions () { 19142 if (wglCreateContextAttribsARB is null) { 19143 wglCreateContextAttribsARB = cast(wglCreateContextAttribsARB_fna)glbindGetProcAddress("wglCreateContextAttribsARB"); 19144 } 19145 } 19146 } 19147 19148 static if (!SdpyIsUsingIVGLBinds) { 19149 19150 interface GL { 19151 extern(System) @nogc nothrow: 19152 19153 void glGetIntegerv(int, void*); 19154 void glMatrixMode(int); 19155 void glPushMatrix(); 19156 void glLoadIdentity(); 19157 void glOrtho(double, double, double, double, double, double); 19158 void glFrustum(double, double, double, double, double, double); 19159 19160 void glPopMatrix(); 19161 void glEnable(int); 19162 void glDisable(int); 19163 void glClear(int); 19164 void glBegin(int); 19165 void glVertex2f(float, float); 19166 void glVertex3f(float, float, float); 19167 void glEnd(); 19168 void glColor3b(byte, byte, byte); 19169 void glColor3ub(ubyte, ubyte, ubyte); 19170 void glColor4b(byte, byte, byte, byte); 19171 void glColor4ub(ubyte, ubyte, ubyte, ubyte); 19172 void glColor3i(int, int, int); 19173 void glColor3ui(uint, uint, uint); 19174 void glColor4i(int, int, int, int); 19175 void glColor4ui(uint, uint, uint, uint); 19176 void glColor3f(float, float, float); 19177 void glColor4f(float, float, float, float); 19178 void glTranslatef(float, float, float); 19179 void glScalef(float, float, float); 19180 version(X11) { 19181 void glSecondaryColor3b(byte, byte, byte); 19182 void glSecondaryColor3ub(ubyte, ubyte, ubyte); 19183 void glSecondaryColor3i(int, int, int); 19184 void glSecondaryColor3ui(uint, uint, uint); 19185 void glSecondaryColor3f(float, float, float); 19186 } 19187 19188 void glDrawElements(int, int, int, void*); 19189 19190 void glRotatef(float, float, float, float); 19191 19192 uint glGetError(); 19193 19194 void glDeleteTextures(int, uint*); 19195 19196 19197 void glRasterPos2i(int, int); 19198 void glDrawPixels(int, int, uint, uint, void*); 19199 void glClearColor(float, float, float, float); 19200 19201 19202 void glPixelStorei(uint, int); 19203 19204 void glGenTextures(uint, uint*); 19205 void glBindTexture(int, int); 19206 void glTexParameteri(uint, uint, int); 19207 void glTexParameterf(uint/*GLenum*/ target, uint/*GLenum*/ pname, float param); 19208 void glTexImage2D(int, int, int, int, int, int, int, int, scope const void*); 19209 void glTexSubImage2D(uint/*GLenum*/ target, int level, int xoffset, int yoffset, 19210 /*GLsizei*/int width, /*GLsizei*/int height, 19211 uint/*GLenum*/ format, uint/*GLenum*/ type, scope const void* pixels); 19212 void glTexEnvf(uint/*GLenum*/ target, uint/*GLenum*/ pname, float param); 19213 19214 void glLineWidth(int); 19215 19216 19217 void glTexCoord2f(float, float); 19218 void glVertex2i(int, int); 19219 void glBlendFunc (int, int); 19220 void glDepthFunc (int); 19221 void glViewport(int, int, int, int); 19222 19223 void glClearDepth(double); 19224 19225 void glReadBuffer(uint); 19226 void glReadPixels(int, int, int, int, int, int, void*); 19227 19228 void glScissor(GLint x, GLint y, GLsizei width, GLsizei height); 19229 19230 void glFlush(); 19231 void glFinish(); 19232 19233 version(Windows) { 19234 BOOL wglCopyContext(HGLRC, HGLRC, UINT); 19235 HGLRC wglCreateContext(HDC); 19236 HGLRC wglCreateLayerContext(HDC, int); 19237 BOOL wglDeleteContext(HGLRC); 19238 BOOL wglDescribeLayerPlane(HDC, int, int, UINT, LPLAYERPLANEDESCRIPTOR); 19239 HGLRC wglGetCurrentContext(); 19240 HDC wglGetCurrentDC(); 19241 int wglGetLayerPaletteEntries(HDC, int, int, int, COLORREF*); 19242 PROC wglGetProcAddress(LPCSTR); 19243 BOOL wglMakeCurrent(HDC, HGLRC); 19244 BOOL wglRealizeLayerPalette(HDC, int, BOOL); 19245 int wglSetLayerPaletteEntries(HDC, int, int, int, const(COLORREF)*); 19246 BOOL wglShareLists(HGLRC, HGLRC); 19247 BOOL wglSwapLayerBuffers(HDC, UINT); 19248 BOOL wglUseFontBitmapsA(HDC, DWORD, DWORD, DWORD); 19249 BOOL wglUseFontBitmapsW(HDC, DWORD, DWORD, DWORD); 19250 BOOL wglUseFontOutlinesA(HDC, DWORD, DWORD, DWORD, FLOAT, FLOAT, int, LPGLYPHMETRICSFLOAT); 19251 BOOL wglUseFontOutlinesW(HDC, DWORD, DWORD, DWORD, FLOAT, FLOAT, int, LPGLYPHMETRICSFLOAT); 19252 } 19253 19254 } 19255 19256 interface GL3 { 19257 extern(System) @nogc nothrow: 19258 19259 void glGenVertexArrays(GLsizei, GLuint*); 19260 void glBindVertexArray(GLuint); 19261 void glDeleteVertexArrays(GLsizei, const(GLuint)*); 19262 void glGenerateMipmap(GLenum); 19263 void glBufferSubData(GLenum, GLintptr, GLsizeiptr, const(GLvoid)*); 19264 void glStencilMask(GLuint); 19265 void glStencilFunc(GLenum, GLint, GLuint); 19266 void glGetShaderInfoLog(GLuint, GLsizei, GLsizei*, GLchar*); 19267 void glGetProgramInfoLog(GLuint, GLsizei, GLsizei*, GLchar*); 19268 GLuint glCreateProgram(); 19269 GLuint glCreateShader(GLenum); 19270 void glShaderSource(GLuint, GLsizei, const(GLchar*)*, const(GLint)*); 19271 void glCompileShader(GLuint); 19272 void glGetShaderiv(GLuint, GLenum, GLint*); 19273 void glAttachShader(GLuint, GLuint); 19274 void glBindAttribLocation(GLuint, GLuint, const(GLchar)*); 19275 void glLinkProgram(GLuint); 19276 void glGetProgramiv(GLuint, GLenum, GLint*); 19277 void glDeleteProgram(GLuint); 19278 void glDeleteShader(GLuint); 19279 GLint glGetUniformLocation(GLuint, const(GLchar)*); 19280 void glGenBuffers(GLsizei, GLuint*); 19281 19282 void glUniform1f(GLint location, GLfloat v0); 19283 void glUniform2f(GLint location, GLfloat v0, GLfloat v1); 19284 void glUniform3f(GLint location, GLfloat v0, GLfloat v1, GLfloat v2); 19285 void glUniform4f(GLint location, GLfloat v0, GLfloat v1, GLfloat v2, GLfloat v3); 19286 void glUniform1i(GLint location, GLint v0); 19287 void glUniform2i(GLint location, GLint v0, GLint v1); 19288 void glUniform3i(GLint location, GLint v0, GLint v1, GLint v2); 19289 void glUniform4i(GLint location, GLint v0, GLint v1, GLint v2, GLint v3); 19290 void glUniform1ui(GLint location, GLuint v0); 19291 void glUniform2ui(GLint location, GLuint v0, GLuint v1); 19292 void glUniform3ui(GLint location, GLuint v0, GLuint v1, GLuint v2); 19293 void glUniform4ui(GLint location, GLuint v0, GLuint v1, GLuint v2, GLuint v3); 19294 void glUniform1fv(GLint location, GLsizei count, const GLfloat *value); 19295 void glUniform2fv(GLint location, GLsizei count, const GLfloat *value); 19296 void glUniform3fv(GLint location, GLsizei count, const GLfloat *value); 19297 void glUniform4fv(GLint location, GLsizei count, const GLfloat *value); 19298 void glUniform1iv(GLint location, GLsizei count, const GLint *value); 19299 void glUniform2iv(GLint location, GLsizei count, const GLint *value); 19300 void glUniform3iv(GLint location, GLsizei count, const GLint *value); 19301 void glUniform4iv(GLint location, GLsizei count, const GLint *value); 19302 void glUniform1uiv(GLint location, GLsizei count, const GLuint *value); 19303 void glUniform2uiv(GLint location, GLsizei count, const GLuint *value); 19304 void glUniform3uiv(GLint location, GLsizei count, const GLuint *value); 19305 void glUniform4uiv(GLint location, GLsizei count, const GLuint *value); 19306 void glUniformMatrix2fv(GLint location, GLsizei count, GLboolean transpose, const GLfloat *value); 19307 void glUniformMatrix3fv(GLint location, GLsizei count, GLboolean transpose, const GLfloat *value); 19308 void glUniformMatrix4fv(GLint location, GLsizei count, GLboolean transpose, const GLfloat *value); 19309 void glUniformMatrix2x3fv(GLint location, GLsizei count, GLboolean transpose, const GLfloat *value); 19310 void glUniformMatrix3x2fv(GLint location, GLsizei count, GLboolean transpose, const GLfloat *value); 19311 void glUniformMatrix2x4fv(GLint location, GLsizei count, GLboolean transpose, const GLfloat *value); 19312 void glUniformMatrix4x2fv(GLint location, GLsizei count, GLboolean transpose, const GLfloat *value); 19313 void glUniformMatrix3x4fv(GLint location, GLsizei count, GLboolean transpose, const GLfloat *value); 19314 void glUniformMatrix4x3fv(GLint location, GLsizei count, GLboolean transpose, const GLfloat *value); 19315 19316 void glColorMask(GLboolean, GLboolean, GLboolean, GLboolean); 19317 void glStencilOpSeparate(GLenum, GLenum, GLenum, GLenum); 19318 void glDrawArrays(GLenum, GLint, GLsizei); 19319 void glStencilOp(GLenum, GLenum, GLenum); 19320 void glUseProgram(GLuint); 19321 void glCullFace(GLenum); 19322 void glFrontFace(GLenum); 19323 void glActiveTexture(GLenum); 19324 void glBindBuffer(GLenum, GLuint); 19325 void glBufferData(GLenum, GLsizeiptr, const(void)*, GLenum); 19326 void glEnableVertexAttribArray(GLuint); 19327 void glVertexAttribPointer(GLuint, GLint, GLenum, GLboolean, GLsizei, const(void)*); 19328 void glUniform1i(GLint, GLint); 19329 void glUniform2fv(GLint, GLsizei, const(GLfloat)*); 19330 void glDisableVertexAttribArray(GLuint); 19331 void glDeleteBuffers(GLsizei, const(GLuint)*); 19332 void glBlendFuncSeparate(GLenum, GLenum, GLenum, GLenum); 19333 void glLogicOp (GLenum opcode); 19334 void glFramebufferTexture2D (GLenum target, GLenum attachment, GLenum textarget, GLuint texture, GLint level); 19335 void glDeleteFramebuffers (GLsizei n, const(GLuint)* framebuffers); 19336 void glGenFramebuffers (GLsizei n, GLuint* framebuffers); 19337 GLenum glCheckFramebufferStatus (GLenum target); 19338 void glBindFramebuffer (GLenum target, GLuint framebuffer); 19339 } 19340 19341 interface GL4 { 19342 extern(System) @nogc nothrow: 19343 19344 void glTextureSubImage2D(uint texture, int level, int xoffset, int yoffset, 19345 /*GLsizei*/int width, /*GLsizei*/int height, 19346 uint/*GLenum*/ format, uint/*GLenum*/ type, scope const void* pixels); 19347 } 19348 19349 interface GLU { 19350 extern(System) @nogc nothrow: 19351 19352 void gluLookAt(double, double, double, double, double, double, double, double, double); 19353 void gluPerspective(double, double, double, double); 19354 19355 char* gluErrorString(uint); 19356 } 19357 19358 19359 enum GL_RED = 0x1903; 19360 enum GL_ALPHA = 0x1906; 19361 19362 enum uint GL_FRONT = 0x0404; 19363 19364 enum uint GL_BLEND = 0x0be2; 19365 enum uint GL_LEQUAL = 0x0203; 19366 19367 19368 enum uint GL_RGB = 0x1907; 19369 enum uint GL_BGRA = 0x80e1; 19370 enum uint GL_RGBA = 0x1908; 19371 enum uint GL_RGBA8 = 0x8058; 19372 enum uint GL_TEXTURE_2D = 0x0DE1; 19373 enum uint GL_TEXTURE_MIN_FILTER = 0x2801; 19374 enum uint GL_NEAREST = 0x2600; 19375 enum uint GL_LINEAR = 0x2601; 19376 enum uint GL_TEXTURE_MAG_FILTER = 0x2800; 19377 enum uint GL_TEXTURE_WRAP_S = 0x2802; 19378 enum uint GL_TEXTURE_WRAP_T = 0x2803; 19379 enum uint GL_REPEAT = 0x2901; 19380 enum uint GL_CLAMP = 0x2900; 19381 enum uint GL_CLAMP_TO_EDGE = 0x812F; 19382 enum uint GL_CLAMP_TO_BORDER = 0x812D; 19383 enum uint GL_DECAL = 0x2101; 19384 enum uint GL_MODULATE = 0x2100; 19385 enum uint GL_TEXTURE_ENV = 0x2300; 19386 enum uint GL_TEXTURE_ENV_MODE = 0x2200; 19387 enum uint GL_REPLACE = 0x1E01; 19388 enum uint GL_LIGHTING = 0x0B50; 19389 enum uint GL_DITHER = 0x0BD0; 19390 19391 enum uint GL_NO_ERROR = 0; 19392 19393 19394 19395 enum int GL_VIEWPORT = 0x0BA2; 19396 enum int GL_MODELVIEW = 0x1700; 19397 enum int GL_TEXTURE = 0x1702; 19398 enum int GL_PROJECTION = 0x1701; 19399 enum int GL_DEPTH_TEST = 0x0B71; 19400 19401 enum int GL_COLOR_BUFFER_BIT = 0x00004000; 19402 enum int GL_ACCUM_BUFFER_BIT = 0x00000200; 19403 enum int GL_DEPTH_BUFFER_BIT = 0x00000100; 19404 enum uint GL_STENCIL_BUFFER_BIT = 0x00000400; 19405 19406 enum int GL_POINTS = 0x0000; 19407 enum int GL_LINES = 0x0001; 19408 enum int GL_LINE_LOOP = 0x0002; 19409 enum int GL_LINE_STRIP = 0x0003; 19410 enum int GL_TRIANGLES = 0x0004; 19411 enum int GL_TRIANGLE_STRIP = 5; 19412 enum int GL_TRIANGLE_FAN = 6; 19413 enum int GL_QUADS = 7; 19414 enum int GL_QUAD_STRIP = 8; 19415 enum int GL_POLYGON = 9; 19416 19417 alias GLvoid = void; 19418 alias GLboolean = ubyte; 19419 alias GLint = int; 19420 alias GLuint = uint; 19421 alias GLenum = uint; 19422 alias GLchar = char; 19423 alias GLsizei = int; 19424 alias GLfloat = float; 19425 alias GLintptr = size_t; 19426 alias GLsizeiptr = ptrdiff_t; 19427 19428 19429 enum uint GL_INVALID_ENUM = 0x0500; 19430 19431 enum uint GL_ZERO = 0; 19432 enum uint GL_ONE = 1; 19433 19434 enum uint GL_BYTE = 0x1400; 19435 enum uint GL_UNSIGNED_BYTE = 0x1401; 19436 enum uint GL_SHORT = 0x1402; 19437 enum uint GL_UNSIGNED_SHORT = 0x1403; 19438 enum uint GL_INT = 0x1404; 19439 enum uint GL_UNSIGNED_INT = 0x1405; 19440 enum uint GL_FLOAT = 0x1406; 19441 enum uint GL_2_BYTES = 0x1407; 19442 enum uint GL_3_BYTES = 0x1408; 19443 enum uint GL_4_BYTES = 0x1409; 19444 enum uint GL_DOUBLE = 0x140A; 19445 19446 enum uint GL_STREAM_DRAW = 0x88E0; 19447 19448 enum uint GL_CCW = 0x0901; 19449 19450 enum uint GL_STENCIL_TEST = 0x0B90; 19451 enum uint GL_SCISSOR_TEST = 0x0C11; 19452 19453 enum uint GL_EQUAL = 0x0202; 19454 enum uint GL_NOTEQUAL = 0x0205; 19455 19456 enum uint GL_ALWAYS = 0x0207; 19457 enum uint GL_KEEP = 0x1E00; 19458 19459 enum uint GL_INCR = 0x1E02; 19460 19461 enum uint GL_INCR_WRAP = 0x8507; 19462 enum uint GL_DECR_WRAP = 0x8508; 19463 19464 enum uint GL_CULL_FACE = 0x0B44; 19465 enum uint GL_BACK = 0x0405; 19466 19467 enum uint GL_FRAGMENT_SHADER = 0x8B30; 19468 enum uint GL_VERTEX_SHADER = 0x8B31; 19469 19470 enum uint GL_COMPILE_STATUS = 0x8B81; 19471 enum uint GL_LINK_STATUS = 0x8B82; 19472 19473 enum uint GL_ELEMENT_ARRAY_BUFFER = 0x8893; 19474 19475 enum uint GL_STATIC_DRAW = 0x88E4; 19476 19477 enum uint GL_UNPACK_ALIGNMENT = 0x0CF5; 19478 enum uint GL_UNPACK_ROW_LENGTH = 0x0CF2; 19479 enum uint GL_UNPACK_SKIP_PIXELS = 0x0CF4; 19480 enum uint GL_UNPACK_SKIP_ROWS = 0x0CF3; 19481 19482 enum uint GL_GENERATE_MIPMAP = 0x8191; 19483 enum uint GL_LINEAR_MIPMAP_LINEAR = 0x2703; 19484 19485 enum uint GL_TEXTURE0 = 0x84C0U; 19486 enum uint GL_TEXTURE1 = 0x84C1U; 19487 19488 enum uint GL_ARRAY_BUFFER = 0x8892; 19489 19490 enum uint GL_SRC_COLOR = 0x0300; 19491 enum uint GL_ONE_MINUS_SRC_COLOR = 0x0301; 19492 enum uint GL_SRC_ALPHA = 0x0302; 19493 enum uint GL_ONE_MINUS_SRC_ALPHA = 0x0303; 19494 enum uint GL_DST_ALPHA = 0x0304; 19495 enum uint GL_ONE_MINUS_DST_ALPHA = 0x0305; 19496 enum uint GL_DST_COLOR = 0x0306; 19497 enum uint GL_ONE_MINUS_DST_COLOR = 0x0307; 19498 enum uint GL_SRC_ALPHA_SATURATE = 0x0308; 19499 19500 enum uint GL_INVERT = 0x150AU; 19501 19502 enum uint GL_DEPTH_STENCIL = 0x84F9U; 19503 enum uint GL_UNSIGNED_INT_24_8 = 0x84FAU; 19504 19505 enum uint GL_FRAMEBUFFER = 0x8D40U; 19506 enum uint GL_COLOR_ATTACHMENT0 = 0x8CE0U; 19507 enum uint GL_DEPTH_STENCIL_ATTACHMENT = 0x821AU; 19508 19509 enum uint GL_FRAMEBUFFER_COMPLETE = 0x8CD5U; 19510 enum uint GL_FRAMEBUFFER_INCOMPLETE_ATTACHMENT = 0x8CD6U; 19511 enum uint GL_FRAMEBUFFER_INCOMPLETE_MISSING_ATTACHMENT = 0x8CD7U; 19512 enum uint GL_FRAMEBUFFER_INCOMPLETE_DIMENSIONS = 0x8CD9U; 19513 enum uint GL_FRAMEBUFFER_UNSUPPORTED = 0x8CDDU; 19514 19515 enum uint GL_COLOR_LOGIC_OP = 0x0BF2U; 19516 enum uint GL_CLEAR = 0x1500U; 19517 enum uint GL_COPY = 0x1503U; 19518 enum uint GL_XOR = 0x1506U; 19519 19520 enum uint GL_FRAMEBUFFER_BINDING = 0x8CA6U; 19521 19522 enum uint GL_TEXTURE_LOD_BIAS = 0x8501; 19523 19524 } 19525 } 19526 19527 /++ 19528 History: 19529 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. 19530 +/ 19531 __gshared bool gluSuccessfullyLoaded = true; 19532 19533 version(without_opengl) {} else { 19534 static if(!SdpyIsUsingIVGLBinds) { 19535 version(Windows) { 19536 mixin DynamicLoad!(GL, "opengl32", 1, openGlLibrariesSuccessfullyLoaded) gl; 19537 mixin DynamicLoad!(GLU, "glu32", 1, gluSuccessfullyLoaded) glu; 19538 } else { 19539 mixin DynamicLoad!(GL, "GL", 1, openGlLibrariesSuccessfullyLoaded) gl; 19540 mixin DynamicLoad!(GLU, "GLU", 3, gluSuccessfullyLoaded) glu; 19541 } 19542 mixin DynamicLoadSupplementalOpenGL!(GL3) gl3; 19543 19544 19545 shared static this() { 19546 gl.loadDynamicLibrary(); 19547 19548 // FIXME: this is NOT actually required and should NOT fail if it is not loaded 19549 // unless those functions are actually used 19550 // go to mark b openGlLibrariesSuccessfullyLoaded = false; 19551 glu.loadDynamicLibrary(); 19552 } 19553 } 19554 } 19555 19556 /++ 19557 Convenience method for converting D arrays to opengl buffer data 19558 19559 I would LOVE to overload it with the original glBufferData, but D won't 19560 let me since glBufferData is a function pointer :( 19561 19562 Added: August 25, 2020 (version 8.5) 19563 +/ 19564 version(without_opengl) {} else 19565 void glBufferDataSlice(GLenum target, const(void[]) data, GLenum usage) { 19566 glBufferData(target, data.length, data.ptr, usage); 19567 } 19568 19569 /++ 19570 History: 19571 Added September 1, 2024 19572 +/ 19573 version(without_opengl) {} else 19574 void glBufferSubDataSlice(GLenum target, size_t offset, const(void[]) data, GLenum usage) { 19575 glBufferSubData(target, offset, data.length, data.ptr); 19576 } 19577 19578 /++ 19579 Convenience class for using opengl shaders. 19580 19581 Ensure that you've loaded opengl 3+ and set your active 19582 context before trying to use this. 19583 19584 Added: August 25, 2020 (version 8.5) 19585 +/ 19586 version(without_opengl) {} else 19587 final class OpenGlShader { 19588 private int shaderProgram_; 19589 private @property void shaderProgram(int a) { 19590 shaderProgram_ = a; 19591 } 19592 /// Get the program ID for use in OpenGL functions. 19593 public @property int shaderProgram() { 19594 return shaderProgram_; 19595 } 19596 19597 /++ 19598 19599 +/ 19600 static struct Source { 19601 uint type; /// GL_FRAGMENT_SHADER, GL_VERTEX_SHADER, etc. 19602 string code; /// 19603 } 19604 19605 /++ 19606 Helper method to just compile some shader code and check for errors 19607 while you do glCreateShader, etc. on the outside yourself. 19608 19609 This just does `glShaderSource` and `glCompileShader` for the given code. 19610 19611 If you the OpenGlShader class constructor, you never need to call this yourself. 19612 +/ 19613 static void compile(int sid, Source code) { 19614 const(char)*[1] buffer; 19615 int[1] lengthBuffer; 19616 19617 buffer[0] = code.code.ptr; 19618 lengthBuffer[0] = cast(int) code.code.length; 19619 19620 glShaderSource(sid, cast(int) buffer.length, buffer.ptr, lengthBuffer.ptr); 19621 glCompileShader(sid); 19622 19623 int success; 19624 glGetShaderiv(sid, GL_COMPILE_STATUS, &success); 19625 if(!success) { 19626 char[512] info; 19627 int len; 19628 glGetShaderInfoLog(sid, info.length, &len, info.ptr); 19629 19630 throw new Exception("Shader compile failure: " ~ cast(immutable) info[0 .. len]); 19631 } 19632 } 19633 19634 /++ 19635 Calls `glLinkProgram` and throws if error a occurs. 19636 19637 If you the OpenGlShader class constructor, you never need to call this yourself. 19638 +/ 19639 static void link(int shaderProgram) { 19640 glLinkProgram(shaderProgram); 19641 int success; 19642 glGetProgramiv(shaderProgram, GL_LINK_STATUS, &success); 19643 if(!success) { 19644 char[512] info; 19645 int len; 19646 glGetProgramInfoLog(shaderProgram, info.length, &len, info.ptr); 19647 19648 throw new Exception("Shader link failure: " ~ cast(immutable) info[0 .. len]); 19649 } 19650 } 19651 19652 /++ 19653 Constructs the shader object by calling `glCreateProgram`, then 19654 compiling each given [Source], and finally, linking them together. 19655 19656 Throws: on compile or link failure. 19657 +/ 19658 this(Source[] codes...) { 19659 shaderProgram = glCreateProgram(); 19660 19661 int[16] shadersBufferStack; 19662 19663 int[] shadersBuffer = codes.length <= shadersBufferStack.length ? 19664 shadersBufferStack[0 .. codes.length] : 19665 new int[](codes.length); 19666 19667 foreach(idx, code; codes) { 19668 shadersBuffer[idx] = glCreateShader(code.type); 19669 19670 compile(shadersBuffer[idx], code); 19671 19672 glAttachShader(shaderProgram, shadersBuffer[idx]); 19673 } 19674 19675 link(shaderProgram); 19676 19677 foreach(s; shadersBuffer) 19678 glDeleteShader(s); 19679 } 19680 19681 /// Calls `glUseProgram(this.shaderProgram)` 19682 void use() { 19683 glUseProgram(this.shaderProgram); 19684 } 19685 19686 /// Deletes the program. 19687 void delete_() { 19688 glDeleteProgram(shaderProgram); 19689 shaderProgram = 0; 19690 } 19691 19692 /++ 19693 [OpenGlShader.uniforms].name gives you one of these. 19694 19695 You can get the id out of it or just assign 19696 +/ 19697 static struct Uniform { 19698 /// the id passed to glUniform* 19699 int id; 19700 19701 /// Assigns the 4 floats. You will probably have to call this via the .opAssign name 19702 void opAssign(float x, float y, float z, float w) { 19703 if(id != -1) 19704 glUniform4f(id, x, y, z, w); 19705 } 19706 19707 void opAssign(float x) { 19708 if(id != -1) 19709 glUniform1f(id, x); 19710 } 19711 19712 void opAssign(float x, float y) { 19713 if(id != -1) 19714 glUniform2f(id, x, y); 19715 } 19716 19717 void opAssign(T)(T t) { 19718 t.glUniform(id); 19719 } 19720 } 19721 19722 static struct UniformsHelper { 19723 OpenGlShader _shader; 19724 19725 @property Uniform opDispatch(string name)() { 19726 auto i = glGetUniformLocation(_shader.shaderProgram, name.ptr); 19727 // FIXME: decide what to do here; the exception is liable to be swallowed by the event syste 19728 //if(i == -1) 19729 //throw new Exception("Could not find uniform " ~ name); 19730 return Uniform(i); 19731 } 19732 19733 @property void opDispatch(string name, T)(T t) { 19734 Uniform f = this.opDispatch!name; 19735 t.glUniform(f); 19736 } 19737 } 19738 19739 /++ 19740 Gives access to the uniforms through dot access. 19741 `OpenGlShader.Uniform = shader.uniforms.foo; // calls glGetUniformLocation(this, "foo"); 19742 +/ 19743 @property UniformsHelper uniforms() { return UniformsHelper(this); } 19744 } 19745 19746 version(without_opengl) {} else { 19747 /++ 19748 A static container of experimental types and value constructors for opengl 3+ shaders. 19749 19750 19751 You can declare variables like: 19752 19753 ``` 19754 OGL.vec3f something; 19755 ``` 19756 19757 But generally it would be used with [OpenGlShader]'s uniform helpers like 19758 19759 ``` 19760 shader.uniforms.mouse = OGL.vec(mouseX, mouseY); // or OGL.vec2f if you want to be more specific 19761 ``` 19762 19763 This is still extremely experimental, not very useful at this point, and thus subject to change at random. 19764 19765 19766 History: 19767 Added December 7, 2021. Not yet stable. 19768 +/ 19769 final class OGL { 19770 static: 19771 19772 private template typeFromSpecifier(string specifier) { 19773 static if(specifier == "f") 19774 alias typeFromSpecifier = GLfloat; 19775 else static if(specifier == "i") 19776 alias typeFromSpecifier = GLint; 19777 else static if(specifier == "ui") 19778 alias typeFromSpecifier = GLuint; 19779 else static assert(0, "I don't know this ogl type suffix " ~ specifier); 19780 } 19781 19782 private template CommonType(T...) { 19783 static if(T.length == 1) 19784 alias CommonType = T[0]; 19785 else static if(is(typeof(true ? T[0].init : T[1].init) C)) 19786 alias CommonType = CommonType!(C, T[2 .. $]); 19787 } 19788 19789 private template typesToSpecifier(T...) { 19790 static if(is(CommonType!T == float)) 19791 enum typesToSpecifier = "f"; 19792 else static if(is(CommonType!T == int)) 19793 enum typesToSpecifier = "i"; 19794 else static if(is(CommonType!T == uint)) 19795 enum typesToSpecifier = "ui"; 19796 else static assert(0, "I can't find a gl type suffix for common type " ~ CommonType!T.stringof); 19797 } 19798 19799 private template genNames(size_t dim, size_t dim2 = 0) { 19800 string helper() { 19801 string s; 19802 if(dim2) { 19803 static if(__VERSION__ < 2102) 19804 s ~= "type["~(dim + '0')~"]["~(dim2 + '0')~"] matrix = void;"; // stupid compiler bug 19805 else 19806 s ~= "type["~(dim + '0')~"]["~(dim2 + '0')~"] matrix = 0;"; 19807 } else { 19808 if(dim > 0) s ~= "type x = 0;"; 19809 if(dim > 1) s ~= "type y = 0;"; 19810 if(dim > 2) s ~= "type z = 0;"; 19811 if(dim > 3) s ~= "type w = 0;"; 19812 } 19813 19814 s ~= "this(typeof(this.tupleof) args) { this.tupleof = args; }"; 19815 if(dim2) 19816 s ~= "this(type["~(dim*dim2).stringof~"] t) { (cast(typeof(t)) this.matrix)[] = t[]; }"; 19817 19818 return s; 19819 } 19820 19821 enum genNames = helper(); 19822 } 19823 19824 // there's vec, arrays of vec, mat, and arrays of mat 19825 template opDispatch(string name) 19826 if(name.length > 4 && (name[0 .. 3] == "vec" || name[0 .. 3] == "mat")) 19827 { 19828 static if(name[4] == 'x') { 19829 enum dimX = cast(int) (name[3] - '0'); 19830 static assert(dimX > 0 && dimX <= 4, "Bad dimension for OGL X type " ~ name[3]); 19831 19832 enum dimY = cast(int) (name[5] - '0'); 19833 static assert(dimY > 0 && dimY <= 4, "Bad dimension for OGL Y type " ~ name[5]); 19834 19835 enum isArray = name[$ - 1] == 'v'; 19836 enum typeSpecifier = isArray ? name[6 .. $ - 1] : name[6 .. $]; 19837 alias type = typeFromSpecifier!typeSpecifier; 19838 } else { 19839 enum dim = cast(int) (name[3] - '0'); 19840 static assert(dim > 0 && dim <= 4, "Bad dimension for OGL type " ~ name[3]); 19841 enum isArray = name[$ - 1] == 'v'; 19842 enum typeSpecifier = isArray ? name[4 .. $ - 1] : name[4 .. $]; 19843 alias type = typeFromSpecifier!typeSpecifier; 19844 } 19845 19846 align(1) 19847 struct opDispatch { 19848 align(1): 19849 static if(name[4] == 'x') 19850 mixin(genNames!(dimX, dimY)); 19851 else 19852 mixin(genNames!dim); 19853 19854 private void glUniform(OpenGlShader.Uniform assignTo) { 19855 glUniform(assignTo.id); 19856 } 19857 private void glUniform(int assignTo) { 19858 static if(name[4] == 'x') { 19859 static if(name[3] == name[5]) { 19860 // import std.stdio; writeln(name, " ", this.matrix, dimX, " ", dimY); 19861 mixin("glUniformMatrix" ~ name[5 .. $] ~ "v")(assignTo, 1, true, &this.matrix[0][0]); 19862 } else { 19863 mixin("glUniformMatrix" ~ name[3 .. $] ~ "v")(assignTo, 1, false, this.matrix.ptr); 19864 } 19865 } else 19866 mixin("glUniform" ~ name[3 .. $])(assignTo, this.tupleof); 19867 } 19868 } 19869 } 19870 19871 auto vec(T...)(T members) { 19872 return typeof(this).opDispatch!("vec" ~ toInternal!string(cast(int) T.length)~ typesToSpecifier!T)(members); 19873 } 19874 } 19875 19876 void checkGlError() { 19877 auto error = glGetError(); 19878 int[] errors; 19879 string[] errorStrings; 19880 while(error != GL_NO_ERROR) { 19881 errors ~= error; 19882 switch(error) { 19883 case 0x0500: errorStrings ~= "GL_INVALID_ENUM"; break; 19884 case 0x0501: errorStrings ~= "GL_INVALID_VALUE"; break; 19885 case 0x0502: errorStrings ~= "GL_INVALID_OPERATION"; break; 19886 case 0x0503: errorStrings ~= "GL_STACK_OVERFLOW"; break; 19887 case 0x0504: errorStrings ~= "GL_STACK_UNDERFLOW"; break; 19888 case 0x0505: errorStrings ~= "GL_OUT_OF_MEMORY"; break; 19889 default: errorStrings ~= "idk"; 19890 } 19891 error = glGetError(); 19892 } 19893 if(errors.length) 19894 throw ArsdException!"glGetError"(errors, errorStrings); 19895 } 19896 19897 /++ 19898 A matrix for simple uses that easily integrates with [OpenGlShader]. 19899 19900 Might not be useful to you since it only as some simple functions and 19901 probably isn't that fast. 19902 19903 Note it uses an inline static array for its storage, so copying it 19904 may be expensive. 19905 +/ 19906 struct BasicMatrix(int columns, int rows, T = float) { 19907 static import core.stdc.math; 19908 static if(is(T == float)) { 19909 alias cos = core.stdc.math.cosf; 19910 alias sin = core.stdc.math.sinf; 19911 } else { 19912 alias cos = core.stdc.math.cos; 19913 alias sin = core.stdc.math.sin; 19914 } 19915 19916 T[columns * rows] data = 0.0; 19917 19918 /++ 19919 19920 +/ 19921 this(T[columns * rows] data) { 19922 this.data = data; 19923 } 19924 19925 /++ 19926 Basic operations that operate *in place*. 19927 +/ 19928 static if(columns == 4 && rows == 4) 19929 void translate(T x, T y, T z) { 19930 BasicMatrix m = [ 19931 1, 0, 0, x, 19932 0, 1, 0, y, 19933 0, 0, 1, z, 19934 0, 0, 0, 1 19935 ]; 19936 19937 this *= m; 19938 } 19939 19940 /// ditto 19941 static if(columns == 4 && rows == 4) 19942 void scale(T x, T y, T z) { 19943 BasicMatrix m = [ 19944 x, 0, 0, 0, 19945 0, y, 0, 0, 19946 0, 0, z, 0, 19947 0, 0, 0, 1 19948 ]; 19949 19950 this *= m; 19951 } 19952 19953 /// ditto 19954 static if(columns == 4 && rows == 4) 19955 void rotateX(T theta) { 19956 BasicMatrix m = [ 19957 1, 0, 0, 0, 19958 0, cos(theta), -sin(theta), 0, 19959 0, sin(theta), cos(theta), 0, 19960 0, 0, 0, 1 19961 ]; 19962 19963 this *= m; 19964 } 19965 19966 /// ditto 19967 static if(columns == 4 && rows == 4) 19968 void rotateY(T theta) { 19969 BasicMatrix m = [ 19970 cos(theta), 0, sin(theta), 0, 19971 0, 1, 0, 0, 19972 -sin(theta), 0, cos(theta), 0, 19973 0, 0, 0, 1 19974 ]; 19975 19976 this *= m; 19977 } 19978 19979 /// ditto 19980 static if(columns == 4 && rows == 4) 19981 void rotateZ(T theta) { 19982 BasicMatrix m = [ 19983 cos(theta), -sin(theta), 0, 0, 19984 sin(theta), cos(theta), 0, 0, 19985 0, 0, 1, 0, 19986 0, 0, 0, 1 19987 ]; 19988 19989 this *= m; 19990 } 19991 19992 /++ 19993 19994 +/ 19995 static if(columns == rows) 19996 static BasicMatrix identity() { 19997 BasicMatrix m; 19998 foreach(i; 0 .. columns) 19999 m.data[0 + i + i * columns] = 1.0; 20000 return m; 20001 } 20002 20003 static if(columns == rows) 20004 void loadIdentity() { 20005 this = identity(); 20006 } 20007 20008 static if(columns == 4 && rows == 4) 20009 static BasicMatrix ortho(T l, T r, T b, T t, T n, T f) { 20010 return BasicMatrix([ 20011 2/(r-l), 0, 0, -(r+l)/(r-l), 20012 0, 2/(t-b), 0, -(t+b)/(t-b), 20013 0, 0, -2/(f-n), -(f+n)/(f-n), 20014 0, 0, 0, 1 20015 ]); 20016 } 20017 20018 static if(columns == 4 && rows == 4) 20019 void loadOrtho(T l, T r, T b, T t, T n, T f) { 20020 this = ortho(l, r, b, t, n, f); 20021 } 20022 20023 void opOpAssign(string op : "+")(const BasicMatrix rhs) { 20024 this.data[] += rhs.data; 20025 } 20026 void opOpAssign(string op : "-")(const BasicMatrix rhs) { 20027 this.data[] -= rhs.data; 20028 } 20029 void opOpAssign(string op : "*")(const T rhs) { 20030 this.data[] *= rhs; 20031 } 20032 void opOpAssign(string op : "/")(const T rhs) { 20033 this.data[] /= rhs; 20034 } 20035 void opOpAssign(string op : "*", BM : BasicMatrix!(rhsColumns, rhsRows, rhsT), int rhsColumns, int rhsRows, rhsT)(const BM rhs) { 20036 static assert(columns == rhsRows); 20037 auto multiplySize = columns; 20038 20039 auto tmp = this.data; // copy cuz it is a value type 20040 20041 int idx = 0; 20042 foreach(r; 0 .. rows) 20043 foreach(c; 0 .. columns) { 20044 T sum = 0.0; 20045 20046 foreach(i; 0 .. multiplySize) 20047 sum += this.data[r * columns + i] * rhs.data[i * rhsColumns + c]; 20048 20049 tmp[idx++] = sum; 20050 } 20051 20052 this.data = tmp; 20053 } 20054 } 20055 20056 unittest { 20057 auto m = BasicMatrix!(2, 2)([ 20058 1, 2, 20059 3, 4 20060 ]); 20061 20062 auto m2 = BasicMatrix!(2, 2)([ 20063 5, 6, 20064 7, 8 20065 ]); 20066 20067 import std.conv; 20068 m *= m2; 20069 assert(m.data == [ 20070 19, 22, 20071 43, 50 20072 ], to!string(m.data)); 20073 } 20074 20075 20076 20077 class GlObjectBase { 20078 protected uint _vao; 20079 protected uint _elementsCount; 20080 20081 protected uint element_buffer; 20082 20083 void gen() { 20084 glGenVertexArrays(1, &_vao); 20085 } 20086 20087 void bind() { 20088 glBindVertexArray(_vao); 20089 } 20090 20091 void dispose() { 20092 glDeleteVertexArrays(1, &_vao); 20093 } 20094 20095 void draw() { 20096 bind(); 20097 glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, element_buffer); 20098 glDrawElements(GL_TRIANGLES, _elementsCount, GL_UNSIGNED_INT, null); 20099 } 20100 } 20101 20102 /++ 20103 20104 +/ 20105 class GlObject(T) : GlObjectBase { 20106 protected uint VBO; 20107 20108 this(T[] arr, uint[] indices) { 20109 gen(); 20110 bind(); 20111 20112 glGenBuffers(1, &VBO); 20113 glGenBuffers(1, &element_buffer); 20114 20115 glBindBuffer(GL_ARRAY_BUFFER, VBO); 20116 glBufferDataSlice(GL_ARRAY_BUFFER, arr, GL_STATIC_DRAW); 20117 20118 glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, element_buffer); 20119 glBufferDataSlice(GL_ELEMENT_ARRAY_BUFFER, indices, GL_STATIC_DRAW); 20120 _elementsCount = cast(int) indices.length; 20121 20122 foreach(int idx, memberName; __traits(allMembers, T)) { 20123 static if(memberName != "__ctor") { 20124 static if(is(typeof(__traits(getMember, T, memberName)) == float[N], size_t N)) { 20125 glVertexAttribPointer(idx, N, GL_FLOAT, GL_FALSE, T.sizeof, cast(void*) __traits(getMember, T, memberName).offsetof); 20126 glEnableVertexAttribArray(idx); 20127 } else static assert(0); } 20128 } 20129 } 20130 20131 static string generateShaderDefinitions() { 20132 string code; 20133 20134 foreach(idx, memberName; __traits(allMembers, T)) { 20135 // never use stringof ladies and gents it has a LU thing at the end of it 20136 static if(memberName != "__ctor") 20137 code ~= "layout (location = " ~ idx.stringof[0..$-2] ~ ") in " ~ typeToGl!(typeof(__traits(getMember, T, memberName))) ~ " " ~ memberName ~ ";\n"; 20138 } 20139 20140 return code; 20141 } 20142 } 20143 20144 private string typeToGl(T)() { 20145 static if(is(T == float[4])) 20146 return "vec4"; 20147 else static if(is(T == float[3])) 20148 return "vec3"; 20149 else static if(is(T == float[2])) 20150 return "vec2"; 20151 else static assert(0, T.stringof); 20152 } 20153 20154 20155 } 20156 20157 version(linux) { 20158 version(with_eventloop) {} else { 20159 private int epollFd = -1; 20160 void prepareEventLoop() { 20161 if(epollFd != -1) 20162 return; // already initialized, no need to do it again 20163 import ep = core.sys.linux.epoll; 20164 20165 epollFd = ep.epoll_create1(ep.EPOLL_CLOEXEC); 20166 if(epollFd == -1) 20167 throw new Exception("epoll create failure"); 20168 } 20169 } 20170 } else version(Posix) { 20171 void prepareEventLoop() {} 20172 } 20173 20174 version(X11) { 20175 import core.stdc.locale : LC_ALL; // rdmd fix 20176 __gshared bool sdx_isUTF8Locale; 20177 20178 // This whole crap is used to initialize X11 locale, so that you can use XIM methods later. 20179 // Yes, there are people with non-utf locale (it's me, Ketmar!), but XIM (composing) will 20180 // not work right if app/X11 locale is not utf. This sux. That's why all that "utf detection" 20181 // anal magic is here. I (Ketmar) hope you like it. 20182 // We will use `sdx_isUTF8Locale` on XIM creation to enforce UTF-8 locale, so XCompose will 20183 // always return correct unicode symbols. The detection is here 'cause user can change locale 20184 // later. 20185 20186 // NOTE: IT IS VERY IMPORTANT THAT THIS BE THE LAST STATIC CTOR OF THE FILE since it tests librariesSuccessfullyLoaded 20187 shared static this () @system { 20188 if(!librariesSuccessfullyLoaded) 20189 return; 20190 20191 import core.stdc.locale : setlocale, LC_ALL, LC_CTYPE; 20192 20193 // this doesn't hurt; it may add some locking, but the speed is still 20194 // allows doing 60 FPS videogames; also, ignore the result, as most 20195 // users will probably won't do mulththreaded X11 anyway (and I (ketmar) 20196 // never seen this failing). 20197 if (XInitThreads() == 0) { import core.stdc.stdio; fprintf(stderr, "XInitThreads() failed!\n"); } 20198 20199 setlocale(LC_ALL, ""); 20200 // check if out locale is UTF-8 20201 auto lct = setlocale(LC_CTYPE, null); 20202 if (lct is null) { 20203 sdx_isUTF8Locale = false; 20204 } else { 20205 for (size_t idx = 0; lct[idx] && lct[idx+1] && lct[idx+2]; ++idx) { 20206 if ((lct[idx+0] == 'u' || lct[idx+0] == 'U') && 20207 (lct[idx+1] == 't' || lct[idx+1] == 'T') && 20208 (lct[idx+2] == 'f' || lct[idx+2] == 'F')) 20209 { 20210 sdx_isUTF8Locale = true; 20211 break; 20212 } 20213 } 20214 } 20215 //{ import core.stdc.stdio : stderr, fprintf; fprintf(stderr, "UTF8: %s\n", sdx_isUTF8Locale ? "tan".ptr : "ona".ptr); } 20216 } 20217 } 20218 20219 class ExperimentalTextComponent2 { 20220 /+ 20221 Stage 1: get it working monospace 20222 Stage 2: use proportional font 20223 Stage 3: allow changes in inline style 20224 Stage 4: allow new fonts and sizes in the middle 20225 Stage 5: optimize gap buffer 20226 Stage 6: optimize layout 20227 Stage 7: word wrap 20228 Stage 8: justification 20229 Stage 9: editing, selection, etc. 20230 20231 Operations: 20232 insert text 20233 overstrike text 20234 select 20235 cut 20236 modify 20237 +/ 20238 20239 /++ 20240 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. 20241 +/ 20242 this(SimpleWindow window) { 20243 this.window = window; 20244 } 20245 20246 private SimpleWindow window; 20247 20248 20249 /++ 20250 When you render a [ComponentInFlow], it returns an arbitrary number of these interfaces 20251 representing the internal parts. The first pass is focused on the x parameter, then the 20252 renderer is responsible for going back to the parts in the current line and calling 20253 adjustDownForAscent to change the y params. 20254 +/ 20255 static interface ComponentRenderHelper { 20256 20257 /+ 20258 When you do an edit, possibly stuff on the same line previously need to move (to adjust 20259 the baseline), stuff subsequent needs to move (adjust x) and possibly stuff below needs 20260 to move (adjust y to make room for new line) until you get back to the same position, 20261 then you can stop - if one thing is unchanged, nothing after it is changed too. 20262 20263 Word wrap might change this as if can rewrap tons of stuff, but the same idea applies, 20264 once you reach something that is unchanged, you can stop. 20265 +/ 20266 20267 void adjustDownForAscent(int amount); // at the end of the line it needs to do these 20268 20269 int ascent() const; 20270 int descent() const; 20271 20272 int advance() const; 20273 20274 bool endsWithExplititLineBreak() const; 20275 } 20276 20277 static interface RenderResult { 20278 /++ 20279 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. 20280 +/ 20281 void popFront(); 20282 @property bool empty() const; 20283 @property ComponentRenderHelper front() const; 20284 20285 void repositionForNextLine(Point baseline, int availableWidth); 20286 } 20287 20288 static interface ComponentInFlow { 20289 void draw(ScreenPainter painter); 20290 //RenderResult render(Point baseline, int availableWidth); // FIXME: it needs to be able to say "my cache is good, nothing different" 20291 20292 bool startsWithExplicitLineBreak() const; 20293 } 20294 20295 static class TextFlowComponent : ComponentInFlow { 20296 bool startsWithExplicitLineBreak() const { return false; } // FIXME: if it is block this can return true 20297 20298 Color foreground; 20299 Color background; 20300 20301 OperatingSystemFont font; // should NEVER be null 20302 20303 ubyte attributes; // underline, strike through, display on new block 20304 20305 version(Windows) 20306 const(wchar)[] content; 20307 else 20308 const(char)[] content; // this should NEVER have a newline, except at the end 20309 20310 RenderedComponent[] rendered; // entirely controlled by [rerender] 20311 20312 // could prolly put some spacing around it too like margin / padding 20313 20314 this(Color f, Color b, OperatingSystemFont font, ubyte attr, const(char)[] c) 20315 in { assert(font !is null); 20316 assert(!font.isNull); } 20317 do 20318 { 20319 this.foreground = f; 20320 this.background = b; 20321 this.font = font; 20322 20323 this.attributes = attr; 20324 version(Windows) { 20325 auto conversionFlags = 0;//WindowsStringConversionFlags.convertNewLines; 20326 auto sz = sizeOfConvertedWstring(c, conversionFlags); 20327 auto buffer = new wchar[](sz); 20328 this.content = makeWindowsString(c, buffer, conversionFlags); 20329 } else { 20330 this.content = c.dup; 20331 } 20332 } 20333 20334 void draw(ScreenPainter painter) { 20335 painter.setFont(this.font); 20336 painter.outlineColor = this.foreground; 20337 painter.fillColor = Color.transparent; 20338 foreach(rendered; this.rendered) { 20339 // the component works in term of baseline, 20340 // but the painter works in term of upper left bounding box 20341 // so need to translate that 20342 20343 if(this.background.a) { 20344 painter.fillColor = this.background; 20345 painter.outlineColor = this.background; 20346 20347 painter.drawRectangle(Point(rendered.startX, rendered.startY - this.font.ascent), Size(rendered.width, this.font.height)); 20348 20349 painter.outlineColor = this.foreground; 20350 painter.fillColor = Color.transparent; 20351 } 20352 20353 painter.drawText(Point(rendered.startX, rendered.startY - this.font.ascent), rendered.slice); 20354 20355 // FIXME: strike through, underline, highlight selection, etc. 20356 } 20357 } 20358 } 20359 20360 // I could split the parts into words on render 20361 // for easier word-wrap, each one being an unbreakable "inline-block" 20362 private TextFlowComponent[] parts; 20363 private int needsRerenderFrom; 20364 20365 void addPart(Color f, Color b, OperatingSystemFont font, ubyte attr, const(char)[] c) { 20366 // FIXME: needsRerenderFrom. Basically if the bounding box and baseline is the same as the previous thing, it can prolly just stop. 20367 parts ~= new TextFlowComponent(f, b, font, attr, c); 20368 } 20369 20370 static struct RenderedComponent { 20371 int startX; 20372 int startY; 20373 short width; 20374 // 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! 20375 // for individual chars in here you've gotta process on demand 20376 version(Windows) 20377 const(wchar)[] slice; 20378 else 20379 const(char)[] slice; 20380 } 20381 20382 20383 void rerender(Rectangle boundingBox) { 20384 Point baseline = boundingBox.upperLeft; 20385 20386 this.boundingBox.left = boundingBox.left; 20387 this.boundingBox.top = boundingBox.top; 20388 20389 auto remainingParts = parts; 20390 20391 int largestX; 20392 20393 20394 foreach(part; parts) 20395 part.font.prepareContext(window); 20396 scope(exit) 20397 foreach(part; parts) 20398 part.font.releaseContext(); 20399 20400 calculateNextLine: 20401 20402 int nextLineHeight = 0; 20403 int nextBiggestDescent = 0; 20404 20405 foreach(part; remainingParts) { 20406 auto height = part.font.ascent; 20407 if(height > nextLineHeight) 20408 nextLineHeight = height; 20409 if(part.font.descent > nextBiggestDescent) 20410 nextBiggestDescent = part.font.descent; 20411 if(part.content.length && part.content[$-1] == '\n') 20412 break; 20413 } 20414 20415 baseline.y += nextLineHeight; 20416 auto lineStart = baseline; 20417 20418 while(remainingParts.length) { 20419 remainingParts[0].rendered = null; 20420 20421 bool eol; 20422 if(remainingParts[0].content.length && remainingParts[0].content[$-1] == '\n') 20423 eol = true; 20424 20425 // FIXME: word wrap 20426 auto font = remainingParts[0].font; 20427 auto slice = remainingParts[0].content[0 .. $ - (eol ? 1 : 0)]; 20428 auto width = font.stringWidth(slice, window); 20429 remainingParts[0].rendered ~= RenderedComponent(baseline.x, baseline.y, cast(short) width, slice); 20430 20431 remainingParts = remainingParts[1 .. $]; 20432 baseline.x += width; 20433 20434 if(eol) { 20435 baseline.y += nextBiggestDescent; 20436 if(baseline.x > largestX) 20437 largestX = baseline.x; 20438 baseline.x = lineStart.x; 20439 goto calculateNextLine; 20440 } 20441 } 20442 20443 if(baseline.x > largestX) 20444 largestX = baseline.x; 20445 20446 this.boundingBox.right = largestX; 20447 this.boundingBox.bottom = baseline.y; 20448 } 20449 20450 // you must call rerender first! 20451 void draw(ScreenPainter painter) { 20452 foreach(part; parts) { 20453 part.draw(painter); 20454 } 20455 } 20456 20457 struct IdentifyResult { 20458 TextFlowComponent part; 20459 int charIndexInPart; 20460 int totalCharIndex = -1; // if this is -1, it just means the end 20461 20462 Rectangle boundingBox; 20463 } 20464 20465 IdentifyResult identify(Point pt, bool exact = false) { 20466 if(parts.length == 0) 20467 return IdentifyResult(null, 0); 20468 20469 if(pt.y < boundingBox.top) { 20470 if(exact) 20471 return IdentifyResult(null, 1); 20472 return IdentifyResult(parts[0], 0); 20473 } 20474 if(pt.y > boundingBox.bottom) { 20475 if(exact) 20476 return IdentifyResult(null, 2); 20477 return IdentifyResult(parts[$-1], cast(int) parts[$-1].content.length); 20478 } 20479 20480 int tci = 0; 20481 20482 // I should probably like binary search this or something... 20483 foreach(ref part; parts) { 20484 foreach(rendered; part.rendered) { 20485 auto rect = Rectangle(rendered.startX, rendered.startY - part.font.ascent, rendered.startX + rendered.width, rendered.startY + part.font.descent); 20486 if(rect.contains(pt)) { 20487 auto x = pt.x - rendered.startX; 20488 auto estimatedIdx = x / part.font.averageWidth; 20489 20490 if(estimatedIdx < 0) 20491 estimatedIdx = 0; 20492 20493 if(estimatedIdx > rendered.slice.length) 20494 estimatedIdx = cast(int) rendered.slice.length; 20495 20496 int idx; 20497 int x1, x2; 20498 if(part.font.isMonospace) { 20499 auto w = part.font.averageWidth; 20500 if(!exact && x > (estimatedIdx + 1) * w) 20501 return IdentifyResult(null, 4); 20502 idx = estimatedIdx; 20503 x1 = idx * w; 20504 x2 = (idx + 1) * w; 20505 } else { 20506 idx = estimatedIdx; 20507 20508 part.font.prepareContext(window); 20509 scope(exit) part.font.releaseContext(); 20510 20511 // int iterations; 20512 20513 while(true) { 20514 // iterations++; 20515 x1 = idx ? part.font.stringWidth(rendered.slice[0 .. idx - 1]) : 0; 20516 x2 = part.font.stringWidth(rendered.slice[0 .. idx]); // should be the maximum since `averageWidth` kinda lies. 20517 20518 x1 += rendered.startX; 20519 x2 += rendered.startX; 20520 20521 if(pt.x < x1) { 20522 if(idx == 0) { 20523 if(exact) 20524 return IdentifyResult(null, 6); 20525 else 20526 break; 20527 } 20528 idx--; 20529 } else if(pt.x > x2) { 20530 idx++; 20531 if(idx > rendered.slice.length) { 20532 if(exact) 20533 return IdentifyResult(null, 5); 20534 else 20535 break; 20536 } 20537 } else if(pt.x >= x1 && pt.x <= x2) { 20538 if(idx) 20539 idx--; // point it at the original index 20540 break; // we fit 20541 } 20542 } 20543 20544 // writeln(iterations) 20545 } 20546 20547 20548 return IdentifyResult(part, idx, tci + idx, Rectangle(x1, rect.top, x2, rect.bottom)); // FIXME: utf-8? 20549 } 20550 } 20551 tci += cast(int) part.content.length; // FIXME: utf-8? 20552 } 20553 return IdentifyResult(null, 3); 20554 } 20555 20556 Rectangle boundingBox; // only set after [rerender] 20557 20558 // text will be positioned around the exclusion zone 20559 static struct ExclusionZone { 20560 20561 } 20562 20563 ExclusionZone[] exclusionZones; 20564 } 20565 20566 20567 // Don't use this yet. When I'm happy with it, I will move it to the 20568 // regular module namespace. 20569 mixin template ExperimentalTextComponent() { 20570 20571 static: 20572 20573 alias Rectangle = arsd.color.Rectangle; 20574 20575 struct ForegroundColor { 20576 Color color; 20577 alias color this; 20578 20579 this(Color c) { 20580 color = c; 20581 } 20582 20583 this(int r, int g, int b, int a = 255) { 20584 color = Color(r, g, b, a); 20585 } 20586 20587 static ForegroundColor opDispatch(string s)() if(__traits(compiles, ForegroundColor(mixin("Color." ~ s)))) { 20588 return ForegroundColor(mixin("Color." ~ s)); 20589 } 20590 } 20591 20592 struct BackgroundColor { 20593 Color color; 20594 alias color this; 20595 20596 this(Color c) { 20597 color = c; 20598 } 20599 20600 this(int r, int g, int b, int a = 255) { 20601 color = Color(r, g, b, a); 20602 } 20603 20604 static BackgroundColor opDispatch(string s)() if(__traits(compiles, BackgroundColor(mixin("Color." ~ s)))) { 20605 return BackgroundColor(mixin("Color." ~ s)); 20606 } 20607 } 20608 20609 static class InlineElement { 20610 string text; 20611 20612 BlockElement containingBlock; 20613 20614 Color color = Color.black; 20615 Color backgroundColor = Color.transparent; 20616 ushort styles; 20617 20618 string font; 20619 int fontSize; 20620 20621 int lineHeight; 20622 20623 void* identifier; 20624 20625 Rectangle boundingBox; 20626 int[] letterXs; // FIXME: maybe i should do bounding boxes for every character 20627 20628 bool isMergeCompatible(InlineElement other) { 20629 return 20630 containingBlock is other.containingBlock && 20631 color == other.color && 20632 backgroundColor == other.backgroundColor && 20633 styles == other.styles && 20634 font == other.font && 20635 fontSize == other.fontSize && 20636 lineHeight == other.lineHeight && 20637 true; 20638 } 20639 20640 int xOfIndex(size_t index) { 20641 if(index < letterXs.length) 20642 return letterXs[index]; 20643 else 20644 return boundingBox.right; 20645 } 20646 20647 InlineElement clone() { 20648 auto ie = new InlineElement(); 20649 ie.tupleof = this.tupleof; 20650 return ie; 20651 } 20652 20653 InlineElement getPreviousInlineElement() { 20654 InlineElement prev = null; 20655 foreach(ie; this.containingBlock.parts) { 20656 if(ie is this) 20657 break; 20658 prev = ie; 20659 } 20660 if(prev is null) { 20661 BlockElement pb; 20662 BlockElement cb = this.containingBlock; 20663 moar: 20664 foreach(ie; this.containingBlock.containingLayout.blocks) { 20665 if(ie is cb) 20666 break; 20667 pb = ie; 20668 } 20669 if(pb is null) 20670 return null; 20671 if(pb.parts.length == 0) { 20672 cb = pb; 20673 goto moar; 20674 } 20675 20676 prev = pb.parts[$-1]; 20677 20678 } 20679 return prev; 20680 } 20681 20682 InlineElement getNextInlineElement() { 20683 InlineElement next = null; 20684 foreach(idx, ie; this.containingBlock.parts) { 20685 if(ie is this) { 20686 if(idx + 1 < this.containingBlock.parts.length) 20687 next = this.containingBlock.parts[idx + 1]; 20688 break; 20689 } 20690 } 20691 if(next is null) { 20692 BlockElement n; 20693 foreach(idx, ie; this.containingBlock.containingLayout.blocks) { 20694 if(ie is this.containingBlock) { 20695 if(idx + 1 < this.containingBlock.containingLayout.blocks.length) 20696 n = this.containingBlock.containingLayout.blocks[idx + 1]; 20697 break; 20698 } 20699 } 20700 if(n is null) 20701 return null; 20702 20703 if(n.parts.length) 20704 next = n.parts[0]; 20705 else {} // FIXME 20706 20707 } 20708 return next; 20709 } 20710 20711 } 20712 20713 // Block elements are used entirely for positioning inline elements, 20714 // which are the things that are actually drawn. 20715 class BlockElement { 20716 InlineElement[] parts; 20717 uint alignment; 20718 20719 int whiteSpace; // pre, pre-wrap, wrap 20720 20721 TextLayout containingLayout; 20722 20723 // inputs 20724 Point where; 20725 Size minimumSize; 20726 Size maximumSize; 20727 Rectangle[] excludedBoxes; // like if you want it to write around a floated image or something. Coordinates are relative to the bounding box. 20728 void* identifier; 20729 20730 Rectangle margin; 20731 Rectangle padding; 20732 20733 // outputs 20734 Rectangle[] boundingBoxes; 20735 } 20736 20737 struct TextIdentifyResult { 20738 InlineElement element; 20739 int offset; 20740 20741 private TextIdentifyResult fixupNewline() { 20742 if(element !is null && offset < element.text.length && element.text[offset] == '\n') { 20743 offset--; 20744 } else if(element !is null && offset == element.text.length && element.text.length > 1 && element.text[$-1] == '\n') { 20745 offset--; 20746 } 20747 return this; 20748 } 20749 } 20750 20751 class TextLayout { 20752 BlockElement[] blocks; 20753 Rectangle boundingBox_; 20754 Rectangle boundingBox() { return boundingBox_; } 20755 void boundingBox(Rectangle r) { 20756 if(r != boundingBox_) { 20757 boundingBox_ = r; 20758 layoutInvalidated = true; 20759 } 20760 } 20761 20762 Rectangle contentBoundingBox() { 20763 Rectangle r; 20764 foreach(block; blocks) 20765 foreach(ie; block.parts) { 20766 if(ie.boundingBox.right > r.right) 20767 r.right = ie.boundingBox.right; 20768 if(ie.boundingBox.bottom > r.bottom) 20769 r.bottom = ie.boundingBox.bottom; 20770 } 20771 return r; 20772 } 20773 20774 BlockElement[] getBlocks() { 20775 return blocks; 20776 } 20777 20778 InlineElement[] getTexts() { 20779 InlineElement[] elements; 20780 foreach(block; blocks) 20781 elements ~= block.parts; 20782 return elements; 20783 } 20784 20785 string getPlainText() { 20786 string text; 20787 foreach(block; blocks) 20788 foreach(part; block.parts) 20789 text ~= part.text; 20790 return text; 20791 } 20792 20793 string getHtml() { 20794 return null; // FIXME 20795 } 20796 20797 this(Rectangle boundingBox) { 20798 this.boundingBox = boundingBox; 20799 } 20800 20801 BlockElement addBlock(InlineElement after = null, Rectangle margin = Rectangle(0, 0, 0, 0), Rectangle padding = Rectangle(0, 0, 0, 0)) { 20802 auto be = new BlockElement(); 20803 be.containingLayout = this; 20804 if(after is null) 20805 blocks ~= be; 20806 else { 20807 foreach(idx, b; blocks) { 20808 if(b is after.containingBlock) { 20809 blocks = blocks[0 .. idx + 1] ~ be ~ blocks[idx + 1 .. $]; 20810 break; 20811 } 20812 } 20813 } 20814 return be; 20815 } 20816 20817 void clear() { 20818 blocks = null; 20819 selectionStart = selectionEnd = caret = Caret.init; 20820 } 20821 20822 void addText(Args...)(Args args) { 20823 if(blocks.length == 0) 20824 addBlock(); 20825 20826 InlineElement ie = new InlineElement(); 20827 foreach(idx, arg; args) { 20828 static if(is(typeof(arg) == ForegroundColor)) 20829 ie.color = arg; 20830 else static if(is(typeof(arg) == TextFormat)) { 20831 if(arg & 0x8000) // ~TextFormat.something turns it off 20832 ie.styles &= arg; 20833 else 20834 ie.styles |= arg; 20835 } else static if(is(typeof(arg) == string)) { 20836 static if(idx == 0 && args.length > 1) 20837 static assert(0, "Put styles before the string."); 20838 size_t lastLineIndex; 20839 foreach(cidx, char a; arg) { 20840 if(a == '\n') { 20841 ie.text = arg[lastLineIndex .. cidx + 1]; 20842 lastLineIndex = cidx + 1; 20843 ie.containingBlock = blocks[$-1]; 20844 blocks[$-1].parts ~= ie.clone; 20845 ie.text = null; 20846 } else { 20847 20848 } 20849 } 20850 20851 ie.text = arg[lastLineIndex .. $]; 20852 ie.containingBlock = blocks[$-1]; 20853 blocks[$-1].parts ~= ie.clone; 20854 caret = Caret(this, blocks[$-1].parts[$-1], cast(int) blocks[$-1].parts[$-1].text.length); 20855 } 20856 } 20857 20858 invalidateLayout(); 20859 } 20860 20861 void tryMerge(InlineElement into, InlineElement what) { 20862 if(!into.isMergeCompatible(what)) { 20863 return; // cannot merge, different configs 20864 } 20865 20866 // cool, can merge, bring text together... 20867 into.text ~= what.text; 20868 20869 // and remove what 20870 for(size_t a = 0; a < what.containingBlock.parts.length; a++) { 20871 if(what.containingBlock.parts[a] is what) { 20872 for(size_t i = a; i < what.containingBlock.parts.length - 1; i++) 20873 what.containingBlock.parts[i] = what.containingBlock.parts[i + 1]; 20874 what.containingBlock.parts = what.containingBlock.parts[0 .. $-1]; 20875 20876 } 20877 } 20878 20879 // FIXME: ensure no other carets have a reference to it 20880 } 20881 20882 /// exact = true means return null if no match. otherwise, get the closest one that makes sense for a mouse click. 20883 TextIdentifyResult identify(int x, int y, bool exact = false) { 20884 TextIdentifyResult inexactMatch; 20885 foreach(block; blocks) { 20886 foreach(part; block.parts) { 20887 if(x >= part.boundingBox.left && x < part.boundingBox.right && y >= part.boundingBox.top && y < part.boundingBox.bottom) { 20888 20889 // FIXME binary search 20890 int tidx; 20891 int lastX; 20892 foreach_reverse(idxo, lx; part.letterXs) { 20893 int idx = cast(int) idxo; 20894 if(lx <= x) { 20895 if(lastX && lastX - x < x - lx) 20896 tidx = idx + 1; 20897 else 20898 tidx = idx; 20899 break; 20900 } 20901 lastX = lx; 20902 } 20903 20904 return TextIdentifyResult(part, tidx).fixupNewline; 20905 } else if(!exact) { 20906 // we're not in the box, but are we on the same line? 20907 if(y >= part.boundingBox.top && y < part.boundingBox.bottom) 20908 inexactMatch = TextIdentifyResult(part, x == 0 ? 0 : cast(int) part.text.length); 20909 } 20910 } 20911 } 20912 20913 if(!exact && inexactMatch is TextIdentifyResult.init && blocks.length && blocks[$-1].parts.length) 20914 return TextIdentifyResult(blocks[$-1].parts[$-1], cast(int) blocks[$-1].parts[$-1].text.length).fixupNewline; 20915 20916 return exact ? TextIdentifyResult.init : inexactMatch.fixupNewline; 20917 } 20918 20919 void moveCaretToPixelCoordinates(int x, int y) { 20920 auto result = identify(x, y); 20921 caret.inlineElement = result.element; 20922 caret.offset = result.offset; 20923 } 20924 20925 void selectToPixelCoordinates(int x, int y) { 20926 auto result = identify(x, y); 20927 20928 if(y < caretLastDrawnY1) { 20929 // on a previous line, carat is selectionEnd 20930 selectionEnd = caret; 20931 20932 selectionStart = Caret(this, result.element, result.offset); 20933 } else if(y > caretLastDrawnY2) { 20934 // on a later line 20935 selectionStart = caret; 20936 20937 selectionEnd = Caret(this, result.element, result.offset); 20938 } else { 20939 // on the same line... 20940 if(x <= caretLastDrawnX) { 20941 selectionEnd = caret; 20942 selectionStart = Caret(this, result.element, result.offset); 20943 } else { 20944 selectionStart = caret; 20945 selectionEnd = Caret(this, result.element, result.offset); 20946 } 20947 20948 } 20949 } 20950 20951 20952 /// Call this if the inputs change. It will reflow everything 20953 void redoLayout(ScreenPainter painter) { 20954 //painter.setClipRectangle(boundingBox); 20955 auto pos = Point(boundingBox.left, boundingBox.top); 20956 20957 int lastHeight; 20958 void nl() { 20959 pos.x = boundingBox.left; 20960 pos.y += lastHeight; 20961 } 20962 foreach(block; blocks) { 20963 nl(); 20964 foreach(part; block.parts) { 20965 part.letterXs = null; 20966 20967 auto size = painter.textSize(part.text); 20968 version(Windows) 20969 if(part.text.length && part.text[$-1] == '\n') 20970 size.height /= 2; // windows counts the new line at the end, but we don't want that 20971 20972 part.boundingBox = Rectangle(pos.x, pos.y, pos.x + size.width, pos.y + size.height); 20973 20974 foreach(idx, char c; part.text) { 20975 // FIXME: unicode 20976 part.letterXs ~= painter.textSize(part.text[0 .. idx]).width + pos.x; 20977 } 20978 20979 pos.x += size.width; 20980 if(pos.x >= boundingBox.right) { 20981 pos.y += size.height; 20982 pos.x = boundingBox.left; 20983 lastHeight = 0; 20984 } else { 20985 lastHeight = size.height; 20986 } 20987 20988 if(part.text.length && part.text[$-1] == '\n') 20989 nl(); 20990 } 20991 } 20992 20993 layoutInvalidated = false; 20994 } 20995 20996 bool layoutInvalidated = true; 20997 void invalidateLayout() { 20998 layoutInvalidated = true; 20999 } 21000 21001 // FIXME: caret can remain sometimes when inserting 21002 // FIXME: inserting at the beginning once you already have something can eff it up. 21003 void drawInto(ScreenPainter painter, bool focused = false) { 21004 if(layoutInvalidated) 21005 redoLayout(painter); 21006 foreach(block; blocks) { 21007 foreach(part; block.parts) { 21008 painter.outlineColor = part.color; 21009 painter.fillColor = part.backgroundColor; 21010 21011 auto pos = part.boundingBox.upperLeft; 21012 auto size = part.boundingBox.size; 21013 21014 painter.drawText(pos, part.text); 21015 if(part.styles & TextFormat.underline) 21016 painter.drawLine(Point(pos.x, pos.y + size.height - 4), Point(pos.x + size.width, pos.y + size.height - 4)); 21017 if(part.styles & TextFormat.strikethrough) 21018 painter.drawLine(Point(pos.x, pos.y + size.height/2), Point(pos.x + size.width, pos.y + size.height/2)); 21019 } 21020 } 21021 21022 // on every redraw, I will force the caret to be 21023 // redrawn too, in order to eliminate perceived lag 21024 // when moving around with the mouse. 21025 eraseCaret(painter); 21026 21027 if(focused) { 21028 highlightSelection(painter); 21029 drawCaret(painter); 21030 } 21031 } 21032 21033 Color selectionXorColor = Color(255, 255, 127); 21034 21035 void highlightSelection(ScreenPainter painter) { 21036 if(selectionStart is selectionEnd) 21037 return; // no selection 21038 21039 if(selectionStart.inlineElement is null) return; 21040 if(selectionEnd.inlineElement is null) return; 21041 21042 assert(selectionStart.inlineElement !is null); 21043 assert(selectionEnd.inlineElement !is null); 21044 21045 painter.rasterOp = RasterOp.xor; 21046 painter.outlineColor = Color.transparent; 21047 painter.fillColor = selectionXorColor; 21048 21049 auto at = selectionStart.inlineElement; 21050 auto atOffset = selectionStart.offset; 21051 bool done; 21052 while(at) { 21053 auto box = at.boundingBox; 21054 if(atOffset < at.letterXs.length) 21055 box.left = at.letterXs[atOffset]; 21056 21057 if(at is selectionEnd.inlineElement) { 21058 if(selectionEnd.offset < at.letterXs.length) 21059 box.right = at.letterXs[selectionEnd.offset]; 21060 done = true; 21061 } 21062 21063 painter.drawRectangle(box.upperLeft, box.width, box.height); 21064 21065 if(done) 21066 break; 21067 21068 at = at.getNextInlineElement(); 21069 atOffset = 0; 21070 } 21071 } 21072 21073 int caretLastDrawnX, caretLastDrawnY1, caretLastDrawnY2; 21074 bool caretShowingOnScreen = false; 21075 void drawCaret(ScreenPainter painter) { 21076 //painter.setClipRectangle(boundingBox); 21077 int x, y1, y2; 21078 if(caret.inlineElement is null) { 21079 x = boundingBox.left; 21080 y1 = boundingBox.top + 2; 21081 y2 = boundingBox.top + painter.fontHeight; 21082 } else { 21083 x = caret.inlineElement.xOfIndex(caret.offset); 21084 y1 = caret.inlineElement.boundingBox.top + 2; 21085 y2 = caret.inlineElement.boundingBox.bottom - 2; 21086 } 21087 21088 if(caretShowingOnScreen && (x != caretLastDrawnX || y1 != caretLastDrawnY1 || y2 != caretLastDrawnY2)) 21089 eraseCaret(painter); 21090 21091 painter.pen = Pen(Color.white, 1); 21092 painter.rasterOp = RasterOp.xor; 21093 painter.drawLine( 21094 Point(x, y1), 21095 Point(x, y2) 21096 ); 21097 painter.rasterOp = RasterOp.normal; 21098 caretShowingOnScreen = !caretShowingOnScreen; 21099 21100 if(caretShowingOnScreen) { 21101 caretLastDrawnX = x; 21102 caretLastDrawnY1 = y1; 21103 caretLastDrawnY2 = y2; 21104 } 21105 } 21106 21107 Rectangle caretBoundingBox() { 21108 int x, y1, y2; 21109 if(caret.inlineElement is null) { 21110 x = boundingBox.left; 21111 y1 = boundingBox.top + 2; 21112 y2 = boundingBox.top + 16; 21113 } else { 21114 x = caret.inlineElement.xOfIndex(caret.offset); 21115 y1 = caret.inlineElement.boundingBox.top + 2; 21116 y2 = caret.inlineElement.boundingBox.bottom - 2; 21117 } 21118 21119 return Rectangle(x, y1, x + 1, y2); 21120 } 21121 21122 void eraseCaret(ScreenPainter painter) { 21123 //painter.setClipRectangle(boundingBox); 21124 if(!caretShowingOnScreen) return; 21125 painter.pen = Pen(Color.white, 1); 21126 painter.rasterOp = RasterOp.xor; 21127 painter.drawLine( 21128 Point(caretLastDrawnX, caretLastDrawnY1), 21129 Point(caretLastDrawnX, caretLastDrawnY2) 21130 ); 21131 21132 caretShowingOnScreen = false; 21133 painter.rasterOp = RasterOp.normal; 21134 } 21135 21136 /// Caret movement api 21137 /// These should give the user a logical result based on what they see on screen... 21138 /// thus they locate predominately by *pixels* not char index. (These will generally coincide with monospace fonts tho!) 21139 void moveUp() { 21140 if(caret.inlineElement is null) return; 21141 auto x = caret.inlineElement.xOfIndex(caret.offset); 21142 auto y = caret.inlineElement.boundingBox.top + 2; 21143 21144 y -= caret.inlineElement.boundingBox.bottom - caret.inlineElement.boundingBox.top; 21145 if(y < 0) 21146 return; 21147 21148 auto i = identify(x, y); 21149 21150 if(i.element) { 21151 caret.inlineElement = i.element; 21152 caret.offset = i.offset; 21153 } 21154 } 21155 void moveDown() { 21156 if(caret.inlineElement is null) return; 21157 auto x = caret.inlineElement.xOfIndex(caret.offset); 21158 auto y = caret.inlineElement.boundingBox.bottom - 2; 21159 21160 y += caret.inlineElement.boundingBox.bottom - caret.inlineElement.boundingBox.top; 21161 21162 auto i = identify(x, y); 21163 if(i.element) { 21164 caret.inlineElement = i.element; 21165 caret.offset = i.offset; 21166 } 21167 } 21168 void moveLeft() { 21169 if(caret.inlineElement is null) return; 21170 if(caret.offset) 21171 caret.offset--; 21172 else { 21173 auto p = caret.inlineElement.getPreviousInlineElement(); 21174 if(p) { 21175 caret.inlineElement = p; 21176 if(p.text.length && p.text[$-1] == '\n') 21177 caret.offset = cast(int) p.text.length - 1; 21178 else 21179 caret.offset = cast(int) p.text.length; 21180 } 21181 } 21182 } 21183 void moveRight() { 21184 if(caret.inlineElement is null) return; 21185 if(caret.offset < caret.inlineElement.text.length && caret.inlineElement.text[caret.offset] != '\n') { 21186 caret.offset++; 21187 } else { 21188 auto p = caret.inlineElement.getNextInlineElement(); 21189 if(p) { 21190 caret.inlineElement = p; 21191 caret.offset = 0; 21192 } 21193 } 21194 } 21195 void moveHome() { 21196 if(caret.inlineElement is null) return; 21197 auto x = 0; 21198 auto y = caret.inlineElement.boundingBox.top + 2; 21199 21200 auto i = identify(x, y); 21201 21202 if(i.element) { 21203 caret.inlineElement = i.element; 21204 caret.offset = i.offset; 21205 } 21206 } 21207 void moveEnd() { 21208 if(caret.inlineElement is null) return; 21209 auto x = int.max; 21210 auto y = caret.inlineElement.boundingBox.top + 2; 21211 21212 auto i = identify(x, y); 21213 21214 if(i.element) { 21215 caret.inlineElement = i.element; 21216 caret.offset = i.offset; 21217 } 21218 21219 } 21220 void movePageUp(ref Caret caret) {} 21221 void movePageDown(ref Caret caret) {} 21222 21223 void moveDocumentStart(ref Caret caret) { 21224 if(blocks.length && blocks[0].parts.length) 21225 caret = Caret(this, blocks[0].parts[0], 0); 21226 else 21227 caret = Caret.init; 21228 } 21229 21230 void moveDocumentEnd(ref Caret caret) { 21231 if(blocks.length) { 21232 auto parts = blocks[$-1].parts; 21233 if(parts.length) { 21234 caret = Caret(this, parts[$-1], cast(int) parts[$-1].text.length); 21235 } else { 21236 caret = Caret.init; 21237 } 21238 } else 21239 caret = Caret.init; 21240 } 21241 21242 void deleteSelection() { 21243 if(selectionStart is selectionEnd) 21244 return; 21245 21246 if(selectionStart.inlineElement is null) return; 21247 if(selectionEnd.inlineElement is null) return; 21248 21249 assert(selectionStart.inlineElement !is null); 21250 assert(selectionEnd.inlineElement !is null); 21251 21252 auto at = selectionStart.inlineElement; 21253 21254 if(selectionEnd.inlineElement is at) { 21255 // same element, need to chop out 21256 at.text = at.text[0 .. selectionStart.offset] ~ at.text[selectionEnd.offset .. $]; 21257 at.letterXs = at.letterXs[0 .. selectionStart.offset] ~ at.letterXs[selectionEnd.offset .. $]; 21258 selectionEnd.offset -= selectionEnd.offset - selectionStart.offset; 21259 } else { 21260 // different elements, we can do it with slicing 21261 at.text = at.text[0 .. selectionStart.offset]; 21262 if(selectionStart.offset < at.letterXs.length) 21263 at.letterXs = at.letterXs[0 .. selectionStart.offset]; 21264 21265 at = at.getNextInlineElement(); 21266 21267 while(at) { 21268 if(at is selectionEnd.inlineElement) { 21269 at.text = at.text[selectionEnd.offset .. $]; 21270 if(selectionEnd.offset < at.letterXs.length) 21271 at.letterXs = at.letterXs[selectionEnd.offset .. $]; 21272 selectionEnd.offset = 0; 21273 break; 21274 } else { 21275 auto cfd = at; 21276 cfd.text = null; // delete the whole thing 21277 21278 at = at.getNextInlineElement(); 21279 21280 if(cfd.text.length == 0) { 21281 // and remove cfd 21282 for(size_t a = 0; a < cfd.containingBlock.parts.length; a++) { 21283 if(cfd.containingBlock.parts[a] is cfd) { 21284 for(size_t i = a; i < cfd.containingBlock.parts.length - 1; i++) 21285 cfd.containingBlock.parts[i] = cfd.containingBlock.parts[i + 1]; 21286 cfd.containingBlock.parts = cfd.containingBlock.parts[0 .. $-1]; 21287 21288 } 21289 } 21290 } 21291 } 21292 } 21293 } 21294 21295 caret = selectionEnd; 21296 selectNone(); 21297 21298 invalidateLayout(); 21299 21300 } 21301 21302 /// Plain text editing api. These work at the current caret inside the selected inline element. 21303 void insert(in char[] text) { 21304 foreach(dchar ch; text) 21305 insert(ch); 21306 } 21307 /// ditto 21308 void insert(dchar ch) { 21309 21310 bool selectionDeleted = false; 21311 if(selectionStart !is selectionEnd) { 21312 deleteSelection(); 21313 selectionDeleted = true; 21314 } 21315 21316 if(ch == 127) { 21317 delete_(); 21318 return; 21319 } 21320 if(ch == 8) { 21321 if(!selectionDeleted) 21322 backspace(); 21323 return; 21324 } 21325 21326 invalidateLayout(); 21327 21328 if(ch == 13) ch = 10; 21329 auto e = caret.inlineElement; 21330 if(e is null) { 21331 addText("" ~ cast(char) ch) ; // FIXME 21332 return; 21333 } 21334 21335 if(caret.offset == e.text.length) { 21336 e.text ~= cast(char) ch; // FIXME 21337 caret.offset++; 21338 if(ch == 10) { 21339 auto c = caret.inlineElement.clone; 21340 c.text = null; 21341 c.letterXs = null; 21342 insertPartAfter(c,e); 21343 caret = Caret(this, c, 0); 21344 } 21345 } else { 21346 // FIXME cast char sucks 21347 if(ch == 10) { 21348 auto c = caret.inlineElement.clone; 21349 c.text = e.text[caret.offset .. $]; 21350 if(caret.offset < c.letterXs.length) 21351 c.letterXs = e.letterXs[caret.offset .. $]; // FIXME boundingBox 21352 e.text = e.text[0 .. caret.offset] ~ cast(char) ch; 21353 if(caret.offset <= e.letterXs.length) { 21354 e.letterXs = e.letterXs[0 .. caret.offset] ~ 0; // FIXME bounding box 21355 } 21356 insertPartAfter(c,e); 21357 caret = Caret(this, c, 0); 21358 } else { 21359 e.text = e.text[0 .. caret.offset] ~ cast(char) ch ~ e.text[caret.offset .. $]; 21360 caret.offset++; 21361 } 21362 } 21363 } 21364 21365 void insertPartAfter(InlineElement what, InlineElement where) { 21366 foreach(idx, p; where.containingBlock.parts) { 21367 if(p is where) { 21368 if(idx + 1 == where.containingBlock.parts.length) 21369 where.containingBlock.parts ~= what; 21370 else 21371 where.containingBlock.parts = where.containingBlock.parts[0 .. idx + 1] ~ what ~ where.containingBlock.parts[idx + 1 .. $]; 21372 return; 21373 } 21374 } 21375 } 21376 21377 void cleanupStructures() { 21378 for(size_t i = 0; i < blocks.length; i++) { 21379 auto block = blocks[i]; 21380 for(size_t a = 0; a < block.parts.length; a++) { 21381 auto part = block.parts[a]; 21382 if(part.text.length == 0) { 21383 for(size_t b = a; b < block.parts.length - 1; b++) 21384 block.parts[b] = block.parts[b+1]; 21385 block.parts = block.parts[0 .. $-1]; 21386 } 21387 } 21388 if(block.parts.length == 0) { 21389 for(size_t a = i; a < blocks.length - 1; a++) 21390 blocks[a] = blocks[a+1]; 21391 blocks = blocks[0 .. $-1]; 21392 } 21393 } 21394 } 21395 21396 void backspace() { 21397 try_again: 21398 auto e = caret.inlineElement; 21399 if(e is null) 21400 return; 21401 if(caret.offset == 0) { 21402 auto prev = e.getPreviousInlineElement(); 21403 if(prev is null) 21404 return; 21405 auto newOffset = cast(int) prev.text.length; 21406 tryMerge(prev, e); 21407 caret.inlineElement = prev; 21408 caret.offset = prev is null ? 0 : newOffset; 21409 21410 goto try_again; 21411 } else if(caret.offset == e.text.length) { 21412 e.text = e.text[0 .. $-1]; 21413 caret.offset--; 21414 } else { 21415 e.text = e.text[0 .. caret.offset - 1] ~ e.text[caret.offset .. $]; 21416 caret.offset--; 21417 } 21418 //cleanupStructures(); 21419 21420 invalidateLayout(); 21421 } 21422 void delete_() { 21423 if(selectionStart !is selectionEnd) 21424 deleteSelection(); 21425 else { 21426 auto before = caret; 21427 moveRight(); 21428 if(caret != before) { 21429 backspace(); 21430 } 21431 } 21432 21433 invalidateLayout(); 21434 } 21435 void overstrike() {} 21436 21437 /// Selection API. See also: caret movement. 21438 void selectAll() { 21439 moveDocumentStart(selectionStart); 21440 moveDocumentEnd(selectionEnd); 21441 } 21442 bool selectNone() { 21443 if(selectionStart != selectionEnd) { 21444 selectionStart = selectionEnd = Caret.init; 21445 return true; 21446 } 21447 return false; 21448 } 21449 21450 /// Rich text editing api. These allow you to manipulate the meta data of the current element and add new elements. 21451 /// They will modify the current selection if there is one and will splice one in if needed. 21452 void changeAttributes() {} 21453 21454 21455 /// Text search api. They manipulate the selection and/or caret. 21456 void findText(string text) {} 21457 void findIndex(size_t textIndex) {} 21458 21459 // sample event handlers 21460 21461 void handleEvent(KeyEvent event) { 21462 //if(event.type == KeyEvent.Type.KeyPressed) { 21463 21464 //} 21465 } 21466 21467 void handleEvent(dchar ch) { 21468 21469 } 21470 21471 void handleEvent(MouseEvent event) { 21472 21473 } 21474 21475 bool contentEditable; // can it be edited? 21476 bool contentCaretable; // is there a caret/cursor that moves around in there? 21477 bool contentSelectable; // selectable? 21478 21479 Caret caret; 21480 Caret selectionStart; 21481 Caret selectionEnd; 21482 21483 bool insertMode; 21484 } 21485 21486 struct Caret { 21487 TextLayout layout; 21488 InlineElement inlineElement; 21489 int offset; 21490 } 21491 21492 enum TextFormat : ushort { 21493 // decorations 21494 underline = 1, 21495 strikethrough = 2, 21496 21497 // font selectors 21498 21499 bold = 0x4000 | 1, // weight 700 21500 light = 0x4000 | 2, // weight 300 21501 veryBoldOrLight = 0x4000 | 4, // weight 100 with light, weight 900 with bold 21502 // bold | light is really invalid but should give weight 500 21503 // veryBoldOrLight without one of the others should just give the default for the font; it should be ignored. 21504 21505 italic = 0x4000 | 8, 21506 smallcaps = 0x4000 | 16, 21507 } 21508 21509 void* findFont(string family, int weight, TextFormat formats) { 21510 return null; 21511 } 21512 21513 } 21514 21515 /++ 21516 $(PITFALL This is not yet stable and may break in future versions without notice.) 21517 21518 History: 21519 Added February 19, 2021 21520 +/ 21521 /// Group: drag_and_drop 21522 interface DropHandler { 21523 /++ 21524 Called when the drag enters the handler's area. 21525 +/ 21526 DragAndDropAction dragEnter(DropPackage*); 21527 /++ 21528 Called when the drag leaves the handler's area or is 21529 cancelled. You should free your resources when this is called. 21530 +/ 21531 void dragLeave(); 21532 /++ 21533 Called continually as the drag moves over the handler's area. 21534 21535 Returns: feedback to the dragger 21536 +/ 21537 DropParameters dragOver(Point pt); 21538 /++ 21539 The user dropped the data and you should process it now. You can 21540 access the data through the given [DropPackage]. 21541 +/ 21542 void drop(scope DropPackage*); 21543 /++ 21544 Called when the drop is complete. You should free whatever temporary 21545 resources you were using. It is often reasonable to simply forward 21546 this call to [dragLeave]. 21547 +/ 21548 void finish(); 21549 21550 /++ 21551 Parameters returned by [DropHandler.drop]. 21552 +/ 21553 static struct DropParameters { 21554 /++ 21555 Acceptable action over this area. 21556 +/ 21557 DragAndDropAction action; 21558 /++ 21559 Rectangle, in client coordinates, where the dragger can expect the same result during this drag session and thus need not ask again. 21560 21561 If you leave this as Rectangle.init, the dragger will continue to ask and this can waste resources. 21562 +/ 21563 Rectangle consistentWithin; 21564 } 21565 } 21566 21567 /++ 21568 History: 21569 Added February 19, 2021 21570 +/ 21571 /// Group: drag_and_drop 21572 enum DragAndDropAction { 21573 none = 0, 21574 copy, 21575 move, 21576 link, 21577 ask, 21578 custom 21579 } 21580 21581 /++ 21582 An opaque structure representing dropped data. It contains 21583 private, platform-specific data that your `drop` function 21584 should simply forward. 21585 21586 $(PITFALL This is not yet stable and may break in future versions without notice.) 21587 21588 History: 21589 Added February 19, 2021 21590 +/ 21591 /// Group: drag_and_drop 21592 struct DropPackage { 21593 /++ 21594 Lists the available formats as magic numbers. You should compare these 21595 against looked-up formats (see [DraggableData.getFormatId]) you know you support and can 21596 understand the passed data. 21597 +/ 21598 DraggableData.FormatId[] availableFormats() { 21599 version(X11) { 21600 return xFormats; 21601 } else version(Windows) { 21602 if(pDataObj is null) 21603 return null; 21604 21605 typeof(return) ret; 21606 21607 IEnumFORMATETC ef; 21608 if(pDataObj.EnumFormatEtc(DATADIR.DATADIR_GET, &ef) == S_OK) { 21609 FORMATETC fmt; 21610 ULONG fetched; 21611 while(ef.Next(1, &fmt, &fetched) == S_OK) { 21612 if(fetched == 0) 21613 break; 21614 21615 if(fmt.lindex != -1) 21616 continue; 21617 if(fmt.dwAspect != DVASPECT.DVASPECT_CONTENT) 21618 continue; 21619 if(!(fmt.tymed & TYMED.TYMED_HGLOBAL)) 21620 continue; 21621 21622 ret ~= fmt.cfFormat; 21623 } 21624 } 21625 21626 return ret; 21627 } else throw new NotYetImplementedException(); 21628 } 21629 21630 /++ 21631 Gets data from the drop and optionally accepts it. 21632 21633 Returns: 21634 void because the data is fed asynchronously through the `dg` parameter. 21635 21636 Params: 21637 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. 21638 21639 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. 21640 21641 Calling `getData` again after accepting a drop is not permitted. 21642 21643 format = the format you want, from [availableFormats]. Use [DraggableData.getFormatId] to convert from a MIME string or well-known standard format. 21644 21645 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. 21646 21647 Throws: 21648 if `format` was not compatible with the [availableFormats] or if the drop has already been accepted. 21649 21650 History: 21651 Included in first release of [DropPackage]. 21652 +/ 21653 void getData(DragAndDropAction acceptedAction, DraggableData.FormatId format, void delegate(scope ubyte[] data) dg) { 21654 version(X11) { 21655 21656 auto display = XDisplayConnection.get(); 21657 auto selectionAtom = GetAtom!"XdndSelection"(display); 21658 auto best = format; 21659 21660 static class X11GetSelectionHandler_Drop : X11GetSelectionHandler { 21661 21662 XDisplay* display; 21663 Atom selectionAtom; 21664 DraggableData.FormatId best; 21665 DraggableData.FormatId format; 21666 void delegate(scope ubyte[] data) dg; 21667 DragAndDropAction acceptedAction; 21668 Window sourceWindow; 21669 SimpleWindow win; 21670 this(XDisplay* display, SimpleWindow win, Window sourceWindow, DraggableData.FormatId format, Atom selectionAtom, DraggableData.FormatId best, void delegate(scope ubyte[] data) dg, DragAndDropAction acceptedAction) { 21671 this.display = display; 21672 this.win = win; 21673 this.sourceWindow = sourceWindow; 21674 this.format = format; 21675 this.selectionAtom = selectionAtom; 21676 this.best = best; 21677 this.dg = dg; 21678 this.acceptedAction = acceptedAction; 21679 } 21680 21681 21682 mixin X11GetSelectionHandler_Basics; 21683 21684 void handleData(Atom target, in ubyte[] data) { 21685 //if(target == GetAtom!"UTF8_STRING"(XDisplayConnection.get) || target == XA_STRING || target == GetAtom!"text/plain"(XDisplayConnection.get)) 21686 21687 dg(cast(ubyte[]) data); 21688 21689 if(acceptedAction != DragAndDropAction.none) { 21690 auto display = XDisplayConnection.get; 21691 21692 XClientMessageEvent xclient; 21693 21694 xclient.type = EventType.ClientMessage; 21695 xclient.window = sourceWindow; 21696 xclient.message_type = GetAtom!"XdndFinished"(display); 21697 xclient.format = 32; 21698 xclient.data.l[0] = win.impl.window; 21699 xclient.data.l[1] = 1; // drop successful 21700 xclient.data.l[2] = dndActionAtom(display, acceptedAction); 21701 21702 XSendEvent( 21703 display, 21704 sourceWindow, 21705 false, 21706 EventMask.NoEventMask, 21707 cast(XEvent*) &xclient 21708 ); 21709 21710 XFlush(display); 21711 } 21712 } 21713 21714 Atom findBestFormat(Atom[] answer) { 21715 Atom best = None; 21716 foreach(option; answer) { 21717 if(option == format) { 21718 best = option; 21719 break; 21720 } 21721 /* 21722 if(option == GetAtom!"UTF8_STRING"(XDisplayConnection.get)) { 21723 best = option; 21724 break; 21725 } else if(option == XA_STRING) { 21726 best = option; 21727 } else if(option == GetAtom!"text/plain"(XDisplayConnection.get)) { 21728 best = option; 21729 } 21730 */ 21731 } 21732 return best; 21733 } 21734 } 21735 21736 win.impl.getSelectionHandlers[selectionAtom] = new X11GetSelectionHandler_Drop(display, win, sourceWindow, format, selectionAtom, best, dg, acceptedAction); 21737 21738 XConvertSelection(display, selectionAtom, best, GetAtom!("SDD_DATA", true)(display), win.impl.window, dataTimestamp); 21739 21740 } else version(Windows) { 21741 21742 // clean up like DragLeave 21743 // pass effect back up 21744 21745 FORMATETC t; 21746 assert(format >= 0 && format <= ushort.max); 21747 t.cfFormat = cast(ushort) format; 21748 t.lindex = -1; 21749 t.dwAspect = DVASPECT.DVASPECT_CONTENT; 21750 t.tymed = TYMED.TYMED_HGLOBAL; 21751 21752 STGMEDIUM m; 21753 21754 if(pDataObj.GetData(&t, &m) != S_OK) { 21755 // fail 21756 } else { 21757 // succeed, take the data and clean up 21758 21759 // FIXME: ensure it is legit HGLOBAL 21760 auto handle = m.hGlobal; 21761 21762 if(handle) { 21763 auto sz = GlobalSize(handle); 21764 if(auto ptr = cast(ubyte*) GlobalLock(handle)) { 21765 scope(exit) GlobalUnlock(handle); 21766 scope(exit) GlobalFree(handle); 21767 21768 auto data = ptr[0 .. sz]; 21769 21770 dg(data); 21771 } 21772 } 21773 } 21774 } 21775 } 21776 21777 private: 21778 21779 version(X11) { 21780 SimpleWindow win; 21781 Window sourceWindow; 21782 Time dataTimestamp; 21783 21784 Atom[] xFormats; 21785 } 21786 version(Windows) { 21787 IDataObject pDataObj; 21788 } 21789 } 21790 21791 /++ 21792 A generic helper base class for making a drop handler with a preference list of custom types. 21793 This is the base for [TextDropHandler] and [FilesDropHandler] and you can use it for your own 21794 droppers too. 21795 21796 It assumes the whole window it used, but you can subclass to change that. 21797 21798 $(PITFALL This is not yet stable and may break in future versions without notice.) 21799 21800 History: 21801 Added February 19, 2021 21802 +/ 21803 /// Group: drag_and_drop 21804 class GenericDropHandlerBase : DropHandler { 21805 // no fancy state here so no need to do anything here 21806 void finish() { } 21807 void dragLeave() { } 21808 21809 private DragAndDropAction acceptedAction; 21810 private DraggableData.FormatId acceptedFormat; 21811 private void delegate(scope ubyte[]) acceptedHandler; 21812 21813 struct FormatHandler { 21814 DraggableData.FormatId format; 21815 void delegate(scope ubyte[]) handler; 21816 } 21817 21818 protected abstract FormatHandler[] formatHandlers(); 21819 21820 DragAndDropAction dragEnter(DropPackage* pkg) { 21821 debug(sdpy_dnd) { foreach(fmt; pkg.availableFormats()) writeln(fmt, " ", DraggableData.getFormatName(fmt)); } 21822 foreach(fmt; formatHandlers()) 21823 foreach(f; pkg.availableFormats()) 21824 if(f == fmt.format) { 21825 acceptedFormat = f; 21826 acceptedHandler = fmt.handler; 21827 return acceptedAction = DragAndDropAction.copy; 21828 } 21829 return acceptedAction = DragAndDropAction.none; 21830 } 21831 DropParameters dragOver(Point pt) { 21832 return DropParameters(acceptedAction); 21833 } 21834 21835 void drop(scope DropPackage* dropPackage) { 21836 if(!acceptedFormat || acceptedHandler is null) { 21837 debug(sdpy_dnd) { writeln("drop called w/ handler ", acceptedHandler, " and format ", acceptedFormat); } 21838 return; // prolly shouldn't happen anyway... 21839 } 21840 21841 dropPackage.getData(acceptedAction, acceptedFormat, acceptedHandler); 21842 } 21843 } 21844 21845 /++ 21846 A simple handler for making your window accept drops of plain text. 21847 21848 $(PITFALL This is not yet stable and may break in future versions without notice.) 21849 21850 History: 21851 Added February 22, 2021 21852 +/ 21853 /// Group: drag_and_drop 21854 class TextDropHandler : GenericDropHandlerBase { 21855 private void delegate(in char[] text) dg; 21856 21857 /++ 21858 21859 +/ 21860 this(void delegate(in char[] text) dg) { 21861 this.dg = dg; 21862 } 21863 21864 protected override FormatHandler[] formatHandlers() { 21865 version(X11) 21866 return [ 21867 FormatHandler(GetAtom!"UTF8_STRING"(XDisplayConnection.get), &translator), 21868 FormatHandler(GetAtom!"text/plain;charset=utf-8"(XDisplayConnection.get), &translator), 21869 ]; 21870 else version(Windows) 21871 return [ 21872 FormatHandler(CF_UNICODETEXT, &translator), 21873 ]; 21874 else throw new NotYetImplementedException(); 21875 } 21876 21877 private void translator(scope ubyte[] data) { 21878 version(X11) 21879 dg(cast(char[]) data); 21880 else version(Windows) 21881 dg(makeUtf8StringFromWindowsString(cast(wchar[]) data)); 21882 } 21883 } 21884 21885 /++ 21886 A simple handler for making your window accept drops of files, issued to you as file names. 21887 21888 $(PITFALL This is not yet stable and may break in future versions without notice.) 21889 21890 History: 21891 Added February 22, 2021 21892 +/ 21893 /// Group: drag_and_drop 21894 21895 class FilesDropHandler : GenericDropHandlerBase { 21896 private void delegate(in char[][]) dg; 21897 21898 /++ 21899 21900 +/ 21901 this(void delegate(in char[][] fileNames) dg) { 21902 this.dg = dg; 21903 } 21904 21905 protected override FormatHandler[] formatHandlers() { 21906 version(X11) 21907 return [ 21908 FormatHandler(GetAtom!"text/uri-list"(XDisplayConnection.get), &translator), 21909 ]; 21910 else version(Windows) 21911 return [ 21912 FormatHandler(CF_HDROP, &translator), 21913 ]; 21914 else throw new NotYetImplementedException(); 21915 } 21916 21917 private void translator(scope ubyte[] data) @system { 21918 version(X11) { 21919 char[] listString = cast(char[]) data; 21920 char[][16] buffer; 21921 int count; 21922 char[][] result = buffer[]; 21923 21924 void commit(char[] s) { 21925 if(count == result.length) 21926 result.length += 16; 21927 if(s.length > 7 && s[0 ..7] == "file://") 21928 s = s[7 .. $]; // FIXME: also may need to trim out the host and do some entity decoding 21929 result[count++] = s; 21930 } 21931 21932 size_t last; 21933 foreach(idx, char c; listString) { 21934 if(c == '\n') { 21935 commit(listString[last .. idx - 1]); // a \r 21936 last = idx + 1; // a \n 21937 } 21938 } 21939 21940 if(last < listString.length) { 21941 commit(listString[last .. $]); 21942 } 21943 21944 // FIXME: they are uris now, should I translate it to local file names? 21945 // of course the host name is supposed to be there cuz of X rokking... 21946 21947 dg(result[0 .. count]); 21948 } else version(Windows) { 21949 21950 static struct DROPFILES { 21951 DWORD pFiles; 21952 POINT pt; 21953 BOOL fNC; 21954 BOOL fWide; 21955 } 21956 21957 21958 const(char)[][16] buffer; 21959 int count; 21960 const(char)[][] result = buffer[]; 21961 size_t last; 21962 21963 void commitA(in char[] stuff) { 21964 if(count == result.length) 21965 result.length += 16; 21966 result[count++] = stuff; 21967 } 21968 21969 void commitW(in wchar[] stuff) { 21970 commitA(makeUtf8StringFromWindowsString(stuff)); 21971 } 21972 21973 void magic(T)(T chars) { 21974 size_t idx; 21975 while(chars[idx]) { 21976 last = idx; 21977 while(chars[idx]) { 21978 idx++; 21979 } 21980 static if(is(T == char*)) 21981 commitA(chars[last .. idx]); 21982 else 21983 commitW(chars[last .. idx]); 21984 idx++; 21985 } 21986 } 21987 21988 auto df = cast(DROPFILES*) data.ptr; 21989 if(df.fWide) { 21990 wchar* chars = cast(wchar*) (data.ptr + df.pFiles); 21991 magic(chars); 21992 } else { 21993 char* chars = cast(char*) (data.ptr + df.pFiles); 21994 magic(chars); 21995 } 21996 dg(result[0 .. count]); 21997 } 21998 else throw new NotYetImplementedException(); 21999 } 22000 } 22001 22002 /++ 22003 Interface to describe data being dragged. See also [draggable] helper function. 22004 22005 $(PITFALL This is not yet stable and may break in future versions without notice.) 22006 22007 History: 22008 Added February 19, 2021 22009 +/ 22010 interface DraggableData { 22011 version(X11) 22012 alias FormatId = Atom; 22013 else 22014 alias FormatId = uint; 22015 /++ 22016 Gets the platform-specific FormatId associated with the given named format. 22017 22018 This may be a MIME type, but may also be other various strings defined by the 22019 programs you want to interoperate with. 22020 22021 FIXME: sdpy needs to offer data adapter things that look for compatible formats 22022 and convert it to some particular type for you. 22023 +/ 22024 static FormatId getFormatId(string name)() { 22025 version(X11) 22026 return GetAtom!name(XDisplayConnection.get); 22027 else version(Windows) { 22028 static UINT cache; 22029 if(!cache) 22030 cache = RegisterClipboardFormatA(name); 22031 return cache; 22032 } else 22033 throw new NotYetImplementedException(); 22034 } 22035 22036 /++ 22037 Looks up a string to represent the name for the given format, if there is one. 22038 22039 You should avoid using this function because it is slow. It is provided more for 22040 debugging than for primary use. 22041 +/ 22042 static string getFormatName(FormatId format) { 22043 version(X11) { 22044 if(format == 0) 22045 return "None"; 22046 else 22047 return getAtomName(format, XDisplayConnection.get); 22048 } else version(Windows) { 22049 switch(format) { 22050 case CF_UNICODETEXT: return "CF_UNICODETEXT"; 22051 case CF_DIBV5: return "CF_DIBV5"; 22052 case CF_RIFF: return "CF_RIFF"; 22053 case CF_WAVE: return "CF_WAVE"; 22054 case CF_HDROP: return "CF_HDROP"; 22055 default: 22056 char[1024] name; 22057 auto count = GetClipboardFormatNameA(format, name.ptr, name.length); 22058 return name[0 .. count].idup; 22059 } 22060 } else throw new NotYetImplementedException(); 22061 } 22062 22063 FormatId[] availableFormats(); 22064 // Return the slice of data you filled, empty slice if done. 22065 // this is to support the incremental thing 22066 ubyte[] getData(FormatId format, return scope ubyte[] data); 22067 22068 size_t dataLength(FormatId format); 22069 } 22070 22071 /++ 22072 $(PITFALL This is not yet stable and may break in future versions without notice.) 22073 22074 History: 22075 Added February 19, 2021 22076 +/ 22077 DraggableData draggable(string s) { 22078 version(X11) 22079 return new class X11SetSelectionHandler_Text, DraggableData { 22080 this() { 22081 super(s); 22082 } 22083 22084 override FormatId[] availableFormats() { 22085 return X11SetSelectionHandler_Text.availableFormats(); 22086 } 22087 22088 override ubyte[] getData(FormatId format, return scope ubyte[] data) { 22089 return X11SetSelectionHandler_Text.getData(format, data); 22090 } 22091 22092 size_t dataLength(FormatId format) { 22093 return s.length; 22094 } 22095 }; 22096 else version(Windows) 22097 return new class DraggableData { 22098 FormatId[] availableFormats() { 22099 return [CF_UNICODETEXT]; 22100 } 22101 22102 ubyte[] getData(FormatId format, return scope ubyte[] data) { 22103 return cast(ubyte[]) makeWindowsString(s, cast(wchar[]) data, WindowsStringConversionFlags.convertNewLines | WindowsStringConversionFlags.zeroTerminate); 22104 } 22105 22106 size_t dataLength(FormatId format) { 22107 return sizeOfConvertedWstring(s, WindowsStringConversionFlags.convertNewLines | WindowsStringConversionFlags.zeroTerminate) * wchar.sizeof; 22108 } 22109 }; 22110 else 22111 throw new NotYetImplementedException(); 22112 } 22113 22114 /++ 22115 $(PITFALL This is not yet stable and may break in future versions without notice.) 22116 22117 History: 22118 Added February 19, 2021 22119 +/ 22120 /// Group: drag_and_drop 22121 int doDragDrop(SimpleWindow window, DraggableData handler, DragAndDropAction action = DragAndDropAction.copy) 22122 in { 22123 assert(window !is null); 22124 assert(handler !is null); 22125 } 22126 do 22127 { 22128 version(X11) { 22129 auto sh = cast(X11SetSelectionHandler) handler; 22130 if(sh is null) { 22131 // gotta make my own adapter. 22132 sh = new class X11SetSelectionHandler { 22133 mixin X11SetSelectionHandler_Basics; 22134 22135 Atom[] availableFormats() { return handler.availableFormats(); } 22136 ubyte[] getData(Atom format, return scope ubyte[] data) { 22137 return handler.getData(format, data); 22138 } 22139 22140 // since the drop selection is only ever used once it isn't important 22141 // to reset it. 22142 void done() {} 22143 }; 22144 } 22145 return doDragDropX11(window, sh, action); 22146 } else version(Windows) { 22147 return doDragDropWindows(window, handler, action); 22148 } else throw new NotYetImplementedException(); 22149 } 22150 22151 version(Windows) 22152 private int doDragDropWindows(SimpleWindow window, DraggableData handler, DragAndDropAction action = DragAndDropAction.copy) { 22153 IDataObject obj = new class IDataObject { 22154 ULONG refCount; 22155 ULONG AddRef() { 22156 return ++refCount; 22157 } 22158 ULONG Release() { 22159 return --refCount; 22160 } 22161 HRESULT QueryInterface(const (IID)*riid, LPVOID *ppv) { 22162 if (IID_IUnknown == *riid) { 22163 *ppv = cast(void*) cast(IUnknown) this; 22164 } 22165 else if (IID_IDataObject == *riid) { 22166 *ppv = cast(void*) cast(IDataObject) this; 22167 } 22168 else { 22169 *ppv = null; 22170 return E_NOINTERFACE; 22171 } 22172 22173 AddRef(); 22174 return NOERROR; 22175 } 22176 22177 HRESULT DAdvise(FORMATETC* pformatetc, DWORD advf, IAdviseSink pAdvSink, DWORD* pdwConnection) { 22178 // writeln("Advise"); 22179 return E_NOTIMPL; 22180 } 22181 HRESULT DUnadvise(DWORD dwConnection) { 22182 return E_NOTIMPL; 22183 } 22184 HRESULT EnumDAdvise(IEnumSTATDATA* ppenumAdvise) { 22185 // writeln("EnumDAdvise"); 22186 return OLE_E_ADVISENOTSUPPORTED; 22187 } 22188 // tell what formats it supports 22189 22190 FORMATETC[] types; 22191 this() { 22192 FORMATETC t; 22193 foreach(ty; handler.availableFormats()) { 22194 assert(ty <= ushort.max && ty >= 0); 22195 t.cfFormat = cast(ushort) ty; 22196 t.lindex = -1; 22197 t.dwAspect = DVASPECT.DVASPECT_CONTENT; 22198 t.tymed = TYMED.TYMED_HGLOBAL; 22199 } 22200 types ~= t; 22201 } 22202 HRESULT EnumFormatEtc(DWORD dwDirection, IEnumFORMATETC* ppenumFormatEtc) { 22203 if(dwDirection == DATADIR.DATADIR_GET) { 22204 *ppenumFormatEtc = new class IEnumFORMATETC { 22205 ULONG refCount; 22206 ULONG AddRef() { 22207 return ++refCount; 22208 } 22209 ULONG Release() { 22210 return --refCount; 22211 } 22212 HRESULT QueryInterface(const (IID)*riid, LPVOID *ppv) { 22213 if (IID_IUnknown == *riid) { 22214 *ppv = cast(void*) cast(IUnknown) this; 22215 } 22216 else if (IID_IEnumFORMATETC == *riid) { 22217 *ppv = cast(void*) cast(IEnumFORMATETC) this; 22218 } 22219 else { 22220 *ppv = null; 22221 return E_NOINTERFACE; 22222 } 22223 22224 AddRef(); 22225 return NOERROR; 22226 } 22227 22228 22229 int pos; 22230 this() { 22231 pos = 0; 22232 } 22233 22234 HRESULT Clone(IEnumFORMATETC* ppenum) { 22235 // writeln("clone"); 22236 return E_NOTIMPL; // FIXME 22237 } 22238 22239 // Caller is responsible for freeing memory 22240 HRESULT Next(ULONG celt, FORMATETC* rgelt, ULONG* pceltFetched) { 22241 // fetched may be null if celt is one 22242 if(celt != 1) 22243 return E_NOTIMPL; // FIXME 22244 22245 if(celt + pos > types.length) 22246 return S_FALSE; 22247 22248 *rgelt = types[pos++]; 22249 22250 if(pceltFetched !is null) 22251 *pceltFetched = 1; 22252 22253 // writeln("ok celt ", celt); 22254 return S_OK; 22255 } 22256 22257 HRESULT Reset() { 22258 pos = 0; 22259 return S_OK; 22260 } 22261 22262 HRESULT Skip(ULONG celt) { 22263 if(celt + pos <= types.length) { 22264 pos += celt; 22265 return S_OK; 22266 } 22267 return S_FALSE; 22268 } 22269 }; 22270 22271 return S_OK; 22272 } else 22273 return E_NOTIMPL; 22274 } 22275 // given a format, return the format you'd prefer to use cuz it is identical 22276 HRESULT GetCanonicalFormatEtc(FORMATETC* pformatectIn, FORMATETC* pformatetcOut) { 22277 // FIXME: prolly could be better but meh 22278 // writeln("gcf: ", *pformatectIn); 22279 *pformatetcOut = *pformatectIn; 22280 return S_OK; 22281 } 22282 HRESULT GetData(FORMATETC* pformatetcIn, STGMEDIUM* pmedium) { 22283 foreach(ty; types) { 22284 if(ty == *pformatetcIn) { 22285 auto format = ty.cfFormat; 22286 // writeln("A: ", *pformatetcIn, "\nB: ", ty); 22287 STGMEDIUM medium; 22288 medium.tymed = TYMED.TYMED_HGLOBAL; 22289 22290 auto sz = handler.dataLength(format); 22291 auto handle = GlobalAlloc(GMEM_MOVEABLE, sz); 22292 if(handle is null) throw new WindowsApiException("GlobalAlloc", GetLastError()); 22293 if(auto data = cast(wchar*) GlobalLock(handle)) { 22294 auto slice = data[0 .. sz]; 22295 scope(exit) 22296 GlobalUnlock(handle); 22297 22298 handler.getData(format, cast(ubyte[]) slice[]); 22299 } 22300 22301 22302 medium.hGlobal = handle; // FIXME 22303 *pmedium = medium; 22304 return S_OK; 22305 } 22306 } 22307 return DV_E_FORMATETC; 22308 } 22309 HRESULT GetDataHere(FORMATETC* pformatetcIn, STGMEDIUM* pmedium) { 22310 // writeln("GDH: ", *pformatetcIn); 22311 return E_NOTIMPL; // FIXME 22312 } 22313 HRESULT QueryGetData(FORMATETC* pformatetc) { 22314 auto search = *pformatetc; 22315 search.tymed &= TYMED.TYMED_HGLOBAL; 22316 foreach(ty; types) 22317 if(ty == search) { 22318 // writeln("QueryGetData ", search, " ", types[0]); 22319 return S_OK; 22320 } 22321 if(pformatetc.cfFormat==CF_UNICODETEXT) { 22322 //writeln("QueryGetData FALSE ", search, " ", types[0]); 22323 } 22324 return S_FALSE; 22325 } 22326 HRESULT SetData(FORMATETC* pformatetc, STGMEDIUM* pmedium, BOOL fRelease) { 22327 // writeln("SetData: "); 22328 return E_NOTIMPL; 22329 } 22330 }; 22331 22332 22333 IDropSource src = new class IDropSource { 22334 ULONG refCount; 22335 ULONG AddRef() { 22336 return ++refCount; 22337 } 22338 ULONG Release() { 22339 return --refCount; 22340 } 22341 HRESULT QueryInterface(const (IID)*riid, LPVOID *ppv) { 22342 if (IID_IUnknown == *riid) { 22343 *ppv = cast(void*) cast(IUnknown) this; 22344 } 22345 else if (IID_IDropSource == *riid) { 22346 *ppv = cast(void*) cast(IDropSource) this; 22347 } 22348 else { 22349 *ppv = null; 22350 return E_NOINTERFACE; 22351 } 22352 22353 AddRef(); 22354 return NOERROR; 22355 } 22356 22357 int QueryContinueDrag(int fEscapePressed, uint grfKeyState) { 22358 if(fEscapePressed) 22359 return DRAGDROP_S_CANCEL; 22360 if(!(grfKeyState & MK_LBUTTON)) 22361 return DRAGDROP_S_DROP; 22362 return S_OK; 22363 } 22364 22365 int GiveFeedback(uint dwEffect) { 22366 return DRAGDROP_S_USEDEFAULTCURSORS; 22367 } 22368 }; 22369 22370 DWORD effect; 22371 22372 if(action == DragAndDropAction.none) assert(0, "Don't drag something with a none effect."); 22373 22374 DROPEFFECT de = win32DragAndDropAction(action); 22375 22376 // I'm not as concerned about the GC here since DoDragDrop blocks so the stack frame still sane the whole time 22377 // but still prolly a FIXME 22378 22379 auto ret = DoDragDrop(obj, src, de, &effect); 22380 /+ 22381 if(ret == DRAGDROP_S_DROP) 22382 writeln("drop ", effect); 22383 else if(ret == DRAGDROP_S_CANCEL) 22384 writeln("cancel"); 22385 else if(ret == S_OK) 22386 writeln("ok"); 22387 else writeln(ret); 22388 +/ 22389 22390 return ret; 22391 } 22392 22393 version(Windows) 22394 DROPEFFECT win32DragAndDropAction(DragAndDropAction action) { 22395 DROPEFFECT de; 22396 22397 with(DragAndDropAction) 22398 with(DROPEFFECT) 22399 final switch(action) { 22400 case none: de = DROPEFFECT_NONE; break; 22401 case copy: de = DROPEFFECT_COPY; break; 22402 case move: de = DROPEFFECT_MOVE; break; 22403 case link: de = DROPEFFECT_LINK; break; 22404 case ask: throw new Exception("ask not implemented yet"); 22405 case custom: throw new Exception("custom not implemented yet"); 22406 } 22407 22408 return de; 22409 } 22410 22411 22412 /++ 22413 History: 22414 Added February 19, 2021 22415 +/ 22416 /// Group: drag_and_drop 22417 void enableDragAndDrop(SimpleWindow window, DropHandler handler) { 22418 version(X11) { 22419 auto display = XDisplayConnection.get; 22420 22421 Atom atom = 5; // right??? 22422 22423 XChangeProperty( 22424 display, 22425 window.impl.window, 22426 GetAtom!"XdndAware"(display), 22427 XA_ATOM, 22428 32 /* bits */, 22429 PropModeReplace, 22430 &atom, 22431 1); 22432 22433 window.dropHandler = handler; 22434 } else version(Windows) { 22435 22436 initDnd(); 22437 22438 auto dropTarget = new class (handler) IDropTarget { 22439 DropHandler handler; 22440 this(DropHandler handler) { 22441 this.handler = handler; 22442 } 22443 ULONG refCount; 22444 ULONG AddRef() { 22445 return ++refCount; 22446 } 22447 ULONG Release() { 22448 return --refCount; 22449 } 22450 HRESULT QueryInterface(const (IID)*riid, LPVOID *ppv) { 22451 if (IID_IUnknown == *riid) { 22452 *ppv = cast(void*) cast(IUnknown) this; 22453 } 22454 else if (IID_IDropTarget == *riid) { 22455 *ppv = cast(void*) cast(IDropTarget) this; 22456 } 22457 else { 22458 *ppv = null; 22459 return E_NOINTERFACE; 22460 } 22461 22462 AddRef(); 22463 return NOERROR; 22464 } 22465 22466 22467 // /////////////////// 22468 22469 HRESULT DragEnter(IDataObject pDataObj, DWORD grfKeyState, POINTL pt, DWORD* pdwEffect) { 22470 DropPackage dropPackage = DropPackage(pDataObj); 22471 *pdwEffect = win32DragAndDropAction(handler.dragEnter(&dropPackage)); 22472 return S_OK; // https://docs.microsoft.com/en-us/windows/win32/api/oleidl/nf-oleidl-idroptarget-dragenter 22473 } 22474 22475 HRESULT DragLeave() { 22476 handler.dragLeave(); 22477 // release the IDataObject if needed 22478 return S_OK; 22479 } 22480 22481 HRESULT DragOver(DWORD grfKeyState, POINTL pt, DWORD* pdwEffect) { 22482 auto res = handler.dragOver(Point(pt.x, pt.y)); // FIXME: translate screen coordinates back to window coordinates 22483 22484 *pdwEffect = win32DragAndDropAction(res.action); 22485 // same as DragEnter basically 22486 return S_OK; 22487 } 22488 22489 HRESULT Drop(IDataObject pDataObj, DWORD grfKeyState, POINTL pt, DWORD* pdwEffect) { 22490 DropPackage pkg = DropPackage(pDataObj); 22491 handler.drop(&pkg); 22492 22493 return S_OK; 22494 } 22495 }; 22496 // Windows can hold on to the handler and try to call it 22497 // during which time the GC can't see it. so important to 22498 // manually manage this. At some point i'll FIXME and make 22499 // all my com instances manually managed since they supposed 22500 // to respect the refcount. 22501 import core.memory; 22502 GC.addRoot(cast(void*) dropTarget); 22503 22504 if(RegisterDragDrop(window.impl.hwnd, dropTarget) != S_OK) 22505 throw new WindowsApiException("RegisterDragDrop", GetLastError()); 22506 22507 window.dropHandler = handler; 22508 } else throw new NotYetImplementedException(); 22509 } 22510 22511 22512 22513 static if(UsingSimpledisplayX11) { 22514 22515 enum _NET_WM_STATE_ADD = 1; 22516 enum _NET_WM_STATE_REMOVE = 0; 22517 enum _NET_WM_STATE_TOGGLE = 2; 22518 22519 /// X-specific. Use [SimpleWindow.requestAttention] instead for most cases. 22520 void demandAttention(SimpleWindow window, bool needs = true) { 22521 demandAttention(window.impl.window, needs); 22522 } 22523 22524 /// ditto 22525 void demandAttention(Window window, bool needs = true) { 22526 setNetWmStateAtom(window, GetAtom!("_NET_WM_STATE_DEMANDS_ATTENTION", false)(XDisplayConnection.get), needs); 22527 } 22528 22529 void setNetWmStateAtom(Window window, Atom atom, bool set = true, Atom atom2 = None) { 22530 auto display = XDisplayConnection.get(); 22531 if(atom == None) 22532 return; // non-failure error 22533 //auto atom2 = GetAtom!"_NET_WM_STATE_SHADED"(display); 22534 22535 XClientMessageEvent xclient; 22536 22537 xclient.type = EventType.ClientMessage; 22538 xclient.window = window; 22539 xclient.message_type = GetAtom!"_NET_WM_STATE"(display); 22540 xclient.format = 32; 22541 xclient.data.l[0] = set ? _NET_WM_STATE_ADD : _NET_WM_STATE_REMOVE; 22542 xclient.data.l[1] = atom; 22543 xclient.data.l[2] = atom2; 22544 xclient.data.l[3] = 1; 22545 // [3] == source. 0 == unknown, 1 == app, 2 == else 22546 22547 XSendEvent( 22548 display, 22549 RootWindow(display, DefaultScreen(display)), 22550 false, 22551 EventMask.SubstructureRedirectMask | EventMask.SubstructureNotifyMask, 22552 cast(XEvent*) &xclient 22553 ); 22554 22555 /+ 22556 XChangeProperty( 22557 display, 22558 window.impl.window, 22559 GetAtom!"_NET_WM_STATE"(display), 22560 XA_ATOM, 22561 32 /* bits */, 22562 PropModeAppend, 22563 &atom, 22564 1); 22565 +/ 22566 } 22567 22568 private Atom dndActionAtom(Display* display, DragAndDropAction action) { 22569 Atom actionAtom; 22570 with(DragAndDropAction) 22571 final switch(action) { 22572 case none: actionAtom = None; break; 22573 case copy: actionAtom = GetAtom!"XdndActionCopy"(display); break; 22574 case move: actionAtom = GetAtom!"XdndActionMove"(display); break; 22575 case link: actionAtom = GetAtom!"XdndActionLink"(display); break; 22576 case ask: actionAtom = GetAtom!"XdndActionAsk"(display); break; 22577 case custom: actionAtom = GetAtom!"XdndActionCustom"(display); break; 22578 } 22579 22580 return actionAtom; 22581 } 22582 22583 private int doDragDropX11(SimpleWindow window, X11SetSelectionHandler handler, DragAndDropAction action) { 22584 // FIXME: I need to show user feedback somehow. 22585 auto display = XDisplayConnection.get; 22586 22587 auto actionAtom = dndActionAtom(display, action); 22588 assert(actionAtom, "Don't use action none to accept a drop"); 22589 22590 setX11Selection!"XdndSelection"(window, handler, null); 22591 22592 auto oldKeyHandler = window.handleKeyEvent; 22593 scope(exit) window.handleKeyEvent = oldKeyHandler; 22594 22595 auto oldCharHandler = window.handleCharEvent; 22596 scope(exit) window.handleCharEvent = oldCharHandler; 22597 22598 auto oldMouseHandler = window.handleMouseEvent; 22599 scope(exit) window.handleMouseEvent = oldMouseHandler; 22600 22601 Window[Window] eligibility; // 0 == not eligible, otherwise it is the window id of an eligible child 22602 22603 import core.sys.posix.sys.time; 22604 timeval tv; 22605 gettimeofday(&tv, null); 22606 22607 Time dataTimestamp = tv.tv_sec * 1000 + tv.tv_usec / 1000; 22608 22609 Time lastMouseTimestamp; 22610 22611 bool dnding = true; 22612 Window lastIn = None; 22613 22614 void leave() { 22615 if(lastIn == None) 22616 return; 22617 22618 XEvent ev; 22619 ev.xclient.type = EventType.ClientMessage; 22620 ev.xclient.window = lastIn; 22621 ev.xclient.message_type = GetAtom!("XdndLeave", true)(display); 22622 ev.xclient.format = 32; 22623 ev.xclient.data.l[0] = window.impl.window; 22624 22625 XSendEvent(XDisplayConnection.get, lastIn, false, EventMask.NoEventMask, &ev); 22626 XFlush(display); 22627 22628 lastIn = None; 22629 } 22630 22631 void enter(Window w) { 22632 assert(lastIn == None); 22633 22634 lastIn = w; 22635 22636 XEvent ev; 22637 ev.xclient.type = EventType.ClientMessage; 22638 ev.xclient.window = lastIn; 22639 ev.xclient.message_type = GetAtom!("XdndEnter", true)(display); 22640 ev.xclient.format = 32; 22641 ev.xclient.data.l[0] = window.impl.window; 22642 ev.xclient.data.l[1] = (5 << 24) | 0; // version 5, no more sources. FIXME source types 22643 22644 auto types = handler.availableFormats(); 22645 assert(types.length > 0); 22646 22647 ev.xclient.data.l[2] = types[0]; 22648 if(types.length > 1) 22649 ev.xclient.data.l[3] = types[1]; 22650 if(types.length > 2) 22651 ev.xclient.data.l[4] = types[2]; 22652 22653 // FIXME: other types?!?!? and make sure we skip TARGETS 22654 22655 XSendEvent(XDisplayConnection.get, lastIn, false, EventMask.NoEventMask, &ev); 22656 XFlush(display); 22657 } 22658 22659 void position(int rootX, int rootY) { 22660 assert(lastIn != None); 22661 22662 XEvent ev; 22663 ev.xclient.type = EventType.ClientMessage; 22664 ev.xclient.window = lastIn; 22665 ev.xclient.message_type = GetAtom!("XdndPosition", true)(display); 22666 ev.xclient.format = 32; 22667 ev.xclient.data.l[0] = window.impl.window; 22668 ev.xclient.data.l[1] = 0; // reserved 22669 ev.xclient.data.l[2] = (rootX << 16) | rootY; 22670 ev.xclient.data.l[3] = dataTimestamp; 22671 ev.xclient.data.l[4] = actionAtom; 22672 22673 XSendEvent(XDisplayConnection.get, lastIn, false, EventMask.NoEventMask, &ev); 22674 XFlush(display); 22675 22676 } 22677 22678 void drop() { 22679 XEvent ev; 22680 ev.xclient.type = EventType.ClientMessage; 22681 ev.xclient.window = lastIn; 22682 ev.xclient.message_type = GetAtom!("XdndDrop", true)(display); 22683 ev.xclient.format = 32; 22684 ev.xclient.data.l[0] = window.impl.window; 22685 ev.xclient.data.l[1] = 0; // reserved 22686 ev.xclient.data.l[2] = dataTimestamp; 22687 22688 XSendEvent(XDisplayConnection.get, lastIn, false, EventMask.NoEventMask, &ev); 22689 XFlush(display); 22690 22691 lastIn = None; 22692 dnding = false; 22693 } 22694 22695 // fyi nativeEventHandler can return 0 if it handles it, or otherwise it goes back to the normal handler 22696 // but idk if i should... 22697 22698 window.setEventHandlers( 22699 delegate(KeyEvent ev) { 22700 if(ev.pressed == true && ev.key == Key.Escape) { 22701 // cancel 22702 dnding = false; 22703 } 22704 }, 22705 delegate(MouseEvent ev) { 22706 if(ev.timestamp < lastMouseTimestamp) 22707 return; 22708 22709 lastMouseTimestamp = ev.timestamp; 22710 22711 if(ev.type == MouseEventType.motion) { 22712 auto display = XDisplayConnection.get; 22713 auto root = RootWindow(display, DefaultScreen(display)); 22714 22715 Window topWindow; 22716 int rootX, rootY; 22717 22718 XTranslateCoordinates(display, window.impl.window, root, ev.x, ev.y, &rootX, &rootY, &topWindow); 22719 22720 if(topWindow == None) 22721 return; 22722 22723 top: 22724 if(auto result = topWindow in eligibility) { 22725 auto dropWindow = *result; 22726 if(dropWindow == None) { 22727 leave(); 22728 return; 22729 } 22730 22731 if(dropWindow != lastIn) { 22732 leave(); 22733 enter(dropWindow); 22734 position(rootX, rootY); 22735 } else { 22736 position(rootX, rootY); 22737 } 22738 } else { 22739 // determine eligibility 22740 auto data = cast(Atom[]) getX11PropertyData(topWindow, GetAtom!"XdndAware"(display), XA_ATOM); 22741 if(data.length == 1) { 22742 // in case there is no WM or it isn't reparenting 22743 eligibility[topWindow] = (data[0] == 5) ? topWindow : None; // FIXME I'm supposed to handle older versions too but meh 22744 } else { 22745 22746 Window tryScanChildren(Window search, int maxRecurse) { 22747 // could be reparenting window manager, so gotta check the next few children too 22748 Window child; 22749 int x; 22750 int y; 22751 XTranslateCoordinates(display, window.impl.window, search, ev.x, ev.y, &x, &y, &child); 22752 22753 if(child == None) 22754 return None; 22755 auto data = cast(Atom[]) getX11PropertyData(child, GetAtom!"XdndAware"(display), XA_ATOM); 22756 if(data.length == 1) { 22757 return (data[0] == 5) ? child : None; // FIXME I'm supposed to handle older versions too but meh 22758 } else { 22759 if(maxRecurse) 22760 return tryScanChildren(child, maxRecurse - 1); 22761 else 22762 return None; 22763 } 22764 22765 } 22766 22767 // if a WM puts more than 3 layers on it, like wtf is it doing, screw that. 22768 auto topResult = tryScanChildren(topWindow, 3); 22769 // it is easy to have a false negative due to the mouse going over a WM 22770 // child window like the close button if separate from the frame... so I 22771 // can't really cache negatives, :( 22772 if(topResult != None) { 22773 eligibility[topWindow] = topResult; 22774 goto top; // reload to do the positioning iff eligibility changed lest we endless loop 22775 } 22776 } 22777 22778 } 22779 22780 } else if(ev.type == MouseEventType.buttonReleased) { 22781 drop(); 22782 dnding = false; 22783 } 22784 } 22785 ); 22786 22787 window.grabInput(); 22788 scope(exit) 22789 window.releaseInputGrab(); 22790 22791 22792 EventLoop.get.run(() => dnding); 22793 22794 return 0; 22795 } 22796 22797 /// X-specific 22798 TrueColorImage getWindowNetWmIcon(Window window) { 22799 try { 22800 auto display = XDisplayConnection.get; 22801 22802 auto data = getX11PropertyData (window, GetAtom!"_NET_WM_ICON"(display), XA_CARDINAL); 22803 22804 if (data.length > arch_ulong.sizeof * 2) { 22805 auto meta = cast(arch_ulong[]) (data[0 .. arch_ulong.sizeof * 2]); 22806 // these are an array of rgba images that we have to convert into pixmaps ourself 22807 22808 int width = cast(int) meta[0]; 22809 int height = cast(int) meta[1]; 22810 22811 auto bytes = cast(ubyte[]) (data[arch_ulong.sizeof * 2 .. $]); 22812 22813 static if(arch_ulong.sizeof == 4) { 22814 bytes = bytes[0 .. width * height * 4]; 22815 alias imageData = bytes; 22816 } else static if(arch_ulong.sizeof == 8) { 22817 bytes = bytes[0 .. width * height * 8]; 22818 auto imageData = new ubyte[](4 * width * height); 22819 } else static assert(0); 22820 22821 22822 22823 // this returns ARGB. Remember it is little-endian so 22824 // we have BGRA 22825 // our thing uses RGBA, which in little endian, is ABGR 22826 for(int idx = 0, idx2 = 0; idx < bytes.length; idx += arch_ulong.sizeof, idx2 += 4) { 22827 auto r = bytes[idx + 2]; 22828 auto g = bytes[idx + 1]; 22829 auto b = bytes[idx + 0]; 22830 auto a = bytes[idx + 3]; 22831 22832 imageData[idx2 + 0] = r; 22833 imageData[idx2 + 1] = g; 22834 imageData[idx2 + 2] = b; 22835 imageData[idx2 + 3] = a; 22836 } 22837 22838 return new TrueColorImage(width, height, imageData); 22839 } 22840 22841 return null; 22842 } catch(Exception e) { 22843 return null; 22844 } 22845 } 22846 22847 } /* UsingSimpledisplayX11 */ 22848 22849 22850 void loadBinNameToWindowClassName () { 22851 import core.stdc.stdlib : realloc; 22852 version(linux) { 22853 // args[0] MAY be empty, so we'll just use this 22854 import core.sys.posix.unistd : readlink; 22855 char[1024] ebuf = void; // 1KB should be enough for everyone! 22856 auto len = readlink("/proc/self/exe", ebuf.ptr, ebuf.length); 22857 if (len < 1) return; 22858 } else /*version(Windows)*/ { 22859 import core.runtime : Runtime; 22860 if (Runtime.args.length == 0 || Runtime.args[0].length == 0) return; 22861 auto ebuf = Runtime.args[0]; 22862 auto len = ebuf.length; 22863 } 22864 auto pos = len; 22865 while (pos > 0 && ebuf[pos-1] != '/') --pos; 22866 sdpyWindowClassStr = cast(char*)realloc(sdpyWindowClassStr, len-pos+1); 22867 if (sdpyWindowClassStr is null) return; // oops 22868 sdpyWindowClassStr[0..len-pos+1] = 0; // just in case 22869 sdpyWindowClassStr[0..len-pos] = ebuf[pos..len]; 22870 } 22871 22872 /++ 22873 An interface representing a font that is drawn with custom facilities. 22874 22875 You might want [OperatingSystemFont] instead, which represents 22876 a font loaded and drawn by functions native to the operating system. 22877 22878 WARNING: I might still change this. 22879 +/ 22880 interface DrawableFont : MeasurableFont { 22881 /++ 22882 Please note the point is upperLeft, NOT baseline! This is the point of a bounding box of the string. 22883 22884 Implementations must use the painter's fillColor to draw a rectangle behind the string, 22885 then use the outlineColor to draw the string. It might alpha composite if there's a transparent 22886 fill color, but that's up to the implementation. 22887 +/ 22888 void drawString(ScreenPainter painter, Point upperLeft, in char[] text); 22889 22890 /++ 22891 Requests that the given string is added to the image cache. You should only do this rarely, but 22892 if you have a string that you know will be used over and over again, adding it to a cache can 22893 improve things (assuming the implementation actually has a cache; it is also valid for an implementation 22894 to implement this as a do-nothing method). 22895 +/ 22896 void cacheString(SimpleWindow window, Color foreground, Color background, string text); 22897 } 22898 22899 /++ 22900 Loads a true type font using [arsd.ttf] that can be drawn as images on windows 22901 through a [ScreenPainter]. That module must be compiled in if you choose to use this function. 22902 22903 You should also consider [OperatingSystemFont], which loads and draws a font with 22904 facilities native to the user's operating system. You might also consider 22905 [arsd.ttf.OpenGlLimitedFont] or using [arsd.nanovega] if you are making some kind 22906 of game, as they have their own ways to draw text too. 22907 22908 Be warned: this can be slow, especially on remote connections to the X server, since 22909 it needs to create and transfer bitmaps instead of just text. The [DrawableFont] interface 22910 offers [DrawableFont.cacheString] which can help with this, sometimes. You might want to 22911 experiment in your specific case. 22912 22913 Please note that the return type of [DrawableFont] also includes an implementation of 22914 [MeasurableFont]. 22915 +/ 22916 DrawableFont arsdTtfFont()(in ubyte[] data, int size) { 22917 import arsd.ttf; 22918 static class ArsdTtfFont : DrawableFont { 22919 TtfFont font; 22920 int size; 22921 this(in ubyte[] data, int size) { 22922 font = TtfFont(data); 22923 this.size = size; 22924 22925 22926 auto scale = stbtt_ScaleForPixelHeight(&font.font, size); 22927 int ascent_, descent_, line_gap; 22928 stbtt_GetFontVMetrics(&font.font, &ascent_, &descent_, &line_gap); 22929 22930 int advance, lsb; 22931 stbtt_GetCodepointHMetrics(&font.font, 'x', &advance, &lsb); 22932 xWidth = cast(int) (advance * scale); 22933 stbtt_GetCodepointHMetrics(&font.font, 'M', &advance, &lsb); 22934 MWidth = cast(int) (advance * scale); 22935 } 22936 22937 private int ascent_; 22938 private int descent_; 22939 private int xWidth; 22940 private int MWidth; 22941 22942 bool isMonospace() { 22943 return xWidth == MWidth; 22944 } 22945 int averageWidth() { 22946 return xWidth; 22947 } 22948 int height() { 22949 return size; 22950 } 22951 int ascent() { 22952 return ascent_; 22953 } 22954 int descent() { 22955 return descent_; 22956 } 22957 22958 int stringWidth(scope const(char)[] s, SimpleWindow window = null) { 22959 int width, height; 22960 font.getStringSize(s, size, width, height); 22961 return width; 22962 } 22963 22964 22965 22966 Sprite[string] cache; 22967 22968 void cacheString(SimpleWindow window, Color foreground, Color background, string text) { 22969 auto sprite = new Sprite(window, stringToImage(foreground, background, text)); 22970 cache[text] = sprite; 22971 } 22972 22973 Image stringToImage(Color fg, Color bg, in char[] text) { 22974 int width, height; 22975 auto data = font.renderString(text, size, width, height); 22976 auto image = new TrueColorImage(width, height); 22977 int pos = 0; 22978 foreach(y; 0 .. height) 22979 foreach(x; 0 .. width) { 22980 fg.a = data[0]; 22981 bg.a = 255; 22982 auto color = alphaBlend(fg, bg); 22983 image.imageData.bytes[pos++] = color.r; 22984 image.imageData.bytes[pos++] = color.g; 22985 image.imageData.bytes[pos++] = color.b; 22986 image.imageData.bytes[pos++] = data[0]; 22987 data = data[1 .. $]; 22988 } 22989 assert(data.length == 0); 22990 22991 return Image.fromMemoryImage(image); 22992 } 22993 22994 void drawString(ScreenPainter painter, Point upperLeft, in char[] text) { 22995 Sprite sprite = (text in cache) ? *(text in cache) : null; 22996 22997 auto fg = painter.impl._outlineColor; 22998 auto bg = painter.impl._fillColor; 22999 23000 if(sprite !is null) { 23001 auto w = cast(SimpleWindow) painter.window; 23002 assert(w !is null); 23003 23004 sprite.drawAt(painter, upperLeft); 23005 } else { 23006 painter.drawImage(upperLeft, stringToImage(fg, bg, text)); 23007 } 23008 } 23009 } 23010 23011 return new ArsdTtfFont(data, size); 23012 } 23013 23014 class NotYetImplementedException : Exception { 23015 this(string file = __FILE__, size_t line = __LINE__) { 23016 super("Not yet implemented", file, line); 23017 } 23018 } 23019 23020 /// 23021 __gshared bool librariesSuccessfullyLoaded = true; 23022 /// 23023 __gshared bool openGlLibrariesSuccessfullyLoaded = true; 23024 23025 private mixin template DynamicLoadSupplementalOpenGL(Iface) { 23026 mixin(staticForeachReplacement!Iface); 23027 23028 void loadDynamicLibrary() @nogc { 23029 (cast(void function() @nogc) &loadDynamicLibraryForReal)(); 23030 } 23031 23032 void loadDynamicLibraryForReal() { 23033 foreach(name; __traits(derivedMembers, Iface)) { 23034 mixin("alias tmp = " ~ name ~ ";"); 23035 tmp = cast(typeof(tmp)) glbindGetProcAddress(name); 23036 if(tmp is null) throw new Exception("load failure of function " ~ name ~ " from supplemental OpenGL"); 23037 } 23038 } 23039 } 23040 23041 private const(char)[] staticForeachReplacement(Iface)() pure { 23042 /* 23043 // just this for gdc 9.... 23044 // when i drop support for it and switch to gdc10, we can put this original back for a slight compile time ram decrease 23045 23046 static foreach(name; __traits(derivedMembers, Iface)) 23047 mixin("__gshared typeof(&__traits(getMember, Iface, name)) " ~ name ~ ";"); 23048 */ 23049 23050 char[] code = new char[](__traits(derivedMembers, Iface).length * 64); 23051 size_t pos; 23052 23053 void append(in char[] what) { 23054 if(pos + what.length > code.length) 23055 code.length = (code.length * 3) / 2; 23056 code[pos .. pos + what.length] = what[]; 23057 pos += what.length; 23058 } 23059 23060 foreach(name; __traits(derivedMembers, Iface)) { 23061 append(`__gshared typeof(&__traits(getMember, Iface, "`); 23062 append(name); 23063 append(`")) `); 23064 append(name); 23065 append(";"); 23066 } 23067 23068 return code[0 .. pos]; 23069 } 23070 23071 private mixin template DynamicLoad(Iface, string library, int majorVersion, alias success) { 23072 mixin(staticForeachReplacement!Iface); 23073 23074 private __gshared void* libHandle; 23075 private __gshared bool attempted; 23076 23077 void loadDynamicLibrary() @nogc { 23078 (cast(void function() @nogc) &loadDynamicLibraryForReal)(); 23079 } 23080 23081 bool loadAttempted() { 23082 return attempted; 23083 } 23084 bool loadSuccessful() { 23085 return libHandle !is null; 23086 } 23087 23088 void loadDynamicLibraryForReal() { 23089 attempted = true; 23090 version(Posix) { 23091 import core.sys.posix.dlfcn; 23092 version(OSX) { 23093 version(X11) 23094 libHandle = dlopen("/usr/X11/lib/lib" ~ library ~ ".dylib", RTLD_NOW); 23095 else 23096 libHandle = dlopen(library ~ ".dylib", RTLD_NOW); 23097 } else { 23098 libHandle = dlopen("lib" ~ library ~ ".so", RTLD_NOW); 23099 if(libHandle is null) 23100 libHandle = dlopen(("lib" ~ library ~ ".so." ~ toInternal!string(majorVersion) ~ "\0").ptr, RTLD_NOW); 23101 } 23102 23103 static void* loadsym(void* l, const char* name) { 23104 import core.stdc.stdlib; 23105 if(l is null) 23106 return &abort; 23107 return dlsym(l, name); 23108 } 23109 } else version(Windows) { 23110 import core.sys.windows.winbase; 23111 libHandle = LoadLibrary(library ~ ".dll"); 23112 static void* loadsym(void* l, const char* name) { 23113 import core.stdc.stdlib; 23114 if(l is null) 23115 return &abort; 23116 return GetProcAddress(l, name); 23117 } 23118 } 23119 if(libHandle is null) { 23120 success = false; 23121 //throw new Exception("load failure of library " ~ library); 23122 } 23123 foreach(name; __traits(derivedMembers, Iface)) { 23124 mixin("alias tmp = " ~ name ~ ";"); 23125 tmp = cast(typeof(tmp)) loadsym(libHandle, name); 23126 if(tmp is null) throw new Exception("load failure of function " ~ name ~ " from " ~ library); 23127 } 23128 } 23129 23130 void unloadDynamicLibrary() { 23131 version(Posix) { 23132 import core.sys.posix.dlfcn; 23133 dlclose(libHandle); 23134 } else version(Windows) { 23135 import core.sys.windows.winbase; 23136 FreeLibrary(libHandle); 23137 } 23138 foreach(name; __traits(derivedMembers, Iface)) 23139 mixin(name ~ " = null;"); 23140 } 23141 } 23142 23143 // version(X11) 23144 /++ 23145 Returns the custom scaling factor read out of environment["ARSD_SCALING_FACTOR"]. 23146 23147 $(WARNING 23148 This function is exempted from stability guarantees. 23149 ) 23150 +/ 23151 float customScalingFactorForMonitor(int monitorNumber) @system { 23152 import core.stdc.stdlib; 23153 auto val = getenv("ARSD_SCALING_FACTOR"); 23154 23155 // FIXME: maybe we should assume a default nbased on the dpi thing if this isn't given 23156 if(val is null) 23157 return 1.0; 23158 23159 char[16] buffer = 0; 23160 int pos; 23161 23162 const(char)* at = val; 23163 23164 foreach(item; 0 .. monitorNumber + 1) { 23165 if(*at == 0) 23166 break; // reuse the last number when we at the end of the string 23167 pos = 0; 23168 while(pos + 1 < buffer.length && *at && *at != ';') { 23169 buffer[pos++] = *at; 23170 at++; 23171 } 23172 if(*at) 23173 at++; // skip the semicolon 23174 buffer[pos] = 0; 23175 } 23176 23177 //sdpyPrintDebugString(buffer[0 .. pos]); 23178 23179 import core.stdc.math; 23180 auto f = atof(buffer.ptr); 23181 23182 if(f <= 0.0 || isnan(f) || isinf(f)) 23183 return 1.0; 23184 23185 return f; 23186 } 23187 23188 void guiAbortProcess(string msg) { 23189 import core.stdc.stdlib; 23190 version(Windows) { 23191 WCharzBuffer t = WCharzBuffer(msg); 23192 MessageBoxW(null, t.ptr, "Program Termination"w.ptr, 0); 23193 } else { 23194 import core.stdc.stdio; 23195 fwrite(msg.ptr, 1, msg.length, stderr); 23196 msg = "\n"; 23197 fwrite(msg.ptr, 1, msg.length, stderr); 23198 fflush(stderr); 23199 } 23200 23201 abort(); 23202 } 23203 23204 private int minInternal(int a, int b) { 23205 return (a < b) ? a : b; 23206 } 23207 23208 private alias scriptable = arsd_jsvar_compatible;