1 // https://dpaste.dzfl.pl/7a77355acaec 2 3 // Search for: FIXME: leaks if multithreaded gc 4 5 // https://freedesktop.org/wiki/Specifications/XDND/ 6 7 // https://docs.microsoft.com/en-us/windows/win32/dataxchg/html-clipboard-format 8 9 // https://www.x.org/releases/X11R7.7/doc/libXext/dbelib.html 10 // https://www.x.org/releases/X11R7.6/doc/libXext/synclib.html 11 12 13 // on Mac with X11: -L-L/usr/X11/lib 14 15 /+ 16 17 * I might need to set modal hints too _NET_WM_STATE_MODAL and make sure that TRANSIENT_FOR legit works 18 19 Progress bar in taskbar 20 - i can probably just set a property on the window... 21 it sets that prop to an integer 0 .. 100. Taskbar 22 deletes it or window deletes it when it is handled. 23 - prolly display it as a nice little line at the bottom. 24 25 26 from gtk: 27 28 #define PROGRESS_HINT "_NET_WM_XAPP_PROGRESS" 29 #define PROGRESS_PULSE_HINT "_NET_WM_XAPP_PROGRESS_PULSE" 30 31 >+ if (cardinal > 0) 32 >+ { 33 >+ XChangeProperty (GDK_DISPLAY_XDISPLAY (display), 34 >+ xid, 35 >+ gdk_x11_get_xatom_by_name_for_display (display, atom_name), 36 >+ XA_CARDINAL, 32, 37 >+ PropModeReplace, 38 >+ (guchar *) &cardinal, 1); 39 >+ } 40 >+ else 41 >+ { 42 >+ XDeleteProperty (GDK_DISPLAY_XDISPLAY (display), 43 >+ xid, 44 >+ gdk_x11_get_xatom_by_name_for_display (display, atom_name)); 45 >+ } 46 47 from Windows: 48 49 see: https://docs.microsoft.com/en-us/windows/win32/api/shobjidl_core/nn-shobjidl_core-itaskbarlist3 50 51 interface 52 CoCreateInstance( CLSID_TaskbarList, nullptr, CLSCTX_ALL, __uuidof(ITaskbarList3), (LPVOID*)&m_pTL3 ); 53 auto msg = RegisterWindowMessage(TEXT(“TaskbarButtonCreated”)); 54 listen for msg, return TRUE 55 interface->SetProgressState(hwnd, TBPF_NORMAL); 56 interface->SetProgressValue(hwnd, 40, 100); 57 58 59 My new notification system. 60 - use a unix socket? or a x property? or a udp port? 61 - could of course also get on the dbus train but ugh. 62 - it could also reply with the info as a string for easy remote examination. 63 64 +/ 65 66 /* 67 Event Loop would be nices: 68 69 * add on idle - runs when nothing else happens 70 * which can specify how long to yield for 71 * send messages without a recipient window 72 * setTimeout 73 * setInterval 74 */ 75 76 /* 77 Classic games I want to add: 78 * my tetris clone 79 * pac man 80 */ 81 82 /* 83 Text layout needs a lot of work. Plain drawText is useful but too 84 limited. It will need some kind of text context thing which it will 85 update and you can pass it on and get more details out of it. 86 87 It will need a bounding box, a current cursor location that is updated 88 as drawing continues, and various changable facts (which can also be 89 changed on the painter i guess) like font, color, size, background, 90 etc. 91 92 We can also fetch the caret location from it somehow. 93 94 Should prolly be an overload of drawText 95 96 blink taskbar / demand attention cross platform. FlashWindow and demandAttention 97 98 WS_EX_NOACTIVATE 99 WS_CHILD - owner and owned vs parent and child. Does X have something similar? 100 full screen windows. Can just set the atom on X. Windows will be harder. 101 102 moving windows. resizing windows. 103 104 hide cursor, capture cursor, change cursor. 105 106 REMEMBER: simpledisplay does NOT have to do everything! It just needs to make 107 sure the pieces are there to do its job easily and make other jobs possible. 108 */ 109 110 /++ 111 simpledisplay.d (often abbreviated to "sdpy") provides basic cross-platform GUI-related functionality, 112 including creating windows, drawing on them, working with the clipboard, 113 timers, OpenGL, and more. However, it does NOT provide high level GUI 114 widgets. See my minigui.d, an extension to this module, for that 115 functionality. 116 117 simpledisplay provides cross-platform wrapping for Windows and Linux 118 (and perhaps other OSes that use X11), but also does not prevent you 119 from using the underlying facilities if you need them. It has a goal 120 of working efficiently over a remote X link (at least as far as Xlib 121 reasonably allows.) 122 123 simpledisplay depends on [arsd.color|color.d], which should be available from the 124 same place where you got this file. Other than that, however, it has 125 very few dependencies and ones that don't come with the OS and/or the 126 compiler are all opt-in. 127 128 simpledisplay.d's home base is on my arsd repo on Github. The file is: 129 https://github.com/adamdruppe/arsd/blob/master/simpledisplay.d 130 131 simpledisplay is basically stable. I plan to refactor the internals, 132 and may add new features and fix bugs, but It do not expect to 133 significantly change the API. It has been stable a few years already now. 134 135 Installation_instructions: 136 137 `simpledisplay.d` does not have any dependencies outside the 138 operating system and `color.d`, so it should just work most the 139 time, but there are a few caveats on some systems: 140 141 On Win32, you can pass `-L/subsystem:windows` if you don't want a 142 console to be automatically allocated. 143 144 Please note when compiling on Win64, you need to explicitly list 145 `-Lgdi32.lib -Luser32.lib` on the build command. If you want the Windows 146 subsystem too, use `-L/subsystem:windows -L/entry:mainCRTStartup`. 147 148 If using ldc instead of dmd, use `-L/entry:wmainCRTStartup` instead of `mainCRTStartup`; 149 note the "w". 150 151 I provided a `mixin EnableWindowsSubsystem;` helper to do those linker flags for you, 152 but you still need to use dmd -m32mscoff or -m64 (which dub does by default too fyi). 153 See [EnableWindowsSubsystem] for more information. 154 155 $(PITFALL 156 With the Windows subsystem, there is no console, so standard writeln will throw! 157 You can use [sdpyPrintDebugString] instead of stdio writeln instead which will 158 create a console as needed. 159 ) 160 161 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. 162 163 On Ubuntu, you might need to install X11 development libraries to 164 successfully link. 165 166 $(CONSOLE 167 $ sudo apt-get install libglc-dev 168 $ sudo apt-get install libx11-dev 169 ) 170 171 172 Jump_list: 173 174 Don't worry, you don't have to read this whole documentation file! 175 176 Check out the [#event-example] and [#Pong-example] to get started quickly. 177 178 The main classes you may want to create are [SimpleWindow], [Timer], 179 [Image], and [Sprite]. 180 181 The main functions you'll want are [setClipboardText] and [getClipboardText]. 182 183 There are also platform-specific functions available such as [XDisplayConnection] 184 and [GetAtom] for X11, among others. 185 186 See the examples and topics list below to learn more. 187 188 $(WARNING 189 There should only be one GUI thread per application, 190 and all windows should be created in it and your 191 event loop should run there. 192 193 To do otherwise is undefined behavior and has no 194 cross platform guarantees. 195 ) 196 197 $(H2 About this documentation) 198 199 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. 200 201 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! 202 203 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. 204 205 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. 206 207 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. 208 209 At points, I will talk about implementation details in the documentation. These are sometimes 210 subject to change, but nevertheless useful to understand what is really going on. You can learn 211 more about some of the referenced things by searching the web for info about using them from C. 212 You can always look at the source of simpledisplay.d too for the most authoritative source on 213 its specific implementation. If you disagree with how I did something, please contact me so we 214 can discuss it! 215 216 $(H2 Using with fibers) 217 218 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). 219 220 $(H2 Topics) 221 222 $(H3 $(ID topic-windows) Windows) 223 The [SimpleWindow] class is simpledisplay's flagship feature. It represents a single 224 window on the user's screen. 225 226 You may create multiple windows, if the underlying platform supports it. You may check 227 `static if(multipleWindowsSupported)` at compile time, or catch exceptions thrown by 228 SimpleWindow's constructor at runtime to handle those cases. 229 230 A single running event loop will handle as many windows as needed. 231 232 $(H3 $(ID topic-event-loops) Event loops) 233 The simpledisplay event loop is designed to handle common cases easily while being extensible for more advanced cases, or replaceable by other libraries. 234 235 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: 236 237 --- 238 // dmd example.d simpledisplay.d color.d 239 import arsd.simpledisplay; 240 void main() { 241 auto window = new SimpleWindow(200, 200); 242 window.eventLoop(0, 243 delegate (dchar) { /* got a character key press */ } 244 ); 245 } 246 --- 247 248 $(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.) 249 250 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. 251 252 On Linux, simpledisplay also supports my (deprecated) [arsd.eventloop] module. Compile your program, including the eventloop.d file, with the `-version=with_eventloop` switch. 253 254 It should be possible to integrate simpledisplay with vibe.d as well, though I haven't tried. 255 256 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. 257 258 $(H3 $(ID topic-notification-areas) Notification area (aka systray) icons) 259 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. 260 261 See the [NotificationAreaIcon] class. 262 263 $(H3 $(ID topic-input-handling) Input handling) 264 There are event handlers for low-level keyboard and mouse events, and higher level handlers for character events. 265 266 See [SimpleWindow.handleCharEvent], [SimpleWindow.handleKeyEvent], [SimpleWindow.handleMouseEvent]. 267 268 $(H3 $(ID topic-2d-drawing) 2d Drawing) 269 To draw on your window, use the [SimpleWindow.draw] method. It returns a [ScreenPainter] structure with drawing methods. 270 271 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: 272 273 --- 274 // dmd example.d simpledisplay.d color.d 275 import arsd.simpledisplay; 276 void main() { 277 auto window = new SimpleWindow(200, 200); 278 { // introduce sub-scope 279 auto painter = window.draw(); // begin drawing 280 /* draw here */ 281 painter.outlineColor = Color.red; 282 painter.fillColor = Color.black; 283 painter.drawRectangle(Point(0, 0), 200, 200); 284 } // end scope, calling `painter`'s destructor, drawing to the screen. 285 window.eventLoop(0); // handle events 286 } 287 --- 288 289 Painting is done based on two color properties, a pen and a brush. 290 291 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. 292 293 FIXME Add example of 2d opengl drawing here. 294 $(H3 $(ID topic-3d-drawing) 3d Drawing (or 2d with OpenGL)) 295 simpledisplay can create OpenGL contexts on your window. It works quite differently than 2d drawing. 296 297 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. 298 299 To start, you create a [SimpleWindow] with OpenGL enabled by passing the argument [OpenGlOptions.yes] to the constructor. 300 301 Next, you set [SimpleWindow.redrawOpenGlScene|window.redrawOpenGlScene] to a delegate which draws your frame. 302 303 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]. 304 305 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. 306 307 This example program will draw a rectangle on your window using old-style OpenGL with a pulsating color: 308 309 --- 310 import arsd.simpledisplay; 311 312 void main() { 313 auto window = new SimpleWindow(800, 600, "opengl 1", OpenGlOptions.yes, Resizability.allowResizing); 314 315 float otherColor = 0.0; 316 float colorDelta = 0.05; 317 318 window.redrawOpenGlScene = delegate() { 319 glLoadIdentity(); 320 glBegin(GL_QUADS); 321 322 glColor3f(1.0, otherColor, 0); 323 glVertex3f(-0.8, -0.8, 0); 324 325 glColor3f(1.0, otherColor, 1.0); 326 glVertex3f(0.8, -0.8, 0); 327 328 glColor3f(0, 1.0, otherColor); 329 glVertex3f(0.8, 0.8, 0); 330 331 glColor3f(otherColor, 0, 1.0); 332 glVertex3f(-0.8, 0.8, 0); 333 334 glEnd(); 335 }; 336 337 window.eventLoop(50, () { 338 otherColor += colorDelta; 339 if(otherColor > 1.0) { 340 otherColor = 1.0; 341 colorDelta = -0.05; 342 } 343 if(otherColor < 0) { 344 otherColor = 0; 345 colorDelta = 0.05; 346 } 347 // at the end of the timer, we have to request a redraw 348 // or we won't see the changes. 349 window.redrawOpenGlSceneSoon(); 350 }); 351 } 352 --- 353 354 My [arsd.game] module has some helpers for using old-style opengl to make 2D windows too. See: [arsd.game.create2dWindow]. 355 $(H3 $(ID topic-modern-opengl) Modern OpenGL) 356 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. 357 358 This example program shows how you can set up a shader to draw a rectangle: 359 360 --- 361 module opengl3test; 362 import arsd.simpledisplay; 363 364 // based on https://learnopengl.com/Getting-started/Hello-Triangle 365 366 void main() { 367 // First thing we do, before creating the window, is declare what version we want. 368 setOpenGLContextVersion(3, 3); 369 // turning off legacy compat is required to use version 3.3 and newer 370 openGLContextCompatible = false; 371 372 uint VAO; 373 OpenGlShader shader; 374 375 // then we can create the window. 376 auto window = new SimpleWindow(800, 600, "opengl 3", OpenGlOptions.yes, Resizability.allowResizing); 377 378 // additional setup needs to be done when it is visible, simpledisplay offers a property 379 // for exactly that: 380 window.visibleForTheFirstTime = delegate() { 381 // now with the window loaded, we can start loading the modern opengl functions. 382 383 // you MUST set the context first. 384 window.setAsCurrentOpenGlContext; 385 // then load the remainder of the library 386 gl3.loadDynamicLibrary(); 387 388 // now you can create the shaders, etc. 389 shader = new OpenGlShader( 390 OpenGlShader.Source(GL_VERTEX_SHADER, ` 391 #version 330 core 392 layout (location = 0) in vec3 aPos; 393 void main() { 394 gl_Position = vec4(aPos.x, aPos.y, aPos.z, 1.0); 395 } 396 `), 397 OpenGlShader.Source(GL_FRAGMENT_SHADER, ` 398 #version 330 core 399 out vec4 FragColor; 400 uniform vec4 mycolor; 401 void main() { 402 FragColor = mycolor; 403 } 404 `), 405 ); 406 407 // and do whatever other setup you want. 408 409 float[] vertices = [ 410 0.5f, 0.5f, 0.0f, // top right 411 0.5f, -0.5f, 0.0f, // bottom right 412 -0.5f, -0.5f, 0.0f, // bottom left 413 -0.5f, 0.5f, 0.0f // top left 414 ]; 415 uint[] indices = [ // note that we start from 0! 416 0, 1, 3, // first Triangle 417 1, 2, 3 // second Triangle 418 ]; 419 uint VBO, EBO; 420 glGenVertexArrays(1, &VAO); 421 // bind the Vertex Array Object first, then bind and set vertex buffer(s), and then configure vertex attributes(s). 422 glBindVertexArray(VAO); 423 424 glGenBuffers(1, &VBO); 425 glGenBuffers(1, &EBO); 426 427 glBindBuffer(GL_ARRAY_BUFFER, VBO); 428 glBufferDataSlice(GL_ARRAY_BUFFER, vertices, GL_STATIC_DRAW); 429 430 glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, EBO); 431 glBufferDataSlice(GL_ELEMENT_ARRAY_BUFFER, indices, GL_STATIC_DRAW); 432 433 glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, 3 * float.sizeof, null); 434 glEnableVertexAttribArray(0); 435 436 // the library will set the initial viewport and trigger our first draw, 437 // so these next two lines are NOT needed. they are just here as comments 438 // to show what would happen next. 439 440 // glViewport(0, 0, window.width, window.height); 441 // window.redrawOpenGlSceneNow(); 442 }; 443 444 // this delegate is called any time the window needs to be redrawn or if you call `window.redrawOpenGlSceneNow;` 445 // it is our render method. 446 window.redrawOpenGlScene = delegate() { 447 glClearColor(0.2f, 0.3f, 0.3f, 1.0f); 448 glClear(GL_COLOR_BUFFER_BIT); 449 450 glUseProgram(shader.shaderProgram); 451 452 // the shader helper class has methods to set uniforms too 453 shader.uniforms.mycolor.opAssign(1.0, 1.0, 0, 1.0); 454 455 glBindVertexArray(VAO); 456 glDrawElements(GL_TRIANGLES, 6, GL_UNSIGNED_INT, null); 457 }; 458 459 window.eventLoop(0); 460 } 461 --- 462 463 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. 464 465 466 $(H3 $(ID topic-images) Displaying images) 467 You can also load PNG images using [arsd.png]. 468 469 --- 470 // dmd example.d simpledisplay.d color.d png.d 471 import arsd.simpledisplay; 472 import arsd.png; 473 474 void main() { 475 auto image = Image.fromMemoryImage(readPng("image.png")); 476 displayImage(image); 477 } 478 --- 479 480 Compile with `dmd example.d simpledisplay.d png.d`. 481 482 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. 483 484 $(H3 $(ID topic-sprites) Sprites) 485 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. 486 487 [Sprite] is also the only facility that currently supports alpha blending without using OpenGL . 488 489 $(H3 $(ID topic-clipboard) Clipboard) 490 The free functions [getClipboardText] and [setClipboardText] consist of simpledisplay's cross-platform clipboard support at this time. 491 492 It also has helpers for handling X-specific events. 493 494 $(H3 $(ID topic-dnd) Drag and Drop) 495 See [enableDragAndDrop] and [draggable]. 496 497 $(H3 $(ID topic-timers) Timers) 498 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]. 499 500 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. 501 502 --- 503 import arsd.simpledisplay; 504 505 void main() { 506 auto window = new SimpleWindow(400, 400); 507 // every 100 ms, it will draw a random line 508 // on the window. 509 window.eventLoop(100, { 510 auto painter = window.draw(); 511 512 import std.random; 513 // random color 514 painter.outlineColor = Color(uniform(0, 256), uniform(0, 256), uniform(0, 256)); 515 // random line 516 painter.drawLine( 517 Point(uniform(0, window.width), uniform(0, window.height)), 518 Point(uniform(0, window.width), uniform(0, window.height))); 519 520 }); 521 } 522 --- 523 524 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. 525 526 The pulse timer and instances of the [Timer] class may be combined at will. 527 528 --- 529 import arsd.simpledisplay; 530 531 void main() { 532 auto window = new SimpleWindow(400, 400); 533 auto timer = new Timer(1000, delegate { 534 auto painter = window.draw(); 535 painter.clear(); 536 }); 537 538 window.eventLoop(0); 539 } 540 --- 541 542 Timers are currently only implemented on Windows, using `SetTimer` and Linux, using `timerfd_create`. These deliver timeout messages through your application event loop. 543 544 $(H3 $(ID topic-os-helpers) OS-specific helpers) 545 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. 546 547 See also: `xwindows.d` from my github. 548 549 $(H3 $(ID topic-os-extension) Extending with OS-specific functionality) 550 `handleNativeEvent` and `handleNativeGlobalEvent`. 551 552 $(H3 $(ID topic-integration) Integration with other libraries) 553 Integration with a third-party event loop is possible. 554 555 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. 556 557 $(H3 $(ID topic-guis) GUI widgets) 558 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! 559 560 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. 561 562 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.) 563 564 minigui still needs a lot of work to be finished at this time, but it already offers a number of useful classes. 565 566 $(H2 Platform-specific tips and tricks) 567 568 X_tips: 569 570 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. 571 572 Windows_tips: 573 574 You can add icons or manifest files to your exe using a resource file. 575 576 To create a Windows .ico file, use the gimp or something. I'll write a helper 577 program later. 578 579 Create `yourapp.rc`: 580 581 ```rc 582 1 ICON filename.ico 583 CREATEPROCESS_MANIFEST_RESOURCE_ID RT_MANIFEST "YourApp.exe.manifest" 584 ``` 585 586 And `yourapp.exe.manifest`: 587 588 ```xml 589 <?xml version="1.0" encoding="UTF-8" standalone="yes"?> 590 <assembly xmlns="urn:schemas-microsoft-com:asm.v1" manifestVersion="1.0"> 591 <assemblyIdentity 592 version="1.0.0.0" 593 processorArchitecture="*" 594 name="CompanyName.ProductName.YourApplication" 595 type="win32" 596 /> 597 <description>Your application description here.</description> 598 <dependency> 599 <dependentAssembly> 600 <assemblyIdentity 601 type="win32" 602 name="Microsoft.Windows.Common-Controls" 603 version="6.0.0.0" 604 processorArchitecture="*" 605 publicKeyToken="6595b64144ccf1df" 606 language="*" 607 /> 608 </dependentAssembly> 609 </dependency> 610 <application xmlns="urn:schemas-microsoft-com:asm.v3"> 611 <windowsSettings> 612 <dpiAware xmlns="http://schemas.microsoft.com/SMI/2005/WindowsSettings">true/pm</dpiAware> <!-- old style --> 613 <dpiAwareness xmlns="http://schemas.microsoft.com/SMI/2016/WindowsSettings">PerMonitorV2</dpiAwareness> <!-- new style --> 614 <!-- Un-comment the line below to enable GDI-scaling in this project. This will enable text --> 615 <!-- to render crisply in DPI-unaware contexts --> 616 <!--<gdiScaling xmlns="http://schemas.microsoft.com/SMI/2017/WindowsSettings">true</gdiScaling>--> 617 </windowsSettings> 618 </application> 619 </assembly> 620 ``` 621 622 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`. 623 624 Doing this lets you opt into various new things since Windows XP. 625 626 See: https://docs.microsoft.com/en-us/windows/win32/SbsCs/application-manifests 627 628 $(H2 Tips) 629 630 $(H3 Name conflicts) 631 632 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: 633 634 --- 635 static import sdpy = arsd.simpledisplay; 636 import arsd.simpledisplay : SimpleWindow; 637 638 void main() { 639 auto window = new SimpleWindow(); 640 sdpy.EventLoop.get.run(); 641 } 642 --- 643 644 $(H2 $(ID developer-notes) Developer notes) 645 646 I don't have a Mac, so that code isn't maintained. I would like to have a Cocoa 647 implementation though. 648 649 The NativeSimpleWindowImplementation and NativeScreenPainterImplementation both 650 suck. If I was rewriting it, I wouldn't do it that way again. 651 652 This file must not have any more required dependencies. If you need bindings, add 653 them right to this file. Once it gets into druntime and is there for a while, remove 654 bindings from here to avoid conflicts (or put them in an appropriate version block 655 so it continues to just work on old dmd), but wait a couple releases before making the 656 transition so this module remains usable with older versions of dmd. 657 658 You may have optional dependencies if needed by putting them in version blocks or 659 template functions. You may also extend the module with other modules with UFCS without 660 actually editing this - that is nice to do if you can. 661 662 Try to make functions work the same way across operating systems. I typically make 663 it thinly wrap Windows, then emulate that on Linux. 664 665 A goal of this is to keep a gui hello world to less than 250 KB. This means avoiding 666 Phobos! So try to avoid it. 667 668 See more comments throughout the source. 669 670 I realize this file is fairly large, but over half that is just bindings at the bottom 671 or documentation at the top. Some of the classes are a bit big too, but hopefully easy 672 to understand. I suggest you jump around the source by looking for a particular 673 declaration you're interested in, like `class SimpleWindow` using your editor's search 674 function, then look at one piece at a time. 675 676 Authors: Adam D. Ruppe with the help of others. If you need help, please email me with 677 destructionator@gmail.com or find me on IRC. Our channel is #d on Freenode and you can 678 ping me, adam_d_ruppe, and I'll usually see it if I'm around. 679 680 I live in the eastern United States, so I will most likely not be around at night in 681 that US east timezone. 682 683 License: Copyright Adam D. Ruppe, 2011-2021. Released under the Boost Software License. 684 685 Building documentation: use my adrdox generator, `dub run adrdox`. 686 687 Examples: 688 689 $(DIV $(ID Event-example)) 690 $(H3 $(ID event-example) Event example) 691 This program creates a window and draws events inside them as they 692 happen, scrolling the text in the window as needed. Run this program 693 and experiment to get a feel for where basic input events take place 694 in the library. 695 696 --- 697 // dmd example.d simpledisplay.d color.d 698 import arsd.simpledisplay; 699 import std.conv; 700 701 void main() { 702 auto window = new SimpleWindow(Size(500, 500), "Event example - simpledisplay.d"); 703 704 int y = 0; 705 706 void addLine(string text) { 707 auto painter = window.draw(); 708 709 if(y + painter.fontHeight >= window.height) { 710 painter.scrollArea(Point(0, 0), window.width, window.height, 0, painter.fontHeight); 711 y -= painter.fontHeight; 712 } 713 714 painter.outlineColor = Color.red; 715 painter.fillColor = Color.black; 716 painter.drawRectangle(Point(0, y), window.width, painter.fontHeight); 717 718 painter.outlineColor = Color.white; 719 720 painter.drawText(Point(10, y), text); 721 722 y += painter.fontHeight; 723 } 724 725 window.eventLoop(1000, 726 () { 727 addLine("Timer went off!"); 728 }, 729 (KeyEvent event) { 730 addLine(to!string(event)); 731 }, 732 (MouseEvent event) { 733 addLine(to!string(event)); 734 }, 735 (dchar ch) { 736 addLine(to!string(ch)); 737 } 738 ); 739 } 740 --- 741 742 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. 743 744 $(COMMENT 745 This program displays a pie chart. Clicking on a color will increase its share of the pie. 746 747 --- 748 749 --- 750 ) 751 752 753 +/ 754 module arsd.simpledisplay; 755 756 // FIXME: tetris demo 757 // FIXME: space invaders demo 758 // FIXME: asteroids demo 759 760 /++ $(ID Pong-example) 761 $(H3 Pong) 762 763 This program creates a little Pong-like game. Player one is controlled 764 with the keyboard. Player two is controlled with the mouse. It demos 765 the pulse timer, event handling, and some basic drawing. 766 +/ 767 version(demos) 768 unittest { 769 // dmd example.d simpledisplay.d color.d 770 import arsd.simpledisplay; 771 772 enum paddleMovementSpeed = 8; 773 enum paddleHeight = 48; 774 775 void main() { 776 auto window = new SimpleWindow(600, 400, "Pong game!"); 777 778 int playerOnePosition, playerTwoPosition; 779 int playerOneMovement, playerTwoMovement; 780 int playerOneScore, playerTwoScore; 781 782 int ballX, ballY; 783 int ballDx, ballDy; 784 785 void serve() { 786 import std.random; 787 788 ballX = window.width / 2; 789 ballY = window.height / 2; 790 ballDx = uniform(-4, 4) * 3; 791 ballDy = uniform(-4, 4) * 3; 792 if(ballDx == 0) 793 ballDx = uniform(0, 2) == 0 ? 3 : -3; 794 } 795 796 serve(); 797 798 window.eventLoop(50, // set a 50 ms timer pulls 799 // This runs once per timer pulse 800 delegate () { 801 auto painter = window.draw(); 802 803 painter.clear(); 804 805 // Update everyone's motion 806 playerOnePosition += playerOneMovement; 807 playerTwoPosition += playerTwoMovement; 808 809 ballX += ballDx; 810 ballY += ballDy; 811 812 // Bounce off the top and bottom edges of the window 813 if(ballY + 7 >= window.height) 814 ballDy = -ballDy; 815 if(ballY - 8 <= 0) 816 ballDy = -ballDy; 817 818 // Bounce off the paddle, if it is in position 819 if(ballX - 8 <= 16) { 820 if(ballY + 7 > playerOnePosition && ballY - 8 < playerOnePosition + paddleHeight) { 821 ballDx = -ballDx + 1; // add some speed to keep it interesting 822 ballDy += playerOneMovement; // and y movement based on your controls too 823 ballX = 24; // move it past the paddle so it doesn't wiggle inside 824 } else { 825 // Missed it 826 playerTwoScore ++; 827 serve(); 828 } 829 } 830 831 if(ballX + 7 >= window.width - 16) { // do the same thing but for player 1 832 if(ballY + 7 > playerTwoPosition && ballY - 8 < playerTwoPosition + paddleHeight) { 833 ballDx = -ballDx - 1; 834 ballDy += playerTwoMovement; 835 ballX = window.width - 24; 836 } else { 837 // Missed it 838 playerOneScore ++; 839 serve(); 840 } 841 } 842 843 // Draw the paddles 844 painter.outlineColor = Color.black; 845 painter.drawLine(Point(16, playerOnePosition), Point(16, playerOnePosition + paddleHeight)); 846 painter.drawLine(Point(window.width - 16, playerTwoPosition), Point(window.width - 16, playerTwoPosition + paddleHeight)); 847 848 // Draw the ball 849 painter.fillColor = Color.red; 850 painter.outlineColor = Color.yellow; 851 painter.drawEllipse(Point(ballX - 8, ballY - 8), Point(ballX + 7, ballY + 7)); 852 853 // Draw the score 854 painter.outlineColor = Color.blue; 855 import std.conv; 856 painter.drawText(Point(64, 4), to!string(playerOneScore)); 857 painter.drawText(Point(window.width - 64, 4), to!string(playerTwoScore)); 858 859 }, 860 delegate (KeyEvent event) { 861 // Player 1's controls are the arrow keys on the keyboard 862 if(event.key == Key.Down) 863 playerOneMovement = event.pressed ? paddleMovementSpeed : 0; 864 if(event.key == Key.Up) 865 playerOneMovement = event.pressed ? -paddleMovementSpeed : 0; 866 867 }, 868 delegate (MouseEvent event) { 869 // Player 2's controls are mouse movement while the left button is held down 870 if(event.type == MouseEventType.motion && (event.modifierState & ModifierState.leftButtonDown)) { 871 if(event.dy > 0) 872 playerTwoMovement = paddleMovementSpeed; 873 else if(event.dy < 0) 874 playerTwoMovement = -paddleMovementSpeed; 875 } else { 876 playerTwoMovement = 0; 877 } 878 } 879 ); 880 } 881 } 882 883 /++ $(H3 $(ID example-minesweeper) Minesweeper) 884 885 This minesweeper demo shows how we can implement another classic 886 game with simpledisplay and shows some mouse input and basic output 887 code. 888 +/ 889 version(demos) 890 unittest { 891 import arsd.simpledisplay; 892 893 enum GameSquare { 894 mine = 0, 895 clear, 896 m1, m2, m3, m4, m5, m6, m7, m8 897 } 898 899 enum UserSquare { 900 unknown, 901 revealed, 902 flagged, 903 questioned 904 } 905 906 enum GameState { 907 inProgress, 908 lose, 909 win 910 } 911 912 GameSquare[] board; 913 UserSquare[] userState; 914 GameState gameState; 915 int boardWidth; 916 int boardHeight; 917 918 bool isMine(int x, int y) { 919 if(x < 0 || y < 0 || x >= boardWidth || y >= boardHeight) 920 return false; 921 return board[y * boardWidth + x] == GameSquare.mine; 922 } 923 924 GameState reveal(int x, int y) { 925 if(board[y * boardWidth + x] == GameSquare.clear) { 926 floodFill(userState, boardWidth, boardHeight, 927 UserSquare.unknown, UserSquare.revealed, 928 x, y, 929 (x, y) { 930 if(board[y * boardWidth + x] == GameSquare.clear) 931 return true; 932 else { 933 userState[y * boardWidth + x] = UserSquare.revealed; 934 return false; 935 } 936 }); 937 } else { 938 userState[y * boardWidth + x] = UserSquare.revealed; 939 if(isMine(x, y)) 940 return GameState.lose; 941 } 942 943 foreach(state; userState) { 944 if(state == UserSquare.unknown || state == UserSquare.questioned) 945 return GameState.inProgress; 946 } 947 948 return GameState.win; 949 } 950 951 void initializeBoard(int width, int height, int numberOfMines) { 952 boardWidth = width; 953 boardHeight = height; 954 board.length = width * height; 955 956 userState.length = width * height; 957 userState[] = UserSquare.unknown; 958 959 import std.algorithm, std.random, std.range; 960 961 board[] = GameSquare.clear; 962 963 foreach(minePosition; randomSample(iota(0, board.length), numberOfMines)) 964 board[minePosition] = GameSquare.mine; 965 966 int x; 967 int y; 968 foreach(idx, ref square; board) { 969 if(square == GameSquare.clear) { 970 int danger = 0; 971 danger += isMine(x-1, y-1)?1:0; 972 danger += isMine(x-1, y)?1:0; 973 danger += isMine(x-1, y+1)?1:0; 974 danger += isMine(x, y-1)?1:0; 975 danger += isMine(x, y+1)?1:0; 976 danger += isMine(x+1, y-1)?1:0; 977 danger += isMine(x+1, y)?1:0; 978 danger += isMine(x+1, y+1)?1:0; 979 980 square = cast(GameSquare) (danger + 1); 981 } 982 983 x++; 984 if(x == width) { 985 x = 0; 986 y++; 987 } 988 } 989 } 990 991 void redraw(SimpleWindow window) { 992 import std.conv; 993 994 auto painter = window.draw(); 995 996 painter.clear(); 997 998 final switch(gameState) with(GameState) { 999 case inProgress: 1000 break; 1001 case win: 1002 painter.fillColor = Color.green; 1003 painter.drawRectangle(Point(0, 0), window.width, window.height); 1004 return; 1005 case lose: 1006 painter.fillColor = Color.red; 1007 painter.drawRectangle(Point(0, 0), window.width, window.height); 1008 return; 1009 } 1010 1011 int x = 0; 1012 int y = 0; 1013 1014 foreach(idx, square; board) { 1015 auto state = userState[idx]; 1016 1017 final switch(state) with(UserSquare) { 1018 case unknown: 1019 painter.outlineColor = Color.black; 1020 painter.fillColor = Color(128,128,128); 1021 1022 painter.drawRectangle( 1023 Point(x * 20, y * 20), 1024 20, 20 1025 ); 1026 break; 1027 case revealed: 1028 if(square == GameSquare.clear) { 1029 painter.outlineColor = Color.white; 1030 painter.fillColor = Color.white; 1031 1032 painter.drawRectangle( 1033 Point(x * 20, y * 20), 1034 20, 20 1035 ); 1036 } else { 1037 painter.outlineColor = Color.black; 1038 painter.fillColor = Color.white; 1039 1040 painter.drawText( 1041 Point(x * 20, y * 20), 1042 to!string(square)[1..2], 1043 Point(x * 20 + 20, y * 20 + 20), 1044 TextAlignment.Center | TextAlignment.VerticalCenter); 1045 } 1046 break; 1047 case flagged: 1048 painter.outlineColor = Color.black; 1049 painter.fillColor = Color.red; 1050 painter.drawRectangle( 1051 Point(x * 20, y * 20), 1052 20, 20 1053 ); 1054 break; 1055 case questioned: 1056 painter.outlineColor = Color.black; 1057 painter.fillColor = Color.yellow; 1058 painter.drawRectangle( 1059 Point(x * 20, y * 20), 1060 20, 20 1061 ); 1062 break; 1063 } 1064 1065 x++; 1066 if(x == boardWidth) { 1067 x = 0; 1068 y++; 1069 } 1070 } 1071 1072 } 1073 1074 void main() { 1075 auto window = new SimpleWindow(200, 200); 1076 1077 initializeBoard(10, 10, 10); 1078 1079 redraw(window); 1080 window.eventLoop(0, 1081 delegate (MouseEvent me) { 1082 if(me.type != MouseEventType.buttonPressed) 1083 return; 1084 auto x = me.x / 20; 1085 auto y = me.y / 20; 1086 if(x >= 0 && x < boardWidth && y >= 0 && y < boardHeight) { 1087 if(me.button == MouseButton.left) { 1088 gameState = reveal(x, y); 1089 } else { 1090 userState[y*boardWidth+x] = UserSquare.flagged; 1091 } 1092 redraw(window); 1093 } 1094 } 1095 ); 1096 } 1097 } 1098 1099 /* 1100 version(OSX) { 1101 version=without_opengl; 1102 version=allow_unimplemented_features; 1103 version=OSXCocoa; 1104 pragma(linkerDirective, "-framework Cocoa"); 1105 } 1106 */ 1107 1108 version(without_opengl) { 1109 enum SdpyIsUsingIVGLBinds = false; 1110 } else /*version(Posix)*/ { 1111 static if (__traits(compiles, (){import iv.glbinds;})) { 1112 enum SdpyIsUsingIVGLBinds = true; 1113 public import iv.glbinds; 1114 //pragma(msg, "SDPY: using iv.glbinds"); 1115 } else { 1116 enum SdpyIsUsingIVGLBinds = false; 1117 } 1118 //} else { 1119 // enum SdpyIsUsingIVGLBinds = false; 1120 } 1121 1122 1123 version(Windows) { 1124 //import core.sys.windows.windows; 1125 import core.sys.windows.winnls; 1126 import core.sys.windows.windef; 1127 import core.sys.windows.basetyps; 1128 import core.sys.windows.winbase; 1129 import core.sys.windows.winuser; 1130 import core.sys.windows.shellapi; 1131 import core.sys.windows.wingdi; 1132 static import gdi = core.sys.windows.wingdi; // so i 1133 1134 pragma(lib, "gdi32"); 1135 pragma(lib, "user32"); 1136 1137 // for AlphaBlend... a breaking change.... 1138 version(CRuntime_DigitalMars) { } else 1139 pragma(lib, "msimg32"); 1140 } else version (linux) { 1141 //k8: this is hack for rdmd. sorry. 1142 static import core.sys.linux.epoll; 1143 static import core.sys.linux.timerfd; 1144 } 1145 1146 1147 // FIXME: icons on Windows don't look quite right, I think the transparency mask is off. 1148 1149 // http://wiki.dlang.org/Simpledisplay.d 1150 1151 // see : http://www.sbin.org/doc/Xlib/chapt_09.html section on Keyboard Preferences re: scroll lock led 1152 1153 // Cool stuff: I want right alt and scroll lock to do different stuff for personal use. maybe even right ctrl 1154 // but can i control the scroll lock led 1155 1156 1157 // Note: if you are using Image on X, you might want to do: 1158 /* 1159 static if(UsingSimpledisplayX11) { 1160 if(!Image.impl.xshmAvailable) { 1161 // the images will use the slower XPutImage, you might 1162 // want to consider an alternative method to get better speed 1163 } 1164 } 1165 1166 If the shared memory extension is available though, simpledisplay uses it 1167 for a significant speed boost whenever you draw large Images. 1168 */ 1169 1170 // 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. 1171 1172 // WARNING: if you are using with_eventloop, don't forget to call XFlush(XDisplayConnection.get()); before calling loop()! 1173 1174 /* 1175 Biggest FIXME: 1176 make sure the key event numbers match between X and Windows OR provide symbolic constants on each system 1177 1178 clean up opengl contexts when their windows close 1179 1180 fix resizing the bitmaps/pixmaps 1181 */ 1182 1183 // BTW on Windows: 1184 // -L/SUBSYSTEM:WINDOWS:5.0 1185 // to dmd will make a nice windows binary w/o a console if you want that. 1186 1187 /* 1188 Stuff to add: 1189 1190 use multibyte functions everywhere we can 1191 1192 OpenGL windows 1193 more event stuff 1194 extremely basic windows w/ no decoration for tooltips, splash screens, etc. 1195 1196 1197 resizeEvent 1198 and make the windows non-resizable by default, 1199 or perhaps stretched (if I can find something in X like StretchBlt) 1200 1201 take a screenshot function! 1202 1203 Pens and brushes? 1204 Maybe a global event loop? 1205 1206 Mouse deltas 1207 Key items 1208 */ 1209 1210 /* 1211 From MSDN: 1212 1213 You can also use the GET_X_LPARAM or GET_Y_LPARAM macro to extract the x- or y-coordinate. 1214 1215 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. 1216 1217 */ 1218 1219 version(linux) { 1220 version = X11; 1221 version(without_libnotify) { 1222 // we cool 1223 } 1224 else 1225 version = libnotify; 1226 } 1227 1228 version(libnotify) { 1229 pragma(lib, "dl"); 1230 import core.sys.posix.dlfcn; 1231 1232 void delegate()[int] libnotify_action_delegates; 1233 int libnotify_action_delegates_count; 1234 extern(C) static void libnotify_action_callback_sdpy(void* notification, char* action, void* user_data) { 1235 auto idx = cast(int) user_data; 1236 if(auto dgptr = idx in libnotify_action_delegates) { 1237 (*dgptr)(); 1238 libnotify_action_delegates.remove(idx); 1239 } 1240 } 1241 1242 struct C_DynamicLibrary { 1243 void* handle; 1244 this(string name) { 1245 handle = dlopen((name ~ "\0").ptr, RTLD_NOW); 1246 if(handle is null) 1247 throw new Exception("dlopen"); 1248 } 1249 1250 void close() { 1251 dlclose(handle); 1252 } 1253 1254 ~this() { 1255 // close 1256 } 1257 1258 // FIXME: this looks up by name every time.... 1259 template call(string func, Ret, Args...) { 1260 extern(C) Ret function(Args) fptr; 1261 typeof(fptr) call() { 1262 fptr = cast(typeof(fptr)) dlsym(handle, func); 1263 return fptr; 1264 } 1265 } 1266 } 1267 1268 C_DynamicLibrary* libnotify; 1269 } 1270 1271 version(OSX) { 1272 version(OSXCocoa) {} 1273 else { version = X11; } 1274 } 1275 //version = OSXCocoa; // this was written by KennyTM 1276 version(FreeBSD) 1277 version = X11; 1278 version(Solaris) 1279 version = X11; 1280 1281 version(X11) { 1282 version(without_xft) {} 1283 else version=with_xft; 1284 } 1285 1286 void featureNotImplemented()() { 1287 version(allow_unimplemented_features) 1288 throw new NotYetImplementedException(); 1289 else 1290 static assert(0); 1291 } 1292 1293 // these are so the static asserts don't trigger unless you want to 1294 // add support to it for an OS 1295 version(Windows) 1296 version = with_timer; 1297 version(linux) 1298 version = with_timer; 1299 1300 version(with_timer) 1301 enum bool SimpledisplayTimerAvailable = true; 1302 else 1303 enum bool SimpledisplayTimerAvailable = false; 1304 1305 /// 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. 1306 version(Windows) 1307 enum bool UsingSimpledisplayWindows = true; 1308 else 1309 enum bool UsingSimpledisplayWindows = false; 1310 1311 /// 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. 1312 version(X11) 1313 enum bool UsingSimpledisplayX11 = true; 1314 else 1315 enum bool UsingSimpledisplayX11 = false; 1316 1317 /// 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. 1318 version(OSXCocoa) 1319 enum bool UsingSimpledisplayCocoa = true; 1320 else 1321 enum bool UsingSimpledisplayCocoa = false; 1322 1323 /// Does this platform support multiple windows? If not, trying to create another will cause it to throw an exception. 1324 version(Windows) 1325 enum multipleWindowsSupported = true; 1326 else version(X11) 1327 enum multipleWindowsSupported = true; 1328 else version(OSXCocoa) 1329 enum multipleWindowsSupported = true; 1330 else 1331 static assert(0); 1332 1333 version(without_opengl) 1334 enum bool OpenGlEnabled = false; 1335 else 1336 enum bool OpenGlEnabled = true; 1337 1338 /++ 1339 Adds the necessary pragmas to your application to use the Windows gui subsystem. 1340 If you mix this in above your `main` function, you no longer need to use the linker 1341 flags explicitly. It does the necessary version blocks for various compilers and runtimes. 1342 1343 It does nothing if not compiling for Windows, so you need not version it out yourself. 1344 1345 Please note that Windows gui subsystem applications must NOT use std.stdio's stdout and 1346 stderr writeln. It will fail and throw an exception. 1347 1348 This will NOT work with plain `dmd` on Windows; you must use `dmd -m32mscoff` or `dmd -m64`. 1349 1350 History: 1351 Added November 24, 2021 (dub v10.4) 1352 +/ 1353 mixin template EnableWindowsSubsystem() { 1354 version(Windows) 1355 version(CRuntime_Microsoft) { 1356 pragma(linkerDirective, "/subsystem:windows"); 1357 version(LDC) 1358 pragma(linkerDirective, "/entry:wmainCRTStartup"); 1359 else 1360 pragma(linkerDirective, "/entry:mainCRTStartup"); 1361 } 1362 } 1363 1364 1365 /++ 1366 After selecting a type from [WindowTypes], you may further customize 1367 its behavior by setting one or more of these flags. 1368 1369 1370 The different window types have different meanings of `normal`. If the 1371 window type already is a good match for what you want to do, you should 1372 just use [WindowFlags.normal], the default, which will do the right thing 1373 for your users. 1374 1375 The window flags will not always be honored by the operating system 1376 and window managers; they are hints, not commands. 1377 +/ 1378 enum WindowFlags : int { 1379 normal = 0, /// 1380 skipTaskbar = 1, /// 1381 alwaysOnTop = 2, /// 1382 alwaysOnBottom = 4, /// 1383 cannotBeActivated = 8, /// 1384 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. 1385 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. 1386 /++ 1387 Sets the window as a short-lived child of its parent, but unlike an ordinary child, 1388 it is still a top-level window. This should NOT be set separately for most window types. 1389 1390 A transient window will not keep the application open if its main window closes. 1391 1392 $(PITFALL This may not be correctly implemented and its behavior is subject to change.) 1393 1394 1395 From the ICCM: 1396 1397 $(BLOCKQUOTE 1398 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. 1399 1400 $(CITE https://tronche.com/gui/x/icccm/sec-4.html) 1401 ) 1402 1403 So if you are using a window type that already describes this like [WindowTypes.dropdownMenu] etc., you should not use this flag. 1404 1405 History: 1406 Added February 23, 2021 but not yet stabilized. 1407 +/ 1408 transient = 64, 1409 /++ 1410 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. 1411 1412 This is currently only used for the WM_TAKE_FOCUS protocol on X11 at this time. 1413 1414 History: 1415 Added April 1, 2022 1416 +/ 1417 managesChildWindowFocus = 128, 1418 1419 dontAutoShow = 0x1000_0000, /// Don't automatically show window after creation; you will have to call `show()` manually. 1420 } 1421 1422 /++ 1423 When creating a window, you can pass a type to SimpleWindow's constructor, 1424 then further customize the window by changing `WindowFlags`. 1425 1426 1427 You should mostly only need [normal], [undecorated], and [eventOnly] for normal 1428 use. The others are there to build a foundation for a higher level GUI toolkit, 1429 but are themselves not as high level as you might think from their names. 1430 1431 This list is based on the EMWH spec for X11. 1432 http://standards.freedesktop.org/wm-spec/1.4/ar01s05.html#idm139704063786896 1433 +/ 1434 enum WindowTypes : int { 1435 /// An ordinary application window. 1436 normal, 1437 /// 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. 1438 undecorated, 1439 /// 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. 1440 eventOnly, 1441 /// A drop down menu, such as from a menu bar 1442 dropdownMenu, 1443 /// A popup menu, such as from a right click 1444 popupMenu, 1445 /// A popup bubble notification 1446 notification, 1447 /* 1448 menu, /// a tearable menu bar 1449 splashScreen, /// a loading splash screen for your application 1450 tooltip, /// A tiny window showing temporary help text or something. 1451 comboBoxDropdown, 1452 dialog, 1453 toolbar 1454 */ 1455 /// a child nested inside the parent. You must pass a parent window to the ctor 1456 nestedChild, 1457 1458 /++ 1459 The type you get when you pass in an existing browser handle, which means most 1460 of simpledisplay's fancy things will not be done since they were never set up. 1461 1462 Using this to the main SimpleWindow constructor explicitly will trigger an assertion 1463 failure; you should use the existing handle constructor. 1464 1465 History: 1466 Added November 17, 2022 (previously it would have type `normal`) 1467 +/ 1468 minimallyWrapped 1469 } 1470 1471 1472 private __gshared ushort sdpyOpenGLContextVersion = 0; // default: use legacy call 1473 private __gshared bool sdpyOpenGLContextCompatible = true; // default: allow "deprecated" features 1474 private __gshared char* sdpyWindowClassStr = null; 1475 private __gshared bool sdpyOpenGLContextAllowFallback = false; 1476 1477 /** 1478 Set OpenGL context version to use. This has no effect on non-OpenGL windows. 1479 You may want to change context version if you want to use advanced shaders or 1480 other modern OpenGL techinques. This setting doesn't affect already created 1481 windows. You may use version 2.1 as your default, which should be supported 1482 by any box since 2006, so seems to be a reasonable choice. 1483 1484 Note that by default version is set to `0`, which forces SimpleDisplay to use 1485 old context creation code without any version specified. This is the safest 1486 way to init OpenGL, but it may not give you access to advanced features. 1487 1488 See available OpenGL versions here: https://en.wikipedia.org/wiki/OpenGL 1489 */ 1490 void setOpenGLContextVersion() (ubyte hi, ubyte lo) { sdpyOpenGLContextVersion = cast(ushort)(hi<<8|lo); } 1491 1492 /** 1493 Set OpenGL context mode. Modern (3.0+) OpenGL versions deprecated old fixed 1494 pipeline functions, and without "compatible" mode you won't be able to use 1495 your old non-shader-based code with such contexts. By default SimpleDisplay 1496 creates compatible context, so you can gradually upgrade your OpenGL code if 1497 you want to (or leave it as is, as it should "just work"). 1498 */ 1499 @property void openGLContextCompatible() (bool v) { sdpyOpenGLContextCompatible = v; } 1500 1501 /** 1502 Set to `true` to allow creating OpenGL context with lower version than requested 1503 instead of throwing. If fallback was activated (or legacy OpenGL was requested), 1504 `openGLContextFallbackActivated()` will return `true`. 1505 */ 1506 @property void openGLContextAllowFallback() (bool v) { sdpyOpenGLContextAllowFallback = v; } 1507 1508 /** 1509 After creating OpenGL window, you can check this to see if you got only "legacy" OpenGL context. 1510 */ 1511 @property bool openGLContextFallbackActivated() () { return (sdpyOpenGLContextVersion == 0); } 1512 1513 1514 /** 1515 Set window class name for all following `new SimpleWindow()` calls. 1516 1517 WARNING! For Windows, you should set your class name before creating any 1518 window, and NEVER change it after that! 1519 */ 1520 void sdpyWindowClass (const(char)[] v) { 1521 import core.stdc.stdlib : realloc; 1522 if (v.length == 0) v = "SimpleDisplayWindow"; 1523 sdpyWindowClassStr = cast(char*)realloc(sdpyWindowClassStr, v.length+1); 1524 if (sdpyWindowClassStr is null) return; // oops 1525 sdpyWindowClassStr[0..v.length+1] = 0; 1526 sdpyWindowClassStr[0..v.length] = v[]; 1527 } 1528 1529 /** 1530 Get current window class name. 1531 */ 1532 string sdpyWindowClass () { 1533 if (sdpyWindowClassStr is null) return null; 1534 foreach (immutable idx; 0..size_t.max-1) { 1535 if (sdpyWindowClassStr[idx] == 0) return sdpyWindowClassStr[0..idx].idup; 1536 } 1537 return null; 1538 } 1539 1540 /++ 1541 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. 1542 1543 If you want per-monitor dpi values, check [SimpleWindow.actualDpi], but you can fall back to this if it returns 0. 1544 +/ 1545 float[2] getDpi() { 1546 float[2] dpi; 1547 version(Windows) { 1548 HDC screen = GetDC(null); 1549 dpi[0] = GetDeviceCaps(screen, LOGPIXELSX); 1550 dpi[1] = GetDeviceCaps(screen, LOGPIXELSY); 1551 } else version(X11) { 1552 auto display = XDisplayConnection.get; 1553 auto screen = DefaultScreen(display); 1554 1555 void fallback() { 1556 /+ 1557 // 25.4 millimeters in an inch... 1558 dpi[0] = cast(float) DisplayWidth(display, screen) / DisplayWidthMM(display, screen) * 25.4; 1559 dpi[1] = cast(float) DisplayHeight(display, screen) / DisplayHeightMM(display, screen) * 25.4; 1560 +/ 1561 1562 // the physical size isn't actually as important as the logical size since this is 1563 // all about scaling really 1564 dpi[0] = 96; 1565 dpi[1] = 96; 1566 } 1567 1568 auto xft = getXftDpi(); 1569 if(xft is float.init) 1570 fallback(); 1571 else { 1572 dpi[0] = xft; 1573 dpi[1] = xft; 1574 } 1575 } 1576 1577 return dpi; 1578 } 1579 1580 version(X11) 1581 float getXftDpi() { 1582 auto display = XDisplayConnection.get; 1583 1584 char* resourceString = XResourceManagerString(display); 1585 XrmInitialize(); 1586 1587 if (resourceString) { 1588 auto db = XrmGetStringDatabase(resourceString); 1589 XrmValue value; 1590 char* type; 1591 if (XrmGetResource(db, "Xft.dpi", "String", &type, &value) == true) { 1592 if (value.addr) { 1593 import core.stdc.stdlib; 1594 return atof(cast(char*) value.addr); 1595 } 1596 } 1597 } 1598 1599 return float.init; 1600 } 1601 1602 /++ 1603 Implementation used by [SimpleWindow.takeScreenshot]. 1604 1605 Params: 1606 handle = the native window handle. If `NativeWindowHandle.init`, it will attempt to get the whole screen. 1607 width = the width of the image you wish to capture. If 0, it will attempt to capture the full width of the target. 1608 height = the height of the image you wish to capture. If 0, it will attempt to capture the full height of the target. 1609 x = the x-offset of the image to capture, from the left. 1610 y = the y-offset of the image to capture, from the top. 1611 1612 History: 1613 Added on March 14, 2021 1614 1615 Documented public on September 23, 2021 with full support for null params (dub 10.3) 1616 1617 +/ 1618 TrueColorImage trueColorImageFromNativeHandle(NativeWindowHandle handle, int width = 0, int height = 0, int x = 0, int y = 0) { 1619 TrueColorImage got; 1620 version(X11) { 1621 auto display = XDisplayConnection.get; 1622 if(handle == 0) 1623 handle = RootWindow(display, DefaultScreen(display)); 1624 1625 if(width == 0 || height == 0) { 1626 Window root; 1627 int xpos, ypos; 1628 uint widthret, heightret, borderret, depthret; 1629 XGetGeometry(display, handle, &root, &xpos, &ypos, &widthret, &heightret, &borderret, &depthret); 1630 1631 if(width == 0) 1632 width = widthret; 1633 if(height == 0) 1634 height = heightret; 1635 } 1636 1637 auto image = XGetImage(display, handle, x, y, width, height, (cast(c_ulong) ~0) /*AllPlanes*/, ImageFormat.ZPixmap); 1638 1639 // https://github.com/adamdruppe/arsd/issues/98 1640 1641 auto i = new Image(image); 1642 got = i.toTrueColorImage(); 1643 1644 XDestroyImage(image); 1645 } else version(Windows) { 1646 auto hdc = GetDC(handle); 1647 scope(exit) ReleaseDC(handle, hdc); 1648 1649 if(width == 0 || height == 0) { 1650 BITMAP bmHeader; 1651 auto bm = GetCurrentObject(hdc, OBJ_BITMAP); 1652 GetObject(bm, BITMAP.sizeof, &bmHeader); 1653 if(width == 0) 1654 width = bmHeader.bmWidth; 1655 if(height == 0) 1656 height = bmHeader.bmHeight; 1657 } 1658 1659 auto i = new Image(width, height); 1660 HDC hdcMem = CreateCompatibleDC(hdc); 1661 HBITMAP hbmOld = SelectObject(hdcMem, i.handle); 1662 BitBlt(hdcMem, x, y, width, height, hdc, 0, 0, SRCCOPY); 1663 SelectObject(hdcMem, hbmOld); 1664 DeleteDC(hdcMem); 1665 1666 got = i.toTrueColorImage(); 1667 } else featureNotImplemented(); 1668 1669 return got; 1670 } 1671 1672 version(Windows) extern(Windows) private alias SetProcessDpiAwarenessContext_t = BOOL function(HANDLE); 1673 version(Windows) extern(Windows) private __gshared UINT function(HWND) GetDpiForWindow; 1674 version(Windows) extern(Windows) private __gshared BOOL function(UINT, UINT, PVOID, UINT, UINT) SystemParametersInfoForDpi; 1675 1676 version(Windows) 1677 shared static this() { 1678 auto lib = LoadLibrary("User32.dll"); 1679 if(lib is null) 1680 return; 1681 //scope(exit) 1682 //FreeLibrary(lib); 1683 1684 SetProcessDpiAwarenessContext_t SetProcessDpiAwarenessContext = cast(SetProcessDpiAwarenessContext_t) GetProcAddress(lib, "SetProcessDpiAwarenessContext"); 1685 1686 if(SetProcessDpiAwarenessContext is null) 1687 return; 1688 1689 enum DPI_AWARENESS_CONTEXT_PER_MONITOR_AWARE_V2 = cast(HANDLE) -4; 1690 if(!SetProcessDpiAwarenessContext(DPI_AWARENESS_CONTEXT_PER_MONITOR_AWARE_V2)) { 1691 //writeln(GetLastError()); 1692 } 1693 1694 GetDpiForWindow = cast(typeof(GetDpiForWindow)) GetProcAddress(lib, "GetDpiForWindow"); 1695 SystemParametersInfoForDpi = cast(typeof(SystemParametersInfoForDpi)) GetProcAddress(lib, "SystemParametersInfoForDpi"); 1696 } 1697 1698 /++ 1699 Blocking mode for event loop calls associated with a window instance. 1700 1701 History: 1702 Added December 8, 2021 (dub v10.5). Prior to that, all calls to 1703 `window.eventLoop` were the same as calls to `EventLoop.get.run`; that 1704 is, all would block until the application quit. 1705 1706 That behavior can still be achieved here with `untilApplicationQuits`, 1707 or explicitly calling the top-level `EventLoop.get.run` function. 1708 +/ 1709 enum BlockingMode { 1710 /++ 1711 The event loop call will block until the whole application is ready 1712 to quit if it is the only one running, but if it is nested inside 1713 another one, it will only block until the window you're calling it on 1714 closes. 1715 +/ 1716 automatic = 0x00, 1717 /++ 1718 The event loop call will only return when the whole application 1719 is ready to quit. This usually means all windows have been closed. 1720 1721 This is appropriate for your main application event loop. 1722 +/ 1723 untilApplicationQuits = 0x01, 1724 /++ 1725 The event loop will return when the window you're calling it on 1726 closes. If there are other windows still open, they may be destroyed 1727 unless you have another event loop running later. 1728 1729 This might be appropriate for a modal dialog box loop. Remember that 1730 other windows are still processing input though, so you can end up 1731 with a lengthy call stack if this happens in a loop, similar to a 1732 recursive function (well, it literally is a recursive function, just 1733 not an obvious looking one). 1734 +/ 1735 untilWindowCloses = 0x02, 1736 /++ 1737 If an event loop is already running, this call will immediately 1738 return, allowing the existing loop to handle it. If not, this call 1739 will block until the condition you bitwise-or into the flag. 1740 1741 The default is to block until the application quits, same as with 1742 the `automatic` setting (since if it were nested, which triggers until 1743 window closes in automatic, this flag would instead not block at all), 1744 but if you used `BlockingMode.onlyIfNotNested | BlockingMode.untilWindowCloses`, 1745 it will only nest until the window closes. You might want that if you are 1746 going to open two windows simultaneously and want closing just one of them 1747 to trigger the event loop return. 1748 +/ 1749 onlyIfNotNested = 0x10, 1750 } 1751 1752 /++ 1753 The flagship window class. 1754 1755 1756 SimpleWindow tries to make ordinary windows very easy to create and use without locking you 1757 out of more advanced or complex features of the underlying windowing system. 1758 1759 For many applications, you can simply call `new SimpleWindow(some_width, some_height, "some title")` 1760 and get a suitable window to work with. 1761 1762 From there, you can opt into additional features, like custom resizability and OpenGL support 1763 with the next two constructor arguments. Or, if you need even more, you can set a window type 1764 and customization flags with the final two constructor arguments. 1765 1766 If none of that works for you, you can also create a window using native function calls, then 1767 wrap the window in a SimpleWindow instance by calling `new SimpleWindow(native_handle)`. Remember, 1768 though, if you do this, managing the window is still your own responsibility! Notably, you 1769 will need to destroy it yourself. 1770 +/ 1771 class SimpleWindow : CapableOfHandlingNativeEvent, CapableOfBeingDrawnUpon { 1772 1773 /++ 1774 Copies the window's current state into a [TrueColorImage]. 1775 1776 Be warned: this can be a very slow operation 1777 1778 History: 1779 Actually implemented on March 14, 2021 1780 +/ 1781 TrueColorImage takeScreenshot() { 1782 version(Windows) 1783 return trueColorImageFromNativeHandle(impl.hwnd, _width, _height); 1784 else version(OSXCocoa) 1785 throw new NotYetImplementedException(); 1786 else 1787 return trueColorImageFromNativeHandle(impl.window, _width, _height); 1788 } 1789 1790 /++ 1791 Returns the actual logical DPI for the window on its current display monitor. If the window 1792 straddles monitors, it will return the value of one or the other in a platform-defined manner. 1793 1794 Please note this function may return zero if it doesn't know the answer! 1795 1796 1797 On Windows, it returns the dpi per monitor if the operating system supports it (Windows 10), 1798 or a system dpi value if not, which will live-update if the OS supports it (Windows 8 and up). 1799 1800 On X, it reads the xrandr extension to determine monitor positions and sizes. On some systems, 1801 this is not provided, meaning it will return 0. Otherwise, it will determine which monitor the 1802 window primarily resides on by checking the center point of the window against the monitor map. 1803 1804 Returns: 1805 0 if unknown. Otherwise, a rounded value of dots per inch reported by the monitor. It 1806 assumes the X and Y dpi are the same. 1807 1808 History: 1809 Added November 26, 2021 (dub v10.4) 1810 1811 It said "physical dpi" in the description prior to July 29, 2022, but the behavior was 1812 always a logical value on Windows and usually one on Linux too, so now the docs reflect 1813 that. 1814 1815 Bugs: 1816 Probably plenty. I haven't done a lot of tests on this. I know it doesn't automatically 1817 just work on linux; you need to set ARSD_SCALING_FACTOR as an environment variable to 1818 set it. Set ARSD_SCALING_FACTOR=1;1.5 for example to set it to 1x on the primary monitor 1819 and 1.5 on the secondary monitor. 1820 1821 The local dpi is not necessarily related to the physical dpi of the monitor. The name 1822 is a historical misnomer - the real thing of interest is the scale factor and due to 1823 compatibility concerns the scale would modify dpi values to trick applications. But since 1824 that's the terminology common out there, I used it too. 1825 1826 See_Also: 1827 [getDpi] gives the value provided for the default monitor. Not necessarily the same 1828 as this since the window many be on a different monitor, but it is a reasonable fallback 1829 to use if `actualDpi` returns 0. 1830 1831 [onDpiChanged] is changed when `actualDpi` has changed. 1832 +/ 1833 int actualDpi() { 1834 if(!actualDpiLoadAttempted) { 1835 // FIXME: do the actual monitor we are on 1836 // and on X this is a good chance to load the monitor map. 1837 version(Windows) { 1838 if(GetDpiForWindow) 1839 actualDpi_ = GetDpiForWindow(impl.hwnd); 1840 } else version(X11) { 1841 if(!xRandrInfoLoadAttemped) { 1842 xRandrInfoLoadAttemped = true; 1843 if(!XRandrLibrary.attempted) { 1844 XRandrLibrary.loadDynamicLibrary(); 1845 } 1846 1847 if(XRandrLibrary.loadSuccessful) { 1848 auto display = XDisplayConnection.get; 1849 int scratch; 1850 int major, minor; 1851 if(!XRRQueryExtension(display, &xrrEventBase, &scratch)) 1852 goto fallback; 1853 1854 XRRQueryVersion(display, &major, &minor); 1855 if(major <= 1 && minor < 5) 1856 goto fallback; 1857 1858 int count; 1859 XRRMonitorInfo *monitors = XRRGetMonitors(display, RootWindow(display, DefaultScreen(display)), true, &count); 1860 if(monitors is null) 1861 goto fallback; 1862 scope(exit) XRRFreeMonitors(monitors); 1863 1864 MonitorInfo.info = MonitorInfo.info[0 .. 0]; 1865 MonitorInfo.info.assumeSafeAppend(); 1866 foreach(idx, monitor; monitors[0 .. count]) { 1867 MonitorInfo.info ~= MonitorInfo( 1868 Rectangle(Point(monitor.x, monitor.y), Size(monitor.width, monitor.height)), 1869 Size(monitor.mwidth, monitor.mheight), 1870 cast(int) (customScalingFactorForMonitor(cast(int) idx) * getDpi()[0]) 1871 ); 1872 1873 /+ 1874 if(monitor.mwidth == 0 || monitor.mheight == 0) 1875 // unknown physical size, just guess 96 to avoid divide by zero 1876 MonitorInfo.info ~= MonitorInfo( 1877 Rectangle(Point(monitor.x, monitor.y), Size(monitor.width, monitor.height)), 1878 Size(monitor.mwidth, monitor.mheight), 1879 96 1880 ); 1881 else 1882 // and actual thing 1883 MonitorInfo.info ~= MonitorInfo( 1884 Rectangle(Point(monitor.x, monitor.y), Size(monitor.width, monitor.height)), 1885 Size(monitor.mwidth, monitor.mheight), 1886 minInternal( 1887 // millimeter to int then rounding up. 1888 cast(int)(monitor.width * 25.4 / monitor.mwidth + 0.5), 1889 cast(int)(monitor.height * 25.4 / monitor.mheight + 0.5) 1890 ) 1891 ); 1892 +/ 1893 } 1894 // import std.stdio; writeln("Here", MonitorInfo.info); 1895 } 1896 } 1897 1898 if(XRandrLibrary.loadSuccessful) { 1899 updateActualDpi(true); 1900 //import std.stdio; writeln("updated"); 1901 1902 if(!requestedInput) { 1903 // this is what requests live updates should the configuration change 1904 // each time you select input, it sends an initial event, so very important 1905 // to not get into a loop of selecting input, getting event, updating data, 1906 // and reselecting input... 1907 requestedInput = true; 1908 XRRSelectInput(display, impl.window, RRScreenChangeNotifyMask); 1909 //import std.stdio; writeln("requested input"); 1910 } 1911 } else { 1912 fallback: 1913 // make sure we disable events that aren't coming 1914 xrrEventBase = -1; 1915 // best guess... respect the custom scaling user command to some extent at least though 1916 actualDpi_ = cast(int) (getDpi()[0] * customScalingFactorForMonitor(0)); 1917 } 1918 } 1919 actualDpiLoadAttempted = true; 1920 } 1921 return actualDpi_; 1922 } 1923 1924 private int actualDpi_; 1925 private bool actualDpiLoadAttempted; 1926 1927 version(X11) private { 1928 bool requestedInput; 1929 static bool xRandrInfoLoadAttemped; 1930 struct MonitorInfo { 1931 Rectangle position; 1932 Size size; 1933 int dpi; 1934 1935 static MonitorInfo[] info; 1936 } 1937 bool screenPositionKnown; 1938 int screenPositionX; 1939 int screenPositionY; 1940 void updateActualDpi(bool loadingNow = false) { 1941 if(!loadingNow && !actualDpiLoadAttempted) 1942 actualDpi(); // just to make it do the load 1943 foreach(idx, m; MonitorInfo.info) { 1944 if(m.position.contains(Point(screenPositionX + this.width / 2, screenPositionY + this.height / 2))) { 1945 bool changed = actualDpi_ && actualDpi_ != m.dpi; 1946 actualDpi_ = m.dpi; 1947 //import std.stdio; writeln("monitor ", idx); 1948 if(changed && onDpiChanged) 1949 onDpiChanged(); 1950 break; 1951 } 1952 } 1953 } 1954 } 1955 1956 /++ 1957 Sent when the window is moved to a new DPI context, for example, when it is dragged between monitors 1958 or if the window is moved to a new remote connection or a monitor is hot-swapped. 1959 1960 History: 1961 Added November 26, 2021 (dub v10.4) 1962 1963 See_Also: 1964 [actualDpi] 1965 +/ 1966 void delegate() onDpiChanged; 1967 1968 version(X11) { 1969 void recreateAfterDisconnect() { 1970 if(!stateDiscarded) return; 1971 1972 if(_parent !is null && _parent.stateDiscarded) 1973 _parent.recreateAfterDisconnect(); 1974 1975 bool wasHidden = hidden; 1976 1977 activeScreenPainter = null; // should already be done but just to confirm 1978 1979 actualDpi_ = 0; 1980 actualDpiLoadAttempted = false; 1981 xRandrInfoLoadAttemped = false; 1982 1983 impl.createWindow(_width, _height, _title, openglMode, _parent); 1984 1985 if(auto dh = dropHandler) { 1986 dropHandler = null; 1987 enableDragAndDrop(this, dh); 1988 } 1989 1990 if(recreateAdditionalConnectionState) 1991 recreateAdditionalConnectionState(); 1992 1993 hidden = wasHidden; 1994 stateDiscarded = false; 1995 } 1996 1997 bool stateDiscarded; 1998 void discardConnectionState() { 1999 if(XDisplayConnection.display) 2000 impl.dispose(); // if display is already null, it is hopeless to try to destroy stuff on it anyway 2001 if(discardAdditionalConnectionState) 2002 discardAdditionalConnectionState(); 2003 stateDiscarded = true; 2004 } 2005 2006 void delegate() discardAdditionalConnectionState; 2007 void delegate() recreateAdditionalConnectionState; 2008 2009 } 2010 2011 private DropHandler dropHandler; 2012 2013 SimpleWindow _parent; 2014 bool beingOpenKeepsAppOpen = true; 2015 /++ 2016 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. 2017 2018 The constructor tries to have sane default arguments, so for many cases, you only need to provide a few of them. 2019 2020 Params: 2021 2022 width = the width of the window's client area, in pixels 2023 height = the height of the window's client area, in pixels 2024 title = the title of the window (seen in the title bar, taskbar, etc.). You can change it after construction with the [SimpleWindow.title] property. 2025 opengl = [OpenGlOptions] are yes and no. If yes, it creates an OpenGL context on the window. 2026 resizable = [Resizability] has three options: 2027 $(P `allowResizing`, which allows the window to be resized by the user. The `windowResized` delegate will be called when the size is changed.) 2028 $(P `fixedSize` will not allow the user to resize the window.) 2029 $(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.) 2030 windowType = The type of window you want to make. 2031 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. 2032 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". 2033 +/ 2034 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) { 2035 claimGuiThread(); 2036 version(sdpy_thread_checks) assert(thisIsGuiThread); 2037 this._width = this._virtualWidth = width; 2038 this._height = this._virtualHeight = height; 2039 this.openglMode = opengl; 2040 version(X11) { 2041 // auto scale not implemented except with opengl and even there it is kinda weird 2042 if(resizable == Resizability.automaticallyScaleIfPossible && opengl == OpenGlOptions.no) 2043 resizable = Resizability.fixedSize; 2044 } 2045 this.resizability = resizable; 2046 this.windowType = windowType; 2047 this.customizationFlags = customizationFlags; 2048 this._title = (title is null ? "D Application" : title); 2049 this._parent = parent; 2050 impl.createWindow(width, height, this._title, opengl, parent); 2051 2052 if(windowType == WindowTypes.dropdownMenu || windowType == WindowTypes.popupMenu || windowType == WindowTypes.nestedChild || (customizationFlags & WindowFlags.transient)) 2053 beingOpenKeepsAppOpen = false; 2054 } 2055 2056 /// ditto 2057 this(int width, int height, string title, Resizability resizable, OpenGlOptions opengl = OpenGlOptions.no, WindowTypes windowType = WindowTypes.normal, int customizationFlags = WindowFlags.normal, SimpleWindow parent = null) { 2058 this(width, height, title, opengl, resizable, windowType, customizationFlags, parent); 2059 } 2060 2061 /// Same as above, except using the `Size` struct instead of separate width and height. 2062 this(Size size, string title = null, OpenGlOptions opengl = OpenGlOptions.no, Resizability resizable = Resizability.automaticallyScaleIfPossible) { 2063 this(size.width, size.height, title, opengl, resizable); 2064 } 2065 2066 /// ditto 2067 this(Size size, string title, Resizability resizable, OpenGlOptions opengl = OpenGlOptions.no) { 2068 this(size, title, opengl, resizable); 2069 } 2070 2071 2072 /++ 2073 Creates a window based on the given [Image]. It's client area 2074 width and height is equal to the image. (A window's client area 2075 is the drawable space inside; it excludes the title bar, etc.) 2076 2077 Windows based on images will not be resizable and do not use OpenGL. 2078 2079 It will draw the image in upon creation, but this will be overwritten 2080 upon any draws, including the initial window visible event. 2081 2082 You probably do not want to use this and it may be removed from 2083 the library eventually, or I might change it to be a "permanent" 2084 background image; one that is automatically drawn on it before any 2085 other drawing event. idk. 2086 +/ 2087 this(Image image, string title = null) { 2088 this(image.width, image.height, title); 2089 this.image = image; 2090 } 2091 2092 /++ 2093 Wraps a native window handle with very little additional processing - notably no destruction 2094 this is incomplete so don't use it for much right now. The purpose of this is to make native 2095 windows created through the low level API (so you can use platform-specific options and 2096 other details SimpleWindow does not expose) available to the event loop wrappers. 2097 +/ 2098 this(NativeWindowHandle nativeWindow) { 2099 windowType = WindowTypes.minimallyWrapped; 2100 version(Windows) 2101 impl.hwnd = nativeWindow; 2102 else version(X11) { 2103 impl.window = nativeWindow; 2104 if(nativeWindow) 2105 display = XDisplayConnection.get(); // get initial display to not segfault 2106 } else version(OSXCocoa) 2107 throw new NotYetImplementedException(); 2108 else featureNotImplemented(); 2109 // FIXME: set the size correctly 2110 _width = 1; 2111 _height = 1; 2112 if(nativeWindow) 2113 nativeMapping[nativeWindow] = this; 2114 2115 beingOpenKeepsAppOpen = false; 2116 2117 if(nativeWindow) 2118 CapableOfHandlingNativeEvent.nativeHandleMapping[nativeWindow] = this; 2119 _suppressDestruction = true; // so it doesn't try to close 2120 } 2121 2122 /++ 2123 Used iff [WindowFlags.managesChildWindowFocus] is set when the window is created. 2124 The delegate will be called when the window manager asks you to take focus. 2125 2126 This is currently only used for the WM_TAKE_FOCUS protocol on X11 at this time. 2127 2128 History: 2129 Added April 1, 2022 (dub v10.8) 2130 +/ 2131 SimpleWindow delegate() setRequestedInputFocus; 2132 2133 /// Experimental, do not use yet 2134 /++ 2135 Grabs exclusive input from the user until you release it with 2136 [releaseInputGrab]. 2137 2138 2139 Note: it is extremely rude to do this without good reason. 2140 Reasons may include doing some kind of mouse drag operation 2141 or popping up a temporary menu that should get events and will 2142 be dismissed at ease by the user clicking away. 2143 2144 Params: 2145 keyboard = do you want to grab keyboard input? 2146 mouse = grab mouse input? 2147 confine = confine the mouse cursor to inside this window? 2148 2149 History: 2150 Prior to March 11, 2021, grabbing the keyboard would always also 2151 set the X input focus. Now, it only focuses if it is a non-transient 2152 window and otherwise manages the input direction internally. 2153 2154 This means spurious focus/blur events will no longer be sent and the 2155 application will not steal focus from other applications (which the 2156 window manager may have rejected anyway). 2157 +/ 2158 void grabInput(bool keyboard = true, bool mouse = true, bool confine = false) { 2159 static if(UsingSimpledisplayX11) { 2160 XSync(XDisplayConnection.get, 0); 2161 if(keyboard) { 2162 if(isTransient && _parent) { 2163 /* 2164 FIXME: 2165 setting the keyboard focus is not actually that helpful, what I more likely want 2166 is the events from the parent window to be sent over here if we're transient. 2167 */ 2168 2169 _parent.inputProxy = this; 2170 } else { 2171 XSetInputFocus(XDisplayConnection.get, this.impl.window, RevertToParent, CurrentTime); 2172 } 2173 } 2174 if(mouse) { 2175 if(auto res = XGrabPointer(XDisplayConnection.get, this.impl.window, false /* owner_events */, 2176 EventMask.PointerMotionMask // FIXME: not efficient 2177 | EventMask.ButtonPressMask 2178 | EventMask.ButtonReleaseMask 2179 /* event mask */, GrabMode.GrabModeAsync, GrabMode.GrabModeAsync, confine ? this.impl.window : None, None, CurrentTime) 2180 ) 2181 { 2182 XSync(XDisplayConnection.get, 0); 2183 import core.stdc.stdio; 2184 printf("Grab input failed %d\n", res); 2185 //throw new Exception("Grab input failed"); 2186 } else { 2187 // cool 2188 } 2189 } 2190 2191 } else version(Windows) { 2192 // FIXME: keyboard? 2193 SetCapture(impl.hwnd); 2194 if(confine) { 2195 RECT rcClip; 2196 //RECT rcOldClip; 2197 //GetClipCursor(&rcOldClip); 2198 GetWindowRect(hwnd, &rcClip); 2199 ClipCursor(&rcClip); 2200 } 2201 } else version(OSXCocoa) { 2202 throw new NotYetImplementedException(); 2203 } else static assert(0); 2204 } 2205 2206 private Point imePopupLocation = Point(0, 0); 2207 2208 /++ 2209 Sets the location for the IME (input method editor) to pop up when the user activates it. 2210 2211 Bugs: 2212 Not implemented outside X11. 2213 +/ 2214 void setIMEPopupLocation(Point location) { 2215 static if(UsingSimpledisplayX11) { 2216 imePopupLocation = location; 2217 updateIMEPopupLocation(); 2218 } else { 2219 // this is non-fatal at this point... but still wanna find it when i search for NotYetImplementedException at least 2220 // throw new NotYetImplementedException(); 2221 } 2222 } 2223 2224 /// ditto 2225 void setIMEPopupLocation(int x, int y) { 2226 return setIMEPopupLocation(Point(x, y)); 2227 } 2228 2229 // we need to remind XIM of where we wanted to place the IME whenever the window moves 2230 // so this function gets called in setIMEPopupLocation as well as whenever the window 2231 // receives a ConfigureNotify event 2232 private void updateIMEPopupLocation() { 2233 static if(UsingSimpledisplayX11) { 2234 if (xic is null) { 2235 return; 2236 } 2237 2238 XPoint nspot; 2239 nspot.x = cast(short) imePopupLocation.x; 2240 nspot.y = cast(short) imePopupLocation.y; 2241 XVaNestedList preeditAttr = XVaCreateNestedList(0, /*XNSpotLocation*/"spotLocation".ptr, &nspot, null); 2242 XSetICValues(xic, /*XNPreeditAttributes*/"preeditAttributes".ptr, preeditAttr, null); 2243 XFree(preeditAttr); 2244 } 2245 } 2246 2247 private bool imeFocused = true; 2248 2249 /++ 2250 Tells the IME whether or not an input field is currently focused in the window. 2251 2252 Bugs: 2253 Not implemented outside X11. 2254 +/ 2255 void setIMEFocused(bool value) { 2256 imeFocused = value; 2257 updateIMEFocused(); 2258 } 2259 2260 // used to focus/unfocus the IC if necessary when the window gains/loses focus 2261 private void updateIMEFocused() { 2262 static if(UsingSimpledisplayX11) { 2263 if (xic is null) { 2264 return; 2265 } 2266 2267 if (focused && imeFocused) { 2268 XSetICFocus(xic); 2269 } else { 2270 XUnsetICFocus(xic); 2271 } 2272 } 2273 } 2274 2275 /++ 2276 Returns the native window. 2277 2278 History: 2279 Added November 5, 2021 (dub v10.4). Prior to that, you'd have 2280 to access it through the `impl` member (which is semi-supported 2281 but platform specific and here it is simple enough to offer an accessor). 2282 2283 Bugs: 2284 Not implemented outside Windows or X11. 2285 +/ 2286 NativeWindowHandle nativeWindowHandle() { 2287 version(X11) 2288 return impl.window; 2289 else version(Windows) 2290 return impl.hwnd; 2291 else 2292 throw new NotYetImplementedException(); 2293 } 2294 2295 private bool isTransient() { 2296 with(WindowTypes) 2297 final switch(windowType) { 2298 case normal, undecorated, eventOnly: 2299 case nestedChild, minimallyWrapped: 2300 return (customizationFlags & WindowFlags.transient) ? true : false; 2301 case dropdownMenu, popupMenu, notification: 2302 return true; 2303 } 2304 } 2305 2306 private SimpleWindow inputProxy; 2307 2308 /++ 2309 Releases the grab acquired by [grabInput]. 2310 +/ 2311 void releaseInputGrab() { 2312 static if(UsingSimpledisplayX11) { 2313 XUngrabPointer(XDisplayConnection.get, CurrentTime); 2314 if(_parent) 2315 _parent.inputProxy = null; 2316 } else version(Windows) { 2317 ReleaseCapture(); 2318 ClipCursor(null); 2319 } else version(OSXCocoa) { 2320 throw new NotYetImplementedException(); 2321 } else static assert(0); 2322 } 2323 2324 /++ 2325 Sets the input focus to this window. 2326 2327 You shouldn't call this very often - please let the user control the input focus. 2328 +/ 2329 void focus() { 2330 static if(UsingSimpledisplayX11) { 2331 XSetInputFocus(XDisplayConnection.get, this.impl.window, RevertToParent, CurrentTime); 2332 } else version(Windows) { 2333 SetFocus(this.impl.hwnd); 2334 } else version(OSXCocoa) { 2335 throw new NotYetImplementedException(); 2336 } else static assert(0); 2337 } 2338 2339 /++ 2340 Requests attention from the user for this window. 2341 2342 2343 The typical result of this function is to change the color 2344 of the taskbar icon, though it may be tweaked on specific 2345 platforms. 2346 2347 It is meant to unobtrusively tell the user that something 2348 relevant to them happened in the background and they should 2349 check the window when they get a chance. Upon receiving the 2350 keyboard focus, the window will automatically return to its 2351 natural state. 2352 2353 If the window already has the keyboard focus, this function 2354 may do nothing, because the user is presumed to already be 2355 giving the window attention. 2356 2357 Implementation_note: 2358 2359 `requestAttention` uses the _NET_WM_STATE_DEMANDS_ATTENTION 2360 atom on X11 and the FlashWindow function on Windows. 2361 +/ 2362 void requestAttention() { 2363 if(_focused) 2364 return; 2365 2366 version(Windows) { 2367 FLASHWINFO info; 2368 info.cbSize = info.sizeof; 2369 info.hwnd = impl.hwnd; 2370 info.dwFlags = FLASHW_TRAY; 2371 info.uCount = 1; 2372 2373 FlashWindowEx(&info); 2374 2375 } else version(X11) { 2376 demandingAttention = true; 2377 demandAttention(this, true); 2378 } else version(OSXCocoa) { 2379 throw new NotYetImplementedException(); 2380 } else static assert(0); 2381 } 2382 2383 private bool _focused; 2384 2385 version(X11) private bool demandingAttention; 2386 2387 /// This will be called when WM wants to close your window (i.e. user clicked "close" icon, for example). 2388 /// You'll have to call `close()` manually if you set this delegate. 2389 void delegate () closeQuery; 2390 2391 /// This will be called when window visibility was changed. 2392 void delegate (bool becomesVisible) visibilityChanged; 2393 2394 /// This will be called when window becomes visible for the first time. 2395 /// You can do OpenGL initialization here. Note that in X11 you can't call 2396 /// [setAsCurrentOpenGlContext] right after window creation, or X11 may 2397 /// fail to send reparent and map events (hit that with proprietary NVidia drivers). 2398 /// So you need to wait until this is called and call setAsCurrentOpenGlContext in there, then do the OpenGL initialization. 2399 private bool _visibleForTheFirstTimeCalled; 2400 void delegate () visibleForTheFirstTime; 2401 2402 /// Returns true if the window has been closed. 2403 final @property bool closed() { return _closed; } 2404 2405 private final @property bool notClosed() { return !_closed; } 2406 2407 /// Returns true if the window is focused. 2408 final @property bool focused() { return _focused; } 2409 2410 private bool _visible; 2411 /// Returns true if the window is visible (mapped). 2412 final @property bool visible() { return _visible; } 2413 2414 /// Closes the window. If there are no more open windows, the event loop will terminate. 2415 void close() { 2416 if (!_closed) { 2417 runInGuiThread( { 2418 if(_closed) return; // another thread got to it first. this isn't a big deal, it just means our message was queued 2419 if (onClosing !is null) onClosing(); 2420 impl.closeWindow(); 2421 _closed = true; 2422 } ); 2423 } 2424 } 2425 2426 /++ 2427 `close` is one of the few methods that can be called from other threads. This `shared` overload reflects that. 2428 2429 History: 2430 Overload added on March 7, 2021. 2431 +/ 2432 void close() shared { 2433 (cast() this).close(); 2434 } 2435 2436 /++ 2437 2438 +/ 2439 void maximize() { 2440 version(Windows) 2441 ShowWindow(impl.hwnd, SW_MAXIMIZE); 2442 else version(X11) { 2443 setNetWmStateAtom(this.impl.window, GetAtom!("_NET_WM_STATE_MAXIMIZED_VERT", false)(XDisplayConnection.get), true, GetAtom!("_NET_WM_STATE_MAXIMIZED_HORZ", false)(XDisplayConnection.get)); 2444 2445 // also note _NET_WM_STATE_FULLSCREEN 2446 } 2447 2448 } 2449 2450 private bool _fullscreen; 2451 version(Windows) 2452 private WINDOWPLACEMENT g_wpPrev; 2453 2454 /// not fully implemented but planned for a future release 2455 void fullscreen(bool yes) { 2456 version(Windows) { 2457 g_wpPrev.length = WINDOWPLACEMENT.sizeof; 2458 DWORD dwStyle = GetWindowLong(hwnd, GWL_STYLE); 2459 if (dwStyle & WS_OVERLAPPEDWINDOW) { 2460 MONITORINFO mi; 2461 mi.cbSize = MONITORINFO.sizeof; 2462 if (GetWindowPlacement(hwnd, &g_wpPrev) && 2463 GetMonitorInfo(MonitorFromWindow(hwnd, 2464 MONITOR_DEFAULTTOPRIMARY), &mi)) { 2465 SetWindowLong(hwnd, GWL_STYLE, 2466 dwStyle & ~WS_OVERLAPPEDWINDOW); 2467 SetWindowPos(hwnd, HWND_TOP, 2468 mi.rcMonitor.left, mi.rcMonitor.top, 2469 mi.rcMonitor.right - mi.rcMonitor.left, 2470 mi.rcMonitor.bottom - mi.rcMonitor.top, 2471 SWP_NOOWNERZORDER | SWP_FRAMECHANGED); 2472 } 2473 } else { 2474 SetWindowLong(hwnd, GWL_STYLE, 2475 dwStyle | WS_OVERLAPPEDWINDOW); 2476 SetWindowPlacement(hwnd, &g_wpPrev); 2477 SetWindowPos(hwnd, null, 0, 0, 0, 0, 2478 SWP_NOMOVE | SWP_NOSIZE | SWP_NOZORDER | 2479 SWP_NOOWNERZORDER | SWP_FRAMECHANGED); 2480 } 2481 2482 } else version(X11) { 2483 setNetWmStateAtom(this.impl.window, GetAtom!("_NET_WM_STATE_FULLSCREEN", false)(XDisplayConnection.get), yes); 2484 } 2485 2486 _fullscreen = yes; 2487 2488 } 2489 2490 bool fullscreen() { 2491 return _fullscreen; 2492 } 2493 2494 /++ 2495 Note: only implemented on Windows. No-op on other platforms. You may want to use [hide] instead. 2496 2497 +/ 2498 void minimize() { 2499 version(Windows) 2500 ShowWindow(impl.hwnd, SW_MINIMIZE); 2501 //else version(X11) 2502 //setNetWmStateAtom(this, GetAtom!("_NET_WM_STATE_MINIMIZED", false)(XDisplayConnection.get), true); 2503 } 2504 2505 /// Alias for `hidden = false` 2506 void show() { 2507 hidden = false; 2508 } 2509 2510 /// Alias for `hidden = true` 2511 void hide() { 2512 hidden = true; 2513 } 2514 2515 /// Hide cursor when it enters the window. 2516 void hideCursor() { 2517 version(OSXCocoa) throw new NotYetImplementedException(); else 2518 if (!_closed) impl.hideCursor(); 2519 } 2520 2521 /// Don't hide cursor when it enters the window. 2522 void showCursor() { 2523 version(OSXCocoa) throw new NotYetImplementedException(); else 2524 if (!_closed) impl.showCursor(); 2525 } 2526 2527 /** "Warp" mouse pointer to coordinates relative to window top-left corner. Return "success" flag. 2528 * 2529 * Please remember that the cursor is a shared resource that should usually be left to the user's 2530 * control. Try to think for other approaches before using this function. 2531 * 2532 * Note: "warping" pointer will not send any synthesised mouse events, so you probably doesn't want 2533 * to use it to move mouse pointer to some active GUI area, for example, as your window won't 2534 * receive "mouse moved here" event. 2535 */ 2536 bool warpMouse (int x, int y) { 2537 version(X11) { 2538 if (!_closed) { impl.warpMouse(x, y); return true; } 2539 } else version(Windows) { 2540 if (!_closed) { 2541 POINT point; 2542 point.x = x; 2543 point.y = y; 2544 if(ClientToScreen(impl.hwnd, &point)) { 2545 SetCursorPos(point.x, point.y); 2546 return true; 2547 } 2548 } 2549 } 2550 return false; 2551 } 2552 2553 /// Send dummy window event to ping event loop. Required to process NotificationIcon on X11, for example. 2554 void sendDummyEvent () { 2555 version(X11) { 2556 if (!_closed) { impl.sendDummyEvent(); } 2557 } 2558 } 2559 2560 /// Set window minimal size. 2561 void setMinSize (int minwidth, int minheight) { 2562 version(OSXCocoa) throw new NotYetImplementedException(); else 2563 if (!_closed) impl.setMinSize(minwidth, minheight); 2564 } 2565 2566 /// Set window maximal size. 2567 void setMaxSize (int maxwidth, int maxheight) { 2568 version(OSXCocoa) throw new NotYetImplementedException(); else 2569 if (!_closed) impl.setMaxSize(maxwidth, maxheight); 2570 } 2571 2572 /// Set window resize step (window size will be changed with the given granularity on supported platforms). 2573 /// Currently only supported on X11. 2574 void setResizeGranularity (int granx, int grany) { 2575 version(OSXCocoa) throw new NotYetImplementedException(); else 2576 if (!_closed) impl.setResizeGranularity(granx, grany); 2577 } 2578 2579 /// Move window. 2580 void move(int x, int y) { 2581 version(OSXCocoa) throw new NotYetImplementedException(); else 2582 if (!_closed) impl.move(x, y); 2583 } 2584 2585 /// ditto 2586 void move(Point p) { 2587 version(OSXCocoa) throw new NotYetImplementedException(); else 2588 if (!_closed) impl.move(p.x, p.y); 2589 } 2590 2591 /++ 2592 Resize window. 2593 2594 Note that the width and height of the window are NOT instantly 2595 updated - it waits for the window manager to approve the resize 2596 request, which means you must return to the event loop before the 2597 width and height are actually changed. 2598 +/ 2599 void resize(int w, int h) { 2600 if(!_closed && _fullscreen) fullscreen = false; 2601 version(OSXCocoa) throw new NotYetImplementedException(); else 2602 if (!_closed) impl.resize(w, h); 2603 } 2604 2605 /// Move and resize window (this can be faster and more visually pleasant than doing it separately). 2606 void moveResize (int x, int y, int w, int h) { 2607 if(!_closed && _fullscreen) fullscreen = false; 2608 version(OSXCocoa) throw new NotYetImplementedException(); else 2609 if (!_closed) impl.moveResize(x, y, w, h); 2610 } 2611 2612 private bool _hidden; 2613 2614 /// Returns true if the window is hidden. 2615 final @property bool hidden() { 2616 return _hidden; 2617 } 2618 2619 /// Shows or hides the window based on the bool argument. 2620 final @property void hidden(bool b) { 2621 _hidden = b; 2622 version(Windows) { 2623 ShowWindow(impl.hwnd, b ? SW_HIDE : SW_SHOW); 2624 } else version(X11) { 2625 if(b) 2626 //XUnmapWindow(impl.display, impl.window); 2627 XWithdrawWindow(impl.display, impl.window, DefaultScreen(impl.display)); 2628 else 2629 XMapWindow(impl.display, impl.window); 2630 } else version(OSXCocoa) { 2631 throw new NotYetImplementedException(); 2632 } else static assert(0); 2633 } 2634 2635 /// Sets the window opacity. On X11 this requires a compositor to be running. On windows the WindowFlags.extraComposite must be set at window creation. 2636 void opacity(double opacity) @property 2637 in { 2638 assert(opacity >= 0 && opacity <= 1); 2639 } do { 2640 version (Windows) { 2641 impl.setOpacity(cast(ubyte)(255 * opacity)); 2642 } else version (X11) { 2643 impl.setOpacity(cast(uint)(uint.max * opacity)); 2644 } else throw new NotYetImplementedException(); 2645 } 2646 2647 /++ 2648 Sets your event handlers, without entering the event loop. Useful if you 2649 have multiple windows - set the handlers on each window, then only do 2650 [eventLoop] on your main window or call `EventLoop.get.run();`. 2651 2652 This assigns the given handlers to [handleKeyEvent], [handleCharEvent], 2653 [handlePulse], and [handleMouseEvent] automatically based on the provide 2654 delegate signatures. 2655 +/ 2656 void setEventHandlers(T...)(T eventHandlers) { 2657 // FIXME: add more events 2658 foreach(handler; eventHandlers) { 2659 static if(__traits(compiles, handleKeyEvent = handler)) { 2660 handleKeyEvent = handler; 2661 } else static if(__traits(compiles, handleCharEvent = handler)) { 2662 handleCharEvent = handler; 2663 } else static if(__traits(compiles, handlePulse = handler)) { 2664 handlePulse = handler; 2665 } else static if(__traits(compiles, handleMouseEvent = handler)) { 2666 handleMouseEvent = handler; 2667 } else static assert(0, "I can't use this event handler " ~ typeof(handler).stringof ~ "\nHave you tried using the delegate keyword?"); 2668 } 2669 } 2670 2671 /++ 2672 The event loop automatically returns when the window is closed 2673 pulseTimeout is given in milliseconds. If pulseTimeout == 0, no 2674 pulse timer is created. The event loop will block until an event 2675 arrives or the pulse timer goes off. 2676 2677 The given `eventHandlers` are passed to [setEventHandlers], which in turn 2678 assigns them to [handleKeyEvent], [handleCharEvent], [handlePulse], and 2679 [handleMouseEvent], based on the signature of delegates you provide. 2680 2681 Give one with no parameters to set a timer pulse handler. Give one that 2682 takes [KeyEvent] for a key handler, [MouseEvent], for a mouse handler, 2683 and one that takes `dchar` for a char event handler. You can use as many 2684 or as few handlers as you need for your application. 2685 2686 History: 2687 The overload without `pulseTimeout` was added on December 8, 2021. 2688 2689 On December 9, 2021, the default blocking mode (which is now configurable 2690 because [eventLoopWithBlockingMode] was added) switched from 2691 [BlockingMode.untilApplicationQuits] over to [BlockingMode.automatic]. This 2692 should almost never be noticeable to you since the typical simpledisplay 2693 paradigm has been (and I still recommend) to have one `eventLoop` call. 2694 2695 See_Also: 2696 [eventLoopWithBlockingMode] 2697 +/ 2698 final int eventLoop(T...)( 2699 long pulseTimeout, /// set to zero if you don't want a pulse. 2700 T eventHandlers) /// delegate list like std.concurrency.receive 2701 { 2702 return eventLoopWithBlockingMode(BlockingMode.automatic, pulseTimeout, eventHandlers); 2703 } 2704 2705 /// ditto 2706 final int eventLoop(T...)(T eventHandlers) if(T.length == 0 || is(T[0] == delegate)) 2707 { 2708 return eventLoopWithBlockingMode(BlockingMode.automatic, 0, eventHandlers); 2709 } 2710 2711 /++ 2712 This is the function [eventLoop] forwards to. It, in turn, forwards to `EventLoop.get.run`. 2713 2714 History: 2715 Added December 8, 2021 (dub v10.5) 2716 2717 Previously, this implementation was right inside [eventLoop], but when I wanted 2718 to add the new [BlockingMode] parameter, the compiler got in a trouble loop so I 2719 just renamed it instead of adding as an overload. Besides, the new name makes it 2720 easier to remember the order and avoids ambiguity between two int-like params anyway. 2721 2722 See_Also: 2723 [SimpleWindow.eventLoop], [EventLoop] 2724 2725 Bugs: 2726 The blocking mode is not implemented on OSX Cocoa nor on the (deprecated) arsd.eventloop. 2727 +/ 2728 final int eventLoopWithBlockingMode(T...)( 2729 BlockingMode blockingMode, /// when you want this function to block until 2730 long pulseTimeout, /// set to zero if you don't want a pulse. 2731 T eventHandlers) /// delegate list like std.concurrency.receive 2732 { 2733 setEventHandlers(eventHandlers); 2734 2735 version(with_eventloop) { 2736 // delegates event loop to my other module 2737 version(X11) 2738 XFlush(display); 2739 2740 import arsd.eventloop; 2741 auto handle = setInterval(handlePulse, cast(int) pulseTimeout); 2742 scope(exit) clearInterval(handle); 2743 2744 loop(); 2745 return 0; 2746 } else version(OSXCocoa) { 2747 // FIXME 2748 if (handlePulse !is null && pulseTimeout != 0) { 2749 timer = scheduledTimer(pulseTimeout*1e-3, 2750 view, sel_registerName("simpledisplay_pulse"), 2751 null, true); 2752 } 2753 2754 setNeedsDisplay(view, true); 2755 run(NSApp); 2756 return 0; 2757 } else { 2758 EventLoop el = EventLoop(pulseTimeout, handlePulse); 2759 2760 if((blockingMode & BlockingMode.onlyIfNotNested) && el.impl.refcount > 1) 2761 return 0; 2762 2763 return el.run( 2764 ((blockingMode & 0x0f) == BlockingMode.untilApplicationQuits) ? 2765 null : 2766 &this.notClosed 2767 ); 2768 } 2769 } 2770 2771 /++ 2772 This lets you draw on the window (or its backing buffer) using basic 2773 2D primitives. 2774 2775 Be sure to call this in a limited scope because your changes will not 2776 actually appear on the window until ScreenPainter's destructor runs. 2777 2778 Returns: an instance of [ScreenPainter], which has the drawing methods 2779 on it to draw on this window. 2780 2781 Params: 2782 manualInvalidations = if you set this to true, you will need to 2783 set the invalid rectangle on the painter yourself. If false, it 2784 assumes the whole window has been redrawn each time you draw. 2785 2786 Only invalidated rectangles are blitted back to the window when 2787 the destructor runs. Doing this yourself can reduce flickering 2788 of child windows. 2789 2790 History: 2791 The `manualInvalidations` parameter overload was added on 2792 December 30, 2021 (dub v10.5) 2793 +/ 2794 ScreenPainter draw() { 2795 return draw(false); 2796 } 2797 /// ditto 2798 ScreenPainter draw(bool manualInvalidations) { 2799 return impl.getPainter(manualInvalidations); 2800 } 2801 2802 // This is here to implement the interface we use for various native handlers. 2803 NativeEventHandler getNativeEventHandler() { return handleNativeEvent; } 2804 2805 // maps native window handles to SimpleWindow instances, if there are any 2806 // you shouldn't need this, but it is public in case you do in a native event handler or something 2807 public __gshared SimpleWindow[NativeWindowHandle] nativeMapping; 2808 2809 // the size the user requested in the constructor, in automatic scale modes it always pretends to be this size 2810 private int _virtualWidth; 2811 private int _virtualHeight; 2812 2813 /// Width of the window's drawable client area, in pixels. 2814 @scriptable 2815 final @property int width() const pure nothrow @safe @nogc { 2816 if(resizability == Resizability.automaticallyScaleIfPossible) 2817 return _virtualWidth; 2818 else 2819 return _width; 2820 } 2821 2822 /// Height of the window's drawable client area, in pixels. 2823 @scriptable 2824 final @property int height() const pure nothrow @safe @nogc { 2825 if(resizability == Resizability.automaticallyScaleIfPossible) 2826 return _virtualHeight; 2827 else 2828 return _height; 2829 } 2830 2831 /++ 2832 Returns the actual size of the window, bypassing the logical 2833 illusions of [Resizability.automaticallyScaleIfPossible]. 2834 2835 History: 2836 Added November 11, 2022 (dub v10.10) 2837 +/ 2838 final @property Size actualWindowSize() const pure nothrow @safe @nogc { 2839 return Size(_width, _height); 2840 } 2841 2842 2843 private int _width; 2844 private int _height; 2845 2846 // HACK: making the best of some copy constructor woes with refcounting 2847 private ScreenPainterImplementation* activeScreenPainter_; 2848 2849 protected ScreenPainterImplementation* activeScreenPainter() { return activeScreenPainter_; } 2850 protected void activeScreenPainter(ScreenPainterImplementation* i) { activeScreenPainter_ = i; } 2851 2852 private OpenGlOptions openglMode; 2853 private Resizability resizability; 2854 private WindowTypes windowType; 2855 private int customizationFlags; 2856 2857 /// `true` if OpenGL was initialized for this window. 2858 @property bool isOpenGL () const pure nothrow @safe @nogc { 2859 version(without_opengl) 2860 return false; 2861 else 2862 return (openglMode == OpenGlOptions.yes); 2863 } 2864 @property Resizability resizingMode () const pure nothrow @safe @nogc { return resizability; } /// Original resizability. 2865 @property WindowTypes type () const pure nothrow @safe @nogc { return windowType; } /// Original window type. 2866 @property int customFlags () const pure nothrow @safe @nogc { return customizationFlags; } /// Original customization flags. 2867 2868 /// "Lock" this window handle, to do multithreaded synchronization. You probably won't need 2869 /// to call this, as it's not recommended to share window between threads. 2870 void mtLock () { 2871 version(X11) { 2872 XLockDisplay(this.display); 2873 } 2874 } 2875 2876 /// "Unlock" this window handle, to do multithreaded synchronization. You probably won't need 2877 /// to call this, as it's not recommended to share window between threads. 2878 void mtUnlock () { 2879 version(X11) { 2880 XUnlockDisplay(this.display); 2881 } 2882 } 2883 2884 /// Emit a beep to get user's attention. 2885 void beep () { 2886 version(X11) { 2887 XBell(this.display, 100); 2888 } else version(Windows) { 2889 MessageBeep(0xFFFFFFFF); 2890 } 2891 } 2892 2893 2894 2895 version(without_opengl) {} else { 2896 2897 /// 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`. 2898 void delegate() redrawOpenGlScene; 2899 2900 /// This will allow you to change OpenGL vsync state. 2901 final @property void vsync (bool wait) { 2902 if (this._closed) return; // window may be closed, but timer is still firing; avoid GLXBadDrawable error 2903 version(X11) { 2904 setAsCurrentOpenGlContext(); 2905 glxSetVSync(display, impl.window, wait); 2906 } else version(Windows) { 2907 setAsCurrentOpenGlContext(); 2908 wglSetVSync(wait); 2909 } 2910 } 2911 2912 /// Set this to `false` if you don't need to do `glFinish()` after `swapOpenGlBuffers()`. 2913 /// Note that at least NVidia proprietary driver may segfault if you will modify texture fast 2914 /// enough without waiting 'em to finish their frame business. 2915 bool useGLFinish = true; 2916 2917 // FIXME: it should schedule it for the end of the current iteration of the event loop... 2918 /// 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. 2919 void redrawOpenGlSceneNow() { 2920 version(X11) if (!this._visible) return; // no need to do this if window is invisible 2921 if (this._closed) return; // window may be closed, but timer is still firing; avoid GLXBadDrawable error 2922 if(redrawOpenGlScene is null) 2923 return; 2924 2925 this.mtLock(); 2926 scope(exit) this.mtUnlock(); 2927 2928 this.setAsCurrentOpenGlContext(); 2929 2930 redrawOpenGlScene(); 2931 2932 this.swapOpenGlBuffers(); 2933 // 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. 2934 if (useGLFinish) glFinish(); 2935 } 2936 2937 private bool redrawOpenGlSceneSoonSet = false; 2938 private static class RedrawOpenGlSceneEvent { 2939 SimpleWindow w; 2940 this(SimpleWindow w) { this.w = w; } 2941 } 2942 private RedrawOpenGlSceneEvent redrawOpenGlSceneEvent; 2943 /++ 2944 Queues an opengl redraw as soon as the other pending events are cleared. 2945 +/ 2946 void redrawOpenGlSceneSoon() { 2947 if(!redrawOpenGlSceneSoonSet) { 2948 redrawOpenGlSceneEvent = new RedrawOpenGlSceneEvent(this); 2949 this.addEventListener((RedrawOpenGlSceneEvent e) { e.w.redrawOpenGlSceneNow(); }); 2950 redrawOpenGlSceneSoonSet = true; 2951 } 2952 this.postEvent(redrawOpenGlSceneEvent, true); 2953 } 2954 2955 2956 /// Makes all gl* functions target this window until changed. This is only valid if you passed `OpenGlOptions.yes` to the constructor. 2957 void setAsCurrentOpenGlContext() { 2958 assert(openglMode == OpenGlOptions.yes); 2959 version(X11) { 2960 if(glXMakeCurrent(display, impl.window, impl.glc) == 0) 2961 throw new Exception("glXMakeCurrent"); 2962 } else version(Windows) { 2963 static if (SdpyIsUsingIVGLBinds) import iv.glbinds; // override druntime windows imports 2964 if (!wglMakeCurrent(ghDC, ghRC)) 2965 throw new Exception("wglMakeCurrent " ~ toInternal!int(GetLastError())); // let windows users suffer too 2966 } 2967 } 2968 2969 /// Makes all gl* functions target this window until changed. This is only valid if you passed `OpenGlOptions.yes` to the constructor. 2970 /// This doesn't throw, returning success flag instead. 2971 bool setAsCurrentOpenGlContextNT() nothrow { 2972 assert(openglMode == OpenGlOptions.yes); 2973 version(X11) { 2974 return (glXMakeCurrent(display, impl.window, impl.glc) != 0); 2975 } else version(Windows) { 2976 static if (SdpyIsUsingIVGLBinds) import iv.glbinds; // override druntime windows imports 2977 return wglMakeCurrent(ghDC, ghRC) ? true : false; 2978 } 2979 } 2980 2981 /// 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. 2982 /// This doesn't throw, returning success flag instead. 2983 bool releaseCurrentOpenGlContext() nothrow { 2984 assert(openglMode == OpenGlOptions.yes); 2985 version(X11) { 2986 return (glXMakeCurrent(display, 0, null) != 0); 2987 } else version(Windows) { 2988 static if (SdpyIsUsingIVGLBinds) import iv.glbinds; // override druntime windows imports 2989 return wglMakeCurrent(ghDC, null) ? true : false; 2990 } 2991 } 2992 2993 /++ 2994 simpledisplay always uses double buffering, usually automatically. This 2995 manually swaps the OpenGL buffers. 2996 2997 2998 You should not need to call this yourself because simpledisplay will do it 2999 for you after calling your `redrawOpenGlScene`. 3000 3001 Remember that this may throw an exception, which you can catch in a multithreaded 3002 application to keep your thread from dying from an unhandled exception. 3003 +/ 3004 void swapOpenGlBuffers() { 3005 assert(openglMode == OpenGlOptions.yes); 3006 version(X11) { 3007 if (!this._visible) return; // no need to do this if window is invisible 3008 if (this._closed) return; // window may be closed, but timer is still firing; avoid GLXBadDrawable error 3009 glXSwapBuffers(display, impl.window); 3010 } else version(Windows) { 3011 SwapBuffers(ghDC); 3012 } 3013 } 3014 } 3015 3016 /++ 3017 Set the window title, which is visible on the window manager title bar, operating system taskbar, etc. 3018 3019 3020 --- 3021 auto window = new SimpleWindow(100, 100, "First title"); 3022 window.title = "A new title"; 3023 --- 3024 3025 You may call this function at any time. 3026 +/ 3027 @property void title(string title) { 3028 _title = title; 3029 version(OSXCocoa) throw new NotYetImplementedException(); else 3030 impl.setTitle(title); 3031 } 3032 3033 private string _title; 3034 3035 /// Gets the title 3036 @property string title() { 3037 if(_title is null) 3038 _title = getRealTitle(); 3039 return _title; 3040 } 3041 3042 /++ 3043 Get the title as set by the window manager. 3044 May not match what you attempted to set. 3045 +/ 3046 string getRealTitle() { 3047 static if(is(typeof(impl.getTitle()))) 3048 return impl.getTitle(); 3049 else 3050 return null; 3051 } 3052 3053 // don't use this generally it is not yet really released 3054 version(X11) 3055 @property Image secret_icon() { 3056 return secret_icon_inner; 3057 } 3058 private Image secret_icon_inner; 3059 3060 3061 /// Set the icon that is seen in the title bar or taskbar, etc., for the user. If passed `null`, does nothing. 3062 @property void icon(MemoryImage icon) { 3063 if(icon is null) 3064 return; 3065 auto tci = icon.getAsTrueColorImage(); 3066 version(Windows) { 3067 winIcon = new WindowsIcon(icon); 3068 SendMessageA(impl.hwnd, 0x0080 /*WM_SETICON*/, 0 /*ICON_SMALL*/, cast(LPARAM) winIcon.hIcon); // there is also 1 == ICON_BIG 3069 } else version(X11) { 3070 secret_icon_inner = Image.fromMemoryImage(icon); 3071 // FIXME: ensure this is correct 3072 auto display = XDisplayConnection.get; 3073 arch_ulong[] buffer; 3074 buffer ~= icon.width; 3075 buffer ~= icon.height; 3076 foreach(c; tci.imageData.colors) { 3077 arch_ulong b; 3078 b |= c.a << 24; 3079 b |= c.r << 16; 3080 b |= c.g << 8; 3081 b |= c.b; 3082 buffer ~= b; 3083 } 3084 3085 XChangeProperty( 3086 display, 3087 impl.window, 3088 GetAtom!("_NET_WM_ICON", true)(display), 3089 GetAtom!"CARDINAL"(display), 3090 32 /* bits */, 3091 0 /*PropModeReplace*/, 3092 buffer.ptr, 3093 cast(int) buffer.length); 3094 } else version(OSXCocoa) { 3095 throw new NotYetImplementedException(); 3096 } else static assert(0); 3097 } 3098 3099 version(Windows) 3100 private WindowsIcon winIcon; 3101 3102 bool _suppressDestruction; 3103 3104 ~this() { 3105 if(!thisIsGuiThread) return; // FIXME: leaks if multithreaded gc 3106 if(_suppressDestruction) 3107 return; 3108 impl.dispose(); 3109 } 3110 3111 private bool _closed; 3112 3113 // the idea here is to draw something temporary on top of the main picture e.g. a blinking cursor 3114 /* 3115 ScreenPainter drawTransiently() { 3116 return impl.getPainter(); 3117 } 3118 */ 3119 3120 /// Draws an image on the window. This is meant to provide quick look 3121 /// of a static image generated elsewhere. 3122 @property void image(Image i) { 3123 /+ 3124 version(Windows) { 3125 BITMAP bm; 3126 HDC hdc = GetDC(hwnd); 3127 HDC hdcMem = CreateCompatibleDC(hdc); 3128 HBITMAP hbmOld = SelectObject(hdcMem, i.handle); 3129 3130 GetObject(i.handle, bm.sizeof, &bm); 3131 3132 BitBlt(hdc, 0, 0, bm.bmWidth, bm.bmHeight, hdcMem, 0, 0, SRCCOPY); 3133 3134 SelectObject(hdcMem, hbmOld); 3135 DeleteDC(hdcMem); 3136 ReleaseDC(hwnd, hdc); 3137 3138 /* 3139 RECT r; 3140 r.right = i.width; 3141 r.bottom = i.height; 3142 InvalidateRect(hwnd, &r, false); 3143 */ 3144 } else 3145 version(X11) { 3146 if(!destroyed) { 3147 if(i.usingXshm) 3148 XShmPutImage(display, cast(Drawable) window, gc, i.handle, 0, 0, 0, 0, i.width, i.height, false); 3149 else 3150 XPutImage(display, cast(Drawable) window, gc, i.handle, 0, 0, 0, 0, i.width, i.height); 3151 } 3152 } else 3153 version(OSXCocoa) { 3154 draw().drawImage(Point(0, 0), i); 3155 setNeedsDisplay(view, true); 3156 } else static assert(0); 3157 +/ 3158 auto painter = this.draw; 3159 painter.drawImage(Point(0, 0), i); 3160 } 3161 3162 /++ 3163 Changes the cursor for the window. If the cursor is hidden via [hideCursor], this has no effect. 3164 3165 --- 3166 window.cursor = GenericCursor.Help; 3167 // now the window mouse cursor is set to a generic help 3168 --- 3169 3170 +/ 3171 @property void cursor(MouseCursor cursor) { 3172 version(OSXCocoa) 3173 featureNotImplemented(); 3174 else 3175 if(this.impl.curHidden <= 0) { 3176 static if(UsingSimpledisplayX11) { 3177 auto ch = cursor.cursorHandle; 3178 XDefineCursor(XDisplayConnection.get(), this.impl.window, ch); 3179 } else version(Windows) { 3180 auto ch = cursor.cursorHandle; 3181 impl.currentCursor = ch; 3182 SetCursor(ch); // redraw without waiting for mouse movement to update 3183 } else featureNotImplemented(); 3184 } 3185 3186 } 3187 3188 /// What follows are the event handlers. These are set automatically 3189 /// by the eventLoop function, but are still public so you can change 3190 /// them later. wasPressed == true means key down. false == key up. 3191 3192 /// Handles a low-level keyboard event. Settable through setEventHandlers. 3193 void delegate(KeyEvent ke) handleKeyEvent; 3194 3195 /// Handles a higher level keyboard event - c is the character just pressed. Settable through setEventHandlers. 3196 void delegate(dchar c) handleCharEvent; 3197 3198 /// Handles a timer pulse. Settable through setEventHandlers. 3199 void delegate() handlePulse; 3200 3201 /// Called when the focus changes, param is if we have it (true) or are losing it (false). 3202 void delegate(bool) onFocusChange; 3203 3204 /** Called inside `close()` method. Our window is still alive, and we can free various resources. 3205 * Sometimes it is easier to setup the delegate instead of subclassing. */ 3206 void delegate() onClosing; 3207 3208 /** Called when we received destroy notification. At this stage we cannot do much with our window 3209 * (as it is already dead, and it's native handle cannot be used), but we still can do some 3210 * last minute cleanup. */ 3211 void delegate() onDestroyed; 3212 3213 static if (UsingSimpledisplayX11) 3214 /** Called when Expose event comes. See Xlib manual to understand the arguments. 3215 * Return `false` if you want Simpledisplay to copy backbuffer, or `true` if you did it yourself. 3216 * You will probably never need to setup this handler, it is for very low-level stuff. 3217 * 3218 * WARNING! Xlib is multithread-locked when this handles is called! */ 3219 bool delegate(int x, int y, int width, int height, int eventsLeft) handleExpose; 3220 3221 //version(Windows) 3222 //bool delegate(WPARAM wParam, LPARAM lParam) handleWM_PAINT; 3223 3224 private { 3225 int lastMouseX = int.min; 3226 int lastMouseY = int.min; 3227 void mdx(ref MouseEvent ev) { 3228 if(lastMouseX == int.min || lastMouseY == int.min) { 3229 ev.dx = 0; 3230 ev.dy = 0; 3231 } else { 3232 ev.dx = ev.x - lastMouseX; 3233 ev.dy = ev.y - lastMouseY; 3234 } 3235 3236 lastMouseX = ev.x; 3237 lastMouseY = ev.y; 3238 } 3239 } 3240 3241 /// Mouse event handler. Settable through setEventHandlers. 3242 void delegate(MouseEvent) handleMouseEvent; 3243 3244 /// use to redraw child widgets if you use system apis to add stuff 3245 void delegate() paintingFinished; 3246 3247 void delegate() paintingFinishedDg() { 3248 return paintingFinished; 3249 } 3250 3251 /// handle a resize, after it happens. You must construct the window with Resizability.allowResizing 3252 /// for this to ever happen. 3253 void delegate(int width, int height) windowResized; 3254 3255 /++ 3256 Platform specific - handle any native message this window gets. 3257 3258 Note: this is called *in addition to* other event handlers, unless you either: 3259 3260 1) On X11, return 0 indicating that you handled it. Any other return value is simply discarded. 3261 3262 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. 3263 3264 On Windows, your delegate takes the form of `int delegate(HWND hwnd, UINT message, WPARAM wParam, LPARAM lParam, out int mustReturn)`. 3265 3266 On X, it takes the form of `int delegate(XEvent)`. 3267 3268 History: 3269 In ancient versions, this was `static`. If you want a global hook, use [handleNativeGlobalEvent] instead. 3270 3271 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. 3272 +/ 3273 NativeEventHandler handleNativeEvent_; 3274 3275 @property NativeEventHandler handleNativeEvent() nothrow pure @nogc const @safe { 3276 return handleNativeEvent_; 3277 } 3278 @property void handleNativeEvent(NativeEventHandler neh) nothrow pure @nogc @safe { 3279 handleNativeEvent_ = neh; 3280 } 3281 3282 version(Windows) 3283 // compatibility shim with the old deprecated way 3284 // in this one, if you return 0, it means you must return. otherwise the ret value is ignored. 3285 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) { 3286 handleNativeEvent_ = delegate int(HWND h, UINT m, WPARAM w, LPARAM l, out int r) { 3287 auto ret = dg(h, m, w, l); 3288 if(ret == 0) 3289 r = 1; 3290 return ret; 3291 }; 3292 } 3293 3294 /// This is the same as handleNativeEvent, but static so it can hook ALL events in the loop. 3295 /// If you used to use handleNativeEvent depending on it being static, just change it to use 3296 /// this instead and it will work the same way. 3297 __gshared NativeEventHandler handleNativeGlobalEvent; 3298 3299 // private: 3300 /// The native implementation is available, but you shouldn't use it unless you are 3301 /// familiar with the underlying operating system, don't mind depending on it, and 3302 /// know simpledisplay.d's internals too. It is virtually private; you can hopefully 3303 /// do what you need to do with handleNativeEvent instead. 3304 /// 3305 /// This is likely to eventually change to be just a struct holding platform-specific 3306 /// handles instead of a template mixin at some point because I'm not happy with the 3307 /// code duplication here (ironically). 3308 mixin NativeSimpleWindowImplementation!() impl; 3309 3310 /** 3311 This is in-process one-way (from anything to window) event sending mechanics. 3312 It is thread-safe, so it can be used in multi-threaded applications to send, 3313 for example, "wake up and repaint" events when thread completed some operation. 3314 This will allow to avoid using timer pulse to check events with synchronization, 3315 'cause event handler will be called in UI thread. You can stop guessing which 3316 pulse frequency will be enough for your app. 3317 Note that events handlers may be called in arbitrary order, i.e. last registered 3318 handler can be called first, and vice versa. 3319 */ 3320 public: 3321 /** Is our custom event queue empty? Can be used in simple cases to prevent 3322 * "spamming" window with events it can't cope with. 3323 * It is safe to call this from non-UI threads. 3324 */ 3325 @property bool eventQueueEmpty() () { 3326 synchronized(this) { 3327 foreach (const ref o; eventQueue[0..eventQueueUsed]) if (!o.doProcess) return false; 3328 } 3329 return true; 3330 } 3331 3332 /** Does our custom event queue contains at least one with the given type? 3333 * Can be used in simple cases to prevent "spamming" window with events 3334 * it can't cope with. 3335 * It is safe to call this from non-UI threads. 3336 */ 3337 @property bool eventQueued(ET:Object) () { 3338 synchronized(this) { 3339 foreach (const ref o; eventQueue[0..eventQueueUsed]) { 3340 if (!o.doProcess) { 3341 if (cast(ET)(o.evt)) return true; 3342 } 3343 } 3344 } 3345 return false; 3346 } 3347 3348 /++ 3349 Event listeners added with [addEventListener] have their exceptions swallowed by the event loop. This delegate can handle them again before it proceeds. 3350 3351 History: 3352 Added May 12, 2021 3353 +/ 3354 void delegate(Exception e) nothrow eventUncaughtException; 3355 3356 /** Add listener for custom event. Can be used like this: 3357 * 3358 * --------------------- 3359 * auto eid = win.addEventListener((MyStruct evt) { ... }); 3360 * ... 3361 * win.removeEventListener(eid); 3362 * --------------------- 3363 * 3364 * Returns: 0 on failure (should never happen, so ignore it) 3365 * 3366 * $(WARNING Don't use this method in object destructors!) 3367 * 3368 * $(WARNING It is better to register all event handlers and don't remove 'em, 3369 * 'cause if event handler id counter will overflow, you won't be able 3370 * to register any more events.) 3371 */ 3372 uint addEventListener(ET:Object) (void delegate (ET) dg) { 3373 if (dg is null) return 0; // ignore empty handlers 3374 synchronized(this) { 3375 //FIXME: abort on overflow? 3376 if (++lastUsedHandlerId == 0) { --lastUsedHandlerId; return 0; } // alas, can't register more events. at all. 3377 EventHandlerEntry e; 3378 e.dg = delegate (Object o) { 3379 if (auto co = cast(ET)o) { 3380 try { 3381 dg(co); 3382 } catch (Exception e) { 3383 // sorry! 3384 if(eventUncaughtException) 3385 eventUncaughtException(e); 3386 } 3387 return true; 3388 } 3389 return false; 3390 }; 3391 e.id = lastUsedHandlerId; 3392 auto optr = eventHandlers.ptr; 3393 eventHandlers ~= e; 3394 if (eventHandlers.ptr !is optr) { 3395 import core.memory : GC; 3396 if (eventHandlers.ptr is GC.addrOf(eventHandlers.ptr)) GC.setAttr(eventHandlers.ptr, GC.BlkAttr.NO_INTERIOR); 3397 } 3398 return lastUsedHandlerId; 3399 } 3400 } 3401 3402 /// Remove event listener. It is safe to pass invalid event id here. 3403 /// $(WARNING Don't use this method in object destructors!) 3404 void removeEventListener() (uint id) { 3405 if (id == 0 || id > lastUsedHandlerId) return; 3406 synchronized(this) { 3407 foreach (immutable idx; 0..eventHandlers.length) { 3408 if (eventHandlers[idx].id == id) { 3409 foreach (immutable c; idx+1..eventHandlers.length) eventHandlers[c-1] = eventHandlers[c]; 3410 eventHandlers[$-1].dg = null; 3411 eventHandlers.length -= 1; 3412 eventHandlers.assumeSafeAppend; 3413 return; 3414 } 3415 } 3416 } 3417 } 3418 3419 /// Post event to queue. It is safe to call this from non-UI threads. 3420 /// If `timeoutmsecs` is greater than zero, the event will be delayed for at least `timeoutmsecs` milliseconds. 3421 /// if `replace` is `true`, replace all existing events typed `ET` with the new one (if `evt` is empty, remove 'em all) 3422 /// Returns `true` if event was queued. Always returns `false` if `evt` is null. 3423 bool postTimeout(ET:Object) (ET evt, uint timeoutmsecs, bool replace=false) { 3424 if (this.closed) return false; // closed windows can't handle events 3425 3426 // remove all events of type `ET` 3427 void removeAllET () { 3428 uint eidx = 0, ec = eventQueueUsed; 3429 auto eptr = eventQueue.ptr; 3430 while (eidx < ec) { 3431 if (eptr.doProcess) { ++eidx; ++eptr; continue; } 3432 if (cast(ET)eptr.evt !is null) { 3433 // i found her! 3434 if (inCustomEventProcessor) { 3435 // if we're in custom event processing loop, processor will clear it for us 3436 eptr.evt = null; 3437 ++eidx; 3438 ++eptr; 3439 } else { 3440 foreach (immutable c; eidx+1..ec) eventQueue.ptr[c-1] = eventQueue.ptr[c]; 3441 ec = --eventQueueUsed; 3442 // clear last event (it is already copied) 3443 eventQueue.ptr[ec].evt = null; 3444 } 3445 } else { 3446 ++eidx; 3447 ++eptr; 3448 } 3449 } 3450 } 3451 3452 if (evt is null) { 3453 if (replace) { synchronized(this) removeAllET(); } 3454 // ignore empty events, they can't be handled anyway 3455 return false; 3456 } 3457 3458 // add events even if no event FD/event object created yet 3459 synchronized(this) { 3460 if (replace) removeAllET(); 3461 if (eventQueueUsed == uint.max) return false; // just in case 3462 if (eventQueueUsed < eventQueue.length) { 3463 eventQueue[eventQueueUsed++] = QueuedEvent(evt, timeoutmsecs); 3464 } else { 3465 if (eventQueue.capacity == eventQueue.length) { 3466 // need to reallocate; do a trick to ensure that old array is cleared 3467 auto oarr = eventQueue; 3468 eventQueue ~= QueuedEvent(evt, timeoutmsecs); 3469 // just in case, do yet another check 3470 if (oarr.length != 0 && oarr.ptr !is eventQueue.ptr) foreach (ref e; oarr[0..eventQueueUsed]) e.evt = null; 3471 import core.memory : GC; 3472 if (eventQueue.ptr is GC.addrOf(eventQueue.ptr)) GC.setAttr(eventQueue.ptr, GC.BlkAttr.NO_INTERIOR); 3473 } else { 3474 auto optr = eventQueue.ptr; 3475 eventQueue ~= QueuedEvent(evt, timeoutmsecs); 3476 assert(eventQueue.ptr is optr); 3477 } 3478 ++eventQueueUsed; 3479 assert(eventQueueUsed == eventQueue.length); 3480 } 3481 if (!eventWakeUp()) { 3482 // can't wake up event processor, so there is no reason to keep the event 3483 assert(eventQueueUsed > 0); 3484 eventQueue[--eventQueueUsed].evt = null; 3485 return false; 3486 } 3487 return true; 3488 } 3489 } 3490 3491 /// Post event to queue. It is safe to call this from non-UI threads. 3492 /// if `replace` is `true`, replace all existing events typed `ET` with the new one (if `evt` is empty, remove 'em all) 3493 /// Returns `true` if event was queued. Always returns `false` if `evt` is null. 3494 bool postEvent(ET:Object) (ET evt, bool replace=false) { 3495 return postTimeout!ET(evt, 0, replace); 3496 } 3497 3498 private: 3499 private import core.time : MonoTime; 3500 3501 version(Posix) { 3502 __gshared int customEventFDRead = -1; 3503 __gshared int customEventFDWrite = -1; 3504 __gshared int customSignalFD = -1; 3505 } else version(Windows) { 3506 __gshared HANDLE customEventH = null; 3507 } 3508 3509 // wake up event processor 3510 static bool eventWakeUp () { 3511 version(X11) { 3512 import core.sys.posix.unistd : write; 3513 ulong n = 1; 3514 if (customEventFDWrite >= 0) write(customEventFDWrite, &n, n.sizeof); 3515 return true; 3516 } else version(Windows) { 3517 if (customEventH !is null) SetEvent(customEventH); 3518 return true; 3519 } else { 3520 // not implemented for other OSes 3521 return false; 3522 } 3523 } 3524 3525 static struct QueuedEvent { 3526 Object evt; 3527 bool timed = false; 3528 MonoTime hittime = MonoTime.zero; 3529 bool doProcess = false; // process event at the current iteration (internal flag) 3530 3531 this (Object aevt, uint toutmsecs) { 3532 evt = aevt; 3533 if (toutmsecs > 0) { 3534 import core.time : msecs; 3535 timed = true; 3536 hittime = MonoTime.currTime+toutmsecs.msecs; 3537 } 3538 } 3539 } 3540 3541 alias CustomEventHandler = bool delegate (Object o) nothrow; 3542 static struct EventHandlerEntry { 3543 CustomEventHandler dg; 3544 uint id; 3545 } 3546 3547 uint lastUsedHandlerId; 3548 EventHandlerEntry[] eventHandlers; 3549 QueuedEvent[] eventQueue = null; 3550 uint eventQueueUsed = 0; // to avoid `.assumeSafeAppend` and length changes 3551 bool inCustomEventProcessor = false; // required to properly remove events 3552 3553 // process queued events and call custom event handlers 3554 // this will not process events posted from called handlers (such events are postponed for the next iteration) 3555 void processCustomEvents () { 3556 bool hasSomethingToDo = false; 3557 uint ecount; 3558 bool ocep; 3559 synchronized(this) { 3560 ocep = inCustomEventProcessor; 3561 inCustomEventProcessor = true; 3562 ecount = eventQueueUsed; // user may want to post new events from an event handler; process 'em on next iteration 3563 auto ctt = MonoTime.currTime; 3564 bool hasEmpty = false; 3565 // mark events to process (this is required for `eventQueued()`) 3566 foreach (ref qe; eventQueue[0..ecount]) { 3567 if (qe.evt is null) { hasEmpty = true; continue; } 3568 if (qe.timed) { 3569 qe.doProcess = (qe.hittime <= ctt); 3570 } else { 3571 qe.doProcess = true; 3572 } 3573 hasSomethingToDo = (hasSomethingToDo || qe.doProcess); 3574 } 3575 if (!hasSomethingToDo) { 3576 // remove empty events 3577 if (hasEmpty) { 3578 uint eidx = 0, ec = eventQueueUsed; 3579 auto eptr = eventQueue.ptr; 3580 while (eidx < ec) { 3581 if (eptr.evt is null) { 3582 foreach (immutable c; eidx+1..ec) eventQueue.ptr[c-1] = eventQueue.ptr[c]; 3583 ec = --eventQueueUsed; 3584 eventQueue.ptr[ec].evt = null; // make GC life easier 3585 } else { 3586 ++eidx; 3587 ++eptr; 3588 } 3589 } 3590 } 3591 inCustomEventProcessor = ocep; 3592 return; 3593 } 3594 } 3595 // process marked events 3596 uint efree = 0; // non-processed events will be put at this index 3597 EventHandlerEntry[] eh; 3598 Object evt; 3599 foreach (immutable eidx; 0..ecount) { 3600 synchronized(this) { 3601 if (!eventQueue[eidx].doProcess) { 3602 // skip this event 3603 assert(efree <= eidx); 3604 if (efree != eidx) { 3605 // copy this event to queue start 3606 eventQueue[efree] = eventQueue[eidx]; 3607 eventQueue[eidx].evt = null; // just in case 3608 } 3609 ++efree; 3610 continue; 3611 } 3612 evt = eventQueue[eidx].evt; 3613 eventQueue[eidx].evt = null; // in case event handler will hit GC 3614 if (evt is null) continue; // just in case 3615 // try all handlers; this can be slow, but meh... 3616 eh = eventHandlers; 3617 } 3618 foreach (ref evhan; eh) if (evhan.dg !is null) evhan.dg(evt); 3619 evt = null; 3620 eh = null; 3621 } 3622 synchronized(this) { 3623 // move all unprocessed events to queue top; efree holds first "free index" 3624 foreach (immutable eidx; ecount..eventQueueUsed) { 3625 assert(efree <= eidx); 3626 if (efree != eidx) eventQueue[efree] = eventQueue[eidx]; 3627 ++efree; 3628 } 3629 eventQueueUsed = efree; 3630 // wake up event processor on next event loop iteration if we have more queued events 3631 // also, remove empty events 3632 bool awaken = false; 3633 uint eidx = 0, ec = eventQueueUsed; 3634 auto eptr = eventQueue.ptr; 3635 while (eidx < ec) { 3636 if (eptr.evt is null) { 3637 foreach (immutable c; eidx+1..ec) eventQueue.ptr[c-1] = eventQueue.ptr[c]; 3638 ec = --eventQueueUsed; 3639 eventQueue.ptr[ec].evt = null; // make GC life easier 3640 } else { 3641 if (!awaken && !eptr.timed) { eventWakeUp(); awaken = true; } 3642 ++eidx; 3643 ++eptr; 3644 } 3645 } 3646 inCustomEventProcessor = ocep; 3647 } 3648 } 3649 3650 // for all windows in nativeMapping 3651 package static void processAllCustomEvents () { 3652 3653 cleanupQueue.process(); 3654 3655 justCommunication.processCustomEvents(); 3656 3657 foreach (SimpleWindow sw; SimpleWindow.nativeMapping.byValue) { 3658 if (sw is null || sw.closed) continue; 3659 sw.processCustomEvents(); 3660 } 3661 3662 runPendingRunInGuiThreadDelegates(); 3663 } 3664 3665 // 0: infinite (i.e. no scheduled events in queue) 3666 uint eventQueueTimeoutMSecs () { 3667 synchronized(this) { 3668 if (eventQueueUsed == 0) return 0; 3669 if (inCustomEventProcessor) assert(0, "WUTAFUUUUUUU..."); // the thing that should not be. ABSOLUTELY! (c) 3670 uint res = int.max; 3671 auto ctt = MonoTime.currTime; 3672 foreach (const ref qe; eventQueue[0..eventQueueUsed]) { 3673 if (qe.evt is null) assert(0, "WUTAFUUUUUUU..."); // the thing that should not be. ABSOLUTELY! (c) 3674 if (qe.doProcess) continue; // just in case 3675 if (!qe.timed) return 1; // minimal 3676 if (qe.hittime <= ctt) return 1; // minimal 3677 auto tms = (qe.hittime-ctt).total!"msecs"; 3678 if (tms < 1) tms = 1; // safety net 3679 if (tms >= int.max) tms = int.max-1; // and another safety net 3680 if (res > tms) res = cast(uint)tms; 3681 } 3682 return (res >= int.max ? 0 : res); 3683 } 3684 } 3685 3686 // for all windows in nativeMapping 3687 static uint eventAllQueueTimeoutMSecs () { 3688 uint res = uint.max; 3689 foreach (SimpleWindow sw; SimpleWindow.nativeMapping.byValue) { 3690 if (sw is null || sw.closed) continue; 3691 uint to = sw.eventQueueTimeoutMSecs(); 3692 if (to && to < res) { 3693 res = to; 3694 if (to == 1) break; // can't have less than this 3695 } 3696 } 3697 return (res >= int.max ? 0 : res); 3698 } 3699 3700 version(X11) { 3701 ResizeEvent pendingResizeEvent; 3702 } 3703 3704 /++ 3705 When in opengl mode and automatically resizing, it will set the opengl viewport to stretch. 3706 3707 If you work with multiple opengl contexts and/or threads, this might be more trouble than it is 3708 worth so you can disable it by setting this to `true`. 3709 3710 History: 3711 Added November 13, 2022. 3712 +/ 3713 public bool suppressAutoOpenglViewport = false; 3714 private void updateOpenglViewportIfNeeded(int width, int height) { 3715 if(suppressAutoOpenglViewport) return; 3716 3717 version(without_opengl) {} else 3718 if(openglMode == OpenGlOptions.yes && resizability == Resizability.automaticallyScaleIfPossible) { 3719 setAsCurrentOpenGlContextNT(); 3720 glViewport(0, 0, width, height); 3721 } 3722 } 3723 } 3724 3725 /++ 3726 Magic pseudo-window for just posting events to a global queue. 3727 3728 Not entirely supported, I might delete it at any time. 3729 3730 Added Nov 5, 2021. 3731 +/ 3732 __gshared SimpleWindow justCommunication = new SimpleWindow(NativeWindowHandle.init); 3733 3734 /* Drag and drop support { */ 3735 version(X11) { 3736 3737 } else version(Windows) { 3738 import core.sys.windows.uuid; 3739 import core.sys.windows.ole2; 3740 import core.sys.windows.oleidl; 3741 import core.sys.windows.objidl; 3742 import core.sys.windows.wtypes; 3743 3744 pragma(lib, "ole32"); 3745 void initDnd() { 3746 auto err = OleInitialize(null); 3747 if(err != S_OK && err != S_FALSE) 3748 throw new Exception("init");//err); 3749 } 3750 } 3751 /* } End drag and drop support */ 3752 3753 3754 /// Represents a mouse cursor (aka the mouse pointer, the image seen on screen that indicates where the mouse is pointing). 3755 /// See [GenericCursor]. 3756 class MouseCursor { 3757 int osId; 3758 bool isStockCursor; 3759 private this(int osId) { 3760 this.osId = osId; 3761 this.isStockCursor = true; 3762 } 3763 3764 // https://msdn.microsoft.com/en-us/library/windows/desktop/ms648385(v=vs.85).aspx 3765 this(int xHotSpot, int yHotSpot, ubyte[] andMask, ubyte[] xorMask) {} 3766 3767 version(Windows) { 3768 HCURSOR cursor_; 3769 HCURSOR cursorHandle() { 3770 if(cursor_ is null) 3771 cursor_ = LoadCursor(null, MAKEINTRESOURCE(osId)); 3772 return cursor_; 3773 } 3774 3775 } else static if(UsingSimpledisplayX11) { 3776 Cursor cursor_ = None; 3777 int xDisplaySequence; 3778 3779 Cursor cursorHandle() { 3780 if(this.osId == None) 3781 return None; 3782 3783 // we need to reload if we on a new X connection 3784 if(cursor_ == None || XDisplayConnection.connectionSequenceNumber != xDisplaySequence) { 3785 cursor_ = XCreateFontCursor(XDisplayConnection.get(), this.osId); 3786 xDisplaySequence = XDisplayConnection.connectionSequenceNumber; 3787 } 3788 return cursor_; 3789 } 3790 } 3791 } 3792 3793 // https://developer.mozilla.org/en-US/docs/Web/CSS/cursor 3794 // https://tronche.com/gui/x/xlib/appendix/b/ 3795 // https://msdn.microsoft.com/en-us/library/windows/desktop/ms648391(v=vs.85).aspx 3796 /// 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. 3797 enum GenericCursorType { 3798 Default, /// The default arrow pointer. 3799 Wait, /// A cursor indicating something is loading and the user must wait. 3800 Hand, /// A pointing finger, like the one used hovering over hyperlinks in a web browser. 3801 Help, /// A cursor indicating the user can get help about the pointer location. 3802 Cross, /// A crosshair. 3803 Text, /// An i-beam shape, typically used to indicate text selection is possible. 3804 Move, /// Pointer indicating movement is possible. May also be used as SizeAll. 3805 UpArrow, /// An arrow pointing straight up. 3806 Progress, /// The hourglass and arrow, indicating the computer is working but the user can still work. Not great results on X11. 3807 NotAllowed, /// Indicates the current operation is not allowed. Not great results on X11. 3808 SizeNesw, /// Arrow pointing northeast and southwest (lower-left corner resize indicator). 3809 SizeNs, /// Arrow pointing north and south (upper/lower edge resize indicator). 3810 SizeNwse, /// Arrow pointing northwest and southeast (upper-left corner resize indicator). 3811 SizeWe, /// Arrow pointing west and east (left/right edge resize indicator). 3812 3813 } 3814 3815 /* 3816 X_plus == css cell == Windows ? 3817 */ 3818 3819 /// You get one by `GenericCursor.SomeTime`. See [GenericCursorType] for a list of types. 3820 static struct GenericCursor { 3821 static: 3822 /// 3823 MouseCursor opDispatch(string str)() if(__traits(hasMember, GenericCursorType, str)) { 3824 static MouseCursor mc; 3825 3826 auto type = __traits(getMember, GenericCursorType, str); 3827 3828 if(mc is null) { 3829 3830 version(Windows) { 3831 int osId; 3832 final switch(type) { 3833 case GenericCursorType.Default: osId = IDC_ARROW; break; 3834 case GenericCursorType.Wait: osId = IDC_WAIT; break; 3835 case GenericCursorType.Hand: osId = IDC_HAND; break; 3836 case GenericCursorType.Help: osId = IDC_HELP; break; 3837 case GenericCursorType.Cross: osId = IDC_CROSS; break; 3838 case GenericCursorType.Text: osId = IDC_IBEAM; break; 3839 case GenericCursorType.Move: osId = IDC_SIZEALL; break; 3840 case GenericCursorType.UpArrow: osId = IDC_UPARROW; break; 3841 case GenericCursorType.Progress: osId = IDC_APPSTARTING; break; 3842 case GenericCursorType.NotAllowed: osId = IDC_NO; break; 3843 case GenericCursorType.SizeNesw: osId = IDC_SIZENESW; break; 3844 case GenericCursorType.SizeNs: osId = IDC_SIZENS; break; 3845 case GenericCursorType.SizeNwse: osId = IDC_SIZENWSE; break; 3846 case GenericCursorType.SizeWe: osId = IDC_SIZEWE; break; 3847 } 3848 } else static if(UsingSimpledisplayX11) { 3849 int osId; 3850 final switch(type) { 3851 case GenericCursorType.Default: osId = None; break; 3852 case GenericCursorType.Wait: osId = 150 /* XC_watch */; break; 3853 case GenericCursorType.Hand: osId = 60 /* XC_hand2 */; break; 3854 case GenericCursorType.Help: osId = 92 /* XC_question_arrow */; break; 3855 case GenericCursorType.Cross: osId = 34 /* XC_crosshair */; break; 3856 case GenericCursorType.Text: osId = 152 /* XC_xterm */; break; 3857 case GenericCursorType.Move: osId = 52 /* XC_fleur */; break; 3858 case GenericCursorType.UpArrow: osId = 22 /* XC_center_ptr */; break; 3859 case GenericCursorType.Progress: osId = 150 /* XC_watch, best i can do i think */; break; 3860 3861 case GenericCursorType.NotAllowed: osId = 24 /* XC_circle. not great */; break; 3862 case GenericCursorType.SizeNesw: osId = 12 /* XC_bottom_left_corner */ ; break; 3863 case GenericCursorType.SizeNs: osId = 116 /* XC_sb_v_double_arrow */; break; 3864 case GenericCursorType.SizeNwse: osId = 14 /* XC_bottom_right_corner */; break; 3865 case GenericCursorType.SizeWe: osId = 108 /* XC_sb_h_double_arrow */; break; 3866 } 3867 3868 } else featureNotImplemented(); 3869 3870 mc = new MouseCursor(osId); 3871 } 3872 return mc; 3873 } 3874 } 3875 3876 3877 /++ 3878 If you want to get more control over the event loop, you can use this. 3879 3880 Typically though, you can just call [SimpleWindow.eventLoop] which forwards 3881 to `EventLoop.get.run`. 3882 +/ 3883 struct EventLoop { 3884 @disable this(); 3885 3886 /// Gets a reference to an existing event loop 3887 static EventLoop get() { 3888 return EventLoop(0, null); 3889 } 3890 3891 static void quitApplication() { 3892 EventLoop.get().exit(); 3893 } 3894 3895 private __gshared static Object monitor = new Object(); // deliberate CTFE usage here fyi 3896 3897 /// Construct an application-global event loop for yourself 3898 /// See_Also: [SimpleWindow.setEventHandlers] 3899 this(long pulseTimeout, void delegate() handlePulse) { 3900 synchronized(monitor) { 3901 if(impl is null) { 3902 claimGuiThread(); 3903 version(sdpy_thread_checks) assert(thisIsGuiThread); 3904 impl = new EventLoopImpl(pulseTimeout, handlePulse); 3905 } else { 3906 if(pulseTimeout) { 3907 impl.pulseTimeout = pulseTimeout; 3908 impl.handlePulse = handlePulse; 3909 } 3910 } 3911 impl.refcount++; 3912 } 3913 } 3914 3915 ~this() { 3916 if(impl is null) 3917 return; 3918 impl.refcount--; 3919 if(impl.refcount == 0) { 3920 impl.dispose(); 3921 if(thisIsGuiThread) 3922 guiThreadFinalize(); 3923 } 3924 3925 } 3926 3927 this(this) { 3928 if(impl is null) 3929 return; 3930 impl.refcount++; 3931 } 3932 3933 /// Runs the event loop until the whileCondition, if present, returns false 3934 int run(bool delegate() whileCondition = null) { 3935 assert(impl !is null); 3936 impl.notExited = true; 3937 return impl.run(whileCondition); 3938 } 3939 3940 /// Exits the event loop 3941 void exit() { 3942 assert(impl !is null); 3943 impl.notExited = false; 3944 } 3945 3946 version(linux) 3947 ref void delegate(int) signalHandler() { 3948 assert(impl !is null); 3949 return impl.signalHandler; 3950 } 3951 3952 __gshared static EventLoopImpl* impl; 3953 } 3954 3955 version(linux) 3956 void delegate(int, int) globalHupHandler; 3957 3958 version(Posix) 3959 void makeNonBlocking(int fd) { 3960 import fcntl = core.sys.posix.fcntl; 3961 auto flags = fcntl.fcntl(fd, fcntl.F_GETFL, 0); 3962 if(flags == -1) 3963 throw new Exception("fcntl get"); 3964 flags |= fcntl.O_NONBLOCK; 3965 auto s = fcntl.fcntl(fd, fcntl.F_SETFL, flags); 3966 if(s == -1) 3967 throw new Exception("fcntl set"); 3968 } 3969 3970 struct EventLoopImpl { 3971 int refcount; 3972 3973 bool notExited = true; 3974 3975 version(linux) { 3976 static import ep = core.sys.linux.epoll; 3977 static import unix = core.sys.posix.unistd; 3978 static import err = core.stdc.errno; 3979 import core.sys.linux.timerfd; 3980 3981 void delegate(int) signalHandler; 3982 } 3983 3984 version(X11) { 3985 int pulseFd = -1; 3986 version(linux) ep.epoll_event[16] events = void; 3987 } else version(Windows) { 3988 Timer pulser; 3989 HANDLE[] handles; 3990 } 3991 3992 3993 /// "Lock" this window handle, to do multithreaded synchronization. You probably won't need 3994 /// to call this, as it's not recommended to share window between threads. 3995 void mtLock () { 3996 version(X11) { 3997 XLockDisplay(this.display); 3998 } 3999 } 4000 4001 version(X11) 4002 auto display() { return XDisplayConnection.get; } 4003 4004 /// "Unlock" this window handle, to do multithreaded synchronization. You probably won't need 4005 /// to call this, as it's not recommended to share window between threads. 4006 void mtUnlock () { 4007 version(X11) { 4008 XUnlockDisplay(this.display); 4009 } 4010 } 4011 4012 version(with_eventloop) 4013 void initialize(long pulseTimeout) {} 4014 else 4015 void initialize(long pulseTimeout) { 4016 version(Windows) { 4017 if(pulseTimeout && handlePulse !is null) 4018 pulser = new Timer(cast(int) pulseTimeout, handlePulse); 4019 4020 if (customEventH is null) { 4021 customEventH = CreateEvent(null, FALSE/*autoreset*/, FALSE/*initial state*/, null); 4022 if (customEventH !is null) { 4023 handles ~= customEventH; 4024 } else { 4025 // this is something that should not be; better be safe than sorry 4026 throw new Exception("can't create eventfd for custom event processing"); 4027 } 4028 } 4029 4030 SimpleWindow.processAllCustomEvents(); // process events added before event object creation 4031 } 4032 4033 version(linux) { 4034 prepareEventLoop(); 4035 { 4036 auto display = XDisplayConnection.get; 4037 // adding Xlib file 4038 ep.epoll_event ev = void; 4039 { import core.stdc.string : memset; memset(&ev, 0, ev.sizeof); } // this makes valgrind happy 4040 ev.events = ep.EPOLLIN; 4041 ev.data.fd = display.fd; 4042 //import std.conv; 4043 if(ep.epoll_ctl(epollFd, ep.EPOLL_CTL_ADD, display.fd, &ev) == -1) 4044 throw new Exception("add x fd");// ~ to!string(epollFd)); 4045 displayFd = display.fd; 4046 } 4047 4048 if(pulseTimeout && handlePulse !is null) { 4049 pulseFd = timerfd_create(CLOCK_MONOTONIC, 0); 4050 if(pulseFd == -1) 4051 throw new Exception("pulse timer create failed"); 4052 4053 itimerspec value; 4054 value.it_value.tv_sec = cast(int) (pulseTimeout / 1000); 4055 value.it_value.tv_nsec = (pulseTimeout % 1000) * 1000_000; 4056 4057 value.it_interval.tv_sec = cast(int) (pulseTimeout / 1000); 4058 value.it_interval.tv_nsec = (pulseTimeout % 1000) * 1000_000; 4059 4060 if(timerfd_settime(pulseFd, 0, &value, null) == -1) 4061 throw new Exception("couldn't make pulse timer"); 4062 4063 ep.epoll_event ev = void; 4064 { import core.stdc.string : memset; memset(&ev, 0, ev.sizeof); } // this makes valgrind happy 4065 ev.events = ep.EPOLLIN; 4066 ev.data.fd = pulseFd; 4067 ep.epoll_ctl(epollFd, ep.EPOLL_CTL_ADD, pulseFd, &ev); 4068 } 4069 4070 // eventfd for custom events 4071 if (customEventFDWrite == -1) { 4072 customEventFDWrite = eventfd(0, 0); 4073 customEventFDRead = customEventFDWrite; 4074 if (customEventFDRead >= 0) { 4075 ep.epoll_event ev = void; 4076 { import core.stdc.string : memset; memset(&ev, 0, ev.sizeof); } // this makes valgrind happy 4077 ev.events = ep.EPOLLIN; 4078 ev.data.fd = customEventFDRead; 4079 ep.epoll_ctl(epollFd, ep.EPOLL_CTL_ADD, customEventFDRead, &ev); 4080 } else { 4081 // this is something that should not be; better be safe than sorry 4082 throw new Exception("can't create eventfd for custom event processing"); 4083 } 4084 } 4085 4086 if (customSignalFD == -1) { 4087 import core.sys.linux.sys.signalfd; 4088 4089 sigset_t sigset; 4090 auto err = sigemptyset(&sigset); 4091 assert(!err); 4092 err = sigaddset(&sigset, SIGINT); 4093 assert(!err); 4094 err = sigaddset(&sigset, SIGHUP); 4095 assert(!err); 4096 err = sigprocmask(SIG_BLOCK, &sigset, null); 4097 assert(!err); 4098 4099 customSignalFD = signalfd(-1, &sigset, SFD_NONBLOCK); 4100 assert(customSignalFD != -1); 4101 4102 ep.epoll_event ev = void; 4103 { import core.stdc.string : memset; memset(&ev, 0, ev.sizeof); } // this makes valgrind happy 4104 ev.events = ep.EPOLLIN; 4105 ev.data.fd = customSignalFD; 4106 ep.epoll_ctl(epollFd, ep.EPOLL_CTL_ADD, customSignalFD, &ev); 4107 } 4108 } else version(Posix) { 4109 prepareEventLoop(); 4110 if (customEventFDRead == -1) { 4111 int[2] bfr; 4112 import core.sys.posix.unistd; 4113 auto ret = pipe(bfr); 4114 if(ret == -1) throw new Exception("pipe"); 4115 customEventFDRead = bfr[0]; 4116 customEventFDWrite = bfr[1]; 4117 } 4118 4119 } 4120 4121 SimpleWindow.processAllCustomEvents(); // process events added before event FD creation 4122 4123 version(linux) { 4124 this.mtLock(); 4125 scope(exit) this.mtUnlock(); 4126 XPending(display); // no, really 4127 } 4128 4129 disposed = false; 4130 } 4131 4132 bool disposed = true; 4133 version(X11) 4134 int displayFd = -1; 4135 4136 version(with_eventloop) 4137 void dispose() {} 4138 else 4139 void dispose() { 4140 disposed = true; 4141 version(X11) { 4142 if(pulseFd != -1) { 4143 import unix = core.sys.posix.unistd; 4144 unix.close(pulseFd); 4145 pulseFd = -1; 4146 } 4147 4148 version(linux) 4149 if(displayFd != -1) { 4150 // 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 4151 ep.epoll_event ev = void; 4152 { import core.stdc.string : memset; memset(&ev, 0, ev.sizeof); } // this makes valgrind happy 4153 ev.events = ep.EPOLLIN; 4154 ev.data.fd = displayFd; 4155 //import std.conv; 4156 ep.epoll_ctl(epollFd, ep.EPOLL_CTL_DEL, displayFd, &ev); 4157 displayFd = -1; 4158 } 4159 4160 } else version(Windows) { 4161 if(pulser !is null) { 4162 pulser.destroy(); 4163 pulser = null; 4164 } 4165 if (customEventH !is null) { 4166 CloseHandle(customEventH); 4167 customEventH = null; 4168 } 4169 } 4170 } 4171 4172 this(long pulseTimeout, void delegate() handlePulse) { 4173 this.pulseTimeout = pulseTimeout; 4174 this.handlePulse = handlePulse; 4175 initialize(pulseTimeout); 4176 } 4177 4178 private long pulseTimeout; 4179 void delegate() handlePulse; 4180 4181 ~this() { 4182 dispose(); 4183 } 4184 4185 version(Posix) 4186 ref int customEventFDRead() { return SimpleWindow.customEventFDRead; } 4187 version(Posix) 4188 ref int customEventFDWrite() { return SimpleWindow.customEventFDWrite; } 4189 version(linux) 4190 ref int customSignalFD() { return SimpleWindow.customSignalFD; } 4191 version(Windows) 4192 ref auto customEventH() { return SimpleWindow.customEventH; } 4193 4194 version(with_eventloop) { 4195 int loopHelper(bool delegate() whileCondition) { 4196 // FIXME: whileCondition 4197 import arsd.eventloop; 4198 loop(); 4199 return 0; 4200 } 4201 } else 4202 int loopHelper(bool delegate() whileCondition) { 4203 version(X11) { 4204 bool done = false; 4205 4206 XFlush(display); 4207 insideXEventLoop = true; 4208 scope(exit) insideXEventLoop = false; 4209 4210 version(linux) { 4211 while(!done && (whileCondition is null || whileCondition() == true) && notExited) { 4212 bool forceXPending = false; 4213 auto wto = SimpleWindow.eventAllQueueTimeoutMSecs(); 4214 // eh... some events may be queued for "squashing" (or "late delivery"), so we have to do the following magic 4215 { 4216 this.mtLock(); 4217 scope(exit) this.mtUnlock(); 4218 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 4219 } 4220 //{ import core.stdc.stdio; printf("*** wto=%d; force=%d\n", wto, (forceXPending ? 1 : 0)); } 4221 auto nfds = ep.epoll_wait(epollFd, events.ptr, events.length, (wto == 0 || wto >= int.max ? -1 : cast(int)wto)); 4222 if(nfds == -1) { 4223 if(err.errno == err.EINTR) { 4224 //if(forceXPending) goto xpending; 4225 continue; // interrupted by signal, just try again 4226 } 4227 throw new Exception("epoll wait failure"); 4228 } 4229 4230 SimpleWindow.processAllCustomEvents(); // anyway 4231 //version(sdddd) { import std.stdio; writeln("nfds=", nfds, "; [0]=", events[0].data.fd); } 4232 foreach(idx; 0 .. nfds) { 4233 if(done) break; 4234 auto fd = events[idx].data.fd; 4235 assert(fd != -1); // should never happen cuz the api doesn't do that but better to assert than assume. 4236 auto flags = events[idx].events; 4237 if(flags & ep.EPOLLIN) { 4238 if (fd == customSignalFD) { 4239 version(linux) { 4240 import core.sys.linux.sys.signalfd; 4241 import core.sys.posix.unistd : read; 4242 signalfd_siginfo info; 4243 read(customSignalFD, &info, info.sizeof); 4244 4245 auto sig = info.ssi_signo; 4246 4247 if(EventLoop.get.signalHandler !is null) { 4248 EventLoop.get.signalHandler()(sig); 4249 } else { 4250 EventLoop.get.exit(); 4251 } 4252 } 4253 } else if(fd == display.fd) { 4254 version(sdddd) { import std.stdio; writeln("X EVENT PENDING!"); } 4255 this.mtLock(); 4256 scope(exit) this.mtUnlock(); 4257 while(!done && XPending(display)) { 4258 done = doXNextEvent(this.display); 4259 } 4260 forceXPending = false; 4261 } else if(fd == pulseFd) { 4262 long expirationCount; 4263 // 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... 4264 4265 handlePulse(); 4266 4267 // read just to clear the buffer so poll doesn't trigger again 4268 // BTW I read AFTER the pulse because if the pulse handler takes 4269 // a lot of time to execute, we don't want the app to get stuck 4270 // in a loop of timer hits without a chance to do anything else 4271 // 4272 // IOW handlePulse happens at most once per pulse interval. 4273 unix.read(pulseFd, &expirationCount, expirationCount.sizeof); 4274 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 4275 } else if (fd == customEventFDRead) { 4276 // we have some custom events; process 'em 4277 import core.sys.posix.unistd : read; 4278 ulong n; 4279 read(customEventFDRead, &n, n.sizeof); // reset counter value to zero again 4280 //{ import core.stdc.stdio; printf("custom event! count=%u\n", eventQueueUsed); } 4281 //SimpleWindow.processAllCustomEvents(); 4282 } else { 4283 // some other timer 4284 version(sdddd) { import std.stdio; writeln("unknown fd: ", fd); } 4285 4286 if(Timer* t = fd in Timer.mapping) 4287 (*t).trigger(); 4288 4289 if(PosixFdReader* pfr = fd in PosixFdReader.mapping) 4290 (*pfr).ready(flags); 4291 4292 // or i might add support for other FDs too 4293 // but for now it is just timer 4294 // (if you want other fds, use arsd.eventloop and compile with -version=with_eventloop), it offers a fuller api for arbitrary stuff. 4295 } 4296 } 4297 if(flags & ep.EPOLLHUP) { 4298 if(PosixFdReader* pfr = fd in PosixFdReader.mapping) 4299 (*pfr).hup(flags); 4300 if(globalHupHandler) 4301 globalHupHandler(fd, flags); 4302 } 4303 /+ 4304 } else { 4305 // not interested in OUT, we are just reading here. 4306 // 4307 // error or hup might also be reported 4308 // but it shouldn't here since we are only 4309 // using a few types of FD and Xlib will report 4310 // if it dies. 4311 // so instead of thoughtfully handling it, I'll 4312 // just throw. for now at least 4313 4314 throw new Exception("epoll did something else"); 4315 } 4316 +/ 4317 } 4318 // if we won't call `XPending()` here, libX may delay some internal event delivery. 4319 // i.e. we HAVE to repeatedly call `XPending()` even if libX fd wasn't signalled! 4320 xpending: 4321 if (!done && forceXPending) { 4322 this.mtLock(); 4323 scope(exit) this.mtUnlock(); 4324 //{ import core.stdc.stdio; printf("*** queued: %d\n", XEventsQueued(this.display, QueueMode.QueuedAlready)); } 4325 while(!done && XPending(display)) { 4326 done = doXNextEvent(this.display); 4327 } 4328 } 4329 } 4330 } else { 4331 // Generic fallback: yes to simple pulse support, 4332 // but NO timer support! 4333 4334 // FIXME: we could probably support the POSIX timer_create 4335 // signal-based option, but I'm in no rush to write it since 4336 // I prefer the fd-based functions. 4337 while (!done && (whileCondition is null || whileCondition() == true) && notExited) { 4338 4339 import core.sys.posix.poll; 4340 4341 pollfd[] pfds; 4342 pollfd[32] pfdsBuffer; 4343 auto len = PosixFdReader.mapping.length + 2; 4344 // FIXME: i should just reuse the buffer 4345 if(len < pfdsBuffer.length) 4346 pfds = pfdsBuffer[0 .. len]; 4347 else 4348 pfds = new pollfd[](len); 4349 4350 pfds[0].fd = display.fd; 4351 pfds[0].events = POLLIN; 4352 pfds[0].revents = 0; 4353 4354 int slot = 1; 4355 4356 if(customEventFDRead != -1) { 4357 pfds[slot].fd = customEventFDRead; 4358 pfds[slot].events = POLLIN; 4359 pfds[slot].revents = 0; 4360 4361 slot++; 4362 } 4363 4364 foreach(fd, obj; PosixFdReader.mapping) { 4365 if(!obj.enabled) continue; 4366 pfds[slot].fd = fd; 4367 pfds[slot].events = POLLIN; 4368 pfds[slot].revents = 0; 4369 4370 slot++; 4371 } 4372 4373 auto ret = poll(pfds.ptr, slot, pulseTimeout > 0 ? cast(int) pulseTimeout : -1); 4374 if(ret == -1) throw new Exception("poll"); 4375 4376 if(ret == 0) { 4377 // FIXME it may not necessarily time out if events keep coming 4378 if(handlePulse !is null) 4379 handlePulse(); 4380 } else { 4381 foreach(s; 0 .. slot) { 4382 if(pfds[s].revents == 0) continue; 4383 4384 if(pfds[s].fd == display.fd) { 4385 while(!done && XPending(display)) { 4386 this.mtLock(); 4387 scope(exit) this.mtUnlock(); 4388 done = doXNextEvent(this.display); 4389 } 4390 } else if(customEventFDRead != -1 && pfds[s].fd == customEventFDRead) { 4391 4392 import core.sys.posix.unistd : read; 4393 ulong n; 4394 read(customEventFDRead, &n, n.sizeof); 4395 SimpleWindow.processAllCustomEvents(); 4396 } else { 4397 auto obj = PosixFdReader.mapping[pfds[s].fd]; 4398 if(pfds[s].revents & POLLNVAL) { 4399 obj.dispose(); 4400 } else { 4401 obj.ready(pfds[s].revents); 4402 } 4403 } 4404 4405 ret--; 4406 if(ret == 0) break; 4407 } 4408 } 4409 } 4410 } 4411 } 4412 4413 version(Windows) { 4414 int ret = -1; 4415 MSG message; 4416 while(ret != 0 && (whileCondition is null || whileCondition() == true) && notExited) { 4417 eventLoopRound++; 4418 auto wto = SimpleWindow.eventAllQueueTimeoutMSecs(); 4419 auto waitResult = MsgWaitForMultipleObjectsEx( 4420 cast(int) handles.length, handles.ptr, 4421 (wto == 0 ? INFINITE : wto), /* timeout */ 4422 0x04FF, /* QS_ALLINPUT */ 4423 0x0002 /* MWMO_ALERTABLE */ | 0x0004 /* MWMO_INPUTAVAILABLE */); 4424 4425 SimpleWindow.processAllCustomEvents(); // anyway 4426 enum WAIT_OBJECT_0 = 0; 4427 if(waitResult >= WAIT_OBJECT_0 && waitResult < handles.length + WAIT_OBJECT_0) { 4428 auto h = handles[waitResult - WAIT_OBJECT_0]; 4429 if(auto e = h in WindowsHandleReader.mapping) { 4430 (*e).ready(); 4431 } 4432 } else if(waitResult == handles.length + WAIT_OBJECT_0) { 4433 // message ready 4434 int count; 4435 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 4436 ret = GetMessage(&message, null, 0, 0); 4437 if(ret == -1) 4438 throw new Exception("GetMessage failed"); 4439 TranslateMessage(&message); 4440 DispatchMessage(&message); 4441 4442 count++; 4443 if(count > 10) 4444 break; // take the opportunity to catch up on other events 4445 4446 if(ret == 0) { // WM_QUIT 4447 EventLoop.quitApplication(); 4448 break; 4449 } 4450 } 4451 } else if(waitResult == 0x000000C0L /* WAIT_IO_COMPLETION */) { 4452 SleepEx(0, true); // I call this to give it a chance to do stuff like async io 4453 } else if(waitResult == 258L /* WAIT_TIMEOUT */) { 4454 // timeout, should never happen since we aren't using it 4455 } else if(waitResult == 0xFFFFFFFF) { 4456 // failed 4457 throw new Exception("MsgWaitForMultipleObjectsEx failed"); 4458 } else { 4459 // idk.... 4460 } 4461 } 4462 4463 // return message.wParam; 4464 return 0; 4465 } else { 4466 return 0; 4467 } 4468 } 4469 4470 int run(bool delegate() whileCondition = null) { 4471 if(disposed) 4472 initialize(this.pulseTimeout); 4473 4474 version(X11) { 4475 try { 4476 return loopHelper(whileCondition); 4477 } catch(XDisconnectException e) { 4478 if(e.userRequested) { 4479 foreach(item; CapableOfHandlingNativeEvent.nativeHandleMapping) 4480 item.discardConnectionState(); 4481 XCloseDisplay(XDisplayConnection.display); 4482 } 4483 4484 XDisplayConnection.display = null; 4485 4486 this.dispose(); 4487 4488 throw e; 4489 } 4490 } else { 4491 return loopHelper(whileCondition); 4492 } 4493 } 4494 } 4495 4496 4497 /++ 4498 Provides an icon on the system notification area (also known as the system tray). 4499 4500 4501 If a notification area is not available with the NotificationIcon object is created, 4502 it will silently succeed and simply attempt to create one when an area becomes available. 4503 4504 4505 NotificationAreaIcon on Windows assumes you are on Windows Vista or later. 4506 If this is wrong, pass -version=WindowsXP to dmd when compiling and it will 4507 use the older version. 4508 +/ 4509 version(OSXCocoa) {} else // NotYetImplementedException 4510 class NotificationAreaIcon : CapableOfHandlingNativeEvent { 4511 4512 version(X11) { 4513 void recreateAfterDisconnect() { 4514 stateDiscarded = false; 4515 clippixmap = None; 4516 throw new Exception("NOT IMPLEMENTED"); 4517 } 4518 4519 bool stateDiscarded; 4520 void discardConnectionState() { 4521 stateDiscarded = true; 4522 } 4523 } 4524 4525 4526 version(X11) { 4527 Image img; 4528 4529 NativeEventHandler getNativeEventHandler() { 4530 return delegate int(XEvent e) { 4531 switch(e.type) { 4532 case EventType.Expose: 4533 //case EventType.VisibilityNotify: 4534 redraw(); 4535 break; 4536 case EventType.ClientMessage: 4537 version(sddddd) { 4538 import std.stdio; 4539 writeln("\t", e.xclient.message_type == GetAtom!("_XEMBED")(XDisplayConnection.get)); 4540 writeln("\t", e.xclient.format); 4541 writeln("\t", e.xclient.data.l); 4542 } 4543 break; 4544 case EventType.ButtonPress: 4545 auto event = e.xbutton; 4546 if (onClick !is null || onClickEx !is null) { 4547 MouseButton mb = cast(MouseButton)0; 4548 switch (event.button) { 4549 case 1: mb = MouseButton.left; break; // left 4550 case 2: mb = MouseButton.middle; break; // middle 4551 case 3: mb = MouseButton.right; break; // right 4552 case 4: mb = MouseButton.wheelUp; break; // scroll up 4553 case 5: mb = MouseButton.wheelDown; break; // scroll down 4554 case 6: break; // scroll left... 4555 case 7: break; // scroll right... 4556 case 8: mb = MouseButton.backButton; break; 4557 case 9: mb = MouseButton.forwardButton; break; 4558 default: 4559 } 4560 if (mb) { 4561 try { onClick()(mb); } catch (Exception) {} 4562 if (onClickEx !is null) try { onClickEx(event.x_root, event.y_root, mb, cast(ModifierState)event.state); } catch (Exception) {} 4563 } 4564 } 4565 break; 4566 case EventType.EnterNotify: 4567 if (onEnter !is null) { 4568 onEnter(e.xcrossing.x_root, e.xcrossing.y_root, cast(ModifierState)e.xcrossing.state); 4569 } 4570 break; 4571 case EventType.LeaveNotify: 4572 if (onLeave !is null) try { onLeave(); } catch (Exception) {} 4573 break; 4574 case EventType.DestroyNotify: 4575 active = false; 4576 CapableOfHandlingNativeEvent.nativeHandleMapping.remove(nativeHandle); 4577 break; 4578 case EventType.ConfigureNotify: 4579 auto event = e.xconfigure; 4580 this.width = event.width; 4581 this.height = event.height; 4582 //import std.stdio; writeln(width, " x " , height, " @ ", event.x, " ", event.y); 4583 redraw(); 4584 break; 4585 default: return 1; 4586 } 4587 return 1; 4588 }; 4589 } 4590 4591 /* private */ void hideBalloon() { 4592 balloon.close(); 4593 version(with_timer) 4594 timer.destroy(); 4595 balloon = null; 4596 version(with_timer) 4597 timer = null; 4598 } 4599 4600 void redraw() { 4601 if (!active) return; 4602 4603 auto display = XDisplayConnection.get; 4604 auto gc = DefaultGC(display, DefaultScreen(display)); 4605 XClearWindow(display, nativeHandle); 4606 4607 XSetClipMask(display, gc, clippixmap); 4608 4609 XSetForeground(display, gc, 4610 cast(uint) 0 << 16 | 4611 cast(uint) 0 << 8 | 4612 cast(uint) 0); 4613 XFillRectangle(display, nativeHandle, gc, 0, 0, width, height); 4614 4615 if (img is null) { 4616 XSetForeground(display, gc, 4617 cast(uint) 0 << 16 | 4618 cast(uint) 127 << 8 | 4619 cast(uint) 0); 4620 XFillArc(display, nativeHandle, 4621 gc, width / 4, height / 4, width * 2 / 4, height * 2 / 4, 0 * 64, 360 * 64); 4622 } else { 4623 int dx = 0; 4624 int dy = 0; 4625 if(width > img.width) 4626 dx = (width - img.width) / 2; 4627 if(height > img.height) 4628 dy = (height - img.height) / 2; 4629 XSetClipOrigin(display, gc, dx, dy); 4630 4631 if (img.usingXshm) 4632 XShmPutImage(display, cast(Drawable)nativeHandle, gc, img.handle, 0, 0, dx, dy, img.width, img.height, false); 4633 else 4634 XPutImage(display, cast(Drawable)nativeHandle, gc, img.handle, 0, 0, dx, dy, img.width, img.height); 4635 } 4636 XSetClipMask(display, gc, None); 4637 flushGui(); 4638 } 4639 4640 static Window getTrayOwner() { 4641 auto display = XDisplayConnection.get; 4642 auto i = cast(int) DefaultScreen(display); 4643 if(i < 10 && i >= 0) { 4644 static Atom atom; 4645 if(atom == None) 4646 atom = XInternAtom(display, cast(char*) ("_NET_SYSTEM_TRAY_S"~(cast(char) (i + '0')) ~ '\0').ptr, false); 4647 return XGetSelectionOwner(display, atom); 4648 } 4649 return None; 4650 } 4651 4652 static void sendTrayMessage(arch_long message, arch_long d1, arch_long d2, arch_long d3) { 4653 auto to = getTrayOwner(); 4654 auto display = XDisplayConnection.get; 4655 XEvent ev; 4656 ev.xclient.type = EventType.ClientMessage; 4657 ev.xclient.window = to; 4658 ev.xclient.message_type = GetAtom!("_NET_SYSTEM_TRAY_OPCODE", true)(display); 4659 ev.xclient.format = 32; 4660 ev.xclient.data.l[0] = CurrentTime; 4661 ev.xclient.data.l[1] = message; 4662 ev.xclient.data.l[2] = d1; 4663 ev.xclient.data.l[3] = d2; 4664 ev.xclient.data.l[4] = d3; 4665 4666 XSendEvent(XDisplayConnection.get, to, false, EventMask.NoEventMask, &ev); 4667 } 4668 4669 private static NotificationAreaIcon[] activeIcons; 4670 4671 // FIXME: possible leak with this stuff, should be able to clear it and stuff. 4672 private void newManager() { 4673 close(); 4674 createXWin(); 4675 4676 if(this.clippixmap) 4677 XFreePixmap(XDisplayConnection.get, clippixmap); 4678 if(this.originalMemoryImage) 4679 this.icon = this.originalMemoryImage; 4680 else if(this.img) 4681 this.icon = this.img; 4682 } 4683 4684 private void createXWin () { 4685 // create window 4686 auto display = XDisplayConnection.get; 4687 4688 // to check for MANAGER on root window to catch new/changed tray owners 4689 XDisplayConnection.addRootInput(EventMask.StructureNotifyMask); 4690 // so if a thing does appear, we can handle it 4691 foreach(ai; activeIcons) 4692 if(ai is this) 4693 goto alreadythere; 4694 activeIcons ~= this; 4695 alreadythere: 4696 4697 // and check for an existing tray 4698 auto trayOwner = getTrayOwner(); 4699 if(trayOwner == None) 4700 return; 4701 //throw new Exception("No notification area found"); 4702 4703 Visual* v = cast(Visual*) CopyFromParent; 4704 /+ 4705 auto visualProp = getX11PropertyData(trayOwner, GetAtom!("_NET_SYSTEM_TRAY_VISUAL", true)(display)); 4706 if(visualProp !is null) { 4707 c_ulong[] info = cast(c_ulong[]) visualProp; 4708 if(info.length == 1) { 4709 auto vid = info[0]; 4710 int returned; 4711 XVisualInfo t; 4712 t.visualid = vid; 4713 auto got = XGetVisualInfo(display, VisualIDMask, &t, &returned); 4714 if(got !is null) { 4715 if(returned == 1) { 4716 v = got.visual; 4717 import std.stdio; 4718 writeln("using special visual ", *got); 4719 } 4720 XFree(got); 4721 } 4722 } 4723 } 4724 +/ 4725 4726 auto nativeWindow = XCreateWindow(display, RootWindow(display, DefaultScreen(display)), 0, 0, 16, 16, 0, 24, InputOutput, v, 0, null); 4727 assert(nativeWindow); 4728 4729 XSetWindowBackgroundPixmap(display, nativeWindow, 1 /* ParentRelative */); 4730 4731 nativeHandle = nativeWindow; 4732 4733 ///+ 4734 arch_ulong[2] info; 4735 info[0] = 0; 4736 info[1] = 1; 4737 4738 string title = this.name is null ? "simpledisplay.d program" : this.name; 4739 auto XA_UTF8 = XInternAtom(display, "UTF8_STRING".ptr, false); 4740 auto XA_NETWM_NAME = XInternAtom(display, "_NET_WM_NAME".ptr, false); 4741 XChangeProperty(display, nativeWindow, XA_NETWM_NAME, XA_UTF8, 8, PropModeReplace, title.ptr, cast(uint)title.length); 4742 4743 XChangeProperty( 4744 display, 4745 nativeWindow, 4746 GetAtom!("_XEMBED_INFO", true)(display), 4747 GetAtom!("_XEMBED_INFO", true)(display), 4748 32 /* bits */, 4749 0 /*PropModeReplace*/, 4750 info.ptr, 4751 2); 4752 4753 import core.sys.posix.unistd; 4754 arch_ulong pid = getpid(); 4755 4756 XChangeProperty( 4757 display, 4758 nativeWindow, 4759 GetAtom!("_NET_WM_PID", true)(display), 4760 XA_CARDINAL, 4761 32 /* bits */, 4762 0 /*PropModeReplace*/, 4763 &pid, 4764 1); 4765 4766 updateNetWmIcon(); 4767 4768 if (sdpyWindowClassStr !is null && sdpyWindowClassStr[0]) { 4769 //{ import core.stdc.stdio; printf("winclass: [%s]\n", sdpyWindowClassStr); } 4770 XClassHint klass; 4771 XWMHints wh; 4772 XSizeHints size; 4773 klass.res_name = sdpyWindowClassStr; 4774 klass.res_class = sdpyWindowClassStr; 4775 XSetWMProperties(display, nativeWindow, null, null, null, 0, &size, &wh, &klass); 4776 } 4777 4778 // believe it or not, THIS is what xfce needed for the 9999 issue 4779 XSizeHints sh; 4780 c_long spr; 4781 XGetWMNormalHints(display, nativeWindow, &sh, &spr); 4782 sh.flags |= PMaxSize | PMinSize; 4783 // FIXME maybe nicer resizing 4784 sh.min_width = 16; 4785 sh.min_height = 16; 4786 sh.max_width = 16; 4787 sh.max_height = 16; 4788 XSetWMNormalHints(display, nativeWindow, &sh); 4789 4790 4791 //+/ 4792 4793 4794 XSelectInput(display, nativeWindow, 4795 EventMask.ButtonPressMask | EventMask.ExposureMask | EventMask.StructureNotifyMask | EventMask.VisibilityChangeMask | 4796 EventMask.EnterWindowMask | EventMask.LeaveWindowMask); 4797 4798 sendTrayMessage(SYSTEM_TRAY_REQUEST_DOCK, nativeWindow, 0, 0); 4799 CapableOfHandlingNativeEvent.nativeHandleMapping[nativeWindow] = this; 4800 active = true; 4801 } 4802 4803 void updateNetWmIcon() { 4804 if(img is null) return; 4805 auto display = XDisplayConnection.get; 4806 // FIXME: ensure this is correct 4807 arch_ulong[] buffer; 4808 auto imgMi = img.toTrueColorImage; 4809 buffer ~= imgMi.width; 4810 buffer ~= imgMi.height; 4811 foreach(c; imgMi.imageData.colors) { 4812 arch_ulong b; 4813 b |= c.a << 24; 4814 b |= c.r << 16; 4815 b |= c.g << 8; 4816 b |= c.b; 4817 buffer ~= b; 4818 } 4819 4820 XChangeProperty( 4821 display, 4822 nativeHandle, 4823 GetAtom!"_NET_WM_ICON"(display), 4824 GetAtom!"CARDINAL"(display), 4825 32 /* bits */, 4826 0 /*PropModeReplace*/, 4827 buffer.ptr, 4828 cast(int) buffer.length); 4829 } 4830 4831 4832 4833 private SimpleWindow balloon; 4834 version(with_timer) 4835 private Timer timer; 4836 4837 private Window nativeHandle; 4838 private Pixmap clippixmap = None; 4839 private int width = 16; 4840 private int height = 16; 4841 private bool active = false; 4842 4843 void delegate (int x, int y, MouseButton button, ModifierState mods) onClickEx; /// x and y are globals (relative to root window). X11 only. 4844 void delegate (int x, int y, ModifierState mods) onEnter; /// x and y are global window coordinates. X11 only. 4845 void delegate () onLeave; /// X11 only. 4846 4847 @property bool closed () const pure nothrow @safe @nogc { return !active; } /// 4848 4849 /// X11 only. Get global window coordinates and size. This can be used to show various notifications. 4850 void getWindowRect (out int x, out int y, out int width, out int height) { 4851 if (!active) { width = 1; height = 1; return; } // 1: just in case 4852 Window dummyw; 4853 auto dpy = XDisplayConnection.get; 4854 //XWindowAttributes xwa; 4855 //XGetWindowAttributes(dpy, nativeHandle, &xwa); 4856 //XTranslateCoordinates(dpy, nativeHandle, RootWindow(dpy, DefaultScreen(dpy)), xwa.x, xwa.y, &x, &y, &dummyw); 4857 XTranslateCoordinates(dpy, nativeHandle, RootWindow(dpy, DefaultScreen(dpy)), x, y, &x, &y, &dummyw); 4858 width = this.width; 4859 height = this.height; 4860 } 4861 } 4862 4863 /+ 4864 What I actually want from this: 4865 4866 * set / change: icon, tooltip 4867 * handle: mouse click, right click 4868 * show: notification bubble. 4869 +/ 4870 4871 version(Windows) { 4872 WindowsIcon win32Icon; 4873 HWND hwnd; 4874 4875 NOTIFYICONDATAW data; 4876 4877 NativeEventHandler getNativeEventHandler() { 4878 return delegate int(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam, out int mustReturn) { 4879 if(msg == WM_USER) { 4880 auto event = LOWORD(lParam); 4881 auto iconId = HIWORD(lParam); 4882 //auto x = GET_X_LPARAM(wParam); 4883 //auto y = GET_Y_LPARAM(wParam); 4884 switch(event) { 4885 case WM_LBUTTONDOWN: 4886 onClick()(MouseButton.left); 4887 break; 4888 case WM_RBUTTONDOWN: 4889 onClick()(MouseButton.right); 4890 break; 4891 case WM_MBUTTONDOWN: 4892 onClick()(MouseButton.middle); 4893 break; 4894 case WM_MOUSEMOVE: 4895 // sent, we could use it. 4896 break; 4897 case WM_MOUSEWHEEL: 4898 // NOT SENT 4899 break; 4900 //case NIN_KEYSELECT: 4901 //case NIN_SELECT: 4902 //break; 4903 default: {} 4904 } 4905 } 4906 return 0; 4907 }; 4908 } 4909 4910 enum NIF_SHOWTIP = 0x00000080; 4911 4912 private static struct NOTIFYICONDATAW { 4913 DWORD cbSize; 4914 HWND hWnd; 4915 UINT uID; 4916 UINT uFlags; 4917 UINT uCallbackMessage; 4918 HICON hIcon; 4919 WCHAR[128] szTip; 4920 DWORD dwState; 4921 DWORD dwStateMask; 4922 WCHAR[256] szInfo; 4923 union { 4924 UINT uTimeout; 4925 UINT uVersion; 4926 } 4927 WCHAR[64] szInfoTitle; 4928 DWORD dwInfoFlags; 4929 GUID guidItem; 4930 HICON hBalloonIcon; 4931 } 4932 4933 } 4934 4935 /++ 4936 Note that on Windows, only left, right, and middle buttons are sent. 4937 Mouse wheel buttons are NOT set, so don't rely on those events if your 4938 program is meant to be used on Windows too. 4939 +/ 4940 this(string name, MemoryImage icon, void delegate(MouseButton button) onClick) { 4941 // The canonical constructor for Windows needs the MemoryImage, so it is here, 4942 // but on X, we need an Image, so its canonical ctor is there. They should 4943 // forward to each other though. 4944 version(X11) { 4945 this.name = name; 4946 this.onClick = onClick; 4947 createXWin(); 4948 this.icon = icon; 4949 } else version(Windows) { 4950 this.onClick = onClick; 4951 this.win32Icon = new WindowsIcon(icon); 4952 4953 HINSTANCE hInstance = cast(HINSTANCE) GetModuleHandle(null); 4954 4955 static bool registered = false; 4956 if(!registered) { 4957 WNDCLASSEX wc; 4958 wc.cbSize = wc.sizeof; 4959 wc.hInstance = hInstance; 4960 wc.lpfnWndProc = &WndProc; 4961 wc.lpszClassName = "arsd_simpledisplay_notification_icon"w.ptr; 4962 if(!RegisterClassExW(&wc)) 4963 throw new WindowsApiException("RegisterClass"); 4964 registered = true; 4965 } 4966 4967 this.hwnd = CreateWindowW("arsd_simpledisplay_notification_icon"w.ptr, "test"w.ptr /* name */, 0 /* dwStyle */, 0, 0, 0, 0, HWND_MESSAGE, null, hInstance, null); 4968 if(hwnd is null) 4969 throw new Exception("CreateWindow"); 4970 4971 data.cbSize = data.sizeof; 4972 data.hWnd = hwnd; 4973 data.uID = cast(uint) cast(void*) this; 4974 data.uFlags = NIF_MESSAGE | NIF_ICON | NIF_TIP | NIF_STATE | NIF_SHOWTIP /* use default tooltip, for now. */; 4975 // NIF_INFO means show balloon 4976 data.uCallbackMessage = WM_USER; 4977 data.hIcon = this.win32Icon.hIcon; 4978 data.szTip = ""; // FIXME 4979 data.dwState = 0; // NIS_HIDDEN; // windows vista 4980 data.dwStateMask = NIS_HIDDEN; // windows vista 4981 4982 data.uVersion = 4; // NOTIFYICON_VERSION_4; // Windows Vista and up 4983 4984 4985 Shell_NotifyIcon(NIM_ADD, cast(NOTIFYICONDATA*) &data); 4986 4987 CapableOfHandlingNativeEvent.nativeHandleMapping[this.hwnd] = this; 4988 } else version(OSXCocoa) { 4989 throw new NotYetImplementedException(); 4990 } else static assert(0); 4991 } 4992 4993 /// ditto 4994 this(string name, Image icon, void delegate(MouseButton button) onClick) { 4995 version(X11) { 4996 this.onClick = onClick; 4997 this.name = name; 4998 createXWin(); 4999 this.icon = icon; 5000 } else version(Windows) { 5001 this(name, icon is null ? null : icon.toTrueColorImage(), onClick); 5002 } else version(OSXCocoa) { 5003 throw new NotYetImplementedException(); 5004 } else static assert(0); 5005 } 5006 5007 version(X11) { 5008 /++ 5009 X-specific extension (for now at least) 5010 +/ 5011 this(string name, MemoryImage icon, void delegate(int x, int y, MouseButton button, ModifierState mods) onClickEx) { 5012 this.onClickEx = onClickEx; 5013 createXWin(); 5014 if (icon !is null) this.icon = icon; 5015 } 5016 5017 /// ditto 5018 this(string name, Image icon, void delegate(int x, int y, MouseButton button, ModifierState mods) onClickEx) { 5019 this.onClickEx = onClickEx; 5020 createXWin(); 5021 this.icon = icon; 5022 } 5023 } 5024 5025 private void delegate (MouseButton button) onClick_; 5026 5027 /// 5028 @property final void delegate(MouseButton) onClick() { 5029 if(onClick_ is null) 5030 onClick_ = delegate void(MouseButton) {}; 5031 return onClick_; 5032 } 5033 5034 /// ditto 5035 @property final void onClick(void delegate(MouseButton) handler) { 5036 // I made this a property setter so we can wrap smaller arg 5037 // delegates and just forward all to onClickEx or something. 5038 onClick_ = handler; 5039 } 5040 5041 5042 string name_; 5043 @property void name(string n) { 5044 name_ = n; 5045 } 5046 5047 @property string name() { 5048 return name_; 5049 } 5050 5051 private MemoryImage originalMemoryImage; 5052 5053 /// 5054 @property void icon(MemoryImage i) { 5055 version(X11) { 5056 this.originalMemoryImage = i; 5057 if (!active) return; 5058 if (i !is null) { 5059 this.img = Image.fromMemoryImage(i); 5060 this.clippixmap = transparencyMaskFromMemoryImage(i, nativeHandle); 5061 //import std.stdio; writeln("using pixmap ", clippixmap); 5062 updateNetWmIcon(); 5063 redraw(); 5064 } else { 5065 if (this.img !is null) { 5066 this.img = null; 5067 redraw(); 5068 } 5069 } 5070 } else version(Windows) { 5071 this.win32Icon = new WindowsIcon(i); 5072 5073 data.uFlags = NIF_ICON; 5074 data.hIcon = this.win32Icon.hIcon; 5075 5076 Shell_NotifyIcon(NIM_MODIFY, cast(NOTIFYICONDATA*) &data); 5077 } else version(OSXCocoa) { 5078 throw new NotYetImplementedException(); 5079 } else static assert(0); 5080 } 5081 5082 /// ditto 5083 @property void icon (Image i) { 5084 version(X11) { 5085 if (!active) return; 5086 if (i !is img) { 5087 originalMemoryImage = null; 5088 img = i; 5089 redraw(); 5090 } 5091 } else version(Windows) { 5092 this.icon(i is null ? null : i.toTrueColorImage()); 5093 } else version(OSXCocoa) { 5094 throw new NotYetImplementedException(); 5095 } else static assert(0); 5096 } 5097 5098 /++ 5099 Shows a balloon notification. You can only show one balloon at a time, if you call 5100 it twice while one is already up, the first balloon will be replaced. 5101 5102 5103 The user is free to block notifications and they will automatically disappear after 5104 a timeout period. 5105 5106 Params: 5107 title = Title of the notification. Must be 40 chars or less or the OS may truncate it. 5108 message = The message to pop up. Must be 220 chars or less or the OS may truncate it. 5109 icon = the icon to display with the notification. If null, it uses your existing icon. 5110 onclick = delegate called if the user clicks the balloon. (not yet implemented) 5111 timeout = your suggested timeout period. The operating system is free to ignore your suggestion. 5112 +/ 5113 void showBalloon(string title, string message, MemoryImage icon = null, void delegate() onclick = null, int timeout = 2_500) { 5114 bool useCustom = true; 5115 version(libnotify) { 5116 if(onclick is null) // libnotify impl doesn't support callbacks yet because it doesn't do a dbus message loop 5117 try { 5118 if(!active) return; 5119 5120 if(libnotify is null) { 5121 libnotify = new C_DynamicLibrary("libnotify.so"); 5122 libnotify.call!("notify_init", int, const char*)()((ApplicationName ~ "\0").ptr); 5123 } 5124 5125 auto n = libnotify.call!("notify_notification_new", void*, const char*, const char*, const char*)()((title~"\0").ptr, (message~"\0").ptr, null /* icon */); 5126 5127 libnotify.call!("notify_notification_set_timeout", void, void*, int)()(n, timeout); 5128 5129 if(onclick) { 5130 libnotify_action_delegates[libnotify_action_delegates_count] = onclick; 5131 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); 5132 libnotify_action_delegates_count++; 5133 } 5134 5135 // FIXME icon 5136 5137 // set hint image-data 5138 // set default action for onclick 5139 5140 void* error; 5141 libnotify.call!("notify_notification_show", bool, void*, void**)()(n, &error); 5142 5143 useCustom = false; 5144 } catch(Exception e) { 5145 5146 } 5147 } 5148 5149 version(X11) { 5150 if(useCustom) { 5151 if(!active) return; 5152 if(balloon) { 5153 hideBalloon(); 5154 } 5155 // I know there are two specs for this, but one is never 5156 // implemented by any window manager I have ever seen, and 5157 // the other is a bloated mess and too complicated for simpledisplay... 5158 // so doing my own little window instead. 5159 balloon = new SimpleWindow(380, 120, null, OpenGlOptions.no, Resizability.fixedSize, WindowTypes.notification, WindowFlags.dontAutoShow/*, window*/); 5160 5161 int x, y, width, height; 5162 getWindowRect(x, y, width, height); 5163 5164 int bx = x - balloon.width; 5165 int by = y - balloon.height; 5166 if(bx < 0) 5167 bx = x + width + balloon.width; 5168 if(by < 0) 5169 by = y + height; 5170 5171 // just in case, make sure it is actually on scren 5172 if(bx < 0) 5173 bx = 0; 5174 if(by < 0) 5175 by = 0; 5176 5177 balloon.move(bx, by); 5178 auto painter = balloon.draw(); 5179 painter.fillColor = Color(220, 220, 220); 5180 painter.outlineColor = Color.black; 5181 painter.drawRectangle(Point(0, 0), balloon.width, balloon.height); 5182 auto iconWidth = icon is null ? 0 : icon.width; 5183 if(icon) 5184 painter.drawImage(Point(4, 4), Image.fromMemoryImage(icon)); 5185 iconWidth += 6; // margin around the icon 5186 5187 // draw a close button 5188 painter.outlineColor = Color(44, 44, 44); 5189 painter.fillColor = Color(255, 255, 255); 5190 painter.drawRectangle(Point(balloon.width - 15, 3), 13, 13); 5191 painter.pen = Pen(Color.black, 3); 5192 painter.drawLine(Point(balloon.width - 14, 4), Point(balloon.width - 4, 14)); 5193 painter.drawLine(Point(balloon.width - 4, 4), Point(balloon.width - 14, 13)); 5194 painter.pen = Pen(Color.black, 1); 5195 painter.fillColor = Color(220, 220, 220); 5196 5197 // Draw the title and message 5198 painter.drawText(Point(4 + iconWidth, 4), title); 5199 painter.drawLine( 5200 Point(4 + iconWidth, 4 + painter.fontHeight + 1), 5201 Point(balloon.width - 4, 4 + painter.fontHeight + 1), 5202 ); 5203 painter.drawText(Point(4 + iconWidth, 4 + painter.fontHeight + 4), message); 5204 5205 balloon.setEventHandlers( 5206 (MouseEvent ev) { 5207 if(ev.type == MouseEventType.buttonPressed) { 5208 if(ev.x > balloon.width - 16 && ev.y < 16) 5209 hideBalloon(); 5210 else if(onclick) 5211 onclick(); 5212 } 5213 } 5214 ); 5215 balloon.show(); 5216 5217 version(with_timer) 5218 timer = new Timer(timeout, &hideBalloon); 5219 else {} // FIXME 5220 } 5221 } else version(Windows) { 5222 enum NIF_INFO = 0x00000010; 5223 5224 data.uFlags = NIF_INFO; 5225 5226 // FIXME: go back to the last valid unicode code point 5227 if(title.length > 40) 5228 title = title[0 .. 40]; 5229 if(message.length > 220) 5230 message = message[0 .. 220]; 5231 5232 enum NIIF_RESPECT_QUIET_TIME = 0x00000080; 5233 enum NIIF_LARGE_ICON = 0x00000020; 5234 enum NIIF_NOSOUND = 0x00000010; 5235 enum NIIF_USER = 0x00000004; 5236 enum NIIF_ERROR = 0x00000003; 5237 enum NIIF_WARNING = 0x00000002; 5238 enum NIIF_INFO = 0x00000001; 5239 enum NIIF_NONE = 0; 5240 5241 WCharzBuffer t = WCharzBuffer(title); 5242 WCharzBuffer m = WCharzBuffer(message); 5243 5244 t.copyInto(data.szInfoTitle); 5245 m.copyInto(data.szInfo); 5246 data.dwInfoFlags = NIIF_RESPECT_QUIET_TIME; 5247 5248 if(icon !is null) { 5249 auto i = new WindowsIcon(icon); 5250 data.hBalloonIcon = i.hIcon; 5251 data.dwInfoFlags |= NIIF_USER; 5252 } 5253 5254 Shell_NotifyIcon(NIM_MODIFY, cast(NOTIFYICONDATA*) &data); 5255 } else version(OSXCocoa) { 5256 throw new NotYetImplementedException(); 5257 } else static assert(0); 5258 } 5259 5260 /// 5261 //version(Windows) 5262 void show() { 5263 version(X11) { 5264 if(!hidden) 5265 return; 5266 sendTrayMessage(SYSTEM_TRAY_REQUEST_DOCK, nativeHandle, 0, 0); 5267 hidden = false; 5268 } else version(Windows) { 5269 data.uFlags = NIF_STATE; 5270 data.dwState = 0; // NIS_HIDDEN; // windows vista 5271 data.dwStateMask = NIS_HIDDEN; // windows vista 5272 Shell_NotifyIcon(NIM_MODIFY, cast(NOTIFYICONDATA*) &data); 5273 } else version(OSXCocoa) { 5274 throw new NotYetImplementedException(); 5275 } else static assert(0); 5276 } 5277 5278 version(X11) 5279 bool hidden = false; 5280 5281 /// 5282 //version(Windows) 5283 void hide() { 5284 version(X11) { 5285 if(hidden) 5286 return; 5287 hidden = true; 5288 XUnmapWindow(XDisplayConnection.get, nativeHandle); 5289 } else version(Windows) { 5290 data.uFlags = NIF_STATE; 5291 data.dwState = NIS_HIDDEN; // windows vista 5292 data.dwStateMask = NIS_HIDDEN; // windows vista 5293 Shell_NotifyIcon(NIM_MODIFY, cast(NOTIFYICONDATA*) &data); 5294 } else version(OSXCocoa) { 5295 throw new NotYetImplementedException(); 5296 } else static assert(0); 5297 } 5298 5299 /// 5300 void close () { 5301 version(X11) { 5302 if (active) { 5303 active = false; // event handler will set this too, but meh 5304 XUnmapWindow(XDisplayConnection.get, nativeHandle); // 'cause why not; let's be polite 5305 XDestroyWindow(XDisplayConnection.get, nativeHandle); 5306 flushGui(); 5307 } 5308 } else version(Windows) { 5309 Shell_NotifyIcon(NIM_DELETE, cast(NOTIFYICONDATA*) &data); 5310 } else version(OSXCocoa) { 5311 throw new NotYetImplementedException(); 5312 } else static assert(0); 5313 } 5314 5315 ~this() { 5316 if(!thisIsGuiThread) return; // FIXME: leaks if multithreaded gc 5317 version(X11) 5318 if(clippixmap != None) 5319 XFreePixmap(XDisplayConnection.get, clippixmap); 5320 close(); 5321 } 5322 } 5323 5324 version(X11) 5325 /// Call `XFreePixmap` on the return value. 5326 Pixmap transparencyMaskFromMemoryImage(MemoryImage i, Window window) { 5327 char[] data = new char[](i.width * i.height / 8 + 2); 5328 data[] = 0; 5329 5330 int bitOffset = 0; 5331 foreach(c; i.getAsTrueColorImage().imageData.colors) { // FIXME inefficient unnecessary conversion in palette cases 5332 ubyte v = c.a > 128 ? 1 : 0; 5333 data[bitOffset / 8] |= v << (bitOffset%8); 5334 bitOffset++; 5335 } 5336 auto handle = XCreateBitmapFromData(XDisplayConnection.get, cast(Drawable) window, data.ptr, i.width, i.height); 5337 return handle; 5338 } 5339 5340 5341 // basic functions to make timers 5342 /** 5343 A timer that will trigger your function on a given interval. 5344 5345 5346 You create a timer with an interval and a callback. It will continue 5347 to fire on the interval until it is destroyed. 5348 5349 There are currently no one-off timers (instead, just create one and 5350 destroy it when it is triggered) nor are there pause/resume functions - 5351 the timer must again be destroyed and recreated if you want to pause it. 5352 5353 auto timer = new Timer(50, { it happened!; }); 5354 timer.destroy(); 5355 5356 Timers can only be expected to fire when the event loop is running and only 5357 once per iteration through the event loop. 5358 5359 History: 5360 Prior to December 9, 2020, a timer pulse set too high with a handler too 5361 slow could lock up the event loop. It now guarantees other things will 5362 get a chance to run between timer calls, even if that means not keeping up 5363 with the requested interval. 5364 */ 5365 version(with_timer) { 5366 class Timer { 5367 // FIXME: needs pause and unpause 5368 // FIXME: I might add overloads for ones that take a count of 5369 // how many elapsed since last time (on Windows, it will divide 5370 // the ticks thing given, on Linux it is just available) and 5371 // maybe one that takes an instance of the Timer itself too 5372 /// Create a timer with a callback when it triggers. 5373 this(int intervalInMilliseconds, void delegate() onPulse) { 5374 assert(onPulse !is null); 5375 5376 this.intervalInMilliseconds = intervalInMilliseconds; 5377 this.onPulse = onPulse; 5378 5379 version(Windows) { 5380 /* 5381 handle = SetTimer(null, handle, intervalInMilliseconds, &timerCallback); 5382 if(handle == 0) 5383 throw new Exception("SetTimer fail"); 5384 */ 5385 5386 // thanks to Archival 998 for the WaitableTimer blocks 5387 handle = CreateWaitableTimer(null, false, null); 5388 long initialTime = -intervalInMilliseconds; 5389 if(handle is null || !SetWaitableTimer(handle, cast(LARGE_INTEGER*)&initialTime, intervalInMilliseconds, &timerCallback, handle, false)) 5390 throw new Exception("SetWaitableTimer Failed"); 5391 5392 mapping[handle] = this; 5393 5394 } else version(linux) { 5395 static import ep = core.sys.linux.epoll; 5396 5397 import core.sys.linux.timerfd; 5398 5399 fd = timerfd_create(CLOCK_MONOTONIC, 0); 5400 if(fd == -1) 5401 throw new Exception("timer create failed"); 5402 5403 mapping[fd] = this; 5404 5405 itimerspec value; 5406 value.it_value.tv_sec = cast(int) (intervalInMilliseconds / 1000); 5407 value.it_value.tv_nsec = (intervalInMilliseconds % 1000) * 1000_000; 5408 5409 value.it_interval.tv_sec = cast(int) (intervalInMilliseconds / 1000); 5410 value.it_interval.tv_nsec = (intervalInMilliseconds % 1000) * 1000_000; 5411 5412 if(timerfd_settime(fd, 0, &value, null) == -1) 5413 throw new Exception("couldn't make pulse timer"); 5414 5415 version(with_eventloop) { 5416 import arsd.eventloop; 5417 addFileEventListeners(fd, &trigger, null, null); 5418 } else { 5419 prepareEventLoop(); 5420 5421 ep.epoll_event ev = void; 5422 ev.events = ep.EPOLLIN; 5423 ev.data.fd = fd; 5424 ep.epoll_ctl(epollFd, ep.EPOLL_CTL_ADD, fd, &ev); 5425 } 5426 } else featureNotImplemented(); 5427 } 5428 5429 private int intervalInMilliseconds; 5430 5431 // just cuz I sometimes call it this. 5432 alias dispose = destroy; 5433 5434 /// Stop and destroy the timer object. 5435 void destroy() { 5436 version(Windows) { 5437 staticDestroy(handle); 5438 handle = null; 5439 } else version(linux) { 5440 staticDestroy(fd); 5441 fd = -1; 5442 } else featureNotImplemented(); 5443 } 5444 5445 version(Windows) 5446 static void staticDestroy(HANDLE handle) { 5447 if(handle) { 5448 // KillTimer(null, handle); 5449 CancelWaitableTimer(cast(void*)handle); 5450 mapping.remove(handle); 5451 CloseHandle(handle); 5452 } 5453 } 5454 else version(linux) 5455 static void staticDestroy(int fd) { 5456 if(fd != -1) { 5457 import unix = core.sys.posix.unistd; 5458 static import ep = core.sys.linux.epoll; 5459 5460 version(with_eventloop) { 5461 import arsd.eventloop; 5462 removeFileEventListeners(fd); 5463 } else { 5464 ep.epoll_event ev = void; 5465 ev.events = ep.EPOLLIN; 5466 ev.data.fd = fd; 5467 5468 ep.epoll_ctl(epollFd, ep.EPOLL_CTL_DEL, fd, &ev); 5469 } 5470 unix.close(fd); 5471 mapping.remove(fd); 5472 } 5473 } 5474 5475 ~this() { 5476 version(Windows) { if(handle) 5477 cleanupQueue.queue!staticDestroy(handle); 5478 } else version(linux) { if(fd != -1) 5479 cleanupQueue.queue!staticDestroy(fd); 5480 } 5481 } 5482 5483 5484 void changeTime(int intervalInMilliseconds) 5485 { 5486 this.intervalInMilliseconds = intervalInMilliseconds; 5487 version(Windows) 5488 { 5489 if(handle) 5490 { 5491 //handle = SetTimer(null, handle, intervalInMilliseconds, &timerCallback); 5492 long initialTime = -intervalInMilliseconds; 5493 if(handle is null || !SetWaitableTimer(handle, cast(LARGE_INTEGER*)&initialTime, intervalInMilliseconds, &timerCallback, handle, false)) 5494 throw new Exception("couldn't change pulse timer"); 5495 } 5496 } 5497 } 5498 5499 5500 private: 5501 5502 void delegate() onPulse; 5503 5504 int lastEventLoopRoundTriggered; 5505 5506 void trigger() { 5507 version(linux) { 5508 import unix = core.sys.posix.unistd; 5509 long val; 5510 unix.read(fd, &val, val.sizeof); // gotta clear the pipe 5511 } else version(Windows) { 5512 if(this.lastEventLoopRoundTriggered == eventLoopRound) 5513 return; // never try to actually run faster than the event loop 5514 lastEventLoopRoundTriggered = eventLoopRound; 5515 } else featureNotImplemented(); 5516 5517 onPulse(); 5518 } 5519 5520 version(Windows) 5521 void rearm() { 5522 5523 } 5524 5525 version(Windows) 5526 extern(Windows) 5527 //static void timerCallback(HWND, UINT, UINT_PTR timer, DWORD dwTime) nothrow { 5528 static void timerCallback(HANDLE timer, DWORD lowTime, DWORD hiTime) nothrow { 5529 if(Timer* t = timer in mapping) { 5530 try 5531 (*t).trigger(); 5532 catch(Exception e) { sdpy_abort(e); assert(0); } 5533 } 5534 } 5535 5536 version(Windows) { 5537 //UINT_PTR handle; 5538 //static Timer[UINT_PTR] mapping; 5539 HANDLE handle; 5540 __gshared Timer[HANDLE] mapping; 5541 } else version(linux) { 5542 int fd = -1; 5543 __gshared Timer[int] mapping; 5544 } else static assert(0, "timer not supported"); 5545 } 5546 } 5547 5548 version(Windows) 5549 private int eventLoopRound; 5550 5551 version(Windows) 5552 /// 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 5553 class WindowsHandleReader { 5554 /// 5555 this(void delegate() onReady, HANDLE handle) { 5556 this.onReady = onReady; 5557 this.handle = handle; 5558 5559 mapping[handle] = this; 5560 5561 enable(); 5562 } 5563 5564 /// 5565 void enable() { 5566 auto el = EventLoop.get().impl; 5567 el.handles ~= handle; 5568 } 5569 5570 /// 5571 void disable() { 5572 auto el = EventLoop.get().impl; 5573 for(int i = 0; i < el.handles.length; i++) { 5574 if(el.handles[i] is handle) { 5575 el.handles[i] = el.handles[$-1]; 5576 el.handles = el.handles[0 .. $-1]; 5577 return; 5578 } 5579 } 5580 } 5581 5582 void dispose() { 5583 disable(); 5584 if(handle) 5585 mapping.remove(handle); 5586 handle = null; 5587 } 5588 5589 void ready() { 5590 if(onReady) 5591 onReady(); 5592 } 5593 5594 HANDLE handle; 5595 void delegate() onReady; 5596 5597 __gshared WindowsHandleReader[HANDLE] mapping; 5598 } 5599 5600 version(Posix) 5601 /// Lets you add files to the event loop for reading. Use at your own risk. 5602 class PosixFdReader { 5603 /// 5604 this(void delegate() onReady, int fd, bool captureReads = true, bool captureWrites = false) { 5605 this((int, bool, bool) { onReady(); }, fd, captureReads, captureWrites); 5606 } 5607 5608 /// 5609 this(void delegate(int) onReady, int fd, bool captureReads = true, bool captureWrites = false) { 5610 this((int fd, bool, bool) { onReady(fd); }, fd, captureReads, captureWrites); 5611 } 5612 5613 /// 5614 this(void delegate(int fd, bool read, bool write) onReady, int fd, bool captureReads = true, bool captureWrites = false) { 5615 this.onReady = onReady; 5616 this.fd = fd; 5617 this.captureWrites = captureWrites; 5618 this.captureReads = captureReads; 5619 5620 mapping[fd] = this; 5621 5622 version(with_eventloop) { 5623 import arsd.eventloop; 5624 addFileEventListeners(fd, &readyel); 5625 } else { 5626 enable(); 5627 } 5628 } 5629 5630 bool captureReads; 5631 bool captureWrites; 5632 5633 version(with_eventloop) {} else 5634 /// 5635 void enable() { 5636 prepareEventLoop(); 5637 5638 enabled = true; 5639 5640 version(linux) { 5641 static import ep = core.sys.linux.epoll; 5642 ep.epoll_event ev = void; 5643 ev.events = (captureReads ? ep.EPOLLIN : 0) | (captureWrites ? ep.EPOLLOUT : 0); 5644 //import std.stdio; writeln("enable ", fd, " ", captureReads, " ", captureWrites); 5645 ev.data.fd = fd; 5646 ep.epoll_ctl(epollFd, ep.EPOLL_CTL_ADD, fd, &ev); 5647 } else { 5648 5649 } 5650 } 5651 5652 version(with_eventloop) {} else 5653 /// 5654 void disable() { 5655 prepareEventLoop(); 5656 5657 enabled = false; 5658 5659 version(linux) { 5660 static import ep = core.sys.linux.epoll; 5661 ep.epoll_event ev = void; 5662 ev.events = (captureReads ? ep.EPOLLIN : 0) | (captureWrites ? ep.EPOLLOUT : 0); 5663 //import std.stdio; writeln("disable ", fd, " ", captureReads, " ", captureWrites); 5664 ev.data.fd = fd; 5665 ep.epoll_ctl(epollFd, ep.EPOLL_CTL_DEL, fd, &ev); 5666 } 5667 } 5668 5669 version(with_eventloop) {} else 5670 /// 5671 void dispose() { 5672 if(enabled) 5673 disable(); 5674 if(fd != -1) 5675 mapping.remove(fd); 5676 fd = -1; 5677 } 5678 5679 void delegate(int, bool, bool) onReady; 5680 5681 version(with_eventloop) 5682 void readyel() { 5683 onReady(fd, true, true); 5684 } 5685 5686 void ready(uint flags) { 5687 version(linux) { 5688 static import ep = core.sys.linux.epoll; 5689 onReady(fd, (flags & ep.EPOLLIN) ? true : false, (flags & ep.EPOLLOUT) ? true : false); 5690 } else { 5691 import core.sys.posix.poll; 5692 onReady(fd, (flags & POLLIN) ? true : false, (flags & POLLOUT) ? true : false); 5693 } 5694 } 5695 5696 void hup(uint flags) { 5697 if(onHup) 5698 onHup(); 5699 } 5700 5701 void delegate() onHup; 5702 5703 int fd = -1; 5704 private bool enabled; 5705 __gshared PosixFdReader[int] mapping; 5706 } 5707 5708 // basic functions to access the clipboard 5709 /+ 5710 5711 5712 http://msdn.microsoft.com/en-us/library/windows/desktop/ff729168%28v=vs.85%29.aspx 5713 http://msdn.microsoft.com/en-us/library/windows/desktop/ms649039%28v=vs.85%29.aspx 5714 http://msdn.microsoft.com/en-us/library/windows/desktop/ms649035%28v=vs.85%29.aspx 5715 http://msdn.microsoft.com/en-us/library/windows/desktop/ms649051%28v=vs.85%29.aspx 5716 http://msdn.microsoft.com/en-us/library/windows/desktop/ms649037%28v=vs.85%29.aspx 5717 http://msdn.microsoft.com/en-us/library/windows/desktop/ms649035%28v=vs.85%29.aspx 5718 http://msdn.microsoft.com/en-us/library/windows/desktop/ms649016%28v=vs.85%29.aspx 5719 5720 +/ 5721 5722 /++ 5723 this does a delegate because it is actually an async call on X... 5724 the receiver may never be called if the clipboard is empty or unavailable 5725 gets plain text from the clipboard. 5726 +/ 5727 void getClipboardText(SimpleWindow clipboardOwner, void delegate(in char[]) receiver) { 5728 version(Windows) { 5729 HWND hwndOwner = clipboardOwner ? clipboardOwner.impl.hwnd : null; 5730 if(OpenClipboard(hwndOwner) == 0) 5731 throw new Exception("OpenClipboard"); 5732 scope(exit) 5733 CloseClipboard(); 5734 // see: https://docs.microsoft.com/en-us/windows/win32/api/winuser/nf-winuser-getpriorityclipboardformat 5735 if(auto dataHandle = GetClipboardData(CF_UNICODETEXT)) { 5736 5737 if(auto data = cast(wchar*) GlobalLock(dataHandle)) { 5738 scope(exit) 5739 GlobalUnlock(dataHandle); 5740 5741 // FIXME: CR/LF conversions 5742 // FIXME: I might not have to copy it now that the receiver is in char[] instead of string 5743 int len = 0; 5744 auto d = data; 5745 while(*d) { 5746 d++; 5747 len++; 5748 } 5749 string s; 5750 s.reserve(len); 5751 foreach(dchar ch; data[0 .. len]) { 5752 s ~= ch; 5753 } 5754 receiver(s); 5755 } 5756 } 5757 } else version(X11) { 5758 getX11Selection!"CLIPBOARD"(clipboardOwner, receiver); 5759 } else version(OSXCocoa) { 5760 throw new NotYetImplementedException(); 5761 } else static assert(0); 5762 } 5763 5764 // FIXME: a clipboard listener might be cool btw 5765 5766 /++ 5767 this does a delegate because it is actually an async call on X... 5768 the receiver may never be called if the clipboard is empty or unavailable 5769 gets image from the clipboard. 5770 5771 templated because it introduces an optional dependency on arsd.bmp 5772 +/ 5773 void getClipboardImage()(SimpleWindow clipboardOwner, void delegate(MemoryImage) receiver) { 5774 version(Windows) { 5775 HWND hwndOwner = clipboardOwner ? clipboardOwner.impl.hwnd : null; 5776 if(OpenClipboard(hwndOwner) == 0) 5777 throw new Exception("OpenClipboard"); 5778 scope(exit) 5779 CloseClipboard(); 5780 if(auto dataHandle = GetClipboardData(CF_DIBV5)) { 5781 if(auto data = cast(ubyte*) GlobalLock(dataHandle)) { 5782 scope(exit) 5783 GlobalUnlock(dataHandle); 5784 5785 auto len = GlobalSize(dataHandle); 5786 5787 import arsd.bmp; 5788 auto img = readBmp(data[0 .. len], false); 5789 receiver(img); 5790 } 5791 } 5792 } else version(X11) { 5793 getX11Selection!"CLIPBOARD"(clipboardOwner, receiver); 5794 } else version(OSXCocoa) { 5795 throw new NotYetImplementedException(); 5796 } else static assert(0); 5797 } 5798 5799 version(Windows) 5800 struct WCharzBuffer { 5801 wchar[] buffer; 5802 wchar[256] staticBuffer = void; 5803 5804 size_t length() { 5805 return buffer.length; 5806 } 5807 5808 wchar* ptr() { 5809 return buffer.ptr; 5810 } 5811 5812 wchar[] slice() { 5813 return buffer; 5814 } 5815 5816 void copyInto(R)(ref R r) { 5817 static if(is(R == wchar[N], size_t N)) { 5818 r[0 .. this.length] = slice[]; 5819 r[this.length] = 0; 5820 } else static assert(0, "can only copy into wchar[n], not " ~ R.stringof); 5821 } 5822 5823 /++ 5824 conversionFlags = [WindowsStringConversionFlags] 5825 +/ 5826 this(in char[] data, int conversionFlags = 0) { 5827 conversionFlags |= WindowsStringConversionFlags.zeroTerminate; // this ALWAYS zero terminates cuz of its name 5828 auto sz = sizeOfConvertedWstring(data, conversionFlags); 5829 if(sz > staticBuffer.length) 5830 buffer = new wchar[](sz); 5831 else 5832 buffer = staticBuffer[]; 5833 5834 buffer = makeWindowsString(data, buffer, conversionFlags); 5835 } 5836 } 5837 5838 version(Windows) 5839 int sizeOfConvertedWstring(in char[] s, int conversionFlags) { 5840 int size = 0; 5841 5842 if(conversionFlags & WindowsStringConversionFlags.convertNewLines) { 5843 // need to convert line endings, which means the length will get bigger. 5844 5845 // BTW I betcha this could be faster with some simd stuff. 5846 char last; 5847 foreach(char ch; s) { 5848 if(ch == 10 && last != 13) 5849 size++; // will add a 13 before it... 5850 size++; 5851 last = ch; 5852 } 5853 } else { 5854 // no conversion necessary, just estimate based on length 5855 /* 5856 I don't think there's any string with a longer length 5857 in code units when encoded in UTF-16 than it has in UTF-8. 5858 This will probably over allocate, but that's OK. 5859 */ 5860 size = cast(int) s.length; 5861 } 5862 5863 if(conversionFlags & WindowsStringConversionFlags.zeroTerminate) 5864 size++; 5865 5866 return size; 5867 } 5868 5869 version(Windows) 5870 enum WindowsStringConversionFlags : int { 5871 zeroTerminate = 1, 5872 convertNewLines = 2, 5873 } 5874 5875 version(Windows) 5876 class WindowsApiException : Exception { 5877 char[256] buffer; 5878 this(string msg, string file = __FILE__, size_t line = __LINE__, Throwable next = null) { 5879 assert(msg.length < 100); 5880 5881 auto error = GetLastError(); 5882 buffer[0 .. msg.length] = msg; 5883 buffer[msg.length] = ' '; 5884 5885 int pos = cast(int) msg.length + 1; 5886 5887 if(error == 0) 5888 buffer[pos++] = '0'; 5889 else { 5890 5891 auto ec = error; 5892 auto init = pos; 5893 while(ec) { 5894 buffer[pos++] = (ec % 10) + '0'; 5895 ec /= 10; 5896 } 5897 5898 buffer[pos++] = ' '; 5899 5900 size_t size = FormatMessageA(FORMAT_MESSAGE_FROM_SYSTEM | FORMAT_MESSAGE_IGNORE_INSERTS, null, error, MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT), &(buffer[pos]), cast(DWORD) buffer.length - pos, null); 5901 5902 pos += size; 5903 } 5904 5905 5906 super(cast(string) buffer[0 .. pos], file, line, next); 5907 } 5908 } 5909 5910 class ErrnoApiException : Exception { 5911 char[256] buffer; 5912 this(string msg, string file = __FILE__, size_t line = __LINE__, Throwable next = null) { 5913 assert(msg.length < 100); 5914 5915 import core.stdc.errno; 5916 auto error = errno; 5917 buffer[0 .. msg.length] = msg; 5918 buffer[msg.length] = ' '; 5919 5920 int pos = cast(int) msg.length + 1; 5921 5922 if(error == 0) 5923 buffer[pos++] = '0'; 5924 else { 5925 auto init = pos; 5926 while(error) { 5927 buffer[pos++] = (error % 10) + '0'; 5928 error /= 10; 5929 } 5930 for(int i = 0; i < (pos - init) / 2; i++) { 5931 char c = buffer[i + init]; 5932 buffer[i + init] = buffer[pos - (i + init) - 1]; 5933 buffer[pos - (i + init) - 1] = c; 5934 } 5935 } 5936 5937 5938 super(cast(string) buffer[0 .. pos], file, line, next); 5939 } 5940 5941 } 5942 5943 version(Windows) 5944 wchar[] makeWindowsString(in char[] str, wchar[] buffer, int conversionFlags = WindowsStringConversionFlags.zeroTerminate) { 5945 if(str.length == 0) 5946 return null; 5947 5948 int pos = 0; 5949 dchar last; 5950 foreach(dchar c; str) { 5951 if(c <= 0xFFFF) { 5952 if((conversionFlags & WindowsStringConversionFlags.convertNewLines) && c == 10 && last != 13) 5953 buffer[pos++] = 13; 5954 buffer[pos++] = cast(wchar) c; 5955 } else if(c <= 0x10FFFF) { 5956 buffer[pos++] = cast(wchar)((((c - 0x10000) >> 10) & 0x3FF) + 0xD800); 5957 buffer[pos++] = cast(wchar)(((c - 0x10000) & 0x3FF) + 0xDC00); 5958 } 5959 5960 last = c; 5961 } 5962 5963 if(conversionFlags & WindowsStringConversionFlags.zeroTerminate) { 5964 buffer[pos] = 0; 5965 } 5966 5967 return buffer[0 .. pos]; 5968 } 5969 5970 version(Windows) 5971 char[] makeUtf8StringFromWindowsString(in wchar[] str, char[] buffer) { 5972 if(str.length == 0) 5973 return null; 5974 5975 auto got = WideCharToMultiByte(CP_UTF8, 0, str.ptr, cast(int) str.length, buffer.ptr, cast(int) buffer.length, null, null); 5976 if(got == 0) { 5977 if(GetLastError() == ERROR_INSUFFICIENT_BUFFER) 5978 throw new Exception("not enough buffer"); 5979 else 5980 throw new Exception("conversion"); // FIXME: GetLastError 5981 } 5982 return buffer[0 .. got]; 5983 } 5984 5985 version(Windows) 5986 string makeUtf8StringFromWindowsString(in wchar[] str) { 5987 char[] buffer; 5988 auto got = WideCharToMultiByte(CP_UTF8, 0, str.ptr, cast(int) str.length, null, 0, null, null); 5989 buffer.length = got; 5990 5991 // it is unique because we just allocated it above! 5992 return cast(string) makeUtf8StringFromWindowsString(str, buffer); 5993 } 5994 5995 version(Windows) 5996 string makeUtf8StringFromWindowsString(wchar* str) { 5997 char[] buffer; 5998 auto got = WideCharToMultiByte(CP_UTF8, 0, str, -1, null, 0, null, null); 5999 buffer.length = got; 6000 6001 got = WideCharToMultiByte(CP_UTF8, 0, str, -1, buffer.ptr, cast(int) buffer.length, null, null); 6002 if(got == 0) { 6003 if(GetLastError() == ERROR_INSUFFICIENT_BUFFER) 6004 throw new Exception("not enough buffer"); 6005 else 6006 throw new Exception("conversion"); // FIXME: GetLastError 6007 } 6008 return cast(string) buffer[0 .. got]; 6009 } 6010 6011 int findIndexOfZero(in wchar[] str) { 6012 foreach(idx, wchar ch; str) 6013 if(ch == 0) 6014 return cast(int) idx; 6015 return cast(int) str.length; 6016 } 6017 int findIndexOfZero(in char[] str) { 6018 foreach(idx, char ch; str) 6019 if(ch == 0) 6020 return cast(int) idx; 6021 return cast(int) str.length; 6022 } 6023 6024 /// Copies some text to the clipboard. 6025 void setClipboardText(SimpleWindow clipboardOwner, string text) { 6026 assert(clipboardOwner !is null); 6027 version(Windows) { 6028 if(OpenClipboard(clipboardOwner.impl.hwnd) == 0) 6029 throw new Exception("OpenClipboard"); 6030 scope(exit) 6031 CloseClipboard(); 6032 EmptyClipboard(); 6033 auto sz = sizeOfConvertedWstring(text, WindowsStringConversionFlags.convertNewLines | WindowsStringConversionFlags.zeroTerminate); 6034 auto handle = GlobalAlloc(GMEM_MOVEABLE, sz * 2); // zero terminated wchars 6035 if(handle is null) throw new Exception("GlobalAlloc"); 6036 if(auto data = cast(wchar*) GlobalLock(handle)) { 6037 auto slice = data[0 .. sz]; 6038 scope(failure) 6039 GlobalUnlock(handle); 6040 6041 auto str = makeWindowsString(text, slice, WindowsStringConversionFlags.convertNewLines | WindowsStringConversionFlags.zeroTerminate); 6042 6043 GlobalUnlock(handle); 6044 SetClipboardData(CF_UNICODETEXT, handle); 6045 } 6046 } else version(X11) { 6047 setX11Selection!"CLIPBOARD"(clipboardOwner, text); 6048 } else version(OSXCocoa) { 6049 throw new NotYetImplementedException(); 6050 } else static assert(0); 6051 } 6052 6053 void setClipboardImage()(SimpleWindow clipboardOwner, MemoryImage img) { 6054 assert(clipboardOwner !is null); 6055 version(Windows) { 6056 if(OpenClipboard(clipboardOwner.impl.hwnd) == 0) 6057 throw new Exception("OpenClipboard"); 6058 scope(exit) 6059 CloseClipboard(); 6060 EmptyClipboard(); 6061 6062 6063 import arsd.bmp; 6064 ubyte[] mdata; 6065 mdata.reserve(img.width * img.height); 6066 void sink(ubyte b) { 6067 mdata ~= b; 6068 } 6069 writeBmpIndirect(img, &sink, false); 6070 6071 auto handle = GlobalAlloc(GMEM_MOVEABLE, mdata.length); 6072 if(handle is null) throw new Exception("GlobalAlloc"); 6073 if(auto data = cast(ubyte*) GlobalLock(handle)) { 6074 auto slice = data[0 .. mdata.length]; 6075 scope(failure) 6076 GlobalUnlock(handle); 6077 6078 slice[] = mdata[]; 6079 6080 GlobalUnlock(handle); 6081 SetClipboardData(CF_DIB, handle); 6082 } 6083 } else version(X11) { 6084 static class X11SetSelectionHandler_Image : X11SetSelectionHandler { 6085 mixin X11SetSelectionHandler_Basics; 6086 private const(ubyte)[] mdata; 6087 private const(ubyte)[] mdata_original; 6088 this(MemoryImage img) { 6089 import arsd.bmp; 6090 6091 mdata.reserve(img.width * img.height); 6092 void sink(ubyte b) { 6093 mdata ~= b; 6094 } 6095 writeBmpIndirect(img, &sink, true); 6096 6097 mdata_original = mdata; 6098 } 6099 6100 Atom[] availableFormats() { 6101 auto display = XDisplayConnection.get; 6102 return [ 6103 GetAtom!"image/bmp"(display), 6104 GetAtom!"TARGETS"(display) 6105 ]; 6106 } 6107 6108 ubyte[] getData(Atom format, return scope ubyte[] data) { 6109 if(mdata.length < data.length) { 6110 data[0 .. mdata.length] = mdata[]; 6111 auto ret = data[0 .. mdata.length]; 6112 mdata = mdata[$..$]; 6113 return ret; 6114 } else { 6115 data[] = mdata[0 .. data.length]; 6116 mdata = mdata[data.length .. $]; 6117 return data[]; 6118 } 6119 } 6120 6121 void done() { 6122 mdata = mdata_original; 6123 } 6124 } 6125 6126 setX11Selection!"CLIPBOARD"(clipboardOwner, new X11SetSelectionHandler_Image(img)); 6127 } else version(OSXCocoa) { 6128 throw new NotYetImplementedException(); 6129 } else static assert(0); 6130 } 6131 6132 6133 version(X11) { 6134 // and the PRIMARY on X, be sure to put these in static if(UsingSimpledisplayX11) 6135 6136 private Atom*[] interredAtoms; // for discardAndRecreate 6137 6138 // FIXME: do a GetAtomUpfront too that just queues all at CT and combines it all. 6139 /// Platform-specific for X11. 6140 /// History: On February 21, 2021, I changed the default value of `create` to be true. 6141 @property Atom GetAtom(string name, bool create = true)(Display* display) { 6142 static Atom a; 6143 if(!a) { 6144 a = XInternAtom(display, name, !create); 6145 interredAtoms ~= &a; 6146 } 6147 if(a == None) 6148 throw new Exception("XInternAtom " ~ name ~ " " ~ (create ? "true":"false")); 6149 return a; 6150 } 6151 6152 /// Platform-specific for X11 - gets atom names as a string. 6153 string getAtomName(Atom atom, Display* display) { 6154 auto got = XGetAtomName(display, atom); 6155 scope(exit) XFree(got); 6156 import core.stdc.string; 6157 string s = got[0 .. strlen(got)].idup; 6158 return s; 6159 } 6160 6161 /// Asserts ownership of PRIMARY and copies the text into a buffer that clients can request later. 6162 void setPrimarySelection(SimpleWindow window, string text) { 6163 setX11Selection!"PRIMARY"(window, text); 6164 } 6165 6166 /// Asserts ownership of SECONDARY and copies the text into a buffer that clients can request later. 6167 void setSecondarySelection(SimpleWindow window, string text) { 6168 setX11Selection!"SECONDARY"(window, text); 6169 } 6170 6171 interface X11SetSelectionHandler { 6172 // should include TARGETS right now 6173 Atom[] availableFormats(); 6174 // Return the slice of data you filled, empty slice if done. 6175 // this is to support the incremental thing 6176 ubyte[] getData(Atom format, return scope ubyte[] data); 6177 6178 void done(); 6179 6180 void handleRequest(XEvent); 6181 6182 bool matchesIncr(Window, Atom); 6183 void sendMoreIncr(XPropertyEvent*); 6184 } 6185 6186 mixin template X11SetSelectionHandler_Basics() { 6187 Window incrWindow; 6188 Atom incrAtom; 6189 Atom selectionAtom; 6190 Atom formatAtom; 6191 ubyte[] toSend; 6192 bool matchesIncr(Window w, Atom a) { 6193 return incrAtom && incrAtom == a && w == incrWindow; 6194 } 6195 void sendMoreIncr(XPropertyEvent* event) { 6196 auto display = XDisplayConnection.get; 6197 6198 XChangeProperty (display, 6199 incrWindow, 6200 incrAtom, 6201 formatAtom, 6202 8 /* bits */, PropModeReplace, 6203 toSend.ptr, cast(int) toSend.length); 6204 6205 if(toSend.length != 0) { 6206 toSend = this.getData(formatAtom, toSend[]); 6207 } else { 6208 this.done(); 6209 incrWindow = None; 6210 incrAtom = None; 6211 selectionAtom = None; 6212 formatAtom = None; 6213 toSend = null; 6214 } 6215 } 6216 void handleRequest(XEvent ev) { 6217 6218 auto display = XDisplayConnection.get; 6219 6220 XSelectionRequestEvent* event = &ev.xselectionrequest; 6221 XSelectionEvent selectionEvent; 6222 selectionEvent.type = EventType.SelectionNotify; 6223 selectionEvent.display = event.display; 6224 selectionEvent.requestor = event.requestor; 6225 selectionEvent.selection = event.selection; 6226 selectionEvent.time = event.time; 6227 selectionEvent.target = event.target; 6228 6229 bool supportedType() { 6230 foreach(t; this.availableFormats()) 6231 if(t == event.target) 6232 return true; 6233 return false; 6234 } 6235 6236 if(event.property == None) { 6237 selectionEvent.property = event.target; 6238 6239 XSendEvent(display, selectionEvent.requestor, false, 0, cast(XEvent*) &selectionEvent); 6240 XFlush(display); 6241 } if(event.target == GetAtom!"TARGETS"(display)) { 6242 /* respond with the supported types */ 6243 auto tlist = this.availableFormats(); 6244 XChangeProperty(display, event.requestor, event.property, XA_ATOM, 32, PropModeReplace, cast(void*)tlist.ptr, cast(int) tlist.length); 6245 selectionEvent.property = event.property; 6246 6247 XSendEvent(display, selectionEvent.requestor, false, 0, cast(XEvent*) &selectionEvent); 6248 XFlush(display); 6249 } else if(supportedType()) { 6250 auto buffer = new ubyte[](1024 * 64); 6251 auto toSend = this.getData(event.target, buffer[]); 6252 6253 if(toSend.length < 32 * 1024) { 6254 // small enough to send directly... 6255 selectionEvent.property = event.property; 6256 XChangeProperty (display, 6257 selectionEvent.requestor, 6258 selectionEvent.property, 6259 event.target, 6260 8 /* bits */, 0 /* PropModeReplace */, 6261 toSend.ptr, cast(int) toSend.length); 6262 6263 XSendEvent(display, selectionEvent.requestor, false, 0, cast(XEvent*) &selectionEvent); 6264 XFlush(display); 6265 } else { 6266 // large, let's send incrementally 6267 arch_ulong l = toSend.length; 6268 6269 // if I wanted other events from this window don't want to clear that out.... 6270 XWindowAttributes xwa; 6271 XGetWindowAttributes(display, selectionEvent.requestor, &xwa); 6272 6273 XSelectInput(display, selectionEvent.requestor, cast(EventMask) (xwa.your_event_mask | EventMask.PropertyChangeMask)); 6274 6275 incrWindow = event.requestor; 6276 incrAtom = event.property; 6277 formatAtom = event.target; 6278 selectionAtom = event.selection; 6279 this.toSend = toSend; 6280 6281 selectionEvent.property = event.property; 6282 XChangeProperty (display, 6283 selectionEvent.requestor, 6284 selectionEvent.property, 6285 GetAtom!"INCR"(display), 6286 32 /* bits */, PropModeReplace, 6287 &l, 1); 6288 6289 XSendEvent(display, selectionEvent.requestor, false, 0, cast(XEvent*) &selectionEvent); 6290 XFlush(display); 6291 } 6292 //if(after) 6293 //after(); 6294 } else { 6295 debug(sdpy_clip) { 6296 import std.stdio; writeln("Unsupported data ", getAtomName(event.target, display)); 6297 } 6298 selectionEvent.property = None; // I don't know how to handle this type... 6299 XSendEvent(display, selectionEvent.requestor, false, EventMask.NoEventMask, cast(XEvent*) &selectionEvent); 6300 XFlush(display); 6301 } 6302 } 6303 } 6304 6305 class X11SetSelectionHandler_Text : X11SetSelectionHandler { 6306 mixin X11SetSelectionHandler_Basics; 6307 private const(ubyte)[] text; 6308 private const(ubyte)[] text_original; 6309 this(string text) { 6310 this.text = cast(const ubyte[]) text; 6311 this.text_original = this.text; 6312 } 6313 Atom[] availableFormats() { 6314 auto display = XDisplayConnection.get; 6315 return [ 6316 GetAtom!"UTF8_STRING"(display), 6317 GetAtom!"text/plain"(display), 6318 XA_STRING, 6319 GetAtom!"TARGETS"(display) 6320 ]; 6321 } 6322 6323 ubyte[] getData(Atom format, return scope ubyte[] data) { 6324 if(text.length < data.length) { 6325 data[0 .. text.length] = text[]; 6326 return data[0 .. text.length]; 6327 } else { 6328 data[] = text[0 .. data.length]; 6329 text = text[data.length .. $]; 6330 return data[]; 6331 } 6332 } 6333 6334 void done() { 6335 text = text_original; 6336 } 6337 } 6338 6339 /// 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?!) 6340 void setX11Selection(string atomName)(SimpleWindow window, string text, void delegate() after = null) { 6341 setX11Selection!atomName(window, new X11SetSelectionHandler_Text(text), after); 6342 } 6343 6344 void setX11Selection(string atomName)(SimpleWindow window, X11SetSelectionHandler data, void delegate() after = null) { 6345 assert(window !is null); 6346 6347 auto display = XDisplayConnection.get(); 6348 static if (atomName == "PRIMARY") Atom a = XA_PRIMARY; 6349 else static if (atomName == "SECONDARY") Atom a = XA_SECONDARY; 6350 else Atom a = GetAtom!atomName(display); 6351 6352 XSetSelectionOwner(display, a, window.impl.window, 0 /* CurrentTime */); 6353 6354 window.impl.setSelectionHandlers[a] = data; 6355 } 6356 6357 /// 6358 void getPrimarySelection(SimpleWindow window, void delegate(in char[]) handler) { 6359 getX11Selection!"PRIMARY"(window, handler); 6360 } 6361 6362 // added July 28, 2020 6363 // undocumented as experimental tho 6364 interface X11GetSelectionHandler { 6365 void handleData(Atom target, in ubyte[] data); 6366 Atom findBestFormat(Atom[] answer); 6367 6368 void prepareIncremental(Window, Atom); 6369 bool matchesIncr(Window, Atom); 6370 void handleIncrData(Atom, in ubyte[] data); 6371 } 6372 6373 mixin template X11GetSelectionHandler_Basics() { 6374 Window incrWindow; 6375 Atom incrAtom; 6376 6377 void prepareIncremental(Window w, Atom a) { 6378 incrWindow = w; 6379 incrAtom = a; 6380 } 6381 bool matchesIncr(Window w, Atom a) { 6382 return incrWindow == w && incrAtom == a; 6383 } 6384 6385 Atom incrFormatAtom; 6386 ubyte[] incrData; 6387 void handleIncrData(Atom format, in ubyte[] data) { 6388 incrFormatAtom = format; 6389 6390 if(data.length) 6391 incrData ~= data; 6392 else 6393 handleData(incrFormatAtom, incrData); 6394 6395 } 6396 } 6397 6398 /// 6399 void getX11Selection(string atomName)(SimpleWindow window, void delegate(in char[]) handler, Time timestamp = 0 /* CurrentTime */) { 6400 assert(window !is null); 6401 6402 auto display = XDisplayConnection.get(); 6403 auto atom = GetAtom!atomName(display); 6404 6405 static class X11GetSelectionHandler_Text : X11GetSelectionHandler { 6406 this(void delegate(in char[]) handler) { 6407 this.handler = handler; 6408 } 6409 6410 mixin X11GetSelectionHandler_Basics; 6411 6412 void delegate(in char[]) handler; 6413 6414 void handleData(Atom target, in ubyte[] data) { 6415 if(target == GetAtom!"UTF8_STRING"(XDisplayConnection.get) || target == XA_STRING || target == GetAtom!"text/plain"(XDisplayConnection.get)) 6416 handler(cast(const char[]) data); 6417 } 6418 6419 Atom findBestFormat(Atom[] answer) { 6420 Atom best = None; 6421 foreach(option; answer) { 6422 if(option == GetAtom!"UTF8_STRING"(XDisplayConnection.get)) { 6423 best = option; 6424 break; 6425 } else if(option == XA_STRING) { 6426 best = option; 6427 } else if(option == GetAtom!"text/plain"(XDisplayConnection.get)) { 6428 best = option; 6429 } 6430 } 6431 return best; 6432 } 6433 } 6434 6435 window.impl.getSelectionHandlers[atom] = new X11GetSelectionHandler_Text(handler); 6436 6437 auto target = GetAtom!"TARGETS"(display); 6438 6439 // SDD_DATA is "simpledisplay.d data" 6440 XConvertSelection(display, atom, target, GetAtom!("SDD_DATA", true)(display), window.impl.window, timestamp); 6441 } 6442 6443 /// Gets the image on the clipboard, if there is one. Added July 2020. 6444 void getX11Selection(string atomName)(SimpleWindow window, void delegate(MemoryImage) handler) { 6445 assert(window !is null); 6446 6447 auto display = XDisplayConnection.get(); 6448 auto atom = GetAtom!atomName(display); 6449 6450 static class X11GetSelectionHandler_Image : X11GetSelectionHandler { 6451 this(void delegate(MemoryImage) handler) { 6452 this.handler = handler; 6453 } 6454 6455 mixin X11GetSelectionHandler_Basics; 6456 6457 void delegate(MemoryImage) handler; 6458 6459 void handleData(Atom target, in ubyte[] data) { 6460 if(target == GetAtom!"image/bmp"(XDisplayConnection.get)) { 6461 import arsd.bmp; 6462 handler(readBmp(data)); 6463 } 6464 } 6465 6466 Atom findBestFormat(Atom[] answer) { 6467 Atom best = None; 6468 foreach(option; answer) { 6469 if(option == GetAtom!"image/bmp"(XDisplayConnection.get)) { 6470 best = option; 6471 } 6472 } 6473 return best; 6474 } 6475 6476 } 6477 6478 6479 window.impl.getSelectionHandlers[atom] = new X11GetSelectionHandler_Image(handler); 6480 6481 auto target = GetAtom!"TARGETS"(display); 6482 6483 // SDD_DATA is "simpledisplay.d data" 6484 XConvertSelection(display, atom, target, GetAtom!("SDD_DATA", true)(display), window.impl.window, 0 /*CurrentTime*/); 6485 } 6486 6487 6488 /// 6489 void[] getX11PropertyData(Window window, Atom property, Atom type = AnyPropertyType) { 6490 Atom actualType; 6491 int actualFormat; 6492 arch_ulong actualItems; 6493 arch_ulong bytesRemaining; 6494 void* data; 6495 6496 auto display = XDisplayConnection.get(); 6497 if(XGetWindowProperty(display, window, property, 0, 0x7fffffff, false, type, &actualType, &actualFormat, &actualItems, &bytesRemaining, &data) == Success) { 6498 if(actualFormat == 0) 6499 return null; 6500 else { 6501 int byteLength; 6502 if(actualFormat == 32) { 6503 // 32 means it is a C long... which is variable length 6504 actualFormat = cast(int) arch_long.sizeof * 8; 6505 } 6506 6507 // then it is just a bit count 6508 byteLength = cast(int) (actualItems * actualFormat / 8); 6509 6510 auto d = new ubyte[](byteLength); 6511 d[] = cast(ubyte[]) data[0 .. byteLength]; 6512 XFree(data); 6513 return d; 6514 } 6515 } 6516 return null; 6517 } 6518 6519 /* defined in the systray spec */ 6520 enum SYSTEM_TRAY_REQUEST_DOCK = 0; 6521 enum SYSTEM_TRAY_BEGIN_MESSAGE = 1; 6522 enum SYSTEM_TRAY_CANCEL_MESSAGE = 2; 6523 6524 6525 /** Global hotkey handler. Simpledisplay will usually create one for you, but if you want to use subclassing 6526 * instead of delegates, you can subclass this, and override `doHandle()` method. */ 6527 public class GlobalHotkey { 6528 KeyEvent key; 6529 void delegate () handler; 6530 6531 void doHandle () { if (handler !is null) handler(); } /// this will be called by hotkey manager 6532 6533 /// Create from initialzed KeyEvent object 6534 this (KeyEvent akey, void delegate () ahandler=null) { 6535 if (akey.key == 0 || !GlobalHotkeyManager.isGoodModifierMask(akey.modifierState)) throw new Exception("invalid global hotkey"); 6536 key = akey; 6537 handler = ahandler; 6538 } 6539 6540 /// Create from emacs-like key name ("C-M-Y", etc.) 6541 this (const(char)[] akey, void delegate () ahandler=null) { 6542 key = KeyEvent.parse(akey); 6543 if (key.key == 0 || !GlobalHotkeyManager.isGoodModifierMask(key.modifierState)) throw new Exception("invalid global hotkey"); 6544 handler = ahandler; 6545 } 6546 6547 } 6548 6549 private extern(C) int XGrabErrorHandler (Display* dpy, XErrorEvent* evt) nothrow @nogc { 6550 //conwriteln("failed to grab key"); 6551 GlobalHotkeyManager.ghfailed = true; 6552 return 0; 6553 } 6554 6555 private extern(C) int XShmErrorHandler (Display* dpy, XErrorEvent* evt) nothrow @nogc { 6556 Image.impl.xshmfailed = true; 6557 return 0; 6558 } 6559 6560 private __gshared int errorHappened; 6561 private extern(C) int adrlogger (Display* dpy, XErrorEvent* evt) nothrow @nogc { 6562 import core.stdc.stdio; 6563 char[265] buffer; 6564 XGetErrorText(dpy, evt.error_code, buffer.ptr, cast(int) buffer.length); 6565 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); 6566 errorHappened = true; 6567 return 0; 6568 } 6569 6570 /++ 6571 Global hotkey manager. It contains static methods to manage global hotkeys. 6572 6573 --- 6574 try { 6575 GlobalHotkeyManager.register("M-H-A", delegate () { hideShowWindows(); }); 6576 } catch (Exception e) { 6577 conwriteln("ERROR registering hotkey!"); 6578 } 6579 EventLoop.get.run(); 6580 --- 6581 6582 The key strings are based on Emacs. In practical terms, 6583 `M` means `alt` and `H` means the Windows logo key. `C` 6584 is `ctrl`. 6585 6586 $(WARNING 6587 This is X-specific right now. If you are on 6588 Windows, try [registerHotKey] instead. 6589 6590 We will probably merge these into a single 6591 interface later. 6592 ) 6593 +/ 6594 public class GlobalHotkeyManager : CapableOfHandlingNativeEvent { 6595 version(X11) { 6596 void recreateAfterDisconnect() { 6597 throw new Exception("NOT IMPLEMENTED"); 6598 } 6599 void discardConnectionState() { 6600 throw new Exception("NOT IMPLEMENTED"); 6601 } 6602 } 6603 6604 private static immutable uint[8] masklist = [ 0, 6605 KeyOrButtonMask.LockMask, 6606 KeyOrButtonMask.Mod2Mask, 6607 KeyOrButtonMask.Mod3Mask, 6608 KeyOrButtonMask.LockMask|KeyOrButtonMask.Mod2Mask, 6609 KeyOrButtonMask.LockMask|KeyOrButtonMask.Mod3Mask, 6610 KeyOrButtonMask.LockMask|KeyOrButtonMask.Mod2Mask|KeyOrButtonMask.Mod3Mask, 6611 KeyOrButtonMask.Mod2Mask|KeyOrButtonMask.Mod3Mask, 6612 ]; 6613 private __gshared GlobalHotkeyManager ghmanager; 6614 private __gshared bool ghfailed = false; 6615 6616 private static bool isGoodModifierMask (uint modmask) pure nothrow @safe @nogc { 6617 if (modmask == 0) return false; 6618 if (modmask&(KeyOrButtonMask.LockMask|KeyOrButtonMask.Mod2Mask|KeyOrButtonMask.Mod3Mask)) return false; 6619 if (modmask&~(KeyOrButtonMask.Mod5Mask-1)) return false; 6620 return true; 6621 } 6622 6623 private static uint cleanupModifiers (uint modmask) pure nothrow @safe @nogc { 6624 modmask &= ~(KeyOrButtonMask.LockMask|KeyOrButtonMask.Mod2Mask|KeyOrButtonMask.Mod3Mask); // remove caps, num, scroll 6625 modmask &= (KeyOrButtonMask.Mod5Mask-1); // and other modifiers 6626 return modmask; 6627 } 6628 6629 private static uint keyEvent2KeyCode() (scope auto ref const KeyEvent ke) { 6630 uint keycode = cast(uint)ke.key; 6631 auto dpy = XDisplayConnection.get; 6632 return XKeysymToKeycode(dpy, keycode); 6633 } 6634 6635 private static ulong keyCode2Hash() (uint keycode, uint modstate) pure nothrow @safe @nogc { return ((cast(ulong)modstate)<<32)|keycode; } 6636 6637 private __gshared GlobalHotkey[ulong] globalHotkeyList; 6638 6639 NativeEventHandler getNativeEventHandler () { 6640 return delegate int (XEvent e) { 6641 if (e.type != EventType.KeyPress) return 1; 6642 auto kev = cast(const(XKeyEvent)*)&e; 6643 auto hash = keyCode2Hash(e.xkey.keycode, cleanupModifiers(e.xkey.state)); 6644 if (auto ghkp = hash in globalHotkeyList) { 6645 try { 6646 ghkp.doHandle(); 6647 } catch (Exception e) { 6648 import core.stdc.stdio : stderr, fprintf; 6649 stderr.fprintf("HOTKEY HANDLER EXCEPTION: %.*s", cast(uint)e.msg.length, e.msg.ptr); 6650 } 6651 } 6652 return 1; 6653 }; 6654 } 6655 6656 private this () { 6657 auto dpy = XDisplayConnection.get; 6658 auto root = RootWindow(dpy, DefaultScreen(dpy)); 6659 CapableOfHandlingNativeEvent.nativeHandleMapping[root] = this; 6660 XDisplayConnection.addRootInput(EventMask.KeyPressMask); 6661 } 6662 6663 /// Register new global hotkey with initialized `GlobalHotkey` object. 6664 /// This function will throw if it failed to register hotkey (i.e. hotkey is invalid or already taken). 6665 static void register (GlobalHotkey gh) { 6666 if (gh is null) return; 6667 if (gh.key.key == 0 || !isGoodModifierMask(gh.key.modifierState)) throw new Exception("invalid global hotkey"); 6668 6669 auto dpy = XDisplayConnection.get; 6670 immutable keycode = keyEvent2KeyCode(gh.key); 6671 6672 auto hash = keyCode2Hash(keycode, gh.key.modifierState); 6673 if (hash in globalHotkeyList) throw new Exception("duplicate global hotkey"); 6674 if (ghmanager is null) ghmanager = new GlobalHotkeyManager(); 6675 XSync(dpy, 0/*False*/); 6676 6677 Window root = RootWindow(dpy, DefaultScreen(dpy)); 6678 XErrorHandler savedErrorHandler = XSetErrorHandler(&XGrabErrorHandler); 6679 ghfailed = false; 6680 foreach (immutable uint ormask; masklist[]) { 6681 XGrabKey(dpy, keycode, gh.key.modifierState|ormask, /*grab_window*/root, /*owner_events*/0/*False*/, GrabMode.GrabModeAsync, GrabMode.GrabModeAsync); 6682 } 6683 XSync(dpy, 0/*False*/); 6684 XSetErrorHandler(savedErrorHandler); 6685 6686 if (ghfailed) { 6687 savedErrorHandler = XSetErrorHandler(&XGrabErrorHandler); 6688 foreach (immutable uint ormask; masklist[]) XUngrabKey(dpy, keycode, gh.key.modifierState|ormask, /*grab_window*/root); 6689 XSync(dpy, 0/*False*/); 6690 XSetErrorHandler(savedErrorHandler); 6691 throw new Exception("cannot register global hotkey"); 6692 } 6693 6694 globalHotkeyList[hash] = gh; 6695 } 6696 6697 /// Ditto 6698 static void register (const(char)[] akey, void delegate () ahandler) { 6699 register(new GlobalHotkey(akey, ahandler)); 6700 } 6701 6702 private static void removeByHash (ulong hash) { 6703 if (auto ghp = hash in globalHotkeyList) { 6704 auto dpy = XDisplayConnection.get; 6705 immutable keycode = keyEvent2KeyCode(ghp.key); 6706 Window root = RootWindow(dpy, DefaultScreen(dpy)); 6707 XSync(dpy, 0/*False*/); 6708 XErrorHandler savedErrorHandler = XSetErrorHandler(&XGrabErrorHandler); 6709 foreach (immutable uint ormask; masklist[]) XUngrabKey(dpy, keycode, ghp.key.modifierState|ormask, /*grab_window*/root); 6710 XSync(dpy, 0/*False*/); 6711 XSetErrorHandler(savedErrorHandler); 6712 globalHotkeyList.remove(hash); 6713 } 6714 } 6715 6716 /// Register new global hotkey with previously used `GlobalHotkey` object. 6717 /// It is safe to unregister unknown or invalid hotkey. 6718 static void unregister (GlobalHotkey gh) { 6719 //TODO: add second AA for faster search? prolly doesn't worth it. 6720 if (gh is null) return; 6721 foreach (const ref kv; globalHotkeyList.byKeyValue) { 6722 if (kv.value is gh) { 6723 removeByHash(kv.key); 6724 return; 6725 } 6726 } 6727 } 6728 6729 /// Ditto. 6730 static void unregister (const(char)[] key) { 6731 auto kev = KeyEvent.parse(key); 6732 immutable keycode = keyEvent2KeyCode(kev); 6733 removeByHash(keyCode2Hash(keycode, kev.modifierState)); 6734 } 6735 } 6736 } 6737 6738 version(Windows) { 6739 /++ 6740 See [SyntheticInput.sendSyntheticInput] instead for cross-platform applications. 6741 6742 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). 6743 +/ 6744 void sendSyntheticInput(wstring s) { 6745 INPUT[] inputs; 6746 inputs.reserve(s.length * 2); 6747 6748 foreach(wchar c; s) { 6749 INPUT input; 6750 input.type = INPUT_KEYBOARD; 6751 input.ki.wScan = c; 6752 input.ki.dwFlags = KEYEVENTF_UNICODE; 6753 inputs ~= input; 6754 6755 input.ki.dwFlags |= KEYEVENTF_KEYUP; 6756 inputs ~= input; 6757 } 6758 6759 if(SendInput(cast(int) inputs.length, inputs.ptr, INPUT.sizeof) != inputs.length) { 6760 throw new Exception("SendInput failed"); 6761 } 6762 6763 } 6764 6765 6766 // global hotkey helper function 6767 6768 /// Platform-specific for Windows. Registers a global hotkey. Returns a registration ID. See [GlobalHotkeyManager] for Linux. Maybe some day I will merge these. 6769 int registerHotKey(SimpleWindow window, UINT modifiers, UINT vk, void delegate() handler) { 6770 __gshared int hotkeyId = 0; 6771 int id = ++hotkeyId; 6772 if(!RegisterHotKey(window.impl.hwnd, id, modifiers, vk)) 6773 throw new Exception("RegisterHotKey failed"); 6774 6775 __gshared void delegate()[WPARAM][HWND] handlers; 6776 6777 handlers[window.impl.hwnd][id] = handler; 6778 6779 int delegate(HWND, UINT, WPARAM, LPARAM, out int) oldHandler; 6780 6781 auto nativeEventHandler = delegate int(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam, out int mustReturn) { 6782 switch(msg) { 6783 // http://msdn.microsoft.com/en-us/library/windows/desktop/ms646279%28v=vs.85%29.aspx 6784 case WM_HOTKEY: 6785 if(auto list = hwnd in handlers) { 6786 if(auto h = wParam in *list) { 6787 (*h)(); 6788 return 0; 6789 } 6790 } 6791 goto default; 6792 default: 6793 } 6794 if(oldHandler) 6795 return oldHandler(hwnd, msg, wParam, lParam, mustReturn); 6796 return 1; // pass it on 6797 }; 6798 6799 if(window.handleNativeEvent.funcptr !is nativeEventHandler.funcptr) { 6800 oldHandler = window.handleNativeEvent; 6801 window.handleNativeEvent = nativeEventHandler; 6802 } 6803 6804 return id; 6805 } 6806 6807 /// Platform-specific for Windows. Unregisters a key. The id is the value returned by [registerHotKey]. 6808 void unregisterHotKey(SimpleWindow window, int id) { 6809 if(!UnregisterHotKey(window.impl.hwnd, id)) 6810 throw new Exception("UnregisterHotKey"); 6811 } 6812 } 6813 6814 version (X11) { 6815 pragma(lib, "dl"); 6816 import core.sys.posix.dlfcn; 6817 } 6818 6819 /++ 6820 Allows for sending synthetic input to the X server via the Xtst 6821 extension or on Windows using SendInput. 6822 6823 Please remember user input is meant to be user - don't use this 6824 if you have some other alternative! 6825 6826 History: 6827 Added May 17, 2020 with the X implementation. 6828 6829 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.) 6830 Bugs: 6831 All methods on OSX Cocoa will throw not yet implemented exceptions. 6832 +/ 6833 struct SyntheticInput { 6834 @disable this(); 6835 6836 private int* refcount; 6837 6838 version(X11) { 6839 private void* lib; 6840 6841 private extern(C) { 6842 void function(Display*, uint keycode, bool press, arch_ulong delay) XTestFakeKeyEvent; 6843 void function(Display*, uint button, bool press, arch_ulong delay) XTestFakeButtonEvent; 6844 } 6845 } 6846 6847 /// The dummy param must be 0. 6848 this(int dummy) { 6849 version(X11) { 6850 lib = dlopen("libXtst.so", RTLD_NOW); 6851 if(lib is null) 6852 throw new Exception("cannot load xtest lib extension"); 6853 scope(failure) 6854 dlclose(lib); 6855 6856 XTestFakeButtonEvent = cast(typeof(XTestFakeButtonEvent)) dlsym(lib, "XTestFakeButtonEvent"); 6857 XTestFakeKeyEvent = cast(typeof(XTestFakeKeyEvent)) dlsym(lib, "XTestFakeKeyEvent"); 6858 6859 if(XTestFakeKeyEvent is null) 6860 throw new Exception("No XTestFakeKeyEvent"); 6861 if(XTestFakeButtonEvent is null) 6862 throw new Exception("No XTestFakeButtonEvent"); 6863 } 6864 6865 refcount = new int; 6866 *refcount = 1; 6867 } 6868 6869 this(this) { 6870 if(refcount) 6871 *refcount += 1; 6872 } 6873 6874 ~this() { 6875 if(refcount) { 6876 *refcount -= 1; 6877 if(*refcount == 0) 6878 // I commented this because if I close the lib before 6879 // XCloseDisplay, it is liable to segfault... so just 6880 // gonna keep it loaded if it is loaded, no big deal 6881 // anyway. 6882 {} // dlclose(lib); 6883 } 6884 } 6885 6886 /++ 6887 Simulates typing a string into the keyboard. 6888 6889 Bugs: 6890 On X11, this ONLY works with basic ascii! On Windows, it can handle more. 6891 6892 Not implemented except on Windows and X11. 6893 +/ 6894 void sendSyntheticInput(string s) { 6895 version(Windows) { 6896 INPUT[] inputs; 6897 inputs.reserve(s.length * 2); 6898 6899 auto ei = GetMessageExtraInfo(); 6900 6901 foreach(wchar c; s) { 6902 INPUT input; 6903 input.type = INPUT_KEYBOARD; 6904 input.ki.wScan = c; 6905 input.ki.dwFlags = KEYEVENTF_UNICODE; 6906 input.ki.dwExtraInfo = ei; 6907 inputs ~= input; 6908 6909 input.ki.dwFlags |= KEYEVENTF_KEYUP; 6910 inputs ~= input; 6911 } 6912 6913 if(SendInput(cast(int) inputs.length, inputs.ptr, INPUT.sizeof) != inputs.length) { 6914 throw new Exception("SendInput failed"); 6915 } 6916 } else version(X11) { 6917 int delay = 0; 6918 foreach(ch; s) { 6919 pressKey(cast(Key) ch, true, delay); 6920 pressKey(cast(Key) ch, false, delay); 6921 delay += 5; 6922 } 6923 } else throw new NotYetImplementedException(); 6924 } 6925 6926 /++ 6927 Sends a fake press or release key event. 6928 6929 Please note you need to call [flushGui] or return to the event loop for this to actually be sent on X11. 6930 6931 Bugs: 6932 The `delay` parameter is not implemented yet on Windows. 6933 6934 Not implemented except on Windows and X11. 6935 +/ 6936 void pressKey(Key key, bool pressed, int delay = 0) { 6937 version(Windows) { 6938 INPUT input; 6939 input.type = INPUT_KEYBOARD; 6940 input.ki.wVk = cast(ushort) key; 6941 6942 input.ki.dwFlags = pressed ? 0 : KEYEVENTF_KEYUP; 6943 input.ki.dwExtraInfo = GetMessageExtraInfo(); 6944 6945 if(SendInput(1, &input, INPUT.sizeof) != 1) { 6946 throw new Exception("SendInput failed"); 6947 } 6948 } else version(X11) { 6949 XTestFakeKeyEvent(XDisplayConnection.get, XKeysymToKeycode(XDisplayConnection.get, key), pressed, delay + pressed ? 0 : 5); 6950 } else throw new NotYetImplementedException(); 6951 } 6952 6953 /++ 6954 Sends a fake mouse button press or release event. 6955 6956 Please note you need to call [flushGui] or return to the event loop for this to actually be sent on X11. 6957 6958 `pressed` param must be `true` if button is `wheelUp` or `wheelDown`. 6959 6960 Bugs: 6961 The `delay` parameter is not implemented yet on Windows. 6962 6963 The backButton and forwardButton will throw NotYetImplementedException on Windows. 6964 6965 All arguments will throw NotYetImplementedException on OSX Cocoa. 6966 +/ 6967 void pressMouseButton(MouseButton button, bool pressed, int delay = 0) { 6968 version(Windows) { 6969 INPUT input; 6970 input.type = INPUT_MOUSE; 6971 input.mi.dwExtraInfo = GetMessageExtraInfo(); 6972 6973 // input.mi.mouseData for a wheel event 6974 6975 switch(button) { 6976 case MouseButton.left: input.mi.dwFlags = pressed ? MOUSEEVENTF_LEFTDOWN : MOUSEEVENTF_LEFTUP; break; 6977 case MouseButton.middle: input.mi.dwFlags = pressed ? MOUSEEVENTF_MIDDLEDOWN : MOUSEEVENTF_MIDDLEUP; break; 6978 case MouseButton.right: input.mi.dwFlags = pressed ? MOUSEEVENTF_RIGHTDOWN : MOUSEEVENTF_RIGHTUP; break; 6979 case MouseButton.wheelUp: 6980 case MouseButton.wheelDown: 6981 input.mi.dwFlags = MOUSEEVENTF_WHEEL; 6982 input.mi.mouseData = button == MouseButton.wheelUp ? 120 : -120; 6983 break; 6984 case MouseButton.backButton: throw new NotYetImplementedException(); 6985 case MouseButton.forwardButton: throw new NotYetImplementedException(); 6986 default: 6987 } 6988 6989 if(SendInput(1, &input, INPUT.sizeof) != 1) { 6990 throw new Exception("SendInput failed"); 6991 } 6992 } else version(X11) { 6993 int btn; 6994 6995 switch(button) { 6996 case MouseButton.left: btn = 1; break; 6997 case MouseButton.middle: btn = 2; break; 6998 case MouseButton.right: btn = 3; break; 6999 case MouseButton.wheelUp: btn = 4; break; 7000 case MouseButton.wheelDown: btn = 5; break; 7001 case MouseButton.backButton: btn = 8; break; 7002 case MouseButton.forwardButton: btn = 9; break; 7003 default: 7004 } 7005 7006 assert(btn); 7007 7008 XTestFakeButtonEvent(XDisplayConnection.get, btn, pressed, delay); 7009 } else throw new NotYetImplementedException(); 7010 } 7011 7012 /// 7013 static void moveMouseArrowBy(int dx, int dy) { 7014 version(Windows) { 7015 INPUT input; 7016 input.type = INPUT_MOUSE; 7017 input.mi.dwExtraInfo = GetMessageExtraInfo(); 7018 input.mi.dx = dx; 7019 input.mi.dy = dy; 7020 input.mi.dwFlags = MOUSEEVENTF_MOVE; 7021 7022 if(SendInput(1, &input, INPUT.sizeof) != 1) { 7023 throw new Exception("SendInput failed"); 7024 } 7025 } else version(X11) { 7026 auto disp = XDisplayConnection.get(); 7027 XWarpPointer(disp, None, None, 0, 0, 0, 0, dx, dy); 7028 XFlush(disp); 7029 } else throw new NotYetImplementedException(); 7030 } 7031 7032 /// 7033 static void moveMouseArrowTo(int x, int y) { 7034 version(Windows) { 7035 INPUT input; 7036 input.type = INPUT_MOUSE; 7037 input.mi.dwExtraInfo = GetMessageExtraInfo(); 7038 input.mi.dx = x; 7039 input.mi.dy = y; 7040 input.mi.dwFlags = MOUSEEVENTF_MOVE | MOUSEEVENTF_ABSOLUTE; 7041 7042 if(SendInput(1, &input, INPUT.sizeof) != 1) { 7043 throw new Exception("SendInput failed"); 7044 } 7045 } else version(X11) { 7046 auto disp = XDisplayConnection.get(); 7047 auto root = RootWindow(disp, DefaultScreen(disp)); 7048 XWarpPointer(disp, None, root, 0, 0, 0, 0, x, y); 7049 XFlush(disp); 7050 } else throw new NotYetImplementedException(); 7051 } 7052 } 7053 7054 7055 7056 /++ 7057 [ScreenPainter] operations can use different operations to combine the color with the color on screen. 7058 7059 See_Also: 7060 $(LIST 7061 *[ScreenPainter] 7062 *[ScreenPainter.rasterOp] 7063 ) 7064 +/ 7065 enum RasterOp { 7066 normal, /// Replaces the pixel. 7067 xor, /// Uses bitwise xor to draw. 7068 } 7069 7070 // being phobos-free keeps the size WAY down 7071 private const(char)* toStringz(string s) { return (s ~ '\0').ptr; } 7072 package(arsd) const(wchar)* toWStringz(wstring s) { return (s ~ '\0').ptr; } 7073 package(arsd) const(wchar)* toWStringz(string s) { 7074 wstring r; 7075 foreach(dchar c; s) 7076 r ~= c; 7077 r ~= '\0'; 7078 return r.ptr; 7079 } 7080 private string[] split(in void[] a, char c) { 7081 string[] ret; 7082 size_t previous = 0; 7083 foreach(i, char ch; cast(ubyte[]) a) { 7084 if(ch == c) { 7085 ret ~= cast(string) a[previous .. i]; 7086 previous = i + 1; 7087 } 7088 } 7089 if(previous != a.length) 7090 ret ~= cast(string) a[previous .. $]; 7091 return ret; 7092 } 7093 7094 version(without_opengl) { 7095 enum OpenGlOptions { 7096 no, 7097 } 7098 } else { 7099 /++ 7100 Determines if you want an OpenGL context created on the new window. 7101 7102 7103 See more: [#topics-3d|in the 3d topic]. 7104 7105 --- 7106 import arsd.simpledisplay; 7107 void main() { 7108 auto window = new SimpleWindow(500, 500, "OpenGL Test", OpenGlOptions.yes); 7109 7110 // Set up the matrix 7111 window.setAsCurrentOpenGlContext(); // make this window active 7112 7113 // This is called on each frame, we will draw our scene 7114 window.redrawOpenGlScene = delegate() { 7115 7116 }; 7117 7118 window.eventLoop(0); 7119 } 7120 --- 7121 +/ 7122 enum OpenGlOptions { 7123 no, /// No OpenGL context is created 7124 yes, /// Yes, create an OpenGL context 7125 } 7126 7127 version(X11) { 7128 static if (!SdpyIsUsingIVGLBinds) { 7129 7130 7131 struct __GLXFBConfigRec {} 7132 alias GLXFBConfig = __GLXFBConfigRec*; 7133 7134 //pragma(lib, "GL"); 7135 //pragma(lib, "GLU"); 7136 interface GLX { 7137 extern(C) nothrow @nogc { 7138 XVisualInfo* glXChooseVisual(Display *dpy, int screen, 7139 const int *attrib_list); 7140 7141 void glXCopyContext(Display *dpy, GLXContext src, 7142 GLXContext dst, arch_ulong mask); 7143 7144 GLXContext glXCreateContext(Display *dpy, XVisualInfo *vis, 7145 GLXContext share_list, Bool direct); 7146 7147 GLXPixmap glXCreateGLXPixmap(Display *dpy, XVisualInfo *vis, 7148 Pixmap pixmap); 7149 7150 void glXDestroyContext(Display *dpy, GLXContext ctx); 7151 7152 void glXDestroyGLXPixmap(Display *dpy, GLXPixmap pix); 7153 7154 int glXGetConfig(Display *dpy, XVisualInfo *vis, 7155 int attrib, int *value); 7156 7157 GLXContext glXGetCurrentContext(); 7158 7159 GLXDrawable glXGetCurrentDrawable(); 7160 7161 Bool glXIsDirect(Display *dpy, GLXContext ctx); 7162 7163 Bool glXMakeCurrent(Display *dpy, GLXDrawable drawable, 7164 GLXContext ctx); 7165 7166 Bool glXQueryExtension(Display *dpy, int *error_base, int *event_base); 7167 7168 Bool glXQueryVersion(Display *dpy, int *major, int *minor); 7169 7170 void glXSwapBuffers(Display *dpy, GLXDrawable drawable); 7171 7172 void glXUseXFont(Font font, int first, int count, int list_base); 7173 7174 void glXWaitGL(); 7175 7176 void glXWaitX(); 7177 7178 7179 GLXFBConfig* glXChooseFBConfig (Display*, int, int*, int*); 7180 int glXGetFBConfigAttrib (Display*, GLXFBConfig, int, int*); 7181 XVisualInfo* glXGetVisualFromFBConfig (Display*, GLXFBConfig); 7182 7183 char* glXQueryExtensionsString (Display*, int); 7184 void* glXGetProcAddress (const(char)*); 7185 7186 } 7187 } 7188 7189 version(OSX) 7190 mixin DynamicLoad!(GLX, "GL", 0, openGlLibrariesSuccessfullyLoaded) glx; 7191 else 7192 mixin DynamicLoad!(GLX, "GLX", 0, openGlLibrariesSuccessfullyLoaded) glx; 7193 shared static this() { 7194 glx.loadDynamicLibrary(); 7195 } 7196 7197 alias glbindGetProcAddress = glXGetProcAddress; 7198 } 7199 } else version(Windows) { 7200 /* it is done below by interface GL */ 7201 } else 7202 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."); 7203 } 7204 7205 deprecated("Sorry, I misspelled it in the first version! Use `Resizability` instead.") 7206 alias Resizablity = Resizability; 7207 7208 /// When you create a SimpleWindow, you can see its resizability to be one of these via the constructor... 7209 enum Resizability { 7210 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. 7211 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. 7212 /++ 7213 $(PITFALL 7214 Planned for the future but not implemented. 7215 ) 7216 7217 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. 7218 7219 History: 7220 Added November 11, 2022, but not yet implemented and may not be for some time. 7221 +/ 7222 allowResizingMaintainingAspectRatio, 7223 /++ 7224 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. 7225 7226 History: 7227 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. 7228 7229 Your programs should not be affected, as they will continue to function as if the user simply never resized the window at all. 7230 +/ 7231 automaticallyScaleIfPossible, 7232 } 7233 7234 7235 /++ 7236 Alignment for [ScreenPainter.drawText]. Left, Center, or Right may be combined with VerticalTop, VerticalCenter, or VerticalBottom via bitwise or. 7237 +/ 7238 enum TextAlignment : uint { 7239 Left = 0, /// 7240 Center = 1, /// 7241 Right = 2, /// 7242 7243 VerticalTop = 0, /// 7244 VerticalCenter = 4, /// 7245 VerticalBottom = 8, /// 7246 } 7247 7248 public import arsd.color; // no longer stand alone... :-( but i need a common type for this to work with images easily. 7249 alias Rectangle = arsd.color.Rectangle; 7250 7251 7252 /++ 7253 Keyboard press and release events. 7254 +/ 7255 struct KeyEvent { 7256 /// see table below. Always use the symbolic names, even for ASCII characters, since the actual numbers vary across platforms. See [Key] 7257 Key key; 7258 ubyte hardwareCode; /// A platform and hardware specific code for the key 7259 bool pressed; /// true if the key was just pressed, false if it was just released. note: released events aren't always sent... 7260 7261 deprecated("This never actually worked anyway, you should do a character event handler instead.") dchar character; 7262 7263 uint modifierState; /// see enum [ModifierState]. They are bitwise combined together. 7264 7265 SimpleWindow window; /// associated Window 7266 7267 /++ 7268 A view into the upcoming buffer holding coming character events that are sent if and only if neither 7269 the alt or super modifier keys are pressed (check this with `!(modifierState & (ModifierState.window | ModifierState.alt))` 7270 to predict if char events are actually coming.. 7271 7272 Only available on X systems since this information is not given ahead of time elsewhere. 7273 (Well, you COULD probably dig it up, but as far as I know right now, it isn't terribly pretty.) 7274 7275 I'm adding this because it is useful to the terminal emulator, but given its platform specificness 7276 and potential quirks I'd recommend avoiding it. 7277 7278 History: 7279 Added April 26, 2021 (dub v9.5) 7280 +/ 7281 version(X11) 7282 dchar[] charsPossible; 7283 7284 // convert key event to simplified string representation a-la emacs 7285 const(char)[] toStrBuf(bool growdest=false) (char[] dest) const nothrow @trusted { 7286 uint dpos = 0; 7287 void put (const(char)[] s...) nothrow @trusted { 7288 static if (growdest) { 7289 foreach (char ch; s) if (dpos < dest.length) dest.ptr[dpos++] = ch; else { dest ~= ch; ++dpos; } 7290 } else { 7291 foreach (char ch; s) if (dpos < dest.length) dest.ptr[dpos++] = ch; 7292 } 7293 } 7294 7295 void putMod (ModifierState mod, Key key, string text) nothrow @trusted { 7296 if ((this.modifierState&mod) != 0 && (this.pressed || this.key != key)) put(text); 7297 } 7298 7299 if (!this.key && !(this.modifierState&(ModifierState.ctrl|ModifierState.alt|ModifierState.shift|ModifierState.windows))) return null; 7300 7301 // put modifiers 7302 // releasing modifier keys can produce bizarre things like "Ctrl+Ctrl", so hack around it 7303 putMod(ModifierState.ctrl, Key.Ctrl, "Ctrl+"); 7304 putMod(ModifierState.alt, Key.Alt, "Alt+"); 7305 putMod(ModifierState.windows, Key.Shift, "Windows+"); 7306 putMod(ModifierState.shift, Key.Shift, "Shift+"); 7307 7308 if (this.key) { 7309 foreach (string kn; __traits(allMembers, Key)) { 7310 if (this.key == __traits(getMember, Key, kn)) { 7311 // HACK! 7312 static if (kn == "N0") put("0"); 7313 else static if (kn == "N1") put("1"); 7314 else static if (kn == "N2") put("2"); 7315 else static if (kn == "N3") put("3"); 7316 else static if (kn == "N4") put("4"); 7317 else static if (kn == "N5") put("5"); 7318 else static if (kn == "N6") put("6"); 7319 else static if (kn == "N7") put("7"); 7320 else static if (kn == "N8") put("8"); 7321 else static if (kn == "N9") put("9"); 7322 else put(kn); 7323 return dest[0..dpos]; 7324 } 7325 } 7326 put("Unknown"); 7327 } else { 7328 if (dpos && dest[dpos-1] == '+') --dpos; 7329 } 7330 return dest[0..dpos]; 7331 } 7332 7333 string toStr() () { return cast(string)toStrBuf!true(null); } // it is safe to cast here 7334 7335 /** Parse string into key name with modifiers. It accepts things like: 7336 * 7337 * C-H-1 -- emacs style (ctrl, and windows, and 1) 7338 * 7339 * Ctrl+Win+1 -- windows style 7340 * 7341 * Ctrl-Win-1 -- '-' is a valid delimiter too 7342 * 7343 * Ctrl Win 1 -- and space 7344 * 7345 * and even "Win + 1 + Ctrl". 7346 */ 7347 static KeyEvent parse (const(char)[] name, bool* ignoreModsOut=null, int* updown=null) nothrow @trusted @nogc { 7348 auto nanchor = name; // keep it anchored, 'cause `name` may have NO_INTERIOR set 7349 7350 // remove trailing spaces 7351 while (name.length && name[$-1] <= ' ') name = name[0..$-1]; 7352 7353 // tokens delimited by blank, '+', or '-' 7354 // null on eol 7355 const(char)[] getToken () nothrow @trusted @nogc { 7356 // remove leading spaces and delimiters 7357 while (name.length && (name[0] <= ' ' || name[0] == '+' || name[0] == '-')) name = name[1..$]; 7358 if (name.length == 0) return null; // oops, no more tokens 7359 // get token 7360 size_t epos = 0; 7361 while (epos < name.length && name[epos] > ' ' && name[epos] != '+' && name[epos] != '-') ++epos; 7362 assert(epos > 0 && epos <= name.length); 7363 auto res = name[0..epos]; 7364 name = name[epos..$]; 7365 return res; 7366 } 7367 7368 static bool strEquCI (const(char)[] s0, const(char)[] s1) pure nothrow @trusted @nogc { 7369 if (s0.length != s1.length) return false; 7370 foreach (immutable ci, char c0; s0) { 7371 if (c0 >= 'A' && c0 <= 'Z') c0 += 32; // poor man's tolower 7372 char c1 = s1[ci]; 7373 if (c1 >= 'A' && c1 <= 'Z') c1 += 32; // poor man's tolower 7374 if (c0 != c1) return false; 7375 } 7376 return true; 7377 } 7378 7379 if (ignoreModsOut !is null) *ignoreModsOut = false; 7380 if (updown !is null) *updown = -1; 7381 KeyEvent res; 7382 res.key = cast(Key)0; // just in case 7383 const(char)[] tk, tkn; // last token 7384 bool allowEmascStyle = true; 7385 bool ignoreModifiers = false; 7386 tokenloop: for (;;) { 7387 tk = tkn; 7388 tkn = getToken(); 7389 //k8: yay, i took "Bloody Mess" trait from Fallout! 7390 if (tkn.length != 0 && tk.length == 0) { tk = tkn; continue tokenloop; } 7391 if (tkn.length == 0 && tk.length == 0) break; // no more tokens 7392 if (allowEmascStyle && tkn.length != 0) { 7393 if (tk.length == 1) { 7394 char mdc = tk[0]; 7395 if (mdc >= 'a' && mdc <= 'z') mdc -= 32; // poor man's toupper() 7396 if (mdc == 'C' && (res.modifierState&ModifierState.ctrl) == 0) {res.modifierState |= ModifierState.ctrl; continue tokenloop; } 7397 if (mdc == 'M' && (res.modifierState&ModifierState.alt) == 0) { res.modifierState |= ModifierState.alt; continue tokenloop; } 7398 if (mdc == 'H' && (res.modifierState&ModifierState.windows) == 0) { res.modifierState |= ModifierState.windows; continue tokenloop; } 7399 if (mdc == 'S' && (res.modifierState&ModifierState.shift) == 0) { res.modifierState |= ModifierState.shift; continue tokenloop; } 7400 if (mdc == '*') { ignoreModifiers = true; continue tokenloop; } 7401 if (mdc == 'U' || mdc == 'R') { if (updown !is null) *updown = 0; continue tokenloop; } 7402 if (mdc == 'D' || mdc == 'P') { if (updown !is null) *updown = 1; continue tokenloop; } 7403 } 7404 } 7405 allowEmascStyle = false; 7406 if (strEquCI(tk, "Ctrl")) { res.modifierState |= ModifierState.ctrl; continue tokenloop; } 7407 if (strEquCI(tk, "Alt")) { res.modifierState |= ModifierState.alt; continue tokenloop; } 7408 if (strEquCI(tk, "Win") || strEquCI(tk, "Windows")) { res.modifierState |= ModifierState.windows; continue tokenloop; } 7409 if (strEquCI(tk, "Shift")) { res.modifierState |= ModifierState.shift; continue tokenloop; } 7410 if (strEquCI(tk, "Release")) { if (updown !is null) *updown = 0; continue tokenloop; } 7411 if (strEquCI(tk, "Press")) { if (updown !is null) *updown = 1; continue tokenloop; } 7412 if (tk == "*") { ignoreModifiers = true; continue tokenloop; } 7413 if (tk.length == 0) continue; 7414 // try key name 7415 if (res.key == 0) { 7416 // little hack 7417 if (tk.length == 1 && tk[0] >= '0' && tk[0] <= '9') { 7418 final switch (tk[0]) { 7419 case '0': tk = "N0"; break; 7420 case '1': tk = "N1"; break; 7421 case '2': tk = "N2"; break; 7422 case '3': tk = "N3"; break; 7423 case '4': tk = "N4"; break; 7424 case '5': tk = "N5"; break; 7425 case '6': tk = "N6"; break; 7426 case '7': tk = "N7"; break; 7427 case '8': tk = "N8"; break; 7428 case '9': tk = "N9"; break; 7429 } 7430 } 7431 foreach (string kn; __traits(allMembers, Key)) { 7432 if (strEquCI(tk, kn)) { res.key = __traits(getMember, Key, kn); continue tokenloop; } 7433 } 7434 } 7435 // unknown or duplicate key name, get out of here 7436 break; 7437 } 7438 if (ignoreModsOut !is null) *ignoreModsOut = ignoreModifiers; 7439 return res; // something 7440 } 7441 7442 bool opEquals() (const(char)[] name) const nothrow @trusted @nogc { 7443 enum modmask = (ModifierState.ctrl|ModifierState.alt|ModifierState.shift|ModifierState.windows); 7444 void doModKey (ref uint mask, ref Key kk, Key k, ModifierState mst) { 7445 if (kk == k) { mask |= mst; kk = cast(Key)0; } 7446 } 7447 bool ignoreMods; 7448 int updown; 7449 auto ke = KeyEvent.parse(name, &ignoreMods, &updown); 7450 if ((updown == 0 && this.pressed) || (updown == 1 && !this.pressed)) return false; 7451 if (this.key != ke.key) { 7452 // things like "ctrl+alt" are complicated 7453 uint tkm = this.modifierState&modmask; 7454 uint kkm = ke.modifierState&modmask; 7455 Key tk = this.key; 7456 // ke 7457 doModKey(kkm, ke.key, Key.Ctrl, ModifierState.ctrl); 7458 doModKey(kkm, ke.key, Key.Alt, ModifierState.alt); 7459 doModKey(kkm, ke.key, Key.Windows, ModifierState.windows); 7460 doModKey(kkm, ke.key, Key.Shift, ModifierState.shift); 7461 // this 7462 doModKey(tkm, tk, Key.Ctrl, ModifierState.ctrl); 7463 doModKey(tkm, tk, Key.Alt, ModifierState.alt); 7464 doModKey(tkm, tk, Key.Windows, ModifierState.windows); 7465 doModKey(tkm, tk, Key.Shift, ModifierState.shift); 7466 return (tk == ke.key && tkm == kkm); 7467 } 7468 return (ignoreMods || ((this.modifierState&modmask) == (ke.modifierState&modmask))); 7469 } 7470 } 7471 7472 /// Sets the application name. 7473 @property string ApplicationName(string name) { 7474 return _applicationName = name; 7475 } 7476 7477 string _applicationName; 7478 7479 /// ditto 7480 @property string ApplicationName() { 7481 if(_applicationName is null) { 7482 import core.runtime; 7483 return Runtime.args[0]; 7484 } 7485 return _applicationName; 7486 } 7487 7488 7489 /// Type of a [MouseEvent]. 7490 enum MouseEventType : int { 7491 motion = 0, /// The mouse moved inside the window 7492 buttonPressed = 1, /// A mouse button was pressed or the wheel was spun 7493 buttonReleased = 2, /// A mouse button was released 7494 } 7495 7496 // FIXME: mouse move should be distinct from presses+releases, so we can avoid subscribing to those events in X unnecessarily 7497 /++ 7498 Listen for this on your event listeners if you are interested in mouse action. 7499 7500 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. 7501 7502 Examples: 7503 7504 This will draw boxes on the window with the mouse as you hold the left button. 7505 --- 7506 import arsd.simpledisplay; 7507 7508 void main() { 7509 auto window = new SimpleWindow(); 7510 7511 window.eventLoop(0, 7512 (MouseEvent ev) { 7513 if(ev.modifierState & ModifierState.leftButtonDown) { 7514 auto painter = window.draw(); 7515 painter.fillColor = Color.red; 7516 painter.outlineColor = Color.black; 7517 painter.drawRectangle(Point(ev.x / 16 * 16, ev.y / 16 * 16), 16, 16); 7518 } 7519 } 7520 ); 7521 } 7522 --- 7523 +/ 7524 struct MouseEvent { 7525 MouseEventType type; /// movement, press, release, double click. See [MouseEventType] 7526 7527 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. 7528 int y; /// Current Y position of the cursor when the event fired. 7529 7530 int dx; /// Change in X position since last report 7531 int dy; /// Change in Y position since last report 7532 7533 MouseButton button; /// See [MouseButton] 7534 int modifierState; /// See [ModifierState] 7535 7536 version(X11) 7537 private Time timestamp; 7538 7539 /// Returns a linear representation of mouse button, 7540 /// for use with static arrays. Guaranteed to be >= 0 && <= 15 7541 /// 7542 /// Its implementation is based on range-limiting `core.bitop.bsf(button) + 1`. 7543 @property ubyte buttonLinear() const { 7544 import core.bitop; 7545 if(button == 0) 7546 return 0; 7547 return (bsf(button) + 1) & 0b1111; 7548 } 7549 7550 bool doubleClick; /// was it a double click? Only set on type == [MouseEventType.buttonPressed] 7551 7552 SimpleWindow window; /// The window in which the event happened. 7553 7554 Point globalCoordinates() { 7555 Point p; 7556 if(window is null) 7557 throw new Exception("wtf"); 7558 static if(UsingSimpledisplayX11) { 7559 Window child; 7560 XTranslateCoordinates( 7561 XDisplayConnection.get, 7562 window.impl.window, 7563 RootWindow(XDisplayConnection.get, DefaultScreen(XDisplayConnection.get)), 7564 x, y, &p.x, &p.y, &child); 7565 return p; 7566 } else version(Windows) { 7567 POINT[1] points; 7568 points[0].x = x; 7569 points[0].y = y; 7570 MapWindowPoints( 7571 window.impl.hwnd, 7572 null, 7573 points.ptr, 7574 points.length 7575 ); 7576 p.x = points[0].x; 7577 p.y = points[0].y; 7578 7579 return p; 7580 } else version(OSXCocoa) { 7581 throw new NotYetImplementedException(); 7582 } else static assert(0); 7583 } 7584 7585 bool opEquals() (const(char)[] str) pure nothrow @trusted @nogc { return equStr(this, str); } 7586 7587 /** 7588 can contain emacs-like modifier prefix 7589 case-insensitive names: 7590 lmbX/leftX 7591 rmbX/rightX 7592 mmbX/middleX 7593 wheelX 7594 motion (no prefix allowed) 7595 'X' is either "up" or "down" (or "-up"/"-down"); if omited, means "down" 7596 */ 7597 static bool equStr() (scope auto ref const MouseEvent event, const(char)[] str) pure nothrow @trusted @nogc { 7598 if (str.length == 0) return false; // just in case 7599 debug(arsd_mevent_strcmp) { import iv.cmdcon; conwriteln("str=<", str, ">"); } 7600 enum Flag : uint { Up = 0x8000_0000U, Down = 0x4000_0000U, Any = 0x1000_0000U } 7601 auto anchor = str; 7602 uint mods = 0; // uint.max == any 7603 // interesting bits in kmod 7604 uint kmodmask = 7605 ModifierState.shift| 7606 ModifierState.ctrl| 7607 ModifierState.alt| 7608 ModifierState.windows| 7609 ModifierState.leftButtonDown| 7610 ModifierState.middleButtonDown| 7611 ModifierState.rightButtonDown| 7612 0; 7613 uint lastButt = uint.max; // otherwise, bit 31 means "down" 7614 bool wasButtons = false; 7615 while (str.length) { 7616 if (str.ptr[0] <= ' ') { 7617 while (str.length && str.ptr[0] <= ' ') str = str[1..$]; 7618 continue; 7619 } 7620 // one-letter modifier? 7621 if (str.length >= 2 && str.ptr[1] == '-') { 7622 switch (str.ptr[0]) { 7623 case '*': // "any" modifier (cannot be undone) 7624 mods = mods.max; 7625 break; 7626 case 'C': case 'c': // emacs "ctrl" 7627 if (mods != mods.max) mods |= ModifierState.ctrl; 7628 break; 7629 case 'M': case 'm': // emacs "meta" 7630 if (mods != mods.max) mods |= ModifierState.alt; 7631 break; 7632 case 'S': case 's': // emacs "shift" 7633 if (mods != mods.max) mods |= ModifierState.shift; 7634 break; 7635 case 'H': case 'h': // emacs "hyper" (aka winkey) 7636 if (mods != mods.max) mods |= ModifierState.windows; 7637 break; 7638 default: 7639 return false; // unknown modifier 7640 } 7641 str = str[2..$]; 7642 continue; 7643 } 7644 // word 7645 char[16] buf = void; // locased 7646 auto wep = 0; 7647 while (str.length) { 7648 immutable char ch = str.ptr[0]; 7649 if (ch <= ' ' || ch == '-') break; 7650 str = str[1..$]; 7651 if (wep > buf.length) return false; // too long 7652 if (ch >= 'A' && ch <= 'Z') buf.ptr[wep++] = cast(char)(ch+32); // poor man tolower 7653 else if (ch >= 'a' && ch <= 'z') buf.ptr[wep++] = ch; 7654 else return false; // invalid char 7655 } 7656 if (wep == 0) return false; // just in case 7657 uint bnum; 7658 enum UpDown { None = -1, Up, Down, Any } 7659 auto updown = UpDown.None; // 0: up; 1: down 7660 switch (buf[0..wep]) { 7661 // left button 7662 case "lmbup": case "leftup": updown = UpDown.Up; goto case "lmb"; 7663 case "lmbdown": case "leftdown": updown = UpDown.Down; goto case "lmb"; 7664 case "lmbany": case "leftany": updown = UpDown.Any; goto case "lmb"; 7665 case "lmb": case "left": bnum = 0; break; 7666 // middle button 7667 case "mmbup": case "middleup": updown = UpDown.Up; goto case "mmb"; 7668 case "mmbdown": case "middledown": updown = UpDown.Down; goto case "mmb"; 7669 case "mmbany": case "middleany": updown = UpDown.Any; goto case "mmb"; 7670 case "mmb": case "middle": bnum = 1; break; 7671 // right button 7672 case "rmbup": case "rightup": updown = UpDown.Up; goto case "rmb"; 7673 case "rmbdown": case "rightdown": updown = UpDown.Down; goto case "rmb"; 7674 case "rmbany": case "rightany": updown = UpDown.Any; goto case "rmb"; 7675 case "rmb": case "right": bnum = 2; break; 7676 // wheel 7677 case "wheelup": updown = UpDown.Up; goto case "wheel"; 7678 case "wheeldown": updown = UpDown.Down; goto case "wheel"; 7679 case "wheelany": updown = UpDown.Any; goto case "wheel"; 7680 case "wheel": bnum = 3; break; 7681 // motion 7682 case "motion": bnum = 7; break; 7683 // unknown 7684 default: return false; 7685 } 7686 debug(arsd_mevent_strcmp) { import iv.cmdcon; conprintfln(" 0: mods=0x%08x; bnum=%u; updown=%s [%s]", mods, bnum, updown, str); } 7687 // parse possible "-up" or "-down" 7688 if (updown == UpDown.None && bnum < 7 && str.length > 0 && str.ptr[0] == '-') { 7689 wep = 0; 7690 foreach (immutable idx, immutable char ch; str[1..$]) { 7691 if (ch <= ' ' || ch == '-') break; 7692 assert(idx == wep); // for now; trick 7693 if (wep > buf.length) { wep = 0; break; } // too long 7694 if (ch >= 'A' && ch <= 'Z') buf.ptr[wep++] = cast(char)(ch+32); // poor man tolower 7695 else if (ch >= 'a' && ch <= 'z') buf.ptr[wep++] = ch; 7696 else { wep = 0; break; } // invalid char 7697 } 7698 if (wep == 2 && buf[0..wep] == "up") updown = UpDown.Up; 7699 else if (wep == 4 && buf[0..wep] == "down") updown = UpDown.Down; 7700 else if (wep == 3 && buf[0..wep] == "any") updown = UpDown.Any; 7701 // remove parsed part 7702 if (updown != UpDown.None) str = str[wep+1..$]; 7703 } 7704 if (updown == UpDown.None) { 7705 updown = UpDown.Down; 7706 } 7707 wasButtons = wasButtons || (bnum <= 2); 7708 //assert(updown != UpDown.None); 7709 debug(arsd_mevent_strcmp) { import iv.cmdcon; conprintfln(" 1: mods=0x%08x; bnum=%u; updown=%s [%s]", mods, bnum, updown, str); } 7710 // if we have a previous button, it goes to modifiers (unless it is a wheel or motion) 7711 if (lastButt != lastButt.max) { 7712 if ((lastButt&0xff) >= 3) return false; // wheel or motion 7713 if (mods != mods.max) { 7714 uint butbit = 0; 7715 final switch (lastButt&0x03) { 7716 case 0: butbit = ModifierState.leftButtonDown; break; 7717 case 1: butbit = ModifierState.middleButtonDown; break; 7718 case 2: butbit = ModifierState.rightButtonDown; break; 7719 } 7720 if (lastButt&Flag.Down) mods |= butbit; 7721 else if (lastButt&Flag.Up) mods &= ~butbit; 7722 else if (lastButt&Flag.Any) kmodmask &= ~butbit; 7723 } 7724 } 7725 // remember last button 7726 lastButt = bnum|(updown == UpDown.Up ? Flag.Up : updown == UpDown.Any ? Flag.Any : Flag.Down); 7727 } 7728 // no button -- nothing to do 7729 if (lastButt == lastButt.max) return false; 7730 // done parsing, check if something's left 7731 foreach (immutable char ch; str) if (ch > ' ') return false; // oops 7732 // remove action button from mask 7733 if ((lastButt&0xff) < 3) { 7734 final switch (lastButt&0x03) { 7735 case 0: kmodmask &= ~cast(uint)ModifierState.leftButtonDown; break; 7736 case 1: kmodmask &= ~cast(uint)ModifierState.middleButtonDown; break; 7737 case 2: kmodmask &= ~cast(uint)ModifierState.rightButtonDown; break; 7738 } 7739 } 7740 // special case: "Motion" means "ignore buttons" 7741 if ((lastButt&0xff) == 7 && !wasButtons) { 7742 debug(arsd_mevent_strcmp) { import iv.cmdcon; conwriteln(" *: special motion"); } 7743 kmodmask &= ~cast(uint)(ModifierState.leftButtonDown|ModifierState.middleButtonDown|ModifierState.rightButtonDown); 7744 } 7745 uint kmod = event.modifierState&kmodmask; 7746 debug(arsd_mevent_strcmp) { import iv.cmdcon; conprintfln(" *: mods=0x%08x; lastButt=0x%08x; kmod=0x%08x; type=%s", mods, lastButt, kmod, event.type); } 7747 // check modifier state 7748 if (mods != mods.max) { 7749 if (kmod != mods) return false; 7750 } 7751 // now check type 7752 if ((lastButt&0xff) == 7) { 7753 // motion 7754 if (event.type != MouseEventType.motion) return false; 7755 } else if ((lastButt&0xff) == 3) { 7756 // wheel 7757 if (lastButt&Flag.Up) return (event.type == MouseEventType.buttonPressed && event.button == MouseButton.wheelUp); 7758 if (lastButt&Flag.Down) return (event.type == MouseEventType.buttonPressed && event.button == MouseButton.wheelDown); 7759 if (lastButt&Flag.Any) return (event.type == MouseEventType.buttonPressed && (event.button == MouseButton.wheelUp || event.button == MouseButton.wheelUp)); 7760 return false; 7761 } else { 7762 // buttons 7763 if (((lastButt&Flag.Down) != 0 && event.type != MouseEventType.buttonPressed) || 7764 ((lastButt&Flag.Up) != 0 && event.type != MouseEventType.buttonReleased)) 7765 { 7766 return false; 7767 } 7768 // button number 7769 switch (lastButt&0x03) { 7770 case 0: if (event.button != MouseButton.left) return false; break; 7771 case 1: if (event.button != MouseButton.middle) return false; break; 7772 case 2: if (event.button != MouseButton.right) return false; break; 7773 default: return false; 7774 } 7775 } 7776 return true; 7777 } 7778 } 7779 7780 version(arsd_mevent_strcmp_test) unittest { 7781 MouseEvent event; 7782 event.type = MouseEventType.buttonPressed; 7783 event.button = MouseButton.left; 7784 event.modifierState = ModifierState.ctrl; 7785 assert(event == "C-LMB"); 7786 assert(event != "C-LMBUP"); 7787 assert(event != "C-LMB-UP"); 7788 assert(event != "C-S-LMB"); 7789 assert(event == "*-LMB"); 7790 assert(event != "*-LMB-UP"); 7791 7792 event.type = MouseEventType.buttonReleased; 7793 assert(event != "C-LMB"); 7794 assert(event == "C-LMBUP"); 7795 assert(event == "C-LMB-UP"); 7796 assert(event != "C-S-LMB"); 7797 assert(event != "*-LMB"); 7798 assert(event == "*-LMB-UP"); 7799 7800 event.button = MouseButton.right; 7801 event.modifierState |= ModifierState.shift; 7802 event.type = MouseEventType.buttonPressed; 7803 assert(event != "C-LMB"); 7804 assert(event != "C-LMBUP"); 7805 assert(event != "C-LMB-UP"); 7806 assert(event != "C-S-LMB"); 7807 assert(event != "*-LMB"); 7808 assert(event != "*-LMB-UP"); 7809 7810 assert(event != "C-RMB"); 7811 assert(event != "C-RMBUP"); 7812 assert(event != "C-RMB-UP"); 7813 assert(event == "C-S-RMB"); 7814 assert(event == "*-RMB"); 7815 assert(event != "*-RMB-UP"); 7816 } 7817 7818 /// This gives a few more options to drawing lines and such 7819 struct Pen { 7820 Color color; /// the foreground color 7821 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. 7822 Style style; /// See [Style] 7823 /+ 7824 // From X.h 7825 7826 #define LineSolid 0 7827 #define LineOnOffDash 1 7828 #define LineDoubleDash 2 7829 LineDou- The full path of the line is drawn, but the 7830 bleDash even dashes are filled differently from the 7831 odd dashes (see fill-style) with CapButt 7832 style used where even and odd dashes meet. 7833 7834 7835 7836 /* capStyle */ 7837 7838 #define CapNotLast 0 7839 #define CapButt 1 7840 #define CapRound 2 7841 #define CapProjecting 3 7842 7843 /* joinStyle */ 7844 7845 #define JoinMiter 0 7846 #define JoinRound 1 7847 #define JoinBevel 2 7848 7849 /* fillStyle */ 7850 7851 #define FillSolid 0 7852 #define FillTiled 1 7853 #define FillStippled 2 7854 #define FillOpaqueStippled 3 7855 7856 7857 +/ 7858 /// Style of lines drawn 7859 enum Style { 7860 Solid, /// a solid line 7861 Dashed, /// a dashed line 7862 Dotted, /// a dotted line 7863 } 7864 } 7865 7866 7867 /++ 7868 Represents an in-memory image in the format that the GUI expects, but with its raw data available to your program. 7869 7870 7871 On Windows, this means a device-independent bitmap. On X11, it is an XImage. 7872 7873 $(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.) 7874 7875 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. 7876 7877 If you intend to draw an image to screen several times, you will want to convert it into a [Sprite]. 7878 7879 $(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. 7880 7881 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! 7882 7883 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!) 7884 7885 Please call `destroy(image);` when you are done with it. The easiest way to do this is with scope: 7886 7887 --- 7888 auto image = new Image(256, 256); 7889 scope(exit) destroy(image); 7890 --- 7891 7892 As long as you don't hold on to it outside the scope. 7893 7894 I might change it to be an owned pointer at some point in the future. 7895 7896 ) 7897 7898 Drawing pixels on the image may be simple, using the `opIndexAssign` function, but 7899 you can also often get a fair amount of speedup by getting the raw data format and 7900 writing some custom code. 7901 7902 FIXME INSERT EXAMPLES HERE 7903 7904 7905 +/ 7906 final class Image { 7907 /// 7908 this(int width, int height, bool forcexshm=false, bool enableAlpha = false) { 7909 this.width = width; 7910 this.height = height; 7911 this.enableAlpha = enableAlpha; 7912 7913 impl.createImage(width, height, forcexshm, enableAlpha); 7914 } 7915 7916 /// 7917 this(Size size, bool forcexshm=false, bool enableAlpha = false) { 7918 this(size.width, size.height, forcexshm, enableAlpha); 7919 } 7920 7921 private bool suppressDestruction; 7922 7923 version(X11) 7924 this(XImage* handle) { 7925 this.handle = handle; 7926 this.rawData = cast(ubyte*) handle.data; 7927 this.width = handle.width; 7928 this.height = handle.height; 7929 this.enableAlpha = handle.depth == 32; 7930 suppressDestruction = true; 7931 } 7932 7933 ~this() { 7934 if(suppressDestruction) return; 7935 impl.dispose(); 7936 } 7937 7938 // these numbers are used for working with rawData itself, skipping putPixel and getPixel 7939 /// if you do the math yourself you might be able to optimize it. Call these functions only once and cache the value. 7940 pure const @system nothrow { 7941 /* 7942 To use these to draw a blue rectangle with size WxH at position X,Y... 7943 7944 // make certain that it will fit before we proceed 7945 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! 7946 7947 // gather all the values you'll need up front. These can be kept until the image changes size if you want 7948 // (though calculating them isn't really that expensive). 7949 auto nextLineAdjustment = img.adjustmentForNextLine(); 7950 auto offR = img.redByteOffset(); 7951 auto offB = img.blueByteOffset(); 7952 auto offG = img.greenByteOffset(); 7953 auto bpp = img.bytesPerPixel(); 7954 7955 auto data = img.getDataPointer(); 7956 7957 // figure out the starting byte offset 7958 auto offset = img.offsetForTopLeftPixel() + nextLineAdjustment*Y + bpp * X; 7959 7960 auto startOfLine = data + offset; // get our pointer lined up on the first pixel 7961 7962 // and now our drawing loop for the rectangle 7963 foreach(y; 0 .. H) { 7964 auto data = startOfLine; // we keep the start of line separately so moving to the next line is simple and portable 7965 foreach(x; 0 .. W) { 7966 // write our color 7967 data[offR] = 0; 7968 data[offG] = 0; 7969 data[offB] = 255; 7970 7971 data += bpp; // moving to the next pixel is just an addition... 7972 } 7973 startOfLine += nextLineAdjustment; 7974 } 7975 7976 7977 As you can see, the loop itself was very simple thanks to the calculations being moved outside. 7978 7979 FIXME: I wonder if I can make the pixel formats consistently 32 bit across platforms, so the color offsets 7980 can be made into a bitmask or something so we can write them as *uint... 7981 */ 7982 7983 /// 7984 int offsetForTopLeftPixel() { 7985 version(X11) { 7986 return 0; 7987 } else version(Windows) { 7988 if(enableAlpha) { 7989 return (width * 4) * (height - 1); 7990 } else { 7991 return (((cast(int) width * 3 + 3) / 4) * 4) * (height - 1); 7992 } 7993 } else version(OSXCocoa) { 7994 return 0 ; //throw new NotYetImplementedException(); 7995 } else static assert(0, "fill in this info for other OSes"); 7996 } 7997 7998 /// 7999 int offsetForPixel(int x, int y) { 8000 version(X11) { 8001 auto offset = (y * width + x) * 4; 8002 return offset; 8003 } else version(Windows) { 8004 if(enableAlpha) { 8005 auto itemsPerLine = width * 4; 8006 // remember, bmps are upside down 8007 auto offset = itemsPerLine * (height - y - 1) + x * 4; 8008 return offset; 8009 } else { 8010 auto itemsPerLine = ((cast(int) width * 3 + 3) / 4) * 4; 8011 // remember, bmps are upside down 8012 auto offset = itemsPerLine * (height - y - 1) + x * 3; 8013 return offset; 8014 } 8015 } else version(OSXCocoa) { 8016 return 0 ; //throw new NotYetImplementedException(); 8017 } else static assert(0, "fill in this info for other OSes"); 8018 } 8019 8020 /// 8021 int adjustmentForNextLine() { 8022 version(X11) { 8023 return width * 4; 8024 } else version(Windows) { 8025 // windows bmps are upside down, so the adjustment is actually negative 8026 if(enableAlpha) 8027 return - (cast(int) width * 4); 8028 else 8029 return -((cast(int) width * 3 + 3) / 4) * 4; 8030 } else version(OSXCocoa) { 8031 return 0 ; //throw new NotYetImplementedException(); 8032 } else static assert(0, "fill in this info for other OSes"); 8033 } 8034 8035 /// once you have the position of a pixel, use these to get to the proper color 8036 int redByteOffset() { 8037 version(X11) { 8038 return 2; 8039 } else version(Windows) { 8040 return 2; 8041 } else version(OSXCocoa) { 8042 return 0 ; //throw new NotYetImplementedException(); 8043 } else static assert(0, "fill in this info for other OSes"); 8044 } 8045 8046 /// 8047 int greenByteOffset() { 8048 version(X11) { 8049 return 1; 8050 } else version(Windows) { 8051 return 1; 8052 } else version(OSXCocoa) { 8053 return 0 ; //throw new NotYetImplementedException(); 8054 } else static assert(0, "fill in this info for other OSes"); 8055 } 8056 8057 /// 8058 int blueByteOffset() { 8059 version(X11) { 8060 return 0; 8061 } else version(Windows) { 8062 return 0; 8063 } else version(OSXCocoa) { 8064 return 0 ; //throw new NotYetImplementedException(); 8065 } else static assert(0, "fill in this info for other OSes"); 8066 } 8067 8068 /// Only valid if [enableAlpha] is true 8069 int alphaByteOffset() { 8070 version(X11) { 8071 return 3; 8072 } else version(Windows) { 8073 return 3; 8074 } else version(OSXCocoa) { 8075 return 3; //throw new NotYetImplementedException(); 8076 } else static assert(0, "fill in this info for other OSes"); 8077 } 8078 } 8079 8080 /// 8081 final void putPixel(int x, int y, Color c) { 8082 if(x < 0 || x >= width) 8083 return; 8084 if(y < 0 || y >= height) 8085 return; 8086 8087 impl.setPixel(x, y, c); 8088 } 8089 8090 /// 8091 final Color getPixel(int x, int y) { 8092 if(x < 0 || x >= width) 8093 return Color.transparent; 8094 if(y < 0 || y >= height) 8095 return Color.transparent; 8096 8097 version(OSXCocoa) throw new NotYetImplementedException(); else 8098 return impl.getPixel(x, y); 8099 } 8100 8101 /// 8102 final void opIndexAssign(Color c, int x, int y) { 8103 putPixel(x, y, c); 8104 } 8105 8106 /// 8107 TrueColorImage toTrueColorImage() { 8108 auto tci = new TrueColorImage(width, height); 8109 convertToRgbaBytes(tci.imageData.bytes); 8110 return tci; 8111 } 8112 8113 /// 8114 static Image fromMemoryImage(MemoryImage i, bool enableAlpha = false) { 8115 auto tci = i.getAsTrueColorImage(); 8116 auto img = new Image(tci.width, tci.height, false, enableAlpha); 8117 img.setRgbaBytes(tci.imageData.bytes); 8118 return img; 8119 } 8120 8121 /// this is here for interop with arsd.image. where can be a TrueColorImage's data member 8122 /// if you pass in a buffer, it will put it right there. length must be width*height*4 already 8123 /// if you pass null, it will allocate a new one. 8124 ubyte[] getRgbaBytes(ubyte[] where = null) { 8125 if(where is null) 8126 where = new ubyte[this.width*this.height*4]; 8127 convertToRgbaBytes(where); 8128 return where; 8129 } 8130 8131 /// this is here for interop with arsd.image. from can be a TrueColorImage's data member 8132 void setRgbaBytes(in ubyte[] from ) { 8133 assert(from.length == this.width * this.height * 4); 8134 setFromRgbaBytes(from); 8135 } 8136 8137 // FIXME: make properly cross platform by getting rgba right 8138 8139 /// warning: this is not portable across platforms because the data format can change 8140 ubyte* getDataPointer() { 8141 return impl.rawData; 8142 } 8143 8144 /// for use with getDataPointer 8145 final int bytesPerLine() const pure @safe nothrow { 8146 version(Windows) 8147 return enableAlpha ? (width * 4) : (((cast(int) width * 3 + 3) / 4) * 4); 8148 else version(X11) 8149 return 4 * width; 8150 else version(OSXCocoa) 8151 return 4 * width; 8152 else static assert(0); 8153 } 8154 8155 /// for use with getDataPointer 8156 final int bytesPerPixel() const pure @safe nothrow { 8157 version(Windows) 8158 return enableAlpha ? 4 : 3; 8159 else version(X11) 8160 return 4; 8161 else version(OSXCocoa) 8162 return 4; 8163 else static assert(0); 8164 } 8165 8166 /// 8167 immutable int width; 8168 8169 /// 8170 immutable int height; 8171 8172 /// 8173 immutable bool enableAlpha; 8174 //private: 8175 mixin NativeImageImplementation!() impl; 8176 } 8177 8178 /++ 8179 A convenience function to pop up a window displaying the image. 8180 If you pass a win, it will draw the image in it. Otherwise, it will 8181 create a window with the size of the image and run its event loop, closing 8182 when a key is pressed. 8183 8184 History: 8185 `BlockingMode` parameter added on December 8, 2021. Previously, it would 8186 always block until the application quit which could cause bizarre behavior 8187 inside a more complex application. Now, the default is to block until 8188 this window closes if it is the only event loop running, and otherwise, 8189 not to block at all and just pop up the display window asynchronously. 8190 +/ 8191 void displayImage(Image image, SimpleWindow win = null, BlockingMode bm = BlockingMode.untilWindowCloses | BlockingMode.onlyIfNotNested) { 8192 if(win is null) { 8193 win = new SimpleWindow(image); 8194 { 8195 auto p = win.draw; 8196 p.drawImage(Point(0, 0), image); 8197 } 8198 win.eventLoopWithBlockingMode( 8199 bm, 0, 8200 (KeyEvent ev) { 8201 if (ev.pressed && (ev.key == Key.Escape || ev.key == Key.Space)) win.close(); 8202 } ); 8203 } else { 8204 win.image = image; 8205 } 8206 } 8207 8208 enum FontWeight : int { 8209 dontcare = 0, 8210 thin = 100, 8211 extralight = 200, 8212 light = 300, 8213 regular = 400, 8214 medium = 500, 8215 semibold = 600, 8216 bold = 700, 8217 extrabold = 800, 8218 heavy = 900 8219 } 8220 8221 /++ 8222 Interface with the common functionality for font measurements between [OperatingSystemFont] and [DrawableFont]. 8223 8224 History: 8225 Added October 24, 2022. The methods were already on [OperatingSystemFont] before that. 8226 +/ 8227 interface MeasurableFont { 8228 /++ 8229 Returns true if it is a monospace font, meaning each of the 8230 glyphs (at least the ascii characters) have matching width 8231 and no kerning, so you can determine the display width of some 8232 strings by simply multiplying the string width by [averageWidth]. 8233 8234 (Please note that multiply doesn't $(I actually) work in general, 8235 consider characters like tab and newline, but it does sometimes.) 8236 +/ 8237 bool isMonospace(); 8238 8239 /++ 8240 The average width of glyphs in the font, traditionally equal to the 8241 width of the lowercase x. Can be used to estimate bounding boxes, 8242 especially if the font [isMonospace]. 8243 8244 Given in pixels. 8245 +/ 8246 int averageWidth(); 8247 /++ 8248 The height of the bounding box of a line. 8249 +/ 8250 int height(); 8251 /++ 8252 The maximum ascent of a glyph above the baseline. 8253 8254 Given in pixels. 8255 +/ 8256 int ascent(); 8257 /++ 8258 The maximum descent of a glyph below the baseline. For example, how low the g might go. 8259 8260 Given in pixels. 8261 +/ 8262 int descent(); 8263 /++ 8264 The display width of the given string, and if you provide a window, it will use it to 8265 make the pixel count on screen more accurate too, but this shouldn't generally be necessary. 8266 8267 Given in pixels. 8268 +/ 8269 int stringWidth(scope const(char)[] s, SimpleWindow window = null); 8270 8271 } 8272 8273 // FIXME: i need a font cache and it needs to handle disconnects. 8274 8275 /++ 8276 Represents a font loaded off the operating system or the X server. 8277 8278 8279 While the api here is unified cross platform, the fonts are not necessarily 8280 available, even across machines of the same platform, so be sure to always check 8281 for null (using [isNull]) and have a fallback plan. 8282 8283 When you have a font you like, use [ScreenPainter.setFont] to load it for drawing. 8284 8285 Worst case, a null font will automatically fall back to the default font loaded 8286 for your system. 8287 +/ 8288 class OperatingSystemFont : MeasurableFont { 8289 // FIXME: when the X Connection is lost, these need to be invalidated! 8290 // that means I need to store the original stuff again to reconstruct it too. 8291 8292 version(X11) { 8293 XFontStruct* font; 8294 XFontSet fontset; 8295 8296 version(with_xft) { 8297 XftFont* xftFont; 8298 bool isXft; 8299 } 8300 } else version(Windows) { 8301 HFONT font; 8302 int width_; 8303 int height_; 8304 } else version(OSXCocoa) { 8305 // FIXME 8306 } else static assert(0); 8307 8308 /++ 8309 Constructs the class and immediately calls [load]. 8310 +/ 8311 this(string name, int size = 0, FontWeight weight = FontWeight.dontcare, bool italic = false) { 8312 load(name, size, weight, italic); 8313 } 8314 8315 /++ 8316 Constructs the object, but does nothing. Call one of [load] or [loadDefault] to populate the object. 8317 8318 You can also call the platform-specific [loadXft], [loadCoreX], or [loadWin32] functions if appropriate for you. 8319 8320 History: 8321 Added January 24, 2021. 8322 +/ 8323 this() { 8324 // this space intentionally left blank 8325 } 8326 8327 /++ 8328 Loads specifically with the Xft library - a freetype font from a fontconfig string. 8329 8330 History: 8331 Added November 13, 2020. 8332 +/ 8333 version(with_xft) 8334 bool loadXft(string name, int size = 0, FontWeight weight = FontWeight.dontcare, bool italic = false) { 8335 unload(); 8336 8337 if(!XftLibrary.attempted) { 8338 XftLibrary.loadDynamicLibrary(); 8339 } 8340 8341 if(!XftLibrary.loadSuccessful) 8342 return false; 8343 8344 auto display = XDisplayConnection.get; 8345 8346 char[256] nameBuffer = void; 8347 int nbp = 0; 8348 8349 void add(in char[] a) { 8350 nameBuffer[nbp .. nbp + a.length] = a[]; 8351 nbp += a.length; 8352 } 8353 add(name); 8354 8355 if(size) { 8356 add(":size="); 8357 add(toInternal!string(size)); 8358 } 8359 if(weight != FontWeight.dontcare) { 8360 add(":weight="); 8361 add(weightToString(weight)); 8362 } 8363 if(italic) 8364 add(":slant=100"); 8365 8366 nameBuffer[nbp] = 0; 8367 8368 this.xftFont = XftFontOpenName( 8369 display, 8370 DefaultScreen(display), 8371 nameBuffer.ptr 8372 ); 8373 8374 this.isXft = true; 8375 8376 if(xftFont !is null) { 8377 isMonospace_ = stringWidth("x") == stringWidth("M"); 8378 ascent_ = xftFont.ascent; 8379 descent_ = xftFont.descent; 8380 } 8381 8382 return !isNull(); 8383 } 8384 8385 /++ 8386 Lists available fonts from the system that match the given pattern, finding names that are suitable for passing to [OperatingSystemFont]'s constructor. 8387 8388 8389 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. 8390 8391 If `pattern` is null, it returns all available font families. 8392 8393 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. 8394 8395 The format of the pattern is platform-specific. 8396 8397 History: 8398 Added May 1, 2021 (dub v9.5) 8399 +/ 8400 static void listFonts(string pattern, bool delegate(in char[] name) handler) { 8401 version(Windows) { 8402 auto hdc = GetDC(null); 8403 scope(exit) ReleaseDC(null, hdc); 8404 LOGFONT logfont; 8405 static extern(Windows) int proc(const LOGFONT* lf, const TEXTMETRIC* tm, DWORD type, LPARAM p) { 8406 auto localHandler = *(cast(typeof(handler)*) p); 8407 return localHandler(lf.lfFaceName[].sliceCString) ? 1 : 0; 8408 } 8409 EnumFontFamiliesEx(hdc, &logfont, &proc, cast(LPARAM) &handler, 0); 8410 } else version(X11) { 8411 //import core.stdc.stdio; 8412 bool done = false; 8413 version(with_xft) { 8414 if(!XftLibrary.attempted) { 8415 XftLibrary.loadDynamicLibrary(); 8416 } 8417 8418 if(!XftLibrary.loadSuccessful) 8419 goto skipXft; 8420 8421 if(!FontConfigLibrary.attempted) 8422 FontConfigLibrary.loadDynamicLibrary(); 8423 if(!FontConfigLibrary.loadSuccessful) 8424 goto skipXft; 8425 8426 { 8427 auto got = XftListFonts(XDisplayConnection.get, 0, null, "family".ptr, "style".ptr, null); 8428 if(got is null) 8429 goto skipXft; 8430 scope(exit) FcFontSetDestroy(got); 8431 8432 auto fontPatterns = got.fonts[0 .. got.nfont]; 8433 foreach(candidate; fontPatterns) { 8434 char* where, whereStyle; 8435 8436 char* pmg = FcNameUnparse(candidate); 8437 8438 //FcPatternGetString(candidate, "family", 0, &where); 8439 //FcPatternGetString(candidate, "style", 0, &whereStyle); 8440 //if(where && whereStyle) { 8441 if(pmg) { 8442 if(!handler(pmg.sliceCString)) 8443 return; 8444 //printf("%s || %s %s\n", pmg, where, whereStyle); 8445 } 8446 } 8447 } 8448 } 8449 8450 skipXft: 8451 8452 if(pattern is null) 8453 pattern = "*"; 8454 8455 int count; 8456 auto coreFontsRaw = XListFonts(XDisplayConnection.get, pattern.toStringz, 10000 /* max return */, &count); 8457 scope(exit) XFreeFontNames(coreFontsRaw); 8458 8459 auto coreFonts = coreFontsRaw[0 .. count]; 8460 8461 foreach(font; coreFonts) { 8462 char[128] tmp; 8463 tmp[0 ..5] = "core:"; 8464 auto cf = font.sliceCString; 8465 if(5 + cf.length > tmp.length) 8466 assert(0, "a font name was too long, sorry i didn't bother implementing a fallback"); 8467 tmp[5 .. 5 + cf.length] = cf; 8468 if(!handler(tmp[0 .. 5 + cf.length])) 8469 return; 8470 } 8471 } 8472 } 8473 8474 /++ 8475 Returns the raw content of the ttf file, if possible. This allows you to use OperatingSystemFont 8476 to look up fonts that you then pass to things like [arsd.ttf.OpenGlLimitedFont] or [arsd.nanovega]. 8477 8478 Returns null if impossible. It is impossible if the loaded font is not a local TTF file or if the 8479 underlying system doesn't support returning the raw bytes. 8480 8481 History: 8482 Added September 10, 2021 (dub v10.3) 8483 +/ 8484 ubyte[] getTtfBytes() { 8485 if(isNull) 8486 return null; 8487 8488 version(Windows) { 8489 auto dc = GetDC(null); 8490 auto orig = SelectObject(dc, font); 8491 8492 scope(exit) { 8493 SelectObject(dc, orig); 8494 ReleaseDC(null, dc); 8495 } 8496 8497 auto res = GetFontData(dc, 0 /* whole file */, 0 /* offset */, null, 0); 8498 if(res == GDI_ERROR) 8499 return null; 8500 8501 ubyte[] buffer = new ubyte[](res); 8502 res = GetFontData(dc, 0 /* whole file */, 0 /* offset */, buffer.ptr, cast(DWORD) buffer.length); 8503 if(res == GDI_ERROR) 8504 return null; // wtf really tbh 8505 8506 return buffer; 8507 } else version(with_xft) { 8508 if(isXft && xftFont) { 8509 if(!FontConfigLibrary.attempted) 8510 FontConfigLibrary.loadDynamicLibrary(); 8511 if(!FontConfigLibrary.loadSuccessful) 8512 return null; 8513 8514 char* file; 8515 if (FcPatternGetString(xftFont.pattern, "file", 0, &file) == 0 /*FcResultMatch*/) { 8516 if (file !is null && file[0]) { 8517 import core.stdc.stdio; 8518 auto fp = fopen(file, "rb"); 8519 if(fp is null) 8520 return null; 8521 scope(exit) 8522 fclose(fp); 8523 fseek(fp, 0, SEEK_END); 8524 ubyte[] buffer = new ubyte[](ftell(fp)); 8525 fseek(fp, 0, SEEK_SET); 8526 8527 auto got = fread(buffer.ptr, 1, buffer.length, fp); 8528 if(got != buffer.length) 8529 return null; 8530 8531 return buffer; 8532 } 8533 } 8534 } 8535 return null; 8536 } 8537 } 8538 8539 // see also: XftLockFace(font) which gives a FT_Face. from /usr/include/X11/Xft/Xft.h line 352 8540 8541 private string weightToString(FontWeight weight) { 8542 with(FontWeight) 8543 final switch(weight) { 8544 case dontcare: return "*"; 8545 case thin: return "extralight"; 8546 case extralight: return "extralight"; 8547 case light: return "light"; 8548 case regular: return "regular"; 8549 case medium: return "medium"; 8550 case semibold: return "demibold"; 8551 case bold: return "bold"; 8552 case extrabold: return "demibold"; 8553 case heavy: return "black"; 8554 } 8555 } 8556 8557 /++ 8558 Loads specifically a Core X font - rendered on the X server without antialiasing. Best performance. 8559 8560 History: 8561 Added November 13, 2020. Before then, this code was integrated in the [load] function. 8562 +/ 8563 version(X11) 8564 bool loadCoreX(string name, int size = 0, FontWeight weight = FontWeight.dontcare, bool italic = false) { 8565 unload(); 8566 8567 string xfontstr; 8568 8569 if(name.length > 3 && name[0 .. 3] == "-*-") { 8570 // this is kinda a disgusting hack but if the user sends an exact 8571 // string I'd like to honor it... 8572 xfontstr = name; 8573 } else { 8574 string weightstr = weightToString(weight); 8575 string sizestr; 8576 if(size == 0) 8577 sizestr = "*"; 8578 else 8579 sizestr = toInternal!string(size); 8580 xfontstr = "-*-"~name~"-"~weightstr~"-"~(italic ? "i" : "r")~"-*-*-"~sizestr~"-*-*-*-*-*-*-*\0"; 8581 } 8582 8583 //import std.stdio; writeln(xfontstr); 8584 8585 auto display = XDisplayConnection.get; 8586 8587 font = XLoadQueryFont(display, xfontstr.ptr); 8588 if(font is null) 8589 return false; 8590 8591 char** lol; 8592 int lol2; 8593 char* lol3; 8594 fontset = XCreateFontSet(display, xfontstr.ptr, &lol, &lol2, &lol3); 8595 8596 prepareFontInfo(); 8597 8598 return !isNull(); 8599 } 8600 8601 version(X11) 8602 private void prepareFontInfo() { 8603 if(font !is null) { 8604 isMonospace_ = stringWidth("l") == stringWidth("M"); 8605 ascent_ = font.max_bounds.ascent; 8606 descent_ = font.max_bounds.descent; 8607 } 8608 } 8609 8610 /++ 8611 Loads a Windows font. You probably want to use [load] instead to be more generic. 8612 8613 History: 8614 Added November 13, 2020. Before then, this code was integrated in the [load] function. 8615 +/ 8616 version(Windows) 8617 bool loadWin32(string name, int size = 0, FontWeight weight = FontWeight.dontcare, bool italic = false, HDC hdc = null) { 8618 unload(); 8619 8620 WCharzBuffer buffer = WCharzBuffer(name); 8621 font = CreateFont(size, 0, 0, 0, cast(int) weight, italic, 0, 0, 0, 0, 0, 0, 0, buffer.ptr); 8622 8623 prepareFontInfo(hdc); 8624 8625 return !isNull(); 8626 } 8627 8628 version(Windows) 8629 void prepareFontInfo(HDC hdc = null) { 8630 if(font is null) 8631 return; 8632 8633 TEXTMETRIC tm; 8634 auto dc = hdc ? hdc : GetDC(null); 8635 auto orig = SelectObject(dc, font); 8636 GetTextMetrics(dc, &tm); 8637 SelectObject(dc, orig); 8638 if(hdc is null) 8639 ReleaseDC(null, dc); 8640 8641 width_ = tm.tmAveCharWidth; 8642 height_ = tm.tmHeight; 8643 ascent_ = tm.tmAscent; 8644 descent_ = tm.tmDescent; 8645 // 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. 8646 isMonospace_ = (tm.tmPitchAndFamily & TMPF_FIXED_PITCH) == 0; 8647 } 8648 8649 8650 /++ 8651 `name` is a font name, but it can also be a more complicated string parsed in an OS-specific way. 8652 8653 On X, you may prefix a name with `core:` to bypass the freetype engine causing this function to forward to [loadCoreX]. Otherwise, 8654 it calls [loadXft] if the library is available. If the library or font is not available on Xft, it falls back on [loadCoreX]. 8655 8656 On Windows, it forwards directly to [loadWin32]. 8657 8658 Params: 8659 name = font name. This is looked up by the operating system and may be interpreted differently across platforms or user machines and their preferences. 8660 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. 8661 weight = approximate boldness, results may vary. 8662 italic = try to get a slanted version of the given font. 8663 8664 History: 8665 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. 8666 +/ 8667 bool load(string name, int size = 0, FontWeight weight = FontWeight.dontcare, bool italic = false) { 8668 version(X11) { 8669 version(with_xft) { 8670 if(name.length > 5 && name[0 .. 5] == "core:") { 8671 goto core; 8672 } 8673 8674 if(loadXft(name, size, weight, italic)) 8675 return true; 8676 // if xft fails, fallback to core to avoid breaking 8677 // code that already depended on this. 8678 } 8679 8680 core: 8681 8682 if(name.length > 5 && name[0 .. 5] == "core:") { 8683 name = name[5 .. $]; 8684 } 8685 8686 return loadCoreX(name, size, weight, italic); 8687 } else version(Windows) { 8688 return loadWin32(name, size, weight, italic); 8689 } else version(OSXCocoa) { 8690 // FIXME 8691 return false; 8692 } else static assert(0); 8693 } 8694 8695 /// 8696 void unload() { 8697 if(isNull()) 8698 return; 8699 8700 version(X11) { 8701 auto display = XDisplayConnection.display; 8702 8703 if(display is null) 8704 return; 8705 8706 version(with_xft) { 8707 if(isXft) { 8708 if(xftFont) 8709 XftFontClose(display, xftFont); 8710 isXft = false; 8711 xftFont = null; 8712 return; 8713 } 8714 } 8715 8716 if(font && font !is ScreenPainterImplementation.defaultfont) 8717 XFreeFont(display, font); 8718 if(fontset && fontset !is ScreenPainterImplementation.defaultfontset) 8719 XFreeFontSet(display, fontset); 8720 8721 font = null; 8722 fontset = null; 8723 } else version(Windows) { 8724 DeleteObject(font); 8725 font = null; 8726 } else version(OSXCocoa) { 8727 // FIXME 8728 } else static assert(0); 8729 } 8730 8731 private bool isMonospace_; 8732 8733 /++ 8734 History: 8735 Added January 16, 2021 8736 +/ 8737 bool isMonospace() { 8738 return isMonospace_; 8739 } 8740 8741 /++ 8742 Returns the average width of the font, conventionally defined as the width of the lowercase 'x' character. 8743 8744 History: 8745 Added March 26, 2020 8746 Documented January 16, 2021 8747 +/ 8748 int averageWidth() { 8749 version(X11) { 8750 return stringWidth("x"); 8751 } else version(Windows) 8752 return width_; 8753 else assert(0); 8754 } 8755 8756 /++ 8757 Returns the width of the string as drawn on the specified window, or the default screen if the window is null. 8758 8759 History: 8760 Added January 16, 2021 8761 +/ 8762 int stringWidth(scope const(char)[] s, SimpleWindow window = null) { 8763 // FIXME: what about tab? 8764 if(isNull) 8765 return 0; 8766 8767 version(X11) { 8768 version(with_xft) 8769 if(isXft && xftFont !is null) { 8770 //return xftFont.max_advance_width; 8771 XGlyphInfo extents; 8772 XftTextExtentsUtf8(XDisplayConnection.get, xftFont, s.ptr, cast(int) s.length, &extents); 8773 //import std.stdio; writeln(extents); 8774 return extents.xOff; 8775 } 8776 if(font is null) 8777 return 0; 8778 else if(fontset) { 8779 XRectangle rect; 8780 Xutf8TextExtents(fontset, s.ptr, cast(int) s.length, null, &rect); 8781 8782 return rect.width; 8783 } else { 8784 return XTextWidth(font, s.ptr, cast(int) s.length); 8785 } 8786 } else version(Windows) { 8787 WCharzBuffer buffer = WCharzBuffer(s); 8788 8789 return stringWidth(buffer.slice, window); 8790 } 8791 else assert(0); 8792 } 8793 8794 version(Windows) 8795 /// ditto 8796 int stringWidth(scope const(wchar)[] s, SimpleWindow window = null) { 8797 if(isNull) 8798 return 0; 8799 version(Windows) { 8800 SIZE size; 8801 8802 prepareContext(window); 8803 scope(exit) releaseContext(); 8804 8805 GetTextExtentPoint32W(dc, s.ptr, cast(int) s.length, &size); 8806 8807 return size.cx; 8808 } else { 8809 // std.conv can do this easily but it is slow to import and i don't think it is worth it 8810 static assert(0, "not implemented yet"); 8811 //return stringWidth(s, window); 8812 } 8813 } 8814 8815 private { 8816 int prepRefcount; 8817 8818 version(Windows) { 8819 HDC dc; 8820 HANDLE orig; 8821 HWND hwnd; 8822 } 8823 } 8824 /++ 8825 [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. 8826 8827 History: 8828 Added January 23, 2021 8829 +/ 8830 void prepareContext(SimpleWindow window = null) { 8831 prepRefcount++; 8832 if(prepRefcount == 1) { 8833 version(Windows) { 8834 hwnd = window is null ? null : window.impl.hwnd; 8835 dc = GetDC(hwnd); 8836 orig = SelectObject(dc, font); 8837 } 8838 } 8839 } 8840 /// ditto 8841 void releaseContext() { 8842 prepRefcount--; 8843 if(prepRefcount == 0) { 8844 version(Windows) { 8845 SelectObject(dc, orig); 8846 ReleaseDC(hwnd, dc); 8847 hwnd = null; 8848 dc = null; 8849 orig = null; 8850 } 8851 } 8852 } 8853 8854 /+ 8855 FIXME: I think I need advance and kerning pair 8856 8857 int advance(dchar from, dchar to) { } // use dchar.init for first item in string 8858 +/ 8859 8860 /++ 8861 Returns the height of the font. 8862 8863 History: 8864 Added March 26, 2020 8865 Documented January 16, 2021 8866 +/ 8867 int height() { 8868 version(X11) { 8869 version(with_xft) 8870 if(isXft && xftFont !is null) { 8871 return xftFont.ascent + xftFont.descent; // i don't use height here because it doesn't include the baseline pixel 8872 } 8873 if(font is null) 8874 return 0; 8875 return font.max_bounds.ascent + font.max_bounds.descent; 8876 } else version(Windows) 8877 return height_; 8878 else assert(0); 8879 } 8880 8881 private int ascent_; 8882 private int descent_; 8883 8884 /++ 8885 Max ascent above the baseline. 8886 8887 History: 8888 Added January 22, 2021 8889 +/ 8890 int ascent() { 8891 return ascent_; 8892 } 8893 8894 /++ 8895 Max descent below the baseline. 8896 8897 History: 8898 Added January 22, 2021 8899 +/ 8900 int descent() { 8901 return descent_; 8902 } 8903 8904 /++ 8905 Loads the default font used by [ScreenPainter] if none others are loaded. 8906 8907 Returns: 8908 This method mutates the `this` object, but then returns `this` for 8909 easy chaining like: 8910 8911 --- 8912 auto font = foo.isNull ? foo : foo.loadDefault 8913 --- 8914 8915 History: 8916 Added previously, but left unimplemented until January 24, 2021. 8917 +/ 8918 OperatingSystemFont loadDefault() { 8919 unload(); 8920 8921 version(X11) { 8922 // another option would be https://tronche.com/gui/x/xlib/graphics/font-metrics/XQueryFont.html 8923 // but meh since sdpy does its own thing, this should be ok too 8924 8925 ScreenPainterImplementation.ensureDefaultFontLoaded(); 8926 this.font = ScreenPainterImplementation.defaultfont; 8927 this.fontset = ScreenPainterImplementation.defaultfontset; 8928 8929 prepareFontInfo(); 8930 } else version(Windows) { 8931 ScreenPainterImplementation.ensureDefaultFontLoaded(); 8932 this.font = ScreenPainterImplementation.defaultGuiFont; 8933 8934 prepareFontInfo(); 8935 } else throw new NotYetImplementedException(); 8936 8937 return this; 8938 } 8939 8940 /// 8941 bool isNull() { 8942 version(OSXCocoa) throw new NotYetImplementedException(); else { 8943 version(with_xft) 8944 if(isXft) 8945 return xftFont is null; 8946 return font is null; 8947 } 8948 } 8949 8950 /* Metrics */ 8951 /+ 8952 GetABCWidth 8953 GetKerningPairs 8954 8955 if I do it right, I can size it all here, and match 8956 what happens when I draw the full string with the OS functions. 8957 8958 subclasses might do the same thing while getting the glyphs on images 8959 struct GlyphInfo { 8960 int glyph; 8961 8962 size_t stringIdxStart; 8963 size_t stringIdxEnd; 8964 8965 Rectangle boundingBox; 8966 } 8967 GlyphInfo[] getCharBoxes() { 8968 // XftTextExtentsUtf8 8969 return null; 8970 8971 } 8972 +/ 8973 8974 ~this() { 8975 if(!thisIsGuiThread) return; // FIXME: leaks if multithreaded gc 8976 unload(); 8977 } 8978 } 8979 8980 version(Windows) 8981 private string sliceCString(const(wchar)[] w) { 8982 return makeUtf8StringFromWindowsString(cast(wchar*) w.ptr); 8983 } 8984 8985 private inout(char)[] sliceCString(inout(char)* s) { 8986 import core.stdc.string; 8987 auto len = strlen(s); 8988 return s[0 .. len]; 8989 } 8990 8991 /** 8992 The 2D drawing proxy. You acquire one of these with [SimpleWindow.draw] rather 8993 than constructing it directly. Then, it is reference counted so you can pass it 8994 at around and when the last ref goes out of scope, the buffered drawing activities 8995 are all carried out. 8996 8997 8998 Most functions use the outlineColor instead of taking a color themselves. 8999 ScreenPainter is reference counted and draws its buffer to the screen when its 9000 final reference goes out of scope. 9001 */ 9002 struct ScreenPainter { 9003 CapableOfBeingDrawnUpon window; 9004 this(CapableOfBeingDrawnUpon window, NativeWindowHandle handle, bool manualInvalidations) { 9005 this.window = window; 9006 if(window.closed) 9007 return; // null painter is now allowed so no need to throw anymore, this likely happens at the end of a program anyway 9008 //currentClipRectangle = arsd.color.Rectangle(0, 0, window.width, window.height); 9009 currentClipRectangle = arsd.color.Rectangle(short.min, short.min, short.max, short.max); 9010 if(window.activeScreenPainter !is null) { 9011 impl = window.activeScreenPainter; 9012 if(impl.referenceCount == 0) { 9013 impl.window = window; 9014 impl.create(handle); 9015 } 9016 impl.manualInvalidations = manualInvalidations; 9017 impl.referenceCount++; 9018 // writeln("refcount ++ ", impl.referenceCount); 9019 } else { 9020 impl = new ScreenPainterImplementation; 9021 impl.window = window; 9022 impl.create(handle); 9023 impl.referenceCount = 1; 9024 impl.manualInvalidations = manualInvalidations; 9025 window.activeScreenPainter = impl; 9026 //import std.stdio; writeln("constructed"); 9027 } 9028 9029 copyActiveOriginals(); 9030 } 9031 9032 /++ 9033 EXPERIMENTAL. subject to change. 9034 9035 When you draw a cursor, you can draw this to notify your window of where it is, 9036 for IME systems to use. 9037 +/ 9038 void notifyCursorPosition(int x, int y, int width, int height) { 9039 if(auto w = cast(SimpleWindow) window) { 9040 w.setIMEPopupLocation(x + _originX + width, y + _originY + height); 9041 } 9042 } 9043 9044 /++ 9045 If you are using manual invalidations, this informs the 9046 window system that a section needs to be redrawn. 9047 9048 If you didn't opt into manual invalidation, you don't 9049 have to call this. 9050 9051 History: 9052 Added December 30, 2021 (dub v10.5) 9053 +/ 9054 void invalidateRect(Rectangle rect) { 9055 if(impl is null) return; 9056 9057 // transform(rect) 9058 rect.left += _originX; 9059 rect.right += _originX; 9060 rect.top += _originY; 9061 rect.bottom += _originY; 9062 9063 impl.invalidateRect(rect); 9064 } 9065 9066 private Pen originalPen; 9067 private Color originalFillColor; 9068 private arsd.color.Rectangle originalClipRectangle; 9069 void copyActiveOriginals() { 9070 if(impl is null) return; 9071 originalPen = impl._activePen; 9072 originalFillColor = impl._fillColor; 9073 originalClipRectangle = impl._clipRectangle; 9074 } 9075 9076 ~this() { 9077 if(impl is null) return; 9078 impl.referenceCount--; 9079 //writeln("refcount -- ", impl.referenceCount); 9080 if(impl.referenceCount == 0) { 9081 //import std.stdio; writeln("destructed"); 9082 impl.dispose(); 9083 *window.activeScreenPainter = ScreenPainterImplementation.init; 9084 //import std.stdio; writeln("paint finished"); 9085 } else { 9086 // there is still an active reference, reset stuff so the 9087 // next user doesn't get weirdness via the reference 9088 this.rasterOp = RasterOp.normal; 9089 pen = originalPen; 9090 fillColor = originalFillColor; 9091 impl.setClipRectangle(originalClipRectangle.left, originalClipRectangle.top, originalClipRectangle.width, originalClipRectangle.height); 9092 } 9093 } 9094 9095 this(this) { 9096 if(impl is null) return; 9097 impl.referenceCount++; 9098 //writeln("refcount ++ ", impl.referenceCount); 9099 9100 copyActiveOriginals(); 9101 } 9102 9103 private int _originX; 9104 private int _originY; 9105 @property int originX() { return _originX; } 9106 @property int originY() { return _originY; } 9107 @property int originX(int a) { 9108 _originX = a; 9109 return _originX; 9110 } 9111 @property int originY(int a) { 9112 _originY = a; 9113 return _originY; 9114 } 9115 arsd.color.Rectangle currentClipRectangle; // set BEFORE doing any transformations 9116 private void transform(ref Point p) { 9117 if(impl is null) return; 9118 p.x += _originX; 9119 p.y += _originY; 9120 } 9121 9122 // this needs to be checked BEFORE the originX/Y transformation 9123 private bool isClipped(Point p) { 9124 return !currentClipRectangle.contains(p); 9125 } 9126 private bool isClipped(Point p, int width, int height) { 9127 return !currentClipRectangle.overlaps(arsd.color.Rectangle(p, Size(width + 1, height + 1))); 9128 } 9129 private bool isClipped(Point p, Size s) { 9130 return !currentClipRectangle.overlaps(arsd.color.Rectangle(p, Size(s.width + 1, s.height + 1))); 9131 } 9132 private bool isClipped(Point p, Point p2) { 9133 // need to ensure the end points are actually included inside, so the +1 does that 9134 return !currentClipRectangle.overlaps(arsd.color.Rectangle(p, p2 + Point(1, 1))); 9135 } 9136 9137 9138 /++ 9139 Sets the clipping region for drawing. If width == 0 && height == 0, disabled clipping. 9140 9141 Returns: 9142 The old clip rectangle. 9143 9144 History: 9145 Return value was `void` prior to May 10, 2021. 9146 9147 +/ 9148 arsd.color.Rectangle setClipRectangle(Point pt, int width, int height) { 9149 if(impl is null) return currentClipRectangle; 9150 if(pt == currentClipRectangle.upperLeft && width == currentClipRectangle.width && height == currentClipRectangle.height) 9151 return currentClipRectangle; // no need to do anything 9152 auto old = currentClipRectangle; 9153 currentClipRectangle = arsd.color.Rectangle(pt, Size(width, height)); 9154 transform(pt); 9155 9156 impl.setClipRectangle(pt.x, pt.y, width, height); 9157 9158 return old; 9159 } 9160 9161 /// ditto 9162 arsd.color.Rectangle setClipRectangle(arsd.color.Rectangle rect) { 9163 if(impl is null) return currentClipRectangle; 9164 return setClipRectangle(rect.upperLeft, rect.width, rect.height); 9165 } 9166 9167 /// 9168 void setFont(OperatingSystemFont font) { 9169 if(impl is null) return; 9170 impl.setFont(font); 9171 } 9172 9173 /// 9174 int fontHeight() { 9175 if(impl is null) return 0; 9176 return impl.fontHeight(); 9177 } 9178 9179 private Pen activePen; 9180 9181 /// 9182 @property void pen(Pen p) { 9183 if(impl is null) return; 9184 activePen = p; 9185 impl.pen(p); 9186 } 9187 9188 /// 9189 @scriptable 9190 @property void outlineColor(Color c) { 9191 if(impl is null) return; 9192 if(activePen.color == c) 9193 return; 9194 activePen.color = c; 9195 impl.pen(activePen); 9196 } 9197 9198 /// 9199 @scriptable 9200 @property void fillColor(Color c) { 9201 if(impl is null) return; 9202 impl.fillColor(c); 9203 } 9204 9205 /// 9206 @property void rasterOp(RasterOp op) { 9207 if(impl is null) return; 9208 impl.rasterOp(op); 9209 } 9210 9211 9212 void updateDisplay() { 9213 // FIXME this should do what the dtor does 9214 } 9215 9216 /// 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) 9217 void scrollArea(Point upperLeft, int width, int height, int dx, int dy) { 9218 if(impl is null) return; 9219 if(isClipped(upperLeft, width, height)) return; 9220 transform(upperLeft); 9221 version(Windows) { 9222 // http://msdn.microsoft.com/en-us/library/windows/desktop/bb787589%28v=vs.85%29.aspx 9223 RECT scroll = RECT(upperLeft.x, upperLeft.y, upperLeft.x + width, upperLeft.y + height); 9224 RECT clip = scroll; 9225 RECT uncovered; 9226 HRGN hrgn; 9227 if(!ScrollDC(impl.hdc, -dx, -dy, &scroll, &clip, hrgn, &uncovered)) 9228 throw new Exception("ScrollDC"); 9229 9230 } else version(X11) { 9231 // FIXME: clip stuff outside this rectangle 9232 XCopyArea(impl.display, impl.d, impl.d, impl.gc, upperLeft.x, upperLeft.y, width, height, upperLeft.x - dx, upperLeft.y - dy); 9233 } else version(OSXCocoa) { 9234 throw new NotYetImplementedException(); 9235 } else static assert(0); 9236 } 9237 9238 /// 9239 void clear(Color color = Color.white()) { 9240 if(impl is null) return; 9241 fillColor = color; 9242 outlineColor = color; 9243 drawRectangle(Point(0, 0), window.width, window.height); 9244 } 9245 9246 /++ 9247 Draws a pixmap (represented by the [Sprite] class) on the drawable. 9248 9249 Params: 9250 upperLeft = point on the window where the upper left corner of the image will be drawn 9251 imageUpperLeft = point on the image to start the slice to draw 9252 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. 9253 History: 9254 The `imageUpperLeft` and `sliceSize` parameters were added on March 11, 2021 (dub v9.3.0) 9255 +/ 9256 version(OSXCocoa) {} else // NotYetImplementedException 9257 void drawPixmap(Sprite s, Point upperLeft, Point imageUpperLeft = Point(0, 0), Size sliceSize = Size(0, 0)) { 9258 if(impl is null) return; 9259 if(isClipped(upperLeft, s.width, s.height)) return; 9260 transform(upperLeft); 9261 impl.drawPixmap(s, upperLeft.x, upperLeft.y, imageUpperLeft.x, imageUpperLeft.y, sliceSize.width, sliceSize.height); 9262 } 9263 9264 /// 9265 void drawImage(Point upperLeft, Image i, Point upperLeftOfImage = Point(0, 0), int w = 0, int h = 0) { 9266 if(impl is null) return; 9267 //if(isClipped(upperLeft, w, h)) return; // FIXME 9268 transform(upperLeft); 9269 if(w == 0 || w > i.width) 9270 w = i.width; 9271 if(h == 0 || h > i.height) 9272 h = i.height; 9273 if(upperLeftOfImage.x < 0) 9274 upperLeftOfImage.x = 0; 9275 if(upperLeftOfImage.y < 0) 9276 upperLeftOfImage.y = 0; 9277 9278 impl.drawImage(upperLeft.x, upperLeft.y, i, upperLeftOfImage.x, upperLeftOfImage.y, w, h); 9279 } 9280 9281 /// 9282 Size textSize(in char[] text) { 9283 if(impl is null) return Size(0, 0); 9284 return impl.textSize(text); 9285 } 9286 9287 /++ 9288 Draws a string in the window with the set font (see [setFont] to change it). 9289 9290 Params: 9291 upperLeft = the upper left point of the bounding box of the text 9292 text = the string to draw 9293 lowerRight = the lower right point of the bounding box of the text. If 0, 0, there is no lower right bound. 9294 alignment = A [arsd.docs.general_concepts#bitflags|combination] of [TextAlignment] flags 9295 +/ 9296 @scriptable 9297 void drawText(Point upperLeft, in char[] text, Point lowerRight = Point(0, 0), uint alignment = 0) { 9298 if(impl is null) return; 9299 if(lowerRight.x != 0 || lowerRight.y != 0) { 9300 if(isClipped(upperLeft, lowerRight)) return; 9301 transform(lowerRight); 9302 } else { 9303 if(isClipped(upperLeft, textSize(text))) return; 9304 } 9305 transform(upperLeft); 9306 impl.drawText(upperLeft.x, upperLeft.y, lowerRight.x, lowerRight.y, text, alignment); 9307 } 9308 9309 /++ 9310 Draws text using a custom font. 9311 9312 This is still MAJOR work in progress. 9313 9314 Creating a [DrawableFont] can be tricky and require additional dependencies. 9315 +/ 9316 void drawText(DrawableFont font, Point upperLeft, in char[] text) { 9317 if(impl is null) return; 9318 if(isClipped(upperLeft, Point(int.max, int.max))) return; 9319 transform(upperLeft); 9320 font.drawString(this, upperLeft, text); 9321 } 9322 9323 version(Windows) 9324 void drawText(Point upperLeft, scope const(wchar)[] text) { 9325 if(impl is null) return; 9326 if(isClipped(upperLeft, Point(int.max, int.max))) return; 9327 transform(upperLeft); 9328 9329 if(text.length && text[$-1] == '\n') 9330 text = text[0 .. $-1]; // tailing newlines are weird on windows... 9331 9332 TextOutW(impl.hdc, upperLeft.x, upperLeft.y, text.ptr, cast(int) text.length); 9333 } 9334 9335 static struct TextDrawingContext { 9336 Point boundingBoxUpperLeft; 9337 Point boundingBoxLowerRight; 9338 9339 Point currentLocation; 9340 9341 Point lastDrewUpperLeft; 9342 Point lastDrewLowerRight; 9343 9344 // how do i do right aligned rich text? 9345 // i kinda want to do a pre-made drawing then right align 9346 // draw the whole block. 9347 // 9348 // That's exactly the diff: inline vs block stuff. 9349 9350 // I need to get coordinates of an inline section out too, 9351 // not just a bounding box, but a series of bounding boxes 9352 // should be ok. Consider what's needed to detect a click 9353 // on a link in the middle of a paragraph breaking a line. 9354 // 9355 // Generally, we should be able to get the rectangles of 9356 // any portion we draw. 9357 // 9358 // It also needs to tell what text is left if it overflows 9359 // out of the box, so we can do stuff like float images around 9360 // it. It should not attempt to draw a letter that would be 9361 // clipped. 9362 // 9363 // I might also turn off word wrap stuff. 9364 } 9365 9366 void drawText(TextDrawingContext context, in char[] text, uint alignment = 0) { 9367 if(impl is null) return; 9368 // FIXME 9369 } 9370 9371 /// Drawing an individual pixel is slow. Avoid it if possible. 9372 void drawPixel(Point where) { 9373 if(impl is null) return; 9374 if(isClipped(where)) return; 9375 transform(where); 9376 impl.drawPixel(where.x, where.y); 9377 } 9378 9379 9380 /// Draws a pen using the current pen / outlineColor 9381 @scriptable 9382 void drawLine(Point starting, Point ending) { 9383 if(impl is null) return; 9384 if(isClipped(starting, ending)) return; 9385 transform(starting); 9386 transform(ending); 9387 impl.drawLine(starting.x, starting.y, ending.x, ending.y); 9388 } 9389 9390 /// Draws a rectangle using the current pen/outline color for the border and brush/fill color for the insides 9391 /// The outer lines, inclusive of x = 0, y = 0, x = width - 1, and y = height - 1 are drawn with the outlineColor 9392 /// The rest of the pixels are drawn with the fillColor. If fillColor is transparent, those pixels are not drawn. 9393 @scriptable 9394 void drawRectangle(Point upperLeft, int width, int height) { 9395 if(impl is null) return; 9396 if(isClipped(upperLeft, width, height)) return; 9397 transform(upperLeft); 9398 impl.drawRectangle(upperLeft.x, upperLeft.y, width, height); 9399 } 9400 9401 /// ditto 9402 void drawRectangle(Point upperLeft, Size size) { 9403 if(impl is null) return; 9404 if(isClipped(upperLeft, size.width, size.height)) return; 9405 transform(upperLeft); 9406 impl.drawRectangle(upperLeft.x, upperLeft.y, size.width, size.height); 9407 } 9408 9409 /// ditto 9410 void drawRectangle(Point upperLeft, Point lowerRightInclusive) { 9411 if(impl is null) return; 9412 if(isClipped(upperLeft, lowerRightInclusive + Point(1, 1))) return; 9413 transform(upperLeft); 9414 transform(lowerRightInclusive); 9415 impl.drawRectangle(upperLeft.x, upperLeft.y, 9416 lowerRightInclusive.x - upperLeft.x + 1, lowerRightInclusive.y - upperLeft.y + 1); 9417 } 9418 9419 // overload added on May 12, 2021 9420 /// ditto 9421 void drawRectangle(Rectangle rect) { 9422 drawRectangle(rect.upperLeft, rect.size); 9423 } 9424 9425 /// Arguments are the points of the bounding rectangle 9426 void drawEllipse(Point upperLeft, Point lowerRight) { 9427 if(impl is null) return; 9428 if(isClipped(upperLeft, lowerRight)) return; 9429 transform(upperLeft); 9430 transform(lowerRight); 9431 impl.drawEllipse(upperLeft.x, upperLeft.y, lowerRight.x, lowerRight.y); 9432 } 9433 9434 /++ 9435 start and finish are units of degrees * 64 9436 9437 History: 9438 The Windows implementation didn't match the Linux implementation until September 24, 2021. 9439 9440 They still don't exactly match in outlining the arc with straight lines (Windows does, Linux doesn't for now). 9441 +/ 9442 void drawArc(Point upperLeft, int width, int height, int start, int finish) { 9443 if(impl is null) return; 9444 // FIXME: not actually implemented 9445 if(isClipped(upperLeft, width, height)) return; 9446 transform(upperLeft); 9447 impl.drawArc(upperLeft.x, upperLeft.y, width, height, start, finish); 9448 } 9449 9450 /// this function draws a circle with the drawEllipse() function above, it requires the upper left point and the radius 9451 void drawCircle(Point upperLeft, int diameter) { 9452 drawEllipse(upperLeft, Point(upperLeft.x + diameter, upperLeft.y + diameter)); 9453 } 9454 9455 /// . 9456 void drawPolygon(Point[] vertexes) { 9457 if(impl is null) return; 9458 assert(vertexes.length); 9459 int minX = int.max, minY = int.max, maxX = int.min, maxY = int.min; 9460 foreach(ref vertex; vertexes) { 9461 if(vertex.x < minX) 9462 minX = vertex.x; 9463 if(vertex.y < minY) 9464 minY = vertex.y; 9465 if(vertex.x > maxX) 9466 maxX = vertex.x; 9467 if(vertex.y > maxY) 9468 maxY = vertex.y; 9469 transform(vertex); 9470 } 9471 if(isClipped(Point(minX, maxY), Point(maxX + 1, maxY + 1))) return; 9472 impl.drawPolygon(vertexes); 9473 } 9474 9475 /// ditto 9476 void drawPolygon(Point[] vertexes...) { 9477 if(impl is null) return; 9478 drawPolygon(vertexes); 9479 } 9480 9481 9482 // and do a draw/fill in a single call maybe. Windows can do it... but X can't, though it could do two calls. 9483 9484 //mixin NativeScreenPainterImplementation!() impl; 9485 9486 9487 // HACK: if I mixin the impl directly, it won't let me override the copy 9488 // constructor! The linker complains about there being multiple definitions. 9489 // I'll make the best of it and reference count it though. 9490 ScreenPainterImplementation* impl; 9491 } 9492 9493 // HACK: I need a pointer to the implementation so it's separate 9494 struct ScreenPainterImplementation { 9495 CapableOfBeingDrawnUpon window; 9496 int referenceCount; 9497 mixin NativeScreenPainterImplementation!(); 9498 } 9499 9500 // FIXME: i haven't actually tested the sprite class on MS Windows 9501 9502 /** 9503 Sprites are optimized for fast drawing on the screen, but slow for direct pixel 9504 access. They are best for drawing a relatively unchanging image repeatedly on the screen. 9505 9506 9507 On X11, this corresponds to an `XPixmap`. On Windows, it still uses a bitmap, 9508 though I'm not sure that's ideal and the implementation might change. 9509 9510 You create one by giving a window and an image. It optimizes for that window, 9511 and copies the image into it to use as the initial picture. Creating a sprite 9512 can be quite slow (especially over a network connection) so you should do it 9513 as little as possible and just hold on to your sprite handles after making them. 9514 simpledisplay does try to do its best though, using the XSHM extension if available, 9515 but you should still write your code as if it will always be slow. 9516 9517 Then you can use `sprite.drawAt(painter, point);` to draw it, which should be 9518 a fast operation - much faster than drawing the Image itself every time. 9519 9520 `Sprite` represents a scarce resource which should be freed when you 9521 are done with it. Use the `dispose` method to do this. Do not use a `Sprite` 9522 after it has been disposed. If you are unsure about this, don't take chances, 9523 just let the garbage collector do it for you. But ideally, you can manage its 9524 lifetime more efficiently. 9525 9526 $(NOTE `Sprite`, like the rest of simpledisplay's `ScreenPainter`, does not 9527 support alpha blending in its drawing at this time. That might change in the 9528 future, but if you need alpha blending right now, use OpenGL instead. See 9529 `gamehelpers.d` for a similar class to `Sprite` that uses OpenGL: `OpenGlTexture`.) 9530 9531 Update: on April 23, 2021, I finally added alpha blending support. You must opt 9532 in by setting the enableAlpha = true in the constructor. 9533 */ 9534 version(OSXCocoa) {} else // NotYetImplementedException 9535 class Sprite : CapableOfBeingDrawnUpon { 9536 9537 /// 9538 ScreenPainter draw() { 9539 return ScreenPainter(this, handle, false); 9540 } 9541 9542 /++ 9543 Copies the sprite's current state into a [TrueColorImage]. 9544 9545 Be warned: this can be a very slow operation 9546 9547 History: 9548 Actually implemented on March 14, 2021 9549 +/ 9550 TrueColorImage takeScreenshot() { 9551 return trueColorImageFromNativeHandle(handle, width, height); 9552 } 9553 9554 void delegate() paintingFinishedDg() { return null; } 9555 bool closed() { return false; } 9556 ScreenPainterImplementation* activeScreenPainter_; 9557 protected ScreenPainterImplementation* activeScreenPainter() { return activeScreenPainter_; } 9558 protected void activeScreenPainter(ScreenPainterImplementation* i) { activeScreenPainter_ = i; } 9559 9560 version(Windows) 9561 private ubyte* rawData; 9562 // FIXME: sprites are lost when disconnecting from X! We need some way to invalidate them... 9563 // ditto on the XPicture stuff 9564 9565 version(X11) { 9566 private static XRenderPictFormat* RGB24; 9567 private static XRenderPictFormat* ARGB32; 9568 9569 private Picture xrenderPicture; 9570 } 9571 9572 version(X11) 9573 private static void requireXRender() { 9574 if(!XRenderLibrary.loadAttempted) { 9575 XRenderLibrary.loadDynamicLibrary(); 9576 } 9577 9578 if(!XRenderLibrary.loadSuccessful) 9579 throw new Exception("XRender library load failure"); 9580 9581 auto display = XDisplayConnection.get; 9582 9583 // FIXME: if we migrate X displays, these need to be changed 9584 if(RGB24 is null) 9585 RGB24 = XRenderFindStandardFormat(display, PictStandardRGB24); 9586 if(ARGB32 is null) 9587 ARGB32 = XRenderFindStandardFormat(display, PictStandardARGB32); 9588 } 9589 9590 protected this() {} 9591 9592 this(SimpleWindow win, int width, int height, bool enableAlpha = false) { 9593 this._width = width; 9594 this._height = height; 9595 this.enableAlpha = enableAlpha; 9596 9597 version(X11) { 9598 auto display = XDisplayConnection.get(); 9599 9600 if(enableAlpha) { 9601 requireXRender(); 9602 } 9603 9604 handle = XCreatePixmap(display, cast(Drawable) win.window, width, height, enableAlpha ? 32 : DefaultDepthOfDisplay(display)); 9605 9606 if(enableAlpha) { 9607 XRenderPictureAttributes attrs; 9608 xrenderPicture = XRenderCreatePicture(display, handle, ARGB32, 0, &attrs); 9609 } 9610 } else version(Windows) { 9611 version(CRuntime_DigitalMars) { 9612 //if(enableAlpha) 9613 //throw new Exception("Alpha support not available, try recompiling with -m32mscoff"); 9614 } 9615 9616 BITMAPINFO infoheader; 9617 infoheader.bmiHeader.biSize = infoheader.bmiHeader.sizeof; 9618 infoheader.bmiHeader.biWidth = width; 9619 infoheader.bmiHeader.biHeight = height; 9620 infoheader.bmiHeader.biPlanes = 1; 9621 infoheader.bmiHeader.biBitCount = enableAlpha ? 32 : 24; 9622 infoheader.bmiHeader.biCompression = BI_RGB; 9623 9624 // FIXME: this should prolly be a device dependent bitmap... 9625 handle = CreateDIBSection( 9626 null, 9627 &infoheader, 9628 DIB_RGB_COLORS, 9629 cast(void**) &rawData, 9630 null, 9631 0); 9632 9633 if(handle is null) 9634 throw new Exception("couldn't create pixmap"); 9635 } 9636 } 9637 9638 /// Makes a sprite based on the image with the initial contents from the Image 9639 this(SimpleWindow win, Image i) { 9640 this(win, i.width, i.height, i.enableAlpha); 9641 9642 version(X11) { 9643 auto display = XDisplayConnection.get(); 9644 auto gc = XCreateGC(display, this.handle, 0, null); 9645 scope(exit) XFreeGC(display, gc); 9646 if(i.usingXshm) 9647 XShmPutImage(display, cast(Drawable) handle, gc, i.handle, 0, 0, 0, 0, i.width, i.height, false); 9648 else 9649 XPutImage(display, cast(Drawable) handle, gc, i.handle, 0, 0, 0, 0, i.width, i.height); 9650 } else version(Windows) { 9651 auto itemsPerLine = enableAlpha ? (4 * width) : (((cast(int) width * 3 + 3) / 4) * 4); 9652 auto arrLength = itemsPerLine * height; 9653 rawData[0..arrLength] = i.rawData[0..arrLength]; 9654 } else version(OSXCocoa) { 9655 // FIXME: I have no idea if this is even any good 9656 ubyte* rawData; 9657 9658 auto colorSpace = CGColorSpaceCreateDeviceRGB(); 9659 context = CGBitmapContextCreate(null, width, height, 8, 4*width, 9660 colorSpace, 9661 kCGImageAlphaPremultipliedLast 9662 |kCGBitmapByteOrder32Big); 9663 CGColorSpaceRelease(colorSpace); 9664 rawData = CGBitmapContextGetData(context); 9665 9666 auto rdl = (width * height * 4); 9667 rawData[0 .. rdl] = i.rawData[0 .. rdl]; 9668 } else static assert(0); 9669 } 9670 9671 /++ 9672 Draws the image on the specified painter at the specified point. The point is the upper-left point where the image will be drawn. 9673 9674 Params: 9675 where = point on the window where the upper left corner of the image will be drawn 9676 imageUpperLeft = point on the image to start the slice to draw 9677 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. 9678 History: 9679 The `imageUpperLeft` and `sliceSize` parameters were added on March 11, 2021 (dub v9.3.0) 9680 +/ 9681 void drawAt(ScreenPainter painter, Point where, Point imageUpperLeft = Point(0, 0), Size sliceSize = Size(0, 0)) { 9682 painter.drawPixmap(this, where, imageUpperLeft, sliceSize); 9683 } 9684 9685 /// Call this when you're ready to get rid of it 9686 void dispose() { 9687 version(X11) { 9688 staticDispose(xrenderPicture, handle); 9689 xrenderPicture = None; 9690 handle = None; 9691 } else version(Windows) { 9692 staticDispose(handle); 9693 handle = null; 9694 } else version(OSXCocoa) { 9695 staticDispose(context); 9696 context = null; 9697 } else static assert(0); 9698 9699 } 9700 9701 version(X11) 9702 static void staticDispose(Picture xrenderPicture, Pixmap handle) { 9703 if(xrenderPicture) 9704 XRenderFreePicture(XDisplayConnection.get, xrenderPicture); 9705 if(handle) 9706 XFreePixmap(XDisplayConnection.get(), handle); 9707 } 9708 else version(Windows) 9709 static void staticDispose(HBITMAP handle) { 9710 if(handle) 9711 DeleteObject(handle); 9712 } 9713 else version(OSXCocoa) 9714 static void staticDispose(CGContextRef context) { 9715 if(context) 9716 CGContextRelease(context); 9717 } 9718 9719 ~this() { 9720 version(X11) { if(xrenderPicture || handle) 9721 cleanupQueue.queue!staticDispose(xrenderPicture, handle); 9722 } else version(Windows) { if(handle) 9723 cleanupQueue.queue!staticDispose(handle); 9724 } else version(OSXCocoa) { if(context) 9725 cleanupQueue.queue!staticDispose(context); 9726 } else static assert(0); 9727 } 9728 9729 /// 9730 final @property int width() { return _width; } 9731 9732 /// 9733 final @property int height() { return _height; } 9734 9735 /// 9736 static Sprite fromMemoryImage(SimpleWindow win, MemoryImage img, bool enableAlpha = false) { 9737 return new Sprite(win, Image.fromMemoryImage(img, enableAlpha)); 9738 } 9739 9740 auto nativeHandle() { 9741 return handle; 9742 } 9743 9744 private: 9745 9746 int _width; 9747 int _height; 9748 bool enableAlpha; 9749 version(X11) 9750 Pixmap handle; 9751 else version(Windows) 9752 HBITMAP handle; 9753 else version(OSXCocoa) 9754 CGContextRef context; 9755 else static assert(0); 9756 } 9757 9758 /++ 9759 Represents a display-side gradient pseudo-image. Actually construct it with [LinearGradient], [RadialGradient], or [ConicalGradient]. 9760 9761 History: 9762 Added November 20, 2021 (dub v10.4) 9763 +/ 9764 abstract class Gradient : Sprite { 9765 protected this(int w, int h) { 9766 version(X11) { 9767 Sprite.requireXRender(); 9768 9769 super(); 9770 enableAlpha = true; 9771 _width = w; 9772 _height = h; 9773 } else version(Windows) { 9774 super(null, w, h, true); // on Windows i'm just making a bitmap myself 9775 } 9776 } 9777 9778 version(Windows) 9779 final void forEachPixel(scope Color delegate(int x, int y) dg) { 9780 auto ptr = rawData; 9781 foreach(j; 0 .. _height) 9782 foreach(i; 0 .. _width) { 9783 auto color = dg(i, _height - j - 1); // cuz of upside down bitmap 9784 *rawData = (color.a * color.b) / 255; rawData++; 9785 *rawData = (color.a * color.g) / 255; rawData++; 9786 *rawData = (color.a * color.r) / 255; rawData++; 9787 *rawData = color.a; rawData++; 9788 } 9789 } 9790 9791 version(X11) 9792 protected void helper(scope Stop[] stops, scope Picture delegate(scope XFixed[] stopsPositions, scope XRenderColor[] colors) dg) { 9793 assert(stops.length > 0); 9794 assert(stops.length <= 16, "I got lazy with buffers"); 9795 9796 XFixed[16] stopsPositions = void; 9797 XRenderColor[16] colors = void; 9798 9799 foreach(idx, stop; stops) { 9800 stopsPositions[idx] = cast(int)(stop.percentage * ushort.max); 9801 auto c = stop.c; 9802 colors[idx] = XRenderColor( 9803 cast(ushort)(c.r * ushort.max / 255), 9804 cast(ushort)(c.g * ushort.max / 255), 9805 cast(ushort)(c.b * ushort.max / 255), 9806 cast(ushort)(c.a * ubyte.max) // max value here is fractional 9807 ); 9808 } 9809 9810 xrenderPicture = dg(stopsPositions, colors); 9811 } 9812 9813 /// 9814 static struct Stop { 9815 float percentage; /// between 0 and 1.0 9816 Color c; 9817 } 9818 } 9819 9820 /++ 9821 Creates a linear gradient between p1 and p2. 9822 9823 X ONLY RIGHT NOW 9824 9825 History: 9826 Added November 20, 2021 (dub v10.4) 9827 9828 Bugs: 9829 Not yet implemented on Windows. 9830 +/ 9831 class LinearGradient : Gradient { 9832 /++ 9833 9834 +/ 9835 this(Point p1, Point p2, Stop[] stops...) { 9836 super(p2.x, p2.y); 9837 9838 version(X11) { 9839 XLinearGradient gradient; 9840 gradient.p1 = XPointFixed(p1.x * ushort.max, p1.y * ushort.max); 9841 gradient.p2 = XPointFixed(p2.x * ushort.max, p2.y * ushort.max); 9842 9843 helper(stops, (scope XFixed[] stopsPositions, scope XRenderColor[] colors) { 9844 return XRenderCreateLinearGradient( 9845 XDisplayConnection.get, 9846 &gradient, 9847 stopsPositions.ptr, 9848 colors.ptr, 9849 cast(int) stops.length); 9850 }); 9851 } else version(Windows) { 9852 // FIXME 9853 forEachPixel((int x, int y) { 9854 import core.stdc.math; 9855 9856 //sqrtf( 9857 9858 return Color.transparent; 9859 // return Color(x * 2, y * 2, 0, 128); // this result is so beautiful 9860 }); 9861 } 9862 } 9863 } 9864 9865 /++ 9866 A conical gradient goes from color to color around a circumference from a center point. 9867 9868 X ONLY RIGHT NOW 9869 9870 History: 9871 Added November 20, 2021 (dub v10.4) 9872 9873 Bugs: 9874 Not yet implemented on Windows. 9875 +/ 9876 class ConicalGradient : Gradient { 9877 /++ 9878 9879 +/ 9880 this(Point center, float angleInDegrees, Stop[] stops...) { 9881 super(center.x * 2, center.y * 2); 9882 9883 version(X11) { 9884 XConicalGradient gradient; 9885 gradient.center = XPointFixed(center.x * ushort.max, center.y * ushort.max); 9886 gradient.angle = cast(int)(angleInDegrees * ushort.max); 9887 9888 helper(stops, (scope XFixed[] stopsPositions, scope XRenderColor[] colors) { 9889 return XRenderCreateConicalGradient( 9890 XDisplayConnection.get, 9891 &gradient, 9892 stopsPositions.ptr, 9893 colors.ptr, 9894 cast(int) stops.length); 9895 }); 9896 } else version(Windows) { 9897 // FIXME 9898 forEachPixel((int x, int y) { 9899 import core.stdc.math; 9900 9901 //sqrtf( 9902 9903 return Color.transparent; 9904 // return Color(x * 2, y * 2, 0, 128); // this result is so beautiful 9905 }); 9906 9907 } 9908 } 9909 } 9910 9911 /++ 9912 A radial gradient goes from color to color based on distance from the center. 9913 It is like rings of color. 9914 9915 X ONLY RIGHT NOW 9916 9917 9918 More specifically, you create two circles: an inner circle and an outer circle. 9919 The gradient is only drawn in the area outside the inner circle but inside the outer 9920 circle. The closest line between those two circles forms the line for the gradient 9921 and the stops are calculated the same as the [LinearGradient]. Then it just sweeps around. 9922 9923 History: 9924 Added November 20, 2021 (dub v10.4) 9925 9926 Bugs: 9927 Not yet implemented on Windows. 9928 +/ 9929 class RadialGradient : Gradient { 9930 /++ 9931 9932 +/ 9933 this(Point innerCenter, float innerRadius, Point outerCenter, float outerRadius, Stop[] stops...) { 9934 super(cast(int)(outerCenter.x + outerRadius + 0.5), cast(int)(outerCenter.y + outerRadius + 0.5)); 9935 9936 version(X11) { 9937 XRadialGradient gradient; 9938 gradient.inner = XCircle(innerCenter.x * ushort.max, innerCenter.y * ushort.max, cast(int) (innerRadius * ushort.max)); 9939 gradient.outer = XCircle(outerCenter.x * ushort.max, outerCenter.y * ushort.max, cast(int) (outerRadius * ushort.max)); 9940 9941 helper(stops, (scope XFixed[] stopsPositions, scope XRenderColor[] colors) { 9942 return XRenderCreateRadialGradient( 9943 XDisplayConnection.get, 9944 &gradient, 9945 stopsPositions.ptr, 9946 colors.ptr, 9947 cast(int) stops.length); 9948 }); 9949 } else version(Windows) { 9950 // FIXME 9951 forEachPixel((int x, int y) { 9952 import core.stdc.math; 9953 9954 //sqrtf( 9955 9956 return Color.transparent; 9957 // return Color(x * 2, y * 2, 0, 128); // this result is so beautiful 9958 }); 9959 } 9960 } 9961 } 9962 9963 9964 9965 /+ 9966 NOT IMPLEMENTED 9967 9968 A display-stored image optimized for relatively quick drawing, like 9969 [Sprite], but this one supports alpha channel blending and does NOT 9970 support direct drawing upon it with a [ScreenPainter]. 9971 9972 You can think of it as an [arsd.game.OpenGlTexture] for usage with a 9973 plain [ScreenPainter]... sort of. 9974 9975 On X11, it requires the Xrender extension and library. This is available 9976 almost everywhere though. 9977 9978 History: 9979 Added November 14, 2020 but NOT ACTUALLY IMPLEMENTED 9980 +/ 9981 version(none) 9982 class AlphaSprite { 9983 /++ 9984 Copies the given image into it. 9985 +/ 9986 this(MemoryImage img) { 9987 9988 if(!XRenderLibrary.loadAttempted) { 9989 XRenderLibrary.loadDynamicLibrary(); 9990 9991 // FIXME: this needs to be reconstructed when the X server changes 9992 repopulateX(); 9993 } 9994 if(!XRenderLibrary.loadSuccessful) 9995 throw new Exception("XRender library load failure"); 9996 9997 // I probably need to put the alpha mask in a separate Picture 9998 // ugh 9999 // maybe the Sprite itself can have an alpha bitmask anyway 10000 10001 10002 auto display = XDisplayConnection.get(); 10003 pixmap = XCreatePixmap(display, cast(Drawable) win.window, width, height, DefaultDepthOfDisplay(display)); 10004 10005 10006 XRenderPictureAttributes attrs; 10007 10008 handle = XRenderCreatePicture( 10009 XDisplayConnection.get, 10010 pixmap, 10011 RGBA, 10012 0, 10013 &attrs 10014 ); 10015 10016 } 10017 10018 // maybe i'll use the create gradient functions too with static factories.. 10019 10020 void drawAt(ScreenPainter painter, Point where) { 10021 //painter.drawPixmap(this, where); 10022 10023 XRenderPictureAttributes attrs; 10024 10025 auto pic = XRenderCreatePicture( 10026 XDisplayConnection.get, 10027 painter.impl.d, 10028 RGB, 10029 0, 10030 &attrs 10031 ); 10032 10033 XRenderComposite( 10034 XDisplayConnection.get, 10035 3, // PictOpOver 10036 handle, 10037 None, 10038 pic, 10039 0, // src 10040 0, 10041 0, // mask 10042 0, 10043 10, // dest 10044 10, 10045 100, // width 10046 100 10047 ); 10048 10049 /+ 10050 XRenderFreePicture( 10051 XDisplayConnection.get, 10052 pic 10053 ); 10054 10055 XRenderFreePicture( 10056 XDisplayConnection.get, 10057 fill 10058 ); 10059 +/ 10060 // on Windows you can stretch but Xrender still can't :( 10061 } 10062 10063 static XRenderPictFormat* RGB; 10064 static XRenderPictFormat* RGBA; 10065 static void repopulateX() { 10066 auto display = XDisplayConnection.get; 10067 RGB = XRenderFindStandardFormat(display, PictStandardRGB24); 10068 RGBA = XRenderFindStandardFormat(display, PictStandardARGB32); 10069 } 10070 10071 XPixmap pixmap; 10072 Picture handle; 10073 } 10074 10075 /// 10076 interface CapableOfBeingDrawnUpon { 10077 /// 10078 ScreenPainter draw(); 10079 /// 10080 int width(); 10081 /// 10082 int height(); 10083 protected ScreenPainterImplementation* activeScreenPainter(); 10084 protected void activeScreenPainter(ScreenPainterImplementation*); 10085 bool closed(); 10086 10087 void delegate() paintingFinishedDg(); 10088 10089 /// Be warned: this can be a very slow operation 10090 TrueColorImage takeScreenshot(); 10091 } 10092 10093 /// 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]. 10094 void flushGui() { 10095 version(X11) { 10096 auto dpy = XDisplayConnection.get(); 10097 XLockDisplay(dpy); 10098 scope(exit) XUnlockDisplay(dpy); 10099 XFlush(dpy); 10100 } 10101 } 10102 10103 /++ 10104 Runs the given code in the GUI thread when its event loop 10105 is available, blocking until it completes. This allows you 10106 to create and manipulate windows from another thread without 10107 invoking undefined behavior. 10108 10109 If this is the gui thread, it runs the code immediately. 10110 10111 If no gui thread exists yet, the current thread is assumed 10112 to be it. Attempting to create windows or run the event loop 10113 in any other thread will cause an assertion failure. 10114 10115 10116 $(TIP 10117 Did you know you can use UFCS on delegate literals? 10118 10119 () { 10120 // code here 10121 }.runInGuiThread; 10122 ) 10123 10124 Returns: 10125 `true` if the function was called, `false` if it was not. 10126 The function may not be called because the gui thread had 10127 already terminated by the time you called this. 10128 10129 History: 10130 Added April 10, 2020 (v7.2.0) 10131 10132 Return value added and implementation tweaked to avoid locking 10133 at program termination on February 24, 2021 (v9.2.1). 10134 +/ 10135 bool runInGuiThread(scope void delegate() dg) @trusted { 10136 claimGuiThread(); 10137 10138 if(thisIsGuiThread) { 10139 dg(); 10140 return true; 10141 } 10142 10143 if(guiThreadTerminating) 10144 return false; 10145 10146 import core.sync.semaphore; 10147 static Semaphore sc; 10148 if(sc is null) 10149 sc = new Semaphore(); 10150 10151 static RunQueueMember* rqm; 10152 if(rqm is null) 10153 rqm = new RunQueueMember; 10154 rqm.dg = cast(typeof(rqm.dg)) dg; 10155 rqm.signal = sc; 10156 rqm.thrown = null; 10157 10158 synchronized(runInGuiThreadLock) { 10159 runInGuiThreadQueue ~= rqm; 10160 } 10161 10162 if(!SimpleWindow.eventWakeUp()) 10163 throw new Error("runInGuiThread impossible; eventWakeUp failed"); 10164 10165 rqm.signal.wait(); 10166 auto t = rqm.thrown; 10167 10168 if(t) 10169 throw t; 10170 10171 return true; 10172 } 10173 10174 // note it runs sync if this is the gui thread.... 10175 void runInGuiThreadAsync(void delegate() dg, void delegate(Exception) nothrow handleError = null) nothrow { 10176 claimGuiThread(); 10177 10178 try { 10179 10180 if(thisIsGuiThread) { 10181 dg(); 10182 return; 10183 } 10184 10185 if(guiThreadTerminating) 10186 return; 10187 10188 RunQueueMember* rqm = new RunQueueMember; 10189 rqm.dg = cast(typeof(rqm.dg)) dg; 10190 rqm.signal = null; 10191 rqm.thrown = null; 10192 10193 synchronized(runInGuiThreadLock) { 10194 runInGuiThreadQueue ~= rqm; 10195 } 10196 10197 if(!SimpleWindow.eventWakeUp()) 10198 throw new Error("runInGuiThread impossible; eventWakeUp failed"); 10199 } catch(Exception e) { 10200 if(handleError) 10201 handleError(e); 10202 } 10203 } 10204 10205 private void runPendingRunInGuiThreadDelegates() { 10206 more: 10207 RunQueueMember* next; 10208 synchronized(runInGuiThreadLock) { 10209 if(runInGuiThreadQueue.length) { 10210 next = runInGuiThreadQueue[0]; 10211 runInGuiThreadQueue = runInGuiThreadQueue[1 .. $]; 10212 } else { 10213 next = null; 10214 } 10215 } 10216 10217 if(next) { 10218 try { 10219 next.dg(); 10220 next.thrown = null; 10221 } catch(Throwable t) { 10222 next.thrown = t; 10223 } 10224 10225 if(next.signal) 10226 next.signal.notify(); 10227 10228 goto more; 10229 } 10230 } 10231 10232 private void claimGuiThread() nothrow { 10233 import core.atomic; 10234 if(cas(&guiThreadExists_, false, true)) 10235 thisIsGuiThread = true; 10236 } 10237 10238 private struct RunQueueMember { 10239 void delegate() dg; 10240 import core.sync.semaphore; 10241 Semaphore signal; 10242 Throwable thrown; 10243 } 10244 10245 private __gshared RunQueueMember*[] runInGuiThreadQueue; 10246 private __gshared Object runInGuiThreadLock = new Object; // intentional CTFE 10247 private bool thisIsGuiThread = false; 10248 private shared bool guiThreadExists_ = false; 10249 private shared bool guiThreadTerminating = false; 10250 10251 /++ 10252 Returns `true` if a gui thread exists, that is, a thread running the simpledisplay.d 10253 event loop. All windows must be exclusively created and managed by a single thread. 10254 10255 If no gui thread exists, simpledisplay.d will automatically adopt the current thread 10256 when you call one of its constructors. 10257 10258 If a gui thread exists, you should check [thisThreadRunningGui] to see if it is this 10259 one. If so, you can run gui functions on it. If not, don't. The helper functions 10260 [runInGuiThread] and [runInGuiThreadAsync] can be used to help you with this automatically. 10261 10262 The reason this function is available is in case you want to message pass between a gui 10263 thread and your current thread. If no gui thread exists or if this is the gui thread, 10264 you're liable to deadlock when trying to communicate since you'd end up talking to yourself. 10265 10266 History: 10267 Added December 3, 2021 (dub v10.5) 10268 +/ 10269 public bool guiThreadExists() { 10270 return guiThreadExists_; 10271 } 10272 10273 /++ 10274 Returns `true` if this thread is either running or set to be running the 10275 simpledisplay.d gui core event loop because it owns windows. 10276 10277 It is important to keep gui-related functionality in the right thread, so you will 10278 want to `runInGuiThread` when you call them (with some specific exceptions called 10279 out in those specific functions' documentation). Notably, all windows must be 10280 created and managed only from the gui thread. 10281 10282 Will return false if simpledisplay's other functions haven't been called 10283 yet; check [guiThreadExists] in addition to this. 10284 10285 History: 10286 Added December 3, 2021 (dub v10.5) 10287 +/ 10288 public bool thisThreadRunningGui() { 10289 return thisIsGuiThread; 10290 } 10291 10292 /++ 10293 Function to help temporarily print debugging info. It will bypass any stdout/err redirection 10294 and go to the controlling tty or console (attaching to the parent and/or allocating one as 10295 needed on Windows. Please note it may overwrite output from other programs in the parent and the 10296 allocated one will not survive if your program crashes. Use the `fileOverride` to print to a log 10297 file instead if you are in one of those situations). 10298 10299 It does not support outputting very many types; just strings and ints are likely to actually work. 10300 10301 It will perform very slowly and swallows any errors that may occur. Moreover, the specific output 10302 is unspecified meaning I can change it at any time. The only point of this function is to help 10303 in temporary use for printf-style debugging. It is NOT nogc, but you can use the `debug` keyword 10304 and the compiler will cheat for you. It is, however, formally nothrow and trusted to ease its use 10305 in those contexts. 10306 10307 $(WARNING 10308 I reserve the right to change this function at any time. You can use it if it helps you 10309 but do not rely on it for anything permanent. 10310 ) 10311 10312 History: 10313 Added December 3, 2021. Not formally supported under any stable tag. 10314 +/ 10315 void sdpyPrintDebugString(string fileOverride = null, T...)(T t) nothrow @trusted { 10316 try { 10317 version(Windows) { 10318 import core.sys.windows.wincon; 10319 if(!AttachConsole(ATTACH_PARENT_PROCESS)) 10320 AllocConsole(); 10321 const(char)* fn = "CONOUT$"; 10322 } else version(Posix) { 10323 const(char)* fn = "/dev/tty"; 10324 } else static assert(0, "Function not implemented for your system"); 10325 10326 if(fileOverride.length) 10327 fn = fileOverride.ptr; 10328 10329 import core.stdc.stdio; 10330 auto fp = fopen(fn, "wt"); 10331 if(fp is null) return; 10332 scope(exit) fclose(fp); 10333 10334 string str; 10335 foreach(item; t) { 10336 static if(is(typeof(item) : const(char)[])) 10337 str ~= item; 10338 else 10339 str ~= toInternal!string(item); 10340 str ~= " "; 10341 } 10342 str ~= "\n"; 10343 10344 fwrite(str.ptr, 1, str.length, fp); 10345 fflush(fp); 10346 } catch(Exception e) { 10347 // sorry no hope 10348 } 10349 } 10350 10351 private void guiThreadFinalize() { 10352 assert(thisIsGuiThread); 10353 10354 guiThreadTerminating = true; // don't add any more from this point on 10355 runPendingRunInGuiThreadDelegates(); 10356 } 10357 10358 /+ 10359 interface IPromise { 10360 void reportProgress(int current, int max, string message); 10361 10362 /+ // not formally in cuz of templates but still 10363 IPromise Then(); 10364 IPromise Catch(); 10365 IPromise Finally(); 10366 +/ 10367 } 10368 10369 /+ 10370 auto promise = async({ ... }); 10371 promise.Then(whatever). 10372 Then(whateverelse). 10373 Catch((exception) { }); 10374 10375 10376 A promise is run inside a fiber and it looks something like: 10377 10378 try { 10379 auto res = whatever(); 10380 auto res2 = whateverelse(res); 10381 } catch(Exception e) { 10382 { }(e); 10383 } 10384 10385 When a thing succeeds, it is passed as an arg to the next 10386 +/ 10387 class Promise(T) : IPromise { 10388 auto Then() { return null; } 10389 auto Catch() { return null; } 10390 auto Finally() { return null; } 10391 10392 // wait for it to resolve and return the value, or rethrow the error if that occurred. 10393 // cannot be called from the gui thread, but this is caught at runtime instead of compile time. 10394 T await(); 10395 } 10396 10397 interface Task { 10398 } 10399 10400 interface Resolvable(T) : Task { 10401 void run(); 10402 10403 void resolve(T); 10404 10405 Resolvable!T then(void delegate(T)); // returns a new promise 10406 Resolvable!T error(Throwable); // js catch 10407 Resolvable!T completed(); // js finally 10408 10409 } 10410 10411 /++ 10412 Runs `work` in a helper thread and sends its return value back to the main gui 10413 thread as the argument to `uponCompletion`. If `work` throws, the exception is 10414 sent to the `uponThrown` if given, or if null, rethrown from the event loop to 10415 kill the program. 10416 10417 You can call reportProgress(position, max, message) to update your parent window 10418 on your progress. 10419 10420 I should also use `shared` methods. FIXME 10421 10422 History: 10423 Added March 6, 2021 (dub version 9.3). 10424 +/ 10425 void runInWorkerThread(T)(T delegate(Task) work, void delegate(T) uponCompletion) { 10426 uponCompletion(work(null)); 10427 } 10428 10429 +/ 10430 10431 /// Used internal to dispatch events to various classes. 10432 interface CapableOfHandlingNativeEvent { 10433 NativeEventHandler getNativeEventHandler(); 10434 10435 /*private*//*protected*/ __gshared CapableOfHandlingNativeEvent[NativeWindowHandle] nativeHandleMapping; 10436 10437 version(X11) { 10438 // if this is impossible, you are allowed to just throw from it 10439 // Note: if you call it from another object, set a flag cuz the manger will call you again 10440 void recreateAfterDisconnect(); 10441 // discard any *connection specific* state, but keep enough that you 10442 // can be recreated if possible. discardConnectionState() is always called immediately 10443 // before recreateAfterDisconnect(), so you can set a flag there to decide if 10444 // you need initialization order 10445 void discardConnectionState(); 10446 } 10447 } 10448 10449 version(X11) 10450 /++ 10451 State of keys on mouse events, especially motion. 10452 10453 Do not trust the actual integer values in this, they are platform-specific. Always use the names. 10454 +/ 10455 enum ModifierState : uint { 10456 shift = 1, /// 10457 capsLock = 2, /// 10458 ctrl = 4, /// 10459 alt = 8, /// Not always available on Windows 10460 windows = 64, /// ditto 10461 numLock = 16, /// 10462 10463 leftButtonDown = 256, /// these aren't available on Windows for key events, so don't use them for that unless your app is X only. 10464 middleButtonDown = 512, /// ditto 10465 rightButtonDown = 1024, /// ditto 10466 } 10467 else version(Windows) 10468 /// ditto 10469 enum ModifierState : uint { 10470 shift = 4, /// 10471 ctrl = 8, /// 10472 10473 // i'm not sure if the next two are available 10474 alt = 256, /// not always available on Windows 10475 windows = 512, /// ditto 10476 10477 capsLock = 1024, /// 10478 numLock = 2048, /// 10479 10480 leftButtonDown = 1, /// not available on key events 10481 middleButtonDown = 16, /// ditto 10482 rightButtonDown = 2, /// ditto 10483 10484 backButtonDown = 0x20, /// not available on X 10485 forwardButtonDown = 0x40, /// ditto 10486 } 10487 else version(OSXCocoa) 10488 // FIXME FIXME NotYetImplementedException 10489 enum ModifierState : uint { 10490 shift = 1, /// 10491 capsLock = 2, /// 10492 ctrl = 4, /// 10493 alt = 8, /// Not always available on Windows 10494 windows = 64, /// ditto 10495 numLock = 16, /// 10496 10497 leftButtonDown = 256, /// these aren't available on Windows for key events, so don't use them for that unless your app is X only. 10498 middleButtonDown = 512, /// ditto 10499 rightButtonDown = 1024, /// ditto 10500 } 10501 10502 /// The names assume a right-handed mouse. These are bitwise combined on the events that use them. 10503 enum MouseButton : int { 10504 none = 0, 10505 left = 1, /// 10506 right = 2, /// 10507 middle = 4, /// 10508 wheelUp = 8, /// 10509 wheelDown = 16, /// 10510 backButton = 32, /// often found on the thumb and used for back in browsers 10511 forwardButton = 64, /// often found on the thumb and used for forward in browsers 10512 } 10513 10514 version(X11) { 10515 // FIXME: match ASCII whenever we can. Most of it is already there, 10516 // but there's a few exceptions and mismatches with Windows 10517 10518 /// Do not trust the numeric values as they are platform-specific. Always use the symbolic name. 10519 enum Key { 10520 Escape = 0xff1b, /// 10521 F1 = 0xffbe, /// 10522 F2 = 0xffbf, /// 10523 F3 = 0xffc0, /// 10524 F4 = 0xffc1, /// 10525 F5 = 0xffc2, /// 10526 F6 = 0xffc3, /// 10527 F7 = 0xffc4, /// 10528 F8 = 0xffc5, /// 10529 F9 = 0xffc6, /// 10530 F10 = 0xffc7, /// 10531 F11 = 0xffc8, /// 10532 F12 = 0xffc9, /// 10533 PrintScreen = 0xff61, /// 10534 ScrollLock = 0xff14, /// 10535 Pause = 0xff13, /// 10536 Grave = 0x60, /// The $(BACKTICK) ~ key 10537 // number keys across the top of the keyboard 10538 N1 = 0x31, /// Number key atop the keyboard 10539 N2 = 0x32, /// 10540 N3 = 0x33, /// 10541 N4 = 0x34, /// 10542 N5 = 0x35, /// 10543 N6 = 0x36, /// 10544 N7 = 0x37, /// 10545 N8 = 0x38, /// 10546 N9 = 0x39, /// 10547 N0 = 0x30, /// 10548 Dash = 0x2d, /// 10549 Equals = 0x3d, /// 10550 Backslash = 0x5c, /// The \ | key 10551 Backspace = 0xff08, /// 10552 Insert = 0xff63, /// 10553 Home = 0xff50, /// 10554 PageUp = 0xff55, /// 10555 Delete = 0xffff, /// 10556 End = 0xff57, /// 10557 PageDown = 0xff56, /// 10558 Up = 0xff52, /// 10559 Down = 0xff54, /// 10560 Left = 0xff51, /// 10561 Right = 0xff53, /// 10562 10563 Tab = 0xff09, /// 10564 Q = 0x71, /// 10565 W = 0x77, /// 10566 E = 0x65, /// 10567 R = 0x72, /// 10568 T = 0x74, /// 10569 Y = 0x79, /// 10570 U = 0x75, /// 10571 I = 0x69, /// 10572 O = 0x6f, /// 10573 P = 0x70, /// 10574 LeftBracket = 0x5b, /// the [ { key 10575 RightBracket = 0x5d, /// the ] } key 10576 CapsLock = 0xffe5, /// 10577 A = 0x61, /// 10578 S = 0x73, /// 10579 D = 0x64, /// 10580 F = 0x66, /// 10581 G = 0x67, /// 10582 H = 0x68, /// 10583 J = 0x6a, /// 10584 K = 0x6b, /// 10585 L = 0x6c, /// 10586 Semicolon = 0x3b, /// 10587 Apostrophe = 0x27, /// 10588 Enter = 0xff0d, /// 10589 Shift = 0xffe1, /// 10590 Z = 0x7a, /// 10591 X = 0x78, /// 10592 C = 0x63, /// 10593 V = 0x76, /// 10594 B = 0x62, /// 10595 N = 0x6e, /// 10596 M = 0x6d, /// 10597 Comma = 0x2c, /// 10598 Period = 0x2e, /// 10599 Slash = 0x2f, /// the / ? key 10600 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 10601 Ctrl = 0xffe3, /// 10602 Windows = 0xffeb, /// 10603 Alt = 0xffe9, /// 10604 Space = 0x20, /// 10605 Alt_r = 0xffea, /// ditto of shift_r 10606 Windows_r = 0xffec, /// 10607 Menu = 0xff67, /// 10608 Ctrl_r = 0xffe4, /// 10609 10610 NumLock = 0xff7f, /// 10611 Divide = 0xffaf, /// The / key on the number pad 10612 Multiply = 0xffaa, /// The * key on the number pad 10613 Minus = 0xffad, /// The - key on the number pad 10614 Plus = 0xffab, /// The + key on the number pad 10615 PadEnter = 0xff8d, /// Numberpad enter key 10616 Pad1 = 0xff9c, /// Numberpad keys 10617 Pad2 = 0xff99, /// 10618 Pad3 = 0xff9b, /// 10619 Pad4 = 0xff96, /// 10620 Pad5 = 0xff9d, /// 10621 Pad6 = 0xff98, /// 10622 Pad7 = 0xff95, /// 10623 Pad8 = 0xff97, /// 10624 Pad9 = 0xff9a, /// 10625 Pad0 = 0xff9e, /// 10626 PadDot = 0xff9f, /// 10627 } 10628 } else version(Windows) { 10629 // the character here is for en-us layouts and for illustration only 10630 // if you actually want to get characters, wait for character events 10631 // (the argument to your event handler is simply a dchar) 10632 // those will be converted by the OS for the right locale. 10633 10634 enum Key { 10635 Escape = 0x1b, 10636 F1 = 0x70, 10637 F2 = 0x71, 10638 F3 = 0x72, 10639 F4 = 0x73, 10640 F5 = 0x74, 10641 F6 = 0x75, 10642 F7 = 0x76, 10643 F8 = 0x77, 10644 F9 = 0x78, 10645 F10 = 0x79, 10646 F11 = 0x7a, 10647 F12 = 0x7b, 10648 PrintScreen = 0x2c, 10649 ScrollLock = 0x91, 10650 Pause = 0x13, 10651 Grave = 0xc0, 10652 // number keys across the top of the keyboard 10653 N1 = 0x31, 10654 N2 = 0x32, 10655 N3 = 0x33, 10656 N4 = 0x34, 10657 N5 = 0x35, 10658 N6 = 0x36, 10659 N7 = 0x37, 10660 N8 = 0x38, 10661 N9 = 0x39, 10662 N0 = 0x30, 10663 Dash = 0xbd, 10664 Equals = 0xbb, 10665 Backslash = 0xdc, 10666 Backspace = 0x08, 10667 Insert = 0x2d, 10668 Home = 0x24, 10669 PageUp = 0x21, 10670 Delete = 0x2e, 10671 End = 0x23, 10672 PageDown = 0x22, 10673 Up = 0x26, 10674 Down = 0x28, 10675 Left = 0x25, 10676 Right = 0x27, 10677 10678 Tab = 0x09, 10679 Q = 0x51, 10680 W = 0x57, 10681 E = 0x45, 10682 R = 0x52, 10683 T = 0x54, 10684 Y = 0x59, 10685 U = 0x55, 10686 I = 0x49, 10687 O = 0x4f, 10688 P = 0x50, 10689 LeftBracket = 0xdb, 10690 RightBracket = 0xdd, 10691 CapsLock = 0x14, 10692 A = 0x41, 10693 S = 0x53, 10694 D = 0x44, 10695 F = 0x46, 10696 G = 0x47, 10697 H = 0x48, 10698 J = 0x4a, 10699 K = 0x4b, 10700 L = 0x4c, 10701 Semicolon = 0xba, 10702 Apostrophe = 0xde, 10703 Enter = 0x0d, 10704 Shift = 0x10, 10705 Z = 0x5a, 10706 X = 0x58, 10707 C = 0x43, 10708 V = 0x56, 10709 B = 0x42, 10710 N = 0x4e, 10711 M = 0x4d, 10712 Comma = 0xbc, 10713 Period = 0xbe, 10714 Slash = 0xbf, 10715 Shift_r = 0xa1, // FIXME Note: this isn't sent on all computers, sometimes it just sends Shift, so don't rely on it 10716 Ctrl = 0x11, 10717 Windows = 0x5b, 10718 Alt = -5, // FIXME 10719 Space = 0x20, 10720 Alt_r = 0xffea, // ditto of shift_r 10721 Windows_r = 0x5c, // ditto of shift_r 10722 Menu = 0x5d, 10723 Ctrl_r = 0xa3, // ditto of shift_r 10724 10725 NumLock = 0x90, 10726 Divide = 0x6f, 10727 Multiply = 0x6a, 10728 Minus = 0x6d, 10729 Plus = 0x6b, 10730 PadEnter = -8, // FIXME 10731 Pad1 = 0x61, 10732 Pad2 = 0x62, 10733 Pad3 = 0x63, 10734 Pad4 = 0x64, 10735 Pad5 = 0x65, 10736 Pad6 = 0x66, 10737 Pad7 = 0x67, 10738 Pad8 = 0x68, 10739 Pad9 = 0x69, 10740 Pad0 = 0x60, 10741 PadDot = 0x6e, 10742 } 10743 10744 // I'm keeping this around for reference purposes 10745 // ideally all these buttons will be listed for all platforms, 10746 // but now now I'm just focusing on my US keyboard 10747 version(none) 10748 enum Key { 10749 LBUTTON = 0x01, 10750 RBUTTON = 0x02, 10751 CANCEL = 0x03, 10752 MBUTTON = 0x04, 10753 //static if (_WIN32_WINNT > = 0x500) { 10754 XBUTTON1 = 0x05, 10755 XBUTTON2 = 0x06, 10756 //} 10757 BACK = 0x08, 10758 TAB = 0x09, 10759 CLEAR = 0x0C, 10760 RETURN = 0x0D, 10761 SHIFT = 0x10, 10762 CONTROL = 0x11, 10763 MENU = 0x12, 10764 PAUSE = 0x13, 10765 CAPITAL = 0x14, 10766 KANA = 0x15, 10767 HANGEUL = 0x15, 10768 HANGUL = 0x15, 10769 JUNJA = 0x17, 10770 FINAL = 0x18, 10771 HANJA = 0x19, 10772 KANJI = 0x19, 10773 ESCAPE = 0x1B, 10774 CONVERT = 0x1C, 10775 NONCONVERT = 0x1D, 10776 ACCEPT = 0x1E, 10777 MODECHANGE = 0x1F, 10778 SPACE = 0x20, 10779 PRIOR = 0x21, 10780 NEXT = 0x22, 10781 END = 0x23, 10782 HOME = 0x24, 10783 LEFT = 0x25, 10784 UP = 0x26, 10785 RIGHT = 0x27, 10786 DOWN = 0x28, 10787 SELECT = 0x29, 10788 PRINT = 0x2A, 10789 EXECUTE = 0x2B, 10790 SNAPSHOT = 0x2C, 10791 INSERT = 0x2D, 10792 DELETE = 0x2E, 10793 HELP = 0x2F, 10794 LWIN = 0x5B, 10795 RWIN = 0x5C, 10796 APPS = 0x5D, 10797 SLEEP = 0x5F, 10798 NUMPAD0 = 0x60, 10799 NUMPAD1 = 0x61, 10800 NUMPAD2 = 0x62, 10801 NUMPAD3 = 0x63, 10802 NUMPAD4 = 0x64, 10803 NUMPAD5 = 0x65, 10804 NUMPAD6 = 0x66, 10805 NUMPAD7 = 0x67, 10806 NUMPAD8 = 0x68, 10807 NUMPAD9 = 0x69, 10808 MULTIPLY = 0x6A, 10809 ADD = 0x6B, 10810 SEPARATOR = 0x6C, 10811 SUBTRACT = 0x6D, 10812 DECIMAL = 0x6E, 10813 DIVIDE = 0x6F, 10814 F1 = 0x70, 10815 F2 = 0x71, 10816 F3 = 0x72, 10817 F4 = 0x73, 10818 F5 = 0x74, 10819 F6 = 0x75, 10820 F7 = 0x76, 10821 F8 = 0x77, 10822 F9 = 0x78, 10823 F10 = 0x79, 10824 F11 = 0x7A, 10825 F12 = 0x7B, 10826 F13 = 0x7C, 10827 F14 = 0x7D, 10828 F15 = 0x7E, 10829 F16 = 0x7F, 10830 F17 = 0x80, 10831 F18 = 0x81, 10832 F19 = 0x82, 10833 F20 = 0x83, 10834 F21 = 0x84, 10835 F22 = 0x85, 10836 F23 = 0x86, 10837 F24 = 0x87, 10838 NUMLOCK = 0x90, 10839 SCROLL = 0x91, 10840 LSHIFT = 0xA0, 10841 RSHIFT = 0xA1, 10842 LCONTROL = 0xA2, 10843 RCONTROL = 0xA3, 10844 LMENU = 0xA4, 10845 RMENU = 0xA5, 10846 //static if (_WIN32_WINNT > = 0x500) { 10847 BROWSER_BACK = 0xA6, 10848 BROWSER_FORWARD = 0xA7, 10849 BROWSER_REFRESH = 0xA8, 10850 BROWSER_STOP = 0xA9, 10851 BROWSER_SEARCH = 0xAA, 10852 BROWSER_FAVORITES = 0xAB, 10853 BROWSER_HOME = 0xAC, 10854 VOLUME_MUTE = 0xAD, 10855 VOLUME_DOWN = 0xAE, 10856 VOLUME_UP = 0xAF, 10857 MEDIA_NEXT_TRACK = 0xB0, 10858 MEDIA_PREV_TRACK = 0xB1, 10859 MEDIA_STOP = 0xB2, 10860 MEDIA_PLAY_PAUSE = 0xB3, 10861 LAUNCH_MAIL = 0xB4, 10862 LAUNCH_MEDIA_SELECT = 0xB5, 10863 LAUNCH_APP1 = 0xB6, 10864 LAUNCH_APP2 = 0xB7, 10865 //} 10866 OEM_1 = 0xBA, 10867 //static if (_WIN32_WINNT > = 0x500) { 10868 OEM_PLUS = 0xBB, 10869 OEM_COMMA = 0xBC, 10870 OEM_MINUS = 0xBD, 10871 OEM_PERIOD = 0xBE, 10872 //} 10873 OEM_2 = 0xBF, 10874 OEM_3 = 0xC0, 10875 OEM_4 = 0xDB, 10876 OEM_5 = 0xDC, 10877 OEM_6 = 0xDD, 10878 OEM_7 = 0xDE, 10879 OEM_8 = 0xDF, 10880 //static if (_WIN32_WINNT > = 0x500) { 10881 OEM_102 = 0xE2, 10882 //} 10883 PROCESSKEY = 0xE5, 10884 //static if (_WIN32_WINNT > = 0x500) { 10885 PACKET = 0xE7, 10886 //} 10887 ATTN = 0xF6, 10888 CRSEL = 0xF7, 10889 EXSEL = 0xF8, 10890 EREOF = 0xF9, 10891 PLAY = 0xFA, 10892 ZOOM = 0xFB, 10893 NONAME = 0xFC, 10894 PA1 = 0xFD, 10895 OEM_CLEAR = 0xFE, 10896 } 10897 10898 } else version(OSXCocoa) { 10899 // FIXME 10900 enum Key { 10901 Escape = 0x1b, 10902 F1 = 0x70, 10903 F2 = 0x71, 10904 F3 = 0x72, 10905 F4 = 0x73, 10906 F5 = 0x74, 10907 F6 = 0x75, 10908 F7 = 0x76, 10909 F8 = 0x77, 10910 F9 = 0x78, 10911 F10 = 0x79, 10912 F11 = 0x7a, 10913 F12 = 0x7b, 10914 PrintScreen = 0x2c, 10915 ScrollLock = -2, // FIXME 10916 Pause = -3, // FIXME 10917 Grave = 0xc0, 10918 // number keys across the top of the keyboard 10919 N1 = 0x31, 10920 N2 = 0x32, 10921 N3 = 0x33, 10922 N4 = 0x34, 10923 N5 = 0x35, 10924 N6 = 0x36, 10925 N7 = 0x37, 10926 N8 = 0x38, 10927 N9 = 0x39, 10928 N0 = 0x30, 10929 Dash = 0xbd, 10930 Equals = 0xbb, 10931 Backslash = 0xdc, 10932 Backspace = 0x08, 10933 Insert = 0x2d, 10934 Home = 0x24, 10935 PageUp = 0x21, 10936 Delete = 0x2e, 10937 End = 0x23, 10938 PageDown = 0x22, 10939 Up = 0x26, 10940 Down = 0x28, 10941 Left = 0x25, 10942 Right = 0x27, 10943 10944 Tab = 0x09, 10945 Q = 0x51, 10946 W = 0x57, 10947 E = 0x45, 10948 R = 0x52, 10949 T = 0x54, 10950 Y = 0x59, 10951 U = 0x55, 10952 I = 0x49, 10953 O = 0x4f, 10954 P = 0x50, 10955 LeftBracket = 0xdb, 10956 RightBracket = 0xdd, 10957 CapsLock = 0x14, 10958 A = 0x41, 10959 S = 0x53, 10960 D = 0x44, 10961 F = 0x46, 10962 G = 0x47, 10963 H = 0x48, 10964 J = 0x4a, 10965 K = 0x4b, 10966 L = 0x4c, 10967 Semicolon = 0xba, 10968 Apostrophe = 0xde, 10969 Enter = 0x0d, 10970 Shift = 0x10, 10971 Z = 0x5a, 10972 X = 0x58, 10973 C = 0x43, 10974 V = 0x56, 10975 B = 0x42, 10976 N = 0x4e, 10977 M = 0x4d, 10978 Comma = 0xbc, 10979 Period = 0xbe, 10980 Slash = 0xbf, 10981 Shift_r = -4, // FIXME Note: this isn't sent on all computers, sometimes it just sends Shift, so don't rely on it 10982 Ctrl = 0x11, 10983 Windows = 0x5b, 10984 Alt = -5, // FIXME 10985 Space = 0x20, 10986 Alt_r = 0xffea, // ditto of shift_r 10987 Windows_r = -6, // FIXME 10988 Menu = 0x5d, 10989 Ctrl_r = -7, // FIXME 10990 10991 NumLock = 0x90, 10992 Divide = 0x6f, 10993 Multiply = 0x6a, 10994 Minus = 0x6d, 10995 Plus = 0x6b, 10996 PadEnter = -8, // FIXME 10997 // FIXME for the rest of these: 10998 Pad1 = 0xff9c, 10999 Pad2 = 0xff99, 11000 Pad3 = 0xff9b, 11001 Pad4 = 0xff96, 11002 Pad5 = 0xff9d, 11003 Pad6 = 0xff98, 11004 Pad7 = 0xff95, 11005 Pad8 = 0xff97, 11006 Pad9 = 0xff9a, 11007 Pad0 = 0xff9e, 11008 PadDot = 0xff9f, 11009 } 11010 11011 } 11012 11013 /* Additional utilities */ 11014 11015 11016 Color fromHsl(real h, real s, real l) { 11017 return arsd.color.fromHsl([h,s,l]); 11018 } 11019 11020 11021 11022 /* ********** What follows is the system-specific implementations *********/ 11023 version(Windows) { 11024 11025 11026 // helpers for making HICONs from MemoryImages 11027 class WindowsIcon { 11028 struct Win32Icon(int colorCount) { 11029 align(1): 11030 uint biSize; 11031 int biWidth; 11032 int biHeight; 11033 ushort biPlanes; 11034 ushort biBitCount; 11035 uint biCompression; 11036 uint biSizeImage; 11037 int biXPelsPerMeter; 11038 int biYPelsPerMeter; 11039 uint biClrUsed; 11040 uint biClrImportant; 11041 RGBQUAD[colorCount] biColors; 11042 /* Pixels: 11043 Uint8 pixels[] 11044 */ 11045 /* Mask: 11046 Uint8 mask[] 11047 */ 11048 11049 ubyte[4096] data; 11050 11051 void fromMemoryImage(MemoryImage mi, out int icon_len, out int width, out int height) { 11052 width = mi.width; 11053 height = mi.height; 11054 11055 auto indexedImage = cast(IndexedImage) mi; 11056 if(indexedImage is null) 11057 indexedImage = quantize(mi.getAsTrueColorImage()); 11058 11059 assert(width %8 == 0); // i don't want padding nor do i want the and mask to get fancy 11060 assert(height %4 == 0); 11061 11062 int icon_plen = height*((width+3)&~3); 11063 int icon_mlen = height*((((width+7)/8)+3)&~3); 11064 icon_len = 40+icon_plen+icon_mlen + cast(int) RGBQUAD.sizeof * colorCount; 11065 11066 biSize = 40; 11067 biWidth = width; 11068 biHeight = height*2; 11069 biPlanes = 1; 11070 biBitCount = 8; 11071 biSizeImage = icon_plen+icon_mlen; 11072 11073 int offset = 0; 11074 int andOff = icon_plen * 8; // the and offset is in bits 11075 for(int y = height - 1; y >= 0; y--) { 11076 int off2 = y * width; 11077 foreach(x; 0 .. width) { 11078 const b = indexedImage.data[off2 + x]; 11079 data[offset] = b; 11080 offset++; 11081 11082 const andBit = andOff % 8; 11083 const andIdx = andOff / 8; 11084 assert(b < indexedImage.palette.length); 11085 // this is anded to the destination, since and 0 means erase, 11086 // we want that to be opaque, and 1 for transparent 11087 auto transparent = (indexedImage.palette[b].a <= 127); 11088 data[andIdx] |= (transparent ? (1 << (7-andBit)) : 0); 11089 11090 andOff++; 11091 } 11092 11093 andOff += andOff % 32; 11094 } 11095 11096 foreach(idx, entry; indexedImage.palette) { 11097 if(entry.a > 127) { 11098 biColors[idx].rgbBlue = entry.b; 11099 biColors[idx].rgbGreen = entry.g; 11100 biColors[idx].rgbRed = entry.r; 11101 } else { 11102 biColors[idx].rgbBlue = 255; 11103 biColors[idx].rgbGreen = 255; 11104 biColors[idx].rgbRed = 255; 11105 } 11106 } 11107 11108 /* 11109 data[0..icon_plen] = getFlippedUnfilteredDatastream(png); 11110 data[icon_plen..icon_plen+icon_mlen] = getANDMask(png); 11111 //icon_win32.biColors[1] = Win32Icon.RGBQUAD(0,255,0,0); 11112 auto pngMap = fetchPaletteWin32(png); 11113 biColors[0..pngMap.length] = pngMap[]; 11114 */ 11115 } 11116 } 11117 11118 11119 Win32Icon!(256) icon_win32; 11120 11121 11122 this(MemoryImage mi) { 11123 int icon_len, width, height; 11124 11125 icon_win32.fromMemoryImage(mi, icon_len, width, height); 11126 11127 /* 11128 PNG* png = readPnpngData); 11129 PNGHeader pngh = getHeader(png); 11130 void* icon_win32; 11131 if(pngh.depth == 4) { 11132 auto i = new Win32Icon!(16); 11133 i.fromPNG(png, pngh, icon_len, width, height); 11134 icon_win32 = i; 11135 } 11136 else if(pngh.depth == 8) { 11137 auto i = new Win32Icon!(256); 11138 i.fromPNG(png, pngh, icon_len, width, height); 11139 icon_win32 = i; 11140 } else assert(0); 11141 */ 11142 11143 hIcon = CreateIconFromResourceEx(cast(ubyte*) &icon_win32, icon_len, true, 0x00030000, width, height, 0); 11144 11145 if(hIcon is null) throw new Exception("CreateIconFromResourceEx"); 11146 } 11147 11148 ~this() { 11149 if(!thisIsGuiThread) return; // FIXME: leaks if multithreaded gc 11150 DestroyIcon(hIcon); 11151 } 11152 11153 HICON hIcon; 11154 } 11155 11156 11157 11158 11159 11160 11161 alias int delegate(HWND, UINT, WPARAM, LPARAM, out int) NativeEventHandler; 11162 alias HWND NativeWindowHandle; 11163 11164 extern(Windows) 11165 LRESULT WndProc(HWND hWnd, UINT iMessage, WPARAM wParam, LPARAM lParam) nothrow { 11166 try { 11167 if(SimpleWindow.handleNativeGlobalEvent !is null) { 11168 // it returns zero if the message is handled, so we won't do anything more there 11169 // do I like that though? 11170 int mustReturn; 11171 auto ret = SimpleWindow.handleNativeGlobalEvent(hWnd, iMessage, wParam, lParam, mustReturn); 11172 if(mustReturn) 11173 return ret; 11174 } 11175 11176 if(auto window = hWnd in CapableOfHandlingNativeEvent.nativeHandleMapping) { 11177 if(window.getNativeEventHandler !is null) { 11178 int mustReturn; 11179 auto ret = window.getNativeEventHandler()(hWnd, iMessage, wParam, lParam, mustReturn); 11180 if(mustReturn) 11181 return ret; 11182 } 11183 if(auto w = cast(SimpleWindow) (*window)) 11184 return w.windowProcedure(hWnd, iMessage, wParam, lParam); 11185 else 11186 return DefWindowProc(hWnd, iMessage, wParam, lParam); 11187 } else { 11188 return DefWindowProc(hWnd, iMessage, wParam, lParam); 11189 } 11190 } catch (Exception e) { 11191 try { 11192 sdpy_abort(e); 11193 return 0; 11194 } catch(Exception e) { assert(0); } 11195 } 11196 } 11197 11198 void sdpy_abort(Throwable e) nothrow { 11199 try 11200 MessageBoxA(null, (e.toString() ~ "\0").ptr, "Exception caught in WndProc", 0); 11201 catch(Exception e) 11202 MessageBoxA(null, "Exception.toString threw too!", "Exception caught in WndProc", 0); 11203 ExitProcess(1); 11204 } 11205 11206 mixin template NativeScreenPainterImplementation() { 11207 HDC hdc; 11208 HWND hwnd; 11209 //HDC windowHdc; 11210 HBITMAP oldBmp; 11211 11212 void create(NativeWindowHandle window) { 11213 hwnd = window; 11214 11215 if(auto sw = cast(SimpleWindow) this.window) { 11216 // drawing on a window, double buffer 11217 auto windowHdc = GetDC(hwnd); 11218 11219 auto buffer = sw.impl.buffer; 11220 if(buffer is null) { 11221 hdc = windowHdc; 11222 windowDc = true; 11223 } else { 11224 hdc = CreateCompatibleDC(windowHdc); 11225 11226 ReleaseDC(hwnd, windowHdc); 11227 11228 oldBmp = SelectObject(hdc, buffer); 11229 } 11230 } else { 11231 // drawing on something else, draw directly 11232 hdc = CreateCompatibleDC(null); 11233 SelectObject(hdc, window); 11234 } 11235 11236 // X doesn't draw a text background, so neither should we 11237 SetBkMode(hdc, TRANSPARENT); 11238 11239 ensureDefaultFontLoaded(); 11240 11241 if(defaultGuiFont) { 11242 SelectObject(hdc, defaultGuiFont); 11243 // DeleteObject(defaultGuiFont); 11244 } 11245 } 11246 11247 static HFONT defaultGuiFont; 11248 static void ensureDefaultFontLoaded() { 11249 static bool triedDefaultGuiFont = false; 11250 if(!triedDefaultGuiFont) { 11251 NONCLIENTMETRICS params; 11252 params.cbSize = params.sizeof; 11253 if(SystemParametersInfo(SPI_GETNONCLIENTMETRICS, params.sizeof, ¶ms, 0)) { 11254 defaultGuiFont = CreateFontIndirect(¶ms.lfMessageFont); 11255 } 11256 triedDefaultGuiFont = true; 11257 } 11258 } 11259 11260 void setFont(OperatingSystemFont font) { 11261 if(font && font.font) { 11262 if(SelectObject(hdc, font.font) == HGDI_ERROR) { 11263 // error... how to handle tho? 11264 } 11265 } 11266 else if(defaultGuiFont) 11267 SelectObject(hdc, defaultGuiFont); 11268 } 11269 11270 arsd.color.Rectangle _clipRectangle; 11271 11272 void setClipRectangle(int x, int y, int width, int height) { 11273 _clipRectangle = arsd.color.Rectangle(Point(x, y), Size(width, height)); 11274 11275 if(width == 0 || height == 0) { 11276 SelectClipRgn(hdc, null); 11277 } else { 11278 auto region = CreateRectRgn(x, y, x + width, y + height); 11279 SelectClipRgn(hdc, region); 11280 DeleteObject(region); 11281 } 11282 } 11283 11284 11285 // just because we can on Windows... 11286 //void create(Image image); 11287 11288 void invalidateRect(Rectangle invalidRect) { 11289 RECT rect; 11290 rect.left = invalidRect.left; 11291 rect.right = invalidRect.right; 11292 rect.top = invalidRect.top; 11293 rect.bottom = invalidRect.bottom; 11294 InvalidateRect(hwnd, &rect, false); 11295 } 11296 bool manualInvalidations; 11297 11298 void dispose() { 11299 // FIXME: this.window.width/height is probably wrong 11300 // BitBlt(windowHdc, 0, 0, this.window.width, this.window.height, hdc, 0, 0, SRCCOPY); 11301 // ReleaseDC(hwnd, windowHdc); 11302 11303 // FIXME: it shouldn't invalidate the whole thing in all cases... it would be ideal to do this right 11304 if(cast(SimpleWindow) this.window) { 11305 if(!manualInvalidations) 11306 InvalidateRect(hwnd, cast(RECT*)null, false); // no need to erase bg as the whole thing gets bitblt'd ove 11307 } 11308 11309 if(originalPen !is null) 11310 SelectObject(hdc, originalPen); 11311 if(currentPen !is null) 11312 DeleteObject(currentPen); 11313 if(originalBrush !is null) 11314 SelectObject(hdc, originalBrush); 11315 if(currentBrush !is null) 11316 DeleteObject(currentBrush); 11317 11318 SelectObject(hdc, oldBmp); 11319 11320 if(windowDc) 11321 ReleaseDC(hwnd, hdc); 11322 else 11323 DeleteDC(hdc); 11324 11325 if(window.paintingFinishedDg !is null) 11326 window.paintingFinishedDg()(); 11327 } 11328 11329 bool windowDc; 11330 HPEN originalPen; 11331 HPEN currentPen; 11332 11333 Pen _activePen; 11334 11335 Color _outlineColor; 11336 11337 @property void pen(Pen p) { 11338 _activePen = p; 11339 _outlineColor = p.color; 11340 11341 HPEN pen; 11342 if(p.color.a == 0) { 11343 pen = GetStockObject(NULL_PEN); 11344 } else { 11345 int style = PS_SOLID; 11346 final switch(p.style) { 11347 case Pen.Style.Solid: 11348 style = PS_SOLID; 11349 break; 11350 case Pen.Style.Dashed: 11351 style = PS_DASH; 11352 break; 11353 case Pen.Style.Dotted: 11354 style = PS_DOT; 11355 break; 11356 } 11357 pen = CreatePen(style, p.width, RGB(p.color.r, p.color.g, p.color.b)); 11358 } 11359 auto orig = SelectObject(hdc, pen); 11360 if(originalPen is null) 11361 originalPen = orig; 11362 11363 if(currentPen !is null) 11364 DeleteObject(currentPen); 11365 11366 currentPen = pen; 11367 11368 // the outline is like a foreground since it's done that way on X 11369 SetTextColor(hdc, RGB(p.color.r, p.color.g, p.color.b)); 11370 11371 } 11372 11373 @property void rasterOp(RasterOp op) { 11374 int mode; 11375 final switch(op) { 11376 case RasterOp.normal: 11377 mode = R2_COPYPEN; 11378 break; 11379 case RasterOp.xor: 11380 mode = R2_XORPEN; 11381 break; 11382 } 11383 SetROP2(hdc, mode); 11384 } 11385 11386 HBRUSH originalBrush; 11387 HBRUSH currentBrush; 11388 Color _fillColor = Color(1, 1, 1, 1); // what are the odds that they'd set this?? 11389 @property void fillColor(Color c) { 11390 if(c == _fillColor) 11391 return; 11392 _fillColor = c; 11393 HBRUSH brush; 11394 if(c.a == 0) { 11395 brush = GetStockObject(HOLLOW_BRUSH); 11396 } else { 11397 brush = CreateSolidBrush(RGB(c.r, c.g, c.b)); 11398 } 11399 auto orig = SelectObject(hdc, brush); 11400 if(originalBrush is null) 11401 originalBrush = orig; 11402 11403 if(currentBrush !is null) 11404 DeleteObject(currentBrush); 11405 11406 currentBrush = brush; 11407 11408 // background color is NOT set because X doesn't draw text backgrounds 11409 // SetBkColor(hdc, RGB(255, 255, 255)); 11410 } 11411 11412 void drawImage(int x, int y, Image i, int ix, int iy, int w, int h) { 11413 BITMAP bm; 11414 11415 HDC hdcMem = CreateCompatibleDC(hdc); 11416 HBITMAP hbmOld = SelectObject(hdcMem, i.handle); 11417 11418 GetObject(i.handle, bm.sizeof, &bm); 11419 11420 // or should I AlphaBlend!??!?! 11421 BitBlt(hdc, x, y, w /* bm.bmWidth */, /*bm.bmHeight*/ h, hdcMem, ix, iy, SRCCOPY); 11422 11423 SelectObject(hdcMem, hbmOld); 11424 DeleteDC(hdcMem); 11425 } 11426 11427 void drawPixmap(Sprite s, int x, int y, int ix, int iy, int w, int h) { 11428 BITMAP bm; 11429 11430 HDC hdcMem = CreateCompatibleDC(hdc); 11431 HBITMAP hbmOld = SelectObject(hdcMem, s.handle); 11432 11433 GetObject(s.handle, bm.sizeof, &bm); 11434 11435 version(CRuntime_DigitalMars) goto noalpha; 11436 11437 // or should I AlphaBlend!??!?! note it is supposed to be premultiplied http://www.fengyuan.com/article/alphablend.html 11438 if(s.enableAlpha) { 11439 auto dw = w ? w : bm.bmWidth; 11440 auto dh = h ? h : bm.bmHeight; 11441 BLENDFUNCTION bf; 11442 bf.BlendOp = AC_SRC_OVER; 11443 bf.SourceConstantAlpha = 255; 11444 bf.AlphaFormat = AC_SRC_ALPHA; 11445 AlphaBlend(hdc, x, y, dw, dh, hdcMem, ix, iy, dw, dh, bf); 11446 } else { 11447 noalpha: 11448 BitBlt(hdc, x, y, w ? w : bm.bmWidth, h ? h : bm.bmHeight, hdcMem, ix, iy, SRCCOPY); 11449 } 11450 11451 SelectObject(hdcMem, hbmOld); 11452 DeleteDC(hdcMem); 11453 } 11454 11455 Size textSize(scope const(char)[] text) { 11456 bool dummyX; 11457 if(text.length == 0) { 11458 text = " "; 11459 dummyX = true; 11460 } 11461 RECT rect; 11462 WCharzBuffer buffer = WCharzBuffer(text); 11463 DrawTextW(hdc, buffer.ptr, cast(int) buffer.length, &rect, DT_CALCRECT | DT_NOPREFIX); 11464 return Size(dummyX ? 0 : rect.right, rect.bottom); 11465 } 11466 11467 void drawText(int x, int y, int x2, int y2, scope const(char)[] text, uint alignment) { 11468 if(text.length && text[$-1] == '\n') 11469 text = text[0 .. $-1]; // tailing newlines are weird on windows... 11470 if(text.length && text[$-1] == '\r') 11471 text = text[0 .. $-1]; 11472 11473 WCharzBuffer buffer = WCharzBuffer(text, WindowsStringConversionFlags.convertNewLines); 11474 if(x2 == 0 && y2 == 0) { 11475 TextOutW(hdc, x, y, buffer.ptr, cast(int) buffer.length); 11476 } else { 11477 RECT rect; 11478 rect.left = x; 11479 rect.top = y; 11480 rect.right = x2; 11481 rect.bottom = y2; 11482 11483 uint mode = DT_LEFT; 11484 if(alignment & TextAlignment.Right) 11485 mode = DT_RIGHT; 11486 else if(alignment & TextAlignment.Center) 11487 mode = DT_CENTER; 11488 11489 // FIXME: vcenter on windows only works with single line, but I want it to work in all cases 11490 if(alignment & TextAlignment.VerticalCenter) 11491 mode |= DT_VCENTER | DT_SINGLELINE; 11492 11493 DrawTextW(hdc, buffer.ptr, cast(int) buffer.length, &rect, mode | DT_NOPREFIX); 11494 } 11495 11496 /* 11497 uint mode; 11498 11499 if(alignment & TextAlignment.Center) 11500 mode = TA_CENTER; 11501 11502 SetTextAlign(hdc, mode); 11503 */ 11504 } 11505 11506 int fontHeight() { 11507 TEXTMETRIC metric; 11508 if(GetTextMetricsW(hdc, &metric)) { 11509 return metric.tmHeight; 11510 } 11511 11512 return 16; // idk just guessing here, maybe we should throw 11513 } 11514 11515 void drawPixel(int x, int y) { 11516 SetPixel(hdc, x, y, RGB(_activePen.color.r, _activePen.color.g, _activePen.color.b)); 11517 } 11518 11519 // The basic shapes, outlined 11520 11521 void drawLine(int x1, int y1, int x2, int y2) { 11522 MoveToEx(hdc, x1, y1, null); 11523 LineTo(hdc, x2, y2); 11524 } 11525 11526 void drawRectangle(int x, int y, int width, int height) { 11527 // FIXME: with a wider pen this might not draw quite right. im not sure. 11528 gdi.Rectangle(hdc, x, y, x + width, y + height); 11529 } 11530 11531 /// Arguments are the points of the bounding rectangle 11532 void drawEllipse(int x1, int y1, int x2, int y2) { 11533 Ellipse(hdc, x1, y1, x2, y2); 11534 } 11535 11536 void drawArc(int x1, int y1, int width, int height, int start, int finish) { 11537 if((start % (360*64)) == (finish % (360*64))) 11538 drawEllipse(x1, y1, x1 + width, y1 + height); 11539 else { 11540 import core.stdc.math; 11541 float startAngle = cast(float) start / 64.0 / 180.0 * 3.14159265358979323; 11542 float endAngle = cast(float) finish / 64.0 / 180.0 * 3.14159265358979323; 11543 11544 auto c1 = cast(int) roundf(cos(startAngle) * width / 2 + x1 + width / 2); 11545 auto c2 = cast(int) roundf(-sin(startAngle) * height / 2 + y1 + height / 2); 11546 auto c3 = cast(int) roundf(cos(endAngle) * width / 2 + x1 + width / 2); 11547 auto c4 = cast(int) roundf(-sin(endAngle) * height / 2 + y1 + height / 2); 11548 11549 if(_activePen.color.a) 11550 Arc(hdc, x1, y1, x1 + width + 1, y1 + height + 1, c1, c2, c3, c4); 11551 if(_fillColor.a) 11552 Pie(hdc, x1, y1, x1 + width + 1, y1 + height + 1, c1, c2, c3, c4); 11553 } 11554 } 11555 11556 void drawPolygon(Point[] vertexes) { 11557 POINT[] points; 11558 points.length = vertexes.length; 11559 11560 foreach(i, p; vertexes) { 11561 points[i].x = p.x; 11562 points[i].y = p.y; 11563 } 11564 11565 Polygon(hdc, points.ptr, cast(int) points.length); 11566 } 11567 } 11568 11569 11570 // Mix this into the SimpleWindow class 11571 mixin template NativeSimpleWindowImplementation() { 11572 int curHidden = 0; // counter 11573 __gshared static bool[string] knownWinClasses; 11574 static bool altPressed = false; 11575 11576 HANDLE oldCursor; 11577 11578 void hideCursor () { 11579 if(curHidden == 0) 11580 oldCursor = SetCursor(null); 11581 ++curHidden; 11582 } 11583 11584 void showCursor () { 11585 --curHidden; 11586 if(curHidden == 0) { 11587 SetCursor(currentCursor is null ? oldCursor : currentCursor); // show it immediately without waiting for mouse movement 11588 } 11589 } 11590 11591 11592 int minWidth = 0, minHeight = 0, maxWidth = int.max, maxHeight = int.max; 11593 11594 void setMinSize (int minwidth, int minheight) { 11595 minWidth = minwidth; 11596 minHeight = minheight; 11597 } 11598 void setMaxSize (int maxwidth, int maxheight) { 11599 maxWidth = maxwidth; 11600 maxHeight = maxheight; 11601 } 11602 11603 // FIXME i'm not sure that Windows has this functionality 11604 // though it is nonessential anyway. 11605 void setResizeGranularity (int granx, int grany) {} 11606 11607 ScreenPainter getPainter(bool manualInvalidations) { 11608 return ScreenPainter(this, hwnd, manualInvalidations); 11609 } 11610 11611 HBITMAP buffer; 11612 11613 void setTitle(string title) { 11614 WCharzBuffer bfr = WCharzBuffer(title); 11615 SetWindowTextW(hwnd, bfr.ptr); 11616 } 11617 11618 string getTitle() { 11619 auto len = GetWindowTextLengthW(hwnd); 11620 if (!len) 11621 return null; 11622 wchar[256] tmpBuffer; 11623 wchar[] buffer = (len <= tmpBuffer.length) ? tmpBuffer[] : new wchar[len]; 11624 auto len2 = GetWindowTextW(hwnd, buffer.ptr, cast(int) buffer.length); 11625 auto str = buffer[0 .. len2]; 11626 return makeUtf8StringFromWindowsString(str); 11627 } 11628 11629 void move(int x, int y) { 11630 RECT rect; 11631 GetWindowRect(hwnd, &rect); 11632 // move it while maintaining the same size... 11633 MoveWindow(hwnd, x, y, rect.right - rect.left, rect.bottom - rect.top, true); 11634 } 11635 11636 void resize(int w, int h) { 11637 RECT rect; 11638 GetWindowRect(hwnd, &rect); 11639 11640 RECT client; 11641 GetClientRect(hwnd, &client); 11642 11643 rect.right = rect.right - client.right + w; 11644 rect.bottom = rect.bottom - client.bottom + h; 11645 11646 // same position, new size for the client rectangle 11647 MoveWindow(hwnd, rect.left, rect.top, rect.right, rect.bottom, true); 11648 11649 updateOpenglViewportIfNeeded(w, h); 11650 } 11651 11652 void moveResize (int x, int y, int w, int h) { 11653 // what's given is the client rectangle, we need to adjust 11654 11655 RECT rect; 11656 rect.left = x; 11657 rect.top = y; 11658 rect.right = w + x; 11659 rect.bottom = h + y; 11660 if(!AdjustWindowRect(&rect, GetWindowLong(hwnd, GWL_STYLE), GetMenu(hwnd) !is null)) 11661 throw new Exception("AdjustWindowRect"); 11662 11663 MoveWindow(hwnd, rect.left, rect.top, rect.right - rect.left, rect.bottom - rect.top, true); 11664 updateOpenglViewportIfNeeded(w, h); 11665 if (windowResized !is null) windowResized(w, h); 11666 } 11667 11668 version(without_opengl) {} else { 11669 HGLRC ghRC; 11670 HDC ghDC; 11671 } 11672 11673 void createWindow(int width, int height, string title, OpenGlOptions opengl, SimpleWindow parent) { 11674 string cnamec; 11675 if (sdpyWindowClassStr is null) loadBinNameToWindowClassName(); 11676 if (sdpyWindowClassStr is null || sdpyWindowClassStr[0] == 0) { 11677 cnamec = "DSimpleWindow"; 11678 } else { 11679 cnamec = sdpyWindowClass; 11680 } 11681 11682 WCharzBuffer cn = WCharzBuffer(cnamec); 11683 11684 HINSTANCE hInstance = cast(HINSTANCE) GetModuleHandle(null); 11685 11686 if(cnamec !in knownWinClasses) { 11687 WNDCLASSEX wc; 11688 11689 // FIXME: I might be able to use cbWndExtra to hold the pointer back 11690 // to the object. Maybe. 11691 wc.cbSize = wc.sizeof; 11692 wc.cbClsExtra = 0; 11693 wc.cbWndExtra = 0; 11694 wc.hbrBackground = cast(HBRUSH) (COLOR_WINDOW+1); // GetStockObject(WHITE_BRUSH); 11695 wc.hCursor = LoadCursorW(null, IDC_ARROW); 11696 wc.hIcon = LoadIcon(hInstance, null); 11697 wc.hInstance = hInstance; 11698 wc.lpfnWndProc = &WndProc; 11699 wc.lpszClassName = cn.ptr; 11700 wc.hIconSm = null; 11701 wc.style = CS_HREDRAW | CS_VREDRAW | CS_DBLCLKS; 11702 if(!RegisterClassExW(&wc)) 11703 throw new WindowsApiException("RegisterClassExW"); 11704 knownWinClasses[cnamec] = true; 11705 } 11706 11707 int style; 11708 uint flags = WS_EX_ACCEPTFILES; // accept drag-drop files 11709 11710 // FIXME: windowType and customizationFlags 11711 final switch(windowType) { 11712 case WindowTypes.normal: 11713 if(resizability == Resizability.fixedSize) { 11714 style = WS_SYSMENU | WS_OVERLAPPED | WS_CAPTION; 11715 } else { 11716 style = WS_OVERLAPPEDWINDOW; 11717 } 11718 break; 11719 case WindowTypes.undecorated: 11720 style = WS_POPUP | WS_SYSMENU; 11721 break; 11722 case WindowTypes.eventOnly: 11723 _hidden = true; 11724 break; 11725 case WindowTypes.dropdownMenu: 11726 case WindowTypes.popupMenu: 11727 case WindowTypes.notification: 11728 style = WS_POPUP; 11729 flags |= WS_EX_NOACTIVATE; 11730 break; 11731 case WindowTypes.nestedChild: 11732 style = WS_CHILD; 11733 break; 11734 case WindowTypes.minimallyWrapped: 11735 assert(0, "construct minimally wrapped through the other ctor overlad"); 11736 } 11737 11738 if ((customizationFlags & WindowFlags.extraComposite) != 0) 11739 flags |= WS_EX_LAYERED; // composite window for better performance and effects support 11740 11741 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 11742 CW_USEDEFAULT, CW_USEDEFAULT, width, height, 11743 parent is null ? null : parent.impl.hwnd, null, hInstance, null); 11744 11745 if ((customizationFlags & WindowFlags.extraComposite) != 0) 11746 setOpacity(255); 11747 11748 SimpleWindow.nativeMapping[hwnd] = this; 11749 CapableOfHandlingNativeEvent.nativeHandleMapping[hwnd] = this; 11750 11751 if(windowType == WindowTypes.eventOnly) 11752 return; 11753 11754 HDC hdc = GetDC(hwnd); 11755 11756 11757 version(without_opengl) {} 11758 else { 11759 if(opengl == OpenGlOptions.yes) { 11760 if(!openGlLibrariesSuccessfullyLoaded) throw new Exception("OpenGL libraries did not load"); 11761 static if (SdpyIsUsingIVGLBinds) {if (glbindGetProcAddress("glHint") is null) assert(0, "GL: error loading OpenGL"); } // loads all necessary functions 11762 static if (SdpyIsUsingIVGLBinds) import iv.glbinds; // override druntime windows imports 11763 ghDC = hdc; 11764 PIXELFORMATDESCRIPTOR pfd; 11765 11766 pfd.nSize = PIXELFORMATDESCRIPTOR.sizeof; 11767 pfd.nVersion = 1; 11768 pfd.dwFlags = PFD_DRAW_TO_WINDOW | PFD_SUPPORT_OPENGL | PFD_DOUBLEBUFFER; 11769 pfd.dwLayerMask = PFD_MAIN_PLANE; 11770 pfd.iPixelType = PFD_TYPE_RGBA; 11771 pfd.cColorBits = 24; 11772 pfd.cDepthBits = 24; 11773 pfd.cAccumBits = 0; 11774 pfd.cStencilBits = 8; // any reasonable OpenGL implementation should support this anyway 11775 11776 auto pixelformat = ChoosePixelFormat(hdc, &pfd); 11777 11778 if (pixelformat == 0) 11779 throw new WindowsApiException("ChoosePixelFormat"); 11780 11781 if (SetPixelFormat(hdc, pixelformat, &pfd) == 0) 11782 throw new WindowsApiException("SetPixelFormat"); 11783 11784 if (sdpyOpenGLContextVersion && wglCreateContextAttribsARB is null) { 11785 // windoze is idiotic: we have to have OpenGL context to get function addresses 11786 // so we will create fake context to get that stupid address 11787 auto tmpcc = wglCreateContext(ghDC); 11788 if (tmpcc !is null) { 11789 scope(exit) { wglMakeCurrent(ghDC, null); wglDeleteContext(tmpcc); } 11790 wglMakeCurrent(ghDC, tmpcc); 11791 wglInitOtherFunctions(); 11792 } 11793 } 11794 11795 if (wglCreateContextAttribsARB !is null && sdpyOpenGLContextVersion) { 11796 int[9] contextAttribs = [ 11797 WGL_CONTEXT_MAJOR_VERSION_ARB, (sdpyOpenGLContextVersion>>8), 11798 WGL_CONTEXT_MINOR_VERSION_ARB, (sdpyOpenGLContextVersion&0xff), 11799 WGL_CONTEXT_PROFILE_MASK_ARB, (sdpyOpenGLContextCompatible ? WGL_CONTEXT_COMPATIBILITY_PROFILE_BIT_ARB : WGL_CONTEXT_CORE_PROFILE_BIT_ARB), 11800 // for modern context, set "forward compatibility" flag too 11801 (sdpyOpenGLContextCompatible ? 0/*None*/ : WGL_CONTEXT_FLAGS_ARB), WGL_CONTEXT_FORWARD_COMPATIBLE_BIT_ARB, 11802 0/*None*/, 11803 ]; 11804 ghRC = wglCreateContextAttribsARB(ghDC, null, contextAttribs.ptr); 11805 if (ghRC is null && sdpyOpenGLContextAllowFallback) { 11806 // activate fallback mode 11807 // 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; 11808 ghRC = wglCreateContext(ghDC); 11809 } 11810 if (ghRC is null) 11811 throw new WindowsApiException("wglCreateContextAttribsARB"); 11812 } else { 11813 // try to do at least something 11814 if (sdpyOpenGLContextAllowFallback || sdpyOpenGLContextVersion == 0) { 11815 sdpyOpenGLContextVersion = 0; 11816 ghRC = wglCreateContext(ghDC); 11817 } 11818 if (ghRC is null) 11819 throw new WindowsApiException("wglCreateContext"); 11820 } 11821 } 11822 } 11823 11824 if(opengl == OpenGlOptions.no) { 11825 buffer = CreateCompatibleBitmap(hdc, width, height); 11826 11827 auto hdcBmp = CreateCompatibleDC(hdc); 11828 // make sure it's filled with a blank slate 11829 auto oldBmp = SelectObject(hdcBmp, buffer); 11830 auto oldBrush = SelectObject(hdcBmp, GetStockObject(WHITE_BRUSH)); 11831 auto oldPen = SelectObject(hdcBmp, GetStockObject(WHITE_PEN)); 11832 gdi.Rectangle(hdcBmp, 0, 0, width, height); 11833 SelectObject(hdcBmp, oldBmp); 11834 SelectObject(hdcBmp, oldBrush); 11835 SelectObject(hdcBmp, oldPen); 11836 DeleteDC(hdcBmp); 11837 11838 bmpWidth = width; 11839 bmpHeight = height; 11840 11841 ReleaseDC(hwnd, hdc); // we keep this in opengl mode since it is a class member now 11842 } 11843 11844 // We want the window's client area to match the image size 11845 RECT rcClient, rcWindow; 11846 POINT ptDiff; 11847 GetClientRect(hwnd, &rcClient); 11848 GetWindowRect(hwnd, &rcWindow); 11849 ptDiff.x = (rcWindow.right - rcWindow.left) - rcClient.right; 11850 ptDiff.y = (rcWindow.bottom - rcWindow.top) - rcClient.bottom; 11851 MoveWindow(hwnd,rcWindow.left, rcWindow.top, width + ptDiff.x, height + ptDiff.y, true); 11852 11853 if ((customizationFlags&WindowFlags.dontAutoShow) == 0) { 11854 ShowWindow(hwnd, SW_SHOWNORMAL); 11855 } else { 11856 _hidden = true; 11857 } 11858 this._visibleForTheFirstTimeCalled = false; // hack! 11859 } 11860 11861 11862 void dispose() { 11863 if(buffer) 11864 DeleteObject(buffer); 11865 } 11866 11867 void closeWindow() { 11868 DestroyWindow(hwnd); 11869 } 11870 11871 bool setOpacity(ubyte alpha) { 11872 return SetLayeredWindowAttributes(hwnd, 0, alpha, LWA_ALPHA) == TRUE; 11873 } 11874 11875 HANDLE currentCursor; 11876 11877 // returns zero if it recognized the event 11878 static int triggerEvents(HWND hwnd, uint msg, WPARAM wParam, LPARAM lParam, int offsetX, int offsetY, SimpleWindow wind) { 11879 MouseEvent mouse; 11880 11881 void mouseEvent(bool isScreen, ulong mods) { 11882 auto x = LOWORD(lParam); 11883 auto y = HIWORD(lParam); 11884 if(isScreen) { 11885 POINT p; 11886 p.x = x; 11887 p.y = y; 11888 ScreenToClient(hwnd, &p); 11889 x = cast(ushort) p.x; 11890 y = cast(ushort) p.y; 11891 } 11892 11893 if(wind.resizability == Resizability.automaticallyScaleIfPossible) { 11894 x = cast(ushort)( x * wind._virtualWidth / wind._width ); 11895 y = cast(ushort)( y * wind._virtualHeight / wind._height ); 11896 } 11897 11898 mouse.x = x + offsetX; 11899 mouse.y = y + offsetY; 11900 11901 wind.mdx(mouse); 11902 mouse.modifierState = cast(int) mods; 11903 mouse.window = wind; 11904 11905 if(wind.handleMouseEvent) 11906 wind.handleMouseEvent(mouse); 11907 } 11908 11909 switch(msg) { 11910 case WM_GETMINMAXINFO: 11911 MINMAXINFO* mmi = cast(MINMAXINFO*) lParam; 11912 11913 if(wind.minWidth > 0) { 11914 RECT rect; 11915 rect.left = 100; 11916 rect.top = 100; 11917 rect.right = wind.minWidth + 100; 11918 rect.bottom = wind.minHeight + 100; 11919 if(!AdjustWindowRect(&rect, GetWindowLong(wind.hwnd, GWL_STYLE), GetMenu(wind.hwnd) !is null)) 11920 throw new WindowsApiException("AdjustWindowRect"); 11921 11922 mmi.ptMinTrackSize.x = rect.right - rect.left; 11923 mmi.ptMinTrackSize.y = rect.bottom - rect.top; 11924 } 11925 11926 if(wind.maxWidth < int.max) { 11927 RECT rect; 11928 rect.left = 100; 11929 rect.top = 100; 11930 rect.right = wind.maxWidth + 100; 11931 rect.bottom = wind.maxHeight + 100; 11932 if(!AdjustWindowRect(&rect, GetWindowLong(wind.hwnd, GWL_STYLE), GetMenu(wind.hwnd) !is null)) 11933 throw new WindowsApiException("AdjustWindowRect"); 11934 11935 mmi.ptMaxTrackSize.x = rect.right - rect.left; 11936 mmi.ptMaxTrackSize.y = rect.bottom - rect.top; 11937 } 11938 break; 11939 case WM_CHAR: 11940 wchar c = cast(wchar) wParam; 11941 if(wind.handleCharEvent) 11942 wind.handleCharEvent(cast(dchar) c); 11943 break; 11944 case WM_SETFOCUS: 11945 case WM_KILLFOCUS: 11946 wind._focused = (msg == WM_SETFOCUS); 11947 if (msg == WM_SETFOCUS) altPressed = false; //k8: reset alt state on defocus (it is better than nothing...) 11948 if(wind.onFocusChange) 11949 wind.onFocusChange(msg == WM_SETFOCUS); 11950 break; 11951 11952 case WM_SYSKEYDOWN: 11953 goto case; 11954 case WM_SYSKEYUP: 11955 if(lParam & (1 << 29)) { 11956 goto case; 11957 } else { 11958 // no window has keyboard focus 11959 goto default; 11960 } 11961 case WM_KEYDOWN: 11962 case WM_KEYUP: 11963 KeyEvent ev; 11964 ev.key = cast(Key) wParam; 11965 ev.pressed = (msg == WM_KEYDOWN || msg == WM_SYSKEYDOWN); 11966 if (wParam == 0x12) ev.key = Key.Alt; // windows does it this way 11967 11968 ev.hardwareCode = (lParam & 0xff0000) >> 16; 11969 11970 if(GetKeyState(Key.Shift)&0x8000 || GetKeyState(Key.Shift_r)&0x8000) 11971 ev.modifierState |= ModifierState.shift; 11972 //k8: this doesn't work; thanks for nothing, windows 11973 /*if(GetKeyState(Key.Alt)&0x8000 || GetKeyState(Key.Alt_r)&0x8000) 11974 ev.modifierState |= ModifierState.alt;*/ 11975 // this never seems to actually be set 11976 // if (lParam & 0x2000 /* KF_ALTDOWN */) ev.modifierState |= ModifierState.alt; else ev.modifierState &= ~ModifierState.alt; 11977 11978 if (wParam == 0x12) { 11979 altPressed = (msg == WM_SYSKEYDOWN); 11980 } 11981 11982 if(msg == WM_KEYDOWN || msg == WM_KEYUP) { 11983 altPressed = false; 11984 } 11985 // sdpyPrintDebugString(altPressed ? "alt down" : " up "); 11986 11987 if (altPressed) ev.modifierState |= ModifierState.alt; else ev.modifierState &= ~ModifierState.alt; 11988 if(GetKeyState(Key.Ctrl)&0x8000 || GetKeyState(Key.Ctrl_r)&0x8000) 11989 ev.modifierState |= ModifierState.ctrl; 11990 if(GetKeyState(Key.Windows)&0x8000 || GetKeyState(Key.Windows_r)&0x8000) 11991 ev.modifierState |= ModifierState.windows; 11992 if(GetKeyState(Key.NumLock)) 11993 ev.modifierState |= ModifierState.numLock; 11994 if(GetKeyState(Key.CapsLock)) 11995 ev.modifierState |= ModifierState.capsLock; 11996 11997 /+ 11998 // we always want to send the character too, so let's convert it 11999 ubyte[256] state; 12000 wchar[16] buffer; 12001 GetKeyboardState(state.ptr); 12002 ToUnicodeEx(wParam, lParam, state.ptr, buffer.ptr, buffer.length, 0, null); 12003 12004 foreach(dchar d; buffer) { 12005 ev.character = d; 12006 break; 12007 } 12008 +/ 12009 12010 ev.window = wind; 12011 if(wind.handleKeyEvent) 12012 wind.handleKeyEvent(ev); 12013 break; 12014 case 0x020a /*WM_MOUSEWHEEL*/: 12015 // send click 12016 mouse.type = cast(MouseEventType) 1; 12017 mouse.button = ((GET_WHEEL_DELTA_WPARAM(wParam) > 0) ? MouseButton.wheelUp : MouseButton.wheelDown); 12018 mouseEvent(true, LOWORD(wParam)); 12019 12020 // also send release 12021 mouse.type = cast(MouseEventType) 2; 12022 mouse.button = ((GET_WHEEL_DELTA_WPARAM(wParam) > 0) ? MouseButton.wheelUp : MouseButton.wheelDown); 12023 mouseEvent(true, LOWORD(wParam)); 12024 break; 12025 case WM_MOUSEMOVE: 12026 mouse.type = cast(MouseEventType) 0; 12027 mouseEvent(false, wParam); 12028 break; 12029 case WM_LBUTTONDOWN: 12030 case WM_LBUTTONDBLCLK: 12031 mouse.type = cast(MouseEventType) 1; 12032 mouse.button = MouseButton.left; 12033 mouse.doubleClick = msg == WM_LBUTTONDBLCLK; 12034 mouseEvent(false, wParam); 12035 break; 12036 case WM_LBUTTONUP: 12037 mouse.type = cast(MouseEventType) 2; 12038 mouse.button = MouseButton.left; 12039 mouseEvent(false, wParam); 12040 break; 12041 case WM_RBUTTONDOWN: 12042 case WM_RBUTTONDBLCLK: 12043 mouse.type = cast(MouseEventType) 1; 12044 mouse.button = MouseButton.right; 12045 mouse.doubleClick = msg == WM_RBUTTONDBLCLK; 12046 mouseEvent(false, wParam); 12047 break; 12048 case WM_RBUTTONUP: 12049 mouse.type = cast(MouseEventType) 2; 12050 mouse.button = MouseButton.right; 12051 mouseEvent(false, wParam); 12052 break; 12053 case WM_MBUTTONDOWN: 12054 case WM_MBUTTONDBLCLK: 12055 mouse.type = cast(MouseEventType) 1; 12056 mouse.button = MouseButton.middle; 12057 mouse.doubleClick = msg == WM_MBUTTONDBLCLK; 12058 mouseEvent(false, wParam); 12059 break; 12060 case WM_MBUTTONUP: 12061 mouse.type = cast(MouseEventType) 2; 12062 mouse.button = MouseButton.middle; 12063 mouseEvent(false, wParam); 12064 break; 12065 case WM_XBUTTONDOWN: 12066 case WM_XBUTTONDBLCLK: 12067 mouse.type = cast(MouseEventType) 1; 12068 mouse.button = HIWORD(wParam) == 1 ? MouseButton.backButton : MouseButton.forwardButton; 12069 mouse.doubleClick = msg == WM_XBUTTONDBLCLK; 12070 mouseEvent(false, wParam); 12071 return 1; // MSDN says special treatment here, return TRUE to bypass simulation programs 12072 case WM_XBUTTONUP: 12073 mouse.type = cast(MouseEventType) 2; 12074 mouse.button = HIWORD(wParam) == 1 ? MouseButton.backButton : MouseButton.forwardButton; 12075 mouseEvent(false, wParam); 12076 return 1; // see: https://msdn.microsoft.com/en-us/library/windows/desktop/ms646246(v=vs.85).aspx 12077 12078 default: return 1; 12079 } 12080 return 0; 12081 } 12082 12083 HWND hwnd; 12084 private int oldWidth; 12085 private int oldHeight; 12086 private bool inSizeMove; 12087 12088 /++ 12089 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. 12090 12091 History: 12092 Added November 23, 2021 12093 12094 Not fully stable, may be moved out of the impl struct. 12095 12096 Default value changed to `true` on February 15, 2021 12097 +/ 12098 bool doLiveResizing = true; 12099 12100 package int bmpWidth; 12101 package int bmpHeight; 12102 12103 // the extern(Windows) wndproc should just forward to this 12104 LRESULT windowProcedure(HWND hwnd, uint msg, WPARAM wParam, LPARAM lParam) { 12105 try { 12106 assert(hwnd is this.hwnd); 12107 12108 if(triggerEvents(hwnd, msg, wParam, lParam, 0, 0, this)) 12109 switch(msg) { 12110 case WM_MENUCHAR: // menu active but key not associated with a thing. 12111 // you would ideally use this for like a search function but sdpy not that ideally designed. alas. 12112 // The main things we can do are select, execute, close, or ignore 12113 // the default is ignore, but it doesn't *just* ignore it - it also dings an audio alert to 12114 // the user. This can be a bit annoying for sdpy things so instead im overriding and setting it 12115 // to close, which can be really annoying when you hit the wrong button. but meh i think for sdpy 12116 // that's the lesser bad choice rn. Can always override by returning true in triggerEvents.... 12117 12118 // returns the value in the *high order word* of the return value 12119 // hence the << 16 12120 return 1 << 16; // MNC_CLOSE, close the menu without dinging at the user 12121 case WM_SETCURSOR: 12122 if(cast(HWND) wParam !is hwnd) 12123 return 0; // further processing elsewhere 12124 12125 if(LOWORD(lParam) == HTCLIENT && (this.curHidden > 0 || currentCursor !is null)) { 12126 SetCursor(this.curHidden > 0 ? null : currentCursor); 12127 return 1; 12128 } else { 12129 return DefWindowProc(hwnd, msg, wParam, lParam); 12130 } 12131 //break; 12132 12133 case WM_CLOSE: 12134 if (this.closeQuery !is null) this.closeQuery(); else this.close(); 12135 break; 12136 case WM_DESTROY: 12137 if (this.onDestroyed !is null) try { this.onDestroyed(); } catch (Exception e) {} // sorry 12138 SimpleWindow.nativeMapping.remove(hwnd); 12139 CapableOfHandlingNativeEvent.nativeHandleMapping.remove(hwnd); 12140 12141 bool anyImportant = false; 12142 foreach(SimpleWindow w; SimpleWindow.nativeMapping) 12143 if(w.beingOpenKeepsAppOpen) { 12144 anyImportant = true; 12145 break; 12146 } 12147 if(!anyImportant) { 12148 PostQuitMessage(0); 12149 } 12150 break; 12151 case 0x02E0 /*WM_DPICHANGED*/: 12152 this.actualDpi_ = LOWORD(wParam); // hiword is the y param but it is the same per docs 12153 12154 RECT* prcNewWindow = cast(RECT*)lParam; 12155 // docs say this is the recommended position and we should honor it 12156 SetWindowPos(hwnd, 12157 null, 12158 prcNewWindow.left, 12159 prcNewWindow.top, 12160 prcNewWindow.right - prcNewWindow.left, 12161 prcNewWindow.bottom - prcNewWindow.top, 12162 SWP_NOZORDER | SWP_NOACTIVATE); 12163 12164 // doing this because of https://github.com/microsoft/Windows-classic-samples/blob/main/Samples/DPIAwarenessPerWindow/client/DpiAwarenessContext.cpp 12165 // im not sure it is completely correct 12166 // but without it the tabs and such do look weird as things change. 12167 if(SystemParametersInfoForDpi) { 12168 LOGFONT lfText; 12169 SystemParametersInfoForDpi(SPI_GETICONTITLELOGFONT, lfText.sizeof, &lfText, FALSE, this.actualDpi_); 12170 HFONT hFontNew = CreateFontIndirect(&lfText); 12171 if (hFontNew) 12172 { 12173 //DeleteObject(hFontOld); 12174 static extern(Windows) BOOL helper(HWND hWnd, LPARAM lParam) { 12175 SendMessage(hWnd, WM_SETFONT, cast(WPARAM)lParam, MAKELPARAM(TRUE, 0)); 12176 return TRUE; 12177 } 12178 EnumChildWindows(hwnd, &helper, cast(LPARAM) hFontNew); 12179 } 12180 } 12181 12182 if(this.onDpiChanged) 12183 this.onDpiChanged(); 12184 break; 12185 case WM_ENTERIDLE: 12186 // when a menu is up, it stops normal event processing (modal message loop) 12187 // but this at least gives us a chance to SOMETIMES catch up 12188 // FIXME: I can use SetTimer while idle to keep working i think... but idk when i'd destroy it. 12189 SimpleWindow.processAllCustomEvents; 12190 SimpleWindow.processAllCustomEvents; 12191 SleepEx(0, true); 12192 break; 12193 case WM_SIZE: 12194 if(wParam == 1 /* SIZE_MINIMIZED */) 12195 break; 12196 _width = LOWORD(lParam); 12197 _height = HIWORD(lParam); 12198 12199 // I want to avoid tearing in the windows (my code is inefficient 12200 // so this is a hack around that) so while sizing, we don't trigger, 12201 // but we do want to trigger on events like mazimize. 12202 if(!inSizeMove || doLiveResizing) 12203 goto size_changed; 12204 break; 12205 /+ 12206 case WM_SIZING: 12207 import std.stdio; writeln("size"); 12208 break; 12209 +/ 12210 // I don't like the tearing I get when redrawing on WM_SIZE 12211 // (I know there's other ways to fix that but I don't like that behavior anyway) 12212 // so instead it is going to redraw only at the end of a size. 12213 case 0x0231: /* WM_ENTERSIZEMOVE */ 12214 inSizeMove = true; 12215 break; 12216 case 0x0232: /* WM_EXITSIZEMOVE */ 12217 inSizeMove = false; 12218 12219 size_changed: 12220 12221 // nothing relevant changed, don't bother redrawing 12222 if(oldWidth == _width && oldHeight == _height) { 12223 break; 12224 } 12225 12226 // note: OpenGL windows don't use a backing bmp, so no need to change them 12227 // if resizability is anything other than allowResizing, it is meant to either stretch the one image or just do nothing 12228 if(openglMode == OpenGlOptions.no) { // && resizability == Resizability.allowResizing) { 12229 // gotta get the double buffer bmp to match the window 12230 // FIXME: could this be more efficient? it never relinquishes a large bitmap 12231 12232 // if it is auto-scaled, we keep the backing bitmap the same size all the time 12233 if(resizability != Resizability.automaticallyScaleIfPossible) 12234 if(_width > bmpWidth || _height > bmpHeight) { 12235 auto hdc = GetDC(hwnd); 12236 auto oldBuffer = buffer; 12237 buffer = CreateCompatibleBitmap(hdc, _width, _height); 12238 12239 auto hdcBmp = CreateCompatibleDC(hdc); 12240 auto oldBmp = SelectObject(hdcBmp, buffer); 12241 12242 auto hdcOldBmp = CreateCompatibleDC(hdc); 12243 auto oldOldBmp = SelectObject(hdcOldBmp, oldBuffer); 12244 12245 /+ 12246 RECT r; 12247 r.left = 0; 12248 r.top = 0; 12249 r.right = width; 12250 r.bottom = height; 12251 auto c = Color.green; 12252 auto brush = CreateSolidBrush(RGB(c.r, c.g, c.b)); 12253 FillRect(hdcBmp, &r, brush); 12254 DeleteObject(brush); 12255 +/ 12256 12257 BitBlt(hdcBmp, 0, 0, bmpWidth, bmpHeight, hdcOldBmp, 0, 0, SRCCOPY); 12258 12259 bmpWidth = _width; 12260 bmpHeight = _height; 12261 12262 SelectObject(hdcOldBmp, oldOldBmp); 12263 DeleteDC(hdcOldBmp); 12264 12265 SelectObject(hdcBmp, oldBmp); 12266 DeleteDC(hdcBmp); 12267 12268 ReleaseDC(hwnd, hdc); 12269 12270 DeleteObject(oldBuffer); 12271 } 12272 } 12273 12274 updateOpenglViewportIfNeeded(width, height); 12275 12276 if(resizability != Resizability.automaticallyScaleIfPossible) 12277 if(windowResized !is null) 12278 windowResized(_width, _height); 12279 12280 if(inSizeMove) { 12281 SimpleWindow.processAllCustomEvents(); 12282 SimpleWindow.processAllCustomEvents(); 12283 } else { 12284 // when it is all done, make sure everything is freshly drawn or there might be 12285 // weird bugs left. 12286 RedrawWindow(hwnd, null, null, RDW_ERASE | RDW_INVALIDATE | RDW_ALLCHILDREN); 12287 } 12288 12289 oldWidth = this._width; 12290 oldHeight = this._height; 12291 break; 12292 case WM_ERASEBKGND: 12293 // call `visibleForTheFirstTime` here, so we can do initialization as early as possible 12294 if (!this._visibleForTheFirstTimeCalled) { 12295 this._visibleForTheFirstTimeCalled = true; 12296 if (this.visibleForTheFirstTime !is null) { 12297 this.visibleForTheFirstTime(); 12298 } 12299 } 12300 // block it in OpenGL mode, 'cause no sane person will (or should) draw windows controls over OpenGL scene 12301 version(without_opengl) {} else { 12302 if (openglMode == OpenGlOptions.yes) return 1; 12303 } 12304 // call windows default handler, so it can paint standard controls 12305 goto default; 12306 case WM_CTLCOLORBTN: 12307 case WM_CTLCOLORSTATIC: 12308 SetBkMode(cast(HDC) wParam, TRANSPARENT); 12309 return cast(typeof(return)) //GetStockObject(NULL_BRUSH); 12310 GetSysColorBrush(COLOR_3DFACE); 12311 //break; 12312 case WM_SHOWWINDOW: 12313 this._visible = (wParam != 0); 12314 if (!this._visibleForTheFirstTimeCalled && this._visible) { 12315 this._visibleForTheFirstTimeCalled = true; 12316 if (this.visibleForTheFirstTime !is null) { 12317 this.visibleForTheFirstTime(); 12318 } 12319 } 12320 if (this.visibilityChanged !is null) this.visibilityChanged(this._visible); 12321 break; 12322 case WM_PAINT: { 12323 if (!this._visibleForTheFirstTimeCalled) { 12324 this._visibleForTheFirstTimeCalled = true; 12325 if (this.visibleForTheFirstTime !is null) { 12326 this.visibleForTheFirstTime(); 12327 } 12328 } 12329 12330 BITMAP bm; 12331 PAINTSTRUCT ps; 12332 12333 HDC hdc = BeginPaint(hwnd, &ps); 12334 12335 if(openglMode == OpenGlOptions.no) { 12336 12337 HDC hdcMem = CreateCompatibleDC(hdc); 12338 HBITMAP hbmOld = SelectObject(hdcMem, buffer); 12339 12340 GetObject(buffer, bm.sizeof, &bm); 12341 12342 // FIXME: only BitBlt the invalidated rectangle, not the whole thing 12343 if(resizability == Resizability.automaticallyScaleIfPossible) 12344 StretchBlt(hdc, 0, 0, this._width, this._height, hdcMem, 0, 0, bm.bmWidth, bm.bmHeight, SRCCOPY); 12345 else 12346 BitBlt(hdc, 0, 0, bm.bmWidth, bm.bmHeight, hdcMem, 0, 0, SRCCOPY); 12347 //BitBlt(hdc, ps.rcPaint.left, ps.rcPaint.top, ps.rcPaint.right - ps.rcPaint.left, ps.rcPaint.top - ps.rcPaint.bottom, hdcMem, 0, 0, SRCCOPY); 12348 12349 SelectObject(hdcMem, hbmOld); 12350 DeleteDC(hdcMem); 12351 EndPaint(hwnd, &ps); 12352 } else { 12353 EndPaint(hwnd, &ps); 12354 version(without_opengl) {} else 12355 redrawOpenGlSceneSoon(); 12356 } 12357 } break; 12358 default: 12359 return DefWindowProc(hwnd, msg, wParam, lParam); 12360 } 12361 return 0; 12362 12363 } 12364 catch(Throwable t) { 12365 sdpyPrintDebugString(t.toString); 12366 return 0; 12367 } 12368 } 12369 } 12370 12371 mixin template NativeImageImplementation() { 12372 HBITMAP handle; 12373 ubyte* rawData; 12374 12375 final: 12376 12377 Color getPixel(int x, int y) { 12378 auto itemsPerLine = ((cast(int) width * 3 + 3) / 4) * 4; 12379 // remember, bmps are upside down 12380 auto offset = itemsPerLine * (height - y - 1) + x * 3; 12381 12382 Color c; 12383 if(enableAlpha) 12384 c.a = rawData[offset + 3]; 12385 else 12386 c.a = 255; 12387 c.b = rawData[offset + 0]; 12388 c.g = rawData[offset + 1]; 12389 c.r = rawData[offset + 2]; 12390 c.unPremultiply(); 12391 return c; 12392 } 12393 12394 void setPixel(int x, int y, Color c) { 12395 auto itemsPerLine = ((cast(int) width * 3 + 3) / 4) * 4; 12396 // remember, bmps are upside down 12397 auto offset = itemsPerLine * (height - y - 1) + x * 3; 12398 12399 if(enableAlpha) 12400 c.premultiply(); 12401 12402 rawData[offset + 0] = c.b; 12403 rawData[offset + 1] = c.g; 12404 rawData[offset + 2] = c.r; 12405 if(enableAlpha) 12406 rawData[offset + 3] = c.a; 12407 } 12408 12409 void convertToRgbaBytes(ubyte[] where) { 12410 assert(where.length == this.width * this.height * 4); 12411 12412 auto itemsPerLine = ((cast(int) width * 3 + 3) / 4) * 4; 12413 int idx = 0; 12414 int offset = itemsPerLine * (height - 1); 12415 // remember, bmps are upside down 12416 for(int y = height - 1; y >= 0; y--) { 12417 auto offsetStart = offset; 12418 for(int x = 0; x < width; x++) { 12419 where[idx + 0] = rawData[offset + 2]; // r 12420 where[idx + 1] = rawData[offset + 1]; // g 12421 where[idx + 2] = rawData[offset + 0]; // b 12422 if(enableAlpha) { 12423 where[idx + 3] = rawData[offset + 3]; // a 12424 unPremultiplyRgba(where[idx .. idx + 4]); 12425 offset++; 12426 } else 12427 where[idx + 3] = 255; // a 12428 idx += 4; 12429 offset += 3; 12430 } 12431 12432 offset = offsetStart - itemsPerLine; 12433 } 12434 } 12435 12436 void setFromRgbaBytes(in ubyte[] what) { 12437 assert(what.length == this.width * this.height * 4); 12438 12439 auto itemsPerLine = enableAlpha ? (width * 4) : (((cast(int) width * 3 + 3) / 4) * 4); 12440 int idx = 0; 12441 int offset = itemsPerLine * (height - 1); 12442 // remember, bmps are upside down 12443 for(int y = height - 1; y >= 0; y--) { 12444 auto offsetStart = offset; 12445 for(int x = 0; x < width; x++) { 12446 if(enableAlpha) { 12447 auto a = what[idx + 3]; 12448 12449 rawData[offset + 2] = (a * what[idx + 0]) / 255; // r 12450 rawData[offset + 1] = (a * what[idx + 1]) / 255; // g 12451 rawData[offset + 0] = (a * what[idx + 2]) / 255; // b 12452 rawData[offset + 3] = a; // a 12453 //premultiplyBgra(rawData[offset .. offset + 4]); 12454 offset++; 12455 } else { 12456 rawData[offset + 2] = what[idx + 0]; // r 12457 rawData[offset + 1] = what[idx + 1]; // g 12458 rawData[offset + 0] = what[idx + 2]; // b 12459 } 12460 idx += 4; 12461 offset += 3; 12462 } 12463 12464 offset = offsetStart - itemsPerLine; 12465 } 12466 } 12467 12468 12469 void createImage(int width, int height, bool forcexshm=false, bool enableAlpha = false) { 12470 BITMAPINFO infoheader; 12471 infoheader.bmiHeader.biSize = infoheader.bmiHeader.sizeof; 12472 infoheader.bmiHeader.biWidth = width; 12473 infoheader.bmiHeader.biHeight = height; 12474 infoheader.bmiHeader.biPlanes = 1; 12475 infoheader.bmiHeader.biBitCount = enableAlpha ? 32: 24; 12476 infoheader.bmiHeader.biCompression = BI_RGB; 12477 12478 handle = CreateDIBSection( 12479 null, 12480 &infoheader, 12481 DIB_RGB_COLORS, 12482 cast(void**) &rawData, 12483 null, 12484 0); 12485 if(handle is null) 12486 throw new WindowsApiException("create image failed"); 12487 12488 } 12489 12490 void dispose() { 12491 DeleteObject(handle); 12492 } 12493 } 12494 12495 enum KEY_ESCAPE = 27; 12496 } 12497 version(X11) { 12498 /// This is the default font used. You might change this before doing anything else with 12499 /// the library if you want to try something else. Surround that in `static if(UsingSimpledisplayX11)` 12500 /// for cross-platform compatibility. 12501 //__gshared string xfontstr = "-*-dejavu sans-medium-r-*-*-12-*-*-*-*-*-*-*"; 12502 //__gshared string xfontstr = "-*-dejavu sans-medium-r-*-*-12-*-*-*-*-*-*-*"; 12503 __gshared string xfontstr = "-*-lucida-medium-r-normal-sans-12-*-*-*-*-*-*-*"; 12504 //__gshared string xfontstr = "-*-fixed-medium-r-*-*-14-*-*-*-*-*-*-*"; 12505 12506 alias int delegate(XEvent) NativeEventHandler; 12507 alias Window NativeWindowHandle; 12508 12509 enum KEY_ESCAPE = 9; 12510 12511 mixin template NativeScreenPainterImplementation() { 12512 Display* display; 12513 Drawable d; 12514 Drawable destiny; 12515 12516 // FIXME: should the gc be static too so it isn't recreated every time draw is called? 12517 GC gc; 12518 12519 __gshared bool fontAttempted; 12520 12521 __gshared XFontStruct* defaultfont; 12522 __gshared XFontSet defaultfontset; 12523 12524 XFontStruct* font; 12525 XFontSet fontset; 12526 12527 void create(NativeWindowHandle window) { 12528 this.display = XDisplayConnection.get(); 12529 12530 Drawable buffer = None; 12531 if(auto sw = cast(SimpleWindow) this.window) { 12532 buffer = sw.impl.buffer; 12533 this.destiny = cast(Drawable) window; 12534 } else { 12535 buffer = cast(Drawable) window; 12536 this.destiny = None; 12537 } 12538 12539 this.d = cast(Drawable) buffer; 12540 12541 auto dgc = DefaultGC(display, DefaultScreen(display)); 12542 12543 this.gc = XCreateGC(display, d, 0, null); 12544 12545 XCopyGC(display, dgc, 0xffffffff, this.gc); 12546 12547 ensureDefaultFontLoaded(); 12548 12549 font = defaultfont; 12550 fontset = defaultfontset; 12551 12552 if(font) { 12553 XSetFont(display, gc, font.fid); 12554 } 12555 } 12556 12557 static void ensureDefaultFontLoaded() { 12558 if(!fontAttempted) { 12559 auto display = XDisplayConnection.get; 12560 auto font = XLoadQueryFont(display, xfontstr.ptr); 12561 // if the user font choice fails, fixed is pretty reliable (required by X to start!) and not bad either 12562 if(font is null) { 12563 xfontstr = "-*-fixed-medium-r-*-*-13-*-*-*-*-*-*-*"; 12564 font = XLoadQueryFont(display, xfontstr.ptr); 12565 } 12566 12567 char** lol; 12568 int lol2; 12569 char* lol3; 12570 auto fontset = XCreateFontSet(display, xfontstr.ptr, &lol, &lol2, &lol3); 12571 12572 fontAttempted = true; 12573 12574 defaultfont = font; 12575 defaultfontset = fontset; 12576 } 12577 } 12578 12579 arsd.color.Rectangle _clipRectangle; 12580 void setClipRectangle(int x, int y, int width, int height) { 12581 _clipRectangle = arsd.color.Rectangle(Point(x, y), Size(width, height)); 12582 if(width == 0 || height == 0) { 12583 XSetClipMask(display, gc, None); 12584 12585 if(xrenderPicturePainter) { 12586 12587 XRectangle[1] rects; 12588 rects[0] = XRectangle(short.min, short.min, short.max, short.max); 12589 XRenderSetPictureClipRectangles(display, xrenderPicturePainter, 0, 0, rects.ptr, cast(int) rects.length); 12590 } 12591 12592 version(with_xft) { 12593 if(xftFont is null || xftDraw is null) 12594 return; 12595 XftDrawSetClip(xftDraw, null); 12596 } 12597 } else { 12598 XRectangle[1] rects; 12599 rects[0] = XRectangle(cast(short)(x), cast(short)(y), cast(short) width, cast(short) height); 12600 XSetClipRectangles(XDisplayConnection.get, gc, 0, 0, rects.ptr, 1, 0); 12601 12602 if(xrenderPicturePainter) 12603 XRenderSetPictureClipRectangles(display, xrenderPicturePainter, 0, 0, rects.ptr, cast(int) rects.length); 12604 12605 version(with_xft) { 12606 if(xftFont is null || xftDraw is null) 12607 return; 12608 XftDrawSetClipRectangles(xftDraw, 0, 0, rects.ptr, 1); 12609 } 12610 } 12611 } 12612 12613 version(with_xft) { 12614 XftFont* xftFont; 12615 XftDraw* xftDraw; 12616 12617 XftColor xftColor; 12618 12619 void updateXftColor() { 12620 if(xftFont is null) 12621 return; 12622 12623 // not bothering with XftColorFree since p sure i don't need it on 24 bit displays.... 12624 XRenderColor colorIn = XRenderColor(_outlineColor.r * 255, _outlineColor.g * 255, _outlineColor.b * 255, _outlineColor.a * 255); 12625 12626 XftColorAllocValue( 12627 display, 12628 DefaultVisual(display, DefaultScreen(display)), 12629 DefaultColormap(display, 0), 12630 &colorIn, 12631 &xftColor 12632 ); 12633 } 12634 } 12635 12636 void setFont(OperatingSystemFont font) { 12637 version(with_xft) { 12638 if(font && font.isXft && font.xftFont) 12639 this.xftFont = font.xftFont; 12640 else 12641 this.xftFont = null; 12642 12643 if(this.xftFont) { 12644 if(xftDraw is null) { 12645 xftDraw = XftDrawCreate( 12646 display, 12647 d, 12648 DefaultVisual(display, DefaultScreen(display)), 12649 DefaultColormap(display, 0) 12650 ); 12651 12652 updateXftColor(); 12653 } 12654 12655 return; 12656 } 12657 } 12658 12659 if(font && font.font) { 12660 this.font = font.font; 12661 this.fontset = font.fontset; 12662 XSetFont(display, gc, font.font.fid); 12663 } else { 12664 this.font = defaultfont; 12665 this.fontset = defaultfontset; 12666 } 12667 12668 } 12669 12670 private Picture xrenderPicturePainter; 12671 12672 bool manualInvalidations; 12673 void invalidateRect(Rectangle invalidRect) { 12674 // FIXME if manualInvalidations 12675 } 12676 12677 void dispose() { 12678 this.rasterOp = RasterOp.normal; 12679 12680 if(xrenderPicturePainter) { 12681 XRenderFreePicture(display, xrenderPicturePainter); 12682 xrenderPicturePainter = None; 12683 } 12684 12685 // FIXME: this.window.width/height is probably wrong 12686 12687 // src x,y then dest x, y 12688 if(destiny != None) { 12689 // FIXME: if manual invalidations we can actually only copy some of the area. 12690 // if(manualInvalidations) 12691 XSetClipMask(display, gc, None); 12692 XCopyArea(display, d, destiny, gc, 0, 0, this.window.width, this.window.height, 0, 0); 12693 } 12694 12695 XFreeGC(display, gc); 12696 12697 version(with_xft) 12698 if(xftDraw) { 12699 XftDrawDestroy(xftDraw); 12700 xftDraw = null; 12701 } 12702 12703 /+ 12704 // this should prolly legit never be used since if it destroys the font handle from a OperatingSystemFont, it also ruins a reusable resource. 12705 if(font && font !is defaultfont) { 12706 XFreeFont(display, font); 12707 font = null; 12708 } 12709 if(fontset && fontset !is defaultfontset) { 12710 XFreeFontSet(display, fontset); 12711 fontset = null; 12712 } 12713 +/ 12714 XFlush(display); 12715 12716 if(window.paintingFinishedDg !is null) 12717 window.paintingFinishedDg()(); 12718 } 12719 12720 bool backgroundIsNotTransparent = true; 12721 bool foregroundIsNotTransparent = true; 12722 12723 bool _penInitialized = false; 12724 Pen _activePen; 12725 12726 Color _outlineColor; 12727 Color _fillColor; 12728 12729 @property void pen(Pen p) { 12730 if(_penInitialized && p == _activePen) { 12731 return; 12732 } 12733 _penInitialized = true; 12734 _activePen = p; 12735 _outlineColor = p.color; 12736 12737 int style; 12738 12739 byte dashLength; 12740 12741 final switch(p.style) { 12742 case Pen.Style.Solid: 12743 style = 0 /*LineSolid*/; 12744 break; 12745 case Pen.Style.Dashed: 12746 style = 1 /*LineOnOffDash*/; 12747 dashLength = 4; 12748 break; 12749 case Pen.Style.Dotted: 12750 style = 1 /*LineOnOffDash*/; 12751 dashLength = 1; 12752 break; 12753 } 12754 12755 XSetLineAttributes(display, gc, p.width, style, 0, 0); 12756 if(dashLength) 12757 XSetDashes(display, gc, 0, &dashLength, 1); 12758 12759 if(p.color.a == 0) { 12760 foregroundIsNotTransparent = false; 12761 return; 12762 } 12763 12764 foregroundIsNotTransparent = true; 12765 12766 XSetForeground(display, gc, colorToX(p.color, display)); 12767 12768 version(with_xft) 12769 updateXftColor(); 12770 } 12771 12772 RasterOp _currentRasterOp; 12773 bool _currentRasterOpInitialized = false; 12774 @property void rasterOp(RasterOp op) { 12775 if(_currentRasterOpInitialized && _currentRasterOp == op) 12776 return; 12777 _currentRasterOp = op; 12778 _currentRasterOpInitialized = true; 12779 int mode; 12780 final switch(op) { 12781 case RasterOp.normal: 12782 mode = GXcopy; 12783 break; 12784 case RasterOp.xor: 12785 mode = GXxor; 12786 break; 12787 } 12788 XSetFunction(display, gc, mode); 12789 } 12790 12791 12792 bool _fillColorInitialized = false; 12793 12794 @property void fillColor(Color c) { 12795 if(_fillColorInitialized && _fillColor == c) 12796 return; // already good, no need to waste time calling it 12797 _fillColor = c; 12798 _fillColorInitialized = true; 12799 if(c.a == 0) { 12800 backgroundIsNotTransparent = false; 12801 return; 12802 } 12803 12804 backgroundIsNotTransparent = true; 12805 12806 XSetBackground(display, gc, colorToX(c, display)); 12807 12808 } 12809 12810 void swapColors() { 12811 auto tmp = _fillColor; 12812 fillColor = _outlineColor; 12813 auto newPen = _activePen; 12814 newPen.color = tmp; 12815 pen(newPen); 12816 } 12817 12818 uint colorToX(Color c, Display* display) { 12819 auto visual = DefaultVisual(display, DefaultScreen(display)); 12820 import core.bitop; 12821 uint color = 0; 12822 { 12823 auto startBit = bsf(visual.red_mask); 12824 auto lastBit = bsr(visual.red_mask); 12825 auto r = cast(uint) c.r; 12826 r >>= 7 - (lastBit - startBit); 12827 r <<= startBit; 12828 color |= r; 12829 } 12830 { 12831 auto startBit = bsf(visual.green_mask); 12832 auto lastBit = bsr(visual.green_mask); 12833 auto g = cast(uint) c.g; 12834 g >>= 7 - (lastBit - startBit); 12835 g <<= startBit; 12836 color |= g; 12837 } 12838 { 12839 auto startBit = bsf(visual.blue_mask); 12840 auto lastBit = bsr(visual.blue_mask); 12841 auto b = cast(uint) c.b; 12842 b >>= 7 - (lastBit - startBit); 12843 b <<= startBit; 12844 color |= b; 12845 } 12846 12847 12848 12849 return color; 12850 } 12851 12852 void drawImage(int x, int y, Image i, int ix, int iy, int w, int h) { 12853 // source x, source y 12854 if(ix >= i.width) return; 12855 if(iy >= i.height) return; 12856 if(ix + w > i.width) w = i.width - ix; 12857 if(iy + h > i.height) h = i.height - iy; 12858 if(i.usingXshm) 12859 XShmPutImage(display, d, gc, i.handle, ix, iy, x, y, w, h, false); 12860 else 12861 XPutImage(display, d, gc, i.handle, ix, iy, x, y, w, h); 12862 } 12863 12864 void drawPixmap(Sprite s, int x, int y, int ix, int iy, int w, int h) { 12865 if(s.enableAlpha) { 12866 // the Sprite must be created first, meaning if we're here, XRender is already loaded 12867 if(this.xrenderPicturePainter == None) { 12868 XRenderPictureAttributes attrs; 12869 // FIXME: I can prolly reuse this as long as the pixmap itself is valid. 12870 xrenderPicturePainter = XRenderCreatePicture(display, d, Sprite.RGB24, 0, &attrs); 12871 12872 // need to initialize the clip 12873 XRectangle[1] rects; 12874 rects[0] = XRectangle(cast(short)(_clipRectangle.left), cast(short)(_clipRectangle.top), cast(short) _clipRectangle.width, cast(short) _clipRectangle.height); 12875 12876 XRenderSetPictureClipRectangles(display, xrenderPicturePainter, 0, 0, rects.ptr, cast(int) rects.length); 12877 } 12878 12879 XRenderComposite( 12880 display, 12881 3, // PicOpOver 12882 s.xrenderPicture, 12883 None, 12884 this.xrenderPicturePainter, 12885 ix, 12886 iy, 12887 0, 12888 0, 12889 x, 12890 y, 12891 w ? w : s.width, 12892 h ? h : s.height 12893 ); 12894 } else { 12895 XCopyArea(display, s.handle, d, gc, ix, iy, w ? w : s.width, h ? h : s.height, x, y); 12896 } 12897 } 12898 12899 int fontHeight() { 12900 version(with_xft) 12901 if(xftFont !is null) 12902 return xftFont.height; 12903 if(font) 12904 return font.max_bounds.ascent + font.max_bounds.descent; 12905 return 12; // pretty common default... 12906 } 12907 12908 int textWidth(in char[] line) { 12909 version(with_xft) 12910 if(xftFont) { 12911 if(line.length == 0) 12912 return 0; 12913 XGlyphInfo extents; 12914 XftTextExtentsUtf8(display, xftFont, line.ptr, cast(int) line.length, &extents); 12915 return extents.width; 12916 } 12917 12918 if(fontset) { 12919 if(line.length == 0) 12920 return 0; 12921 XRectangle rect; 12922 Xutf8TextExtents(fontset, line.ptr, cast(int) line.length, null, &rect); 12923 12924 return rect.width; 12925 } 12926 12927 if(font) 12928 // FIXME: unicode 12929 return XTextWidth( font, line.ptr, cast(int) line.length); 12930 else 12931 return fontHeight / 2 * cast(int) line.length; // if no font is loaded, it is prolly Fixed, which is a 2:1 ratio 12932 } 12933 12934 Size textSize(in char[] text) { 12935 auto maxWidth = 0; 12936 auto lineHeight = fontHeight; 12937 int h = text.length ? 0 : lineHeight + 4; // if text is empty, it still gives the line height 12938 foreach(line; text.split('\n')) { 12939 int textWidth = this.textWidth(line); 12940 if(textWidth > maxWidth) 12941 maxWidth = textWidth; 12942 h += lineHeight + 4; 12943 } 12944 return Size(maxWidth, h); 12945 } 12946 12947 void drawText(in int x, in int y, in int x2, in int y2, in char[] originalText, in uint alignment) { 12948 const(char)[] text; 12949 version(with_xft) 12950 if(xftFont) { 12951 text = originalText; 12952 goto loaded; 12953 } 12954 12955 if(fontset) 12956 text = originalText; 12957 else { 12958 text.reserve(originalText.length); 12959 // the first 256 unicode codepoints are the same as ascii and latin-1, which is what X expects, so we can keep all those 12960 // then strip the rest so there isn't garbage 12961 foreach(dchar ch; originalText) 12962 if(ch < 256) 12963 text ~= cast(ubyte) ch; 12964 else 12965 text ~= 191; // FIXME: using a random character (upside down question mark) to fill the space 12966 } 12967 loaded: 12968 if(text.length == 0) 12969 return; 12970 12971 // FIXME: should we clip it to the bounding box? 12972 int textHeight = fontHeight; 12973 12974 auto lines = text.split('\n'); 12975 12976 const lineHeight = textHeight; 12977 textHeight *= lines.length; 12978 12979 int cy = y; 12980 12981 if(alignment & TextAlignment.VerticalBottom) { 12982 if(y2 <= 0) 12983 return; 12984 auto h = y2 - y; 12985 if(h > textHeight) { 12986 cy += h - textHeight; 12987 cy -= lineHeight / 2; 12988 } 12989 } else if(alignment & TextAlignment.VerticalCenter) { 12990 if(y2 <= 0) 12991 return; 12992 auto h = y2 - y; 12993 if(textHeight < h) { 12994 cy += (h - textHeight) / 2; 12995 //cy -= lineHeight / 4; 12996 } 12997 } 12998 12999 foreach(line; text.split('\n')) { 13000 int textWidth = this.textWidth(line); 13001 13002 int px = x, py = cy; 13003 13004 if(alignment & TextAlignment.Center) { 13005 if(x2 <= 0) 13006 return; 13007 auto w = x2 - x; 13008 if(w > textWidth) 13009 px += (w - textWidth) / 2; 13010 } else if(alignment & TextAlignment.Right) { 13011 if(x2 <= 0) 13012 return; 13013 auto pos = x2 - textWidth; 13014 if(pos > x) 13015 px = pos; 13016 } 13017 13018 version(with_xft) 13019 if(xftFont) { 13020 XftDrawStringUtf8(xftDraw, &xftColor, xftFont, px, py + xftFont.ascent, line.ptr, cast(int) line.length); 13021 13022 goto carry_on; 13023 } 13024 13025 if(fontset) 13026 Xutf8DrawString(display, d, fontset, gc, px, py + (font ? font.max_bounds.ascent : lineHeight), line.ptr, cast(int) line.length); 13027 else 13028 XDrawString(display, d, gc, px, py + (font ? font.max_bounds.ascent : lineHeight), line.ptr, cast(int) line.length); 13029 carry_on: 13030 cy += lineHeight + 4; 13031 } 13032 } 13033 13034 void drawPixel(int x, int y) { 13035 XDrawPoint(display, d, gc, x, y); 13036 } 13037 13038 // The basic shapes, outlined 13039 13040 void drawLine(int x1, int y1, int x2, int y2) { 13041 if(foregroundIsNotTransparent) 13042 XDrawLine(display, d, gc, x1, y1, x2, y2); 13043 } 13044 13045 void drawRectangle(int x, int y, int width, int height) { 13046 if(backgroundIsNotTransparent) { 13047 swapColors(); 13048 XFillRectangle(display, d, gc, x+1, y+1, width-2, height-2); // Need to ensure pixels are only drawn once... 13049 swapColors(); 13050 } 13051 // 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 13052 if(foregroundIsNotTransparent) 13053 XDrawRectangle(display, d, gc, x + _activePen.width / 2, y + _activePen.width / 2, width - 1 - _activePen.width / 2, height - 1 - _activePen.width / 2); 13054 } 13055 13056 /// Arguments are the points of the bounding rectangle 13057 void drawEllipse(int x1, int y1, int x2, int y2) { 13058 drawArc(x1, y1, x2 - x1, y2 - y1, 0, 360 * 64); 13059 } 13060 13061 // NOTE: start and finish are in units of degrees * 64 13062 void drawArc(int x1, int y1, int width, int height, int start, int finish) { 13063 if(backgroundIsNotTransparent) { 13064 swapColors(); 13065 XFillArc(display, d, gc, x1, y1, width, height, start, finish); 13066 swapColors(); 13067 } 13068 if(foregroundIsNotTransparent) { 13069 XDrawArc(display, d, gc, x1, y1, width, height, start, finish); 13070 // Windows draws the straight lines on the edges too so FIXME sort of 13071 } 13072 } 13073 13074 void drawPolygon(Point[] vertexes) { 13075 XPoint[16] pointsBuffer; 13076 XPoint[] points; 13077 if(vertexes.length <= pointsBuffer.length) 13078 points = pointsBuffer[0 .. vertexes.length]; 13079 else 13080 points.length = vertexes.length; 13081 13082 foreach(i, p; vertexes) { 13083 points[i].x = cast(short) p.x; 13084 points[i].y = cast(short) p.y; 13085 } 13086 13087 if(backgroundIsNotTransparent) { 13088 swapColors(); 13089 XFillPolygon(display, d, gc, points.ptr, cast(int) points.length, PolygonShape.Complex, CoordMode.CoordModeOrigin); 13090 swapColors(); 13091 } 13092 if(foregroundIsNotTransparent) { 13093 XDrawLines(display, d, gc, points.ptr, cast(int) points.length, CoordMode.CoordModeOrigin); 13094 } 13095 } 13096 } 13097 13098 /* XRender { */ 13099 13100 struct XRenderColor { 13101 ushort red; 13102 ushort green; 13103 ushort blue; 13104 ushort alpha; 13105 } 13106 13107 alias Picture = XID; 13108 alias PictFormat = XID; 13109 13110 struct XGlyphInfo { 13111 ushort width; 13112 ushort height; 13113 short x; 13114 short y; 13115 short xOff; 13116 short yOff; 13117 } 13118 13119 struct XRenderDirectFormat { 13120 short red; 13121 short redMask; 13122 short green; 13123 short greenMask; 13124 short blue; 13125 short blueMask; 13126 short alpha; 13127 short alphaMask; 13128 } 13129 13130 struct XRenderPictFormat { 13131 PictFormat id; 13132 int type; 13133 int depth; 13134 XRenderDirectFormat direct; 13135 Colormap colormap; 13136 } 13137 13138 enum PictFormatID = (1 << 0); 13139 enum PictFormatType = (1 << 1); 13140 enum PictFormatDepth = (1 << 2); 13141 enum PictFormatRed = (1 << 3); 13142 enum PictFormatRedMask =(1 << 4); 13143 enum PictFormatGreen = (1 << 5); 13144 enum PictFormatGreenMask=(1 << 6); 13145 enum PictFormatBlue = (1 << 7); 13146 enum PictFormatBlueMask =(1 << 8); 13147 enum PictFormatAlpha = (1 << 9); 13148 enum PictFormatAlphaMask=(1 << 10); 13149 enum PictFormatColormap =(1 << 11); 13150 13151 struct XRenderPictureAttributes { 13152 int repeat; 13153 Picture alpha_map; 13154 int alpha_x_origin; 13155 int alpha_y_origin; 13156 int clip_x_origin; 13157 int clip_y_origin; 13158 Pixmap clip_mask; 13159 Bool graphics_exposures; 13160 int subwindow_mode; 13161 int poly_edge; 13162 int poly_mode; 13163 Atom dither; 13164 Bool component_alpha; 13165 } 13166 13167 alias int XFixed; 13168 13169 struct XPointFixed { 13170 XFixed x, y; 13171 } 13172 13173 struct XCircle { 13174 XFixed x; 13175 XFixed y; 13176 XFixed radius; 13177 } 13178 13179 struct XTransform { 13180 XFixed[3][3] matrix; 13181 } 13182 13183 struct XFilters { 13184 int nfilter; 13185 char **filter; 13186 int nalias; 13187 short *alias_; 13188 } 13189 13190 struct XIndexValue { 13191 c_ulong pixel; 13192 ushort red, green, blue, alpha; 13193 } 13194 13195 struct XAnimCursor { 13196 Cursor cursor; 13197 c_ulong delay; 13198 } 13199 13200 struct XLinearGradient { 13201 XPointFixed p1; 13202 XPointFixed p2; 13203 } 13204 13205 struct XRadialGradient { 13206 XCircle inner; 13207 XCircle outer; 13208 } 13209 13210 struct XConicalGradient { 13211 XPointFixed center; 13212 XFixed angle; /* in degrees */ 13213 } 13214 13215 enum PictStandardARGB32 = 0; 13216 enum PictStandardRGB24 = 1; 13217 enum PictStandardA8 = 2; 13218 enum PictStandardA4 = 3; 13219 enum PictStandardA1 = 4; 13220 enum PictStandardNUM = 5; 13221 13222 interface XRender { 13223 extern(C) @nogc: 13224 13225 Bool XRenderQueryExtension (Display *dpy, int *event_basep, int *error_basep); 13226 13227 Status XRenderQueryVersion (Display *dpy, 13228 int *major_versionp, 13229 int *minor_versionp); 13230 13231 Status XRenderQueryFormats (Display *dpy); 13232 13233 int XRenderQuerySubpixelOrder (Display *dpy, int screen); 13234 13235 Bool XRenderSetSubpixelOrder (Display *dpy, int screen, int subpixel); 13236 13237 XRenderPictFormat * 13238 XRenderFindVisualFormat (Display *dpy, const Visual *visual); 13239 13240 XRenderPictFormat * 13241 XRenderFindFormat (Display *dpy, 13242 c_ulong mask, 13243 const XRenderPictFormat *templ, 13244 int count); 13245 XRenderPictFormat * 13246 XRenderFindStandardFormat (Display *dpy, 13247 int format); 13248 13249 XIndexValue * 13250 XRenderQueryPictIndexValues(Display *dpy, 13251 const XRenderPictFormat *format, 13252 int *num); 13253 13254 Picture XRenderCreatePicture( 13255 Display *dpy, 13256 Drawable drawable, 13257 const XRenderPictFormat *format, 13258 c_ulong valuemask, 13259 const XRenderPictureAttributes *attributes); 13260 13261 void XRenderChangePicture (Display *dpy, 13262 Picture picture, 13263 c_ulong valuemask, 13264 const XRenderPictureAttributes *attributes); 13265 13266 void 13267 XRenderSetPictureClipRectangles (Display *dpy, 13268 Picture picture, 13269 int xOrigin, 13270 int yOrigin, 13271 const XRectangle *rects, 13272 int n); 13273 13274 void 13275 XRenderSetPictureClipRegion (Display *dpy, 13276 Picture picture, 13277 Region r); 13278 13279 void 13280 XRenderSetPictureTransform (Display *dpy, 13281 Picture picture, 13282 XTransform *transform); 13283 13284 void 13285 XRenderFreePicture (Display *dpy, 13286 Picture picture); 13287 13288 void 13289 XRenderComposite (Display *dpy, 13290 int op, 13291 Picture src, 13292 Picture mask, 13293 Picture dst, 13294 int src_x, 13295 int src_y, 13296 int mask_x, 13297 int mask_y, 13298 int dst_x, 13299 int dst_y, 13300 uint width, 13301 uint height); 13302 13303 13304 Picture XRenderCreateSolidFill (Display *dpy, 13305 const XRenderColor *color); 13306 13307 Picture XRenderCreateLinearGradient (Display *dpy, 13308 const XLinearGradient *gradient, 13309 const XFixed *stops, 13310 const XRenderColor *colors, 13311 int nstops); 13312 13313 Picture XRenderCreateRadialGradient (Display *dpy, 13314 const XRadialGradient *gradient, 13315 const XFixed *stops, 13316 const XRenderColor *colors, 13317 int nstops); 13318 13319 Picture XRenderCreateConicalGradient (Display *dpy, 13320 const XConicalGradient *gradient, 13321 const XFixed *stops, 13322 const XRenderColor *colors, 13323 int nstops); 13324 13325 13326 13327 Cursor 13328 XRenderCreateCursor (Display *dpy, 13329 Picture source, 13330 uint x, 13331 uint y); 13332 13333 XFilters * 13334 XRenderQueryFilters (Display *dpy, Drawable drawable); 13335 13336 void 13337 XRenderSetPictureFilter (Display *dpy, 13338 Picture picture, 13339 const char *filter, 13340 XFixed *params, 13341 int nparams); 13342 13343 Cursor 13344 XRenderCreateAnimCursor (Display *dpy, 13345 int ncursor, 13346 XAnimCursor *cursors); 13347 } 13348 13349 __gshared bool XRenderLibrarySuccessfullyLoaded = true; 13350 mixin DynamicLoad!(XRender, "Xrender", 1, XRenderLibrarySuccessfullyLoaded) XRenderLibrary; 13351 13352 /* XRender } */ 13353 13354 /* Xrandr { */ 13355 13356 struct XRRMonitorInfo { 13357 Atom name; 13358 Bool primary; 13359 Bool automatic; 13360 int noutput; 13361 int x; 13362 int y; 13363 int width; 13364 int height; 13365 int mwidth; 13366 int mheight; 13367 /*RROutput*/ void *outputs; 13368 } 13369 13370 struct XRRScreenChangeNotifyEvent { 13371 int type; /* event base */ 13372 c_ulong serial; /* # of last request processed by server */ 13373 Bool send_event; /* true if this came from a SendEvent request */ 13374 Display *display; /* Display the event was read from */ 13375 Window window; /* window which selected for this event */ 13376 Window root; /* Root window for changed screen */ 13377 Time timestamp; /* when the screen change occurred */ 13378 Time config_timestamp; /* when the last configuration change */ 13379 ushort/*SizeID*/ size_index; 13380 ushort/*SubpixelOrder*/ subpixel_order; 13381 ushort/*Rotation*/ rotation; 13382 int width; 13383 int height; 13384 int mwidth; 13385 int mheight; 13386 } 13387 13388 enum RRScreenChangeNotify = 0; 13389 13390 enum RRScreenChangeNotifyMask = 1; 13391 13392 __gshared int xrrEventBase = -1; 13393 13394 13395 interface XRandr { 13396 extern(C) @nogc: 13397 Bool XRRQueryExtension (Display *dpy, int *event_base_return, int *error_base_return); 13398 Status XRRQueryVersion (Display *dpy, int *major_version_return, int *minor_version_return); 13399 13400 XRRMonitorInfo * XRRGetMonitors(Display *dpy, Window window, Bool get_active, int *nmonitors); 13401 void XRRFreeMonitors(XRRMonitorInfo *monitors); 13402 13403 void XRRSelectInput(Display *dpy, Window window, int mask); 13404 } 13405 13406 __gshared bool XRandrLibrarySuccessfullyLoaded = true; 13407 mixin DynamicLoad!(XRandr, "Xrandr", 2, XRandrLibrarySuccessfullyLoaded) XRandrLibrary; 13408 /* Xrandr } */ 13409 13410 /* Xft { */ 13411 13412 // actually freetype 13413 alias void FT_Face; 13414 13415 // actually fontconfig 13416 private alias FcBool = int; 13417 alias void FcCharSet; 13418 alias void FcPattern; 13419 alias void FcResult; 13420 enum FcEndian { FcEndianBig, FcEndianLittle } 13421 struct FcFontSet { 13422 int nfont; 13423 int sfont; 13424 FcPattern** fonts; 13425 } 13426 13427 // actually XRegion 13428 struct BOX { 13429 short x1, x2, y1, y2; 13430 } 13431 struct _XRegion { 13432 c_long size; 13433 c_long numRects; 13434 BOX* rects; 13435 BOX extents; 13436 } 13437 13438 alias Region = _XRegion*; 13439 13440 // ok actually Xft 13441 13442 struct XftFontInfo; 13443 13444 struct XftFont { 13445 int ascent; 13446 int descent; 13447 int height; 13448 int max_advance_width; 13449 FcCharSet* charset; 13450 FcPattern* pattern; 13451 } 13452 13453 struct XftDraw; 13454 13455 struct XftColor { 13456 c_ulong pixel; 13457 XRenderColor color; 13458 } 13459 13460 struct XftCharSpec { 13461 dchar ucs4; 13462 short x; 13463 short y; 13464 } 13465 13466 struct XftCharFontSpec { 13467 XftFont *font; 13468 dchar ucs4; 13469 short x; 13470 short y; 13471 } 13472 13473 struct XftGlyphSpec { 13474 uint glyph; 13475 short x; 13476 short y; 13477 } 13478 13479 struct XftGlyphFontSpec { 13480 XftFont *font; 13481 uint glyph; 13482 short x; 13483 short y; 13484 } 13485 13486 interface Xft { 13487 extern(C) @nogc pure: 13488 13489 Bool XftColorAllocName (Display *dpy, 13490 const Visual *visual, 13491 Colormap cmap, 13492 const char *name, 13493 XftColor *result); 13494 13495 Bool XftColorAllocValue (Display *dpy, 13496 Visual *visual, 13497 Colormap cmap, 13498 const XRenderColor *color, 13499 XftColor *result); 13500 13501 void XftColorFree (Display *dpy, 13502 Visual *visual, 13503 Colormap cmap, 13504 XftColor *color); 13505 13506 Bool XftDefaultHasRender (Display *dpy); 13507 13508 Bool XftDefaultSet (Display *dpy, FcPattern *defaults); 13509 13510 void XftDefaultSubstitute (Display *dpy, int screen, FcPattern *pattern); 13511 13512 XftDraw * XftDrawCreate (Display *dpy, 13513 Drawable drawable, 13514 Visual *visual, 13515 Colormap colormap); 13516 13517 XftDraw * XftDrawCreateBitmap (Display *dpy, 13518 Pixmap bitmap); 13519 13520 XftDraw * XftDrawCreateAlpha (Display *dpy, 13521 Pixmap pixmap, 13522 int depth); 13523 13524 void XftDrawChange (XftDraw *draw, 13525 Drawable drawable); 13526 13527 Display * XftDrawDisplay (XftDraw *draw); 13528 13529 Drawable XftDrawDrawable (XftDraw *draw); 13530 13531 Colormap XftDrawColormap (XftDraw *draw); 13532 13533 Visual * XftDrawVisual (XftDraw *draw); 13534 13535 void XftDrawDestroy (XftDraw *draw); 13536 13537 Picture XftDrawPicture (XftDraw *draw); 13538 13539 Picture XftDrawSrcPicture (XftDraw *draw, const XftColor *color); 13540 13541 void XftDrawGlyphs (XftDraw *draw, 13542 const XftColor *color, 13543 XftFont *pub, 13544 int x, 13545 int y, 13546 const uint *glyphs, 13547 int nglyphs); 13548 13549 void XftDrawString8 (XftDraw *draw, 13550 const XftColor *color, 13551 XftFont *pub, 13552 int x, 13553 int y, 13554 const char *string, 13555 int len); 13556 13557 void XftDrawString16 (XftDraw *draw, 13558 const XftColor *color, 13559 XftFont *pub, 13560 int x, 13561 int y, 13562 const wchar *string, 13563 int len); 13564 13565 void XftDrawString32 (XftDraw *draw, 13566 const XftColor *color, 13567 XftFont *pub, 13568 int x, 13569 int y, 13570 const dchar *string, 13571 int len); 13572 13573 void XftDrawStringUtf8 (XftDraw *draw, 13574 const XftColor *color, 13575 XftFont *pub, 13576 int x, 13577 int y, 13578 const char *string, 13579 int len); 13580 void XftDrawStringUtf16 (XftDraw *draw, 13581 const XftColor *color, 13582 XftFont *pub, 13583 int x, 13584 int y, 13585 const char *string, 13586 FcEndian endian, 13587 int len); 13588 13589 void XftDrawCharSpec (XftDraw *draw, 13590 const XftColor *color, 13591 XftFont *pub, 13592 const XftCharSpec *chars, 13593 int len); 13594 13595 void XftDrawCharFontSpec (XftDraw *draw, 13596 const XftColor *color, 13597 const XftCharFontSpec *chars, 13598 int len); 13599 13600 void XftDrawGlyphSpec (XftDraw *draw, 13601 const XftColor *color, 13602 XftFont *pub, 13603 const XftGlyphSpec *glyphs, 13604 int len); 13605 13606 void XftDrawGlyphFontSpec (XftDraw *draw, 13607 const XftColor *color, 13608 const XftGlyphFontSpec *glyphs, 13609 int len); 13610 13611 void XftDrawRect (XftDraw *draw, 13612 const XftColor *color, 13613 int x, 13614 int y, 13615 uint width, 13616 uint height); 13617 13618 Bool XftDrawSetClip (XftDraw *draw, 13619 Region r); 13620 13621 13622 Bool XftDrawSetClipRectangles (XftDraw *draw, 13623 int xOrigin, 13624 int yOrigin, 13625 const XRectangle *rects, 13626 int n); 13627 13628 void XftDrawSetSubwindowMode (XftDraw *draw, 13629 int mode); 13630 13631 void XftGlyphExtents (Display *dpy, 13632 XftFont *pub, 13633 const uint *glyphs, 13634 int nglyphs, 13635 XGlyphInfo *extents); 13636 13637 void XftTextExtents8 (Display *dpy, 13638 XftFont *pub, 13639 const char *string, 13640 int len, 13641 XGlyphInfo *extents); 13642 13643 void XftTextExtents16 (Display *dpy, 13644 XftFont *pub, 13645 const wchar *string, 13646 int len, 13647 XGlyphInfo *extents); 13648 13649 void XftTextExtents32 (Display *dpy, 13650 XftFont *pub, 13651 const dchar *string, 13652 int len, 13653 XGlyphInfo *extents); 13654 13655 void XftTextExtentsUtf8 (Display *dpy, 13656 XftFont *pub, 13657 const char *string, 13658 int len, 13659 XGlyphInfo *extents); 13660 13661 void XftTextExtentsUtf16 (Display *dpy, 13662 XftFont *pub, 13663 const char *string, 13664 FcEndian endian, 13665 int len, 13666 XGlyphInfo *extents); 13667 13668 FcPattern * XftFontMatch (Display *dpy, 13669 int screen, 13670 const FcPattern *pattern, 13671 FcResult *result); 13672 13673 XftFont * XftFontOpen (Display *dpy, int screen, ...); 13674 13675 XftFont * XftFontOpenName (Display *dpy, int screen, const char *name); 13676 13677 XftFont * XftFontOpenXlfd (Display *dpy, int screen, const char *xlfd); 13678 13679 FT_Face XftLockFace (XftFont *pub); 13680 13681 void XftUnlockFace (XftFont *pub); 13682 13683 XftFontInfo * XftFontInfoCreate (Display *dpy, const FcPattern *pattern); 13684 13685 void XftFontInfoDestroy (Display *dpy, XftFontInfo *fi); 13686 13687 dchar XftFontInfoHash (const XftFontInfo *fi); 13688 13689 FcBool XftFontInfoEqual (const XftFontInfo *a, const XftFontInfo *b); 13690 13691 XftFont * XftFontOpenInfo (Display *dpy, 13692 FcPattern *pattern, 13693 XftFontInfo *fi); 13694 13695 XftFont * XftFontOpenPattern (Display *dpy, FcPattern *pattern); 13696 13697 XftFont * XftFontCopy (Display *dpy, XftFont *pub); 13698 13699 void XftFontClose (Display *dpy, XftFont *pub); 13700 13701 FcBool XftInitFtLibrary(); 13702 void XftFontLoadGlyphs (Display *dpy, 13703 XftFont *pub, 13704 FcBool need_bitmaps, 13705 const uint *glyphs, 13706 int nglyph); 13707 13708 void XftFontUnloadGlyphs (Display *dpy, 13709 XftFont *pub, 13710 const uint *glyphs, 13711 int nglyph); 13712 13713 FcBool XftFontCheckGlyph (Display *dpy, 13714 XftFont *pub, 13715 FcBool need_bitmaps, 13716 uint glyph, 13717 uint *missing, 13718 int *nmissing); 13719 13720 FcBool XftCharExists (Display *dpy, 13721 XftFont *pub, 13722 dchar ucs4); 13723 13724 uint XftCharIndex (Display *dpy, 13725 XftFont *pub, 13726 dchar ucs4); 13727 FcBool XftInit (const char *config); 13728 13729 int XftGetVersion (); 13730 13731 FcFontSet * XftListFonts (Display *dpy, 13732 int screen, 13733 ...); 13734 13735 FcPattern *XftNameParse (const char *name); 13736 13737 void XftGlyphRender (Display *dpy, 13738 int op, 13739 Picture src, 13740 XftFont *pub, 13741 Picture dst, 13742 int srcx, 13743 int srcy, 13744 int x, 13745 int y, 13746 const uint *glyphs, 13747 int nglyphs); 13748 13749 void XftGlyphSpecRender (Display *dpy, 13750 int op, 13751 Picture src, 13752 XftFont *pub, 13753 Picture dst, 13754 int srcx, 13755 int srcy, 13756 const XftGlyphSpec *glyphs, 13757 int nglyphs); 13758 13759 void XftCharSpecRender (Display *dpy, 13760 int op, 13761 Picture src, 13762 XftFont *pub, 13763 Picture dst, 13764 int srcx, 13765 int srcy, 13766 const XftCharSpec *chars, 13767 int len); 13768 void XftGlyphFontSpecRender (Display *dpy, 13769 int op, 13770 Picture src, 13771 Picture dst, 13772 int srcx, 13773 int srcy, 13774 const XftGlyphFontSpec *glyphs, 13775 int nglyphs); 13776 13777 void XftCharFontSpecRender (Display *dpy, 13778 int op, 13779 Picture src, 13780 Picture dst, 13781 int srcx, 13782 int srcy, 13783 const XftCharFontSpec *chars, 13784 int len); 13785 13786 void XftTextRender8 (Display *dpy, 13787 int op, 13788 Picture src, 13789 XftFont *pub, 13790 Picture dst, 13791 int srcx, 13792 int srcy, 13793 int x, 13794 int y, 13795 const char *string, 13796 int len); 13797 void XftTextRender16 (Display *dpy, 13798 int op, 13799 Picture src, 13800 XftFont *pub, 13801 Picture dst, 13802 int srcx, 13803 int srcy, 13804 int x, 13805 int y, 13806 const wchar *string, 13807 int len); 13808 13809 void XftTextRender16BE (Display *dpy, 13810 int op, 13811 Picture src, 13812 XftFont *pub, 13813 Picture dst, 13814 int srcx, 13815 int srcy, 13816 int x, 13817 int y, 13818 const char *string, 13819 int len); 13820 13821 void XftTextRender16LE (Display *dpy, 13822 int op, 13823 Picture src, 13824 XftFont *pub, 13825 Picture dst, 13826 int srcx, 13827 int srcy, 13828 int x, 13829 int y, 13830 const char *string, 13831 int len); 13832 13833 void XftTextRender32 (Display *dpy, 13834 int op, 13835 Picture src, 13836 XftFont *pub, 13837 Picture dst, 13838 int srcx, 13839 int srcy, 13840 int x, 13841 int y, 13842 const dchar *string, 13843 int len); 13844 13845 void XftTextRender32BE (Display *dpy, 13846 int op, 13847 Picture src, 13848 XftFont *pub, 13849 Picture dst, 13850 int srcx, 13851 int srcy, 13852 int x, 13853 int y, 13854 const char *string, 13855 int len); 13856 13857 void XftTextRender32LE (Display *dpy, 13858 int op, 13859 Picture src, 13860 XftFont *pub, 13861 Picture dst, 13862 int srcx, 13863 int srcy, 13864 int x, 13865 int y, 13866 const char *string, 13867 int len); 13868 13869 void XftTextRenderUtf8 (Display *dpy, 13870 int op, 13871 Picture src, 13872 XftFont *pub, 13873 Picture dst, 13874 int srcx, 13875 int srcy, 13876 int x, 13877 int y, 13878 const char *string, 13879 int len); 13880 13881 void XftTextRenderUtf16 (Display *dpy, 13882 int op, 13883 Picture src, 13884 XftFont *pub, 13885 Picture dst, 13886 int srcx, 13887 int srcy, 13888 int x, 13889 int y, 13890 const char *string, 13891 FcEndian endian, 13892 int len); 13893 FcPattern * XftXlfdParse (const char *xlfd_orig, Bool ignore_scalable, Bool complete); 13894 13895 } 13896 13897 interface FontConfig { 13898 extern(C) @nogc pure: 13899 int FcPatternGetString(const FcPattern *p, const char *object, int n, char ** s); 13900 void FcFontSetDestroy(FcFontSet*); 13901 char* FcNameUnparse(const FcPattern *); 13902 } 13903 13904 mixin DynamicLoad!(Xft, "Xft", 2, librariesSuccessfullyLoaded) XftLibrary; 13905 mixin DynamicLoad!(FontConfig, "fontconfig", 1, librariesSuccessfullyLoaded) FontConfigLibrary; 13906 13907 13908 /* Xft } */ 13909 13910 class XDisconnectException : Exception { 13911 bool userRequested; 13912 this(bool userRequested = true) { 13913 this.userRequested = userRequested; 13914 super("X disconnected"); 13915 } 13916 } 13917 13918 /++ 13919 Platform-specific for X11. Traps errors for the duration of `dg`. Avoid calling this from inside a call to this. 13920 13921 Please note that it returns 13922 +/ 13923 XErrorEvent[] trapXErrors(scope void delegate() dg) { 13924 13925 static XErrorEvent[] errorBuffer; 13926 13927 static extern(C) int handler (Display* dpy, XErrorEvent* evt) nothrow { 13928 errorBuffer ~= *evt; 13929 return 0; 13930 } 13931 13932 auto savedErrorHandler = XSetErrorHandler(&handler); 13933 13934 try { 13935 dg(); 13936 } finally { 13937 XSync(XDisplayConnection.get, 0/*False*/); 13938 XSetErrorHandler(savedErrorHandler); 13939 } 13940 13941 auto bfr = errorBuffer; 13942 errorBuffer = null; 13943 13944 return bfr; 13945 } 13946 13947 /// Platform-specific for X11. A singleton class (well, all its methods are actually static... so more like a namespace) wrapping a `Display*`. 13948 class XDisplayConnection { 13949 private __gshared Display* display; 13950 private __gshared XIM xim; 13951 private __gshared char* displayName; 13952 13953 private __gshared int connectionSequence_; 13954 private __gshared bool isLocal_; 13955 13956 /// use this for lazy caching when reconnection 13957 static int connectionSequenceNumber() { return connectionSequence_; } 13958 13959 /++ 13960 Guesses if the connection appears to be local. 13961 13962 History: 13963 Added June 3, 2021 13964 +/ 13965 static @property bool isLocal() nothrow @trusted @nogc { 13966 return isLocal_; 13967 } 13968 13969 /// Attempts recreation of state, may require application assistance 13970 /// You MUST call this OUTSIDE the event loop. Let the exception kill the loop, 13971 /// then call this, and if successful, reenter the loop. 13972 static void discardAndRecreate(string newDisplayString = null) { 13973 if(insideXEventLoop) 13974 throw new Error("You MUST call discardAndRecreate from OUTSIDE the event loop"); 13975 13976 // 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 13977 auto chnenhm = CapableOfHandlingNativeEvent.nativeHandleMapping.dup; 13978 13979 foreach(handle; chnenhm) { 13980 handle.discardConnectionState(); 13981 } 13982 13983 discardState(); 13984 13985 if(newDisplayString !is null) 13986 setDisplayName(newDisplayString); 13987 13988 auto display = get(); 13989 13990 foreach(handle; chnenhm) { 13991 handle.recreateAfterDisconnect(); 13992 } 13993 } 13994 13995 private __gshared EventMask rootEventMask; 13996 13997 /++ 13998 Requests the specified input from the root window on the connection, in addition to any other request. 13999 14000 14001 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. 14002 14003 $(WARNING it calls XSelectInput itself, which will override any other root window input you have!) 14004 +/ 14005 static void addRootInput(EventMask mask) { 14006 auto old = rootEventMask; 14007 rootEventMask |= mask; 14008 get(); // to ensure display connected 14009 if(display !is null && rootEventMask != old) 14010 XSelectInput(display, RootWindow(display, DefaultScreen(display)), rootEventMask); 14011 } 14012 14013 static void discardState() { 14014 freeImages(); 14015 14016 foreach(atomPtr; interredAtoms) 14017 *atomPtr = 0; 14018 interredAtoms = null; 14019 interredAtoms.assumeSafeAppend(); 14020 14021 ScreenPainterImplementation.fontAttempted = false; 14022 ScreenPainterImplementation.defaultfont = null; 14023 ScreenPainterImplementation.defaultfontset = null; 14024 14025 Image.impl.xshmQueryCompleted = false; 14026 Image.impl._xshmAvailable = false; 14027 14028 SimpleWindow.nativeMapping = null; 14029 CapableOfHandlingNativeEvent.nativeHandleMapping = null; 14030 // GlobalHotkeyManager 14031 14032 display = null; 14033 xim = null; 14034 } 14035 14036 // Do you want to know why do we need all this horrible-looking code? See comment at the bottom. 14037 private static void createXIM () { 14038 import core.stdc.locale : setlocale, LC_ALL; 14039 import core.stdc.stdio : stderr, fprintf; 14040 import core.stdc.stdlib : free; 14041 import core.stdc.string : strdup; 14042 14043 static immutable string[3] mtry = [ "", "@im=local", "@im=" ]; 14044 14045 auto olocale = strdup(setlocale(LC_ALL, null)); 14046 setlocale(LC_ALL, (sdx_isUTF8Locale ? "" : "en_US.UTF-8")); 14047 scope(exit) { setlocale(LC_ALL, olocale); free(olocale); } 14048 14049 //fprintf(stderr, "opening IM...\n"); 14050 foreach (string s; mtry) { 14051 XSetLocaleModifiers(s.ptr); // it's safe, as `s` is string literal 14052 if ((xim = XOpenIM(display, null, null, null)) !is null) return; 14053 } 14054 fprintf(stderr, "createXIM: XOpenIM failed!\n"); 14055 } 14056 14057 // for X11 we will keep all XShm-allocated images in this list, so we can free 'em on connection closing. 14058 // we'll use glibc malloc()/free(), 'cause `unregisterImage()` can be called from object dtor. 14059 static struct ImgList { 14060 size_t img; // class; hide it from GC 14061 ImgList* next; 14062 } 14063 14064 static __gshared ImgList* imglist = null; 14065 static __gshared bool imglistLocked = false; // true: don't register and unregister images 14066 14067 static void registerImage (Image img) { 14068 if (!imglistLocked && img !is null) { 14069 import core.stdc.stdlib : malloc; 14070 auto it = cast(ImgList*)malloc(ImgList.sizeof); 14071 assert(it !is null); // do proper checks 14072 it.img = cast(size_t)cast(void*)img; 14073 it.next = imglist; 14074 imglist = it; 14075 version(sdpy_debug_xshm) { import core.stdc.stdio : printf; printf("registering image %p\n", cast(void*)img); } 14076 } 14077 } 14078 14079 static void unregisterImage (Image img) { 14080 if (!imglistLocked && img !is null) { 14081 import core.stdc.stdlib : free; 14082 ImgList* prev = null; 14083 ImgList* cur = imglist; 14084 while (cur !is null) { 14085 if (cur.img == cast(size_t)cast(void*)img) break; // i found her! 14086 prev = cur; 14087 cur = cur.next; 14088 } 14089 if (cur !is null) { 14090 if (prev is null) imglist = cur.next; else prev.next = cur.next; 14091 free(cur); 14092 version(sdpy_debug_xshm) { import core.stdc.stdio : printf; printf("unregistering image %p\n", cast(void*)img); } 14093 } else { 14094 version(sdpy_debug_xshm) { import core.stdc.stdio : printf; printf("trying to unregister unknown image %p\n", cast(void*)img); } 14095 } 14096 } 14097 } 14098 14099 static void freeImages () { // needed for discardAndRecreate 14100 imglistLocked = true; 14101 scope(exit) imglistLocked = false; 14102 ImgList* cur = imglist; 14103 ImgList* next = null; 14104 while (cur !is null) { 14105 import core.stdc.stdlib : free; 14106 next = cur.next; 14107 version(sdpy_debug_xshm) { import core.stdc.stdio : printf; printf("disposing image %p\n", cast(void*)cur.img); } 14108 (cast(Image)cast(void*)cur.img).dispose(); 14109 free(cur); 14110 cur = next; 14111 } 14112 imglist = null; 14113 } 14114 14115 /// can be used to override normal handling of display name 14116 /// from environment and/or command line 14117 static setDisplayName(string newDisplayName) { 14118 displayName = cast(char*) (newDisplayName ~ '\0'); 14119 } 14120 14121 /// resets to the default display string 14122 static resetDisplayName() { 14123 displayName = null; 14124 } 14125 14126 /// 14127 static Display* get() { 14128 if(display is null) { 14129 if(!librariesSuccessfullyLoaded) 14130 throw new Exception("Unable to load X11 client libraries"); 14131 display = XOpenDisplay(displayName); 14132 14133 isLocal_ = false; 14134 14135 connectionSequence_++; 14136 if(display is null) 14137 throw new Exception("Unable to open X display"); 14138 14139 auto str = display.display_name; 14140 // this is a bit of a hack but like if it looks like a unix socket we assume it is local 14141 // and otherwise it probably isn't 14142 if(str is null || (str[0] != ':' && str[0] != '/')) 14143 isLocal_ = false; 14144 else 14145 isLocal_ = true; 14146 14147 debug(sdpy_x_errors) { 14148 XSetErrorHandler(&adrlogger); 14149 XSynchronize(display, true); 14150 14151 extern(C) int wtf() { 14152 if(errorHappened) { 14153 asm { int 3; } 14154 errorHappened = false; 14155 } 14156 return 0; 14157 } 14158 XSetAfterFunction(display, &wtf); 14159 } 14160 14161 14162 XSetIOErrorHandler(&x11ioerrCB); 14163 Bool sup; 14164 XkbSetDetectableAutoRepeat(display, 1, &sup); // so we will not receive KeyRelease until key is really released 14165 createXIM(); 14166 version(with_eventloop) { 14167 import arsd.eventloop; 14168 addFileEventListeners(display.fd, &eventListener, null, null); 14169 } 14170 } 14171 14172 return display; 14173 } 14174 14175 extern(C) 14176 static int x11ioerrCB(Display* dpy) { 14177 throw new XDisconnectException(false); 14178 } 14179 14180 version(with_eventloop) { 14181 import arsd.eventloop; 14182 static void eventListener(OsFileHandle fd) { 14183 //this.mtLock(); 14184 //scope(exit) this.mtUnlock(); 14185 while(XPending(display)) 14186 doXNextEvent(display); 14187 } 14188 } 14189 14190 // close connection on program exit -- we need this to properly free all images 14191 static ~this () { 14192 // the gui thread must clean up after itself or else Xlib might deadlock 14193 // using this flag on any thread destruction is the easiest way i know of 14194 // (shared static this is run by the LAST thread to exit, which may not be 14195 // the gui thread, and normal static this run by ALL threads, so we gotta check.) 14196 if(thisIsGuiThread) 14197 close(); 14198 } 14199 14200 /// 14201 static void close() { 14202 if(display is null) 14203 return; 14204 14205 version(with_eventloop) { 14206 import arsd.eventloop; 14207 removeFileEventListeners(display.fd); 14208 } 14209 14210 // now remove all registered images to prevent shared memory leaks 14211 freeImages(); 14212 14213 // tbh I don't know why it is doing this but like if this happens to run 14214 // from the other thread there's frequent hanging inside here. 14215 if(thisIsGuiThread) 14216 XCloseDisplay(display); 14217 display = null; 14218 } 14219 } 14220 14221 mixin template NativeImageImplementation() { 14222 XImage* handle; 14223 ubyte* rawData; 14224 14225 XShmSegmentInfo shminfo; 14226 14227 __gshared bool xshmQueryCompleted; 14228 __gshared bool _xshmAvailable; 14229 public static @property bool xshmAvailable() { 14230 if(!xshmQueryCompleted) { 14231 int i1, i2, i3; 14232 xshmQueryCompleted = true; 14233 14234 if(!XDisplayConnection.isLocal) 14235 _xshmAvailable = false; 14236 else 14237 _xshmAvailable = XQueryExtension(XDisplayConnection.get(), "MIT-SHM", &i1, &i2, &i3) != 0; 14238 } 14239 return _xshmAvailable; 14240 } 14241 14242 bool usingXshm; 14243 final: 14244 14245 private __gshared bool xshmfailed; 14246 14247 void createImage(int width, int height, bool forcexshm=false, bool enableAlpha = false) { 14248 auto display = XDisplayConnection.get(); 14249 assert(display !is null); 14250 auto screen = DefaultScreen(display); 14251 14252 // it will only use shared memory for somewhat largish images, 14253 // since otherwise we risk wasting shared memory handles on a lot of little ones 14254 if (xshmAvailable && (forcexshm || (width > 100 && height > 100))) { 14255 14256 14257 // it is possible for the query extension to return true, the DISPLAY check to pass, yet 14258 // the actual use still fails. For example, if the program is in a container and permission denied 14259 // on shared memory, or if it is a local thing forwarded to a remote server, etc. 14260 // 14261 // If it does fail, we need to detect it now, abort the xshm and fall back to core protocol. 14262 14263 14264 // synchronize so preexisting buffers are clear 14265 XSync(display, false); 14266 xshmfailed = false; 14267 14268 auto oldErrorHandler = XSetErrorHandler(&XShmErrorHandler); 14269 14270 14271 usingXshm = true; 14272 handle = XShmCreateImage( 14273 display, 14274 DefaultVisual(display, screen), 14275 enableAlpha ? 32: 24, 14276 ImageFormat.ZPixmap, 14277 null, 14278 &shminfo, 14279 width, height); 14280 if(handle is null) 14281 goto abortXshm1; 14282 14283 if(handle.bytes_per_line != 4 * width) 14284 goto abortXshm2; 14285 14286 shminfo.shmid = shmget(IPC_PRIVATE, handle.bytes_per_line * height, IPC_CREAT | 511 /* 0777 */); 14287 if(shminfo.shmid < 0) 14288 goto abortXshm3; 14289 handle.data = shminfo.shmaddr = rawData = cast(ubyte*) shmat(shminfo.shmid, null, 0); 14290 if(rawData == cast(ubyte*) -1) 14291 goto abortXshm4; 14292 shminfo.readOnly = 0; 14293 XShmAttach(display, &shminfo); 14294 14295 // and now to the final error check to ensure it actually worked. 14296 XSync(display, false); 14297 if(xshmfailed) 14298 goto abortXshm5; 14299 14300 XSetErrorHandler(oldErrorHandler); 14301 14302 XDisplayConnection.registerImage(this); 14303 // if I don't flush here there's a chance the dtor will run before the 14304 // ctor and lead to a bad value X error. While this hurts the efficiency 14305 // it is local anyway so prolly better to keep it simple 14306 XFlush(display); 14307 14308 return; 14309 14310 abortXshm5: 14311 shmdt(shminfo.shmaddr); 14312 rawData = null; 14313 14314 abortXshm4: 14315 shmctl(shminfo.shmid, IPC_RMID, null); 14316 14317 abortXshm3: 14318 // nothing needed, the shmget failed so there's nothing to free 14319 14320 abortXshm2: 14321 XDestroyImage(handle); 14322 handle = null; 14323 14324 abortXshm1: 14325 XSetErrorHandler(oldErrorHandler); 14326 usingXshm = false; 14327 handle = null; 14328 14329 shminfo = typeof(shminfo).init; 14330 14331 _xshmAvailable = false; // don't try again in the future 14332 14333 //import std.stdio; writeln("fallingback"); 14334 14335 goto fallback; 14336 14337 } else { 14338 fallback: 14339 14340 if (forcexshm) throw new Exception("can't create XShm Image"); 14341 // This actually needs to be malloc to avoid a double free error when XDestroyImage is called 14342 import core.stdc.stdlib : malloc; 14343 rawData = cast(ubyte*) malloc(width * height * 4); 14344 14345 handle = XCreateImage( 14346 display, 14347 DefaultVisual(display, screen), 14348 enableAlpha ? 32 : 24, // bpp 14349 ImageFormat.ZPixmap, 14350 0, // offset 14351 rawData, 14352 width, height, 14353 enableAlpha ? 32 : 8 /* FIXME */, 4 * width); // padding, bytes per line 14354 } 14355 } 14356 14357 void dispose() { 14358 // note: this calls free(rawData) for us 14359 if(handle) { 14360 if (usingXshm) { 14361 XDisplayConnection.unregisterImage(this); 14362 if (XDisplayConnection.get()) XShmDetach(XDisplayConnection.get(), &shminfo); 14363 } 14364 XDestroyImage(handle); 14365 if(usingXshm) { 14366 shmdt(shminfo.shmaddr); 14367 shmctl(shminfo.shmid, IPC_RMID, null); 14368 } 14369 handle = null; 14370 } 14371 } 14372 14373 Color getPixel(int x, int y) { 14374 auto offset = (y * width + x) * 4; 14375 Color c; 14376 c.a = enableAlpha ? rawData[offset + 3] : 255; 14377 c.b = rawData[offset + 0]; 14378 c.g = rawData[offset + 1]; 14379 c.r = rawData[offset + 2]; 14380 if(enableAlpha) 14381 c.unPremultiply; 14382 return c; 14383 } 14384 14385 void setPixel(int x, int y, Color c) { 14386 if(enableAlpha) 14387 c.premultiply(); 14388 auto offset = (y * width + x) * 4; 14389 rawData[offset + 0] = c.b; 14390 rawData[offset + 1] = c.g; 14391 rawData[offset + 2] = c.r; 14392 if(enableAlpha) 14393 rawData[offset + 3] = c.a; 14394 } 14395 14396 void convertToRgbaBytes(ubyte[] where) { 14397 assert(where.length == this.width * this.height * 4); 14398 14399 // if rawData had a length.... 14400 //assert(rawData.length == where.length); 14401 for(int idx = 0; idx < where.length; idx += 4) { 14402 where[idx + 0] = rawData[idx + 2]; // r 14403 where[idx + 1] = rawData[idx + 1]; // g 14404 where[idx + 2] = rawData[idx + 0]; // b 14405 where[idx + 3] = enableAlpha ? rawData[idx + 3] : 255; // a 14406 14407 if(enableAlpha) 14408 unPremultiplyRgba(where[idx .. idx + 4]); 14409 } 14410 } 14411 14412 void setFromRgbaBytes(in ubyte[] where) { 14413 assert(where.length == this.width * this.height * 4); 14414 14415 // if rawData had a length.... 14416 //assert(rawData.length == where.length); 14417 for(int idx = 0; idx < where.length; idx += 4) { 14418 rawData[idx + 2] = where[idx + 0]; // r 14419 rawData[idx + 1] = where[idx + 1]; // g 14420 rawData[idx + 0] = where[idx + 2]; // b 14421 if(enableAlpha) { 14422 rawData[idx + 3] = where[idx + 3]; // a 14423 premultiplyBgra(rawData[idx .. idx + 4]); 14424 } 14425 } 14426 } 14427 14428 } 14429 14430 mixin template NativeSimpleWindowImplementation() { 14431 GC gc; 14432 Window window; 14433 Display* display; 14434 14435 Pixmap buffer; 14436 int bufferw, bufferh; // size of the buffer; can be bigger than window 14437 XIC xic; // input context 14438 int curHidden = 0; // counter 14439 Cursor blankCurPtr = 0; 14440 int cursorSequenceNumber = 0; 14441 int warpEventCount = 0; // number of mouse movement events to eat 14442 14443 __gshared X11SetSelectionHandler[Atom] setSelectionHandlers; 14444 X11GetSelectionHandler[Atom] getSelectionHandlers; 14445 14446 version(without_opengl) {} else 14447 GLXContext glc; 14448 14449 private void fixFixedSize(bool forced=false) (int width, int height) { 14450 if (forced || this.resizability == Resizability.fixedSize) { 14451 //{ import core.stdc.stdio; printf("fixing size to: %dx%d\n", width, height); } 14452 XSizeHints sh; 14453 static if (!forced) { 14454 c_long spr; 14455 XGetWMNormalHints(display, window, &sh, &spr); 14456 sh.flags |= PMaxSize | PMinSize; 14457 } else { 14458 sh.flags = PMaxSize | PMinSize; 14459 } 14460 sh.min_width = width; 14461 sh.min_height = height; 14462 sh.max_width = width; 14463 sh.max_height = height; 14464 XSetWMNormalHints(display, window, &sh); 14465 //XFlush(display); 14466 } 14467 } 14468 14469 ScreenPainter getPainter(bool manualInvalidations) { 14470 return ScreenPainter(this, window, manualInvalidations); 14471 } 14472 14473 void move(int x, int y) { 14474 XMoveWindow(display, window, x, y); 14475 } 14476 14477 void resize(int w, int h) { 14478 if (w < 1) w = 1; 14479 if (h < 1) h = 1; 14480 XResizeWindow(display, window, w, h); 14481 14482 // calling this now to avoid waiting for the server to 14483 // acknowledge the resize; draws without returning to the 14484 // event loop will thus actually work. the server's event 14485 // btw might overrule this and resize it again 14486 recordX11Resize(display, this, w, h); 14487 14488 updateOpenglViewportIfNeeded(w, h); 14489 } 14490 14491 void moveResize (int x, int y, int w, int h) { 14492 if (w < 1) w = 1; 14493 if (h < 1) h = 1; 14494 XMoveResizeWindow(display, window, x, y, w, h); 14495 updateOpenglViewportIfNeeded(w, h); 14496 } 14497 14498 void hideCursor () { 14499 if (curHidden++ == 0) { 14500 if (!blankCurPtr || cursorSequenceNumber != XDisplayConnection.connectionSequenceNumber) { 14501 static const(char)[1] cmbmp = 0; 14502 XColor blackcolor = { 0, 0, 0, 0, 0, 0 }; 14503 Pixmap pm = XCreateBitmapFromData(display, window, cmbmp.ptr, 1, 1); 14504 blankCurPtr = XCreatePixmapCursor(display, pm, pm, &blackcolor, &blackcolor, 0, 0); 14505 cursorSequenceNumber = XDisplayConnection.connectionSequenceNumber; 14506 XFreePixmap(display, pm); 14507 } 14508 XDefineCursor(display, window, blankCurPtr); 14509 } 14510 } 14511 14512 void showCursor () { 14513 if (--curHidden == 0) XUndefineCursor(display, window); 14514 } 14515 14516 void warpMouse (int x, int y) { 14517 // here i will send dummy "ignore next mouse motion" event, 14518 // 'cause `XWarpPointer()` sends synthesised mouse motion, 14519 // and we don't need to report it to the user (as warping is 14520 // used when the user needs movement deltas). 14521 //XClientMessageEvent xclient; 14522 XEvent e; 14523 e.xclient.type = EventType.ClientMessage; 14524 e.xclient.window = window; 14525 e.xclient.message_type = GetAtom!("_X11SDPY_INSMME_FLAG_EVENT_", true)(display); // let's hope nobody else will use such stupid name ;-) 14526 e.xclient.format = 32; 14527 e.xclient.data.l[0] = 0; 14528 debug(x11sdpy_warp_debug) { import core.stdc.stdio : printf; printf("X11: sending \"INSMME\"...\n"); } 14529 //{ 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]); } 14530 XSendEvent(display, window, false, EventMask.NoEventMask, /*cast(XEvent*)&xclient*/&e); 14531 // now warp pointer... 14532 debug(x11sdpy_warp_debug) { import core.stdc.stdio : printf; printf("X11: sending \"warp\"...\n"); } 14533 XWarpPointer(display, None, window, 0, 0, 0, 0, x, y); 14534 // ...and flush 14535 debug(x11sdpy_warp_debug) { import core.stdc.stdio : printf; printf("X11: flushing...\n"); } 14536 XFlush(display); 14537 } 14538 14539 void sendDummyEvent () { 14540 // here i will send dummy event to ping event queue 14541 XEvent e; 14542 e.xclient.type = EventType.ClientMessage; 14543 e.xclient.window = window; 14544 e.xclient.message_type = GetAtom!("_X11SDPY_DUMMY_EVENT_", true)(display); // let's hope nobody else will use such stupid name ;-) 14545 e.xclient.format = 32; 14546 e.xclient.data.l[0] = 0; 14547 XSendEvent(display, window, false, EventMask.NoEventMask, /*cast(XEvent*)&xclient*/&e); 14548 XFlush(display); 14549 } 14550 14551 void setTitle(string title) { 14552 if (title.ptr is null) title = ""; 14553 auto XA_UTF8 = XInternAtom(display, "UTF8_STRING".ptr, false); 14554 auto XA_NETWM_NAME = XInternAtom(display, "_NET_WM_NAME".ptr, false); 14555 XTextProperty windowName; 14556 windowName.value = title.ptr; 14557 windowName.encoding = XA_UTF8; //XA_STRING; 14558 windowName.format = 8; 14559 windowName.nitems = cast(uint)title.length; 14560 XSetWMName(display, window, &windowName); 14561 char[1024] namebuf = 0; 14562 auto maxlen = namebuf.length-1; 14563 if (maxlen > title.length) maxlen = title.length; 14564 namebuf[0..maxlen] = title[0..maxlen]; 14565 XStoreName(display, window, namebuf.ptr); 14566 XChangeProperty(display, window, XA_NETWM_NAME, XA_UTF8, 8, PropModeReplace, title.ptr, cast(uint)title.length); 14567 flushGui(); // without this OpenGL windows has a LONG delay before changing title 14568 } 14569 14570 string[] getTitles() { 14571 auto XA_UTF8 = XInternAtom(display, "UTF8_STRING".ptr, false); 14572 auto XA_NETWM_NAME = XInternAtom(display, "_NET_WM_NAME".ptr, false); 14573 XTextProperty textProp; 14574 if (XGetTextProperty(display, window, &textProp, XA_NETWM_NAME) != 0 || XGetWMName(display, window, &textProp) != 0) { 14575 if ((textProp.encoding == XA_UTF8 || textProp.encoding == XA_STRING) && textProp.format == 8) { 14576 return textProp.value[0 .. textProp.nitems].idup.split('\0'); 14577 } else 14578 return []; 14579 } else 14580 return null; 14581 } 14582 14583 string getTitle() { 14584 auto titles = getTitles(); 14585 return titles.length ? titles[0] : null; 14586 } 14587 14588 void setMinSize (int minwidth, int minheight) { 14589 import core.stdc.config : c_long; 14590 if (minwidth < 1) minwidth = 1; 14591 if (minheight < 1) minheight = 1; 14592 XSizeHints sh; 14593 c_long spr; 14594 XGetWMNormalHints(display, window, &sh, &spr); 14595 sh.min_width = minwidth; 14596 sh.min_height = minheight; 14597 sh.flags |= PMinSize; 14598 XSetWMNormalHints(display, window, &sh); 14599 flushGui(); 14600 } 14601 14602 void setMaxSize (int maxwidth, int maxheight) { 14603 import core.stdc.config : c_long; 14604 if (maxwidth < 1) maxwidth = 1; 14605 if (maxheight < 1) maxheight = 1; 14606 XSizeHints sh; 14607 c_long spr; 14608 XGetWMNormalHints(display, window, &sh, &spr); 14609 sh.max_width = maxwidth; 14610 sh.max_height = maxheight; 14611 sh.flags |= PMaxSize; 14612 XSetWMNormalHints(display, window, &sh); 14613 flushGui(); 14614 } 14615 14616 void setResizeGranularity (int granx, int grany) { 14617 import core.stdc.config : c_long; 14618 if (granx < 1) granx = 1; 14619 if (grany < 1) grany = 1; 14620 XSizeHints sh; 14621 c_long spr; 14622 XGetWMNormalHints(display, window, &sh, &spr); 14623 sh.width_inc = granx; 14624 sh.height_inc = grany; 14625 sh.flags |= PResizeInc; 14626 XSetWMNormalHints(display, window, &sh); 14627 flushGui(); 14628 } 14629 14630 void setOpacity (uint opacity) { 14631 arch_ulong o = opacity; 14632 if (opacity == uint.max) 14633 XDeleteProperty(display, window, XInternAtom(display, "_NET_WM_WINDOW_OPACITY".ptr, false)); 14634 else 14635 XChangeProperty(display, window, XInternAtom(display, "_NET_WM_WINDOW_OPACITY".ptr, false), 14636 XA_CARDINAL, 32, PropModeReplace, &o, 1); 14637 } 14638 14639 void createWindow(int width, int height, string title, in OpenGlOptions opengl, SimpleWindow parent) { 14640 version(without_opengl) {} else if(opengl == OpenGlOptions.yes && !openGlLibrariesSuccessfullyLoaded) throw new Exception("OpenGL libraries did not load"); 14641 display = XDisplayConnection.get(); 14642 auto screen = DefaultScreen(display); 14643 14644 bool overrideRedirect = false; 14645 if(windowType == WindowTypes.dropdownMenu || windowType == WindowTypes.popupMenu || windowType == WindowTypes.notification)// || windowType == WindowTypes.nestedChild) 14646 overrideRedirect = true; 14647 14648 version(without_opengl) {} 14649 else { 14650 if(opengl == OpenGlOptions.yes) { 14651 GLXFBConfig fbconf = null; 14652 XVisualInfo* vi = null; 14653 bool useLegacy = false; 14654 static if (SdpyIsUsingIVGLBinds) {if (glbindGetProcAddress("glHint") is null) assert(0, "GL: error loading OpenGL"); } // loads all necessary functions 14655 if (sdpyOpenGLContextVersion != 0 && glXCreateContextAttribsARB_present()) { 14656 int[23] visualAttribs = [ 14657 GLX_X_RENDERABLE , 1/*True*/, 14658 GLX_DRAWABLE_TYPE, GLX_WINDOW_BIT, 14659 GLX_RENDER_TYPE , GLX_RGBA_BIT, 14660 GLX_X_VISUAL_TYPE, GLX_TRUE_COLOR, 14661 GLX_RED_SIZE , 8, 14662 GLX_GREEN_SIZE , 8, 14663 GLX_BLUE_SIZE , 8, 14664 GLX_ALPHA_SIZE , 8, 14665 GLX_DEPTH_SIZE , 24, 14666 GLX_STENCIL_SIZE , 8, 14667 GLX_DOUBLEBUFFER , 1/*True*/, 14668 0/*None*/, 14669 ]; 14670 int fbcount; 14671 GLXFBConfig* fbc = glXChooseFBConfig(display, screen, visualAttribs.ptr, &fbcount); 14672 if (fbcount == 0) { 14673 useLegacy = true; // try to do at least something 14674 } else { 14675 // pick the FB config/visual with the most samples per pixel 14676 int bestidx = -1, bestns = -1; 14677 foreach (int fbi; 0..fbcount) { 14678 int sb, samples; 14679 glXGetFBConfigAttrib(display, fbc[fbi], GLX_SAMPLE_BUFFERS, &sb); 14680 glXGetFBConfigAttrib(display, fbc[fbi], GLX_SAMPLES, &samples); 14681 if (bestidx < 0 || sb && samples > bestns) { bestidx = fbi; bestns = samples; } 14682 } 14683 //{ import core.stdc.stdio; printf("found gl visual with %d samples\n", bestns); } 14684 fbconf = fbc[bestidx]; 14685 // Be sure to free the FBConfig list allocated by glXChooseFBConfig() 14686 XFree(fbc); 14687 vi = cast(XVisualInfo*)glXGetVisualFromFBConfig(display, fbconf); 14688 } 14689 } 14690 if (vi is null || useLegacy) { 14691 static immutable GLint[5] attrs = [ GLX_RGBA, GLX_DEPTH_SIZE, 24, GLX_DOUBLEBUFFER, None ]; 14692 vi = cast(XVisualInfo*)glXChooseVisual(display, 0, attrs.ptr); 14693 useLegacy = true; 14694 } 14695 if (vi is null) throw new Exception("no open gl visual found"); 14696 14697 XSetWindowAttributes swa; 14698 auto root = RootWindow(display, screen); 14699 swa.colormap = XCreateColormap(display, root, vi.visual, AllocNone); 14700 14701 swa.override_redirect = overrideRedirect; 14702 14703 window = XCreateWindow(display, (windowType != WindowTypes.nestedChild || parent is null) ? root : parent.impl.window, 14704 0, 0, width, height, 14705 0, vi.depth, 1 /* InputOutput */, vi.visual, CWColormap | CWOverrideRedirect, &swa); 14706 14707 // now try to use `glXCreateContextAttribsARB()` if it's here 14708 if (!useLegacy) { 14709 // request fairly advanced context, even with stencil buffer! 14710 int[9] contextAttribs = [ 14711 GLX_CONTEXT_MAJOR_VERSION_ARB, (sdpyOpenGLContextVersion>>8), 14712 GLX_CONTEXT_MINOR_VERSION_ARB, (sdpyOpenGLContextVersion&0xff), 14713 /*GLX_CONTEXT_PROFILE_MASK_ARB*/0x9126, (sdpyOpenGLContextCompatible ? /*GLX_CONTEXT_COMPATIBILITY_PROFILE_BIT_ARB*/0x02 : /*GLX_CONTEXT_CORE_PROFILE_BIT_ARB*/ 0x01), 14714 // for modern context, set "forward compatibility" flag too 14715 (sdpyOpenGLContextCompatible ? None : /*GLX_CONTEXT_FLAGS_ARB*/ 0x2094), /*GLX_CONTEXT_FORWARD_COMPATIBLE_BIT_ARB*/ 0x02, 14716 0/*None*/, 14717 ]; 14718 glc = glXCreateContextAttribsARB(display, fbconf, null, 1/*True*/, contextAttribs.ptr); 14719 if (glc is null && sdpyOpenGLContextAllowFallback) { 14720 sdpyOpenGLContextVersion = 0; 14721 glc = glXCreateContext(display, vi, null, /*GL_TRUE*/1); 14722 } 14723 //{ import core.stdc.stdio; printf("using modern ogl v%d.%d\n", contextAttribs[1], contextAttribs[3]); } 14724 } else { 14725 // fallback to old GLX call 14726 if (sdpyOpenGLContextAllowFallback || sdpyOpenGLContextVersion == 0) { 14727 sdpyOpenGLContextVersion = 0; 14728 glc = glXCreateContext(display, vi, null, /*GL_TRUE*/1); 14729 } 14730 } 14731 // sync to ensure any errors generated are processed 14732 XSync(display, 0/*False*/); 14733 //{ import core.stdc.stdio; printf("ogl is here\n"); } 14734 if(glc is null) 14735 throw new Exception("glc"); 14736 } 14737 } 14738 14739 if(opengl == OpenGlOptions.no) { 14740 14741 XSetWindowAttributes swa; 14742 swa.background_pixel = WhitePixel(display, screen); 14743 swa.border_pixel = BlackPixel(display, screen); 14744 swa.override_redirect = overrideRedirect; 14745 auto root = RootWindow(display, screen); 14746 swa.colormap = XCreateColormap(display, root, DefaultVisual(display, screen), AllocNone); 14747 14748 window = XCreateWindow(display, (windowType != WindowTypes.nestedChild || parent is null) ? root : parent.impl.window, 14749 0, 0, width, height, 14750 // I'm commenting that CWBackPixel thing just because it actually causes flicker for no apparent benefit. 14751 0, CopyFromParent, 1 /* InputOutput */, cast(Visual*) CopyFromParent, CWColormap /*| CWBackPixel*/ | CWBorderPixel | CWOverrideRedirect, &swa); 14752 14753 14754 14755 /* 14756 window = XCreateSimpleWindow( 14757 display, 14758 parent is null ? RootWindow(display, screen) : parent.impl.window, 14759 0, 0, // x, y 14760 width, height, 14761 1, // border width 14762 BlackPixel(display, screen), // border 14763 WhitePixel(display, screen)); // background 14764 */ 14765 14766 buffer = XCreatePixmap(display, cast(Drawable) window, width, height, DefaultDepthOfDisplay(display)); 14767 bufferw = width; 14768 bufferh = height; 14769 14770 gc = DefaultGC(display, screen); 14771 14772 // clear out the buffer to get us started... 14773 XSetForeground(display, gc, WhitePixel(display, screen)); 14774 XFillRectangle(display, cast(Drawable) buffer, gc, 0, 0, width, height); 14775 XSetForeground(display, gc, BlackPixel(display, screen)); 14776 } 14777 14778 // input context 14779 //TODO: create this only for top-level windows, and reuse that? 14780 populateXic(); 14781 14782 if (sdpyWindowClassStr is null) loadBinNameToWindowClassName(); 14783 if (sdpyWindowClassStr is null) sdpyWindowClass = "DSimpleWindow"; 14784 // window class 14785 if (sdpyWindowClassStr !is null && sdpyWindowClassStr[0]) { 14786 //{ import core.stdc.stdio; printf("winclass: [%s]\n", sdpyWindowClassStr); } 14787 XClassHint klass; 14788 XWMHints wh; 14789 if(this.customizationFlags & WindowFlags.managesChildWindowFocus) { 14790 wh.input = true; 14791 wh.flags |= InputHint; 14792 } 14793 XSizeHints size; 14794 klass.res_name = sdpyWindowClassStr; 14795 klass.res_class = sdpyWindowClassStr; 14796 XSetWMProperties(display, window, null, null, null, 0, &size, &wh, &klass); 14797 } 14798 14799 setTitle(title); 14800 SimpleWindow.nativeMapping[window] = this; 14801 CapableOfHandlingNativeEvent.nativeHandleMapping[window] = this; 14802 14803 // This gives our window a close button 14804 if (windowType != WindowTypes.eventOnly) { 14805 Atom[2] atoms = [GetAtom!"WM_DELETE_WINDOW"(display), GetAtom!"WM_TAKE_FOCUS"(display)]; 14806 int useAtoms; 14807 if(this.customizationFlags & WindowFlags.managesChildWindowFocus) { 14808 useAtoms = 2; 14809 } else { 14810 useAtoms = 1; 14811 } 14812 assert(useAtoms <= atoms.length); 14813 XSetWMProtocols(display, window, atoms.ptr, useAtoms); 14814 } 14815 14816 // FIXME: windowType and customizationFlags 14817 Atom[8] wsatoms; // here, due to goto 14818 int wmsacount = 0; // here, due to goto 14819 14820 try 14821 final switch(windowType) { 14822 case WindowTypes.normal: 14823 setNetWMWindowType(GetAtom!"_NET_WM_WINDOW_TYPE_NORMAL"(display)); 14824 break; 14825 case WindowTypes.undecorated: 14826 motifHideDecorations(); 14827 setNetWMWindowType(GetAtom!"_NET_WM_WINDOW_TYPE_NORMAL"(display)); 14828 break; 14829 case WindowTypes.eventOnly: 14830 _hidden = true; 14831 XSelectInput(display, window, EventMask.StructureNotifyMask); // without this, we won't get destroy notification 14832 goto hiddenWindow; 14833 //break; 14834 case WindowTypes.nestedChild: 14835 // handled in XCreateWindow calls 14836 break; 14837 14838 case WindowTypes.dropdownMenu: 14839 motifHideDecorations(); 14840 setNetWMWindowType(GetAtom!"_NET_WM_WINDOW_TYPE_DROPDOWN_MENU"(display)); 14841 customizationFlags |= WindowFlags.skipTaskbar | WindowFlags.alwaysOnTop; 14842 break; 14843 case WindowTypes.popupMenu: 14844 motifHideDecorations(); 14845 setNetWMWindowType(GetAtom!"_NET_WM_WINDOW_TYPE_POPUP_MENU"(display)); 14846 customizationFlags |= WindowFlags.skipTaskbar | WindowFlags.alwaysOnTop; 14847 break; 14848 case WindowTypes.notification: 14849 motifHideDecorations(); 14850 setNetWMWindowType(GetAtom!"_NET_WM_WINDOW_TYPE_NOTIFICATION"(display)); 14851 customizationFlags |= WindowFlags.skipTaskbar | WindowFlags.alwaysOnTop; 14852 break; 14853 case WindowTypes.minimallyWrapped: 14854 assert(0, "don't create a minimallyWrapped thing explicitly!"); 14855 /+ 14856 case WindowTypes.menu: 14857 atoms[0] = GetAtom!"_NET_WM_WINDOW_TYPE_MENU"(display); 14858 motifHideDecorations(); 14859 break; 14860 case WindowTypes.desktop: 14861 atoms[0] = GetAtom!"_NET_WM_WINDOW_TYPE_DESKTOP"(display); 14862 break; 14863 case WindowTypes.dock: 14864 atoms[0] = GetAtom!"_NET_WM_WINDOW_TYPE_DOCK"(display); 14865 break; 14866 case WindowTypes.toolbar: 14867 atoms[0] = GetAtom!"_NET_WM_WINDOW_TYPE_TOOLBAR"(display); 14868 break; 14869 case WindowTypes.menu: 14870 atoms[0] = GetAtom!"_NET_WM_WINDOW_TYPE_MENU"(display); 14871 break; 14872 case WindowTypes.utility: 14873 atoms[0] = GetAtom!"_NET_WM_WINDOW_TYPE_UTILITY"(display); 14874 break; 14875 case WindowTypes.splash: 14876 atoms[0] = GetAtom!"_NET_WM_WINDOW_TYPE_SPLASH"(display); 14877 break; 14878 case WindowTypes.dialog: 14879 atoms[0] = GetAtom!"_NET_WM_WINDOW_TYPE_DIALOG"(display); 14880 break; 14881 case WindowTypes.tooltip: 14882 atoms[0] = GetAtom!"_NET_WM_WINDOW_TYPE_TOOLTIP"(display); 14883 break; 14884 case WindowTypes.notification: 14885 atoms[0] = GetAtom!"_NET_WM_WINDOW_TYPE_NOTIFICATION"(display); 14886 break; 14887 case WindowTypes.combo: 14888 atoms[0] = GetAtom!"_NET_WM_WINDOW_TYPE_COMBO"(display); 14889 break; 14890 case WindowTypes.dnd: 14891 atoms[0] = GetAtom!"_NET_WM_WINDOW_TYPE_DND"(display); 14892 break; 14893 +/ 14894 } 14895 catch(Exception e) { 14896 // XInternAtom failed, prolly a WM 14897 // that doesn't support these things 14898 } 14899 14900 if (customizationFlags&WindowFlags.skipTaskbar) wsatoms[wmsacount++] = GetAtom!("_NET_WM_STATE_SKIP_TASKBAR", true)(display); 14901 // the two following flags may be ignored by WM 14902 if (customizationFlags&WindowFlags.alwaysOnTop) wsatoms[wmsacount++] = GetAtom!("_NET_WM_STATE_ABOVE", true)(display); 14903 if (customizationFlags&WindowFlags.alwaysOnBottom) wsatoms[wmsacount++] = GetAtom!("_NET_WM_STATE_BELOW", true)(display); 14904 14905 if (wmsacount != 0) XChangeProperty(display, window, GetAtom!("_NET_WM_STATE", true)(display), XA_ATOM, 32 /* bits */,0 /*PropModeReplace*/, wsatoms.ptr, wmsacount); 14906 14907 if (this.resizability == Resizability.fixedSize || (opengl == OpenGlOptions.no && this.resizability != Resizability.allowResizing)) fixFixedSize!true(width, height); 14908 14909 // What would be ideal here is if they only were 14910 // selected if there was actually an event handler 14911 // for them... 14912 14913 selectDefaultInput((customizationFlags & WindowFlags.alwaysRequestMouseMotionEvents)?true:false); 14914 14915 hiddenWindow: 14916 14917 // set the pid property for lookup later by window managers 14918 // a standard convenience 14919 import core.sys.posix.unistd; 14920 arch_ulong pid = getpid(); 14921 14922 XChangeProperty( 14923 display, 14924 impl.window, 14925 GetAtom!("_NET_WM_PID", true)(display), 14926 XA_CARDINAL, 14927 32 /* bits */, 14928 0 /*PropModeReplace*/, 14929 &pid, 14930 1); 14931 14932 if(isTransient && parent) { // customizationFlags & WindowFlags.transient) { 14933 if(parent is null) assert(0); 14934 XChangeProperty( 14935 display, 14936 impl.window, 14937 GetAtom!("WM_TRANSIENT_FOR", true)(display), 14938 XA_WINDOW, 14939 32 /* bits */, 14940 0 /*PropModeReplace*/, 14941 &parent.impl.window, 14942 1); 14943 14944 } 14945 14946 if(windowType != WindowTypes.eventOnly && (customizationFlags&WindowFlags.dontAutoShow) == 0) { 14947 XMapWindow(display, window); 14948 } else { 14949 _hidden = true; 14950 } 14951 } 14952 14953 void populateXic() { 14954 if (XDisplayConnection.xim !is null) { 14955 xic = XCreateIC(XDisplayConnection.xim, 14956 /*XNInputStyle*/"inputStyle".ptr, XIMPreeditNothing|XIMStatusNothing, 14957 /*XNClientWindow*/"clientWindow".ptr, window, 14958 /*XNFocusWindow*/"focusWindow".ptr, window, 14959 null); 14960 if (xic is null) { 14961 import core.stdc.stdio : stderr, fprintf; 14962 fprintf(stderr, "XCreateIC failed for window %u\n", cast(uint)window); 14963 } 14964 } 14965 } 14966 14967 void selectDefaultInput(bool forceIncludeMouseMotion) { 14968 auto mask = EventMask.ExposureMask | 14969 EventMask.KeyPressMask | 14970 EventMask.KeyReleaseMask | 14971 EventMask.PropertyChangeMask | 14972 EventMask.FocusChangeMask | 14973 EventMask.StructureNotifyMask | 14974 EventMask.SubstructureNotifyMask | 14975 EventMask.VisibilityChangeMask 14976 | EventMask.ButtonPressMask 14977 | EventMask.ButtonReleaseMask 14978 ; 14979 14980 // xshm is our shortcut for local connections 14981 if(XDisplayConnection.isLocal || forceIncludeMouseMotion) 14982 mask |= EventMask.PointerMotionMask; 14983 else 14984 mask |= EventMask.ButtonMotionMask; 14985 14986 XSelectInput(display, window, mask); 14987 } 14988 14989 14990 void setNetWMWindowType(Atom type) { 14991 Atom[2] atoms; 14992 14993 atoms[0] = type; 14994 // generic fallback 14995 atoms[1] = GetAtom!"_NET_WM_WINDOW_TYPE_NORMAL"(display); 14996 14997 XChangeProperty( 14998 display, 14999 impl.window, 15000 GetAtom!"_NET_WM_WINDOW_TYPE"(display), 15001 XA_ATOM, 15002 32 /* bits */, 15003 0 /*PropModeReplace*/, 15004 atoms.ptr, 15005 cast(int) atoms.length); 15006 } 15007 15008 void motifHideDecorations(bool hide = true) { 15009 MwmHints hints; 15010 hints.flags = MWM_HINTS_DECORATIONS; 15011 hints.decorations = hide ? 0 : 1; 15012 15013 XChangeProperty( 15014 display, 15015 impl.window, 15016 GetAtom!"_MOTIF_WM_HINTS"(display), 15017 GetAtom!"_MOTIF_WM_HINTS"(display), 15018 32 /* bits */, 15019 0 /*PropModeReplace*/, 15020 &hints, 15021 hints.sizeof / 4); 15022 } 15023 15024 /*k8: unused 15025 void createOpenGlContext() { 15026 15027 } 15028 */ 15029 15030 void closeWindow() { 15031 // I can't close this or a child window closing will 15032 // break events for everyone. So I'm just leaking it right 15033 // now and that is probably perfectly fine... 15034 version(none) 15035 if (customEventFDRead != -1) { 15036 import core.sys.posix.unistd : close; 15037 auto same = customEventFDRead == customEventFDWrite; 15038 15039 close(customEventFDRead); 15040 if(!same) 15041 close(customEventFDWrite); 15042 customEventFDRead = -1; 15043 customEventFDWrite = -1; 15044 } 15045 if(buffer) 15046 XFreePixmap(display, buffer); 15047 bufferw = bufferh = 0; 15048 if (blankCurPtr && cursorSequenceNumber == XDisplayConnection.connectionSequenceNumber) XFreeCursor(display, blankCurPtr); 15049 XDestroyWindow(display, window); 15050 XFlush(display); 15051 } 15052 15053 void dispose() { 15054 } 15055 15056 bool destroyed = false; 15057 } 15058 15059 bool insideXEventLoop; 15060 } 15061 15062 version(X11) { 15063 15064 int mouseDoubleClickTimeout = 350; /// Double click timeout. X only, you probably shouldn't change this. 15065 15066 private class ResizeEvent { 15067 int width, height; 15068 } 15069 15070 void recordX11ResizeAsync(Display* display, SimpleWindow win, int width, int height) { 15071 if(win.windowType == WindowTypes.minimallyWrapped) 15072 return; 15073 15074 if(win.pendingResizeEvent is null) { 15075 win.pendingResizeEvent = new ResizeEvent(); 15076 win.addEventListener((ResizeEvent re) { 15077 recordX11Resize(XDisplayConnection.get, win, re.width, re.height); 15078 }); 15079 } 15080 win.pendingResizeEvent.width = width; 15081 win.pendingResizeEvent.height = height; 15082 if(!win.eventQueued!ResizeEvent) { 15083 win.postEvent(win.pendingResizeEvent); 15084 } 15085 } 15086 15087 void recordX11Resize(Display* display, SimpleWindow win, int width, int height) { 15088 if(win.windowType == WindowTypes.minimallyWrapped) 15089 return; 15090 if(win.closed) 15091 return; 15092 15093 if(width != win.width || height != win.height) { 15094 15095 // import std.stdio; writeln("RESIZE: ", width, "x", height, " was ", win._width, "x", win._height, " window: ", win.windowType, "-", win.title, " ", win.window); 15096 win._width = width; 15097 win._height = height; 15098 15099 if(win.openglMode == OpenGlOptions.no) { 15100 // FIXME: could this be more efficient? 15101 15102 if (win.bufferw < width || win.bufferh < height) { 15103 //{ 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); } 15104 // grow the internal buffer to match the window... 15105 auto newPixmap = XCreatePixmap(display, cast(Drawable) win.window, width, height, DefaultDepthOfDisplay(display)); 15106 { 15107 GC xgc = XCreateGC(win.display, cast(Drawable)win.window, 0, null); 15108 XCopyGC(win.display, win.gc, 0xffffffff, xgc); 15109 scope(exit) XFreeGC(win.display, xgc); 15110 XSetClipMask(win.display, xgc, None); 15111 XSetForeground(win.display, xgc, 0); 15112 XFillRectangle(display, cast(Drawable)newPixmap, xgc, 0, 0, width, height); 15113 } 15114 XCopyArea(display, 15115 cast(Drawable) win.buffer, 15116 cast(Drawable) newPixmap, 15117 win.gc, 0, 0, 15118 win.bufferw < width ? win.bufferw : win.width, 15119 win.bufferh < height ? win.bufferh : win.height, 15120 0, 0); 15121 15122 XFreePixmap(display, win.buffer); 15123 win.buffer = newPixmap; 15124 win.bufferw = width; 15125 win.bufferh = height; 15126 } 15127 15128 // clear unused parts of the buffer 15129 if (win.bufferw > width || win.bufferh > height) { 15130 GC xgc = XCreateGC(win.display, cast(Drawable)win.window, 0, null); 15131 XCopyGC(win.display, win.gc, 0xffffffff, xgc); 15132 scope(exit) XFreeGC(win.display, xgc); 15133 XSetClipMask(win.display, xgc, None); 15134 XSetForeground(win.display, xgc, 0); 15135 immutable int maxw = (win.bufferw > width ? win.bufferw : width); 15136 immutable int maxh = (win.bufferh > height ? win.bufferh : height); 15137 XFillRectangle(win.display, cast(Drawable)win.buffer, xgc, width, 0, maxw, maxh); // let X11 do clipping 15138 XFillRectangle(win.display, cast(Drawable)win.buffer, xgc, 0, height, maxw, maxh); // let X11 do clipping 15139 } 15140 15141 } 15142 15143 win.updateOpenglViewportIfNeeded(width, height); 15144 15145 win.fixFixedSize(width, height); //k8: this does nothing on my FluxBox; wtf?! 15146 15147 if(win.resizability != Resizability.automaticallyScaleIfPossible) 15148 if(win.windowResized !is null) { 15149 XUnlockDisplay(display); 15150 scope(exit) XLockDisplay(display); 15151 win.windowResized(width, height); 15152 } 15153 } 15154 } 15155 15156 15157 /// Platform-specific, you might use it when doing a custom event loop. 15158 bool doXNextEvent(Display* display) { 15159 bool done; 15160 XEvent e; 15161 XNextEvent(display, &e); 15162 version(sddddd) { 15163 import std.stdio, std.conv : to; 15164 if(auto win = e.xany.window in CapableOfHandlingNativeEvent.nativeHandleMapping) { 15165 if(typeid(cast(Object) *win) == NotificationAreaIcon.classinfo) 15166 writeln("event for: ", e.xany.window, "; type is ", to!string(cast(EventType)e.type)); 15167 } 15168 } 15169 15170 // filter out compose events 15171 if (XFilterEvent(&e, None)) { 15172 //{ import core.stdc.stdio : printf; printf("XFilterEvent filtered!\n"); } 15173 //NOTE: we should ungrab keyboard here, but simpledisplay doesn't use keyboard grabbing (yet) 15174 return false; 15175 } 15176 // process keyboard mapping changes 15177 if (e.type == EventType.KeymapNotify) { 15178 //{ import core.stdc.stdio : printf; printf("KeymapNotify processed!\n"); } 15179 XRefreshKeyboardMapping(&e.xmapping); 15180 return false; 15181 } 15182 15183 version(with_eventloop) 15184 import arsd.eventloop; 15185 15186 if(SimpleWindow.handleNativeGlobalEvent !is null) { 15187 // see windows impl's comments 15188 XUnlockDisplay(display); 15189 scope(exit) XLockDisplay(display); 15190 auto ret = SimpleWindow.handleNativeGlobalEvent(e); 15191 if(ret == 0) 15192 return done; 15193 } 15194 15195 15196 if(auto win = e.xany.window in CapableOfHandlingNativeEvent.nativeHandleMapping) { 15197 if(win.getNativeEventHandler !is null) { 15198 XUnlockDisplay(display); 15199 scope(exit) XLockDisplay(display); 15200 auto ret = win.getNativeEventHandler()(e); 15201 if(ret == 0) 15202 return done; 15203 } 15204 } 15205 15206 if(xrrEventBase != -1 && e.type == xrrEventBase + RRScreenChangeNotify) { 15207 if(auto win = e.xany.window in SimpleWindow.nativeMapping) { 15208 // we get this because of the RRScreenChangeNotifyMask 15209 15210 // this isn't actually an ideal way to do it since it wastes time 15211 // but meh it is simple and it works. 15212 win.actualDpiLoadAttempted = false; 15213 SimpleWindow.xRandrInfoLoadAttemped = false; 15214 win.updateActualDpi(); // trigger a reload 15215 } 15216 } 15217 15218 switch(e.type) { 15219 case EventType.SelectionClear: 15220 if(auto win = e.xselectionclear.window in SimpleWindow.nativeMapping) { 15221 // FIXME so it is supposed to finish any in progress transfers... but idk... 15222 //import std.stdio; writeln("SelectionClear"); 15223 SimpleWindow.impl.setSelectionHandlers.remove(e.xselectionclear.selection); 15224 } 15225 break; 15226 case EventType.SelectionRequest: 15227 if(auto win = e.xselectionrequest.owner in SimpleWindow.nativeMapping) 15228 if(auto ssh = e.xselectionrequest.selection in SimpleWindow.impl.setSelectionHandlers) { 15229 // import std.stdio; printf("SelectionRequest %s\n", XGetAtomName(e.xselectionrequest.display, e.xselectionrequest.target)); 15230 XUnlockDisplay(display); 15231 scope(exit) XLockDisplay(display); 15232 (*ssh).handleRequest(e); 15233 } 15234 break; 15235 case EventType.PropertyNotify: 15236 // import std.stdio; printf("PropertyNotify %s %d\n", XGetAtomName(e.xproperty.display, e.xproperty.atom), e.xproperty.state); 15237 15238 foreach(ssh; SimpleWindow.impl.setSelectionHandlers) { 15239 if(ssh.matchesIncr(e.xproperty.window, e.xproperty.atom) && e.xproperty.state == PropertyNotification.PropertyDelete) 15240 ssh.sendMoreIncr(&e.xproperty); 15241 } 15242 15243 15244 if(auto win = e.xproperty.window in SimpleWindow.nativeMapping) 15245 if(auto handler = e.xproperty.atom in win.getSelectionHandlers) { 15246 if(handler.matchesIncr(e.xproperty.window, e.xproperty.atom) && e.xproperty.state == PropertyNotification.PropertyNewValue) { 15247 Atom target; 15248 int format; 15249 arch_ulong bytesafter, length; 15250 void* value; 15251 15252 ubyte[] s; 15253 Atom targetToKeep; 15254 15255 XGetWindowProperty( 15256 e.xproperty.display, 15257 e.xproperty.window, 15258 e.xproperty.atom, 15259 0, 15260 100000 /* length */, 15261 true, /* erase it to signal we got it and want more */ 15262 0 /*AnyPropertyType*/, 15263 &target, &format, &length, &bytesafter, &value); 15264 15265 if(!targetToKeep) 15266 targetToKeep = target; 15267 15268 auto id = (cast(ubyte*) value)[0 .. length]; 15269 15270 handler.handleIncrData(targetToKeep, id); 15271 15272 XFree(value); 15273 } 15274 } 15275 break; 15276 case EventType.SelectionNotify: 15277 if(auto win = e.xselection.requestor in SimpleWindow.nativeMapping) 15278 if(auto handler = e.xproperty.atom in win.getSelectionHandlers) { 15279 if(e.xselection.property == None) { // || e.xselection.property == GetAtom!("NULL", true)(e.xselection.display)) { 15280 XUnlockDisplay(display); 15281 scope(exit) XLockDisplay(display); 15282 handler.handleData(None, null); 15283 } else { 15284 Atom target; 15285 int format; 15286 arch_ulong bytesafter, length; 15287 void* value; 15288 XGetWindowProperty( 15289 e.xselection.display, 15290 e.xselection.requestor, 15291 e.xselection.property, 15292 0, 15293 100000 /* length */, 15294 //false, /* don't erase it */ 15295 true, /* do erase it lol */ 15296 0 /*AnyPropertyType*/, 15297 &target, &format, &length, &bytesafter, &value); 15298 15299 // FIXME: I don't have to copy it now since it is in char[] instead of string 15300 15301 { 15302 XUnlockDisplay(display); 15303 scope(exit) XLockDisplay(display); 15304 15305 if(target == XA_ATOM) { 15306 // initial request, see what they are able to work with and request the best one 15307 // we can handle, if available 15308 15309 Atom[] answer = (cast(Atom*) value)[0 .. length]; 15310 Atom best = handler.findBestFormat(answer); 15311 15312 /+ 15313 writeln("got ", answer); 15314 foreach(a; answer) 15315 printf("%s\n", XGetAtomName(display, a)); 15316 writeln("best ", best); 15317 +/ 15318 15319 if(best != None) { 15320 // actually request the best format 15321 XConvertSelection(e.xselection.display, e.xselection.selection, best, GetAtom!("SDD_DATA", true)(display), e.xselection.requestor, 0 /*CurrentTime*/); 15322 } 15323 } else if(target == GetAtom!"INCR"(display)) { 15324 // incremental 15325 15326 handler.prepareIncremental(e.xselection.requestor, e.xselection.property); 15327 15328 // signal the sending program that we see 15329 // the incr and are ready to receive more. 15330 XDeleteProperty( 15331 e.xselection.display, 15332 e.xselection.requestor, 15333 e.xselection.property); 15334 } else { 15335 // unsupported type... maybe, forward 15336 handler.handleData(target, cast(ubyte[]) value[0 .. length]); 15337 } 15338 } 15339 XFree(value); 15340 /* 15341 XDeleteProperty( 15342 e.xselection.display, 15343 e.xselection.requestor, 15344 e.xselection.property); 15345 */ 15346 } 15347 } 15348 break; 15349 case EventType.ConfigureNotify: 15350 auto event = e.xconfigure; 15351 if(auto win = event.window in SimpleWindow.nativeMapping) { 15352 if(win.windowType == WindowTypes.minimallyWrapped) 15353 break; 15354 //version(sdddd) { import std.stdio; writeln(" w=", event.width, "; h=", event.height); } 15355 15356 /+ 15357 The ICCCM says window managers must send a synthetic event when the window 15358 is moved but NOT when it is resized. In the resize case, an event is sent 15359 with position (0, 0) which can be wrong and break the dpi calculations. 15360 15361 So we only consider the synthetic events from the WM and otherwise 15362 need to wait for some other event to get the position which... sucks. 15363 15364 I'd rather not have windows changing their layout on mouse motion after 15365 switching monitors... might be forced to but for now just ignoring it. 15366 15367 Easiest way to switch monitors without sending a size position is by 15368 maximize or fullscreen in a setup like mine, but on most setups those 15369 work on the monitor it is already living on, so it should be ok most the 15370 time. 15371 +/ 15372 if(event.send_event) { 15373 win.screenPositionKnown = true; 15374 win.screenPositionX = event.x; 15375 win.screenPositionY = event.y; 15376 win.updateActualDpi(); 15377 } 15378 15379 win.updateIMEPopupLocation(); 15380 recordX11ResizeAsync(display, *win, event.width, event.height); 15381 } 15382 break; 15383 case EventType.Expose: 15384 if(auto win = e.xexpose.window in SimpleWindow.nativeMapping) { 15385 if(win.windowType == WindowTypes.minimallyWrapped) 15386 break; 15387 // if it is closing from a popup menu, it can get 15388 // an Expose event right by the end and trigger a 15389 // BadDrawable error ... we'll just check 15390 // closed to handle that. 15391 if((*win).closed) break; 15392 if((*win).openglMode == OpenGlOptions.no) { 15393 bool doCopy = true;// e.xexpose.count == 0; // the count is better if we copy all area but meh 15394 if (win.handleExpose !is null) doCopy = !win.handleExpose(e.xexpose.x, e.xexpose.y, e.xexpose.width, e.xexpose.height, e.xexpose.count); 15395 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); 15396 } else { 15397 // need to redraw the scene somehow 15398 if(e.xexpose.count == 0) { // only do the last one since redrawOpenGlSceneNow always does it all 15399 XUnlockDisplay(display); 15400 scope(exit) XLockDisplay(display); 15401 version(without_opengl) {} else 15402 win.redrawOpenGlSceneSoon(); 15403 } 15404 } 15405 } 15406 break; 15407 case EventType.FocusIn: 15408 case EventType.FocusOut: 15409 15410 if(auto win = e.xfocus.window in SimpleWindow.nativeMapping) { 15411 /+ 15412 15413 void info(string detail) { 15414 string s; 15415 import std.conv; 15416 import std.datetime; 15417 s ~= to!string(Clock.currTime); 15418 s ~= " "; 15419 s ~= e.type == EventType.FocusIn ? "in " : "out"; 15420 s ~= " "; 15421 s ~= win.windowType == WindowTypes.nestedChild ? "child " : "main "; 15422 s ~= e.xfocus.mode == NotifyModes.NotifyNormal ? " normal ": " grabbed "; 15423 s ~= detail; 15424 s ~= " "; 15425 15426 sdpyPrintDebugString(s); 15427 15428 } 15429 15430 switch(e.xfocus.detail) { 15431 case NotifyDetail.NotifyAncestor: info("Ancestor"); break; 15432 case NotifyDetail.NotifyVirtual: info("Virtual"); break; 15433 case NotifyDetail.NotifyInferior: info("Inferior"); break; 15434 case NotifyDetail.NotifyNonlinear: info("Nonlinear"); break; 15435 case NotifyDetail.NotifyNonlinearVirtual: info("nlinearvirtual"); break; 15436 case NotifyDetail.NotifyPointer: info("pointer"); break; 15437 case NotifyDetail.NotifyPointerRoot: info("pointerroot"); break; 15438 case NotifyDetail.NotifyDetailNone: info("none"); break; 15439 default: 15440 15441 } 15442 +/ 15443 15444 15445 if(e.xfocus.detail == NotifyDetail.NotifyPointer) 15446 break; // just ignore these they seem irrelevant 15447 15448 auto old = win._focused; 15449 win._focused = e.type == EventType.FocusIn; 15450 15451 // yes, we are losing the focus, but to our own child. that's actually kinda keeping it. 15452 if(e.type == EventType.FocusOut && e.xfocus.detail == NotifyDetail.NotifyInferior) 15453 win._focused = true; 15454 15455 if(win.demandingAttention) 15456 demandAttention(*win, false); 15457 15458 win.updateIMEFocused(); 15459 15460 if(old != win._focused && win.onFocusChange) { 15461 XUnlockDisplay(display); 15462 scope(exit) XLockDisplay(display); 15463 win.onFocusChange(win._focused); 15464 } 15465 } 15466 break; 15467 case EventType.VisibilityNotify: 15468 if(auto win = e.xfocus.window in SimpleWindow.nativeMapping) { 15469 if (e.xvisibility.state == VisibilityNotify.VisibilityFullyObscured) { 15470 if (win.visibilityChanged !is null) { 15471 XUnlockDisplay(display); 15472 scope(exit) XLockDisplay(display); 15473 win.visibilityChanged(false); 15474 } 15475 } else { 15476 if (win.visibilityChanged !is null) { 15477 XUnlockDisplay(display); 15478 scope(exit) XLockDisplay(display); 15479 win.visibilityChanged(true); 15480 } 15481 } 15482 } 15483 break; 15484 case EventType.ClientMessage: 15485 if (e.xclient.message_type == GetAtom!("_X11SDPY_INSMME_FLAG_EVENT_", true)(e.xany.display)) { 15486 // "ignore next mouse motion" event, increment ignore counter for teh window 15487 if (auto win = e.xclient.window in SimpleWindow.nativeMapping) { 15488 ++(*win).warpEventCount; 15489 debug(x11sdpy_warp_debug) { import core.stdc.stdio : printf; printf("X11: got \"INSMME\" message, new count=%d\n", (*win).warpEventCount); } 15490 } else { 15491 debug(x11sdpy_warp_debug) { import core.stdc.stdio : printf; printf("X11: got \"INSMME\" WTF?!!\n"); } 15492 } 15493 } else if(e.xclient.data.l[0] == GetAtom!"WM_DELETE_WINDOW"(e.xany.display)) { 15494 // user clicked the close button on the window manager 15495 if(auto win = e.xclient.window in SimpleWindow.nativeMapping) { 15496 XUnlockDisplay(display); 15497 scope(exit) XLockDisplay(display); 15498 if ((*win).closeQuery !is null) (*win).closeQuery(); else (*win).close(); 15499 } 15500 15501 } else if(e.xclient.data.l[0] == GetAtom!"WM_TAKE_FOCUS"(e.xany.display)) { 15502 //import std.stdio; writeln("HAPPENED"); 15503 // user clicked the close button on the window manager 15504 if(auto win = e.xclient.window in SimpleWindow.nativeMapping) { 15505 XUnlockDisplay(display); 15506 scope(exit) XLockDisplay(display); 15507 15508 auto setTo = *win; 15509 15510 if(win.setRequestedInputFocus !is null) { 15511 auto s = win.setRequestedInputFocus(); 15512 if(s !is null) { 15513 setTo = s; 15514 } 15515 } 15516 15517 assert(setTo !is null); 15518 15519 // FIXME: so this is actually supposed to focus to a relevant child window if appropriate 15520 15521 XSetInputFocus(display, setTo.impl.window, RevertToParent, e.xclient.data.l[1]); 15522 } 15523 } else if(e.xclient.message_type == GetAtom!"MANAGER"(e.xany.display)) { 15524 foreach(nai; NotificationAreaIcon.activeIcons) 15525 nai.newManager(); 15526 } else if(auto win = e.xclient.window in SimpleWindow.nativeMapping) { 15527 15528 bool xDragWindow = true; 15529 if(xDragWindow && e.xclient.message_type == GetAtom!"XdndStatus"(e.xany.display)) { 15530 //XDefineCursor(display, xDragWindow.impl.window, 15531 //import std.stdio; writeln("XdndStatus ", e.xclient.data.l); 15532 } 15533 if(auto dh = win.dropHandler) { 15534 15535 static Atom[3] xFormatsBuffer; 15536 static Atom[] xFormats; 15537 15538 void resetXFormats() { 15539 xFormatsBuffer[] = 0; 15540 xFormats = xFormatsBuffer[]; 15541 } 15542 15543 if(e.xclient.message_type == GetAtom!"XdndEnter"(e.xany.display)) { 15544 // on Windows it is supposed to return the effect you actually do FIXME 15545 15546 auto sourceWindow = e.xclient.data.l[0]; 15547 15548 xFormatsBuffer[0] = e.xclient.data.l[2]; 15549 xFormatsBuffer[1] = e.xclient.data.l[3]; 15550 xFormatsBuffer[2] = e.xclient.data.l[4]; 15551 15552 if(e.xclient.data.l[1] & 1) { 15553 // can just grab it all but like we don't necessarily need them... 15554 xFormats = cast(Atom[]) getX11PropertyData(sourceWindow, GetAtom!"XdndTypeList"(display), XA_ATOM); 15555 } else { 15556 int len; 15557 foreach(fmt; xFormatsBuffer) 15558 if(fmt) len++; 15559 xFormats = xFormatsBuffer[0 .. len]; 15560 } 15561 15562 auto pkg = DropPackage(*win, e.xclient.data.l[0], 0, xFormats); 15563 15564 dh.dragEnter(&pkg); 15565 } else if(e.xclient.message_type == GetAtom!"XdndPosition"(e.xany.display)) { 15566 15567 auto pack = e.xclient.data.l[2]; 15568 15569 auto result = dh.dragOver(Point((pack & 0xffff0000) >> 16, pack & 0xffff)); // FIXME: translate screen coordinates back to window coords 15570 15571 15572 XClientMessageEvent xclient; 15573 15574 xclient.type = EventType.ClientMessage; 15575 xclient.window = e.xclient.data.l[0]; 15576 xclient.message_type = GetAtom!"XdndStatus"(display); 15577 xclient.format = 32; 15578 xclient.data.l[0] = win.impl.window; 15579 xclient.data.l[1] = (result.action != DragAndDropAction.none) ? 1 : 0; // will accept 15580 auto r = result.consistentWithin; 15581 xclient.data.l[2] = ((cast(short) r.left) << 16) | (cast(short) r.top); 15582 xclient.data.l[3] = ((cast(short) r.width) << 16) | (cast(short) r.height); 15583 xclient.data.l[4] = dndActionAtom(e.xany.display, result.action); 15584 15585 XSendEvent( 15586 display, 15587 e.xclient.data.l[0], 15588 false, 15589 EventMask.NoEventMask, 15590 cast(XEvent*) &xclient 15591 ); 15592 15593 15594 } else if(e.xclient.message_type == GetAtom!"XdndLeave"(e.xany.display)) { 15595 //import std.stdio; writeln("XdndLeave"); 15596 // drop cancelled. 15597 // data.l[0] is the source window 15598 dh.dragLeave(); 15599 15600 resetXFormats(); 15601 } else if(e.xclient.message_type == GetAtom!"XdndDrop"(e.xany.display)) { 15602 // drop happening, should fetch data, then send finished 15603 //import std.stdio; writeln("XdndDrop"); 15604 15605 auto pkg = DropPackage(*win, e.xclient.data.l[0], e.xclient.data.l[2], xFormats); 15606 15607 dh.drop(&pkg); 15608 15609 resetXFormats(); 15610 } else if(e.xclient.message_type == GetAtom!"XdndFinished"(e.xany.display)) { 15611 // import std.stdio; writeln("XdndFinished"); 15612 15613 dh.finish(); 15614 } 15615 15616 } 15617 } 15618 break; 15619 case EventType.MapNotify: 15620 if(auto win = e.xmap.window in SimpleWindow.nativeMapping) { 15621 (*win)._visible = true; 15622 if (!(*win)._visibleForTheFirstTimeCalled) { 15623 (*win)._visibleForTheFirstTimeCalled = true; 15624 if ((*win).visibleForTheFirstTime !is null) { 15625 XUnlockDisplay(display); 15626 scope(exit) XLockDisplay(display); 15627 (*win).visibleForTheFirstTime(); 15628 } 15629 } 15630 if ((*win).visibilityChanged !is null) { 15631 XUnlockDisplay(display); 15632 scope(exit) XLockDisplay(display); 15633 (*win).visibilityChanged(true); 15634 } 15635 } 15636 break; 15637 case EventType.UnmapNotify: 15638 if(auto win = e.xunmap.window in SimpleWindow.nativeMapping) { 15639 win._visible = false; 15640 if (win.visibilityChanged !is null) { 15641 XUnlockDisplay(display); 15642 scope(exit) XLockDisplay(display); 15643 win.visibilityChanged(false); 15644 } 15645 } 15646 break; 15647 case EventType.DestroyNotify: 15648 if(auto win = e.xdestroywindow.window in SimpleWindow.nativeMapping) { 15649 if(win.destroyed) 15650 break; // might get a notification both for itself and from its parent 15651 if (win.onDestroyed !is null) try { win.onDestroyed(); } catch (Exception e) {} // sorry 15652 win._closed = true; // just in case 15653 win.destroyed = true; 15654 if (win.xic !is null) { 15655 XDestroyIC(win.xic); 15656 win.xic = null; // just in case 15657 } 15658 SimpleWindow.nativeMapping.remove(e.xdestroywindow.window); 15659 bool anyImportant = false; 15660 foreach(SimpleWindow w; SimpleWindow.nativeMapping) 15661 if(w.beingOpenKeepsAppOpen) { 15662 anyImportant = true; 15663 break; 15664 } 15665 if(!anyImportant) { 15666 EventLoop.quitApplication(); 15667 done = true; 15668 } 15669 } 15670 auto window = e.xdestroywindow.window; 15671 if(window in CapableOfHandlingNativeEvent.nativeHandleMapping) 15672 CapableOfHandlingNativeEvent.nativeHandleMapping.remove(window); 15673 15674 version(with_eventloop) { 15675 if(done) exit(); 15676 } 15677 break; 15678 15679 case EventType.MotionNotify: 15680 MouseEvent mouse; 15681 auto event = e.xmotion; 15682 15683 mouse.type = MouseEventType.motion; 15684 mouse.x = event.x; 15685 mouse.y = event.y; 15686 mouse.modifierState = event.state; 15687 15688 mouse.timestamp = event.time; 15689 15690 if(auto win = e.xmotion.window in SimpleWindow.nativeMapping) { 15691 mouse.window = *win; 15692 if (win.warpEventCount > 0) { 15693 debug(x11sdpy_warp_debug) { import core.stdc.stdio : printf; printf("X11: got \"warp motion\" message, current count=%d\n", (*win).warpEventCount); } 15694 --(*win).warpEventCount; 15695 (*win).mdx(mouse); // so deltas will be correctly updated 15696 } else { 15697 win.warpEventCount = 0; // just in case 15698 (*win).mdx(mouse); 15699 if((*win).handleMouseEvent) { 15700 XUnlockDisplay(display); 15701 scope(exit) XLockDisplay(display); 15702 (*win).handleMouseEvent(mouse); 15703 } 15704 } 15705 } 15706 15707 version(with_eventloop) 15708 send(mouse); 15709 break; 15710 case EventType.ButtonPress: 15711 case EventType.ButtonRelease: 15712 MouseEvent mouse; 15713 auto event = e.xbutton; 15714 15715 mouse.timestamp = event.time; 15716 15717 mouse.type = cast(MouseEventType) (e.type == EventType.ButtonPress ? 1 : 2); 15718 mouse.x = event.x; 15719 mouse.y = event.y; 15720 15721 static Time lastMouseDownTime = 0; 15722 15723 mouse.doubleClick = e.type == EventType.ButtonPress && (event.time - lastMouseDownTime) < mouseDoubleClickTimeout; 15724 if(e.type == EventType.ButtonPress) lastMouseDownTime = event.time; 15725 15726 switch(event.button) { 15727 case 1: mouse.button = MouseButton.left; break; // left 15728 case 2: mouse.button = MouseButton.middle; break; // middle 15729 case 3: mouse.button = MouseButton.right; break; // right 15730 case 4: mouse.button = MouseButton.wheelUp; break; // scroll up 15731 case 5: mouse.button = MouseButton.wheelDown; break; // scroll down 15732 case 6: break; // idk 15733 case 7: break; // idk 15734 case 8: mouse.button = MouseButton.backButton; break; 15735 case 9: mouse.button = MouseButton.forwardButton; break; 15736 default: 15737 } 15738 15739 // FIXME: double check this 15740 mouse.modifierState = event.state; 15741 15742 //mouse.modifierState = event.detail; 15743 15744 if(auto win = e.xbutton.window in SimpleWindow.nativeMapping) { 15745 mouse.window = *win; 15746 (*win).mdx(mouse); 15747 if((*win).handleMouseEvent) { 15748 XUnlockDisplay(display); 15749 scope(exit) XLockDisplay(display); 15750 (*win).handleMouseEvent(mouse); 15751 } 15752 } 15753 version(with_eventloop) 15754 send(mouse); 15755 break; 15756 15757 case EventType.KeyPress: 15758 case EventType.KeyRelease: 15759 //if (e.type == EventType.KeyPress) { import core.stdc.stdio : stderr, fprintf; fprintf(stderr, "X11 keyboard event!\n"); } 15760 KeyEvent ke; 15761 ke.pressed = e.type == EventType.KeyPress; 15762 ke.hardwareCode = cast(ubyte) e.xkey.keycode; 15763 15764 auto sym = XKeycodeToKeysym( 15765 XDisplayConnection.get(), 15766 e.xkey.keycode, 15767 0); 15768 15769 ke.key = cast(Key) sym;//e.xkey.keycode; 15770 15771 ke.modifierState = e.xkey.state; 15772 15773 // import std.stdio; writefln("%x", sym); 15774 wchar_t[128] charbuf = void; // buffer for XwcLookupString; composed value can consist of many chars! 15775 int charbuflen = 0; // return value of XwcLookupString 15776 if (ke.pressed) { 15777 auto win = e.xkey.window in SimpleWindow.nativeMapping; 15778 if (win !is null && win.xic !is null) { 15779 //{ import core.stdc.stdio : printf; printf("using xic!\n"); } 15780 Status status; 15781 charbuflen = XwcLookupString(win.xic, &e.xkey, charbuf.ptr, cast(int)charbuf.length, &sym, &status); 15782 //{ import core.stdc.stdio : printf; printf("charbuflen=%d\n", charbuflen); } 15783 } else { 15784 //{ import core.stdc.stdio : printf; printf("NOT using xic!\n"); } 15785 // If XIM initialization failed, don't process intl chars. Sorry, boys and girls. 15786 char[16] buffer; 15787 auto res = XLookupString(&e.xkey, buffer.ptr, buffer.length, null, null); 15788 if (res && buffer[0] < 128) charbuf[charbuflen++] = cast(wchar_t)buffer[0]; 15789 } 15790 } 15791 15792 // if there's no char, subst one 15793 if (charbuflen == 0) { 15794 switch (sym) { 15795 case 0xff09: charbuf[charbuflen++] = '\t'; break; 15796 case 0xff8d: // keypad enter 15797 case 0xff0d: charbuf[charbuflen++] = '\n'; break; 15798 default : // ignore 15799 } 15800 } 15801 15802 if (auto win = e.xkey.window in SimpleWindow.nativeMapping) { 15803 ke.window = *win; 15804 15805 15806 if(win.inputProxy) 15807 win = &win.inputProxy; 15808 15809 // char events are separate since they are on Windows too 15810 // also, xcompose can generate long char sequences 15811 // don't send char events if Meta and/or Hyper is pressed 15812 // TODO: ctrl+char should only send control chars; not yet 15813 if ((e.xkey.state&ModifierState.ctrl) != 0) { 15814 if (charbuflen > 1 || charbuf[0] >= ' ') charbuflen = 0; 15815 } 15816 15817 dchar[32] charsComingBuffer; 15818 int charsComingPosition; 15819 dchar[] charsComing = charsComingBuffer[]; 15820 15821 if (ke.pressed && charbuflen > 0) { 15822 // FIXME: I think Windows sends these on releases... we should try to match that, but idk about repeats. 15823 foreach (immutable dchar ch; charbuf[0..charbuflen]) { 15824 if(charsComingPosition >= charsComing.length) 15825 charsComing.length = charsComingPosition + 8; 15826 15827 charsComing[charsComingPosition++] = ch; 15828 } 15829 15830 charsComing = charsComing[0 .. charsComingPosition]; 15831 } else { 15832 charsComing = null; 15833 } 15834 15835 ke.charsPossible = charsComing; 15836 15837 if (win.handleKeyEvent) { 15838 XUnlockDisplay(display); 15839 scope(exit) XLockDisplay(display); 15840 win.handleKeyEvent(ke); 15841 } 15842 15843 // Super and alt modifier keys never actually send the chars, they are assumed to be special. 15844 if ((e.xkey.state&(ModifierState.alt|ModifierState.windows)) == 0 && win.handleCharEvent) { 15845 XUnlockDisplay(display); 15846 scope(exit) XLockDisplay(display); 15847 foreach(ch; charsComing) 15848 win.handleCharEvent(ch); 15849 } 15850 } 15851 15852 version(with_eventloop) 15853 send(ke); 15854 break; 15855 default: 15856 } 15857 15858 return done; 15859 } 15860 } 15861 15862 /* *************************************** */ 15863 /* Done with simpledisplay stuff */ 15864 /* *************************************** */ 15865 15866 // Necessary C library bindings follow 15867 version(Windows) {} else 15868 version(X11) { 15869 15870 extern(C) int eventfd (uint initval, int flags) nothrow @trusted @nogc; 15871 15872 // X11 bindings needed here 15873 /* 15874 A little of this is from the bindings project on 15875 D Source and some of it is copy/paste from the C 15876 header. 15877 15878 The DSource listing consistently used D's long 15879 where C used long. That's wrong - C long is 32 bit, so 15880 it should be int in D. I changed that here. 15881 15882 Note: 15883 This isn't complete, just took what I needed for myself. 15884 */ 15885 15886 import core.stdc.stddef : wchar_t; 15887 15888 interface XLib { 15889 extern(C) nothrow @nogc { 15890 char* XResourceManagerString(Display*); 15891 void XrmInitialize(); 15892 XrmDatabase XrmGetStringDatabase(char* data); 15893 bool XrmGetResource(XrmDatabase, const char*, const char*, char**, XrmValue*); 15894 15895 Cursor XCreateFontCursor(Display*, uint shape); 15896 int XDefineCursor(Display* display, Window w, Cursor cursor); 15897 int XUndefineCursor(Display* display, Window w); 15898 15899 Pixmap XCreateBitmapFromData(Display* display, Drawable d, const(char)* data, uint width, uint height); 15900 Cursor XCreatePixmapCursor(Display* display, Pixmap source, Pixmap mask, XColor* foreground_color, XColor* background_color, uint x, uint y); 15901 int XFreeCursor(Display* display, Cursor cursor); 15902 15903 int XLookupString(XKeyEvent *event_struct, char *buffer_return, int bytes_buffer, KeySym *keysym_return, void *status_in_out); 15904 15905 int XwcLookupString(XIC ic, XKeyPressedEvent* event, wchar_t* buffer_return, int wchars_buffer, KeySym* keysym_return, Status* status_return); 15906 15907 XVaNestedList XVaCreateNestedList(int unused, ...); 15908 15909 char *XKeysymToString(KeySym keysym); 15910 KeySym XKeycodeToKeysym( 15911 Display* /* display */, 15912 KeyCode /* keycode */, 15913 int /* index */ 15914 ); 15915 15916 int XConvertSelection(Display *display, Atom selection, Atom target, Atom property, Window requestor, Time time); 15917 15918 int XFree(void*); 15919 int XDeleteProperty(Display *display, Window w, Atom property); 15920 15921 int XChangeProperty(Display *display, Window w, Atom property, Atom type, int format, int mode, in void *data, int nelements); 15922 15923 int XGetWindowProperty(Display *display, Window w, Atom property, arch_long 15924 long_offset, arch_long long_length, Bool del, Atom req_type, Atom 15925 *actual_type_return, int *actual_format_return, arch_ulong 15926 *nitems_return, arch_ulong *bytes_after_return, void** prop_return); 15927 Atom* XListProperties(Display *display, Window w, int *num_prop_return); 15928 Status XGetTextProperty(Display *display, Window w, XTextProperty *text_prop_return, Atom property); 15929 Status XQueryTree(Display *display, Window w, Window *root_return, Window *parent_return, Window **children_return, uint *nchildren_return); 15930 15931 int XSetSelectionOwner(Display *display, Atom selection, Window owner, Time time); 15932 15933 Window XGetSelectionOwner(Display *display, Atom selection); 15934 15935 XVisualInfo* XGetVisualInfo(Display*, c_long, XVisualInfo*, int*); 15936 15937 char** XListFonts(Display*, const char*, int, int*); 15938 void XFreeFontNames(char**); 15939 15940 Display* XOpenDisplay(const char*); 15941 int XCloseDisplay(Display*); 15942 15943 int function() XSynchronize(Display*, bool); 15944 int function() XSetAfterFunction(Display*, int function() proc); 15945 15946 Bool XQueryExtension(Display*, const char*, int*, int*, int*); 15947 15948 Bool XSupportsLocale(); 15949 char* XSetLocaleModifiers(const(char)* modifier_list); 15950 XOM XOpenOM(Display* display, _XrmHashBucketRec* rdb, const(char)* res_name, const(char)* res_class); 15951 Status XCloseOM(XOM om); 15952 15953 XIM XOpenIM(Display* dpy, _XrmHashBucketRec* rdb, const(char)* res_name, const(char)* res_class); 15954 Status XCloseIM(XIM im); 15955 15956 char* XGetIMValues(XIM im, ...) /*_X_SENTINEL(0)*/; 15957 char* XSetIMValues(XIM im, ...) /*_X_SENTINEL(0)*/; 15958 Display* XDisplayOfIM(XIM im); 15959 char* XLocaleOfIM(XIM im); 15960 XIC XCreateIC(XIM im, ...) /*_X_SENTINEL(0)*/; 15961 void XDestroyIC(XIC ic); 15962 void XSetICFocus(XIC ic); 15963 void XUnsetICFocus(XIC ic); 15964 //wchar_t* XwcResetIC(XIC ic); 15965 char* XmbResetIC(XIC ic); 15966 char* Xutf8ResetIC(XIC ic); 15967 char* XSetICValues(XIC ic, ...) /*_X_SENTINEL(0)*/; 15968 char* XGetICValues(XIC ic, ...) /*_X_SENTINEL(0)*/; 15969 XIM XIMOfIC(XIC ic); 15970 15971 uint XSendEvent(Display* display, Window w, Bool propagate, arch_long event_mask, XEvent* event_send); 15972 15973 15974 XFontStruct *XLoadQueryFont(Display *display, in char *name); 15975 int XFreeFont(Display *display, XFontStruct *font_struct); 15976 int XSetFont(Display* display, GC gc, Font font); 15977 int XTextWidth(XFontStruct*, in char*, int); 15978 15979 int XSetLineAttributes(Display *display, GC gc, uint line_width, int line_style, int cap_style, int join_style); 15980 int XSetDashes(Display *display, GC gc, int dash_offset, in byte* dash_list, int n); 15981 15982 Window XCreateSimpleWindow( 15983 Display* /* display */, 15984 Window /* parent */, 15985 int /* x */, 15986 int /* y */, 15987 uint /* width */, 15988 uint /* height */, 15989 uint /* border_width */, 15990 uint /* border */, 15991 uint /* background */ 15992 ); 15993 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); 15994 15995 int XReparentWindow(Display*, Window, Window, int, int); 15996 int XClearWindow(Display*, Window); 15997 int XMoveResizeWindow(Display*, Window, int, int, uint, uint); 15998 int XMoveWindow(Display*, Window, int, int); 15999 int XResizeWindow(Display *display, Window w, uint width, uint height); 16000 16001 Colormap XCreateColormap(Display *display, Window w, Visual *visual, int alloc); 16002 16003 Status XGetWindowAttributes(Display*, Window, XWindowAttributes*); 16004 16005 XImage *XCreateImage( 16006 Display* /* display */, 16007 Visual* /* visual */, 16008 uint /* depth */, 16009 int /* format */, 16010 int /* offset */, 16011 ubyte* /* data */, 16012 uint /* width */, 16013 uint /* height */, 16014 int /* bitmap_pad */, 16015 int /* bytes_per_line */ 16016 ); 16017 16018 Status XInitImage (XImage* image); 16019 16020 Atom XInternAtom( 16021 Display* /* display */, 16022 const char* /* atom_name */, 16023 Bool /* only_if_exists */ 16024 ); 16025 16026 Status XInternAtoms(Display*, const char**, int, Bool, Atom*); 16027 char* XGetAtomName(Display*, Atom); 16028 Status XGetAtomNames(Display*, Atom*, int count, char**); 16029 16030 int XPutImage( 16031 Display* /* display */, 16032 Drawable /* d */, 16033 GC /* gc */, 16034 XImage* /* image */, 16035 int /* src_x */, 16036 int /* src_y */, 16037 int /* dest_x */, 16038 int /* dest_y */, 16039 uint /* width */, 16040 uint /* height */ 16041 ); 16042 16043 XImage *XGetImage(Display *display, Drawable d, int x, int y, uint width, uint height, c_ulong plane_mask, int format); 16044 16045 16046 int XDestroyWindow( 16047 Display* /* display */, 16048 Window /* w */ 16049 ); 16050 16051 int XDestroyImage(XImage*); 16052 16053 int XSelectInput( 16054 Display* /* display */, 16055 Window /* w */, 16056 EventMask /* event_mask */ 16057 ); 16058 16059 int XMapWindow( 16060 Display* /* display */, 16061 Window /* w */ 16062 ); 16063 16064 Status XIconifyWindow(Display*, Window, int); 16065 int XMapRaised(Display*, Window); 16066 int XMapSubwindows(Display*, Window); 16067 16068 int XNextEvent( 16069 Display* /* display */, 16070 XEvent* /* event_return */ 16071 ); 16072 16073 int XMaskEvent(Display*, arch_long, XEvent*); 16074 16075 Bool XFilterEvent(XEvent *event, Window window); 16076 int XRefreshKeyboardMapping(XMappingEvent *event_map); 16077 16078 Status XSetWMProtocols( 16079 Display* /* display */, 16080 Window /* w */, 16081 Atom* /* protocols */, 16082 int /* count */ 16083 ); 16084 16085 void XSetWMNormalHints(Display *display, Window w, XSizeHints *hints); 16086 Status XGetWMNormalHints(Display *display, Window w, XSizeHints *hints, c_long* supplied_return); 16087 16088 16089 Status XInitThreads(); 16090 void XLockDisplay (Display* display); 16091 void XUnlockDisplay (Display* display); 16092 16093 void XSetWMProperties(Display*, Window, XTextProperty*, XTextProperty*, char**, int, XSizeHints*, XWMHints*, XClassHint*); 16094 16095 int XSetWindowBackground (Display* display, Window w, c_ulong background_pixel); 16096 int XSetWindowBackgroundPixmap (Display* display, Window w, Pixmap background_pixmap); 16097 //int XSetWindowBorder (Display* display, Window w, c_ulong border_pixel); 16098 //int XSetWindowBorderPixmap (Display* display, Window w, Pixmap border_pixmap); 16099 //int XSetWindowBorderWidth (Display* display, Window w, uint width); 16100 16101 16102 // check out Xft too: http://www.keithp.com/~keithp/render/Xft.tutorial 16103 int XDrawString(Display*, Drawable, GC, int, int, in char*, int); 16104 int XDrawLine(Display*, Drawable, GC, int, int, int, int); 16105 int XDrawRectangle(Display*, Drawable, GC, int, int, uint, uint); 16106 int XDrawArc(Display*, Drawable, GC, int, int, uint, uint, int, int); 16107 int XFillRectangle(Display*, Drawable, GC, int, int, uint, uint); 16108 int XFillArc(Display*, Drawable, GC, int, int, uint, uint, int, int); 16109 int XDrawPoint(Display*, Drawable, GC, int, int); 16110 int XSetForeground(Display*, GC, uint); 16111 int XSetBackground(Display*, GC, uint); 16112 16113 XFontSet XCreateFontSet(Display*, const char*, char***, int*, char**); 16114 void XFreeFontSet(Display*, XFontSet); 16115 void Xutf8DrawString(Display*, Drawable, XFontSet, GC, int, int, in char*, int); 16116 void Xutf8DrawText(Display*, Drawable, GC, int, int, XmbTextItem*, int); 16117 16118 int Xutf8TextExtents(XFontSet font_set, const char *, int num_bytes, XRectangle *overall_ink_return, XRectangle *overall_logical_return); 16119 16120 16121 //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); 16122 16123 void XDrawText(Display*, Drawable, GC, int, int, XTextItem*, int); 16124 int XSetFunction(Display*, GC, int); 16125 16126 GC XCreateGC(Display*, Drawable, uint, void*); 16127 int XCopyGC(Display*, GC, uint, GC); 16128 int XFreeGC(Display*, GC); 16129 16130 bool XCheckWindowEvent(Display*, Window, int, XEvent*); 16131 bool XCheckMaskEvent(Display*, int, XEvent*); 16132 16133 int XPending(Display*); 16134 int XEventsQueued(Display* display, int mode); 16135 16136 Pixmap XCreatePixmap(Display*, Drawable, uint, uint, uint); 16137 int XFreePixmap(Display*, Pixmap); 16138 int XCopyArea(Display*, Drawable, Drawable, GC, int, int, uint, uint, int, int); 16139 int XFlush(Display*); 16140 int XBell(Display*, int); 16141 int XSync(Display*, bool); 16142 16143 int XGrabKey (Display* display, int keycode, uint modifiers, Window grab_window, Bool owner_events, int pointer_mode, int keyboard_mode); 16144 int XUngrabKey (Display* display, int keycode, uint modifiers, Window grab_window); 16145 16146 int XGrabKeyboard(Display*, Window, Bool, int, int, Time); 16147 int XUngrabKeyboard(Display*, Time); 16148 16149 KeyCode XKeysymToKeycode (Display* display, KeySym keysym); 16150 16151 KeySym XStringToKeysym(const char *string); 16152 16153 Bool XCheckTypedEvent(Display *display, int event_type, XEvent *event_return); 16154 16155 Window XDefaultRootWindow(Display*); 16156 16157 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); 16158 16159 int XUngrabButton(Display *display, uint button, uint modifiers, Window grab_window); 16160 16161 int XDrawLines(Display*, Drawable, GC, XPoint*, int, CoordMode); 16162 int XFillPolygon(Display*, Drawable, GC, XPoint*, int, PolygonShape, CoordMode); 16163 16164 Status XAllocColor(Display*, Colormap, XColor*); 16165 16166 int XWithdrawWindow(Display*, Window, int); 16167 int XUnmapWindow(Display*, Window); 16168 int XLowerWindow(Display*, Window); 16169 int XRaiseWindow(Display*, Window); 16170 16171 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); 16172 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); 16173 16174 int XGetInputFocus(Display*, Window*, int*); 16175 int XSetInputFocus(Display*, Window, int, Time); 16176 16177 XErrorHandler XSetErrorHandler(XErrorHandler); 16178 16179 int XGetErrorText(Display*, int, char*, int); 16180 16181 Bool XkbSetDetectableAutoRepeat(Display* dpy, Bool detectable, Bool* supported); 16182 16183 16184 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); 16185 int XUngrabPointer(Display *display, Time time); 16186 int XChangeActivePointerGrab(Display *display, uint event_mask, Cursor cursor, Time time); 16187 16188 int XCopyPlane(Display*, Drawable, Drawable, GC, int, int, uint, uint, int, int, arch_ulong); 16189 16190 Status XGetGeometry(Display*, Drawable, Window*, int*, int*, uint*, uint*, uint*, uint*); 16191 int XSetClipMask(Display*, GC, Pixmap); 16192 int XSetClipOrigin(Display*, GC, int, int); 16193 16194 void XSetClipRectangles(Display*, GC, int, int, XRectangle*, int, int); 16195 16196 void XSetWMName(Display*, Window, XTextProperty*); 16197 Status XGetWMName(Display*, Window, XTextProperty*); 16198 int XStoreName(Display* display, Window w, const(char)* window_name); 16199 16200 XIOErrorHandler XSetIOErrorHandler (XIOErrorHandler handler); 16201 16202 } 16203 } 16204 16205 interface Xext { 16206 extern(C) nothrow @nogc { 16207 Status XShmAttach(Display*, XShmSegmentInfo*); 16208 Status XShmDetach(Display*, XShmSegmentInfo*); 16209 Status XShmPutImage( 16210 Display* /* dpy */, 16211 Drawable /* d */, 16212 GC /* gc */, 16213 XImage* /* image */, 16214 int /* src_x */, 16215 int /* src_y */, 16216 int /* dst_x */, 16217 int /* dst_y */, 16218 uint /* src_width */, 16219 uint /* src_height */, 16220 Bool /* send_event */ 16221 ); 16222 16223 Status XShmQueryExtension(Display*); 16224 16225 XImage *XShmCreateImage( 16226 Display* /* dpy */, 16227 Visual* /* visual */, 16228 uint /* depth */, 16229 int /* format */, 16230 char* /* data */, 16231 XShmSegmentInfo* /* shminfo */, 16232 uint /* width */, 16233 uint /* height */ 16234 ); 16235 16236 Pixmap XShmCreatePixmap( 16237 Display* /* dpy */, 16238 Drawable /* d */, 16239 char* /* data */, 16240 XShmSegmentInfo* /* shminfo */, 16241 uint /* width */, 16242 uint /* height */, 16243 uint /* depth */ 16244 ); 16245 16246 } 16247 } 16248 16249 // this requires -lXpm 16250 //int XpmCreatePixmapFromData(Display*, Drawable, in char**, Pixmap*, Pixmap*, void*); // FIXME: void* should be XpmAttributes 16251 16252 16253 mixin DynamicLoad!(XLib, "X11", 6, librariesSuccessfullyLoaded) xlib; 16254 mixin DynamicLoad!(Xext, "Xext", 6, librariesSuccessfullyLoaded) xext; 16255 shared static this() { 16256 xlib.loadDynamicLibrary(); 16257 xext.loadDynamicLibrary(); 16258 } 16259 16260 16261 extern(C) nothrow @nogc { 16262 16263 alias XrmDatabase = void*; 16264 struct XrmValue { 16265 uint size; 16266 void* addr; 16267 } 16268 16269 struct XVisualInfo { 16270 Visual* visual; 16271 VisualID visualid; 16272 int screen; 16273 uint depth; 16274 int c_class; 16275 c_ulong red_mask; 16276 c_ulong green_mask; 16277 c_ulong blue_mask; 16278 int colormap_size; 16279 int bits_per_rgb; 16280 } 16281 16282 enum VisualNoMask= 0x0; 16283 enum VisualIDMask= 0x1; 16284 enum VisualScreenMask=0x2; 16285 enum VisualDepthMask= 0x4; 16286 enum VisualClassMask= 0x8; 16287 enum VisualRedMaskMask=0x10; 16288 enum VisualGreenMaskMask=0x20; 16289 enum VisualBlueMaskMask=0x40; 16290 enum VisualColormapSizeMask=0x80; 16291 enum VisualBitsPerRGBMask=0x100; 16292 enum VisualAllMask= 0x1FF; 16293 16294 enum AnyKey = 0; 16295 enum AnyModifier = 1 << 15; 16296 16297 // XIM and other crap 16298 struct _XOM {} 16299 struct _XIM {} 16300 struct _XIC {} 16301 alias XOM = _XOM*; 16302 alias XIM = _XIM*; 16303 alias XIC = _XIC*; 16304 16305 alias XVaNestedList = void*; 16306 16307 alias XIMStyle = arch_ulong; 16308 enum : arch_ulong { 16309 XIMPreeditArea = 0x0001, 16310 XIMPreeditCallbacks = 0x0002, 16311 XIMPreeditPosition = 0x0004, 16312 XIMPreeditNothing = 0x0008, 16313 XIMPreeditNone = 0x0010, 16314 XIMStatusArea = 0x0100, 16315 XIMStatusCallbacks = 0x0200, 16316 XIMStatusNothing = 0x0400, 16317 XIMStatusNone = 0x0800, 16318 } 16319 16320 16321 /* X Shared Memory Extension functions */ 16322 //pragma(lib, "Xshm"); 16323 alias arch_ulong ShmSeg; 16324 struct XShmSegmentInfo { 16325 ShmSeg shmseg; 16326 int shmid; 16327 ubyte* shmaddr; 16328 Bool readOnly; 16329 } 16330 16331 // and the necessary OS functions 16332 int shmget(int, size_t, int); 16333 void* shmat(int, in void*, int); 16334 int shmdt(in void*); 16335 int shmctl (int shmid, int cmd, void* ptr /*struct shmid_ds *buf*/); 16336 16337 enum IPC_PRIVATE = 0; 16338 enum IPC_CREAT = 512; 16339 enum IPC_RMID = 0; 16340 16341 /* MIT-SHM end */ 16342 16343 16344 enum MappingType:int { 16345 MappingModifier =0, 16346 MappingKeyboard =1, 16347 MappingPointer =2 16348 } 16349 16350 /* ImageFormat -- PutImage, GetImage */ 16351 enum ImageFormat:int { 16352 XYBitmap =0, /* depth 1, XYFormat */ 16353 XYPixmap =1, /* depth == drawable depth */ 16354 ZPixmap =2 /* depth == drawable depth */ 16355 } 16356 16357 enum ModifierName:int { 16358 ShiftMapIndex =0, 16359 LockMapIndex =1, 16360 ControlMapIndex =2, 16361 Mod1MapIndex =3, 16362 Mod2MapIndex =4, 16363 Mod3MapIndex =5, 16364 Mod4MapIndex =6, 16365 Mod5MapIndex =7 16366 } 16367 16368 enum ButtonMask:int { 16369 Button1Mask =1<<8, 16370 Button2Mask =1<<9, 16371 Button3Mask =1<<10, 16372 Button4Mask =1<<11, 16373 Button5Mask =1<<12, 16374 AnyModifier =1<<15/* used in GrabButton, GrabKey */ 16375 } 16376 16377 enum KeyOrButtonMask:uint { 16378 ShiftMask =1<<0, 16379 LockMask =1<<1, 16380 ControlMask =1<<2, 16381 Mod1Mask =1<<3, 16382 Mod2Mask =1<<4, 16383 Mod3Mask =1<<5, 16384 Mod4Mask =1<<6, 16385 Mod5Mask =1<<7, 16386 Button1Mask =1<<8, 16387 Button2Mask =1<<9, 16388 Button3Mask =1<<10, 16389 Button4Mask =1<<11, 16390 Button5Mask =1<<12, 16391 AnyModifier =1<<15/* used in GrabButton, GrabKey */ 16392 } 16393 16394 enum ButtonName:int { 16395 Button1 =1, 16396 Button2 =2, 16397 Button3 =3, 16398 Button4 =4, 16399 Button5 =5 16400 } 16401 16402 /* Notify modes */ 16403 enum NotifyModes:int 16404 { 16405 NotifyNormal =0, 16406 NotifyGrab =1, 16407 NotifyUngrab =2, 16408 NotifyWhileGrabbed =3 16409 } 16410 enum NotifyHint = 1; /* for MotionNotify events */ 16411 16412 /* Notify detail */ 16413 enum NotifyDetail:int 16414 { 16415 NotifyAncestor =0, 16416 NotifyVirtual =1, 16417 NotifyInferior =2, 16418 NotifyNonlinear =3, 16419 NotifyNonlinearVirtual =4, 16420 NotifyPointer =5, 16421 NotifyPointerRoot =6, 16422 NotifyDetailNone =7 16423 } 16424 16425 /* Visibility notify */ 16426 16427 enum VisibilityNotify:int 16428 { 16429 VisibilityUnobscured =0, 16430 VisibilityPartiallyObscured =1, 16431 VisibilityFullyObscured =2 16432 } 16433 16434 16435 enum WindowStackingMethod:int 16436 { 16437 Above =0, 16438 Below =1, 16439 TopIf =2, 16440 BottomIf =3, 16441 Opposite =4 16442 } 16443 16444 /* Circulation request */ 16445 enum CirculationRequest:int 16446 { 16447 PlaceOnTop =0, 16448 PlaceOnBottom =1 16449 } 16450 16451 enum PropertyNotification:int 16452 { 16453 PropertyNewValue =0, 16454 PropertyDelete =1 16455 } 16456 16457 enum ColorMapNotification:int 16458 { 16459 ColormapUninstalled =0, 16460 ColormapInstalled =1 16461 } 16462 16463 16464 struct _XPrivate {} 16465 struct _XrmHashBucketRec {} 16466 16467 alias void* XPointer; 16468 alias void* XExtData; 16469 16470 version( X86_64 ) { 16471 alias ulong XID; 16472 alias ulong arch_ulong; 16473 alias long arch_long; 16474 } else version (AArch64) { 16475 alias ulong XID; 16476 alias ulong arch_ulong; 16477 alias long arch_long; 16478 } else { 16479 alias uint XID; 16480 alias uint arch_ulong; 16481 alias int arch_long; 16482 } 16483 16484 alias XID Window; 16485 alias XID Drawable; 16486 alias XID Pixmap; 16487 16488 alias arch_ulong Atom; 16489 alias int Bool; 16490 alias Display XDisplay; 16491 16492 alias int ByteOrder; 16493 alias arch_ulong Time; 16494 alias void ScreenFormat; 16495 16496 struct XImage { 16497 int width, height; /* size of image */ 16498 int xoffset; /* number of pixels offset in X direction */ 16499 ImageFormat format; /* XYBitmap, XYPixmap, ZPixmap */ 16500 void *data; /* pointer to image data */ 16501 ByteOrder byte_order; /* data byte order, LSBFirst, MSBFirst */ 16502 int bitmap_unit; /* quant. of scanline 8, 16, 32 */ 16503 int bitmap_bit_order; /* LSBFirst, MSBFirst */ 16504 int bitmap_pad; /* 8, 16, 32 either XY or ZPixmap */ 16505 int depth; /* depth of image */ 16506 int bytes_per_line; /* accelarator to next line */ 16507 int bits_per_pixel; /* bits per pixel (ZPixmap) */ 16508 arch_ulong red_mask; /* bits in z arrangment */ 16509 arch_ulong green_mask; 16510 arch_ulong blue_mask; 16511 XPointer obdata; /* hook for the object routines to hang on */ 16512 static struct F { /* image manipulation routines */ 16513 XImage* function( 16514 XDisplay* /* display */, 16515 Visual* /* visual */, 16516 uint /* depth */, 16517 int /* format */, 16518 int /* offset */, 16519 ubyte* /* data */, 16520 uint /* width */, 16521 uint /* height */, 16522 int /* bitmap_pad */, 16523 int /* bytes_per_line */) create_image; 16524 int function(XImage *) destroy_image; 16525 arch_ulong function(XImage *, int, int) get_pixel; 16526 int function(XImage *, int, int, arch_ulong) put_pixel; 16527 XImage* function(XImage *, int, int, uint, uint) sub_image; 16528 int function(XImage *, arch_long) add_pixel; 16529 } 16530 F f; 16531 } 16532 version(X86_64) static assert(XImage.sizeof == 136); 16533 else version(X86) static assert(XImage.sizeof == 88); 16534 16535 struct XCharStruct { 16536 short lbearing; /* origin to left edge of raster */ 16537 short rbearing; /* origin to right edge of raster */ 16538 short width; /* advance to next char's origin */ 16539 short ascent; /* baseline to top edge of raster */ 16540 short descent; /* baseline to bottom edge of raster */ 16541 ushort attributes; /* per char flags (not predefined) */ 16542 } 16543 16544 /* 16545 * To allow arbitrary information with fonts, there are additional properties 16546 * returned. 16547 */ 16548 struct XFontProp { 16549 Atom name; 16550 arch_ulong card32; 16551 } 16552 16553 alias Atom Font; 16554 16555 struct XFontStruct { 16556 XExtData *ext_data; /* Hook for extension to hang data */ 16557 Font fid; /* Font ID for this font */ 16558 uint direction; /* Direction the font is painted */ 16559 uint min_char_or_byte2; /* First character */ 16560 uint max_char_or_byte2; /* Last character */ 16561 uint min_byte1; /* First row that exists (for two-byte fonts) */ 16562 uint max_byte1; /* Last row that exists (for two-byte fonts) */ 16563 Bool all_chars_exist; /* Flag if all characters have nonzero size */ 16564 uint default_char; /* Char to print for undefined character */ 16565 int n_properties; /* How many properties there are */ 16566 XFontProp *properties; /* Pointer to array of additional properties*/ 16567 XCharStruct min_bounds; /* Minimum bounds over all existing char*/ 16568 XCharStruct max_bounds; /* Maximum bounds over all existing char*/ 16569 XCharStruct *per_char; /* first_char to last_char information */ 16570 int ascent; /* Max extent above baseline for spacing */ 16571 int descent; /* Max descent below baseline for spacing */ 16572 } 16573 16574 16575 /* 16576 * Definitions of specific events. 16577 */ 16578 struct XKeyEvent 16579 { 16580 int type; /* of event */ 16581 arch_ulong serial; /* # of last request processed by server */ 16582 Bool send_event; /* true if this came from a SendEvent request */ 16583 Display *display; /* Display the event was read from */ 16584 Window window; /* "event" window it is reported relative to */ 16585 Window root; /* root window that the event occurred on */ 16586 Window subwindow; /* child window */ 16587 Time time; /* milliseconds */ 16588 int x, y; /* pointer x, y coordinates in event window */ 16589 int x_root, y_root; /* coordinates relative to root */ 16590 KeyOrButtonMask state; /* key or button mask */ 16591 uint keycode; /* detail */ 16592 Bool same_screen; /* same screen flag */ 16593 } 16594 version(X86_64) static assert(XKeyEvent.sizeof == 96); 16595 alias XKeyEvent XKeyPressedEvent; 16596 alias XKeyEvent XKeyReleasedEvent; 16597 16598 struct XButtonEvent 16599 { 16600 int type; /* of event */ 16601 arch_ulong serial; /* # of last request processed by server */ 16602 Bool send_event; /* true if this came from a SendEvent request */ 16603 Display *display; /* Display the event was read from */ 16604 Window window; /* "event" window it is reported relative to */ 16605 Window root; /* root window that the event occurred on */ 16606 Window subwindow; /* child window */ 16607 Time time; /* milliseconds */ 16608 int x, y; /* pointer x, y coordinates in event window */ 16609 int x_root, y_root; /* coordinates relative to root */ 16610 KeyOrButtonMask state; /* key or button mask */ 16611 uint button; /* detail */ 16612 Bool same_screen; /* same screen flag */ 16613 } 16614 alias XButtonEvent XButtonPressedEvent; 16615 alias XButtonEvent XButtonReleasedEvent; 16616 16617 struct XMotionEvent{ 16618 int type; /* of event */ 16619 arch_ulong serial; /* # of last request processed by server */ 16620 Bool send_event; /* true if this came from a SendEvent request */ 16621 Display *display; /* Display the event was read from */ 16622 Window window; /* "event" window reported relative to */ 16623 Window root; /* root window that the event occurred on */ 16624 Window subwindow; /* child window */ 16625 Time time; /* milliseconds */ 16626 int x, y; /* pointer x, y coordinates in event window */ 16627 int x_root, y_root; /* coordinates relative to root */ 16628 KeyOrButtonMask state; /* key or button mask */ 16629 byte is_hint; /* detail */ 16630 Bool same_screen; /* same screen flag */ 16631 } 16632 alias XMotionEvent XPointerMovedEvent; 16633 16634 struct XCrossingEvent{ 16635 int type; /* of event */ 16636 arch_ulong serial; /* # of last request processed by server */ 16637 Bool send_event; /* true if this came from a SendEvent request */ 16638 Display *display; /* Display the event was read from */ 16639 Window window; /* "event" window reported relative to */ 16640 Window root; /* root window that the event occurred on */ 16641 Window subwindow; /* child window */ 16642 Time time; /* milliseconds */ 16643 int x, y; /* pointer x, y coordinates in event window */ 16644 int x_root, y_root; /* coordinates relative to root */ 16645 NotifyModes mode; /* NotifyNormal, NotifyGrab, NotifyUngrab */ 16646 NotifyDetail detail; 16647 /* 16648 * NotifyAncestor, NotifyVirtual, NotifyInferior, 16649 * NotifyNonlinear,NotifyNonlinearVirtual 16650 */ 16651 Bool same_screen; /* same screen flag */ 16652 Bool focus; /* Boolean focus */ 16653 KeyOrButtonMask state; /* key or button mask */ 16654 } 16655 alias XCrossingEvent XEnterWindowEvent; 16656 alias XCrossingEvent XLeaveWindowEvent; 16657 16658 struct XFocusChangeEvent{ 16659 int type; /* FocusIn or FocusOut */ 16660 arch_ulong serial; /* # of last request processed by server */ 16661 Bool send_event; /* true if this came from a SendEvent request */ 16662 Display *display; /* Display the event was read from */ 16663 Window window; /* window of event */ 16664 NotifyModes mode; /* NotifyNormal, NotifyWhileGrabbed, 16665 NotifyGrab, NotifyUngrab */ 16666 NotifyDetail detail; 16667 /* 16668 * NotifyAncestor, NotifyVirtual, NotifyInferior, 16669 * NotifyNonlinear,NotifyNonlinearVirtual, NotifyPointer, 16670 * NotifyPointerRoot, NotifyDetailNone 16671 */ 16672 } 16673 alias XFocusChangeEvent XFocusInEvent; 16674 alias XFocusChangeEvent XFocusOutEvent; 16675 16676 enum CWBackPixmap = (1L<<0); 16677 enum CWBackPixel = (1L<<1); 16678 enum CWBorderPixmap = (1L<<2); 16679 enum CWBorderPixel = (1L<<3); 16680 enum CWBitGravity = (1L<<4); 16681 enum CWWinGravity = (1L<<5); 16682 enum CWBackingStore = (1L<<6); 16683 enum CWBackingPlanes = (1L<<7); 16684 enum CWBackingPixel = (1L<<8); 16685 enum CWOverrideRedirect = (1L<<9); 16686 enum CWSaveUnder = (1L<<10); 16687 enum CWEventMask = (1L<<11); 16688 enum CWDontPropagate = (1L<<12); 16689 enum CWColormap = (1L<<13); 16690 enum CWCursor = (1L<<14); 16691 16692 struct XWindowAttributes { 16693 int x, y; /* location of window */ 16694 int width, height; /* width and height of window */ 16695 int border_width; /* border width of window */ 16696 int depth; /* depth of window */ 16697 Visual *visual; /* the associated visual structure */ 16698 Window root; /* root of screen containing window */ 16699 int class_; /* InputOutput, InputOnly*/ 16700 int bit_gravity; /* one of the bit gravity values */ 16701 int win_gravity; /* one of the window gravity values */ 16702 int backing_store; /* NotUseful, WhenMapped, Always */ 16703 arch_ulong backing_planes; /* planes to be preserved if possible */ 16704 arch_ulong backing_pixel; /* value to be used when restoring planes */ 16705 Bool save_under; /* boolean, should bits under be saved? */ 16706 Colormap colormap; /* color map to be associated with window */ 16707 Bool map_installed; /* boolean, is color map currently installed*/ 16708 int map_state; /* IsUnmapped, IsUnviewable, IsViewable */ 16709 arch_long all_event_masks; /* set of events all people have interest in*/ 16710 arch_long your_event_mask; /* my event mask */ 16711 arch_long do_not_propagate_mask; /* set of events that should not propagate */ 16712 Bool override_redirect; /* boolean value for override-redirect */ 16713 Screen *screen; /* back pointer to correct screen */ 16714 } 16715 16716 enum IsUnmapped = 0; 16717 enum IsUnviewable = 1; 16718 enum IsViewable = 2; 16719 16720 struct XSetWindowAttributes { 16721 Pixmap background_pixmap;/* background, None, or ParentRelative */ 16722 arch_ulong background_pixel;/* background pixel */ 16723 Pixmap border_pixmap; /* border of the window or CopyFromParent */ 16724 arch_ulong border_pixel;/* border pixel value */ 16725 int bit_gravity; /* one of bit gravity values */ 16726 int win_gravity; /* one of the window gravity values */ 16727 int backing_store; /* NotUseful, WhenMapped, Always */ 16728 arch_ulong backing_planes;/* planes to be preserved if possible */ 16729 arch_ulong backing_pixel;/* value to use in restoring planes */ 16730 Bool save_under; /* should bits under be saved? (popups) */ 16731 arch_long event_mask; /* set of events that should be saved */ 16732 arch_long do_not_propagate_mask;/* set of events that should not propagate */ 16733 Bool override_redirect; /* boolean value for override_redirect */ 16734 Colormap colormap; /* color map to be associated with window */ 16735 Cursor cursor; /* cursor to be displayed (or None) */ 16736 } 16737 16738 16739 alias int Status; 16740 16741 16742 enum EventMask:int 16743 { 16744 NoEventMask =0, 16745 KeyPressMask =1<<0, 16746 KeyReleaseMask =1<<1, 16747 ButtonPressMask =1<<2, 16748 ButtonReleaseMask =1<<3, 16749 EnterWindowMask =1<<4, 16750 LeaveWindowMask =1<<5, 16751 PointerMotionMask =1<<6, 16752 PointerMotionHintMask =1<<7, 16753 Button1MotionMask =1<<8, 16754 Button2MotionMask =1<<9, 16755 Button3MotionMask =1<<10, 16756 Button4MotionMask =1<<11, 16757 Button5MotionMask =1<<12, 16758 ButtonMotionMask =1<<13, 16759 KeymapStateMask =1<<14, 16760 ExposureMask =1<<15, 16761 VisibilityChangeMask =1<<16, 16762 StructureNotifyMask =1<<17, 16763 ResizeRedirectMask =1<<18, 16764 SubstructureNotifyMask =1<<19, 16765 SubstructureRedirectMask=1<<20, 16766 FocusChangeMask =1<<21, 16767 PropertyChangeMask =1<<22, 16768 ColormapChangeMask =1<<23, 16769 OwnerGrabButtonMask =1<<24 16770 } 16771 16772 struct MwmHints { 16773 c_ulong flags; 16774 c_ulong functions; 16775 c_ulong decorations; 16776 c_long input_mode; 16777 c_ulong status; 16778 } 16779 16780 enum { 16781 MWM_HINTS_FUNCTIONS = (1L << 0), 16782 MWM_HINTS_DECORATIONS = (1L << 1), 16783 16784 MWM_FUNC_ALL = (1L << 0), 16785 MWM_FUNC_RESIZE = (1L << 1), 16786 MWM_FUNC_MOVE = (1L << 2), 16787 MWM_FUNC_MINIMIZE = (1L << 3), 16788 MWM_FUNC_MAXIMIZE = (1L << 4), 16789 MWM_FUNC_CLOSE = (1L << 5), 16790 16791 MWM_DECOR_ALL = (1L << 0), 16792 MWM_DECOR_BORDER = (1L << 1), 16793 MWM_DECOR_RESIZEH = (1L << 2), 16794 MWM_DECOR_TITLE = (1L << 3), 16795 MWM_DECOR_MENU = (1L << 4), 16796 MWM_DECOR_MINIMIZE = (1L << 5), 16797 MWM_DECOR_MAXIMIZE = (1L << 6), 16798 } 16799 16800 import core.stdc.config : c_long, c_ulong; 16801 16802 /* Size hints mask bits */ 16803 16804 enum USPosition = (1L << 0) /* user specified x, y */; 16805 enum USSize = (1L << 1) /* user specified width, height */; 16806 enum PPosition = (1L << 2) /* program specified position */; 16807 enum PSize = (1L << 3) /* program specified size */; 16808 enum PMinSize = (1L << 4) /* program specified minimum size */; 16809 enum PMaxSize = (1L << 5) /* program specified maximum size */; 16810 enum PResizeInc = (1L << 6) /* program specified resize increments */; 16811 enum PAspect = (1L << 7) /* program specified min and max aspect ratios */; 16812 enum PBaseSize = (1L << 8); 16813 enum PWinGravity = (1L << 9); 16814 enum PAllHints = (PPosition|PSize| PMinSize|PMaxSize| PResizeInc|PAspect); 16815 struct XSizeHints { 16816 arch_long flags; /* marks which fields in this structure are defined */ 16817 int x, y; /* Obsolete */ 16818 int width, height; /* Obsolete */ 16819 int min_width, min_height; 16820 int max_width, max_height; 16821 int width_inc, height_inc; 16822 struct Aspect { 16823 int x; /* numerator */ 16824 int y; /* denominator */ 16825 } 16826 16827 Aspect min_aspect; 16828 Aspect max_aspect; 16829 int base_width, base_height; 16830 int win_gravity; 16831 /* this structure may be extended in the future */ 16832 } 16833 16834 16835 16836 enum EventType:int 16837 { 16838 KeyPress =2, 16839 KeyRelease =3, 16840 ButtonPress =4, 16841 ButtonRelease =5, 16842 MotionNotify =6, 16843 EnterNotify =7, 16844 LeaveNotify =8, 16845 FocusIn =9, 16846 FocusOut =10, 16847 KeymapNotify =11, 16848 Expose =12, 16849 GraphicsExpose =13, 16850 NoExpose =14, 16851 VisibilityNotify =15, 16852 CreateNotify =16, 16853 DestroyNotify =17, 16854 UnmapNotify =18, 16855 MapNotify =19, 16856 MapRequest =20, 16857 ReparentNotify =21, 16858 ConfigureNotify =22, 16859 ConfigureRequest =23, 16860 GravityNotify =24, 16861 ResizeRequest =25, 16862 CirculateNotify =26, 16863 CirculateRequest =27, 16864 PropertyNotify =28, 16865 SelectionClear =29, 16866 SelectionRequest =30, 16867 SelectionNotify =31, 16868 ColormapNotify =32, 16869 ClientMessage =33, 16870 MappingNotify =34, 16871 LASTEvent =35 /* must be bigger than any event # */ 16872 } 16873 /* generated on EnterWindow and FocusIn when KeyMapState selected */ 16874 struct XKeymapEvent 16875 { 16876 int type; 16877 arch_ulong serial; /* # of last request processed by server */ 16878 Bool send_event; /* true if this came from a SendEvent request */ 16879 Display *display; /* Display the event was read from */ 16880 Window window; 16881 byte[32] key_vector; 16882 } 16883 16884 struct XExposeEvent 16885 { 16886 int type; 16887 arch_ulong serial; /* # of last request processed by server */ 16888 Bool send_event; /* true if this came from a SendEvent request */ 16889 Display *display; /* Display the event was read from */ 16890 Window window; 16891 int x, y; 16892 int width, height; 16893 int count; /* if non-zero, at least this many more */ 16894 } 16895 16896 struct XGraphicsExposeEvent{ 16897 int type; 16898 arch_ulong serial; /* # of last request processed by server */ 16899 Bool send_event; /* true if this came from a SendEvent request */ 16900 Display *display; /* Display the event was read from */ 16901 Drawable drawable; 16902 int x, y; 16903 int width, height; 16904 int count; /* if non-zero, at least this many more */ 16905 int major_code; /* core is CopyArea or CopyPlane */ 16906 int minor_code; /* not defined in the core */ 16907 } 16908 16909 struct XNoExposeEvent{ 16910 int type; 16911 arch_ulong serial; /* # of last request processed by server */ 16912 Bool send_event; /* true if this came from a SendEvent request */ 16913 Display *display; /* Display the event was read from */ 16914 Drawable drawable; 16915 int major_code; /* core is CopyArea or CopyPlane */ 16916 int minor_code; /* not defined in the core */ 16917 } 16918 16919 struct XVisibilityEvent{ 16920 int type; 16921 arch_ulong serial; /* # of last request processed by server */ 16922 Bool send_event; /* true if this came from a SendEvent request */ 16923 Display *display; /* Display the event was read from */ 16924 Window window; 16925 VisibilityNotify state; /* Visibility state */ 16926 } 16927 16928 struct XCreateWindowEvent{ 16929 int type; 16930 arch_ulong serial; /* # of last request processed by server */ 16931 Bool send_event; /* true if this came from a SendEvent request */ 16932 Display *display; /* Display the event was read from */ 16933 Window parent; /* parent of the window */ 16934 Window window; /* window id of window created */ 16935 int x, y; /* window location */ 16936 int width, height; /* size of window */ 16937 int border_width; /* border width */ 16938 Bool override_redirect; /* creation should be overridden */ 16939 } 16940 16941 struct XDestroyWindowEvent 16942 { 16943 int type; 16944 arch_ulong serial; /* # of last request processed by server */ 16945 Bool send_event; /* true if this came from a SendEvent request */ 16946 Display *display; /* Display the event was read from */ 16947 Window event; 16948 Window window; 16949 } 16950 16951 struct XUnmapEvent 16952 { 16953 int type; 16954 arch_ulong serial; /* # of last request processed by server */ 16955 Bool send_event; /* true if this came from a SendEvent request */ 16956 Display *display; /* Display the event was read from */ 16957 Window event; 16958 Window window; 16959 Bool from_configure; 16960 } 16961 16962 struct XMapEvent 16963 { 16964 int type; 16965 arch_ulong serial; /* # of last request processed by server */ 16966 Bool send_event; /* true if this came from a SendEvent request */ 16967 Display *display; /* Display the event was read from */ 16968 Window event; 16969 Window window; 16970 Bool override_redirect; /* Boolean, is override set... */ 16971 } 16972 16973 struct XMapRequestEvent 16974 { 16975 int type; 16976 arch_ulong serial; /* # of last request processed by server */ 16977 Bool send_event; /* true if this came from a SendEvent request */ 16978 Display *display; /* Display the event was read from */ 16979 Window parent; 16980 Window window; 16981 } 16982 16983 struct XReparentEvent 16984 { 16985 int type; 16986 arch_ulong serial; /* # of last request processed by server */ 16987 Bool send_event; /* true if this came from a SendEvent request */ 16988 Display *display; /* Display the event was read from */ 16989 Window event; 16990 Window window; 16991 Window parent; 16992 int x, y; 16993 Bool override_redirect; 16994 } 16995 16996 struct XConfigureEvent 16997 { 16998 int type; 16999 arch_ulong serial; /* # of last request processed by server */ 17000 Bool send_event; /* true if this came from a SendEvent request */ 17001 Display *display; /* Display the event was read from */ 17002 Window event; 17003 Window window; 17004 int x, y; 17005 int width, height; 17006 int border_width; 17007 Window above; 17008 Bool override_redirect; 17009 } 17010 17011 struct XGravityEvent 17012 { 17013 int type; 17014 arch_ulong serial; /* # of last request processed by server */ 17015 Bool send_event; /* true if this came from a SendEvent request */ 17016 Display *display; /* Display the event was read from */ 17017 Window event; 17018 Window window; 17019 int x, y; 17020 } 17021 17022 struct XResizeRequestEvent 17023 { 17024 int type; 17025 arch_ulong serial; /* # of last request processed by server */ 17026 Bool send_event; /* true if this came from a SendEvent request */ 17027 Display *display; /* Display the event was read from */ 17028 Window window; 17029 int width, height; 17030 } 17031 17032 struct XConfigureRequestEvent 17033 { 17034 int type; 17035 arch_ulong serial; /* # of last request processed by server */ 17036 Bool send_event; /* true if this came from a SendEvent request */ 17037 Display *display; /* Display the event was read from */ 17038 Window parent; 17039 Window window; 17040 int x, y; 17041 int width, height; 17042 int border_width; 17043 Window above; 17044 WindowStackingMethod detail; /* Above, Below, TopIf, BottomIf, Opposite */ 17045 arch_ulong value_mask; 17046 } 17047 17048 struct XCirculateEvent 17049 { 17050 int type; 17051 arch_ulong serial; /* # of last request processed by server */ 17052 Bool send_event; /* true if this came from a SendEvent request */ 17053 Display *display; /* Display the event was read from */ 17054 Window event; 17055 Window window; 17056 CirculationRequest place; /* PlaceOnTop, PlaceOnBottom */ 17057 } 17058 17059 struct XCirculateRequestEvent 17060 { 17061 int type; 17062 arch_ulong serial; /* # of last request processed by server */ 17063 Bool send_event; /* true if this came from a SendEvent request */ 17064 Display *display; /* Display the event was read from */ 17065 Window parent; 17066 Window window; 17067 CirculationRequest place; /* PlaceOnTop, PlaceOnBottom */ 17068 } 17069 17070 struct XPropertyEvent 17071 { 17072 int type; 17073 arch_ulong serial; /* # of last request processed by server */ 17074 Bool send_event; /* true if this came from a SendEvent request */ 17075 Display *display; /* Display the event was read from */ 17076 Window window; 17077 Atom atom; 17078 Time time; 17079 PropertyNotification state; /* NewValue, Deleted */ 17080 } 17081 17082 struct XSelectionClearEvent 17083 { 17084 int type; 17085 arch_ulong serial; /* # of last request processed by server */ 17086 Bool send_event; /* true if this came from a SendEvent request */ 17087 Display *display; /* Display the event was read from */ 17088 Window window; 17089 Atom selection; 17090 Time time; 17091 } 17092 17093 struct XSelectionRequestEvent 17094 { 17095 int type; 17096 arch_ulong serial; /* # of last request processed by server */ 17097 Bool send_event; /* true if this came from a SendEvent request */ 17098 Display *display; /* Display the event was read from */ 17099 Window owner; 17100 Window requestor; 17101 Atom selection; 17102 Atom target; 17103 Atom property; 17104 Time time; 17105 } 17106 17107 struct XSelectionEvent 17108 { 17109 int type; 17110 arch_ulong serial; /* # of last request processed by server */ 17111 Bool send_event; /* true if this came from a SendEvent request */ 17112 Display *display; /* Display the event was read from */ 17113 Window requestor; 17114 Atom selection; 17115 Atom target; 17116 Atom property; /* ATOM or None */ 17117 Time time; 17118 } 17119 version(X86_64) static assert(XSelectionClearEvent.sizeof == 56); 17120 17121 struct XColormapEvent 17122 { 17123 int type; 17124 arch_ulong serial; /* # of last request processed by server */ 17125 Bool send_event; /* true if this came from a SendEvent request */ 17126 Display *display; /* Display the event was read from */ 17127 Window window; 17128 Colormap colormap; /* COLORMAP or None */ 17129 Bool new_; /* C++ */ 17130 ColorMapNotification state; /* ColormapInstalled, ColormapUninstalled */ 17131 } 17132 version(X86_64) static assert(XColormapEvent.sizeof == 56); 17133 17134 struct XClientMessageEvent 17135 { 17136 int type; 17137 arch_ulong serial; /* # of last request processed by server */ 17138 Bool send_event; /* true if this came from a SendEvent request */ 17139 Display *display; /* Display the event was read from */ 17140 Window window; 17141 Atom message_type; 17142 int format; 17143 union Data{ 17144 byte[20] b; 17145 short[10] s; 17146 arch_ulong[5] l; 17147 } 17148 Data data; 17149 17150 } 17151 version(X86_64) static assert(XClientMessageEvent.sizeof == 96); 17152 17153 struct XMappingEvent 17154 { 17155 int type; 17156 arch_ulong serial; /* # of last request processed by server */ 17157 Bool send_event; /* true if this came from a SendEvent request */ 17158 Display *display; /* Display the event was read from */ 17159 Window window; /* unused */ 17160 MappingType request; /* one of MappingModifier, MappingKeyboard, 17161 MappingPointer */ 17162 int first_keycode; /* first keycode */ 17163 int count; /* defines range of change w. first_keycode*/ 17164 } 17165 17166 struct XErrorEvent 17167 { 17168 int type; 17169 Display *display; /* Display the event was read from */ 17170 XID resourceid; /* resource id */ 17171 arch_ulong serial; /* serial number of failed request */ 17172 ubyte error_code; /* error code of failed request */ 17173 ubyte request_code; /* Major op-code of failed request */ 17174 ubyte minor_code; /* Minor op-code of failed request */ 17175 } 17176 17177 struct XAnyEvent 17178 { 17179 int type; 17180 arch_ulong serial; /* # of last request processed by server */ 17181 Bool send_event; /* true if this came from a SendEvent request */ 17182 Display *display;/* Display the event was read from */ 17183 Window window; /* window on which event was requested in event mask */ 17184 } 17185 17186 union XEvent{ 17187 int type; /* must not be changed; first element */ 17188 XAnyEvent xany; 17189 XKeyEvent xkey; 17190 XButtonEvent xbutton; 17191 XMotionEvent xmotion; 17192 XCrossingEvent xcrossing; 17193 XFocusChangeEvent xfocus; 17194 XExposeEvent xexpose; 17195 XGraphicsExposeEvent xgraphicsexpose; 17196 XNoExposeEvent xnoexpose; 17197 XVisibilityEvent xvisibility; 17198 XCreateWindowEvent xcreatewindow; 17199 XDestroyWindowEvent xdestroywindow; 17200 XUnmapEvent xunmap; 17201 XMapEvent xmap; 17202 XMapRequestEvent xmaprequest; 17203 XReparentEvent xreparent; 17204 XConfigureEvent xconfigure; 17205 XGravityEvent xgravity; 17206 XResizeRequestEvent xresizerequest; 17207 XConfigureRequestEvent xconfigurerequest; 17208 XCirculateEvent xcirculate; 17209 XCirculateRequestEvent xcirculaterequest; 17210 XPropertyEvent xproperty; 17211 XSelectionClearEvent xselectionclear; 17212 XSelectionRequestEvent xselectionrequest; 17213 XSelectionEvent xselection; 17214 XColormapEvent xcolormap; 17215 XClientMessageEvent xclient; 17216 XMappingEvent xmapping; 17217 XErrorEvent xerror; 17218 XKeymapEvent xkeymap; 17219 arch_ulong[24] pad; 17220 } 17221 17222 17223 struct Display { 17224 XExtData *ext_data; /* hook for extension to hang data */ 17225 _XPrivate *private1; 17226 int fd; /* Network socket. */ 17227 int private2; 17228 int proto_major_version;/* major version of server's X protocol */ 17229 int proto_minor_version;/* minor version of servers X protocol */ 17230 char *vendor; /* vendor of the server hardware */ 17231 XID private3; 17232 XID private4; 17233 XID private5; 17234 int private6; 17235 XID function(Display*)resource_alloc;/* allocator function */ 17236 ByteOrder byte_order; /* screen byte order, LSBFirst, MSBFirst */ 17237 int bitmap_unit; /* padding and data requirements */ 17238 int bitmap_pad; /* padding requirements on bitmaps */ 17239 ByteOrder bitmap_bit_order; /* LeastSignificant or MostSignificant */ 17240 int nformats; /* number of pixmap formats in list */ 17241 ScreenFormat *pixmap_format; /* pixmap format list */ 17242 int private8; 17243 int release; /* release of the server */ 17244 _XPrivate *private9; 17245 _XPrivate *private10; 17246 int qlen; /* Length of input event queue */ 17247 arch_ulong last_request_read; /* seq number of last event read */ 17248 arch_ulong request; /* sequence number of last request. */ 17249 XPointer private11; 17250 XPointer private12; 17251 XPointer private13; 17252 XPointer private14; 17253 uint max_request_size; /* maximum number 32 bit words in request*/ 17254 _XrmHashBucketRec *db; 17255 int function (Display*)private15; 17256 char *display_name; /* "host:display" string used on this connect*/ 17257 int default_screen; /* default screen for operations */ 17258 int nscreens; /* number of screens on this server*/ 17259 Screen *screens; /* pointer to list of screens */ 17260 arch_ulong motion_buffer; /* size of motion buffer */ 17261 arch_ulong private16; 17262 int min_keycode; /* minimum defined keycode */ 17263 int max_keycode; /* maximum defined keycode */ 17264 XPointer private17; 17265 XPointer private18; 17266 int private19; 17267 byte *xdefaults; /* contents of defaults from server */ 17268 /* there is more to this structure, but it is private to Xlib */ 17269 } 17270 17271 // I got these numbers from a C program as a sanity test 17272 version(X86_64) { 17273 static assert(Display.sizeof == 296); 17274 static assert(XPointer.sizeof == 8); 17275 static assert(XErrorEvent.sizeof == 40); 17276 static assert(XAnyEvent.sizeof == 40); 17277 static assert(XMappingEvent.sizeof == 56); 17278 static assert(XEvent.sizeof == 192); 17279 } else version (AArch64) { 17280 // omit check for aarch64 17281 } else { 17282 static assert(Display.sizeof == 176); 17283 static assert(XPointer.sizeof == 4); 17284 static assert(XEvent.sizeof == 96); 17285 } 17286 17287 struct Depth 17288 { 17289 int depth; /* this depth (Z) of the depth */ 17290 int nvisuals; /* number of Visual types at this depth */ 17291 Visual *visuals; /* list of visuals possible at this depth */ 17292 } 17293 17294 alias void* GC; 17295 alias c_ulong VisualID; 17296 alias XID Colormap; 17297 alias XID Cursor; 17298 alias XID KeySym; 17299 alias uint KeyCode; 17300 enum None = 0; 17301 } 17302 17303 version(without_opengl) {} 17304 else { 17305 extern(C) nothrow @nogc { 17306 17307 17308 static if(!SdpyIsUsingIVGLBinds) { 17309 enum GLX_USE_GL= 1; /* support GLX rendering */ 17310 enum GLX_BUFFER_SIZE= 2; /* depth of the color buffer */ 17311 enum GLX_LEVEL= 3; /* level in plane stacking */ 17312 enum GLX_RGBA= 4; /* true if RGBA mode */ 17313 enum GLX_DOUBLEBUFFER= 5; /* double buffering supported */ 17314 enum GLX_STEREO= 6; /* stereo buffering supported */ 17315 enum GLX_AUX_BUFFERS= 7; /* number of aux buffers */ 17316 enum GLX_RED_SIZE= 8; /* number of red component bits */ 17317 enum GLX_GREEN_SIZE= 9; /* number of green component bits */ 17318 enum GLX_BLUE_SIZE= 10; /* number of blue component bits */ 17319 enum GLX_ALPHA_SIZE= 11; /* number of alpha component bits */ 17320 enum GLX_DEPTH_SIZE= 12; /* number of depth bits */ 17321 enum GLX_STENCIL_SIZE= 13; /* number of stencil bits */ 17322 enum GLX_ACCUM_RED_SIZE= 14; /* number of red accum bits */ 17323 enum GLX_ACCUM_GREEN_SIZE= 15; /* number of green accum bits */ 17324 enum GLX_ACCUM_BLUE_SIZE= 16; /* number of blue accum bits */ 17325 enum GLX_ACCUM_ALPHA_SIZE= 17; /* number of alpha accum bits */ 17326 17327 17328 //XVisualInfo* glXChooseVisual(Display *dpy, int screen, in int *attrib_list); 17329 17330 17331 17332 enum GL_TRUE = 1; 17333 enum GL_FALSE = 0; 17334 alias int GLint; 17335 } 17336 17337 alias XID GLXContextID; 17338 alias XID GLXPixmap; 17339 alias XID GLXDrawable; 17340 alias XID GLXPbuffer; 17341 alias XID GLXWindow; 17342 alias XID GLXFBConfigID; 17343 alias void* GLXContext; 17344 17345 } 17346 } 17347 17348 enum AllocNone = 0; 17349 17350 extern(C) { 17351 /* WARNING, this type not in Xlib spec */ 17352 extern(C) alias XIOErrorHandler = int function (Display* display); 17353 } 17354 17355 extern(C) nothrow 17356 alias XErrorHandler = int function(Display*, XErrorEvent*); 17357 17358 extern(C) nothrow @nogc { 17359 struct Screen{ 17360 XExtData *ext_data; /* hook for extension to hang data */ 17361 Display *display; /* back pointer to display structure */ 17362 Window root; /* Root window id. */ 17363 int width, height; /* width and height of screen */ 17364 int mwidth, mheight; /* width and height of in millimeters */ 17365 int ndepths; /* number of depths possible */ 17366 Depth *depths; /* list of allowable depths on the screen */ 17367 int root_depth; /* bits per pixel */ 17368 Visual *root_visual; /* root visual */ 17369 GC default_gc; /* GC for the root root visual */ 17370 Colormap cmap; /* default color map */ 17371 uint white_pixel; 17372 uint black_pixel; /* White and Black pixel values */ 17373 int max_maps, min_maps; /* max and min color maps */ 17374 int backing_store; /* Never, WhenMapped, Always */ 17375 bool save_unders; 17376 int root_input_mask; /* initial root input mask */ 17377 } 17378 17379 struct Visual 17380 { 17381 XExtData *ext_data; /* hook for extension to hang data */ 17382 VisualID visualid; /* visual id of this visual */ 17383 int class_; /* class of screen (monochrome, etc.) */ 17384 c_ulong red_mask, green_mask, blue_mask; /* mask values */ 17385 int bits_per_rgb; /* log base 2 of distinct color values */ 17386 int map_entries; /* color map entries */ 17387 } 17388 17389 alias Display* _XPrivDisplay; 17390 17391 extern(D) Screen* ScreenOfDisplay(Display* dpy, int scr) { 17392 assert(dpy !is null); 17393 return &dpy.screens[scr]; 17394 } 17395 17396 extern(D) Window RootWindow(Display *dpy,int scr) { 17397 return ScreenOfDisplay(dpy,scr).root; 17398 } 17399 17400 struct XWMHints { 17401 arch_long flags; 17402 Bool input; 17403 int initial_state; 17404 Pixmap icon_pixmap; 17405 Window icon_window; 17406 int icon_x, icon_y; 17407 Pixmap icon_mask; 17408 XID window_group; 17409 } 17410 17411 struct XClassHint { 17412 char* res_name; 17413 char* res_class; 17414 } 17415 17416 extern(D) int DefaultScreen(Display *dpy) { 17417 return dpy.default_screen; 17418 } 17419 17420 extern(D) int DefaultDepth(Display* dpy, int scr) { return ScreenOfDisplay(dpy, scr).root_depth; } 17421 extern(D) int DisplayWidth(Display* dpy, int scr) { return ScreenOfDisplay(dpy, scr).width; } 17422 extern(D) int DisplayHeight(Display* dpy, int scr) { return ScreenOfDisplay(dpy, scr).height; } 17423 extern(D) int DisplayWidthMM(Display* dpy, int scr) { return ScreenOfDisplay(dpy, scr).mwidth; } 17424 extern(D) int DisplayHeightMM(Display* dpy, int scr) { return ScreenOfDisplay(dpy, scr).mheight; } 17425 extern(D) auto DefaultColormap(Display* dpy, int scr) { return ScreenOfDisplay(dpy, scr).cmap; } 17426 17427 extern(D) int ConnectionNumber(Display* dpy) { return dpy.fd; } 17428 17429 enum int AnyPropertyType = 0; 17430 enum int Success = 0; 17431 17432 enum int RevertToNone = None; 17433 enum int PointerRoot = 1; 17434 enum Time CurrentTime = 0; 17435 enum int RevertToPointerRoot = PointerRoot; 17436 enum int RevertToParent = 2; 17437 17438 extern(D) int DefaultDepthOfDisplay(Display* dpy) { 17439 return ScreenOfDisplay(dpy, DefaultScreen(dpy)).root_depth; 17440 } 17441 17442 extern(D) Visual* DefaultVisual(Display *dpy,int scr) { 17443 return ScreenOfDisplay(dpy,scr).root_visual; 17444 } 17445 17446 extern(D) GC DefaultGC(Display *dpy,int scr) { 17447 return ScreenOfDisplay(dpy,scr).default_gc; 17448 } 17449 17450 extern(D) uint BlackPixel(Display *dpy,int scr) { 17451 return ScreenOfDisplay(dpy,scr).black_pixel; 17452 } 17453 17454 extern(D) uint WhitePixel(Display *dpy,int scr) { 17455 return ScreenOfDisplay(dpy,scr).white_pixel; 17456 } 17457 17458 alias void* XFontSet; // i think 17459 struct XmbTextItem { 17460 char* chars; 17461 int nchars; 17462 int delta; 17463 XFontSet font_set; 17464 } 17465 17466 struct XTextItem { 17467 char* chars; 17468 int nchars; 17469 int delta; 17470 Font font; 17471 } 17472 17473 enum { 17474 GXclear = 0x0, /* 0 */ 17475 GXand = 0x1, /* src AND dst */ 17476 GXandReverse = 0x2, /* src AND NOT dst */ 17477 GXcopy = 0x3, /* src */ 17478 GXandInverted = 0x4, /* NOT src AND dst */ 17479 GXnoop = 0x5, /* dst */ 17480 GXxor = 0x6, /* src XOR dst */ 17481 GXor = 0x7, /* src OR dst */ 17482 GXnor = 0x8, /* NOT src AND NOT dst */ 17483 GXequiv = 0x9, /* NOT src XOR dst */ 17484 GXinvert = 0xa, /* NOT dst */ 17485 GXorReverse = 0xb, /* src OR NOT dst */ 17486 GXcopyInverted = 0xc, /* NOT src */ 17487 GXorInverted = 0xd, /* NOT src OR dst */ 17488 GXnand = 0xe, /* NOT src OR NOT dst */ 17489 GXset = 0xf, /* 1 */ 17490 } 17491 enum QueueMode : int { 17492 QueuedAlready, 17493 QueuedAfterReading, 17494 QueuedAfterFlush 17495 } 17496 17497 enum GrabMode { GrabModeSync = 0, GrabModeAsync = 1 } 17498 17499 struct XPoint { 17500 short x; 17501 short y; 17502 } 17503 17504 enum CoordMode:int { 17505 CoordModeOrigin = 0, 17506 CoordModePrevious = 1 17507 } 17508 17509 enum PolygonShape:int { 17510 Complex = 0, 17511 Nonconvex = 1, 17512 Convex = 2 17513 } 17514 17515 struct XTextProperty { 17516 const(char)* value; /* same as Property routines */ 17517 Atom encoding; /* prop type */ 17518 int format; /* prop data format: 8, 16, or 32 */ 17519 arch_ulong nitems; /* number of data items in value */ 17520 } 17521 17522 version( X86_64 ) { 17523 static assert(XTextProperty.sizeof == 32); 17524 } 17525 17526 17527 struct XGCValues { 17528 int function_; /* logical operation */ 17529 arch_ulong plane_mask;/* plane mask */ 17530 arch_ulong foreground;/* foreground pixel */ 17531 arch_ulong background;/* background pixel */ 17532 int line_width; /* line width */ 17533 int line_style; /* LineSolid, LineOnOffDash, LineDoubleDash */ 17534 int cap_style; /* CapNotLast, CapButt, 17535 CapRound, CapProjecting */ 17536 int join_style; /* JoinMiter, JoinRound, JoinBevel */ 17537 int fill_style; /* FillSolid, FillTiled, 17538 FillStippled, FillOpaeueStippled */ 17539 int fill_rule; /* EvenOddRule, WindingRule */ 17540 int arc_mode; /* ArcChord, ArcPieSlice */ 17541 Pixmap tile; /* tile pixmap for tiling operations */ 17542 Pixmap stipple; /* stipple 1 plane pixmap for stipping */ 17543 int ts_x_origin; /* offset for tile or stipple operations */ 17544 int ts_y_origin; 17545 Font font; /* default text font for text operations */ 17546 int subwindow_mode; /* ClipByChildren, IncludeInferiors */ 17547 Bool graphics_exposures;/* boolean, should exposures be generated */ 17548 int clip_x_origin; /* origin for clipping */ 17549 int clip_y_origin; 17550 Pixmap clip_mask; /* bitmap clipping; other calls for rects */ 17551 int dash_offset; /* patterned/dashed line information */ 17552 char dashes; 17553 } 17554 17555 struct XColor { 17556 arch_ulong pixel; 17557 ushort red, green, blue; 17558 byte flags; 17559 byte pad; 17560 } 17561 17562 struct XRectangle { 17563 short x; 17564 short y; 17565 ushort width; 17566 ushort height; 17567 } 17568 17569 enum ClipByChildren = 0; 17570 enum IncludeInferiors = 1; 17571 17572 enum Atom XA_PRIMARY = 1; 17573 enum Atom XA_SECONDARY = 2; 17574 enum Atom XA_STRING = 31; 17575 enum Atom XA_CARDINAL = 6; 17576 enum Atom XA_WM_NAME = 39; 17577 enum Atom XA_ATOM = 4; 17578 enum Atom XA_WINDOW = 33; 17579 enum Atom XA_WM_HINTS = 35; 17580 enum int PropModeAppend = 2; 17581 enum int PropModeReplace = 0; 17582 enum int PropModePrepend = 1; 17583 17584 enum int CopyFromParent = 0; 17585 enum int InputOutput = 1; 17586 17587 // XWMHints 17588 enum InputHint = 1 << 0; 17589 enum StateHint = 1 << 1; 17590 enum IconPixmapHint = (1L << 2); 17591 enum IconWindowHint = (1L << 3); 17592 enum IconPositionHint = (1L << 4); 17593 enum IconMaskHint = (1L << 5); 17594 enum WindowGroupHint = (1L << 6); 17595 enum AllHints = (InputHint|StateHint|IconPixmapHint|IconWindowHint|IconPositionHint|IconMaskHint|WindowGroupHint); 17596 enum XUrgencyHint = (1L << 8); 17597 17598 // GC Components 17599 enum GCFunction = (1L<<0); 17600 enum GCPlaneMask = (1L<<1); 17601 enum GCForeground = (1L<<2); 17602 enum GCBackground = (1L<<3); 17603 enum GCLineWidth = (1L<<4); 17604 enum GCLineStyle = (1L<<5); 17605 enum GCCapStyle = (1L<<6); 17606 enum GCJoinStyle = (1L<<7); 17607 enum GCFillStyle = (1L<<8); 17608 enum GCFillRule = (1L<<9); 17609 enum GCTile = (1L<<10); 17610 enum GCStipple = (1L<<11); 17611 enum GCTileStipXOrigin = (1L<<12); 17612 enum GCTileStipYOrigin = (1L<<13); 17613 enum GCFont = (1L<<14); 17614 enum GCSubwindowMode = (1L<<15); 17615 enum GCGraphicsExposures= (1L<<16); 17616 enum GCClipXOrigin = (1L<<17); 17617 enum GCClipYOrigin = (1L<<18); 17618 enum GCClipMask = (1L<<19); 17619 enum GCDashOffset = (1L<<20); 17620 enum GCDashList = (1L<<21); 17621 enum GCArcMode = (1L<<22); 17622 enum GCLastBit = 22; 17623 17624 17625 enum int WithdrawnState = 0; 17626 enum int NormalState = 1; 17627 enum int IconicState = 3; 17628 17629 } 17630 } else version (OSXCocoa) { 17631 private: 17632 alias void* id; 17633 alias void* Class; 17634 alias void* SEL; 17635 alias void* IMP; 17636 alias void* Ivar; 17637 alias byte BOOL; 17638 alias const(void)* CFStringRef; 17639 alias const(void)* CFAllocatorRef; 17640 alias const(void)* CFTypeRef; 17641 alias const(void)* CGContextRef; 17642 alias const(void)* CGColorSpaceRef; 17643 alias const(void)* CGImageRef; 17644 alias ulong CGBitmapInfo; 17645 17646 struct objc_super { 17647 id self; 17648 Class superclass; 17649 } 17650 17651 struct CFRange { 17652 long location, length; 17653 } 17654 17655 struct NSPoint { 17656 double x, y; 17657 17658 static fromTuple(T)(T tupl) { 17659 return NSPoint(tupl.tupleof); 17660 } 17661 } 17662 struct NSSize { 17663 double width, height; 17664 } 17665 struct NSRect { 17666 NSPoint origin; 17667 NSSize size; 17668 } 17669 alias NSPoint CGPoint; 17670 alias NSSize CGSize; 17671 alias NSRect CGRect; 17672 17673 struct CGAffineTransform { 17674 double a, b, c, d, tx, ty; 17675 } 17676 17677 enum NSApplicationActivationPolicyRegular = 0; 17678 enum NSBackingStoreBuffered = 2; 17679 enum kCFStringEncodingUTF8 = 0x08000100; 17680 17681 enum : size_t { 17682 NSBorderlessWindowMask = 0, 17683 NSTitledWindowMask = 1 << 0, 17684 NSClosableWindowMask = 1 << 1, 17685 NSMiniaturizableWindowMask = 1 << 2, 17686 NSResizableWindowMask = 1 << 3, 17687 NSTexturedBackgroundWindowMask = 1 << 8 17688 } 17689 17690 enum : ulong { 17691 kCGImageAlphaNone, 17692 kCGImageAlphaPremultipliedLast, 17693 kCGImageAlphaPremultipliedFirst, 17694 kCGImageAlphaLast, 17695 kCGImageAlphaFirst, 17696 kCGImageAlphaNoneSkipLast, 17697 kCGImageAlphaNoneSkipFirst 17698 } 17699 enum : ulong { 17700 kCGBitmapAlphaInfoMask = 0x1F, 17701 kCGBitmapFloatComponents = (1 << 8), 17702 kCGBitmapByteOrderMask = 0x7000, 17703 kCGBitmapByteOrderDefault = (0 << 12), 17704 kCGBitmapByteOrder16Little = (1 << 12), 17705 kCGBitmapByteOrder32Little = (2 << 12), 17706 kCGBitmapByteOrder16Big = (3 << 12), 17707 kCGBitmapByteOrder32Big = (4 << 12) 17708 } 17709 enum CGPathDrawingMode { 17710 kCGPathFill, 17711 kCGPathEOFill, 17712 kCGPathStroke, 17713 kCGPathFillStroke, 17714 kCGPathEOFillStroke 17715 } 17716 enum objc_AssociationPolicy : size_t { 17717 OBJC_ASSOCIATION_ASSIGN = 0, 17718 OBJC_ASSOCIATION_RETAIN_NONATOMIC = 1, 17719 OBJC_ASSOCIATION_COPY_NONATOMIC = 3, 17720 OBJC_ASSOCIATION_RETAIN = 0x301, //01401, 17721 OBJC_ASSOCIATION_COPY = 0x303 //01403 17722 } 17723 17724 extern(C) { 17725 id objc_msgSend(id receiver, SEL selector, ...); 17726 id objc_msgSendSuper(objc_super* superStruct, SEL selector, ...); 17727 id objc_getClass(const(char)* name); 17728 SEL sel_registerName(const(char)* str); 17729 Class objc_allocateClassPair(Class superclass, const(char)* name, 17730 size_t extra_bytes); 17731 void objc_registerClassPair(Class cls); 17732 BOOL class_addMethod(Class cls, SEL name, IMP imp, const(char)* types); 17733 id objc_getAssociatedObject(id object, void* key); 17734 void objc_setAssociatedObject(id object, void* key, id value, 17735 objc_AssociationPolicy policy); 17736 Ivar class_getInstanceVariable(Class cls, const(char)* name); 17737 id object_getIvar(id object, Ivar ivar); 17738 void object_setIvar(id object, Ivar ivar, id value); 17739 BOOL class_addIvar(Class cls, const(char)* name, 17740 size_t size, ubyte alignment, const(char)* types); 17741 17742 extern __gshared id NSApp; 17743 17744 void CFRelease(CFTypeRef obj); 17745 17746 CFStringRef CFStringCreateWithBytes(CFAllocatorRef allocator, 17747 const(char)* bytes, long numBytes, 17748 long encoding, 17749 BOOL isExternalRepresentation); 17750 long CFStringGetBytes(CFStringRef theString, CFRange range, long encoding, 17751 char lossByte, bool isExternalRepresentation, 17752 char* buffer, long maxBufLen, long* usedBufLen); 17753 long CFStringGetLength(CFStringRef theString); 17754 17755 CGContextRef CGBitmapContextCreate(void* data, 17756 size_t width, size_t height, 17757 size_t bitsPerComponent, 17758 size_t bytesPerRow, 17759 CGColorSpaceRef colorspace, 17760 CGBitmapInfo bitmapInfo); 17761 void CGContextRelease(CGContextRef c); 17762 ubyte* CGBitmapContextGetData(CGContextRef c); 17763 CGImageRef CGBitmapContextCreateImage(CGContextRef c); 17764 size_t CGBitmapContextGetWidth(CGContextRef c); 17765 size_t CGBitmapContextGetHeight(CGContextRef c); 17766 17767 CGColorSpaceRef CGColorSpaceCreateDeviceRGB(); 17768 void CGColorSpaceRelease(CGColorSpaceRef cs); 17769 17770 void CGContextSetRGBStrokeColor(CGContextRef c, 17771 double red, double green, double blue, 17772 double alpha); 17773 void CGContextSetRGBFillColor(CGContextRef c, 17774 double red, double green, double blue, 17775 double alpha); 17776 void CGContextDrawImage(CGContextRef c, CGRect rect, CGImageRef image); 17777 void CGContextShowTextAtPoint(CGContextRef c, double x, double y, 17778 const(char)* str, size_t length); 17779 void CGContextStrokeLineSegments(CGContextRef c, 17780 const(CGPoint)* points, size_t count); 17781 17782 void CGContextBeginPath(CGContextRef c); 17783 void CGContextDrawPath(CGContextRef c, CGPathDrawingMode mode); 17784 void CGContextAddEllipseInRect(CGContextRef c, CGRect rect); 17785 void CGContextAddArc(CGContextRef c, double x, double y, double radius, 17786 double startAngle, double endAngle, long clockwise); 17787 void CGContextAddRect(CGContextRef c, CGRect rect); 17788 void CGContextAddLines(CGContextRef c, 17789 const(CGPoint)* points, size_t count); 17790 void CGContextSaveGState(CGContextRef c); 17791 void CGContextRestoreGState(CGContextRef c); 17792 void CGContextSelectFont(CGContextRef c, const(char)* name, double size, 17793 ulong textEncoding); 17794 CGAffineTransform CGContextGetTextMatrix(CGContextRef c); 17795 void CGContextSetTextMatrix(CGContextRef c, CGAffineTransform t); 17796 17797 void CGImageRelease(CGImageRef image); 17798 } 17799 17800 private: 17801 // A convenient method to create a CFString (=NSString) from a D string. 17802 CFStringRef createCFString(string str) { 17803 return CFStringCreateWithBytes(null, str.ptr, cast(long) str.length, 17804 kCFStringEncodingUTF8, false); 17805 } 17806 17807 // Objective-C calls. 17808 RetType objc_msgSend_specialized(string selector, RetType, T...)(id self, T args) { 17809 auto _cmd = sel_registerName(selector.ptr); 17810 alias extern(C) RetType function(id, SEL, T) ExpectedType; 17811 return (cast(ExpectedType)&objc_msgSend)(self, _cmd, args); 17812 } 17813 RetType objc_msgSend_classMethod(string selector, RetType, T...)(const(char)* className, T args) { 17814 auto _cmd = sel_registerName(selector.ptr); 17815 auto cls = objc_getClass(className); 17816 alias extern(C) RetType function(id, SEL, T) ExpectedType; 17817 return (cast(ExpectedType)&objc_msgSend)(cls, _cmd, args); 17818 } 17819 RetType objc_msgSend_classMethod(string className, string selector, RetType, T...)(T args) { 17820 return objc_msgSend_classMethod!(selector, RetType, T)(className.ptr, args); 17821 } 17822 17823 alias objc_msgSend_specialized!("setNeedsDisplay:", void, BOOL) setNeedsDisplay; 17824 alias objc_msgSend_classMethod!("alloc", id) alloc; 17825 alias objc_msgSend_specialized!("initWithContentRect:styleMask:backing:defer:", 17826 id, NSRect, size_t, size_t, BOOL) initWithContentRect; 17827 alias objc_msgSend_specialized!("setTitle:", void, CFStringRef) setTitle; 17828 alias objc_msgSend_specialized!("center", void) center; 17829 alias objc_msgSend_specialized!("initWithFrame:", id, NSRect) initWithFrame; 17830 alias objc_msgSend_specialized!("setContentView:", void, id) setContentView; 17831 alias objc_msgSend_specialized!("release", void) release; 17832 alias objc_msgSend_classMethod!("NSColor", "whiteColor", id) whiteNSColor; 17833 alias objc_msgSend_specialized!("setBackgroundColor:", void, id) setBackgroundColor; 17834 alias objc_msgSend_specialized!("makeKeyAndOrderFront:", void, id) makeKeyAndOrderFront; 17835 alias objc_msgSend_specialized!("invalidate", void) invalidate; 17836 alias objc_msgSend_specialized!("close", void) close; 17837 alias objc_msgSend_classMethod!("NSTimer", "scheduledTimerWithTimeInterval:target:selector:userInfo:repeats:", 17838 id, double, id, SEL, id, BOOL) scheduledTimer; 17839 alias objc_msgSend_specialized!("run", void) run; 17840 alias objc_msgSend_classMethod!("NSGraphicsContext", "currentContext", 17841 id) currentNSGraphicsContext; 17842 alias objc_msgSend_specialized!("graphicsPort", CGContextRef) graphicsPort; 17843 alias objc_msgSend_specialized!("characters", CFStringRef) characters; 17844 alias objc_msgSend_specialized!("superclass", Class) superclass; 17845 alias objc_msgSend_specialized!("init", id) init; 17846 alias objc_msgSend_specialized!("addItem:", void, id) addItem; 17847 alias objc_msgSend_specialized!("setMainMenu:", void, id) setMainMenu; 17848 alias objc_msgSend_specialized!("initWithTitle:action:keyEquivalent:", 17849 id, CFStringRef, SEL, CFStringRef) initWithTitle; 17850 alias objc_msgSend_specialized!("setSubmenu:", void, id) setSubmenu; 17851 alias objc_msgSend_specialized!("setDelegate:", void, id) setDelegate; 17852 alias objc_msgSend_specialized!("activateIgnoringOtherApps:", 17853 void, BOOL) activateIgnoringOtherApps; 17854 alias objc_msgSend_classMethod!("NSApplication", "sharedApplication", 17855 id) sharedNSApplication; 17856 alias objc_msgSend_specialized!("setActivationPolicy:", void, ptrdiff_t) setActivationPolicy; 17857 } else static assert(0, "Unsupported operating system"); 17858 17859 17860 version(OSXCocoa) { 17861 // I don't know anything about the Mac, but a couple years ago, KennyTM on the newsgroup wrote this for me 17862 // 17863 // http://forum.dlang.org/thread/innr0v$1deh$1@digitalmars.com?page=4#post-int88l:24uaf:241:40digitalmars.com 17864 // https://github.com/kennytm/simpledisplay.d/blob/osx/simpledisplay.d 17865 // 17866 // and it is about time I merged it in here. It is available with -version=OSXCocoa until someone tests it for me! 17867 // Probably won't even fully compile right now 17868 17869 import std.math : PI; 17870 import std.algorithm : map; 17871 import std.array : array; 17872 17873 alias SimpleWindow NativeWindowHandle; 17874 alias void delegate(id) NativeEventHandler; 17875 17876 __gshared Ivar simpleWindowIvar; 17877 17878 enum KEY_ESCAPE = 27; 17879 17880 mixin template NativeImageImplementation() { 17881 CGContextRef context; 17882 ubyte* rawData; 17883 final: 17884 17885 void convertToRgbaBytes(ubyte[] where) { 17886 assert(where.length == this.width * this.height * 4); 17887 17888 // if rawData had a length.... 17889 //assert(rawData.length == where.length); 17890 for(long idx = 0; idx < where.length; idx += 4) { 17891 auto alpha = rawData[idx + 3]; 17892 if(alpha == 255) { 17893 where[idx + 0] = rawData[idx + 0]; // r 17894 where[idx + 1] = rawData[idx + 1]; // g 17895 where[idx + 2] = rawData[idx + 2]; // b 17896 where[idx + 3] = rawData[idx + 3]; // a 17897 } else { 17898 where[idx + 0] = cast(ubyte)(rawData[idx + 0] * 255 / alpha); // r 17899 where[idx + 1] = cast(ubyte)(rawData[idx + 1] * 255 / alpha); // g 17900 where[idx + 2] = cast(ubyte)(rawData[idx + 2] * 255 / alpha); // b 17901 where[idx + 3] = rawData[idx + 3]; // a 17902 17903 } 17904 } 17905 } 17906 17907 void setFromRgbaBytes(in ubyte[] where) { 17908 // FIXME: this is probably wrong 17909 assert(where.length == this.width * this.height * 4); 17910 17911 // if rawData had a length.... 17912 //assert(rawData.length == where.length); 17913 for(long idx = 0; idx < where.length; idx += 4) { 17914 auto alpha = rawData[idx + 3]; 17915 if(alpha == 255) { 17916 rawData[idx + 0] = where[idx + 0]; // r 17917 rawData[idx + 1] = where[idx + 1]; // g 17918 rawData[idx + 2] = where[idx + 2]; // b 17919 rawData[idx + 3] = where[idx + 3]; // a 17920 } else { 17921 rawData[idx + 0] = cast(ubyte)(where[idx + 0] * 255 / alpha); // r 17922 rawData[idx + 1] = cast(ubyte)(where[idx + 1] * 255 / alpha); // g 17923 rawData[idx + 2] = cast(ubyte)(where[idx + 2] * 255 / alpha); // b 17924 rawData[idx + 3] = where[idx + 3]; // a 17925 17926 } 17927 } 17928 } 17929 17930 17931 void createImage(int width, int height, bool forcexshm=false) { 17932 auto colorSpace = CGColorSpaceCreateDeviceRGB(); 17933 context = CGBitmapContextCreate(null, width, height, 8, 4*width, 17934 colorSpace, 17935 kCGImageAlphaPremultipliedLast 17936 |kCGBitmapByteOrder32Big); 17937 CGColorSpaceRelease(colorSpace); 17938 rawData = CGBitmapContextGetData(context); 17939 } 17940 void dispose() { 17941 CGContextRelease(context); 17942 } 17943 17944 void setPixel(int x, int y, Color c) { 17945 auto offset = (y * width + x) * 4; 17946 if (c.a == 255) { 17947 rawData[offset + 0] = c.r; 17948 rawData[offset + 1] = c.g; 17949 rawData[offset + 2] = c.b; 17950 rawData[offset + 3] = c.a; 17951 } else { 17952 rawData[offset + 0] = cast(ubyte)(c.r*c.a/255); 17953 rawData[offset + 1] = cast(ubyte)(c.g*c.a/255); 17954 rawData[offset + 2] = cast(ubyte)(c.b*c.a/255); 17955 rawData[offset + 3] = c.a; 17956 } 17957 } 17958 } 17959 17960 mixin template NativeScreenPainterImplementation() { 17961 CGContextRef context; 17962 ubyte[4] _outlineComponents; 17963 id view; 17964 17965 void create(NativeWindowHandle window) { 17966 context = window.drawingContext; 17967 view = window.view; 17968 } 17969 17970 void dispose() { 17971 setNeedsDisplay(view, true); 17972 } 17973 17974 bool manualInvalidations; 17975 void invalidateRect(Rectangle invalidRect) { } 17976 17977 // NotYetImplementedException 17978 Size textSize(in char[] txt) { return Size(32, 16); throw new NotYetImplementedException(); } 17979 void rasterOp(RasterOp op) {} 17980 Pen _activePen; 17981 Color _fillColor; 17982 Rectangle _clipRectangle; 17983 void setClipRectangle(int, int, int, int) {} 17984 void setFont(OperatingSystemFont) {} 17985 int fontHeight() { return 14; } 17986 17987 // end 17988 17989 void pen(Pen pen) { 17990 _activePen = pen; 17991 auto color = pen.color; // FIXME 17992 double alphaComponent = color.a/255.0f; 17993 CGContextSetRGBStrokeColor(context, 17994 color.r/255.0f, color.g/255.0f, color.b/255.0f, alphaComponent); 17995 17996 if (color.a != 255) { 17997 _outlineComponents[0] = cast(ubyte)(color.r*color.a/255); 17998 _outlineComponents[1] = cast(ubyte)(color.g*color.a/255); 17999 _outlineComponents[2] = cast(ubyte)(color.b*color.a/255); 18000 _outlineComponents[3] = color.a; 18001 } else { 18002 _outlineComponents[0] = color.r; 18003 _outlineComponents[1] = color.g; 18004 _outlineComponents[2] = color.b; 18005 _outlineComponents[3] = color.a; 18006 } 18007 } 18008 18009 @property void fillColor(Color color) { 18010 CGContextSetRGBFillColor(context, 18011 color.r/255.0f, color.g/255.0f, color.b/255.0f, color.a/255.0f); 18012 } 18013 18014 void drawImage(int x, int y, Image image, int ulx, int upy, int width, int height) { 18015 // NotYetImplementedException for upper left/width/height 18016 auto cgImage = CGBitmapContextCreateImage(image.context); 18017 auto size = CGSize(CGBitmapContextGetWidth(image.context), 18018 CGBitmapContextGetHeight(image.context)); 18019 CGContextDrawImage(context, CGRect(CGPoint(x, y), size), cgImage); 18020 CGImageRelease(cgImage); 18021 } 18022 18023 version(OSXCocoa) {} else // NotYetImplementedException 18024 void drawPixmap(Sprite image, int x, int y) { 18025 // FIXME: is this efficient? 18026 auto cgImage = CGBitmapContextCreateImage(image.context); 18027 auto size = CGSize(CGBitmapContextGetWidth(image.context), 18028 CGBitmapContextGetHeight(image.context)); 18029 CGContextDrawImage(context, CGRect(CGPoint(x, y), size), cgImage); 18030 CGImageRelease(cgImage); 18031 } 18032 18033 18034 void drawText(int x, int y, int x2, int y2, in char[] text, uint alignment) { 18035 // FIXME: alignment 18036 if (_outlineComponents[3] != 0) { 18037 CGContextSaveGState(context); 18038 auto invAlpha = 1.0f/_outlineComponents[3]; 18039 CGContextSetRGBFillColor(context, _outlineComponents[0]*invAlpha, 18040 _outlineComponents[1]*invAlpha, 18041 _outlineComponents[2]*invAlpha, 18042 _outlineComponents[3]/255.0f); 18043 CGContextShowTextAtPoint(context, x, y + 12 /* this is cuz this picks baseline but i want bounding box */, text.ptr, text.length); 18044 // auto cfstr = cast(id)createCFString(text); 18045 // objc_msgSend(cfstr, sel_registerName("drawAtPoint:withAttributes:"), 18046 // NSPoint(x, y), null); 18047 // CFRelease(cfstr); 18048 CGContextRestoreGState(context); 18049 } 18050 } 18051 18052 void drawPixel(int x, int y) { 18053 auto rawData = CGBitmapContextGetData(context); 18054 auto width = CGBitmapContextGetWidth(context); 18055 auto height = CGBitmapContextGetHeight(context); 18056 auto offset = ((height - y - 1) * width + x) * 4; 18057 rawData[offset .. offset+4] = _outlineComponents; 18058 } 18059 18060 void drawLine(int x1, int y1, int x2, int y2) { 18061 CGPoint[2] linePoints; 18062 linePoints[0] = CGPoint(x1, y1); 18063 linePoints[1] = CGPoint(x2, y2); 18064 CGContextStrokeLineSegments(context, linePoints.ptr, linePoints.length); 18065 } 18066 18067 void drawRectangle(int x, int y, int width, int height) { 18068 CGContextBeginPath(context); 18069 auto rect = CGRect(CGPoint(x, y), CGSize(width, height)); 18070 CGContextAddRect(context, rect); 18071 CGContextDrawPath(context, CGPathDrawingMode.kCGPathFillStroke); 18072 } 18073 18074 void drawEllipse(int x1, int y1, int x2, int y2) { 18075 CGContextBeginPath(context); 18076 auto rect = CGRect(CGPoint(x1, y1), CGSize(x2-x1, y2-y1)); 18077 CGContextAddEllipseInRect(context, rect); 18078 CGContextDrawPath(context, CGPathDrawingMode.kCGPathFillStroke); 18079 } 18080 18081 void drawArc(int x1, int y1, int width, int height, int start, int finish) { 18082 // @@@BUG@@@ Does not support elliptic arc (width != height). 18083 CGContextBeginPath(context); 18084 CGContextAddArc(context, x1+width*0.5f, y1+height*0.5f, width, 18085 start*PI/(180*64), finish*PI/(180*64), 0); 18086 CGContextDrawPath(context, CGPathDrawingMode.kCGPathFillStroke); 18087 } 18088 18089 void drawPolygon(Point[] intPoints) { 18090 CGContextBeginPath(context); 18091 auto points = array(map!(CGPoint.fromTuple)(intPoints)); 18092 CGContextAddLines(context, points.ptr, points.length); 18093 CGContextDrawPath(context, CGPathDrawingMode.kCGPathFillStroke); 18094 } 18095 } 18096 18097 mixin template NativeSimpleWindowImplementation() { 18098 void createWindow(int width, int height, string title, OpenGlOptions opengl, SimpleWindow parent) { 18099 synchronized { 18100 if (NSApp == null) initializeApp(); 18101 } 18102 18103 auto contentRect = NSRect(NSPoint(0, 0), NSSize(width, height)); 18104 18105 // create the window. 18106 window = initWithContentRect(alloc("NSWindow"), 18107 contentRect, 18108 NSTitledWindowMask 18109 |NSClosableWindowMask 18110 |NSMiniaturizableWindowMask 18111 |NSResizableWindowMask, 18112 NSBackingStoreBuffered, 18113 true); 18114 18115 // set the title & move the window to center. 18116 auto windowTitle = createCFString(title); 18117 setTitle(window, windowTitle); 18118 CFRelease(windowTitle); 18119 center(window); 18120 18121 // create area to draw on. 18122 auto colorSpace = CGColorSpaceCreateDeviceRGB(); 18123 drawingContext = CGBitmapContextCreate(null, width, height, 18124 8, 4*width, colorSpace, 18125 kCGImageAlphaPremultipliedLast 18126 |kCGBitmapByteOrder32Big); 18127 CGColorSpaceRelease(colorSpace); 18128 CGContextSelectFont(drawingContext, "Lucida Grande", 12.0f, 1); 18129 auto matrix = CGContextGetTextMatrix(drawingContext); 18130 matrix.c = -matrix.c; 18131 matrix.d = -matrix.d; 18132 CGContextSetTextMatrix(drawingContext, matrix); 18133 18134 // create the subview that things will be drawn on. 18135 view = initWithFrame(alloc("SDGraphicsView"), contentRect); 18136 setContentView(window, view); 18137 object_setIvar(view, simpleWindowIvar, cast(id)this); 18138 release(view); 18139 18140 setBackgroundColor(window, whiteNSColor); 18141 makeKeyAndOrderFront(window, null); 18142 } 18143 void dispose() { 18144 closeWindow(); 18145 release(window); 18146 } 18147 void closeWindow() { 18148 invalidate(timer); 18149 .close(window); 18150 } 18151 18152 ScreenPainter getPainter(bool manualInvalidations) { 18153 return ScreenPainter(this, this, manualInvalidations); 18154 } 18155 18156 id window; 18157 id timer; 18158 id view; 18159 CGContextRef drawingContext; 18160 } 18161 18162 extern(C) { 18163 private: 18164 BOOL returnTrue3(id self, SEL _cmd, id app) { 18165 return true; 18166 } 18167 BOOL returnTrue2(id self, SEL _cmd) { 18168 return true; 18169 } 18170 18171 void pulse(id self, SEL _cmd) { 18172 auto simpleWindow = cast(SimpleWindow)object_getIvar(self, simpleWindowIvar); 18173 simpleWindow.handlePulse(); 18174 setNeedsDisplay(self, true); 18175 } 18176 void drawRect(id self, SEL _cmd, NSRect rect) { 18177 auto simpleWindow = cast(SimpleWindow)object_getIvar(self, simpleWindowIvar); 18178 auto curCtx = graphicsPort(currentNSGraphicsContext); 18179 auto cgImage = CGBitmapContextCreateImage(simpleWindow.drawingContext); 18180 auto size = CGSize(CGBitmapContextGetWidth(simpleWindow.drawingContext), 18181 CGBitmapContextGetHeight(simpleWindow.drawingContext)); 18182 CGContextDrawImage(curCtx, CGRect(CGPoint(0, 0), size), cgImage); 18183 CGImageRelease(cgImage); 18184 } 18185 void keyDown(id self, SEL _cmd, id event) { 18186 auto simpleWindow = cast(SimpleWindow)object_getIvar(self, simpleWindowIvar); 18187 18188 // the event may have multiple characters, and we send them all at 18189 // once. 18190 if (simpleWindow.handleCharEvent || simpleWindow.handleKeyEvent) { 18191 auto chars = characters(event); 18192 auto range = CFRange(0, CFStringGetLength(chars)); 18193 auto buffer = new char[range.length*3]; 18194 long actualLength; 18195 CFStringGetBytes(chars, range, kCFStringEncodingUTF8, 0, false, 18196 buffer.ptr, cast(int) buffer.length, &actualLength); 18197 foreach (dchar dc; buffer[0..actualLength]) { 18198 if (simpleWindow.handleCharEvent) 18199 simpleWindow.handleCharEvent(dc); 18200 // NotYetImplementedException 18201 //if (simpleWindow.handleKeyEvent) 18202 //simpleWindow.handleKeyEvent(KeyEvent(dc)); // FIXME: what about keyUp? 18203 } 18204 } 18205 18206 // the event's 'keyCode' is hardware-dependent. I don't think people 18207 // will like it. Let's leave it to the native handler. 18208 18209 // perform the default action. 18210 18211 // so the default action is to make a bomp sound and i dont want that 18212 // sooooooooo yeah not gonna do that. 18213 18214 //auto superData = objc_super(self, superclass(self)); 18215 //alias extern(C) void function(objc_super*, SEL, id) T; 18216 //(cast(T)&objc_msgSendSuper)(&superData, _cmd, event); 18217 } 18218 } 18219 18220 // initialize the app so that it can be interacted with the user. 18221 // based on http://cocoawithlove.com/2010/09/minimalist-cocoa-programming.html 18222 private void initializeApp() { 18223 // push an autorelease pool to avoid leaking. 18224 init(alloc("NSAutoreleasePool")); 18225 18226 // create a new NSApp instance 18227 sharedNSApplication; 18228 setActivationPolicy(NSApp, NSApplicationActivationPolicyRegular); 18229 18230 // create the "Quit" menu. 18231 auto menuBar = init(alloc("NSMenu")); 18232 auto appMenuItem = init(alloc("NSMenuItem")); 18233 addItem(menuBar, appMenuItem); 18234 setMainMenu(NSApp, menuBar); 18235 release(appMenuItem); 18236 release(menuBar); 18237 18238 auto appMenu = init(alloc("NSMenu")); 18239 auto quitTitle = createCFString("Quit"); 18240 auto q = createCFString("q"); 18241 auto quitItem = initWithTitle(alloc("NSMenuItem"), 18242 quitTitle, sel_registerName("terminate:"), q); 18243 addItem(appMenu, quitItem); 18244 setSubmenu(appMenuItem, appMenu); 18245 release(quitItem); 18246 release(appMenu); 18247 CFRelease(q); 18248 CFRelease(quitTitle); 18249 18250 // assign a delegate for the application, allow it to quit when the last 18251 // window is closed. 18252 auto delegateClass = objc_allocateClassPair(objc_getClass("NSObject"), 18253 "SDWindowCloseDelegate", 0); 18254 class_addMethod(delegateClass, 18255 sel_registerName("applicationShouldTerminateAfterLastWindowClosed:"), 18256 &returnTrue3, "c@:@"); 18257 objc_registerClassPair(delegateClass); 18258 18259 auto appDelegate = init(alloc("SDWindowCloseDelegate")); 18260 setDelegate(NSApp, appDelegate); 18261 activateIgnoringOtherApps(NSApp, true); 18262 18263 // create a new view that draws the graphics and respond to keyDown 18264 // events. 18265 auto viewClass = objc_allocateClassPair(objc_getClass("NSView"), 18266 "SDGraphicsView", (void*).sizeof); 18267 class_addIvar(viewClass, "simpledisplay_simpleWindow", 18268 (void*).sizeof, (void*).alignof, "^v"); 18269 class_addMethod(viewClass, sel_registerName("simpledisplay_pulse"), 18270 &pulse, "v@:"); 18271 class_addMethod(viewClass, sel_registerName("drawRect:"), 18272 &drawRect, "v@:{NSRect={NSPoint=ff}{NSSize=ff}}"); 18273 class_addMethod(viewClass, sel_registerName("isFlipped"), 18274 &returnTrue2, "c@:"); 18275 class_addMethod(viewClass, sel_registerName("acceptsFirstResponder"), 18276 &returnTrue2, "c@:"); 18277 class_addMethod(viewClass, sel_registerName("keyDown:"), 18278 &keyDown, "v@:@"); 18279 objc_registerClassPair(viewClass); 18280 simpleWindowIvar = class_getInstanceVariable(viewClass, 18281 "simpledisplay_simpleWindow"); 18282 } 18283 } 18284 18285 version(without_opengl) {} else 18286 extern(System) nothrow @nogc { 18287 //enum uint GL_VERSION = 0x1F02; 18288 //const(char)* glGetString (/*GLenum*/uint); 18289 version(X11) { 18290 static if (!SdpyIsUsingIVGLBinds) { 18291 18292 enum GLX_X_RENDERABLE = 0x8012; 18293 enum GLX_DRAWABLE_TYPE = 0x8010; 18294 enum GLX_RENDER_TYPE = 0x8011; 18295 enum GLX_X_VISUAL_TYPE = 0x22; 18296 enum GLX_TRUE_COLOR = 0x8002; 18297 enum GLX_WINDOW_BIT = 0x00000001; 18298 enum GLX_RGBA_BIT = 0x00000001; 18299 enum GLX_COLOR_INDEX_BIT = 0x00000002; 18300 enum GLX_SAMPLE_BUFFERS = 0x186a0; 18301 enum GLX_SAMPLES = 0x186a1; 18302 enum GLX_CONTEXT_MAJOR_VERSION_ARB = 0x2091; 18303 enum GLX_CONTEXT_MINOR_VERSION_ARB = 0x2092; 18304 } 18305 18306 // GLX_EXT_swap_control 18307 alias glXSwapIntervalEXT = void function (Display* dpy, /*GLXDrawable*/Drawable drawable, int interval); 18308 private __gshared glXSwapIntervalEXT _glx_swapInterval_fn = null; 18309 18310 //k8: ugly code to prevent warnings when sdpy is compiled into .a 18311 extern(System) { 18312 alias glXCreateContextAttribsARB_fna = GLXContext function (Display *dpy, GLXFBConfig config, GLXContext share_context, /*Bool*/int direct, const(int)* attrib_list); 18313 } 18314 private __gshared /*glXCreateContextAttribsARB_fna*/void* glXCreateContextAttribsARBFn = cast(void*)1; //HACK! 18315 18316 // this made public so we don't have to get it again and again 18317 public bool glXCreateContextAttribsARB_present () { 18318 if (glXCreateContextAttribsARBFn is cast(void*)1) { 18319 // get it 18320 glXCreateContextAttribsARBFn = cast(void*)glbindGetProcAddress("glXCreateContextAttribsARB"); 18321 //{ import core.stdc.stdio; printf("checking glXCreateContextAttribsARB: %shere\n", (glXCreateContextAttribsARBFn !is null ? "".ptr : "not ".ptr)); } 18322 } 18323 return (glXCreateContextAttribsARBFn !is null); 18324 } 18325 18326 // this made public so we don't have to get it again and again 18327 public GLXContext glXCreateContextAttribsARB (Display *dpy, GLXFBConfig config, GLXContext share_context, /*Bool*/int direct, const(int)* attrib_list) { 18328 if (!glXCreateContextAttribsARB_present()) assert(0, "glXCreateContextAttribsARB is not present"); 18329 return (cast(glXCreateContextAttribsARB_fna)glXCreateContextAttribsARBFn)(dpy, config, share_context, direct, attrib_list); 18330 } 18331 18332 // extern(C) private __gshared int function(int) glXSwapIntervalSGI; // seems totally redundant to the tohers 18333 extern(C) private __gshared int function(int) glXSwapIntervalMESA; 18334 18335 void glxSetVSync (Display* dpy, /*GLXDrawable*/Drawable drawable, bool wait) { 18336 if (cast(void*)_glx_swapInterval_fn is cast(void*)1) return; 18337 if (_glx_swapInterval_fn is null) { 18338 _glx_swapInterval_fn = cast(glXSwapIntervalEXT)glXGetProcAddress("glXSwapIntervalEXT"); 18339 if (_glx_swapInterval_fn is null) { 18340 _glx_swapInterval_fn = cast(glXSwapIntervalEXT)1; 18341 return; 18342 } 18343 version(sdddd) { import std.stdio; debug writeln("glXSwapIntervalEXT found!"); } 18344 } 18345 18346 if(glXSwapIntervalMESA is null) { 18347 // it seems to require both to actually take effect on many computers 18348 // idk why 18349 glXSwapIntervalMESA = cast(typeof(glXSwapIntervalMESA)) glXGetProcAddress("glXSwapIntervalMESA"); 18350 if(glXSwapIntervalMESA is null) 18351 glXSwapIntervalMESA = cast(typeof(glXSwapIntervalMESA)) 1; 18352 } 18353 18354 if(cast(void*) glXSwapIntervalMESA > cast(void*) 1) 18355 glXSwapIntervalMESA(wait ? 1 : 0); 18356 18357 _glx_swapInterval_fn(dpy, drawable, (wait ? 1 : 0)); 18358 } 18359 } else version(Windows) { 18360 static if (!SdpyIsUsingIVGLBinds) { 18361 enum GL_TRUE = 1; 18362 enum GL_FALSE = 0; 18363 alias int GLint; 18364 18365 public void* glbindGetProcAddress (const(char)* name) { 18366 void* res = wglGetProcAddress(name); 18367 if (res is null) { 18368 /+ 18369 //{ import core.stdc.stdio; printf("GL: '%s' not found (0)\n", name); } 18370 import core.sys.windows.windef, core.sys.windows.winbase; 18371 __gshared HINSTANCE dll = null; 18372 if (dll is null) { 18373 dll = LoadLibraryA("opengl32.dll"); 18374 if (dll is null) return null; // <32, but idc 18375 } 18376 res = GetProcAddress(dll, name); 18377 +/ 18378 res = GetProcAddress(gl.libHandle, name); 18379 } 18380 //{ import core.stdc.stdio; printf(" GL: '%s' is 0x%08x\n", name, cast(uint)res); } 18381 return res; 18382 } 18383 } 18384 18385 18386 private __gshared extern(System) BOOL function(int) wglSwapIntervalEXT; 18387 void wglSetVSync(bool wait) { 18388 if(wglSwapIntervalEXT is null) { 18389 wglSwapIntervalEXT = cast(typeof(wglSwapIntervalEXT)) wglGetProcAddress("wglSwapIntervalEXT"); 18390 if(wglSwapIntervalEXT is null) 18391 wglSwapIntervalEXT = cast(typeof(wglSwapIntervalEXT)) 1; 18392 } 18393 if(cast(void*) wglSwapIntervalEXT is cast(void*) 1) 18394 return; 18395 18396 wglSwapIntervalEXT(wait ? 1 : 0); 18397 } 18398 18399 enum WGL_CONTEXT_MAJOR_VERSION_ARB = 0x2091; 18400 enum WGL_CONTEXT_MINOR_VERSION_ARB = 0x2092; 18401 enum WGL_CONTEXT_LAYER_PLANE_ARB = 0x2093; 18402 enum WGL_CONTEXT_FLAGS_ARB = 0x2094; 18403 enum WGL_CONTEXT_PROFILE_MASK_ARB = 0x9126; 18404 18405 enum WGL_CONTEXT_DEBUG_BIT_ARB = 0x0001; 18406 enum WGL_CONTEXT_FORWARD_COMPATIBLE_BIT_ARB = 0x0002; 18407 18408 enum WGL_CONTEXT_CORE_PROFILE_BIT_ARB = 0x00000001; 18409 enum WGL_CONTEXT_COMPATIBILITY_PROFILE_BIT_ARB = 0x00000002; 18410 18411 alias wglCreateContextAttribsARB_fna = HGLRC function (HDC hDC, HGLRC hShareContext, const(int)* attribList); 18412 __gshared wglCreateContextAttribsARB_fna wglCreateContextAttribsARB = null; 18413 18414 void wglInitOtherFunctions () { 18415 if (wglCreateContextAttribsARB is null) { 18416 wglCreateContextAttribsARB = cast(wglCreateContextAttribsARB_fna)glbindGetProcAddress("wglCreateContextAttribsARB"); 18417 } 18418 } 18419 } 18420 18421 static if (!SdpyIsUsingIVGLBinds) { 18422 18423 interface GL { 18424 extern(System) @nogc nothrow: 18425 18426 void glGetIntegerv(int, void*); 18427 void glMatrixMode(int); 18428 void glPushMatrix(); 18429 void glLoadIdentity(); 18430 void glOrtho(double, double, double, double, double, double); 18431 void glFrustum(double, double, double, double, double, double); 18432 18433 void glPopMatrix(); 18434 void glEnable(int); 18435 void glDisable(int); 18436 void glClear(int); 18437 void glBegin(int); 18438 void glVertex2f(float, float); 18439 void glVertex3f(float, float, float); 18440 void glEnd(); 18441 void glColor3b(byte, byte, byte); 18442 void glColor3ub(ubyte, ubyte, ubyte); 18443 void glColor4b(byte, byte, byte, byte); 18444 void glColor4ub(ubyte, ubyte, ubyte, ubyte); 18445 void glColor3i(int, int, int); 18446 void glColor3ui(uint, uint, uint); 18447 void glColor4i(int, int, int, int); 18448 void glColor4ui(uint, uint, uint, uint); 18449 void glColor3f(float, float, float); 18450 void glColor4f(float, float, float, float); 18451 void glTranslatef(float, float, float); 18452 void glScalef(float, float, float); 18453 version(X11) { 18454 void glSecondaryColor3b(byte, byte, byte); 18455 void glSecondaryColor3ub(ubyte, ubyte, ubyte); 18456 void glSecondaryColor3i(int, int, int); 18457 void glSecondaryColor3ui(uint, uint, uint); 18458 void glSecondaryColor3f(float, float, float); 18459 } 18460 18461 void glDrawElements(int, int, int, void*); 18462 18463 void glRotatef(float, float, float, float); 18464 18465 uint glGetError(); 18466 18467 void glDeleteTextures(int, uint*); 18468 18469 18470 void glRasterPos2i(int, int); 18471 void glDrawPixels(int, int, uint, uint, void*); 18472 void glClearColor(float, float, float, float); 18473 18474 18475 void glPixelStorei(uint, int); 18476 18477 void glGenTextures(uint, uint*); 18478 void glBindTexture(int, int); 18479 void glTexParameteri(uint, uint, int); 18480 void glTexParameterf(uint/*GLenum*/ target, uint/*GLenum*/ pname, float param); 18481 void glTexImage2D(int, int, int, int, int, int, int, int, in void*); 18482 void glTexSubImage2D(uint/*GLenum*/ target, int level, int xoffset, int yoffset, 18483 /*GLsizei*/int width, /*GLsizei*/int height, 18484 uint/*GLenum*/ format, uint/*GLenum*/ type, in void* pixels); 18485 void glTexEnvf(uint/*GLenum*/ target, uint/*GLenum*/ pname, float param); 18486 18487 void glLineWidth(int); 18488 18489 18490 void glTexCoord2f(float, float); 18491 void glVertex2i(int, int); 18492 void glBlendFunc (int, int); 18493 void glDepthFunc (int); 18494 void glViewport(int, int, int, int); 18495 18496 void glClearDepth(double); 18497 18498 void glReadBuffer(uint); 18499 void glReadPixels(int, int, int, int, int, int, void*); 18500 18501 void glFlush(); 18502 void glFinish(); 18503 18504 version(Windows) { 18505 BOOL wglCopyContext(HGLRC, HGLRC, UINT); 18506 HGLRC wglCreateContext(HDC); 18507 HGLRC wglCreateLayerContext(HDC, int); 18508 BOOL wglDeleteContext(HGLRC); 18509 BOOL wglDescribeLayerPlane(HDC, int, int, UINT, LPLAYERPLANEDESCRIPTOR); 18510 HGLRC wglGetCurrentContext(); 18511 HDC wglGetCurrentDC(); 18512 int wglGetLayerPaletteEntries(HDC, int, int, int, COLORREF*); 18513 PROC wglGetProcAddress(LPCSTR); 18514 BOOL wglMakeCurrent(HDC, HGLRC); 18515 BOOL wglRealizeLayerPalette(HDC, int, BOOL); 18516 int wglSetLayerPaletteEntries(HDC, int, int, int, const(COLORREF)*); 18517 BOOL wglShareLists(HGLRC, HGLRC); 18518 BOOL wglSwapLayerBuffers(HDC, UINT); 18519 BOOL wglUseFontBitmapsA(HDC, DWORD, DWORD, DWORD); 18520 BOOL wglUseFontBitmapsW(HDC, DWORD, DWORD, DWORD); 18521 BOOL wglUseFontOutlinesA(HDC, DWORD, DWORD, DWORD, FLOAT, FLOAT, int, LPGLYPHMETRICSFLOAT); 18522 BOOL wglUseFontOutlinesW(HDC, DWORD, DWORD, DWORD, FLOAT, FLOAT, int, LPGLYPHMETRICSFLOAT); 18523 } 18524 18525 } 18526 18527 interface GL3 { 18528 extern(System) @nogc nothrow: 18529 18530 void glGenVertexArrays(GLsizei, GLuint*); 18531 void glBindVertexArray(GLuint); 18532 void glDeleteVertexArrays(GLsizei, const(GLuint)*); 18533 void glGenerateMipmap(GLenum); 18534 void glBufferSubData(GLenum, GLintptr, GLsizeiptr, const(GLvoid)*); 18535 void glStencilMask(GLuint); 18536 void glStencilFunc(GLenum, GLint, GLuint); 18537 void glGetShaderInfoLog(GLuint, GLsizei, GLsizei*, GLchar*); 18538 void glGetProgramInfoLog(GLuint, GLsizei, GLsizei*, GLchar*); 18539 GLuint glCreateProgram(); 18540 GLuint glCreateShader(GLenum); 18541 void glShaderSource(GLuint, GLsizei, const(GLchar*)*, const(GLint)*); 18542 void glCompileShader(GLuint); 18543 void glGetShaderiv(GLuint, GLenum, GLint*); 18544 void glAttachShader(GLuint, GLuint); 18545 void glBindAttribLocation(GLuint, GLuint, const(GLchar)*); 18546 void glLinkProgram(GLuint); 18547 void glGetProgramiv(GLuint, GLenum, GLint*); 18548 void glDeleteProgram(GLuint); 18549 void glDeleteShader(GLuint); 18550 GLint glGetUniformLocation(GLuint, const(GLchar)*); 18551 void glGenBuffers(GLsizei, GLuint*); 18552 18553 void glUniform1f(GLint location, GLfloat v0); 18554 void glUniform2f(GLint location, GLfloat v0, GLfloat v1); 18555 void glUniform3f(GLint location, GLfloat v0, GLfloat v1, GLfloat v2); 18556 void glUniform4f(GLint location, GLfloat v0, GLfloat v1, GLfloat v2, GLfloat v3); 18557 void glUniform1i(GLint location, GLint v0); 18558 void glUniform2i(GLint location, GLint v0, GLint v1); 18559 void glUniform3i(GLint location, GLint v0, GLint v1, GLint v2); 18560 void glUniform4i(GLint location, GLint v0, GLint v1, GLint v2, GLint v3); 18561 void glUniform1ui(GLint location, GLuint v0); 18562 void glUniform2ui(GLint location, GLuint v0, GLuint v1); 18563 void glUniform3ui(GLint location, GLuint v0, GLuint v1, GLuint v2); 18564 void glUniform4ui(GLint location, GLuint v0, GLuint v1, GLuint v2, GLuint v3); 18565 void glUniform1fv(GLint location, GLsizei count, const GLfloat *value); 18566 void glUniform2fv(GLint location, GLsizei count, const GLfloat *value); 18567 void glUniform3fv(GLint location, GLsizei count, const GLfloat *value); 18568 void glUniform4fv(GLint location, GLsizei count, const GLfloat *value); 18569 void glUniform1iv(GLint location, GLsizei count, const GLint *value); 18570 void glUniform2iv(GLint location, GLsizei count, const GLint *value); 18571 void glUniform3iv(GLint location, GLsizei count, const GLint *value); 18572 void glUniform4iv(GLint location, GLsizei count, const GLint *value); 18573 void glUniform1uiv(GLint location, GLsizei count, const GLuint *value); 18574 void glUniform2uiv(GLint location, GLsizei count, const GLuint *value); 18575 void glUniform3uiv(GLint location, GLsizei count, const GLuint *value); 18576 void glUniform4uiv(GLint location, GLsizei count, const GLuint *value); 18577 void glUniformMatrix2fv(GLint location, GLsizei count, GLboolean transpose, const GLfloat *value); 18578 void glUniformMatrix3fv(GLint location, GLsizei count, GLboolean transpose, const GLfloat *value); 18579 void glUniformMatrix4fv(GLint location, GLsizei count, GLboolean transpose, const GLfloat *value); 18580 void glUniformMatrix2x3fv(GLint location, GLsizei count, GLboolean transpose, const GLfloat *value); 18581 void glUniformMatrix3x2fv(GLint location, GLsizei count, GLboolean transpose, const GLfloat *value); 18582 void glUniformMatrix2x4fv(GLint location, GLsizei count, GLboolean transpose, const GLfloat *value); 18583 void glUniformMatrix4x2fv(GLint location, GLsizei count, GLboolean transpose, const GLfloat *value); 18584 void glUniformMatrix3x4fv(GLint location, GLsizei count, GLboolean transpose, const GLfloat *value); 18585 void glUniformMatrix4x3fv(GLint location, GLsizei count, GLboolean transpose, const GLfloat *value); 18586 18587 void glColorMask(GLboolean, GLboolean, GLboolean, GLboolean); 18588 void glStencilOpSeparate(GLenum, GLenum, GLenum, GLenum); 18589 void glDrawArrays(GLenum, GLint, GLsizei); 18590 void glStencilOp(GLenum, GLenum, GLenum); 18591 void glUseProgram(GLuint); 18592 void glCullFace(GLenum); 18593 void glFrontFace(GLenum); 18594 void glActiveTexture(GLenum); 18595 void glBindBuffer(GLenum, GLuint); 18596 void glBufferData(GLenum, GLsizeiptr, const(void)*, GLenum); 18597 void glEnableVertexAttribArray(GLuint); 18598 void glVertexAttribPointer(GLuint, GLint, GLenum, GLboolean, GLsizei, const(void)*); 18599 void glUniform1i(GLint, GLint); 18600 void glUniform2fv(GLint, GLsizei, const(GLfloat)*); 18601 void glDisableVertexAttribArray(GLuint); 18602 void glDeleteBuffers(GLsizei, const(GLuint)*); 18603 void glBlendFuncSeparate(GLenum, GLenum, GLenum, GLenum); 18604 void glLogicOp (GLenum opcode); 18605 void glFramebufferTexture2D (GLenum target, GLenum attachment, GLenum textarget, GLuint texture, GLint level); 18606 void glDeleteFramebuffers (GLsizei n, const(GLuint)* framebuffers); 18607 void glGenFramebuffers (GLsizei n, GLuint* framebuffers); 18608 GLenum glCheckFramebufferStatus (GLenum target); 18609 void glBindFramebuffer (GLenum target, GLuint framebuffer); 18610 } 18611 18612 interface GL4 { 18613 extern(System) @nogc nothrow: 18614 18615 void glTextureSubImage2D(uint texture, int level, int xoffset, int yoffset, 18616 /*GLsizei*/int width, /*GLsizei*/int height, 18617 uint/*GLenum*/ format, uint/*GLenum*/ type, in void* pixels); 18618 } 18619 18620 interface GLU { 18621 extern(System) @nogc nothrow: 18622 18623 void gluLookAt(double, double, double, double, double, double, double, double, double); 18624 void gluPerspective(double, double, double, double); 18625 18626 char* gluErrorString(uint); 18627 } 18628 18629 18630 enum GL_RED = 0x1903; 18631 enum GL_ALPHA = 0x1906; 18632 18633 enum uint GL_FRONT = 0x0404; 18634 18635 enum uint GL_BLEND = 0x0be2; 18636 enum uint GL_LEQUAL = 0x0203; 18637 18638 18639 enum uint GL_RGB = 0x1907; 18640 enum uint GL_BGRA = 0x80e1; 18641 enum uint GL_RGBA = 0x1908; 18642 enum uint GL_TEXTURE_2D = 0x0DE1; 18643 enum uint GL_TEXTURE_MIN_FILTER = 0x2801; 18644 enum uint GL_NEAREST = 0x2600; 18645 enum uint GL_LINEAR = 0x2601; 18646 enum uint GL_TEXTURE_MAG_FILTER = 0x2800; 18647 enum uint GL_TEXTURE_WRAP_S = 0x2802; 18648 enum uint GL_TEXTURE_WRAP_T = 0x2803; 18649 enum uint GL_REPEAT = 0x2901; 18650 enum uint GL_CLAMP = 0x2900; 18651 enum uint GL_CLAMP_TO_EDGE = 0x812F; 18652 enum uint GL_CLAMP_TO_BORDER = 0x812D; 18653 enum uint GL_DECAL = 0x2101; 18654 enum uint GL_MODULATE = 0x2100; 18655 enum uint GL_TEXTURE_ENV = 0x2300; 18656 enum uint GL_TEXTURE_ENV_MODE = 0x2200; 18657 enum uint GL_REPLACE = 0x1E01; 18658 enum uint GL_LIGHTING = 0x0B50; 18659 enum uint GL_DITHER = 0x0BD0; 18660 18661 enum uint GL_NO_ERROR = 0; 18662 18663 18664 18665 enum int GL_VIEWPORT = 0x0BA2; 18666 enum int GL_MODELVIEW = 0x1700; 18667 enum int GL_TEXTURE = 0x1702; 18668 enum int GL_PROJECTION = 0x1701; 18669 enum int GL_DEPTH_TEST = 0x0B71; 18670 18671 enum int GL_COLOR_BUFFER_BIT = 0x00004000; 18672 enum int GL_ACCUM_BUFFER_BIT = 0x00000200; 18673 enum int GL_DEPTH_BUFFER_BIT = 0x00000100; 18674 enum uint GL_STENCIL_BUFFER_BIT = 0x00000400; 18675 18676 enum int GL_POINTS = 0x0000; 18677 enum int GL_LINES = 0x0001; 18678 enum int GL_LINE_LOOP = 0x0002; 18679 enum int GL_LINE_STRIP = 0x0003; 18680 enum int GL_TRIANGLES = 0x0004; 18681 enum int GL_TRIANGLE_STRIP = 5; 18682 enum int GL_TRIANGLE_FAN = 6; 18683 enum int GL_QUADS = 7; 18684 enum int GL_QUAD_STRIP = 8; 18685 enum int GL_POLYGON = 9; 18686 18687 alias GLvoid = void; 18688 alias GLboolean = ubyte; 18689 alias GLuint = uint; 18690 alias GLenum = uint; 18691 alias GLchar = char; 18692 alias GLsizei = int; 18693 alias GLfloat = float; 18694 alias GLintptr = size_t; 18695 alias GLsizeiptr = ptrdiff_t; 18696 18697 18698 enum uint GL_INVALID_ENUM = 0x0500; 18699 18700 enum uint GL_ZERO = 0; 18701 enum uint GL_ONE = 1; 18702 18703 enum uint GL_BYTE = 0x1400; 18704 enum uint GL_UNSIGNED_BYTE = 0x1401; 18705 enum uint GL_SHORT = 0x1402; 18706 enum uint GL_UNSIGNED_SHORT = 0x1403; 18707 enum uint GL_INT = 0x1404; 18708 enum uint GL_UNSIGNED_INT = 0x1405; 18709 enum uint GL_FLOAT = 0x1406; 18710 enum uint GL_2_BYTES = 0x1407; 18711 enum uint GL_3_BYTES = 0x1408; 18712 enum uint GL_4_BYTES = 0x1409; 18713 enum uint GL_DOUBLE = 0x140A; 18714 18715 enum uint GL_STREAM_DRAW = 0x88E0; 18716 18717 enum uint GL_CCW = 0x0901; 18718 18719 enum uint GL_STENCIL_TEST = 0x0B90; 18720 enum uint GL_SCISSOR_TEST = 0x0C11; 18721 18722 enum uint GL_EQUAL = 0x0202; 18723 enum uint GL_NOTEQUAL = 0x0205; 18724 18725 enum uint GL_ALWAYS = 0x0207; 18726 enum uint GL_KEEP = 0x1E00; 18727 18728 enum uint GL_INCR = 0x1E02; 18729 18730 enum uint GL_INCR_WRAP = 0x8507; 18731 enum uint GL_DECR_WRAP = 0x8508; 18732 18733 enum uint GL_CULL_FACE = 0x0B44; 18734 enum uint GL_BACK = 0x0405; 18735 18736 enum uint GL_FRAGMENT_SHADER = 0x8B30; 18737 enum uint GL_VERTEX_SHADER = 0x8B31; 18738 18739 enum uint GL_COMPILE_STATUS = 0x8B81; 18740 enum uint GL_LINK_STATUS = 0x8B82; 18741 18742 enum uint GL_ELEMENT_ARRAY_BUFFER = 0x8893; 18743 18744 enum uint GL_STATIC_DRAW = 0x88E4; 18745 18746 enum uint GL_UNPACK_ALIGNMENT = 0x0CF5; 18747 enum uint GL_UNPACK_ROW_LENGTH = 0x0CF2; 18748 enum uint GL_UNPACK_SKIP_PIXELS = 0x0CF4; 18749 enum uint GL_UNPACK_SKIP_ROWS = 0x0CF3; 18750 18751 enum uint GL_GENERATE_MIPMAP = 0x8191; 18752 enum uint GL_LINEAR_MIPMAP_LINEAR = 0x2703; 18753 18754 enum uint GL_TEXTURE0 = 0x84C0U; 18755 enum uint GL_TEXTURE1 = 0x84C1U; 18756 18757 enum uint GL_ARRAY_BUFFER = 0x8892; 18758 18759 enum uint GL_SRC_COLOR = 0x0300; 18760 enum uint GL_ONE_MINUS_SRC_COLOR = 0x0301; 18761 enum uint GL_SRC_ALPHA = 0x0302; 18762 enum uint GL_ONE_MINUS_SRC_ALPHA = 0x0303; 18763 enum uint GL_DST_ALPHA = 0x0304; 18764 enum uint GL_ONE_MINUS_DST_ALPHA = 0x0305; 18765 enum uint GL_DST_COLOR = 0x0306; 18766 enum uint GL_ONE_MINUS_DST_COLOR = 0x0307; 18767 enum uint GL_SRC_ALPHA_SATURATE = 0x0308; 18768 18769 enum uint GL_INVERT = 0x150AU; 18770 18771 enum uint GL_DEPTH_STENCIL = 0x84F9U; 18772 enum uint GL_UNSIGNED_INT_24_8 = 0x84FAU; 18773 18774 enum uint GL_FRAMEBUFFER = 0x8D40U; 18775 enum uint GL_COLOR_ATTACHMENT0 = 0x8CE0U; 18776 enum uint GL_DEPTH_STENCIL_ATTACHMENT = 0x821AU; 18777 18778 enum uint GL_FRAMEBUFFER_COMPLETE = 0x8CD5U; 18779 enum uint GL_FRAMEBUFFER_INCOMPLETE_ATTACHMENT = 0x8CD6U; 18780 enum uint GL_FRAMEBUFFER_INCOMPLETE_MISSING_ATTACHMENT = 0x8CD7U; 18781 enum uint GL_FRAMEBUFFER_INCOMPLETE_DIMENSIONS = 0x8CD9U; 18782 enum uint GL_FRAMEBUFFER_UNSUPPORTED = 0x8CDDU; 18783 18784 enum uint GL_COLOR_LOGIC_OP = 0x0BF2U; 18785 enum uint GL_CLEAR = 0x1500U; 18786 enum uint GL_COPY = 0x1503U; 18787 enum uint GL_XOR = 0x1506U; 18788 18789 enum uint GL_FRAMEBUFFER_BINDING = 0x8CA6U; 18790 18791 enum uint GL_TEXTURE_LOD_BIAS = 0x8501; 18792 18793 } 18794 } 18795 18796 /++ 18797 History: 18798 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. 18799 +/ 18800 __gshared bool gluSuccessfullyLoaded = true; 18801 18802 version(without_opengl) {} else { 18803 static if(!SdpyIsUsingIVGLBinds) { 18804 version(Windows) { 18805 mixin DynamicLoad!(GL, "opengl32", 1, openGlLibrariesSuccessfullyLoaded) gl; 18806 mixin DynamicLoad!(GLU, "glu32", 1, gluSuccessfullyLoaded) glu; 18807 } else { 18808 mixin DynamicLoad!(GL, "GL", 1, openGlLibrariesSuccessfullyLoaded) gl; 18809 mixin DynamicLoad!(GLU, "GLU", 3, gluSuccessfullyLoaded) glu; 18810 } 18811 mixin DynamicLoadSupplementalOpenGL!(GL3) gl3; 18812 18813 18814 shared static this() { 18815 gl.loadDynamicLibrary(); 18816 18817 // FIXME: this is NOT actually required and should NOT fail if it is not loaded 18818 // unless those functions are actually used 18819 // go to mark b openGlLibrariesSuccessfullyLoaded = false; 18820 glu.loadDynamicLibrary(); 18821 } 18822 } 18823 } 18824 18825 /++ 18826 Convenience method for converting D arrays to opengl buffer data 18827 18828 I would LOVE to overload it with the original glBufferData, but D won't 18829 let me since glBufferData is a function pointer :( 18830 18831 Added: August 25, 2020 (version 8.5) 18832 +/ 18833 version(without_opengl) {} else 18834 void glBufferDataSlice(GLenum target, const(void[]) data, GLenum usage) { 18835 glBufferData(target, data.length, data.ptr, usage); 18836 } 18837 18838 /+ 18839 /++ 18840 A matrix for simple uses that easily integrates with [OpenGlShader]. 18841 18842 Might not be useful to you since it only as some simple functions and 18843 probably isn't that fast. 18844 18845 Note it uses an inline static array for its storage, so copying it 18846 may be expensive. 18847 +/ 18848 struct BasicMatrix(int columns, int rows, T = float) { 18849 import core.stdc.math; 18850 18851 T[columns * rows] data = 0.0; 18852 18853 /++ 18854 Basic operations that operate *in place*. 18855 +/ 18856 void translate() { 18857 18858 } 18859 18860 /// ditto 18861 void scale() { 18862 18863 } 18864 18865 /// ditto 18866 void rotate() { 18867 18868 } 18869 18870 /++ 18871 18872 +/ 18873 static if(columns == rows) 18874 static BasicMatrix identity() { 18875 BasicMatrix m; 18876 foreach(i; 0 .. columns) 18877 data[0 + i + i * columns] = 1.0; 18878 return m; 18879 } 18880 18881 static BasicMatrix ortho() { 18882 return BasicMatrix.init; 18883 } 18884 } 18885 +/ 18886 18887 /++ 18888 Convenience class for using opengl shaders. 18889 18890 Ensure that you've loaded opengl 3+ and set your active 18891 context before trying to use this. 18892 18893 Added: August 25, 2020 (version 8.5) 18894 +/ 18895 version(without_opengl) {} else 18896 final class OpenGlShader { 18897 private int shaderProgram_; 18898 private @property void shaderProgram(int a) { 18899 shaderProgram_ = a; 18900 } 18901 /// Get the program ID for use in OpenGL functions. 18902 public @property int shaderProgram() { 18903 return shaderProgram_; 18904 } 18905 18906 /++ 18907 18908 +/ 18909 static struct Source { 18910 uint type; /// GL_FRAGMENT_SHADER, GL_VERTEX_SHADER, etc. 18911 string code; /// 18912 } 18913 18914 /++ 18915 Helper method to just compile some shader code and check for errors 18916 while you do glCreateShader, etc. on the outside yourself. 18917 18918 This just does `glShaderSource` and `glCompileShader` for the given code. 18919 18920 If you the OpenGlShader class constructor, you never need to call this yourself. 18921 +/ 18922 static void compile(int sid, Source code) { 18923 const(char)*[1] buffer; 18924 int[1] lengthBuffer; 18925 18926 buffer[0] = code.code.ptr; 18927 lengthBuffer[0] = cast(int) code.code.length; 18928 18929 glShaderSource(sid, cast(int) buffer.length, buffer.ptr, lengthBuffer.ptr); 18930 glCompileShader(sid); 18931 18932 int success; 18933 glGetShaderiv(sid, GL_COMPILE_STATUS, &success); 18934 if(!success) { 18935 char[512] info; 18936 int len; 18937 glGetShaderInfoLog(sid, info.length, &len, info.ptr); 18938 18939 throw new Exception("Shader compile failure: " ~ cast(immutable) info[0 .. len]); 18940 } 18941 } 18942 18943 /++ 18944 Calls `glLinkProgram` and throws if error a occurs. 18945 18946 If you the OpenGlShader class constructor, you never need to call this yourself. 18947 +/ 18948 static void link(int shaderProgram) { 18949 glLinkProgram(shaderProgram); 18950 int success; 18951 glGetProgramiv(shaderProgram, GL_LINK_STATUS, &success); 18952 if(!success) { 18953 char[512] info; 18954 int len; 18955 glGetProgramInfoLog(shaderProgram, info.length, &len, info.ptr); 18956 18957 throw new Exception("Shader link failure: " ~ cast(immutable) info[0 .. len]); 18958 } 18959 } 18960 18961 /++ 18962 Constructs the shader object by calling `glCreateProgram`, then 18963 compiling each given [Source], and finally, linking them together. 18964 18965 Throws: on compile or link failure. 18966 +/ 18967 this(Source[] codes...) { 18968 shaderProgram = glCreateProgram(); 18969 18970 int[16] shadersBufferStack; 18971 18972 int[] shadersBuffer = codes.length <= shadersBufferStack.length ? 18973 shadersBufferStack[0 .. codes.length] : 18974 new int[](codes.length); 18975 18976 foreach(idx, code; codes) { 18977 shadersBuffer[idx] = glCreateShader(code.type); 18978 18979 compile(shadersBuffer[idx], code); 18980 18981 glAttachShader(shaderProgram, shadersBuffer[idx]); 18982 } 18983 18984 link(shaderProgram); 18985 18986 foreach(s; shadersBuffer) 18987 glDeleteShader(s); 18988 } 18989 18990 /// Calls `glUseProgram(this.shaderProgram)` 18991 void use() { 18992 glUseProgram(this.shaderProgram); 18993 } 18994 18995 /// Deletes the program. 18996 void delete_() { 18997 glDeleteProgram(shaderProgram); 18998 shaderProgram = 0; 18999 } 19000 19001 /++ 19002 [OpenGlShader.uniforms].name gives you one of these. 19003 19004 You can get the id out of it or just assign 19005 +/ 19006 static struct Uniform { 19007 /// the id passed to glUniform* 19008 int id; 19009 19010 /// Assigns the 4 floats. You will probably have to call this via the .opAssign name 19011 void opAssign(float x, float y, float z, float w) { 19012 if(id != -1) 19013 glUniform4f(id, x, y, z, w); 19014 } 19015 19016 void opAssign(float x) { 19017 if(id != -1) 19018 glUniform1f(id, x); 19019 } 19020 19021 void opAssign(float x, float y) { 19022 if(id != -1) 19023 glUniform2f(id, x, y); 19024 } 19025 19026 void opAssign(T)(T t) { 19027 t.glUniform(id); 19028 } 19029 } 19030 19031 static struct UniformsHelper { 19032 OpenGlShader _shader; 19033 19034 @property Uniform opDispatch(string name)() { 19035 auto i = glGetUniformLocation(_shader.shaderProgram, name.ptr); 19036 // FIXME: decide what to do here; the exception is liable to be swallowed by the event syste 19037 //if(i == -1) 19038 //throw new Exception("Could not find uniform " ~ name); 19039 return Uniform(i); 19040 } 19041 19042 @property void opDispatch(string name, T)(T t) { 19043 Uniform f = this.opDispatch!name; 19044 t.glUniform(f); 19045 } 19046 } 19047 19048 /++ 19049 Gives access to the uniforms through dot access. 19050 `OpenGlShader.Uniform = shader.uniforms.foo; // calls glGetUniformLocation(this, "foo"); 19051 +/ 19052 @property UniformsHelper uniforms() { return UniformsHelper(this); } 19053 } 19054 19055 version(without_opengl) {} else { 19056 /++ 19057 A static container of experimental types and value constructors for opengl 3+ shaders. 19058 19059 19060 You can declare variables like: 19061 19062 ``` 19063 OGL.vec3f something; 19064 ``` 19065 19066 But generally it would be used with [OpenGlShader]'s uniform helpers like 19067 19068 ``` 19069 shader.uniforms.mouse = OGL.vec(mouseX, mouseY); // or OGL.vec2f if you want to be more specific 19070 ``` 19071 19072 This is still extremely experimental, not very useful at this point, and thus subject to change at random. 19073 19074 19075 History: 19076 Added December 7, 2021. Not yet stable. 19077 +/ 19078 final class OGL { 19079 static: 19080 19081 private template typeFromSpecifier(string specifier) { 19082 static if(specifier == "f") 19083 alias typeFromSpecifier = GLfloat; 19084 else static if(specifier == "i") 19085 alias typeFromSpecifier = GLint; 19086 else static if(specifier == "ui") 19087 alias typeFromSpecifier = GLuint; 19088 else static assert(0, "I don't know this ogl type suffix " ~ specifier); 19089 } 19090 19091 private template CommonType(T...) { 19092 static if(T.length == 1) 19093 alias CommonType = T[0]; 19094 else static if(is(typeof(true ? T[0].init : T[1].init) C)) 19095 alias CommonType = CommonType!(C, T[2 .. $]); 19096 } 19097 19098 private template typesToSpecifier(T...) { 19099 static if(is(CommonType!T == float)) 19100 enum typesToSpecifier = "f"; 19101 else static if(is(CommonType!T == int)) 19102 enum typesToSpecifier = "i"; 19103 else static if(is(CommonType!T == uint)) 19104 enum typesToSpecifier = "ui"; 19105 else static assert(0, "I can't find a gl type suffix for common type " ~ CommonType!T.stringof); 19106 } 19107 19108 private template genNames(size_t dim, size_t dim2 = 0) { 19109 string helper() { 19110 string s; 19111 if(dim2) { 19112 s ~= "type["~(dim + '0')~"]["~(dim2 + '0')~"] matrix;"; 19113 } else { 19114 if(dim > 0) s ~= "type x = 0;"; 19115 if(dim > 1) s ~= "type y = 0;"; 19116 if(dim > 2) s ~= "type z = 0;"; 19117 if(dim > 3) s ~= "type w = 0;"; 19118 } 19119 return s; 19120 } 19121 19122 enum genNames = helper(); 19123 } 19124 19125 // there's vec, arrays of vec, mat, and arrays of mat 19126 template opDispatch(string name) 19127 if(name.length > 4 && (name[0 .. 3] == "vec" || name[0 .. 3] == "mat")) 19128 { 19129 static if(name[4] == 'x') { 19130 enum dimX = cast(int) (name[3] - '0'); 19131 static assert(dimX > 0 && dimX <= 4, "Bad dimension for OGL X type " ~ name[3]); 19132 19133 enum dimY = cast(int) (name[5] - '0'); 19134 static assert(dimY > 0 && dimY <= 4, "Bad dimension for OGL Y type " ~ name[5]); 19135 19136 enum isArray = name[$ - 1] == 'v'; 19137 enum typeSpecifier = isArray ? name[6 .. $ - 1] : name[6 .. $]; 19138 alias type = typeFromSpecifier!typeSpecifier; 19139 } else { 19140 enum dim = cast(int) (name[3] - '0'); 19141 static assert(dim > 0 && dim <= 4, "Bad dimension for OGL type " ~ name[3]); 19142 enum isArray = name[$ - 1] == 'v'; 19143 enum typeSpecifier = isArray ? name[4 .. $ - 1] : name[4 .. $]; 19144 alias type = typeFromSpecifier!typeSpecifier; 19145 } 19146 19147 align(1) 19148 struct opDispatch { 19149 align(1): 19150 static if(name[4] == 'x') 19151 mixin(genNames!(dimX, dimY)); 19152 else 19153 mixin(genNames!dim); 19154 19155 private void glUniform(OpenGlShader.Uniform assignTo) { 19156 glUniform(assignTo.id); 19157 } 19158 private void glUniform(int assignTo) { 19159 static if(name[4] == 'x') { 19160 // FIXME 19161 pragma(msg, "This matrix uniform helper has never been tested!!!!"); 19162 mixin("glUniformMatrix" ~ name[3 .. $] ~ "v")(assignTo, dimX * dimY, false, this.matrix.ptr); 19163 } else 19164 mixin("glUniform" ~ name[3 .. $])(assignTo, this.tupleof); 19165 } 19166 } 19167 } 19168 19169 auto vec(T...)(T members) { 19170 return typeof(this).opDispatch!("vec" ~ toInternal!string(cast(int) T.length)~ typesToSpecifier!T)(members); 19171 } 19172 } 19173 } 19174 19175 version(linux) { 19176 version(with_eventloop) {} else { 19177 private int epollFd = -1; 19178 void prepareEventLoop() { 19179 if(epollFd != -1) 19180 return; // already initialized, no need to do it again 19181 import ep = core.sys.linux.epoll; 19182 19183 epollFd = ep.epoll_create1(ep.EPOLL_CLOEXEC); 19184 if(epollFd == -1) 19185 throw new Exception("epoll create failure"); 19186 } 19187 } 19188 } else version(Posix) { 19189 void prepareEventLoop() {} 19190 } 19191 19192 version(X11) { 19193 import core.stdc.locale : LC_ALL; // rdmd fix 19194 __gshared bool sdx_isUTF8Locale; 19195 19196 // This whole crap is used to initialize X11 locale, so that you can use XIM methods later. 19197 // Yes, there are people with non-utf locale (it's me, Ketmar!), but XIM (composing) will 19198 // not work right if app/X11 locale is not utf. This sux. That's why all that "utf detection" 19199 // anal magic is here. I (Ketmar) hope you like it. 19200 // We will use `sdx_isUTF8Locale` on XIM creation to enforce UTF-8 locale, so XCompose will 19201 // always return correct unicode symbols. The detection is here 'cause user can change locale 19202 // later. 19203 19204 // NOTE: IT IS VERY IMPORTANT THAT THIS BE THE LAST STATIC CTOR OF THE FILE since it tests librariesSuccessfullyLoaded 19205 shared static this () { 19206 if(!librariesSuccessfullyLoaded) 19207 return; 19208 19209 import core.stdc.locale : setlocale, LC_ALL, LC_CTYPE; 19210 19211 // this doesn't hurt; it may add some locking, but the speed is still 19212 // allows doing 60 FPS videogames; also, ignore the result, as most 19213 // users will probably won't do mulththreaded X11 anyway (and I (ketmar) 19214 // never seen this failing). 19215 if (XInitThreads() == 0) { import core.stdc.stdio; fprintf(stderr, "XInitThreads() failed!\n"); } 19216 19217 setlocale(LC_ALL, ""); 19218 // check if out locale is UTF-8 19219 auto lct = setlocale(LC_CTYPE, null); 19220 if (lct is null) { 19221 sdx_isUTF8Locale = false; 19222 } else { 19223 for (size_t idx = 0; lct[idx] && lct[idx+1] && lct[idx+2]; ++idx) { 19224 if ((lct[idx+0] == 'u' || lct[idx+0] == 'U') && 19225 (lct[idx+1] == 't' || lct[idx+1] == 'T') && 19226 (lct[idx+2] == 'f' || lct[idx+2] == 'F')) 19227 { 19228 sdx_isUTF8Locale = true; 19229 break; 19230 } 19231 } 19232 } 19233 //{ import core.stdc.stdio : stderr, fprintf; fprintf(stderr, "UTF8: %s\n", sdx_isUTF8Locale ? "tan".ptr : "ona".ptr); } 19234 } 19235 } 19236 19237 class ExperimentalTextComponent2 { 19238 /+ 19239 Stage 1: get it working monospace 19240 Stage 2: use proportional font 19241 Stage 3: allow changes in inline style 19242 Stage 4: allow new fonts and sizes in the middle 19243 Stage 5: optimize gap buffer 19244 Stage 6: optimize layout 19245 Stage 7: word wrap 19246 Stage 8: justification 19247 Stage 9: editing, selection, etc. 19248 19249 Operations: 19250 insert text 19251 overstrike text 19252 select 19253 cut 19254 modify 19255 +/ 19256 19257 /++ 19258 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. 19259 +/ 19260 this(SimpleWindow window) { 19261 this.window = window; 19262 } 19263 19264 private SimpleWindow window; 19265 19266 19267 /++ 19268 When you render a [ComponentInFlow], it returns an arbitrary number of these interfaces 19269 representing the internal parts. The first pass is focused on the x parameter, then the 19270 renderer is responsible for going back to the parts in the current line and calling 19271 adjustDownForAscent to change the y params. 19272 +/ 19273 static interface ComponentRenderHelper { 19274 19275 /+ 19276 When you do an edit, possibly stuff on the same line previously need to move (to adjust 19277 the baseline), stuff subsequent needs to move (adjust x) and possibly stuff below needs 19278 to move (adjust y to make room for new line) until you get back to the same position, 19279 then you can stop - if one thing is unchanged, nothing after it is changed too. 19280 19281 Word wrap might change this as if can rewrap tons of stuff, but the same idea applies, 19282 once you reach something that is unchanged, you can stop. 19283 +/ 19284 19285 void adjustDownForAscent(int amount); // at the end of the line it needs to do these 19286 19287 int ascent() const; 19288 int descent() const; 19289 19290 int advance() const; 19291 19292 bool endsWithExplititLineBreak() const; 19293 } 19294 19295 static interface RenderResult { 19296 /++ 19297 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. 19298 +/ 19299 void popFront(); 19300 @property bool empty() const; 19301 @property ComponentRenderHelper front() const; 19302 19303 void repositionForNextLine(Point baseline, int availableWidth); 19304 } 19305 19306 static interface ComponentInFlow { 19307 void draw(ScreenPainter painter); 19308 //RenderResult render(Point baseline, int availableWidth); // FIXME: it needs to be able to say "my cache is good, nothing different" 19309 19310 bool startsWithExplicitLineBreak() const; 19311 } 19312 19313 static class TextFlowComponent : ComponentInFlow { 19314 bool startsWithExplicitLineBreak() const { return false; } // FIXME: if it is block this can return true 19315 19316 Color foreground; 19317 Color background; 19318 19319 OperatingSystemFont font; // should NEVER be null 19320 19321 ubyte attributes; // underline, strike through, display on new block 19322 19323 version(Windows) 19324 const(wchar)[] content; 19325 else 19326 const(char)[] content; // this should NEVER have a newline, except at the end 19327 19328 RenderedComponent[] rendered; // entirely controlled by [rerender] 19329 19330 // could prolly put some spacing around it too like margin / padding 19331 19332 this(Color f, Color b, OperatingSystemFont font, ubyte attr, const(char)[] c) 19333 in { assert(font !is null); 19334 assert(!font.isNull); } 19335 do 19336 { 19337 this.foreground = f; 19338 this.background = b; 19339 this.font = font; 19340 19341 this.attributes = attr; 19342 version(Windows) { 19343 auto conversionFlags = 0;//WindowsStringConversionFlags.convertNewLines; 19344 auto sz = sizeOfConvertedWstring(c, conversionFlags); 19345 auto buffer = new wchar[](sz); 19346 this.content = makeWindowsString(c, buffer, conversionFlags); 19347 } else { 19348 this.content = c.dup; 19349 } 19350 } 19351 19352 void draw(ScreenPainter painter) { 19353 painter.setFont(this.font); 19354 painter.outlineColor = this.foreground; 19355 painter.fillColor = Color.transparent; 19356 foreach(rendered; this.rendered) { 19357 // the component works in term of baseline, 19358 // but the painter works in term of upper left bounding box 19359 // so need to translate that 19360 19361 if(this.background.a) { 19362 painter.fillColor = this.background; 19363 painter.outlineColor = this.background; 19364 19365 painter.drawRectangle(Point(rendered.startX, rendered.startY - this.font.ascent), Size(rendered.width, this.font.height)); 19366 19367 painter.outlineColor = this.foreground; 19368 painter.fillColor = Color.transparent; 19369 } 19370 19371 painter.drawText(Point(rendered.startX, rendered.startY - this.font.ascent), rendered.slice); 19372 19373 // FIXME: strike through, underline, highlight selection, etc. 19374 } 19375 } 19376 } 19377 19378 // I could split the parts into words on render 19379 // for easier word-wrap, each one being an unbreakable "inline-block" 19380 private TextFlowComponent[] parts; 19381 private int needsRerenderFrom; 19382 19383 void addPart(Color f, Color b, OperatingSystemFont font, ubyte attr, const(char)[] c) { 19384 // FIXME: needsRerenderFrom. Basically if the bounding box and baseline is the same as the previous thing, it can prolly just stop. 19385 parts ~= new TextFlowComponent(f, b, font, attr, c); 19386 } 19387 19388 static struct RenderedComponent { 19389 int startX; 19390 int startY; 19391 short width; 19392 // 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! 19393 // for individual chars in here you've gotta process on demand 19394 version(Windows) 19395 const(wchar)[] slice; 19396 else 19397 const(char)[] slice; 19398 } 19399 19400 19401 void rerender(Rectangle boundingBox) { 19402 Point baseline = boundingBox.upperLeft; 19403 19404 this.boundingBox.left = boundingBox.left; 19405 this.boundingBox.top = boundingBox.top; 19406 19407 auto remainingParts = parts; 19408 19409 int largestX; 19410 19411 19412 foreach(part; parts) 19413 part.font.prepareContext(window); 19414 scope(exit) 19415 foreach(part; parts) 19416 part.font.releaseContext(); 19417 19418 calculateNextLine: 19419 19420 int nextLineHeight = 0; 19421 int nextBiggestDescent = 0; 19422 19423 foreach(part; remainingParts) { 19424 auto height = part.font.ascent; 19425 if(height > nextLineHeight) 19426 nextLineHeight = height; 19427 if(part.font.descent > nextBiggestDescent) 19428 nextBiggestDescent = part.font.descent; 19429 if(part.content.length && part.content[$-1] == '\n') 19430 break; 19431 } 19432 19433 baseline.y += nextLineHeight; 19434 auto lineStart = baseline; 19435 19436 while(remainingParts.length) { 19437 remainingParts[0].rendered = null; 19438 19439 bool eol; 19440 if(remainingParts[0].content.length && remainingParts[0].content[$-1] == '\n') 19441 eol = true; 19442 19443 // FIXME: word wrap 19444 auto font = remainingParts[0].font; 19445 auto slice = remainingParts[0].content[0 .. $ - (eol ? 1 : 0)]; 19446 auto width = font.stringWidth(slice, window); 19447 remainingParts[0].rendered ~= RenderedComponent(baseline.x, baseline.y, cast(short) width, slice); 19448 19449 remainingParts = remainingParts[1 .. $]; 19450 baseline.x += width; 19451 19452 if(eol) { 19453 baseline.y += nextBiggestDescent; 19454 if(baseline.x > largestX) 19455 largestX = baseline.x; 19456 baseline.x = lineStart.x; 19457 goto calculateNextLine; 19458 } 19459 } 19460 19461 if(baseline.x > largestX) 19462 largestX = baseline.x; 19463 19464 this.boundingBox.right = largestX; 19465 this.boundingBox.bottom = baseline.y; 19466 } 19467 19468 // you must call rerender first! 19469 void draw(ScreenPainter painter) { 19470 foreach(part; parts) { 19471 part.draw(painter); 19472 } 19473 } 19474 19475 struct IdentifyResult { 19476 TextFlowComponent part; 19477 int charIndexInPart; 19478 int totalCharIndex = -1; // if this is -1, it just means the end 19479 19480 Rectangle boundingBox; 19481 } 19482 19483 IdentifyResult identify(Point pt, bool exact = false) { 19484 if(parts.length == 0) 19485 return IdentifyResult(null, 0); 19486 19487 if(pt.y < boundingBox.top) { 19488 if(exact) 19489 return IdentifyResult(null, 1); 19490 return IdentifyResult(parts[0], 0); 19491 } 19492 if(pt.y > boundingBox.bottom) { 19493 if(exact) 19494 return IdentifyResult(null, 2); 19495 return IdentifyResult(parts[$-1], cast(int) parts[$-1].content.length); 19496 } 19497 19498 int tci = 0; 19499 19500 // I should probably like binary search this or something... 19501 foreach(ref part; parts) { 19502 foreach(rendered; part.rendered) { 19503 auto rect = Rectangle(rendered.startX, rendered.startY - part.font.ascent, rendered.startX + rendered.width, rendered.startY + part.font.descent); 19504 if(rect.contains(pt)) { 19505 auto x = pt.x - rendered.startX; 19506 auto estimatedIdx = x / part.font.averageWidth; 19507 19508 if(estimatedIdx < 0) 19509 estimatedIdx = 0; 19510 19511 if(estimatedIdx > rendered.slice.length) 19512 estimatedIdx = cast(int) rendered.slice.length; 19513 19514 int idx; 19515 int x1, x2; 19516 if(part.font.isMonospace) { 19517 auto w = part.font.averageWidth; 19518 if(!exact && x > (estimatedIdx + 1) * w) 19519 return IdentifyResult(null, 4); 19520 idx = estimatedIdx; 19521 x1 = idx * w; 19522 x2 = (idx + 1) * w; 19523 } else { 19524 idx = estimatedIdx; 19525 19526 part.font.prepareContext(window); 19527 scope(exit) part.font.releaseContext(); 19528 19529 // int iterations; 19530 19531 while(true) { 19532 // iterations++; 19533 x1 = idx ? part.font.stringWidth(rendered.slice[0 .. idx - 1]) : 0; 19534 x2 = part.font.stringWidth(rendered.slice[0 .. idx]); // should be the maximum since `averageWidth` kinda lies. 19535 19536 x1 += rendered.startX; 19537 x2 += rendered.startX; 19538 19539 if(pt.x < x1) { 19540 if(idx == 0) { 19541 if(exact) 19542 return IdentifyResult(null, 6); 19543 else 19544 break; 19545 } 19546 idx--; 19547 } else if(pt.x > x2) { 19548 idx++; 19549 if(idx > rendered.slice.length) { 19550 if(exact) 19551 return IdentifyResult(null, 5); 19552 else 19553 break; 19554 } 19555 } else if(pt.x >= x1 && pt.x <= x2) { 19556 if(idx) 19557 idx--; // point it at the original index 19558 break; // we fit 19559 } 19560 } 19561 19562 // import std.stdio; writeln(iterations) 19563 } 19564 19565 19566 return IdentifyResult(part, idx, tci + idx, Rectangle(x1, rect.top, x2, rect.bottom)); // FIXME: utf-8? 19567 } 19568 } 19569 tci += cast(int) part.content.length; // FIXME: utf-8? 19570 } 19571 return IdentifyResult(null, 3); 19572 } 19573 19574 Rectangle boundingBox; // only set after [rerender] 19575 19576 // text will be positioned around the exclusion zone 19577 static struct ExclusionZone { 19578 19579 } 19580 19581 ExclusionZone[] exclusionZones; 19582 } 19583 19584 19585 // Don't use this yet. When I'm happy with it, I will move it to the 19586 // regular module namespace. 19587 mixin template ExperimentalTextComponent() { 19588 19589 static: 19590 19591 alias Rectangle = arsd.color.Rectangle; 19592 19593 struct ForegroundColor { 19594 Color color; 19595 alias color this; 19596 19597 this(Color c) { 19598 color = c; 19599 } 19600 19601 this(int r, int g, int b, int a = 255) { 19602 color = Color(r, g, b, a); 19603 } 19604 19605 static ForegroundColor opDispatch(string s)() if(__traits(compiles, ForegroundColor(mixin("Color." ~ s)))) { 19606 return ForegroundColor(mixin("Color." ~ s)); 19607 } 19608 } 19609 19610 struct BackgroundColor { 19611 Color color; 19612 alias color this; 19613 19614 this(Color c) { 19615 color = c; 19616 } 19617 19618 this(int r, int g, int b, int a = 255) { 19619 color = Color(r, g, b, a); 19620 } 19621 19622 static BackgroundColor opDispatch(string s)() if(__traits(compiles, BackgroundColor(mixin("Color." ~ s)))) { 19623 return BackgroundColor(mixin("Color." ~ s)); 19624 } 19625 } 19626 19627 static class InlineElement { 19628 string text; 19629 19630 BlockElement containingBlock; 19631 19632 Color color = Color.black; 19633 Color backgroundColor = Color.transparent; 19634 ushort styles; 19635 19636 string font; 19637 int fontSize; 19638 19639 int lineHeight; 19640 19641 void* identifier; 19642 19643 Rectangle boundingBox; 19644 int[] letterXs; // FIXME: maybe i should do bounding boxes for every character 19645 19646 bool isMergeCompatible(InlineElement other) { 19647 return 19648 containingBlock is other.containingBlock && 19649 color == other.color && 19650 backgroundColor == other.backgroundColor && 19651 styles == other.styles && 19652 font == other.font && 19653 fontSize == other.fontSize && 19654 lineHeight == other.lineHeight && 19655 true; 19656 } 19657 19658 int xOfIndex(size_t index) { 19659 if(index < letterXs.length) 19660 return letterXs[index]; 19661 else 19662 return boundingBox.right; 19663 } 19664 19665 InlineElement clone() { 19666 auto ie = new InlineElement(); 19667 ie.tupleof = this.tupleof; 19668 return ie; 19669 } 19670 19671 InlineElement getPreviousInlineElement() { 19672 InlineElement prev = null; 19673 foreach(ie; this.containingBlock.parts) { 19674 if(ie is this) 19675 break; 19676 prev = ie; 19677 } 19678 if(prev is null) { 19679 BlockElement pb; 19680 BlockElement cb = this.containingBlock; 19681 moar: 19682 foreach(ie; this.containingBlock.containingLayout.blocks) { 19683 if(ie is cb) 19684 break; 19685 pb = ie; 19686 } 19687 if(pb is null) 19688 return null; 19689 if(pb.parts.length == 0) { 19690 cb = pb; 19691 goto moar; 19692 } 19693 19694 prev = pb.parts[$-1]; 19695 19696 } 19697 return prev; 19698 } 19699 19700 InlineElement getNextInlineElement() { 19701 InlineElement next = null; 19702 foreach(idx, ie; this.containingBlock.parts) { 19703 if(ie is this) { 19704 if(idx + 1 < this.containingBlock.parts.length) 19705 next = this.containingBlock.parts[idx + 1]; 19706 break; 19707 } 19708 } 19709 if(next is null) { 19710 BlockElement n; 19711 foreach(idx, ie; this.containingBlock.containingLayout.blocks) { 19712 if(ie is this.containingBlock) { 19713 if(idx + 1 < this.containingBlock.containingLayout.blocks.length) 19714 n = this.containingBlock.containingLayout.blocks[idx + 1]; 19715 break; 19716 } 19717 } 19718 if(n is null) 19719 return null; 19720 19721 if(n.parts.length) 19722 next = n.parts[0]; 19723 else {} // FIXME 19724 19725 } 19726 return next; 19727 } 19728 19729 } 19730 19731 // Block elements are used entirely for positioning inline elements, 19732 // which are the things that are actually drawn. 19733 class BlockElement { 19734 InlineElement[] parts; 19735 uint alignment; 19736 19737 int whiteSpace; // pre, pre-wrap, wrap 19738 19739 TextLayout containingLayout; 19740 19741 // inputs 19742 Point where; 19743 Size minimumSize; 19744 Size maximumSize; 19745 Rectangle[] excludedBoxes; // like if you want it to write around a floated image or something. Coordinates are relative to the bounding box. 19746 void* identifier; 19747 19748 Rectangle margin; 19749 Rectangle padding; 19750 19751 // outputs 19752 Rectangle[] boundingBoxes; 19753 } 19754 19755 struct TextIdentifyResult { 19756 InlineElement element; 19757 int offset; 19758 19759 private TextIdentifyResult fixupNewline() { 19760 if(element !is null && offset < element.text.length && element.text[offset] == '\n') { 19761 offset--; 19762 } else if(element !is null && offset == element.text.length && element.text.length > 1 && element.text[$-1] == '\n') { 19763 offset--; 19764 } 19765 return this; 19766 } 19767 } 19768 19769 class TextLayout { 19770 BlockElement[] blocks; 19771 Rectangle boundingBox_; 19772 Rectangle boundingBox() { return boundingBox_; } 19773 void boundingBox(Rectangle r) { 19774 if(r != boundingBox_) { 19775 boundingBox_ = r; 19776 layoutInvalidated = true; 19777 } 19778 } 19779 19780 Rectangle contentBoundingBox() { 19781 Rectangle r; 19782 foreach(block; blocks) 19783 foreach(ie; block.parts) { 19784 if(ie.boundingBox.right > r.right) 19785 r.right = ie.boundingBox.right; 19786 if(ie.boundingBox.bottom > r.bottom) 19787 r.bottom = ie.boundingBox.bottom; 19788 } 19789 return r; 19790 } 19791 19792 BlockElement[] getBlocks() { 19793 return blocks; 19794 } 19795 19796 InlineElement[] getTexts() { 19797 InlineElement[] elements; 19798 foreach(block; blocks) 19799 elements ~= block.parts; 19800 return elements; 19801 } 19802 19803 string getPlainText() { 19804 string text; 19805 foreach(block; blocks) 19806 foreach(part; block.parts) 19807 text ~= part.text; 19808 return text; 19809 } 19810 19811 string getHtml() { 19812 return null; // FIXME 19813 } 19814 19815 this(Rectangle boundingBox) { 19816 this.boundingBox = boundingBox; 19817 } 19818 19819 BlockElement addBlock(InlineElement after = null, Rectangle margin = Rectangle(0, 0, 0, 0), Rectangle padding = Rectangle(0, 0, 0, 0)) { 19820 auto be = new BlockElement(); 19821 be.containingLayout = this; 19822 if(after is null) 19823 blocks ~= be; 19824 else { 19825 foreach(idx, b; blocks) { 19826 if(b is after.containingBlock) { 19827 blocks = blocks[0 .. idx + 1] ~ be ~ blocks[idx + 1 .. $]; 19828 break; 19829 } 19830 } 19831 } 19832 return be; 19833 } 19834 19835 void clear() { 19836 blocks = null; 19837 selectionStart = selectionEnd = caret = Caret.init; 19838 } 19839 19840 void addText(Args...)(Args args) { 19841 if(blocks.length == 0) 19842 addBlock(); 19843 19844 InlineElement ie = new InlineElement(); 19845 foreach(idx, arg; args) { 19846 static if(is(typeof(arg) == ForegroundColor)) 19847 ie.color = arg; 19848 else static if(is(typeof(arg) == TextFormat)) { 19849 if(arg & 0x8000) // ~TextFormat.something turns it off 19850 ie.styles &= arg; 19851 else 19852 ie.styles |= arg; 19853 } else static if(is(typeof(arg) == string)) { 19854 static if(idx == 0 && args.length > 1) 19855 static assert(0, "Put styles before the string."); 19856 size_t lastLineIndex; 19857 foreach(cidx, char a; arg) { 19858 if(a == '\n') { 19859 ie.text = arg[lastLineIndex .. cidx + 1]; 19860 lastLineIndex = cidx + 1; 19861 ie.containingBlock = blocks[$-1]; 19862 blocks[$-1].parts ~= ie.clone; 19863 ie.text = null; 19864 } else { 19865 19866 } 19867 } 19868 19869 ie.text = arg[lastLineIndex .. $]; 19870 ie.containingBlock = blocks[$-1]; 19871 blocks[$-1].parts ~= ie.clone; 19872 caret = Caret(this, blocks[$-1].parts[$-1], cast(int) blocks[$-1].parts[$-1].text.length); 19873 } 19874 } 19875 19876 invalidateLayout(); 19877 } 19878 19879 void tryMerge(InlineElement into, InlineElement what) { 19880 if(!into.isMergeCompatible(what)) { 19881 return; // cannot merge, different configs 19882 } 19883 19884 // cool, can merge, bring text together... 19885 into.text ~= what.text; 19886 19887 // and remove what 19888 for(size_t a = 0; a < what.containingBlock.parts.length; a++) { 19889 if(what.containingBlock.parts[a] is what) { 19890 for(size_t i = a; i < what.containingBlock.parts.length - 1; i++) 19891 what.containingBlock.parts[i] = what.containingBlock.parts[i + 1]; 19892 what.containingBlock.parts = what.containingBlock.parts[0 .. $-1]; 19893 19894 } 19895 } 19896 19897 // FIXME: ensure no other carets have a reference to it 19898 } 19899 19900 /// exact = true means return null if no match. otherwise, get the closest one that makes sense for a mouse click. 19901 TextIdentifyResult identify(int x, int y, bool exact = false) { 19902 TextIdentifyResult inexactMatch; 19903 foreach(block; blocks) { 19904 foreach(part; block.parts) { 19905 if(x >= part.boundingBox.left && x < part.boundingBox.right && y >= part.boundingBox.top && y < part.boundingBox.bottom) { 19906 19907 // FIXME binary search 19908 int tidx; 19909 int lastX; 19910 foreach_reverse(idxo, lx; part.letterXs) { 19911 int idx = cast(int) idxo; 19912 if(lx <= x) { 19913 if(lastX && lastX - x < x - lx) 19914 tidx = idx + 1; 19915 else 19916 tidx = idx; 19917 break; 19918 } 19919 lastX = lx; 19920 } 19921 19922 return TextIdentifyResult(part, tidx).fixupNewline; 19923 } else if(!exact) { 19924 // we're not in the box, but are we on the same line? 19925 if(y >= part.boundingBox.top && y < part.boundingBox.bottom) 19926 inexactMatch = TextIdentifyResult(part, x == 0 ? 0 : cast(int) part.text.length); 19927 } 19928 } 19929 } 19930 19931 if(!exact && inexactMatch is TextIdentifyResult.init && blocks.length && blocks[$-1].parts.length) 19932 return TextIdentifyResult(blocks[$-1].parts[$-1], cast(int) blocks[$-1].parts[$-1].text.length).fixupNewline; 19933 19934 return exact ? TextIdentifyResult.init : inexactMatch.fixupNewline; 19935 } 19936 19937 void moveCaretToPixelCoordinates(int x, int y) { 19938 auto result = identify(x, y); 19939 caret.inlineElement = result.element; 19940 caret.offset = result.offset; 19941 } 19942 19943 void selectToPixelCoordinates(int x, int y) { 19944 auto result = identify(x, y); 19945 19946 if(y < caretLastDrawnY1) { 19947 // on a previous line, carat is selectionEnd 19948 selectionEnd = caret; 19949 19950 selectionStart = Caret(this, result.element, result.offset); 19951 } else if(y > caretLastDrawnY2) { 19952 // on a later line 19953 selectionStart = caret; 19954 19955 selectionEnd = Caret(this, result.element, result.offset); 19956 } else { 19957 // on the same line... 19958 if(x <= caretLastDrawnX) { 19959 selectionEnd = caret; 19960 selectionStart = Caret(this, result.element, result.offset); 19961 } else { 19962 selectionStart = caret; 19963 selectionEnd = Caret(this, result.element, result.offset); 19964 } 19965 19966 } 19967 } 19968 19969 19970 /// Call this if the inputs change. It will reflow everything 19971 void redoLayout(ScreenPainter painter) { 19972 //painter.setClipRectangle(boundingBox); 19973 auto pos = Point(boundingBox.left, boundingBox.top); 19974 19975 int lastHeight; 19976 void nl() { 19977 pos.x = boundingBox.left; 19978 pos.y += lastHeight; 19979 } 19980 foreach(block; blocks) { 19981 nl(); 19982 foreach(part; block.parts) { 19983 part.letterXs = null; 19984 19985 auto size = painter.textSize(part.text); 19986 version(Windows) 19987 if(part.text.length && part.text[$-1] == '\n') 19988 size.height /= 2; // windows counts the new line at the end, but we don't want that 19989 19990 part.boundingBox = Rectangle(pos.x, pos.y, pos.x + size.width, pos.y + size.height); 19991 19992 foreach(idx, char c; part.text) { 19993 // FIXME: unicode 19994 part.letterXs ~= painter.textSize(part.text[0 .. idx]).width + pos.x; 19995 } 19996 19997 pos.x += size.width; 19998 if(pos.x >= boundingBox.right) { 19999 pos.y += size.height; 20000 pos.x = boundingBox.left; 20001 lastHeight = 0; 20002 } else { 20003 lastHeight = size.height; 20004 } 20005 20006 if(part.text.length && part.text[$-1] == '\n') 20007 nl(); 20008 } 20009 } 20010 20011 layoutInvalidated = false; 20012 } 20013 20014 bool layoutInvalidated = true; 20015 void invalidateLayout() { 20016 layoutInvalidated = true; 20017 } 20018 20019 // FIXME: caret can remain sometimes when inserting 20020 // FIXME: inserting at the beginning once you already have something can eff it up. 20021 void drawInto(ScreenPainter painter, bool focused = false) { 20022 if(layoutInvalidated) 20023 redoLayout(painter); 20024 foreach(block; blocks) { 20025 foreach(part; block.parts) { 20026 painter.outlineColor = part.color; 20027 painter.fillColor = part.backgroundColor; 20028 20029 auto pos = part.boundingBox.upperLeft; 20030 auto size = part.boundingBox.size; 20031 20032 painter.drawText(pos, part.text); 20033 if(part.styles & TextFormat.underline) 20034 painter.drawLine(Point(pos.x, pos.y + size.height - 4), Point(pos.x + size.width, pos.y + size.height - 4)); 20035 if(part.styles & TextFormat.strikethrough) 20036 painter.drawLine(Point(pos.x, pos.y + size.height/2), Point(pos.x + size.width, pos.y + size.height/2)); 20037 } 20038 } 20039 20040 // on every redraw, I will force the caret to be 20041 // redrawn too, in order to eliminate perceived lag 20042 // when moving around with the mouse. 20043 eraseCaret(painter); 20044 20045 if(focused) { 20046 highlightSelection(painter); 20047 drawCaret(painter); 20048 } 20049 } 20050 20051 Color selectionXorColor = Color(255, 255, 127); 20052 20053 void highlightSelection(ScreenPainter painter) { 20054 if(selectionStart is selectionEnd) 20055 return; // no selection 20056 20057 if(selectionStart.inlineElement is null) return; 20058 if(selectionEnd.inlineElement is null) return; 20059 20060 assert(selectionStart.inlineElement !is null); 20061 assert(selectionEnd.inlineElement !is null); 20062 20063 painter.rasterOp = RasterOp.xor; 20064 painter.outlineColor = Color.transparent; 20065 painter.fillColor = selectionXorColor; 20066 20067 auto at = selectionStart.inlineElement; 20068 auto atOffset = selectionStart.offset; 20069 bool done; 20070 while(at) { 20071 auto box = at.boundingBox; 20072 if(atOffset < at.letterXs.length) 20073 box.left = at.letterXs[atOffset]; 20074 20075 if(at is selectionEnd.inlineElement) { 20076 if(selectionEnd.offset < at.letterXs.length) 20077 box.right = at.letterXs[selectionEnd.offset]; 20078 done = true; 20079 } 20080 20081 painter.drawRectangle(box.upperLeft, box.width, box.height); 20082 20083 if(done) 20084 break; 20085 20086 at = at.getNextInlineElement(); 20087 atOffset = 0; 20088 } 20089 } 20090 20091 int caretLastDrawnX, caretLastDrawnY1, caretLastDrawnY2; 20092 bool caretShowingOnScreen = false; 20093 void drawCaret(ScreenPainter painter) { 20094 //painter.setClipRectangle(boundingBox); 20095 int x, y1, y2; 20096 if(caret.inlineElement is null) { 20097 x = boundingBox.left; 20098 y1 = boundingBox.top + 2; 20099 y2 = boundingBox.top + painter.fontHeight; 20100 } else { 20101 x = caret.inlineElement.xOfIndex(caret.offset); 20102 y1 = caret.inlineElement.boundingBox.top + 2; 20103 y2 = caret.inlineElement.boundingBox.bottom - 2; 20104 } 20105 20106 if(caretShowingOnScreen && (x != caretLastDrawnX || y1 != caretLastDrawnY1 || y2 != caretLastDrawnY2)) 20107 eraseCaret(painter); 20108 20109 painter.pen = Pen(Color.white, 1); 20110 painter.rasterOp = RasterOp.xor; 20111 painter.drawLine( 20112 Point(x, y1), 20113 Point(x, y2) 20114 ); 20115 painter.rasterOp = RasterOp.normal; 20116 caretShowingOnScreen = !caretShowingOnScreen; 20117 20118 if(caretShowingOnScreen) { 20119 caretLastDrawnX = x; 20120 caretLastDrawnY1 = y1; 20121 caretLastDrawnY2 = y2; 20122 } 20123 } 20124 20125 Rectangle caretBoundingBox() { 20126 int x, y1, y2; 20127 if(caret.inlineElement is null) { 20128 x = boundingBox.left; 20129 y1 = boundingBox.top + 2; 20130 y2 = boundingBox.top + 16; 20131 } else { 20132 x = caret.inlineElement.xOfIndex(caret.offset); 20133 y1 = caret.inlineElement.boundingBox.top + 2; 20134 y2 = caret.inlineElement.boundingBox.bottom - 2; 20135 } 20136 20137 return Rectangle(x, y1, x + 1, y2); 20138 } 20139 20140 void eraseCaret(ScreenPainter painter) { 20141 //painter.setClipRectangle(boundingBox); 20142 if(!caretShowingOnScreen) return; 20143 painter.pen = Pen(Color.white, 1); 20144 painter.rasterOp = RasterOp.xor; 20145 painter.drawLine( 20146 Point(caretLastDrawnX, caretLastDrawnY1), 20147 Point(caretLastDrawnX, caretLastDrawnY2) 20148 ); 20149 20150 caretShowingOnScreen = false; 20151 painter.rasterOp = RasterOp.normal; 20152 } 20153 20154 /// Caret movement api 20155 /// These should give the user a logical result based on what they see on screen... 20156 /// thus they locate predominately by *pixels* not char index. (These will generally coincide with monospace fonts tho!) 20157 void moveUp() { 20158 if(caret.inlineElement is null) return; 20159 auto x = caret.inlineElement.xOfIndex(caret.offset); 20160 auto y = caret.inlineElement.boundingBox.top + 2; 20161 20162 y -= caret.inlineElement.boundingBox.bottom - caret.inlineElement.boundingBox.top; 20163 if(y < 0) 20164 return; 20165 20166 auto i = identify(x, y); 20167 20168 if(i.element) { 20169 caret.inlineElement = i.element; 20170 caret.offset = i.offset; 20171 } 20172 } 20173 void moveDown() { 20174 if(caret.inlineElement is null) return; 20175 auto x = caret.inlineElement.xOfIndex(caret.offset); 20176 auto y = caret.inlineElement.boundingBox.bottom - 2; 20177 20178 y += caret.inlineElement.boundingBox.bottom - caret.inlineElement.boundingBox.top; 20179 20180 auto i = identify(x, y); 20181 if(i.element) { 20182 caret.inlineElement = i.element; 20183 caret.offset = i.offset; 20184 } 20185 } 20186 void moveLeft() { 20187 if(caret.inlineElement is null) return; 20188 if(caret.offset) 20189 caret.offset--; 20190 else { 20191 auto p = caret.inlineElement.getPreviousInlineElement(); 20192 if(p) { 20193 caret.inlineElement = p; 20194 if(p.text.length && p.text[$-1] == '\n') 20195 caret.offset = cast(int) p.text.length - 1; 20196 else 20197 caret.offset = cast(int) p.text.length; 20198 } 20199 } 20200 } 20201 void moveRight() { 20202 if(caret.inlineElement is null) return; 20203 if(caret.offset < caret.inlineElement.text.length && caret.inlineElement.text[caret.offset] != '\n') { 20204 caret.offset++; 20205 } else { 20206 auto p = caret.inlineElement.getNextInlineElement(); 20207 if(p) { 20208 caret.inlineElement = p; 20209 caret.offset = 0; 20210 } 20211 } 20212 } 20213 void moveHome() { 20214 if(caret.inlineElement is null) return; 20215 auto x = 0; 20216 auto y = caret.inlineElement.boundingBox.top + 2; 20217 20218 auto i = identify(x, y); 20219 20220 if(i.element) { 20221 caret.inlineElement = i.element; 20222 caret.offset = i.offset; 20223 } 20224 } 20225 void moveEnd() { 20226 if(caret.inlineElement is null) return; 20227 auto x = int.max; 20228 auto y = caret.inlineElement.boundingBox.top + 2; 20229 20230 auto i = identify(x, y); 20231 20232 if(i.element) { 20233 caret.inlineElement = i.element; 20234 caret.offset = i.offset; 20235 } 20236 20237 } 20238 void movePageUp(ref Caret caret) {} 20239 void movePageDown(ref Caret caret) {} 20240 20241 void moveDocumentStart(ref Caret caret) { 20242 if(blocks.length && blocks[0].parts.length) 20243 caret = Caret(this, blocks[0].parts[0], 0); 20244 else 20245 caret = Caret.init; 20246 } 20247 20248 void moveDocumentEnd(ref Caret caret) { 20249 if(blocks.length) { 20250 auto parts = blocks[$-1].parts; 20251 if(parts.length) { 20252 caret = Caret(this, parts[$-1], cast(int) parts[$-1].text.length); 20253 } else { 20254 caret = Caret.init; 20255 } 20256 } else 20257 caret = Caret.init; 20258 } 20259 20260 void deleteSelection() { 20261 if(selectionStart is selectionEnd) 20262 return; 20263 20264 if(selectionStart.inlineElement is null) return; 20265 if(selectionEnd.inlineElement is null) return; 20266 20267 assert(selectionStart.inlineElement !is null); 20268 assert(selectionEnd.inlineElement !is null); 20269 20270 auto at = selectionStart.inlineElement; 20271 20272 if(selectionEnd.inlineElement is at) { 20273 // same element, need to chop out 20274 at.text = at.text[0 .. selectionStart.offset] ~ at.text[selectionEnd.offset .. $]; 20275 at.letterXs = at.letterXs[0 .. selectionStart.offset] ~ at.letterXs[selectionEnd.offset .. $]; 20276 selectionEnd.offset -= selectionEnd.offset - selectionStart.offset; 20277 } else { 20278 // different elements, we can do it with slicing 20279 at.text = at.text[0 .. selectionStart.offset]; 20280 if(selectionStart.offset < at.letterXs.length) 20281 at.letterXs = at.letterXs[0 .. selectionStart.offset]; 20282 20283 at = at.getNextInlineElement(); 20284 20285 while(at) { 20286 if(at is selectionEnd.inlineElement) { 20287 at.text = at.text[selectionEnd.offset .. $]; 20288 if(selectionEnd.offset < at.letterXs.length) 20289 at.letterXs = at.letterXs[selectionEnd.offset .. $]; 20290 selectionEnd.offset = 0; 20291 break; 20292 } else { 20293 auto cfd = at; 20294 cfd.text = null; // delete the whole thing 20295 20296 at = at.getNextInlineElement(); 20297 20298 if(cfd.text.length == 0) { 20299 // and remove cfd 20300 for(size_t a = 0; a < cfd.containingBlock.parts.length; a++) { 20301 if(cfd.containingBlock.parts[a] is cfd) { 20302 for(size_t i = a; i < cfd.containingBlock.parts.length - 1; i++) 20303 cfd.containingBlock.parts[i] = cfd.containingBlock.parts[i + 1]; 20304 cfd.containingBlock.parts = cfd.containingBlock.parts[0 .. $-1]; 20305 20306 } 20307 } 20308 } 20309 } 20310 } 20311 } 20312 20313 caret = selectionEnd; 20314 selectNone(); 20315 20316 invalidateLayout(); 20317 20318 } 20319 20320 /// Plain text editing api. These work at the current caret inside the selected inline element. 20321 void insert(in char[] text) { 20322 foreach(dchar ch; text) 20323 insert(ch); 20324 } 20325 /// ditto 20326 void insert(dchar ch) { 20327 20328 bool selectionDeleted = false; 20329 if(selectionStart !is selectionEnd) { 20330 deleteSelection(); 20331 selectionDeleted = true; 20332 } 20333 20334 if(ch == 127) { 20335 delete_(); 20336 return; 20337 } 20338 if(ch == 8) { 20339 if(!selectionDeleted) 20340 backspace(); 20341 return; 20342 } 20343 20344 invalidateLayout(); 20345 20346 if(ch == 13) ch = 10; 20347 auto e = caret.inlineElement; 20348 if(e is null) { 20349 addText("" ~ cast(char) ch) ; // FIXME 20350 return; 20351 } 20352 20353 if(caret.offset == e.text.length) { 20354 e.text ~= cast(char) ch; // FIXME 20355 caret.offset++; 20356 if(ch == 10) { 20357 auto c = caret.inlineElement.clone; 20358 c.text = null; 20359 c.letterXs = null; 20360 insertPartAfter(c,e); 20361 caret = Caret(this, c, 0); 20362 } 20363 } else { 20364 // FIXME cast char sucks 20365 if(ch == 10) { 20366 auto c = caret.inlineElement.clone; 20367 c.text = e.text[caret.offset .. $]; 20368 if(caret.offset < c.letterXs.length) 20369 c.letterXs = e.letterXs[caret.offset .. $]; // FIXME boundingBox 20370 e.text = e.text[0 .. caret.offset] ~ cast(char) ch; 20371 if(caret.offset <= e.letterXs.length) { 20372 e.letterXs = e.letterXs[0 .. caret.offset] ~ 0; // FIXME bounding box 20373 } 20374 insertPartAfter(c,e); 20375 caret = Caret(this, c, 0); 20376 } else { 20377 e.text = e.text[0 .. caret.offset] ~ cast(char) ch ~ e.text[caret.offset .. $]; 20378 caret.offset++; 20379 } 20380 } 20381 } 20382 20383 void insertPartAfter(InlineElement what, InlineElement where) { 20384 foreach(idx, p; where.containingBlock.parts) { 20385 if(p is where) { 20386 if(idx + 1 == where.containingBlock.parts.length) 20387 where.containingBlock.parts ~= what; 20388 else 20389 where.containingBlock.parts = where.containingBlock.parts[0 .. idx + 1] ~ what ~ where.containingBlock.parts[idx + 1 .. $]; 20390 return; 20391 } 20392 } 20393 } 20394 20395 void cleanupStructures() { 20396 for(size_t i = 0; i < blocks.length; i++) { 20397 auto block = blocks[i]; 20398 for(size_t a = 0; a < block.parts.length; a++) { 20399 auto part = block.parts[a]; 20400 if(part.text.length == 0) { 20401 for(size_t b = a; b < block.parts.length - 1; b++) 20402 block.parts[b] = block.parts[b+1]; 20403 block.parts = block.parts[0 .. $-1]; 20404 } 20405 } 20406 if(block.parts.length == 0) { 20407 for(size_t a = i; a < blocks.length - 1; a++) 20408 blocks[a] = blocks[a+1]; 20409 blocks = blocks[0 .. $-1]; 20410 } 20411 } 20412 } 20413 20414 void backspace() { 20415 try_again: 20416 auto e = caret.inlineElement; 20417 if(e is null) 20418 return; 20419 if(caret.offset == 0) { 20420 auto prev = e.getPreviousInlineElement(); 20421 if(prev is null) 20422 return; 20423 auto newOffset = cast(int) prev.text.length; 20424 tryMerge(prev, e); 20425 caret.inlineElement = prev; 20426 caret.offset = prev is null ? 0 : newOffset; 20427 20428 goto try_again; 20429 } else if(caret.offset == e.text.length) { 20430 e.text = e.text[0 .. $-1]; 20431 caret.offset--; 20432 } else { 20433 e.text = e.text[0 .. caret.offset - 1] ~ e.text[caret.offset .. $]; 20434 caret.offset--; 20435 } 20436 //cleanupStructures(); 20437 20438 invalidateLayout(); 20439 } 20440 void delete_() { 20441 if(selectionStart !is selectionEnd) 20442 deleteSelection(); 20443 else { 20444 auto before = caret; 20445 moveRight(); 20446 if(caret != before) { 20447 backspace(); 20448 } 20449 } 20450 20451 invalidateLayout(); 20452 } 20453 void overstrike() {} 20454 20455 /// Selection API. See also: caret movement. 20456 void selectAll() { 20457 moveDocumentStart(selectionStart); 20458 moveDocumentEnd(selectionEnd); 20459 } 20460 bool selectNone() { 20461 if(selectionStart != selectionEnd) { 20462 selectionStart = selectionEnd = Caret.init; 20463 return true; 20464 } 20465 return false; 20466 } 20467 20468 /// Rich text editing api. These allow you to manipulate the meta data of the current element and add new elements. 20469 /// They will modify the current selection if there is one and will splice one in if needed. 20470 void changeAttributes() {} 20471 20472 20473 /// Text search api. They manipulate the selection and/or caret. 20474 void findText(string text) {} 20475 void findIndex(size_t textIndex) {} 20476 20477 // sample event handlers 20478 20479 void handleEvent(KeyEvent event) { 20480 //if(event.type == KeyEvent.Type.KeyPressed) { 20481 20482 //} 20483 } 20484 20485 void handleEvent(dchar ch) { 20486 20487 } 20488 20489 void handleEvent(MouseEvent event) { 20490 20491 } 20492 20493 bool contentEditable; // can it be edited? 20494 bool contentCaretable; // is there a caret/cursor that moves around in there? 20495 bool contentSelectable; // selectable? 20496 20497 Caret caret; 20498 Caret selectionStart; 20499 Caret selectionEnd; 20500 20501 bool insertMode; 20502 } 20503 20504 struct Caret { 20505 TextLayout layout; 20506 InlineElement inlineElement; 20507 int offset; 20508 } 20509 20510 enum TextFormat : ushort { 20511 // decorations 20512 underline = 1, 20513 strikethrough = 2, 20514 20515 // font selectors 20516 20517 bold = 0x4000 | 1, // weight 700 20518 light = 0x4000 | 2, // weight 300 20519 veryBoldOrLight = 0x4000 | 4, // weight 100 with light, weight 900 with bold 20520 // bold | light is really invalid but should give weight 500 20521 // veryBoldOrLight without one of the others should just give the default for the font; it should be ignored. 20522 20523 italic = 0x4000 | 8, 20524 smallcaps = 0x4000 | 16, 20525 } 20526 20527 void* findFont(string family, int weight, TextFormat formats) { 20528 return null; 20529 } 20530 20531 } 20532 20533 /++ 20534 $(PITFALL This is not yet stable and may break in future versions without notice.) 20535 20536 History: 20537 Added February 19, 2021 20538 +/ 20539 /// Group: drag_and_drop 20540 interface DropHandler { 20541 /++ 20542 Called when the drag enters the handler's area. 20543 +/ 20544 DragAndDropAction dragEnter(DropPackage*); 20545 /++ 20546 Called when the drag leaves the handler's area or is 20547 cancelled. You should free your resources when this is called. 20548 +/ 20549 void dragLeave(); 20550 /++ 20551 Called continually as the drag moves over the handler's area. 20552 20553 Returns: feedback to the dragger 20554 +/ 20555 DropParameters dragOver(Point pt); 20556 /++ 20557 The user dropped the data and you should process it now. You can 20558 access the data through the given [DropPackage]. 20559 +/ 20560 void drop(scope DropPackage*); 20561 /++ 20562 Called when the drop is complete. You should free whatever temporary 20563 resources you were using. It is often reasonable to simply forward 20564 this call to [dragLeave]. 20565 +/ 20566 void finish(); 20567 20568 /++ 20569 Parameters returned by [DropHandler.drop]. 20570 +/ 20571 static struct DropParameters { 20572 /++ 20573 Acceptable action over this area. 20574 +/ 20575 DragAndDropAction action; 20576 /++ 20577 Rectangle, in client coordinates, where the dragger can expect the same result during this drag session and thus need not ask again. 20578 20579 If you leave this as Rectangle.init, the dragger will continue to ask and this can waste resources. 20580 +/ 20581 Rectangle consistentWithin; 20582 } 20583 } 20584 20585 /++ 20586 History: 20587 Added February 19, 2021 20588 +/ 20589 /// Group: drag_and_drop 20590 enum DragAndDropAction { 20591 none = 0, 20592 copy, 20593 move, 20594 link, 20595 ask, 20596 custom 20597 } 20598 20599 /++ 20600 An opaque structure representing dropped data. It contains 20601 private, platform-specific data that your `drop` function 20602 should simply forward. 20603 20604 $(PITFALL This is not yet stable and may break in future versions without notice.) 20605 20606 History: 20607 Added February 19, 2021 20608 +/ 20609 /// Group: drag_and_drop 20610 struct DropPackage { 20611 /++ 20612 Lists the available formats as magic numbers. You should compare these 20613 against looked-up formats (see [DraggableData.getFormatId]) you know you support and can 20614 understand the passed data. 20615 +/ 20616 DraggableData.FormatId[] availableFormats() { 20617 version(X11) { 20618 return xFormats; 20619 } else version(Windows) { 20620 if(pDataObj is null) 20621 return null; 20622 20623 typeof(return) ret; 20624 20625 IEnumFORMATETC ef; 20626 if(pDataObj.EnumFormatEtc(DATADIR.DATADIR_GET, &ef) == S_OK) { 20627 FORMATETC fmt; 20628 ULONG fetched; 20629 while(ef.Next(1, &fmt, &fetched) == S_OK) { 20630 if(fetched == 0) 20631 break; 20632 20633 if(fmt.lindex != -1) 20634 continue; 20635 if(fmt.dwAspect != DVASPECT.DVASPECT_CONTENT) 20636 continue; 20637 if(!(fmt.tymed & TYMED.TYMED_HGLOBAL)) 20638 continue; 20639 20640 ret ~= fmt.cfFormat; 20641 } 20642 } 20643 20644 return ret; 20645 } 20646 } 20647 20648 /++ 20649 Gets data from the drop and optionally accepts it. 20650 20651 Returns: 20652 void because the data is fed asynchronously through the `dg` parameter. 20653 20654 Params: 20655 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. 20656 20657 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. 20658 20659 Calling `getData` again after accepting a drop is not permitted. 20660 20661 format = the format you want, from [availableFormats]. Use [DraggableData.getFormatId] to convert from a MIME string or well-known standard format. 20662 20663 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. 20664 20665 Throws: 20666 if `format` was not compatible with the [availableFormats] or if the drop has already been accepted. 20667 20668 History: 20669 Included in first release of [DropPackage]. 20670 +/ 20671 void getData(DragAndDropAction acceptedAction, DraggableData.FormatId format, void delegate(scope ubyte[] data) dg) { 20672 version(X11) { 20673 20674 auto display = XDisplayConnection.get(); 20675 auto selectionAtom = GetAtom!"XdndSelection"(display); 20676 auto best = format; 20677 20678 static class X11GetSelectionHandler_Drop : X11GetSelectionHandler { 20679 20680 XDisplay* display; 20681 Atom selectionAtom; 20682 DraggableData.FormatId best; 20683 DraggableData.FormatId format; 20684 void delegate(scope ubyte[] data) dg; 20685 DragAndDropAction acceptedAction; 20686 Window sourceWindow; 20687 SimpleWindow win; 20688 this(XDisplay* display, SimpleWindow win, Window sourceWindow, DraggableData.FormatId format, Atom selectionAtom, DraggableData.FormatId best, void delegate(scope ubyte[] data) dg, DragAndDropAction acceptedAction) { 20689 this.display = display; 20690 this.win = win; 20691 this.sourceWindow = sourceWindow; 20692 this.format = format; 20693 this.selectionAtom = selectionAtom; 20694 this.best = best; 20695 this.dg = dg; 20696 this.acceptedAction = acceptedAction; 20697 } 20698 20699 20700 mixin X11GetSelectionHandler_Basics; 20701 20702 void handleData(Atom target, in ubyte[] data) { 20703 //if(target == GetAtom!"UTF8_STRING"(XDisplayConnection.get) || target == XA_STRING || target == GetAtom!"text/plain"(XDisplayConnection.get)) 20704 20705 dg(cast(ubyte[]) data); 20706 20707 if(acceptedAction != DragAndDropAction.none) { 20708 auto display = XDisplayConnection.get; 20709 20710 XClientMessageEvent xclient; 20711 20712 xclient.type = EventType.ClientMessage; 20713 xclient.window = sourceWindow; 20714 xclient.message_type = GetAtom!"XdndFinished"(display); 20715 xclient.format = 32; 20716 xclient.data.l[0] = win.impl.window; 20717 xclient.data.l[1] = 1; // drop successful 20718 xclient.data.l[2] = dndActionAtom(display, acceptedAction); 20719 20720 XSendEvent( 20721 display, 20722 sourceWindow, 20723 false, 20724 EventMask.NoEventMask, 20725 cast(XEvent*) &xclient 20726 ); 20727 20728 XFlush(display); 20729 } 20730 } 20731 20732 Atom findBestFormat(Atom[] answer) { 20733 Atom best = None; 20734 foreach(option; answer) { 20735 if(option == format) { 20736 best = option; 20737 break; 20738 } 20739 /* 20740 if(option == GetAtom!"UTF8_STRING"(XDisplayConnection.get)) { 20741 best = option; 20742 break; 20743 } else if(option == XA_STRING) { 20744 best = option; 20745 } else if(option == GetAtom!"text/plain"(XDisplayConnection.get)) { 20746 best = option; 20747 } 20748 */ 20749 } 20750 return best; 20751 } 20752 } 20753 20754 win.impl.getSelectionHandlers[selectionAtom] = new X11GetSelectionHandler_Drop(display, win, sourceWindow, format, selectionAtom, best, dg, acceptedAction); 20755 20756 XConvertSelection(display, selectionAtom, best, GetAtom!("SDD_DATA", true)(display), win.impl.window, dataTimestamp); 20757 20758 } else version(Windows) { 20759 20760 // clean up like DragLeave 20761 // pass effect back up 20762 20763 FORMATETC t; 20764 assert(format >= 0 && format <= ushort.max); 20765 t.cfFormat = cast(ushort) format; 20766 t.lindex = -1; 20767 t.dwAspect = DVASPECT.DVASPECT_CONTENT; 20768 t.tymed = TYMED.TYMED_HGLOBAL; 20769 20770 STGMEDIUM m; 20771 20772 if(pDataObj.GetData(&t, &m) != S_OK) { 20773 // fail 20774 } else { 20775 // succeed, take the data and clean up 20776 20777 // FIXME: ensure it is legit HGLOBAL 20778 auto handle = m.hGlobal; 20779 20780 if(handle) { 20781 auto sz = GlobalSize(handle); 20782 if(auto ptr = cast(ubyte*) GlobalLock(handle)) { 20783 scope(exit) GlobalUnlock(handle); 20784 scope(exit) GlobalFree(handle); 20785 20786 auto data = ptr[0 .. sz]; 20787 20788 dg(data); 20789 } 20790 } 20791 } 20792 } 20793 } 20794 20795 private: 20796 20797 version(X11) { 20798 SimpleWindow win; 20799 Window sourceWindow; 20800 Time dataTimestamp; 20801 20802 Atom[] xFormats; 20803 } 20804 version(Windows) { 20805 IDataObject pDataObj; 20806 } 20807 } 20808 20809 /++ 20810 A generic helper base class for making a drop handler with a preference list of custom types. 20811 This is the base for [TextDropHandler] and [FilesDropHandler] and you can use it for your own 20812 droppers too. 20813 20814 It assumes the whole window it used, but you can subclass to change that. 20815 20816 $(PITFALL This is not yet stable and may break in future versions without notice.) 20817 20818 History: 20819 Added February 19, 2021 20820 +/ 20821 /// Group: drag_and_drop 20822 class GenericDropHandlerBase : DropHandler { 20823 // no fancy state here so no need to do anything here 20824 void finish() { } 20825 void dragLeave() { } 20826 20827 private DragAndDropAction acceptedAction; 20828 private DraggableData.FormatId acceptedFormat; 20829 private void delegate(scope ubyte[]) acceptedHandler; 20830 20831 struct FormatHandler { 20832 DraggableData.FormatId format; 20833 void delegate(scope ubyte[]) handler; 20834 } 20835 20836 protected abstract FormatHandler[] formatHandlers(); 20837 20838 DragAndDropAction dragEnter(DropPackage* pkg) { 20839 debug(sdpy_dnd) { import std.stdio; foreach(fmt; pkg.availableFormats()) writeln(fmt, " ", DraggableData.getFormatName(fmt)); } 20840 foreach(fmt; formatHandlers()) 20841 foreach(f; pkg.availableFormats()) 20842 if(f == fmt.format) { 20843 acceptedFormat = f; 20844 acceptedHandler = fmt.handler; 20845 return acceptedAction = DragAndDropAction.copy; 20846 } 20847 return acceptedAction = DragAndDropAction.none; 20848 } 20849 DropParameters dragOver(Point pt) { 20850 return DropParameters(acceptedAction); 20851 } 20852 20853 void drop(scope DropPackage* dropPackage) { 20854 if(!acceptedFormat || acceptedHandler is null) { 20855 debug(sdpy_dnd) { import std.stdio; writeln("drop called w/ handler ", acceptedHandler, " and format ", acceptedFormat); } 20856 return; // prolly shouldn't happen anyway... 20857 } 20858 20859 dropPackage.getData(acceptedAction, acceptedFormat, acceptedHandler); 20860 } 20861 } 20862 20863 /++ 20864 A simple handler for making your window accept drops of plain text. 20865 20866 $(PITFALL This is not yet stable and may break in future versions without notice.) 20867 20868 History: 20869 Added February 22, 2021 20870 +/ 20871 /// Group: drag_and_drop 20872 class TextDropHandler : GenericDropHandlerBase { 20873 private void delegate(in char[] text) dg; 20874 20875 /++ 20876 20877 +/ 20878 this(void delegate(in char[] text) dg) { 20879 this.dg = dg; 20880 } 20881 20882 protected override FormatHandler[] formatHandlers() { 20883 version(X11) 20884 return [ 20885 FormatHandler(GetAtom!"UTF8_STRING"(XDisplayConnection.get), &translator), 20886 FormatHandler(GetAtom!"text/plain;charset=utf-8"(XDisplayConnection.get), &translator), 20887 ]; 20888 else version(Windows) 20889 return [ 20890 FormatHandler(CF_UNICODETEXT, &translator), 20891 ]; 20892 } 20893 20894 private void translator(scope ubyte[] data) { 20895 version(X11) 20896 dg(cast(char[]) data); 20897 else version(Windows) 20898 dg(makeUtf8StringFromWindowsString(cast(wchar[]) data)); 20899 } 20900 } 20901 20902 /++ 20903 A simple handler for making your window accept drops of files, issued to you as file names. 20904 20905 $(PITFALL This is not yet stable and may break in future versions without notice.) 20906 20907 History: 20908 Added February 22, 2021 20909 +/ 20910 /// Group: drag_and_drop 20911 20912 class FilesDropHandler : GenericDropHandlerBase { 20913 private void delegate(in char[][]) dg; 20914 20915 /++ 20916 20917 +/ 20918 this(void delegate(in char[][] fileNames) dg) { 20919 this.dg = dg; 20920 } 20921 20922 protected override FormatHandler[] formatHandlers() { 20923 version(X11) 20924 return [ 20925 FormatHandler(GetAtom!"text/uri-list"(XDisplayConnection.get), &translator), 20926 ]; 20927 else version(Windows) 20928 return [ 20929 FormatHandler(CF_HDROP, &translator), 20930 ]; 20931 } 20932 20933 private void translator(scope ubyte[] data) { 20934 version(X11) { 20935 char[] listString = cast(char[]) data; 20936 char[][16] buffer; 20937 int count; 20938 char[][] result = buffer[]; 20939 20940 void commit(char[] s) { 20941 if(count == result.length) 20942 result.length += 16; 20943 if(s.length > 7 && s[0 ..7] == "file://") 20944 s = s[7 .. $]; // FIXME: also may need to trim out the host and do some entity decoding 20945 result[count++] = s; 20946 } 20947 20948 size_t last; 20949 foreach(idx, char c; listString) { 20950 if(c == '\n') { 20951 commit(listString[last .. idx - 1]); // a \r 20952 last = idx + 1; // a \n 20953 } 20954 } 20955 20956 if(last < listString.length) { 20957 commit(listString[last .. $]); 20958 } 20959 20960 // FIXME: they are uris now, should I translate it to local file names? 20961 // of course the host name is supposed to be there cuz of X rokking... 20962 20963 dg(result[0 .. count]); 20964 } else version(Windows) { 20965 20966 static struct DROPFILES { 20967 DWORD pFiles; 20968 POINT pt; 20969 BOOL fNC; 20970 BOOL fWide; 20971 } 20972 20973 20974 const(char)[][16] buffer; 20975 int count; 20976 const(char)[][] result = buffer[]; 20977 size_t last; 20978 20979 void commitA(in char[] stuff) { 20980 if(count == result.length) 20981 result.length += 16; 20982 result[count++] = stuff; 20983 } 20984 20985 void commitW(in wchar[] stuff) { 20986 commitA(makeUtf8StringFromWindowsString(stuff)); 20987 } 20988 20989 void magic(T)(T chars) { 20990 size_t idx; 20991 while(chars[idx]) { 20992 last = idx; 20993 while(chars[idx]) { 20994 idx++; 20995 } 20996 static if(is(T == char*)) 20997 commitA(chars[last .. idx]); 20998 else 20999 commitW(chars[last .. idx]); 21000 idx++; 21001 } 21002 } 21003 21004 auto df = cast(DROPFILES*) data.ptr; 21005 if(df.fWide) { 21006 wchar* chars = cast(wchar*) (data.ptr + df.pFiles); 21007 magic(chars); 21008 } else { 21009 char* chars = cast(char*) (data.ptr + df.pFiles); 21010 magic(chars); 21011 } 21012 dg(result[0 .. count]); 21013 } 21014 } 21015 } 21016 21017 /++ 21018 Interface to describe data being dragged. See also [draggable] helper function. 21019 21020 $(PITFALL This is not yet stable and may break in future versions without notice.) 21021 21022 History: 21023 Added February 19, 2021 21024 +/ 21025 interface DraggableData { 21026 version(X11) 21027 alias FormatId = Atom; 21028 else 21029 alias FormatId = uint; 21030 /++ 21031 Gets the platform-specific FormatId associated with the given named format. 21032 21033 This may be a MIME type, but may also be other various strings defined by the 21034 programs you want to interoperate with. 21035 21036 FIXME: sdpy needs to offer data adapter things that look for compatible formats 21037 and convert it to some particular type for you. 21038 +/ 21039 static FormatId getFormatId(string name)() { 21040 version(X11) 21041 return GetAtom!name(XDisplayConnection.get); 21042 else version(Windows) { 21043 static UINT cache; 21044 if(!cache) 21045 cache = RegisterClipboardFormatA(name); 21046 return cache; 21047 } else 21048 throw new NotYetImplementedException(); 21049 } 21050 21051 /++ 21052 Looks up a string to represent the name for the given format, if there is one. 21053 21054 You should avoid using this function because it is slow. It is provided more for 21055 debugging than for primary use. 21056 +/ 21057 static string getFormatName(FormatId format) { 21058 version(X11) { 21059 if(format == 0) 21060 return "None"; 21061 else 21062 return getAtomName(format, XDisplayConnection.get); 21063 } else version(Windows) { 21064 switch(format) { 21065 case CF_UNICODETEXT: return "CF_UNICODETEXT"; 21066 case CF_DIBV5: return "CF_DIBV5"; 21067 case CF_RIFF: return "CF_RIFF"; 21068 case CF_WAVE: return "CF_WAVE"; 21069 case CF_HDROP: return "CF_HDROP"; 21070 default: 21071 char[1024] name; 21072 auto count = GetClipboardFormatNameA(format, name.ptr, name.length); 21073 return name[0 .. count].idup; 21074 } 21075 } 21076 } 21077 21078 FormatId[] availableFormats(); 21079 // Return the slice of data you filled, empty slice if done. 21080 // this is to support the incremental thing 21081 ubyte[] getData(FormatId format, return scope ubyte[] data); 21082 21083 size_t dataLength(FormatId format); 21084 } 21085 21086 /++ 21087 $(PITFALL This is not yet stable and may break in future versions without notice.) 21088 21089 History: 21090 Added February 19, 2021 21091 +/ 21092 DraggableData draggable(string s) { 21093 version(X11) 21094 return new class X11SetSelectionHandler_Text, DraggableData { 21095 this() { 21096 super(s); 21097 } 21098 21099 override FormatId[] availableFormats() { 21100 return X11SetSelectionHandler_Text.availableFormats(); 21101 } 21102 21103 override ubyte[] getData(FormatId format, return scope ubyte[] data) { 21104 return X11SetSelectionHandler_Text.getData(format, data); 21105 } 21106 21107 size_t dataLength(FormatId format) { 21108 return s.length; 21109 } 21110 }; 21111 version(Windows) 21112 return new class DraggableData { 21113 FormatId[] availableFormats() { 21114 return [CF_UNICODETEXT]; 21115 } 21116 21117 ubyte[] getData(FormatId format, return scope ubyte[] data) { 21118 return cast(ubyte[]) makeWindowsString(s, cast(wchar[]) data, WindowsStringConversionFlags.convertNewLines | WindowsStringConversionFlags.zeroTerminate); 21119 } 21120 21121 size_t dataLength(FormatId format) { 21122 return sizeOfConvertedWstring(s, WindowsStringConversionFlags.convertNewLines | WindowsStringConversionFlags.zeroTerminate) * wchar.sizeof; 21123 } 21124 }; 21125 } 21126 21127 /++ 21128 $(PITFALL This is not yet stable and may break in future versions without notice.) 21129 21130 History: 21131 Added February 19, 2021 21132 +/ 21133 /// Group: drag_and_drop 21134 int doDragDrop(SimpleWindow window, DraggableData handler, DragAndDropAction action = DragAndDropAction.copy) 21135 in { 21136 assert(window !is null); 21137 assert(handler !is null); 21138 } 21139 do 21140 { 21141 version(X11) { 21142 auto sh = cast(X11SetSelectionHandler) handler; 21143 if(sh is null) { 21144 // gotta make my own adapter. 21145 sh = new class X11SetSelectionHandler { 21146 mixin X11SetSelectionHandler_Basics; 21147 21148 Atom[] availableFormats() { return handler.availableFormats(); } 21149 ubyte[] getData(Atom format, return scope ubyte[] data) { 21150 return handler.getData(format, data); 21151 } 21152 21153 // since the drop selection is only ever used once it isn't important 21154 // to reset it. 21155 void done() {} 21156 }; 21157 } 21158 return doDragDropX11(window, sh, action); 21159 } else version(Windows) { 21160 return doDragDropWindows(window, handler, action); 21161 } else throw new NotYetImplementedException(); 21162 } 21163 21164 version(Windows) 21165 private int doDragDropWindows(SimpleWindow window, DraggableData handler, DragAndDropAction action = DragAndDropAction.copy) { 21166 IDataObject obj = new class IDataObject { 21167 ULONG refCount; 21168 ULONG AddRef() { 21169 return ++refCount; 21170 } 21171 ULONG Release() { 21172 return --refCount; 21173 } 21174 HRESULT QueryInterface(const (IID)*riid, LPVOID *ppv) { 21175 if (IID_IUnknown == *riid) { 21176 *ppv = cast(void*) cast(IUnknown) this; 21177 } 21178 else if (IID_IDataObject == *riid) { 21179 *ppv = cast(void*) cast(IDataObject) this; 21180 } 21181 else { 21182 *ppv = null; 21183 return E_NOINTERFACE; 21184 } 21185 21186 AddRef(); 21187 return NOERROR; 21188 } 21189 21190 HRESULT DAdvise(FORMATETC* pformatetc, DWORD advf, IAdviseSink pAdvSink, DWORD* pdwConnection) { 21191 // import std.stdio; writeln("Advise"); 21192 return E_NOTIMPL; 21193 } 21194 HRESULT DUnadvise(DWORD dwConnection) { 21195 return E_NOTIMPL; 21196 } 21197 HRESULT EnumDAdvise(IEnumSTATDATA* ppenumAdvise) { 21198 // import std.stdio; writeln("EnumDAdvise"); 21199 return OLE_E_ADVISENOTSUPPORTED; 21200 } 21201 // tell what formats it supports 21202 21203 FORMATETC[] types; 21204 this() { 21205 FORMATETC t; 21206 foreach(ty; handler.availableFormats()) { 21207 assert(ty <= ushort.max && ty >= 0); 21208 t.cfFormat = cast(ushort) ty; 21209 t.lindex = -1; 21210 t.dwAspect = DVASPECT.DVASPECT_CONTENT; 21211 t.tymed = TYMED.TYMED_HGLOBAL; 21212 } 21213 types ~= t; 21214 } 21215 HRESULT EnumFormatEtc(DWORD dwDirection, IEnumFORMATETC* ppenumFormatEtc) { 21216 if(dwDirection == DATADIR.DATADIR_GET) { 21217 *ppenumFormatEtc = new class IEnumFORMATETC { 21218 ULONG refCount; 21219 ULONG AddRef() { 21220 return ++refCount; 21221 } 21222 ULONG Release() { 21223 return --refCount; 21224 } 21225 HRESULT QueryInterface(const (IID)*riid, LPVOID *ppv) { 21226 if (IID_IUnknown == *riid) { 21227 *ppv = cast(void*) cast(IUnknown) this; 21228 } 21229 else if (IID_IEnumFORMATETC == *riid) { 21230 *ppv = cast(void*) cast(IEnumFORMATETC) this; 21231 } 21232 else { 21233 *ppv = null; 21234 return E_NOINTERFACE; 21235 } 21236 21237 AddRef(); 21238 return NOERROR; 21239 } 21240 21241 21242 int pos; 21243 this() { 21244 pos = 0; 21245 } 21246 21247 HRESULT Clone(IEnumFORMATETC* ppenum) { 21248 // import std.stdio; writeln("clone"); 21249 return E_NOTIMPL; // FIXME 21250 } 21251 21252 // Caller is responsible for freeing memory 21253 HRESULT Next(ULONG celt, FORMATETC* rgelt, ULONG* pceltFetched) { 21254 // fetched may be null if celt is one 21255 if(celt != 1) 21256 return E_NOTIMPL; // FIXME 21257 21258 if(celt + pos > types.length) 21259 return S_FALSE; 21260 21261 *rgelt = types[pos++]; 21262 21263 if(pceltFetched !is null) 21264 *pceltFetched = 1; 21265 21266 // import std.stdio; writeln("ok celt ", celt); 21267 return S_OK; 21268 } 21269 21270 HRESULT Reset() { 21271 pos = 0; 21272 return S_OK; 21273 } 21274 21275 HRESULT Skip(ULONG celt) { 21276 if(celt + pos <= types.length) { 21277 pos += celt; 21278 return S_OK; 21279 } 21280 return S_FALSE; 21281 } 21282 }; 21283 21284 return S_OK; 21285 } else 21286 return E_NOTIMPL; 21287 } 21288 // given a format, return the format you'd prefer to use cuz it is identical 21289 HRESULT GetCanonicalFormatEtc(FORMATETC* pformatectIn, FORMATETC* pformatetcOut) { 21290 // FIXME: prolly could be better but meh 21291 // import std.stdio; writeln("gcf: ", *pformatectIn); 21292 *pformatetcOut = *pformatectIn; 21293 return S_OK; 21294 } 21295 HRESULT GetData(FORMATETC* pformatetcIn, STGMEDIUM* pmedium) { 21296 foreach(ty; types) { 21297 if(ty == *pformatetcIn) { 21298 auto format = ty.cfFormat; 21299 // import std.stdio; writeln("A: ", *pformatetcIn, "\nB: ", ty); 21300 STGMEDIUM medium; 21301 medium.tymed = TYMED.TYMED_HGLOBAL; 21302 21303 auto sz = handler.dataLength(format); 21304 auto handle = GlobalAlloc(GMEM_MOVEABLE, sz); 21305 if(handle is null) throw new Exception("GlobalAlloc"); 21306 if(auto data = cast(wchar*) GlobalLock(handle)) { 21307 auto slice = data[0 .. sz]; 21308 scope(exit) 21309 GlobalUnlock(handle); 21310 21311 handler.getData(format, cast(ubyte[]) slice[]); 21312 } 21313 21314 21315 medium.hGlobal = handle; // FIXME 21316 *pmedium = medium; 21317 return S_OK; 21318 } 21319 } 21320 return DV_E_FORMATETC; 21321 } 21322 HRESULT GetDataHere(FORMATETC* pformatetcIn, STGMEDIUM* pmedium) { 21323 // import std.stdio; writeln("GDH: ", *pformatetcIn); 21324 return E_NOTIMPL; // FIXME 21325 } 21326 HRESULT QueryGetData(FORMATETC* pformatetc) { 21327 auto search = *pformatetc; 21328 search.tymed &= TYMED.TYMED_HGLOBAL; 21329 foreach(ty; types) 21330 if(ty == search) { 21331 // import std.stdio; writeln("QueryGetData ", search, " ", types[0]); 21332 return S_OK; 21333 } 21334 if(pformatetc.cfFormat==CF_UNICODETEXT) { 21335 //import std.stdio; writeln("QueryGetData FALSE ", search, " ", types[0]); 21336 } 21337 return S_FALSE; 21338 } 21339 HRESULT SetData(FORMATETC* pformatetc, STGMEDIUM* pmedium, BOOL fRelease) { 21340 // import std.stdio; writeln("SetData: "); 21341 return E_NOTIMPL; 21342 } 21343 }; 21344 21345 21346 IDropSource src = new class IDropSource { 21347 ULONG refCount; 21348 ULONG AddRef() { 21349 return ++refCount; 21350 } 21351 ULONG Release() { 21352 return --refCount; 21353 } 21354 HRESULT QueryInterface(const (IID)*riid, LPVOID *ppv) { 21355 if (IID_IUnknown == *riid) { 21356 *ppv = cast(void*) cast(IUnknown) this; 21357 } 21358 else if (IID_IDropSource == *riid) { 21359 *ppv = cast(void*) cast(IDropSource) this; 21360 } 21361 else { 21362 *ppv = null; 21363 return E_NOINTERFACE; 21364 } 21365 21366 AddRef(); 21367 return NOERROR; 21368 } 21369 21370 int QueryContinueDrag(int fEscapePressed, uint grfKeyState) { 21371 if(fEscapePressed) 21372 return DRAGDROP_S_CANCEL; 21373 if(!(grfKeyState & MK_LBUTTON)) 21374 return DRAGDROP_S_DROP; 21375 return S_OK; 21376 } 21377 21378 int GiveFeedback(uint dwEffect) { 21379 return DRAGDROP_S_USEDEFAULTCURSORS; 21380 } 21381 }; 21382 21383 DWORD effect; 21384 21385 if(action == DragAndDropAction.none) assert(0, "Don't drag something with a none effect."); 21386 21387 DROPEFFECT de = win32DragAndDropAction(action); 21388 21389 // I'm not as concerned about the GC here since DoDragDrop blocks so the stack frame still sane the whole time 21390 // but still prolly a FIXME 21391 21392 auto ret = DoDragDrop(obj, src, de, &effect); 21393 /+ 21394 import std.stdio; 21395 if(ret == DRAGDROP_S_DROP) 21396 writeln("drop ", effect); 21397 else if(ret == DRAGDROP_S_CANCEL) 21398 writeln("cancel"); 21399 else if(ret == S_OK) 21400 writeln("ok"); 21401 else writeln(ret); 21402 +/ 21403 21404 return ret; 21405 } 21406 21407 version(Windows) 21408 DROPEFFECT win32DragAndDropAction(DragAndDropAction action) { 21409 DROPEFFECT de; 21410 21411 with(DragAndDropAction) 21412 with(DROPEFFECT) 21413 final switch(action) { 21414 case none: de = DROPEFFECT_NONE; break; 21415 case copy: de = DROPEFFECT_COPY; break; 21416 case move: de = DROPEFFECT_MOVE; break; 21417 case link: de = DROPEFFECT_LINK; break; 21418 case ask: throw new Exception("ask not implemented yet"); 21419 case custom: throw new Exception("custom not implemented yet"); 21420 } 21421 21422 return de; 21423 } 21424 21425 21426 /++ 21427 History: 21428 Added February 19, 2021 21429 +/ 21430 /// Group: drag_and_drop 21431 void enableDragAndDrop(SimpleWindow window, DropHandler handler) { 21432 version(X11) { 21433 auto display = XDisplayConnection.get; 21434 21435 Atom atom = 5; // right??? 21436 21437 XChangeProperty( 21438 display, 21439 window.impl.window, 21440 GetAtom!"XdndAware"(display), 21441 XA_ATOM, 21442 32 /* bits */, 21443 PropModeReplace, 21444 &atom, 21445 1); 21446 21447 window.dropHandler = handler; 21448 } else version(Windows) { 21449 21450 initDnd(); 21451 21452 auto dropTarget = new class (handler) IDropTarget { 21453 DropHandler handler; 21454 this(DropHandler handler) { 21455 this.handler = handler; 21456 } 21457 ULONG refCount; 21458 ULONG AddRef() { 21459 return ++refCount; 21460 } 21461 ULONG Release() { 21462 return --refCount; 21463 } 21464 HRESULT QueryInterface(const (IID)*riid, LPVOID *ppv) { 21465 if (IID_IUnknown == *riid) { 21466 *ppv = cast(void*) cast(IUnknown) this; 21467 } 21468 else if (IID_IDropTarget == *riid) { 21469 *ppv = cast(void*) cast(IDropTarget) this; 21470 } 21471 else { 21472 *ppv = null; 21473 return E_NOINTERFACE; 21474 } 21475 21476 AddRef(); 21477 return NOERROR; 21478 } 21479 21480 21481 // /////////////////// 21482 21483 HRESULT DragEnter(IDataObject pDataObj, DWORD grfKeyState, POINTL pt, DWORD* pdwEffect) { 21484 DropPackage dropPackage = DropPackage(pDataObj); 21485 *pdwEffect = win32DragAndDropAction(handler.dragEnter(&dropPackage)); 21486 return S_OK; // https://docs.microsoft.com/en-us/windows/win32/api/oleidl/nf-oleidl-idroptarget-dragenter 21487 } 21488 21489 HRESULT DragLeave() { 21490 handler.dragLeave(); 21491 // release the IDataObject if needed 21492 return S_OK; 21493 } 21494 21495 HRESULT DragOver(DWORD grfKeyState, POINTL pt, DWORD* pdwEffect) { 21496 auto res = handler.dragOver(Point(pt.x, pt.y)); // FIXME: translate screen coordinates back to window coordinates 21497 21498 *pdwEffect = win32DragAndDropAction(res.action); 21499 // same as DragEnter basically 21500 return S_OK; 21501 } 21502 21503 HRESULT Drop(IDataObject pDataObj, DWORD grfKeyState, POINTL pt, DWORD* pdwEffect) { 21504 DropPackage pkg = DropPackage(pDataObj); 21505 handler.drop(&pkg); 21506 21507 return S_OK; 21508 } 21509 }; 21510 // Windows can hold on to the handler and try to call it 21511 // during which time the GC can't see it. so important to 21512 // manually manage this. At some point i'll FIXME and make 21513 // all my com instances manually managed since they supposed 21514 // to respect the refcount. 21515 import core.memory; 21516 GC.addRoot(cast(void*) dropTarget); 21517 21518 if(RegisterDragDrop(window.impl.hwnd, dropTarget) != S_OK) 21519 throw new Exception("register"); 21520 21521 window.dropHandler = handler; 21522 } else throw new NotYetImplementedException(); 21523 } 21524 21525 21526 21527 static if(UsingSimpledisplayX11) { 21528 21529 enum _NET_WM_STATE_ADD = 1; 21530 enum _NET_WM_STATE_REMOVE = 0; 21531 enum _NET_WM_STATE_TOGGLE = 2; 21532 21533 /// X-specific. Use [SimpleWindow.requestAttention] instead for most cases. 21534 void demandAttention(SimpleWindow window, bool needs = true) { 21535 demandAttention(window.impl.window, needs); 21536 } 21537 21538 /// ditto 21539 void demandAttention(Window window, bool needs = true) { 21540 setNetWmStateAtom(window, GetAtom!("_NET_WM_STATE_DEMANDS_ATTENTION", false)(XDisplayConnection.get), needs); 21541 } 21542 21543 void setNetWmStateAtom(Window window, Atom atom, bool set = true, Atom atom2 = None) { 21544 auto display = XDisplayConnection.get(); 21545 if(atom == None) 21546 return; // non-failure error 21547 //auto atom2 = GetAtom!"_NET_WM_STATE_SHADED"(display); 21548 21549 XClientMessageEvent xclient; 21550 21551 xclient.type = EventType.ClientMessage; 21552 xclient.window = window; 21553 xclient.message_type = GetAtom!"_NET_WM_STATE"(display); 21554 xclient.format = 32; 21555 xclient.data.l[0] = set ? _NET_WM_STATE_ADD : _NET_WM_STATE_REMOVE; 21556 xclient.data.l[1] = atom; 21557 xclient.data.l[2] = atom2; 21558 xclient.data.l[3] = 1; 21559 // [3] == source. 0 == unknown, 1 == app, 2 == else 21560 21561 XSendEvent( 21562 display, 21563 RootWindow(display, DefaultScreen(display)), 21564 false, 21565 EventMask.SubstructureRedirectMask | EventMask.SubstructureNotifyMask, 21566 cast(XEvent*) &xclient 21567 ); 21568 21569 /+ 21570 XChangeProperty( 21571 display, 21572 window.impl.window, 21573 GetAtom!"_NET_WM_STATE"(display), 21574 XA_ATOM, 21575 32 /* bits */, 21576 PropModeAppend, 21577 &atom, 21578 1); 21579 +/ 21580 } 21581 21582 private Atom dndActionAtom(Display* display, DragAndDropAction action) { 21583 Atom actionAtom; 21584 with(DragAndDropAction) 21585 final switch(action) { 21586 case none: actionAtom = None; break; 21587 case copy: actionAtom = GetAtom!"XdndActionCopy"(display); break; 21588 case move: actionAtom = GetAtom!"XdndActionMove"(display); break; 21589 case link: actionAtom = GetAtom!"XdndActionLink"(display); break; 21590 case ask: actionAtom = GetAtom!"XdndActionAsk"(display); break; 21591 case custom: actionAtom = GetAtom!"XdndActionCustom"(display); break; 21592 } 21593 21594 return actionAtom; 21595 } 21596 21597 private int doDragDropX11(SimpleWindow window, X11SetSelectionHandler handler, DragAndDropAction action) { 21598 // FIXME: I need to show user feedback somehow. 21599 auto display = XDisplayConnection.get; 21600 21601 auto actionAtom = dndActionAtom(display, action); 21602 assert(actionAtom, "Don't use action none to accept a drop"); 21603 21604 setX11Selection!"XdndSelection"(window, handler, null); 21605 21606 auto oldKeyHandler = window.handleKeyEvent; 21607 scope(exit) window.handleKeyEvent = oldKeyHandler; 21608 21609 auto oldCharHandler = window.handleCharEvent; 21610 scope(exit) window.handleCharEvent = oldCharHandler; 21611 21612 auto oldMouseHandler = window.handleMouseEvent; 21613 scope(exit) window.handleMouseEvent = oldMouseHandler; 21614 21615 Window[Window] eligibility; // 0 == not eligible, otherwise it is the window id of an eligible child 21616 21617 import core.sys.posix.sys.time; 21618 timeval tv; 21619 gettimeofday(&tv, null); 21620 21621 Time dataTimestamp = tv.tv_sec * 1000 + tv.tv_usec / 1000; 21622 21623 Time lastMouseTimestamp; 21624 21625 bool dnding = true; 21626 Window lastIn = None; 21627 21628 void leave() { 21629 if(lastIn == None) 21630 return; 21631 21632 XEvent ev; 21633 ev.xclient.type = EventType.ClientMessage; 21634 ev.xclient.window = lastIn; 21635 ev.xclient.message_type = GetAtom!("XdndLeave", true)(display); 21636 ev.xclient.format = 32; 21637 ev.xclient.data.l[0] = window.impl.window; 21638 21639 XSendEvent(XDisplayConnection.get, lastIn, false, EventMask.NoEventMask, &ev); 21640 XFlush(display); 21641 21642 lastIn = None; 21643 } 21644 21645 void enter(Window w) { 21646 assert(lastIn == None); 21647 21648 lastIn = w; 21649 21650 XEvent ev; 21651 ev.xclient.type = EventType.ClientMessage; 21652 ev.xclient.window = lastIn; 21653 ev.xclient.message_type = GetAtom!("XdndEnter", true)(display); 21654 ev.xclient.format = 32; 21655 ev.xclient.data.l[0] = window.impl.window; 21656 ev.xclient.data.l[1] = (5 << 24) | 0; // version 5, no more sources. FIXME source types 21657 21658 auto types = handler.availableFormats(); 21659 assert(types.length > 0); 21660 21661 ev.xclient.data.l[2] = types[0]; 21662 if(types.length > 1) 21663 ev.xclient.data.l[3] = types[1]; 21664 if(types.length > 2) 21665 ev.xclient.data.l[4] = types[2]; 21666 21667 // FIXME: other types?!?!? and make sure we skip TARGETS 21668 21669 XSendEvent(XDisplayConnection.get, lastIn, false, EventMask.NoEventMask, &ev); 21670 XFlush(display); 21671 } 21672 21673 void position(int rootX, int rootY) { 21674 assert(lastIn != None); 21675 21676 XEvent ev; 21677 ev.xclient.type = EventType.ClientMessage; 21678 ev.xclient.window = lastIn; 21679 ev.xclient.message_type = GetAtom!("XdndPosition", true)(display); 21680 ev.xclient.format = 32; 21681 ev.xclient.data.l[0] = window.impl.window; 21682 ev.xclient.data.l[1] = 0; // reserved 21683 ev.xclient.data.l[2] = (rootX << 16) | rootY; 21684 ev.xclient.data.l[3] = dataTimestamp; 21685 ev.xclient.data.l[4] = actionAtom; 21686 21687 XSendEvent(XDisplayConnection.get, lastIn, false, EventMask.NoEventMask, &ev); 21688 XFlush(display); 21689 21690 } 21691 21692 void drop() { 21693 XEvent ev; 21694 ev.xclient.type = EventType.ClientMessage; 21695 ev.xclient.window = lastIn; 21696 ev.xclient.message_type = GetAtom!("XdndDrop", true)(display); 21697 ev.xclient.format = 32; 21698 ev.xclient.data.l[0] = window.impl.window; 21699 ev.xclient.data.l[1] = 0; // reserved 21700 ev.xclient.data.l[2] = dataTimestamp; 21701 21702 XSendEvent(XDisplayConnection.get, lastIn, false, EventMask.NoEventMask, &ev); 21703 XFlush(display); 21704 21705 lastIn = None; 21706 dnding = false; 21707 } 21708 21709 // fyi nativeEventHandler can return 0 if it handles it, or otherwise it goes back to the normal handler 21710 // but idk if i should... 21711 21712 window.setEventHandlers( 21713 delegate(KeyEvent ev) { 21714 if(ev.pressed == true && ev.key == Key.Escape) { 21715 // cancel 21716 dnding = false; 21717 } 21718 }, 21719 delegate(MouseEvent ev) { 21720 if(ev.timestamp < lastMouseTimestamp) 21721 return; 21722 21723 lastMouseTimestamp = ev.timestamp; 21724 21725 if(ev.type == MouseEventType.motion) { 21726 auto display = XDisplayConnection.get; 21727 auto root = RootWindow(display, DefaultScreen(display)); 21728 21729 Window topWindow; 21730 int rootX, rootY; 21731 21732 XTranslateCoordinates(display, window.impl.window, root, ev.x, ev.y, &rootX, &rootY, &topWindow); 21733 21734 if(topWindow == None) 21735 return; 21736 21737 top: 21738 if(auto result = topWindow in eligibility) { 21739 auto dropWindow = *result; 21740 if(dropWindow == None) { 21741 leave(); 21742 return; 21743 } 21744 21745 if(dropWindow != lastIn) { 21746 leave(); 21747 enter(dropWindow); 21748 position(rootX, rootY); 21749 } else { 21750 position(rootX, rootY); 21751 } 21752 } else { 21753 // determine eligibility 21754 auto data = cast(Atom[]) getX11PropertyData(topWindow, GetAtom!"XdndAware"(display), XA_ATOM); 21755 if(data.length == 1) { 21756 // in case there is no WM or it isn't reparenting 21757 eligibility[topWindow] = (data[0] == 5) ? topWindow : None; // FIXME I'm supposed to handle older versions too but meh 21758 } else { 21759 21760 Window tryScanChildren(Window search, int maxRecurse) { 21761 // could be reparenting window manager, so gotta check the next few children too 21762 Window child; 21763 int x; 21764 int y; 21765 XTranslateCoordinates(display, window.impl.window, search, ev.x, ev.y, &x, &y, &child); 21766 21767 if(child == None) 21768 return None; 21769 auto data = cast(Atom[]) getX11PropertyData(child, GetAtom!"XdndAware"(display), XA_ATOM); 21770 if(data.length == 1) { 21771 return (data[0] == 5) ? child : None; // FIXME I'm supposed to handle older versions too but meh 21772 } else { 21773 if(maxRecurse) 21774 return tryScanChildren(child, maxRecurse - 1); 21775 else 21776 return None; 21777 } 21778 21779 } 21780 21781 // if a WM puts more than 3 layers on it, like wtf is it doing, screw that. 21782 auto topResult = tryScanChildren(topWindow, 3); 21783 // it is easy to have a false negative due to the mouse going over a WM 21784 // child window like the close button if separate from the frame... so I 21785 // can't really cache negatives, :( 21786 if(topResult != None) { 21787 eligibility[topWindow] = topResult; 21788 goto top; // reload to do the positioning iff eligibility changed lest we endless loop 21789 } 21790 } 21791 21792 } 21793 21794 } else if(ev.type == MouseEventType.buttonReleased) { 21795 drop(); 21796 dnding = false; 21797 } 21798 } 21799 ); 21800 21801 window.grabInput(); 21802 scope(exit) 21803 window.releaseInputGrab(); 21804 21805 21806 EventLoop.get.run(() => dnding); 21807 21808 return 0; 21809 } 21810 21811 /// X-specific 21812 TrueColorImage getWindowNetWmIcon(Window window) { 21813 try { 21814 auto display = XDisplayConnection.get; 21815 21816 auto data = getX11PropertyData (window, GetAtom!"_NET_WM_ICON"(display), XA_CARDINAL); 21817 21818 if (data.length > arch_ulong.sizeof * 2) { 21819 auto meta = cast(arch_ulong[]) (data[0 .. arch_ulong.sizeof * 2]); 21820 // these are an array of rgba images that we have to convert into pixmaps ourself 21821 21822 int width = cast(int) meta[0]; 21823 int height = cast(int) meta[1]; 21824 21825 auto bytes = cast(ubyte[]) (data[arch_ulong.sizeof * 2 .. $]); 21826 21827 static if(arch_ulong.sizeof == 4) { 21828 bytes = bytes[0 .. width * height * 4]; 21829 alias imageData = bytes; 21830 } else static if(arch_ulong.sizeof == 8) { 21831 bytes = bytes[0 .. width * height * 8]; 21832 auto imageData = new ubyte[](4 * width * height); 21833 } else static assert(0); 21834 21835 21836 21837 // this returns ARGB. Remember it is little-endian so 21838 // we have BGRA 21839 // our thing uses RGBA, which in little endian, is ABGR 21840 for(int idx = 0, idx2 = 0; idx < bytes.length; idx += arch_ulong.sizeof, idx2 += 4) { 21841 auto r = bytes[idx + 2]; 21842 auto g = bytes[idx + 1]; 21843 auto b = bytes[idx + 0]; 21844 auto a = bytes[idx + 3]; 21845 21846 imageData[idx2 + 0] = r; 21847 imageData[idx2 + 1] = g; 21848 imageData[idx2 + 2] = b; 21849 imageData[idx2 + 3] = a; 21850 } 21851 21852 return new TrueColorImage(width, height, imageData); 21853 } 21854 21855 return null; 21856 } catch(Exception e) { 21857 return null; 21858 } 21859 } 21860 21861 } /* UsingSimpledisplayX11 */ 21862 21863 21864 void loadBinNameToWindowClassName () { 21865 import core.stdc.stdlib : realloc; 21866 version(linux) { 21867 // args[0] MAY be empty, so we'll just use this 21868 import core.sys.posix.unistd : readlink; 21869 char[1024] ebuf = void; // 1KB should be enough for everyone! 21870 auto len = readlink("/proc/self/exe", ebuf.ptr, ebuf.length); 21871 if (len < 1) return; 21872 } else /*version(Windows)*/ { 21873 import core.runtime : Runtime; 21874 if (Runtime.args.length == 0 || Runtime.args[0].length == 0) return; 21875 auto ebuf = Runtime.args[0]; 21876 auto len = ebuf.length; 21877 } 21878 auto pos = len; 21879 while (pos > 0 && ebuf[pos-1] != '/') --pos; 21880 sdpyWindowClassStr = cast(char*)realloc(sdpyWindowClassStr, len-pos+1); 21881 if (sdpyWindowClassStr is null) return; // oops 21882 sdpyWindowClassStr[0..len-pos+1] = 0; // just in case 21883 sdpyWindowClassStr[0..len-pos] = ebuf[pos..len]; 21884 } 21885 21886 /++ 21887 An interface representing a font that is drawn with custom facilities. 21888 21889 You might want [OperatingSystemFont] instead, which represents 21890 a font loaded and drawn by functions native to the operating system. 21891 21892 WARNING: I might still change this. 21893 +/ 21894 interface DrawableFont : MeasurableFont { 21895 /++ 21896 Please note the point is upperLeft, NOT baseline! This is the point of a bounding box of the string. 21897 21898 Implementations must use the painter's fillColor to draw a rectangle behind the string, 21899 then use the outlineColor to draw the string. It might alpha composite if there's a transparent 21900 fill color, but that's up to the implementation. 21901 +/ 21902 void drawString(ScreenPainter painter, Point upperLeft, in char[] text); 21903 21904 /++ 21905 Requests that the given string is added to the image cache. You should only do this rarely, but 21906 if you have a string that you know will be used over and over again, adding it to a cache can 21907 improve things (assuming the implementation actually has a cache; it is also valid for an implementation 21908 to implement this as a do-nothing method). 21909 +/ 21910 void cacheString(SimpleWindow window, Color foreground, Color background, string text); 21911 } 21912 21913 /++ 21914 Loads a true type font using [arsd.ttf] that can be drawn as images on windows 21915 through a [ScreenPainter]. That module must be compiled in if you choose to use this function. 21916 21917 You should also consider [OperatingSystemFont], which loads and draws a font with 21918 facilities native to the user's operating system. You might also consider 21919 [arsd.ttf.OpenGlLimitedFont] or using [arsd.nanovega] if you are making some kind 21920 of game, as they have their own ways to draw text too. 21921 21922 Be warned: this can be slow, especially on remote connections to the X server, since 21923 it needs to create and transfer bitmaps instead of just text. The [DrawableFont] interface 21924 offers [DrawableFont.cacheString] which can help with this, sometimes. You might want to 21925 experiment in your specific case. 21926 21927 Please note that the return type of [DrawableFont] also includes an implementation of 21928 [MeasurableFont]. 21929 +/ 21930 DrawableFont arsdTtfFont()(in ubyte[] data, int size) { 21931 import arsd.ttf; 21932 static class ArsdTtfFont : DrawableFont { 21933 TtfFont font; 21934 int size; 21935 this(in ubyte[] data, int size) { 21936 font = TtfFont(data); 21937 this.size = size; 21938 21939 21940 auto scale = stbtt_ScaleForPixelHeight(&font.font, size); 21941 int ascent_, descent_, line_gap; 21942 stbtt_GetFontVMetrics(&font.font, &ascent_, &descent_, &line_gap); 21943 21944 int advance, lsb; 21945 stbtt_GetCodepointHMetrics(&font.font, 'x', &advance, &lsb); 21946 xWidth = cast(int) (advance * scale); 21947 stbtt_GetCodepointHMetrics(&font.font, 'M', &advance, &lsb); 21948 MWidth = cast(int) (advance * scale); 21949 } 21950 21951 private int ascent_; 21952 private int descent_; 21953 private int xWidth; 21954 private int MWidth; 21955 21956 bool isMonospace() { 21957 return xWidth == MWidth; 21958 } 21959 int averageWidth() { 21960 return xWidth; 21961 } 21962 int height() { 21963 return size; 21964 } 21965 int ascent() { 21966 return ascent_; 21967 } 21968 int descent() { 21969 return descent_; 21970 } 21971 21972 int stringWidth(scope const(char)[] s, SimpleWindow window = null) { 21973 int width, height; 21974 font.getStringSize(s, size, width, height); 21975 return width; 21976 } 21977 21978 21979 21980 Sprite[string] cache; 21981 21982 void cacheString(SimpleWindow window, Color foreground, Color background, string text) { 21983 auto sprite = new Sprite(window, stringToImage(foreground, background, text)); 21984 cache[text] = sprite; 21985 } 21986 21987 Image stringToImage(Color fg, Color bg, in char[] text) { 21988 int width, height; 21989 auto data = font.renderString(text, size, width, height); 21990 auto image = new TrueColorImage(width, height); 21991 int pos = 0; 21992 foreach(y; 0 .. height) 21993 foreach(x; 0 .. width) { 21994 fg.a = data[0]; 21995 bg.a = 255; 21996 auto color = alphaBlend(fg, bg); 21997 image.imageData.bytes[pos++] = color.r; 21998 image.imageData.bytes[pos++] = color.g; 21999 image.imageData.bytes[pos++] = color.b; 22000 image.imageData.bytes[pos++] = data[0]; 22001 data = data[1 .. $]; 22002 } 22003 assert(data.length == 0); 22004 22005 return Image.fromMemoryImage(image); 22006 } 22007 22008 void drawString(ScreenPainter painter, Point upperLeft, in char[] text) { 22009 Sprite sprite = (text in cache) ? *(text in cache) : null; 22010 22011 auto fg = painter.impl._outlineColor; 22012 auto bg = painter.impl._fillColor; 22013 22014 if(sprite !is null) { 22015 auto w = cast(SimpleWindow) painter.window; 22016 assert(w !is null); 22017 22018 sprite.drawAt(painter, upperLeft); 22019 } else { 22020 painter.drawImage(upperLeft, stringToImage(fg, bg, text)); 22021 } 22022 } 22023 } 22024 22025 return new ArsdTtfFont(data, size); 22026 } 22027 22028 class NotYetImplementedException : Exception { 22029 this(string file = __FILE__, size_t line = __LINE__) { 22030 super("Not yet implemented", file, line); 22031 } 22032 } 22033 22034 /// 22035 __gshared bool librariesSuccessfullyLoaded = true; 22036 /// 22037 __gshared bool openGlLibrariesSuccessfullyLoaded = true; 22038 22039 private mixin template DynamicLoadSupplementalOpenGL(Iface) { 22040 mixin(staticForeachReplacement!Iface); 22041 22042 void loadDynamicLibrary() @nogc { 22043 (cast(void function() @nogc) &loadDynamicLibraryForReal)(); 22044 } 22045 22046 void loadDynamicLibraryForReal() { 22047 foreach(name; __traits(derivedMembers, Iface)) { 22048 mixin("alias tmp = " ~ name ~ ";"); 22049 tmp = cast(typeof(tmp)) glbindGetProcAddress(name); 22050 if(tmp is null) throw new Exception("load failure of function " ~ name ~ " from supplemental OpenGL"); 22051 } 22052 } 22053 } 22054 22055 private const(char)[] staticForeachReplacement(Iface)() pure { 22056 /* 22057 // just this for gdc 9.... 22058 // when i drop support for it and switch to gdc10, we can put this original back for a slight compile time ram decrease 22059 22060 static foreach(name; __traits(derivedMembers, Iface)) 22061 mixin("__gshared typeof(&__traits(getMember, Iface, name)) " ~ name ~ ";"); 22062 */ 22063 22064 char[] code = new char[](__traits(derivedMembers, Iface).length * 64); 22065 size_t pos; 22066 22067 void append(in char[] what) { 22068 if(pos + what.length > code.length) 22069 code.length = (code.length * 3) / 2; 22070 code[pos .. pos + what.length] = what[]; 22071 pos += what.length; 22072 } 22073 22074 foreach(name; __traits(derivedMembers, Iface)) { 22075 append(`__gshared typeof(&__traits(getMember, Iface, "`); 22076 append(name); 22077 append(`")) `); 22078 append(name); 22079 append(";"); 22080 } 22081 22082 return code[0 .. pos]; 22083 } 22084 22085 private mixin template DynamicLoad(Iface, string library, int majorVersion, alias success) { 22086 mixin(staticForeachReplacement!Iface); 22087 22088 private __gshared void* libHandle; 22089 private __gshared bool attempted; 22090 22091 void loadDynamicLibrary() @nogc { 22092 (cast(void function() @nogc) &loadDynamicLibraryForReal)(); 22093 } 22094 22095 bool loadAttempted() { 22096 return attempted; 22097 } 22098 bool loadSuccessful() { 22099 return libHandle !is null; 22100 } 22101 22102 void loadDynamicLibraryForReal() { 22103 attempted = true; 22104 version(Posix) { 22105 import core.sys.posix.dlfcn; 22106 version(OSX) { 22107 version(X11) 22108 libHandle = dlopen("/usr/X11/lib/lib" ~ library ~ ".dylib", RTLD_NOW); 22109 else 22110 libHandle = dlopen(library ~ ".dylib", RTLD_NOW); 22111 } else { 22112 libHandle = dlopen("lib" ~ library ~ ".so", RTLD_NOW); 22113 if(libHandle is null) 22114 libHandle = dlopen(("lib" ~ library ~ ".so." ~ toInternal!string(majorVersion) ~ "\0").ptr, RTLD_NOW); 22115 } 22116 22117 static void* loadsym(void* l, const char* name) { 22118 import core.stdc.stdlib; 22119 if(l is null) 22120 return &abort; 22121 return dlsym(l, name); 22122 } 22123 } else version(Windows) { 22124 import core.sys.windows.winbase; 22125 libHandle = LoadLibrary(library ~ ".dll"); 22126 static void* loadsym(void* l, const char* name) { 22127 import core.stdc.stdlib; 22128 if(l is null) 22129 return &abort; 22130 return GetProcAddress(l, name); 22131 } 22132 } 22133 if(libHandle is null) { 22134 success = false; 22135 //throw new Exception("load failure of library " ~ library); 22136 } 22137 foreach(name; __traits(derivedMembers, Iface)) { 22138 mixin("alias tmp = " ~ name ~ ";"); 22139 tmp = cast(typeof(tmp)) loadsym(libHandle, name); 22140 if(tmp is null) throw new Exception("load failure of function " ~ name ~ " from " ~ library); 22141 } 22142 } 22143 22144 void unloadDynamicLibrary() { 22145 version(Posix) { 22146 import core.sys.posix.dlfcn; 22147 dlclose(libHandle); 22148 } else version(Windows) { 22149 import core.sys.windows.winbase; 22150 FreeLibrary(libHandle); 22151 } 22152 foreach(name; __traits(derivedMembers, Iface)) 22153 mixin(name ~ " = null;"); 22154 } 22155 } 22156 22157 /+ 22158 The GC can be called from any thread, and a lot of cleanup must be done 22159 on the gui thread. Since the GC can interrupt any locks - including being 22160 triggered inside a critical section - it is vital to avoid deadlocks to get 22161 these functions called from the right place. 22162 22163 If the buffer overflows, things are going to get leaked. I'm kinda ok with that 22164 right now. 22165 22166 The cleanup function is run when the event loop gets around to it, which is just 22167 whenever there's something there after it has been woken up for other work. It does 22168 NOT wake up the loop itself - can't risk doing that from inside the GC in another thread. 22169 (Well actually it might be ok but i don't wanna mess with it right now.) 22170 +/ 22171 private struct CleanupQueue { 22172 import core.stdc.stdlib; 22173 22174 void queue(alias func, T...)(T args) { 22175 static struct Args { 22176 T args; 22177 } 22178 static struct RealJob { 22179 Job j; 22180 Args a; 22181 } 22182 static void call(Job* data) { 22183 auto rj = cast(RealJob*) data; 22184 func(rj.a.args); 22185 } 22186 22187 RealJob* thing = cast(RealJob*) malloc(RealJob.sizeof); 22188 thing.j.call = &call; 22189 thing.a.args = args; 22190 22191 buffer[tail++] = cast(Job*) thing; 22192 22193 // FIXME: set overflowed 22194 } 22195 22196 void process() { 22197 const tail = this.tail; 22198 22199 while(tail != head) { 22200 Job* job = cast(Job*) buffer[head++]; 22201 job.call(job); 22202 free(job); 22203 } 22204 22205 if(overflowed) 22206 throw new Exception("cleanup overflowed"); 22207 } 22208 22209 private: 22210 22211 ubyte tail; // must ONLY be written by queue 22212 ubyte head; // must ONLY be written by process 22213 bool overflowed; 22214 22215 static struct Job { 22216 void function(Job*) call; 22217 } 22218 22219 void*[256] buffer; 22220 } 22221 private __gshared CleanupQueue cleanupQueue; 22222 22223 version(X11) 22224 /++ 22225 Returns the custom scaling factor read out of environment["ARSD_SCALING_FACTOR"]. 22226 22227 $(WARNING 22228 This function is exempted from stability guarantees. 22229 ) 22230 +/ 22231 float customScalingFactorForMonitor(int monitorNumber) { 22232 import core.stdc.stdlib; 22233 auto val = getenv("ARSD_SCALING_FACTOR"); 22234 22235 if(val is null) 22236 return 1.0; 22237 22238 char[16] buffer = 0; 22239 int pos; 22240 22241 const(char)* at = val; 22242 22243 foreach(item; 0 .. monitorNumber + 1) { 22244 if(*at == 0) 22245 break; // reuse the last number when we at the end of the string 22246 pos = 0; 22247 while(pos + 1 < buffer.length && *at && *at != ';') { 22248 buffer[pos++] = *at; 22249 at++; 22250 } 22251 if(*at) 22252 at++; // skip the semicolon 22253 buffer[pos] = 0; 22254 } 22255 22256 //sdpyPrintDebugString(buffer[0 .. pos]); 22257 22258 import core.stdc.math; 22259 auto f = atof(buffer.ptr); 22260 22261 if(f <= 0.0 || isnan(f) || isinf(f)) 22262 return 1.0; 22263 22264 return f; 22265 } 22266 22267 void guiAbortProcess(string msg) { 22268 import core.stdc.stdlib; 22269 version(Windows) { 22270 WCharzBuffer t = WCharzBuffer(msg); 22271 MessageBoxW(null, t.ptr, "Program Termination"w.ptr, 0); 22272 } else { 22273 import core.stdc.stdio; 22274 fwrite(msg.ptr, 1, msg.length, stderr); 22275 msg = "\n"; 22276 fwrite(msg.ptr, 1, msg.length, stderr); 22277 fflush(stderr); 22278 } 22279 22280 abort(); 22281 } 22282 22283 private int minInternal(int a, int b) { 22284 return (a < b) ? a : b; 22285 } 22286 22287 private alias scriptable = arsd_jsvar_compatible;