1 // https://dpaste.dzfl.pl/7a77355acaec 2 3 /+ 4 To share some stuff between two opengl threads: 5 windows 6 https://www.khronos.org/opengl/wiki/OpenGL_and_multithreading 7 linux 8 https://stackoverflow.com/questions/18879520/sharing-opengl-objects-between-contexts-on-linux 9 +/ 10 11 12 // Search for: FIXME: leaks if multithreaded gc 13 14 // https://freedesktop.org/wiki/Specifications/XDND/ 15 16 // https://docs.microsoft.com/en-us/windows/win32/dataxchg/html-clipboard-format 17 18 // https://www.x.org/releases/X11R7.7/doc/libXext/dbelib.html 19 // https://www.x.org/releases/X11R7.6/doc/libXext/synclib.html 20 21 22 // on Mac with X11: -L-L/usr/X11/lib 23 24 /+ 25 26 * I might need to set modal hints too _NET_WM_STATE_MODAL and make sure that TRANSIENT_FOR legit works 27 28 Progress bar in taskbar 29 - i can probably just set a property on the window... 30 it sets that prop to an integer 0 .. 100. Taskbar 31 deletes it or window deletes it when it is handled. 32 - prolly display it as a nice little line at the bottom. 33 34 35 from gtk: 36 37 #define PROGRESS_HINT "_NET_WM_XAPP_PROGRESS" 38 #define PROGRESS_PULSE_HINT "_NET_WM_XAPP_PROGRESS_PULSE" 39 40 >+ if (cardinal > 0) 41 >+ { 42 >+ XChangeProperty (GDK_DISPLAY_XDISPLAY (display), 43 >+ xid, 44 >+ gdk_x11_get_xatom_by_name_for_display (display, atom_name), 45 >+ XA_CARDINAL, 32, 46 >+ PropModeReplace, 47 >+ (guchar *) &cardinal, 1); 48 >+ } 49 >+ else 50 >+ { 51 >+ XDeleteProperty (GDK_DISPLAY_XDISPLAY (display), 52 >+ xid, 53 >+ gdk_x11_get_xatom_by_name_for_display (display, atom_name)); 54 >+ } 55 56 from Windows: 57 58 see: https://docs.microsoft.com/en-us/windows/win32/api/shobjidl_core/nn-shobjidl_core-itaskbarlist3 59 60 interface 61 CoCreateInstance( CLSID_TaskbarList, nullptr, CLSCTX_ALL, __uuidof(ITaskbarList3), (LPVOID*)&m_pTL3 ); 62 auto msg = RegisterWindowMessage(TEXT(“TaskbarButtonCreated”)); 63 listen for msg, return TRUE 64 interface->SetProgressState(hwnd, TBPF_NORMAL); 65 interface->SetProgressValue(hwnd, 40, 100); 66 67 68 My new notification system. 69 - use a unix socket? or a x property? or a udp port? 70 - could of course also get on the dbus train but ugh. 71 - it could also reply with the info as a string for easy remote examination. 72 73 +/ 74 75 /* 76 Event Loop would be nices: 77 78 * add on idle - runs when nothing else happens 79 * which can specify how long to yield for 80 * send messages without a recipient window 81 * setTimeout 82 * setInterval 83 */ 84 85 /* 86 Classic games I want to add: 87 * my tetris clone 88 * pac man 89 */ 90 91 /* 92 Text layout needs a lot of work. Plain drawText is useful but too 93 limited. It will need some kind of text context thing which it will 94 update and you can pass it on and get more details out of it. 95 96 It will need a bounding box, a current cursor location that is updated 97 as drawing continues, and various changable facts (which can also be 98 changed on the painter i guess) like font, color, size, background, 99 etc. 100 101 We can also fetch the caret location from it somehow. 102 103 Should prolly be an overload of drawText 104 105 blink taskbar / demand attention cross platform. FlashWindow and demandAttention 106 107 WS_EX_NOACTIVATE 108 WS_CHILD - owner and owned vs parent and child. Does X have something similar? 109 full screen windows. Can just set the atom on X. Windows will be harder. 110 111 moving windows. resizing windows. 112 113 hide cursor, capture cursor, change cursor. 114 115 REMEMBER: simpledisplay does NOT have to do everything! It just needs to make 116 sure the pieces are there to do its job easily and make other jobs possible. 117 */ 118 119 /++ 120 simpledisplay.d (often abbreviated to "sdpy") provides basic cross-platform GUI-related functionality, 121 including creating windows, drawing on them, working with the clipboard, 122 timers, OpenGL, and more. However, it does NOT provide high level GUI 123 widgets. See my minigui.d, an extension to this module, for that 124 functionality. 125 126 simpledisplay provides cross-platform wrapping for Windows and Linux 127 (and perhaps other OSes that use X11), but also does not prevent you 128 from using the underlying facilities if you need them. It has a goal 129 of working efficiently over a remote X link (at least as far as Xlib 130 reasonably allows.) 131 132 simpledisplay depends on [arsd.color|color.d], which should be available from the 133 same place where you got this file. Other than that, however, it has 134 very few dependencies and ones that don't come with the OS and/or the 135 compiler are all opt-in. 136 137 simpledisplay.d's home base is on my arsd repo on Github. The file is: 138 https://github.com/adamdruppe/arsd/blob/master/simpledisplay.d 139 140 simpledisplay is basically stable. I plan to refactor the internals, 141 and may add new features and fix bugs, but It do not expect to 142 significantly change the API. It has been stable a few years already now. 143 144 Installation_instructions: 145 146 `simpledisplay.d` does not have any dependencies outside the 147 operating system and `color.d`, so it should just work most the 148 time, but there are a few caveats on some systems: 149 150 On Win32, you can pass `-L/subsystem:windows` if you don't want a 151 console to be automatically allocated. 152 153 Please note when compiling on Win64, you need to explicitly list 154 `-Lgdi32.lib -Luser32.lib` on the build command. If you want the Windows 155 subsystem too, use `-L/subsystem:windows -L/entry:mainCRTStartup`. 156 157 If using ldc instead of dmd, use `-L/entry:wmainCRTStartup` instead of `mainCRTStartup`; 158 note the "w". 159 160 I provided a `mixin EnableWindowsSubsystem;` helper to do those linker flags for you, 161 but you still need to use dmd -m32mscoff or -m64 (which dub does by default too fyi). 162 See [EnableWindowsSubsystem] for more information. 163 164 $(PITFALL 165 With the Windows subsystem, there is no console, so standard writeln will throw! 166 You can use [sdpyPrintDebugString] instead of stdio writeln instead which will 167 create a console as needed. 168 ) 169 170 On Mac, when compiling with X11, you need XQuartz and -L-L/usr/X11R6/lib passed to dmd. If using the Cocoa implementation on Mac, you need to pass `-L-framework -LCocoa` to dmd. For OpenGL, add `-L-framework -LOpenGL` to the build command. 171 172 On Ubuntu, you might need to install X11 development libraries to 173 successfully link. 174 175 $(CONSOLE 176 $ sudo apt-get install libglc-dev 177 $ sudo apt-get install libx11-dev 178 ) 179 180 181 Jump_list: 182 183 Don't worry, you don't have to read this whole documentation file! 184 185 Check out the [#event-example] and [#Pong-example] to get started quickly. 186 187 The main classes you may want to create are [SimpleWindow], [Timer], 188 [Image], and [Sprite]. 189 190 The main functions you'll want are [setClipboardText] and [getClipboardText]. 191 192 There are also platform-specific functions available such as [XDisplayConnection] 193 and [GetAtom] for X11, among others. 194 195 See the examples and topics list below to learn more. 196 197 $(WARNING 198 There should only be one GUI thread per application, 199 and all windows should be created in it and your 200 event loop should run there. 201 202 To do otherwise is undefined behavior and has no 203 cross platform guarantees. 204 ) 205 206 $(H2 About this documentation) 207 208 The goal here is to give some complete programs as overview examples first, then a look at each major feature with working examples first, then, finally, the inline class and method list will follow. 209 210 Scan for headers for a topic - $(B they will visually stand out) - you're interested in to get started quickly and feel free to copy and paste any example as a starting point for your program. I encourage you to learn the library by experimenting with the examples! 211 212 All examples are provided with no copyright restrictions whatsoever. You do not need to credit me or carry any kind of notice with the source if you copy and paste from them. 213 214 To get started, download `simpledisplay.d` and `color.d` to a working directory. Copy an example info a file called `example.d` and compile using the command given at the top of each example. 215 216 If you need help, email me: destructionator@gmail.com or IRC us, #d on Freenode (I am destructionator or adam_d_ruppe there). If you learn something that isn't documented, I appreciate pull requests on github to this file. 217 218 At points, I will talk about implementation details in the documentation. These are sometimes 219 subject to change, but nevertheless useful to understand what is really going on. You can learn 220 more about some of the referenced things by searching the web for info about using them from C. 221 You can always look at the source of simpledisplay.d too for the most authoritative source on 222 its specific implementation. If you disagree with how I did something, please contact me so we 223 can discuss it! 224 225 $(H2 Using with fibers) 226 227 simpledisplay can be used with [core.thread.Fiber], but be warned many of the functions can use a significant amount of stack space. I recommend at least 64 KB stack for each fiber (just set through the second argument to Fiber's constructor). 228 229 $(H2 Topics) 230 231 $(H3 $(ID topic-windows) Windows) 232 The [SimpleWindow] class is simpledisplay's flagship feature. It represents a single 233 window on the user's screen. 234 235 You may create multiple windows, if the underlying platform supports it. You may check 236 `static if(multipleWindowsSupported)` at compile time, or catch exceptions thrown by 237 SimpleWindow's constructor at runtime to handle those cases. 238 239 A single running event loop will handle as many windows as needed. 240 241 $(H3 $(ID topic-event-loops) Event loops) 242 The simpledisplay event loop is designed to handle common cases easily while being extensible for more advanced cases, or replaceable by other libraries. 243 244 The most common scenario is creating a window, then calling [SimpleWindow.eventLoop|window.eventLoop] when setup is complete. You can pass several handlers to the `eventLoop` method right there: 245 246 --- 247 // dmd example.d simpledisplay.d color.d 248 import arsd.simpledisplay; 249 void main() { 250 auto window = new SimpleWindow(200, 200); 251 window.eventLoop(0, 252 delegate (dchar) { /* got a character key press */ } 253 ); 254 } 255 --- 256 257 $(TIP If you get a compile error saying "I can't use this event handler", the most common thing in my experience is passing a function instead of a delegate. The simple solution is to use the `delegate` keyword, like I did in the example above.) 258 259 On Linux, the event loop is implemented with the `epoll` system call for efficiency an extensibility to other files. On Windows, it runs a traditional `GetMessage` + `DispatchMessage` loop, with a call to `SleepEx` in each iteration to allow the thread to enter an alertable wait state regularly, primarily so Overlapped I/O callbacks will get a chance to run. 260 261 On Linux, simpledisplay also supports my (deprecated) [arsd.eventloop] module. Compile your program, including the eventloop.d file, with the `-version=with_eventloop` switch. 262 263 It should be possible to integrate simpledisplay with vibe.d as well, though I haven't tried. 264 265 You can also run the event loop independently of a window, with [EventLoop.run|EventLoop.get.run], though since it will automatically terminate when there are no open windows, you will want to have one anyway. 266 267 $(H3 $(ID topic-notification-areas) Notification area (aka systray) icons) 268 Notification area icons are currently implemented on X11 and Windows. On X11, it defaults to using `libnotify` to show bubbles, if available, and will do a custom bubble window if not. You can `version=without_libnotify` to avoid this run-time dependency, if you like. 269 270 See the [NotificationAreaIcon] class. 271 272 $(H3 $(ID topic-input-handling) Input handling) 273 There are event handlers for low-level keyboard and mouse events, and higher level handlers for character events. 274 275 See [SimpleWindow.handleCharEvent], [SimpleWindow.handleKeyEvent], [SimpleWindow.handleMouseEvent]. 276 277 $(H3 $(ID topic-2d-drawing) 2d Drawing) 278 To draw on your window, use the [SimpleWindow.draw] method. It returns a [ScreenPainter] structure with drawing methods. 279 280 Important: `ScreenPainter` double-buffers and will not actually update the window until its destructor is run. Always ensure the painter instance goes out-of-scope before proceeding. You can do this by calling it inside an event handler, a timer callback, or an small scope inside main. For example: 281 282 --- 283 // dmd example.d simpledisplay.d color.d 284 import arsd.simpledisplay; 285 void main() { 286 auto window = new SimpleWindow(200, 200); 287 { // introduce sub-scope 288 auto painter = window.draw(); // begin drawing 289 /* draw here */ 290 painter.outlineColor = Color.red; 291 painter.fillColor = Color.black; 292 painter.drawRectangle(Point(0, 0), 200, 200); 293 } // end scope, calling `painter`'s destructor, drawing to the screen. 294 window.eventLoop(0); // handle events 295 } 296 --- 297 298 Painting is done based on two color properties, a pen and a brush. 299 300 At this time, the 2d drawing does not support alpha blending, except for the [Sprite] class. If you need that, use a 2d OpenGL context instead. 301 302 FIXME Add example of 2d opengl drawing here. 303 $(H3 $(ID topic-3d-drawing) 3d Drawing (or 2d with OpenGL)) 304 simpledisplay can create OpenGL contexts on your window. It works quite differently than 2d drawing. 305 306 Note that it is still possible to draw 2d on top of an OpenGL window, using the `draw` method, though I don't recommend it. 307 308 To start, you create a [SimpleWindow] with OpenGL enabled by passing the argument [OpenGlOptions.yes] to the constructor. 309 310 Next, you set [SimpleWindow.redrawOpenGlScene|window.redrawOpenGlScene] to a delegate which draws your frame. 311 312 To force a redraw of the scene, call [SimpleWindow.redrawOpenGlSceneNow|window.redrawOpenGlSceneNow()] or to queue a redraw after processing the next batch of pending events, use [SimpleWindow.redrawOpenGlSceneSoon|window.redrawOpenGlSceneSoon]. 313 314 simpledisplay supports both old-style `glBegin` and newer-style shader-based code all through its built-in bindings. See the next section of the docs to see a shader-based program. 315 316 This example program will draw a rectangle on your window using old-style OpenGL with a pulsating color: 317 318 --- 319 import arsd.simpledisplay; 320 321 void main() { 322 auto window = new SimpleWindow(800, 600, "opengl 1", OpenGlOptions.yes, Resizability.allowResizing); 323 324 float otherColor = 0.0; 325 float colorDelta = 0.05; 326 327 window.redrawOpenGlScene = delegate() { 328 glLoadIdentity(); 329 glBegin(GL_QUADS); 330 331 glColor3f(1.0, otherColor, 0); 332 glVertex3f(-0.8, -0.8, 0); 333 334 glColor3f(1.0, otherColor, 1.0); 335 glVertex3f(0.8, -0.8, 0); 336 337 glColor3f(0, 1.0, otherColor); 338 glVertex3f(0.8, 0.8, 0); 339 340 glColor3f(otherColor, 0, 1.0); 341 glVertex3f(-0.8, 0.8, 0); 342 343 glEnd(); 344 }; 345 346 window.eventLoop(50, () { 347 otherColor += colorDelta; 348 if(otherColor > 1.0) { 349 otherColor = 1.0; 350 colorDelta = -0.05; 351 } 352 if(otherColor < 0) { 353 otherColor = 0; 354 colorDelta = 0.05; 355 } 356 // at the end of the timer, we have to request a redraw 357 // or we won't see the changes. 358 window.redrawOpenGlSceneSoon(); 359 }); 360 } 361 --- 362 363 My [arsd.game] module has some helpers for using old-style opengl to make 2D windows too. See: [arsd.game.create2dWindow]. 364 $(H3 $(ID topic-modern-opengl) Modern OpenGL) 365 simpledisplay's opengl support, by default, is for "legacy" opengl. To use "modern" functions, you must opt-into them with a little more setup. But the library provides helpers for this too. 366 367 This example program shows how you can set up a shader to draw a rectangle: 368 369 --- 370 module opengl3test; 371 import arsd.simpledisplay; 372 373 // based on https://learnopengl.com/Getting-started/Hello-Triangle 374 375 void main() { 376 // First thing we do, before creating the window, is declare what version we want. 377 setOpenGLContextVersion(3, 3); 378 // turning off legacy compat is required to use version 3.3 and newer 379 openGLContextCompatible = false; 380 381 uint VAO; 382 OpenGlShader shader; 383 384 // then we can create the window. 385 auto window = new SimpleWindow(800, 600, "opengl 3", OpenGlOptions.yes, Resizability.allowResizing); 386 387 // additional setup needs to be done when it is visible, simpledisplay offers a property 388 // for exactly that: 389 window.visibleForTheFirstTime = delegate() { 390 // now with the window loaded, we can start loading the modern opengl functions. 391 392 // you MUST set the context first. 393 window.setAsCurrentOpenGlContext; 394 // then load the remainder of the library 395 gl3.loadDynamicLibrary(); 396 397 // now you can create the shaders, etc. 398 shader = new OpenGlShader( 399 OpenGlShader.Source(GL_VERTEX_SHADER, ` 400 #version 330 core 401 layout (location = 0) in vec3 aPos; 402 void main() { 403 gl_Position = vec4(aPos.x, aPos.y, aPos.z, 1.0); 404 } 405 `), 406 OpenGlShader.Source(GL_FRAGMENT_SHADER, ` 407 #version 330 core 408 out vec4 FragColor; 409 uniform vec4 mycolor; 410 void main() { 411 FragColor = mycolor; 412 } 413 `), 414 ); 415 416 // and do whatever other setup you want. 417 418 float[] vertices = [ 419 0.5f, 0.5f, 0.0f, // top right 420 0.5f, -0.5f, 0.0f, // bottom right 421 -0.5f, -0.5f, 0.0f, // bottom left 422 -0.5f, 0.5f, 0.0f // top left 423 ]; 424 uint[] indices = [ // note that we start from 0! 425 0, 1, 3, // first Triangle 426 1, 2, 3 // second Triangle 427 ]; 428 uint VBO, EBO; 429 glGenVertexArrays(1, &VAO); 430 // bind the Vertex Array Object first, then bind and set vertex buffer(s), and then configure vertex attributes(s). 431 glBindVertexArray(VAO); 432 433 glGenBuffers(1, &VBO); 434 glGenBuffers(1, &EBO); 435 436 glBindBuffer(GL_ARRAY_BUFFER, VBO); 437 glBufferDataSlice(GL_ARRAY_BUFFER, vertices, GL_STATIC_DRAW); 438 439 glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, EBO); 440 glBufferDataSlice(GL_ELEMENT_ARRAY_BUFFER, indices, GL_STATIC_DRAW); 441 442 glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, 3 * float.sizeof, null); 443 glEnableVertexAttribArray(0); 444 445 // the library will set the initial viewport and trigger our first draw, 446 // so these next two lines are NOT needed. they are just here as comments 447 // to show what would happen next. 448 449 // glViewport(0, 0, window.width, window.height); 450 // window.redrawOpenGlSceneNow(); 451 }; 452 453 // this delegate is called any time the window needs to be redrawn or if you call `window.redrawOpenGlSceneNow;` 454 // it is our render method. 455 window.redrawOpenGlScene = delegate() { 456 glClearColor(0.2f, 0.3f, 0.3f, 1.0f); 457 glClear(GL_COLOR_BUFFER_BIT); 458 459 glUseProgram(shader.shaderProgram); 460 461 // the shader helper class has methods to set uniforms too 462 shader.uniforms.mycolor.opAssign(1.0, 1.0, 0, 1.0); 463 464 glBindVertexArray(VAO); 465 glDrawElements(GL_TRIANGLES, 6, GL_UNSIGNED_INT, null); 466 }; 467 468 window.eventLoop(0); 469 } 470 --- 471 472 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. 473 474 475 $(H3 $(ID topic-images) Displaying images) 476 You can also load PNG images using [arsd.png]. 477 478 --- 479 // dmd example.d simpledisplay.d color.d png.d 480 import arsd.simpledisplay; 481 import arsd.png; 482 483 void main() { 484 auto image = Image.fromMemoryImage(readPng("image.png")); 485 displayImage(image); 486 } 487 --- 488 489 Compile with `dmd example.d simpledisplay.d png.d`. 490 491 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. 492 493 $(H3 $(ID topic-sprites) Sprites) 494 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. 495 496 [Sprite] is also the only facility that currently supports alpha blending without using OpenGL . 497 498 $(H3 $(ID topic-clipboard) Clipboard) 499 The free functions [getClipboardText] and [setClipboardText] consist of simpledisplay's cross-platform clipboard support at this time. 500 501 It also has helpers for handling X-specific events. 502 503 $(H3 $(ID topic-dnd) Drag and Drop) 504 See [enableDragAndDrop] and [draggable]. 505 506 $(H3 $(ID topic-timers) Timers) 507 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]. 508 509 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. 510 511 --- 512 import arsd.simpledisplay; 513 514 void main() { 515 auto window = new SimpleWindow(400, 400); 516 // every 100 ms, it will draw a random line 517 // on the window. 518 window.eventLoop(100, { 519 auto painter = window.draw(); 520 521 import std.random; 522 // random color 523 painter.outlineColor = Color(uniform(0, 256), uniform(0, 256), uniform(0, 256)); 524 // random line 525 painter.drawLine( 526 Point(uniform(0, window.width), uniform(0, window.height)), 527 Point(uniform(0, window.width), uniform(0, window.height))); 528 529 }); 530 } 531 --- 532 533 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. 534 535 The pulse timer and instances of the [Timer] class may be combined at will. 536 537 --- 538 import arsd.simpledisplay; 539 540 void main() { 541 auto window = new SimpleWindow(400, 400); 542 auto timer = new Timer(1000, delegate { 543 auto painter = window.draw(); 544 painter.clear(); 545 }); 546 547 window.eventLoop(0); 548 } 549 --- 550 551 Timers are currently only implemented on Windows, using `SetTimer` and Linux, using `timerfd_create`. These deliver timeout messages through your application event loop. 552 553 $(H3 $(ID topic-os-helpers) OS-specific helpers) 554 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. 555 556 See also: `xwindows.d` from my github. 557 558 $(H3 $(ID topic-os-extension) Extending with OS-specific functionality) 559 `handleNativeEvent` and `handleNativeGlobalEvent`. 560 561 $(H3 $(ID topic-integration) Integration with other libraries) 562 Integration with a third-party event loop is possible. 563 564 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. 565 566 $(H3 $(ID topic-guis) GUI widgets) 567 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! 568 569 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. 570 571 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.) 572 573 minigui still needs a lot of work to be finished at this time, but it already offers a number of useful classes. 574 575 $(H2 Platform-specific tips and tricks) 576 577 X_tips: 578 579 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. 580 581 Windows_tips: 582 583 You can add icons or manifest files to your exe using a resource file. 584 585 To create a Windows .ico file, use the gimp or something. I'll write a helper 586 program later. 587 588 Create `yourapp.rc`: 589 590 ```rc 591 1 ICON filename.ico 592 CREATEPROCESS_MANIFEST_RESOURCE_ID RT_MANIFEST "YourApp.exe.manifest" 593 ``` 594 595 And `yourapp.exe.manifest`: 596 597 ```xml 598 <?xml version="1.0" encoding="UTF-8" standalone="yes"?> 599 <assembly xmlns="urn:schemas-microsoft-com:asm.v1" manifestVersion="1.0"> 600 <assemblyIdentity 601 version="1.0.0.0" 602 processorArchitecture="*" 603 name="CompanyName.ProductName.YourApplication" 604 type="win32" 605 /> 606 <description>Your application description here.</description> 607 <dependency> 608 <dependentAssembly> 609 <assemblyIdentity 610 type="win32" 611 name="Microsoft.Windows.Common-Controls" 612 version="6.0.0.0" 613 processorArchitecture="*" 614 publicKeyToken="6595b64144ccf1df" 615 language="*" 616 /> 617 </dependentAssembly> 618 </dependency> 619 <application xmlns="urn:schemas-microsoft-com:asm.v3"> 620 <windowsSettings> 621 <dpiAware xmlns="http://schemas.microsoft.com/SMI/2005/WindowsSettings">true/pm</dpiAware> <!-- old style --> 622 <dpiAwareness xmlns="http://schemas.microsoft.com/SMI/2016/WindowsSettings">PerMonitorV2</dpiAwareness> <!-- new style --> 623 <!-- Un-comment the line below to enable GDI-scaling in this project. This will enable text --> 624 <!-- to render crisply in DPI-unaware contexts --> 625 <!--<gdiScaling xmlns="http://schemas.microsoft.com/SMI/2017/WindowsSettings">true</gdiScaling>--> 626 </windowsSettings> 627 </application> 628 </assembly> 629 ``` 630 631 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`. 632 633 Doing this lets you opt into various new things since Windows XP. 634 635 See: https://docs.microsoft.com/en-us/windows/win32/SbsCs/application-manifests 636 637 $(H2 Tips) 638 639 $(H3 Name conflicts) 640 641 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: 642 643 --- 644 static import sdpy = arsd.simpledisplay; 645 import arsd.simpledisplay : SimpleWindow; 646 647 void main() { 648 auto window = new SimpleWindow(); 649 sdpy.EventLoop.get.run(); 650 } 651 --- 652 653 $(H2 $(ID developer-notes) Developer notes) 654 655 I don't have a Mac, so that code isn't maintained. I would like to have a Cocoa 656 implementation though. 657 658 The NativeSimpleWindowImplementation and NativeScreenPainterImplementation both 659 suck. If I was rewriting it, I wouldn't do it that way again. 660 661 This file must not have any more required dependencies. If you need bindings, add 662 them right to this file. Once it gets into druntime and is there for a while, remove 663 bindings from here to avoid conflicts (or put them in an appropriate version block 664 so it continues to just work on old dmd), but wait a couple releases before making the 665 transition so this module remains usable with older versions of dmd. 666 667 You may have optional dependencies if needed by putting them in version blocks or 668 template functions. You may also extend the module with other modules with UFCS without 669 actually editing this - that is nice to do if you can. 670 671 Try to make functions work the same way across operating systems. I typically make 672 it thinly wrap Windows, then emulate that on Linux. 673 674 A goal of this is to keep a gui hello world to less than 250 KB. This means avoiding 675 Phobos! So try to avoid it. 676 677 See more comments throughout the source. 678 679 I realize this file is fairly large, but over half that is just bindings at the bottom 680 or documentation at the top. Some of the classes are a bit big too, but hopefully easy 681 to understand. I suggest you jump around the source by looking for a particular 682 declaration you're interested in, like `class SimpleWindow` using your editor's search 683 function, then look at one piece at a time. 684 685 Authors: Adam D. Ruppe with the help of others. If you need help, please email me with 686 destructionator@gmail.com or find me on IRC. Our channel is #d on Freenode and you can 687 ping me, adam_d_ruppe, and I'll usually see it if I'm around. 688 689 I live in the eastern United States, so I will most likely not be around at night in 690 that US east timezone. 691 692 License: Copyright Adam D. Ruppe, 2011-2021. Released under the Boost Software License. 693 694 Building documentation: use my adrdox generator, `dub run adrdox`. 695 696 Examples: 697 698 $(DIV $(ID Event-example)) 699 $(H3 $(ID event-example) Event example) 700 This program creates a window and draws events inside them as they 701 happen, scrolling the text in the window as needed. Run this program 702 and experiment to get a feel for where basic input events take place 703 in the library. 704 705 --- 706 // dmd example.d simpledisplay.d color.d 707 import arsd.simpledisplay; 708 import std.conv; 709 710 void main() { 711 auto window = new SimpleWindow(Size(500, 500), "Event example - simpledisplay.d"); 712 713 int y = 0; 714 715 void addLine(string text) { 716 auto painter = window.draw(); 717 718 if(y + painter.fontHeight >= window.height) { 719 painter.scrollArea(Point(0, 0), window.width, window.height, 0, painter.fontHeight); 720 y -= painter.fontHeight; 721 } 722 723 painter.outlineColor = Color.red; 724 painter.fillColor = Color.black; 725 painter.drawRectangle(Point(0, y), window.width, painter.fontHeight); 726 727 painter.outlineColor = Color.white; 728 729 painter.drawText(Point(10, y), text); 730 731 y += painter.fontHeight; 732 } 733 734 window.eventLoop(1000, 735 () { 736 addLine("Timer went off!"); 737 }, 738 (KeyEvent event) { 739 addLine(to!string(event)); 740 }, 741 (MouseEvent event) { 742 addLine(to!string(event)); 743 }, 744 (dchar ch) { 745 addLine(to!string(ch)); 746 } 747 ); 748 } 749 --- 750 751 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. 752 753 $(COMMENT 754 This program displays a pie chart. Clicking on a color will increase its share of the pie. 755 756 --- 757 758 --- 759 ) 760 761 History: 762 simpledisplay was stand alone until about 2015. It then added a dependency on [arsd.color] and changed its name to `arsd.simpledisplay`. 763 764 On March 4, 2023 (dub v11.0), it started importing [arsd.core] as well, making that a build-time requirement. 765 +/ 766 module arsd.simpledisplay; 767 768 import arsd.core; 769 770 // FIXME: tetris demo 771 // FIXME: space invaders demo 772 // FIXME: asteroids demo 773 774 /++ $(ID Pong-example) 775 $(H3 Pong) 776 777 This program creates a little Pong-like game. Player one is controlled 778 with the keyboard. Player two is controlled with the mouse. It demos 779 the pulse timer, event handling, and some basic drawing. 780 +/ 781 version(demos) 782 unittest { 783 // dmd example.d simpledisplay.d color.d 784 import arsd.simpledisplay; 785 786 enum paddleMovementSpeed = 8; 787 enum paddleHeight = 48; 788 789 void main() { 790 auto window = new SimpleWindow(600, 400, "Pong game!"); 791 792 int playerOnePosition, playerTwoPosition; 793 int playerOneMovement, playerTwoMovement; 794 int playerOneScore, playerTwoScore; 795 796 int ballX, ballY; 797 int ballDx, ballDy; 798 799 void serve() { 800 import std.random; 801 802 ballX = window.width / 2; 803 ballY = window.height / 2; 804 ballDx = uniform(-4, 4) * 3; 805 ballDy = uniform(-4, 4) * 3; 806 if(ballDx == 0) 807 ballDx = uniform(0, 2) == 0 ? 3 : -3; 808 } 809 810 serve(); 811 812 window.eventLoop(50, // set a 50 ms timer pulls 813 // This runs once per timer pulse 814 delegate () { 815 auto painter = window.draw(); 816 817 painter.clear(); 818 819 // Update everyone's motion 820 playerOnePosition += playerOneMovement; 821 playerTwoPosition += playerTwoMovement; 822 823 ballX += ballDx; 824 ballY += ballDy; 825 826 // Bounce off the top and bottom edges of the window 827 if(ballY + 7 >= window.height) 828 ballDy = -ballDy; 829 if(ballY - 8 <= 0) 830 ballDy = -ballDy; 831 832 // Bounce off the paddle, if it is in position 833 if(ballX - 8 <= 16) { 834 if(ballY + 7 > playerOnePosition && ballY - 8 < playerOnePosition + paddleHeight) { 835 ballDx = -ballDx + 1; // add some speed to keep it interesting 836 ballDy += playerOneMovement; // and y movement based on your controls too 837 ballX = 24; // move it past the paddle so it doesn't wiggle inside 838 } else { 839 // Missed it 840 playerTwoScore ++; 841 serve(); 842 } 843 } 844 845 if(ballX + 7 >= window.width - 16) { // do the same thing but for player 1 846 if(ballY + 7 > playerTwoPosition && ballY - 8 < playerTwoPosition + paddleHeight) { 847 ballDx = -ballDx - 1; 848 ballDy += playerTwoMovement; 849 ballX = window.width - 24; 850 } else { 851 // Missed it 852 playerOneScore ++; 853 serve(); 854 } 855 } 856 857 // Draw the paddles 858 painter.outlineColor = Color.black; 859 painter.drawLine(Point(16, playerOnePosition), Point(16, playerOnePosition + paddleHeight)); 860 painter.drawLine(Point(window.width - 16, playerTwoPosition), Point(window.width - 16, playerTwoPosition + paddleHeight)); 861 862 // Draw the ball 863 painter.fillColor = Color.red; 864 painter.outlineColor = Color.yellow; 865 painter.drawEllipse(Point(ballX - 8, ballY - 8), Point(ballX + 7, ballY + 7)); 866 867 // Draw the score 868 painter.outlineColor = Color.blue; 869 import std.conv; 870 painter.drawText(Point(64, 4), to!string(playerOneScore)); 871 painter.drawText(Point(window.width - 64, 4), to!string(playerTwoScore)); 872 873 }, 874 delegate (KeyEvent event) { 875 // Player 1's controls are the arrow keys on the keyboard 876 if(event.key == Key.Down) 877 playerOneMovement = event.pressed ? paddleMovementSpeed : 0; 878 if(event.key == Key.Up) 879 playerOneMovement = event.pressed ? -paddleMovementSpeed : 0; 880 881 }, 882 delegate (MouseEvent event) { 883 // Player 2's controls are mouse movement while the left button is held down 884 if(event.type == MouseEventType.motion && (event.modifierState & ModifierState.leftButtonDown)) { 885 if(event.dy > 0) 886 playerTwoMovement = paddleMovementSpeed; 887 else if(event.dy < 0) 888 playerTwoMovement = -paddleMovementSpeed; 889 } else { 890 playerTwoMovement = 0; 891 } 892 } 893 ); 894 } 895 } 896 897 /++ $(H3 $(ID example-minesweeper) Minesweeper) 898 899 This minesweeper demo shows how we can implement another classic 900 game with simpledisplay and shows some mouse input and basic output 901 code. 902 +/ 903 version(demos) 904 unittest { 905 import arsd.simpledisplay; 906 907 enum GameSquare { 908 mine = 0, 909 clear, 910 m1, m2, m3, m4, m5, m6, m7, m8 911 } 912 913 enum UserSquare { 914 unknown, 915 revealed, 916 flagged, 917 questioned 918 } 919 920 enum GameState { 921 inProgress, 922 lose, 923 win 924 } 925 926 GameSquare[] board; 927 UserSquare[] userState; 928 GameState gameState; 929 int boardWidth; 930 int boardHeight; 931 932 bool isMine(int x, int y) { 933 if(x < 0 || y < 0 || x >= boardWidth || y >= boardHeight) 934 return false; 935 return board[y * boardWidth + x] == GameSquare.mine; 936 } 937 938 GameState reveal(int x, int y) { 939 if(board[y * boardWidth + x] == GameSquare.clear) { 940 floodFill(userState, boardWidth, boardHeight, 941 UserSquare.unknown, UserSquare.revealed, 942 x, y, 943 (x, y) { 944 if(board[y * boardWidth + x] == GameSquare.clear) 945 return true; 946 else { 947 userState[y * boardWidth + x] = UserSquare.revealed; 948 return false; 949 } 950 }); 951 } else { 952 userState[y * boardWidth + x] = UserSquare.revealed; 953 if(isMine(x, y)) 954 return GameState.lose; 955 } 956 957 foreach(state; userState) { 958 if(state == UserSquare.unknown || state == UserSquare.questioned) 959 return GameState.inProgress; 960 } 961 962 return GameState.win; 963 } 964 965 void initializeBoard(int width, int height, int numberOfMines) { 966 boardWidth = width; 967 boardHeight = height; 968 board.length = width * height; 969 970 userState.length = width * height; 971 userState[] = UserSquare.unknown; 972 973 import std.algorithm, std.random, std.range; 974 975 board[] = GameSquare.clear; 976 977 foreach(minePosition; randomSample(iota(0, board.length), numberOfMines)) 978 board[minePosition] = GameSquare.mine; 979 980 int x; 981 int y; 982 foreach(idx, ref square; board) { 983 if(square == GameSquare.clear) { 984 int danger = 0; 985 danger += isMine(x-1, y-1)?1:0; 986 danger += isMine(x-1, y)?1:0; 987 danger += isMine(x-1, y+1)?1:0; 988 danger += isMine(x, y-1)?1:0; 989 danger += isMine(x, y+1)?1:0; 990 danger += isMine(x+1, y-1)?1:0; 991 danger += isMine(x+1, y)?1:0; 992 danger += isMine(x+1, y+1)?1:0; 993 994 square = cast(GameSquare) (danger + 1); 995 } 996 997 x++; 998 if(x == width) { 999 x = 0; 1000 y++; 1001 } 1002 } 1003 } 1004 1005 void redraw(SimpleWindow window) { 1006 import std.conv; 1007 1008 auto painter = window.draw(); 1009 1010 painter.clear(); 1011 1012 final switch(gameState) with(GameState) { 1013 case inProgress: 1014 break; 1015 case win: 1016 painter.fillColor = Color.green; 1017 painter.drawRectangle(Point(0, 0), window.width, window.height); 1018 return; 1019 case lose: 1020 painter.fillColor = Color.red; 1021 painter.drawRectangle(Point(0, 0), window.width, window.height); 1022 return; 1023 } 1024 1025 int x = 0; 1026 int y = 0; 1027 1028 foreach(idx, square; board) { 1029 auto state = userState[idx]; 1030 1031 final switch(state) with(UserSquare) { 1032 case unknown: 1033 painter.outlineColor = Color.black; 1034 painter.fillColor = Color(128,128,128); 1035 1036 painter.drawRectangle( 1037 Point(x * 20, y * 20), 1038 20, 20 1039 ); 1040 break; 1041 case revealed: 1042 if(square == GameSquare.clear) { 1043 painter.outlineColor = Color.white; 1044 painter.fillColor = Color.white; 1045 1046 painter.drawRectangle( 1047 Point(x * 20, y * 20), 1048 20, 20 1049 ); 1050 } else { 1051 painter.outlineColor = Color.black; 1052 painter.fillColor = Color.white; 1053 1054 painter.drawText( 1055 Point(x * 20, y * 20), 1056 to!string(square)[1..2], 1057 Point(x * 20 + 20, y * 20 + 20), 1058 TextAlignment.Center | TextAlignment.VerticalCenter); 1059 } 1060 break; 1061 case flagged: 1062 painter.outlineColor = Color.black; 1063 painter.fillColor = Color.red; 1064 painter.drawRectangle( 1065 Point(x * 20, y * 20), 1066 20, 20 1067 ); 1068 break; 1069 case questioned: 1070 painter.outlineColor = Color.black; 1071 painter.fillColor = Color.yellow; 1072 painter.drawRectangle( 1073 Point(x * 20, y * 20), 1074 20, 20 1075 ); 1076 break; 1077 } 1078 1079 x++; 1080 if(x == boardWidth) { 1081 x = 0; 1082 y++; 1083 } 1084 } 1085 1086 } 1087 1088 void main() { 1089 auto window = new SimpleWindow(200, 200); 1090 1091 initializeBoard(10, 10, 10); 1092 1093 redraw(window); 1094 window.eventLoop(0, 1095 delegate (MouseEvent me) { 1096 if(me.type != MouseEventType.buttonPressed) 1097 return; 1098 auto x = me.x / 20; 1099 auto y = me.y / 20; 1100 if(x >= 0 && x < boardWidth && y >= 0 && y < boardHeight) { 1101 if(me.button == MouseButton.left) { 1102 gameState = reveal(x, y); 1103 } else { 1104 userState[y*boardWidth+x] = UserSquare.flagged; 1105 } 1106 redraw(window); 1107 } 1108 } 1109 ); 1110 } 1111 } 1112 1113 import arsd.core; 1114 1115 // FIXME: tetris demo 1116 // FIXME: space invaders demo 1117 // FIXME: asteroids demo 1118 1119 1120 /* 1121 version(OSX) { 1122 version=without_opengl; 1123 version=allow_unimplemented_features; 1124 version=OSXCocoa; 1125 pragma(linkerDirective, "-framework Cocoa"); 1126 } 1127 */ 1128 1129 version(without_opengl) { 1130 enum SdpyIsUsingIVGLBinds = false; 1131 } else /*version(Posix)*/ { 1132 static if (__traits(compiles, (){import iv.glbinds;})) { 1133 enum SdpyIsUsingIVGLBinds = true; 1134 public import iv.glbinds; 1135 //pragma(msg, "SDPY: using iv.glbinds"); 1136 } else { 1137 enum SdpyIsUsingIVGLBinds = false; 1138 } 1139 //} else { 1140 // enum SdpyIsUsingIVGLBinds = false; 1141 } 1142 1143 1144 version(Windows) { 1145 //import core.sys.windows.windows; 1146 import core.sys.windows.winnls; 1147 import core.sys.windows.windef; 1148 import core.sys.windows.basetyps; 1149 import core.sys.windows.winbase; 1150 import core.sys.windows.winuser; 1151 import core.sys.windows.shellapi; 1152 import core.sys.windows.wingdi; 1153 static import gdi = core.sys.windows.wingdi; // so i 1154 1155 pragma(lib, "gdi32"); 1156 pragma(lib, "user32"); 1157 1158 // for AlphaBlend... a breaking change.... 1159 version(CRuntime_DigitalMars) { } else 1160 pragma(lib, "msimg32"); 1161 } else version (linux) { 1162 //k8: this is hack for rdmd. sorry. 1163 static import core.sys.linux.epoll; 1164 static import core.sys.linux.timerfd; 1165 } 1166 1167 1168 // FIXME: icons on Windows don't look quite right, I think the transparency mask is off. 1169 1170 // http://wiki.dlang.org/Simpledisplay.d 1171 1172 // see : http://www.sbin.org/doc/Xlib/chapt_09.html section on Keyboard Preferences re: scroll lock led 1173 1174 // Cool stuff: I want right alt and scroll lock to do different stuff for personal use. maybe even right ctrl 1175 // but can i control the scroll lock led 1176 1177 1178 // Note: if you are using Image on X, you might want to do: 1179 /* 1180 static if(UsingSimpledisplayX11) { 1181 if(!Image.impl.xshmAvailable) { 1182 // the images will use the slower XPutImage, you might 1183 // want to consider an alternative method to get better speed 1184 } 1185 } 1186 1187 If the shared memory extension is available though, simpledisplay uses it 1188 for a significant speed boost whenever you draw large Images. 1189 */ 1190 1191 // 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. 1192 1193 // WARNING: if you are using with_eventloop, don't forget to call XFlush(XDisplayConnection.get()); before calling loop()! 1194 1195 /* 1196 Biggest FIXME: 1197 make sure the key event numbers match between X and Windows OR provide symbolic constants on each system 1198 1199 clean up opengl contexts when their windows close 1200 1201 fix resizing the bitmaps/pixmaps 1202 */ 1203 1204 // BTW on Windows: 1205 // -L/SUBSYSTEM:WINDOWS:5.0 1206 // to dmd will make a nice windows binary w/o a console if you want that. 1207 1208 /* 1209 Stuff to add: 1210 1211 use multibyte functions everywhere we can 1212 1213 OpenGL windows 1214 more event stuff 1215 extremely basic windows w/ no decoration for tooltips, splash screens, etc. 1216 1217 1218 resizeEvent 1219 and make the windows non-resizable by default, 1220 or perhaps stretched (if I can find something in X like StretchBlt) 1221 1222 take a screenshot function! 1223 1224 Pens and brushes? 1225 Maybe a global event loop? 1226 1227 Mouse deltas 1228 Key items 1229 */ 1230 1231 /* 1232 From MSDN: 1233 1234 You can also use the GET_X_LPARAM or GET_Y_LPARAM macro to extract the x- or y-coordinate. 1235 1236 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. 1237 1238 */ 1239 1240 version(linux) { 1241 version = X11; 1242 version(without_libnotify) { 1243 // we cool 1244 } 1245 else 1246 version = libnotify; 1247 } 1248 1249 version(libnotify) { 1250 pragma(lib, "dl"); 1251 import core.sys.posix.dlfcn; 1252 1253 void delegate()[int] libnotify_action_delegates; 1254 int libnotify_action_delegates_count; 1255 extern(C) static void libnotify_action_callback_sdpy(void* notification, char* action, void* user_data) { 1256 auto idx = cast(int) user_data; 1257 if(auto dgptr = idx in libnotify_action_delegates) { 1258 (*dgptr)(); 1259 libnotify_action_delegates.remove(idx); 1260 } 1261 } 1262 1263 struct C_DynamicLibrary { 1264 void* handle; 1265 this(string name) { 1266 handle = dlopen((name ~ "\0").ptr, RTLD_NOW); 1267 if(handle is null) 1268 throw new Exception("dlopen"); 1269 } 1270 1271 void close() { 1272 dlclose(handle); 1273 } 1274 1275 ~this() { 1276 // close 1277 } 1278 1279 // FIXME: this looks up by name every time.... 1280 template call(string func, Ret, Args...) { 1281 extern(C) Ret function(Args) fptr; 1282 typeof(fptr) call() { 1283 fptr = cast(typeof(fptr)) dlsym(handle, func); 1284 return fptr; 1285 } 1286 } 1287 } 1288 1289 C_DynamicLibrary* libnotify; 1290 } 1291 1292 version(OSX) { 1293 version(OSXCocoa) {} 1294 else { version = X11; } 1295 } 1296 //version = OSXCocoa; // this was written by KennyTM 1297 version(FreeBSD) 1298 version = X11; 1299 version(Solaris) 1300 version = X11; 1301 1302 version(X11) { 1303 version(without_xft) {} 1304 else version=with_xft; 1305 } 1306 1307 void featureNotImplemented()() { 1308 version(allow_unimplemented_features) 1309 throw new NotYetImplementedException(); 1310 else 1311 static assert(0); 1312 } 1313 1314 // these are so the static asserts don't trigger unless you want to 1315 // add support to it for an OS 1316 version(Windows) 1317 version = with_timer; 1318 version(linux) 1319 version = with_timer; 1320 1321 version(with_timer) 1322 enum bool SimpledisplayTimerAvailable = true; 1323 else 1324 enum bool SimpledisplayTimerAvailable = false; 1325 1326 /// 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. 1327 version(Windows) 1328 enum bool UsingSimpledisplayWindows = true; 1329 else 1330 enum bool UsingSimpledisplayWindows = false; 1331 1332 /// 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. 1333 version(X11) 1334 enum bool UsingSimpledisplayX11 = true; 1335 else 1336 enum bool UsingSimpledisplayX11 = false; 1337 1338 /// 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. 1339 version(OSXCocoa) 1340 enum bool UsingSimpledisplayCocoa = true; 1341 else 1342 enum bool UsingSimpledisplayCocoa = false; 1343 1344 /// Does this platform support multiple windows? If not, trying to create another will cause it to throw an exception. 1345 version(Windows) 1346 enum multipleWindowsSupported = true; 1347 else version(X11) 1348 enum multipleWindowsSupported = true; 1349 else version(OSXCocoa) 1350 enum multipleWindowsSupported = true; 1351 else 1352 static assert(0); 1353 1354 version(without_opengl) 1355 enum bool OpenGlEnabled = false; 1356 else 1357 enum bool OpenGlEnabled = true; 1358 1359 /++ 1360 Adds the necessary pragmas to your application to use the Windows gui subsystem. 1361 If you mix this in above your `main` function, you no longer need to use the linker 1362 flags explicitly. It does the necessary version blocks for various compilers and runtimes. 1363 1364 It does nothing if not compiling for Windows, so you need not version it out yourself. 1365 1366 Please note that Windows gui subsystem applications must NOT use std.stdio's stdout and 1367 stderr writeln. It will fail and throw an exception. 1368 1369 This will NOT work with plain `dmd` on Windows; you must use `dmd -m32mscoff` or `dmd -m64`. 1370 1371 History: 1372 Added November 24, 2021 (dub v10.4) 1373 +/ 1374 mixin template EnableWindowsSubsystem() { 1375 version(Windows) 1376 version(CRuntime_Microsoft) { 1377 pragma(linkerDirective, "/subsystem:windows"); 1378 version(LDC) 1379 pragma(linkerDirective, "/entry:wmainCRTStartup"); 1380 else 1381 pragma(linkerDirective, "/entry:mainCRTStartup"); 1382 } 1383 } 1384 1385 1386 /++ 1387 After selecting a type from [WindowTypes], you may further customize 1388 its behavior by setting one or more of these flags. 1389 1390 1391 The different window types have different meanings of `normal`. If the 1392 window type already is a good match for what you want to do, you should 1393 just use [WindowFlags.normal], the default, which will do the right thing 1394 for your users. 1395 1396 The window flags will not always be honored by the operating system 1397 and window managers; they are hints, not commands. 1398 +/ 1399 enum WindowFlags : int { 1400 normal = 0, /// 1401 skipTaskbar = 1, /// 1402 alwaysOnTop = 2, /// 1403 alwaysOnBottom = 4, /// 1404 cannotBeActivated = 8, /// 1405 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. 1406 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. 1407 /++ 1408 Sets the window as a short-lived child of its parent, but unlike an ordinary child, 1409 it is still a top-level window. This should NOT be set separately for most window types. 1410 1411 A transient window will not keep the application open if its main window closes. 1412 1413 $(PITFALL This may not be correctly implemented and its behavior is subject to change.) 1414 1415 1416 From the ICCM: 1417 1418 $(BLOCKQUOTE 1419 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. 1420 1421 $(CITE https://tronche.com/gui/x/icccm/sec-4.html) 1422 ) 1423 1424 So if you are using a window type that already describes this like [WindowTypes.dropdownMenu] etc., you should not use this flag. 1425 1426 History: 1427 Added February 23, 2021 but not yet stabilized. 1428 +/ 1429 transient = 64, 1430 /++ 1431 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. 1432 1433 This is currently only used for the WM_TAKE_FOCUS protocol on X11 at this time. 1434 1435 History: 1436 Added April 1, 2022 1437 +/ 1438 managesChildWindowFocus = 128, 1439 1440 dontAutoShow = 0x1000_0000, /// Don't automatically show window after creation; you will have to call `show()` manually. 1441 } 1442 1443 /++ 1444 When creating a window, you can pass a type to SimpleWindow's constructor, 1445 then further customize the window by changing `WindowFlags`. 1446 1447 1448 You should mostly only need [normal], [undecorated], and [eventOnly] for normal 1449 use. The others are there to build a foundation for a higher level GUI toolkit, 1450 but are themselves not as high level as you might think from their names. 1451 1452 This list is based on the EMWH spec for X11. 1453 http://standards.freedesktop.org/wm-spec/1.4/ar01s05.html#idm139704063786896 1454 +/ 1455 enum WindowTypes : int { 1456 /// An ordinary application window. 1457 normal, 1458 /// 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. 1459 undecorated, 1460 /// 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. 1461 eventOnly, 1462 /// A drop down menu, such as from a menu bar 1463 dropdownMenu, 1464 /// A popup menu, such as from a right click 1465 popupMenu, 1466 /// A popup bubble notification 1467 notification, 1468 /* 1469 menu, /// a tearable menu bar 1470 splashScreen, /// a loading splash screen for your application 1471 tooltip, /// A tiny window showing temporary help text or something. 1472 comboBoxDropdown, 1473 dialog, 1474 toolbar 1475 */ 1476 /// a child nested inside the parent. You must pass a parent window to the ctor 1477 nestedChild, 1478 1479 /++ 1480 The type you get when you pass in an existing browser handle, which means most 1481 of simpledisplay's fancy things will not be done since they were never set up. 1482 1483 Using this to the main SimpleWindow constructor explicitly will trigger an assertion 1484 failure; you should use the existing handle constructor. 1485 1486 History: 1487 Added November 17, 2022 (previously it would have type `normal`) 1488 +/ 1489 minimallyWrapped 1490 } 1491 1492 1493 private __gshared ushort sdpyOpenGLContextVersion = 0; // default: use legacy call 1494 private __gshared bool sdpyOpenGLContextCompatible = true; // default: allow "deprecated" features 1495 private __gshared char* sdpyWindowClassStr = null; 1496 private __gshared bool sdpyOpenGLContextAllowFallback = false; 1497 1498 /** 1499 Set OpenGL context version to use. This has no effect on non-OpenGL windows. 1500 You may want to change context version if you want to use advanced shaders or 1501 other modern OpenGL techinques. This setting doesn't affect already created 1502 windows. You may use version 2.1 as your default, which should be supported 1503 by any box since 2006, so seems to be a reasonable choice. 1504 1505 Note that by default version is set to `0`, which forces SimpleDisplay to use 1506 old context creation code without any version specified. This is the safest 1507 way to init OpenGL, but it may not give you access to advanced features. 1508 1509 See available OpenGL versions here: https://en.wikipedia.org/wiki/OpenGL 1510 */ 1511 void setOpenGLContextVersion() (ubyte hi, ubyte lo) { sdpyOpenGLContextVersion = cast(ushort)(hi<<8|lo); } 1512 1513 /** 1514 Set OpenGL context mode. Modern (3.0+) OpenGL versions deprecated old fixed 1515 pipeline functions, and without "compatible" mode you won't be able to use 1516 your old non-shader-based code with such contexts. By default SimpleDisplay 1517 creates compatible context, so you can gradually upgrade your OpenGL code if 1518 you want to (or leave it as is, as it should "just work"). 1519 */ 1520 @property void openGLContextCompatible() (bool v) { sdpyOpenGLContextCompatible = v; } 1521 1522 /** 1523 Set to `true` to allow creating OpenGL context with lower version than requested 1524 instead of throwing. If fallback was activated (or legacy OpenGL was requested), 1525 `openGLContextFallbackActivated()` will return `true`. 1526 */ 1527 @property void openGLContextAllowFallback() (bool v) { sdpyOpenGLContextAllowFallback = v; } 1528 1529 /** 1530 After creating OpenGL window, you can check this to see if you got only "legacy" OpenGL context. 1531 */ 1532 @property bool openGLContextFallbackActivated() () { return (sdpyOpenGLContextVersion == 0); } 1533 1534 /++ 1535 History: 1536 Added April 24, 2023 (dub v11.0) 1537 +/ 1538 auto openGLCurrentContext() { 1539 version(Windows) 1540 return wglGetCurrentContext(); 1541 else 1542 return glXGetCurrentContext(); 1543 } 1544 1545 1546 /** 1547 Set window class name for all following `new SimpleWindow()` calls. 1548 1549 WARNING! For Windows, you should set your class name before creating any 1550 window, and NEVER change it after that! 1551 */ 1552 void sdpyWindowClass (const(char)[] v) { 1553 import core.stdc.stdlib : realloc; 1554 if (v.length == 0) v = "SimpleDisplayWindow"; 1555 sdpyWindowClassStr = cast(char*)realloc(sdpyWindowClassStr, v.length+1); 1556 if (sdpyWindowClassStr is null) return; // oops 1557 sdpyWindowClassStr[0..v.length+1] = 0; 1558 sdpyWindowClassStr[0..v.length] = v[]; 1559 } 1560 1561 /** 1562 Get current window class name. 1563 */ 1564 string sdpyWindowClass () { 1565 if (sdpyWindowClassStr is null) return null; 1566 foreach (immutable idx; 0..size_t.max-1) { 1567 if (sdpyWindowClassStr[idx] == 0) return sdpyWindowClassStr[0..idx].idup; 1568 } 1569 return null; 1570 } 1571 1572 /++ 1573 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. 1574 1575 If you want per-monitor dpi values, check [SimpleWindow.actualDpi], but you can fall back to this if it returns 0. 1576 +/ 1577 float[2] getDpi() { 1578 float[2] dpi; 1579 version(Windows) { 1580 HDC screen = GetDC(null); 1581 dpi[0] = GetDeviceCaps(screen, LOGPIXELSX); 1582 dpi[1] = GetDeviceCaps(screen, LOGPIXELSY); 1583 } else version(X11) { 1584 auto display = XDisplayConnection.get; 1585 auto screen = DefaultScreen(display); 1586 1587 void fallback() { 1588 /+ 1589 // 25.4 millimeters in an inch... 1590 dpi[0] = cast(float) DisplayWidth(display, screen) / DisplayWidthMM(display, screen) * 25.4; 1591 dpi[1] = cast(float) DisplayHeight(display, screen) / DisplayHeightMM(display, screen) * 25.4; 1592 +/ 1593 1594 // the physical size isn't actually as important as the logical size since this is 1595 // all about scaling really 1596 dpi[0] = 96; 1597 dpi[1] = 96; 1598 } 1599 1600 auto xft = getXftDpi(); 1601 if(xft is float.init) 1602 fallback(); 1603 else { 1604 dpi[0] = xft; 1605 dpi[1] = xft; 1606 } 1607 } 1608 1609 return dpi; 1610 } 1611 1612 version(X11) 1613 float getXftDpi() { 1614 auto display = XDisplayConnection.get; 1615 1616 char* resourceString = XResourceManagerString(display); 1617 XrmInitialize(); 1618 1619 if (resourceString) { 1620 auto db = XrmGetStringDatabase(resourceString); 1621 XrmValue value; 1622 char* type; 1623 if (XrmGetResource(db, "Xft.dpi", "String", &type, &value) == true) { 1624 if (value.addr) { 1625 import core.stdc.stdlib; 1626 return atof(cast(char*) value.addr); 1627 } 1628 } 1629 } 1630 1631 return float.init; 1632 } 1633 1634 /++ 1635 Implementation used by [SimpleWindow.takeScreenshot]. 1636 1637 Params: 1638 handle = the native window handle. If `NativeWindowHandle.init`, it will attempt to get the whole screen. 1639 width = the width of the image you wish to capture. If 0, it will attempt to capture the full width of the target. 1640 height = the height of the image you wish to capture. If 0, it will attempt to capture the full height of the target. 1641 x = the x-offset of the image to capture, from the left. 1642 y = the y-offset of the image to capture, from the top. 1643 1644 History: 1645 Added on March 14, 2021 1646 1647 Documented public on September 23, 2021 with full support for null params (dub 10.3) 1648 1649 +/ 1650 TrueColorImage trueColorImageFromNativeHandle(NativeWindowHandle handle, int width = 0, int height = 0, int x = 0, int y = 0) { 1651 TrueColorImage got; 1652 version(X11) { 1653 auto display = XDisplayConnection.get; 1654 if(handle == 0) 1655 handle = RootWindow(display, DefaultScreen(display)); 1656 1657 if(width == 0 || height == 0) { 1658 Window root; 1659 int xpos, ypos; 1660 uint widthret, heightret, borderret, depthret; 1661 XGetGeometry(display, handle, &root, &xpos, &ypos, &widthret, &heightret, &borderret, &depthret); 1662 1663 if(width == 0) 1664 width = widthret; 1665 if(height == 0) 1666 height = heightret; 1667 } 1668 1669 auto image = XGetImage(display, handle, x, y, width, height, (cast(c_ulong) ~0) /*AllPlanes*/, ImageFormat.ZPixmap); 1670 1671 // https://github.com/adamdruppe/arsd/issues/98 1672 1673 auto i = new Image(image); 1674 got = i.toTrueColorImage(); 1675 1676 XDestroyImage(image); 1677 } else version(Windows) { 1678 auto hdc = GetDC(handle); 1679 scope(exit) ReleaseDC(handle, hdc); 1680 1681 if(width == 0 || height == 0) { 1682 BITMAP bmHeader; 1683 auto bm = GetCurrentObject(hdc, OBJ_BITMAP); 1684 GetObject(bm, BITMAP.sizeof, &bmHeader); 1685 if(width == 0) 1686 width = bmHeader.bmWidth; 1687 if(height == 0) 1688 height = bmHeader.bmHeight; 1689 } 1690 1691 auto i = new Image(width, height); 1692 HDC hdcMem = CreateCompatibleDC(hdc); 1693 HBITMAP hbmOld = SelectObject(hdcMem, i.handle); 1694 BitBlt(hdcMem, x, y, width, height, hdc, 0, 0, SRCCOPY); 1695 SelectObject(hdcMem, hbmOld); 1696 DeleteDC(hdcMem); 1697 1698 got = i.toTrueColorImage(); 1699 } else featureNotImplemented(); 1700 1701 return got; 1702 } 1703 1704 version(Windows) extern(Windows) private alias SetProcessDpiAwarenessContext_t = BOOL function(HANDLE); 1705 version(Windows) extern(Windows) private __gshared UINT function(HWND) GetDpiForWindow; 1706 version(Windows) extern(Windows) private __gshared BOOL function(UINT, UINT, PVOID, UINT, UINT) SystemParametersInfoForDpi; 1707 1708 version(Windows) 1709 shared static this() { 1710 auto lib = LoadLibrary("User32.dll"); 1711 if(lib is null) 1712 return; 1713 //scope(exit) 1714 //FreeLibrary(lib); 1715 1716 SetProcessDpiAwarenessContext_t SetProcessDpiAwarenessContext = cast(SetProcessDpiAwarenessContext_t) GetProcAddress(lib, "SetProcessDpiAwarenessContext"); 1717 1718 if(SetProcessDpiAwarenessContext is null) 1719 return; 1720 1721 enum DPI_AWARENESS_CONTEXT_PER_MONITOR_AWARE_V2 = cast(HANDLE) -4; 1722 if(!SetProcessDpiAwarenessContext(DPI_AWARENESS_CONTEXT_PER_MONITOR_AWARE_V2)) { 1723 //writeln(GetLastError()); 1724 } 1725 1726 GetDpiForWindow = cast(typeof(GetDpiForWindow)) GetProcAddress(lib, "GetDpiForWindow"); 1727 SystemParametersInfoForDpi = cast(typeof(SystemParametersInfoForDpi)) GetProcAddress(lib, "SystemParametersInfoForDpi"); 1728 } 1729 1730 /++ 1731 Blocking mode for event loop calls associated with a window instance. 1732 1733 History: 1734 Added December 8, 2021 (dub v10.5). Prior to that, all calls to 1735 `window.eventLoop` were the same as calls to `EventLoop.get.run`; that 1736 is, all would block until the application quit. 1737 1738 That behavior can still be achieved here with `untilApplicationQuits`, 1739 or explicitly calling the top-level `EventLoop.get.run` function. 1740 +/ 1741 enum BlockingMode { 1742 /++ 1743 The event loop call will block until the whole application is ready 1744 to quit if it is the only one running, but if it is nested inside 1745 another one, it will only block until the window you're calling it on 1746 closes. 1747 +/ 1748 automatic = 0x00, 1749 /++ 1750 The event loop call will only return when the whole application 1751 is ready to quit. This usually means all windows have been closed. 1752 1753 This is appropriate for your main application event loop. 1754 +/ 1755 untilApplicationQuits = 0x01, 1756 /++ 1757 The event loop will return when the window you're calling it on 1758 closes. If there are other windows still open, they may be destroyed 1759 unless you have another event loop running later. 1760 1761 This might be appropriate for a modal dialog box loop. Remember that 1762 other windows are still processing input though, so you can end up 1763 with a lengthy call stack if this happens in a loop, similar to a 1764 recursive function (well, it literally is a recursive function, just 1765 not an obvious looking one). 1766 +/ 1767 untilWindowCloses = 0x02, 1768 /++ 1769 If an event loop is already running, this call will immediately 1770 return, allowing the existing loop to handle it. If not, this call 1771 will block until the condition you bitwise-or into the flag. 1772 1773 The default is to block until the application quits, same as with 1774 the `automatic` setting (since if it were nested, which triggers until 1775 window closes in automatic, this flag would instead not block at all), 1776 but if you used `BlockingMode.onlyIfNotNested | BlockingMode.untilWindowCloses`, 1777 it will only nest until the window closes. You might want that if you are 1778 going to open two windows simultaneously and want closing just one of them 1779 to trigger the event loop return. 1780 +/ 1781 onlyIfNotNested = 0x10, 1782 } 1783 1784 /++ 1785 The flagship window class. 1786 1787 1788 SimpleWindow tries to make ordinary windows very easy to create and use without locking you 1789 out of more advanced or complex features of the underlying windowing system. 1790 1791 For many applications, you can simply call `new SimpleWindow(some_width, some_height, "some title")` 1792 and get a suitable window to work with. 1793 1794 From there, you can opt into additional features, like custom resizability and OpenGL support 1795 with the next two constructor arguments. Or, if you need even more, you can set a window type 1796 and customization flags with the final two constructor arguments. 1797 1798 If none of that works for you, you can also create a window using native function calls, then 1799 wrap the window in a SimpleWindow instance by calling `new SimpleWindow(native_handle)`. Remember, 1800 though, if you do this, managing the window is still your own responsibility! Notably, you 1801 will need to destroy it yourself. 1802 +/ 1803 class SimpleWindow : CapableOfHandlingNativeEvent, CapableOfBeingDrawnUpon { 1804 1805 /++ 1806 Copies the window's current state into a [TrueColorImage]. 1807 1808 Be warned: this can be a very slow operation 1809 1810 History: 1811 Actually implemented on March 14, 2021 1812 +/ 1813 TrueColorImage takeScreenshot() { 1814 version(Windows) 1815 return trueColorImageFromNativeHandle(impl.hwnd, _width, _height); 1816 else version(OSXCocoa) 1817 throw new NotYetImplementedException(); 1818 else 1819 return trueColorImageFromNativeHandle(impl.window, _width, _height); 1820 } 1821 1822 /++ 1823 Returns the actual logical DPI for the window on its current display monitor. If the window 1824 straddles monitors, it will return the value of one or the other in a platform-defined manner. 1825 1826 Please note this function may return zero if it doesn't know the answer! 1827 1828 1829 On Windows, it returns the dpi per monitor if the operating system supports it (Windows 10), 1830 or a system dpi value if not, which will live-update if the OS supports it (Windows 8 and up). 1831 1832 On X, it reads the xrandr extension to determine monitor positions and sizes. On some systems, 1833 this is not provided, meaning it will return 0. Otherwise, it will determine which monitor the 1834 window primarily resides on by checking the center point of the window against the monitor map. 1835 1836 Returns: 1837 0 if unknown. Otherwise, a rounded value of dots per inch reported by the monitor. It 1838 assumes the X and Y dpi are the same. 1839 1840 History: 1841 Added November 26, 2021 (dub v10.4) 1842 1843 It said "physical dpi" in the description prior to July 29, 2022, but the behavior was 1844 always a logical value on Windows and usually one on Linux too, so now the docs reflect 1845 that. 1846 1847 Bugs: 1848 Probably plenty. I haven't done a lot of tests on this. I know it doesn't automatically 1849 just work on linux; you need to set ARSD_SCALING_FACTOR as an environment variable to 1850 set it. Set ARSD_SCALING_FACTOR=1;1.5 for example to set it to 1x on the primary monitor 1851 and 1.5 on the secondary monitor. 1852 1853 The local dpi is not necessarily related to the physical dpi of the monitor. The name 1854 is a historical misnomer - the real thing of interest is the scale factor and due to 1855 compatibility concerns the scale would modify dpi values to trick applications. But since 1856 that's the terminology common out there, I used it too. 1857 1858 See_Also: 1859 [getDpi] gives the value provided for the default monitor. Not necessarily the same 1860 as this since the window many be on a different monitor, but it is a reasonable fallback 1861 to use if `actualDpi` returns 0. 1862 1863 [onDpiChanged] is changed when `actualDpi` has changed. 1864 +/ 1865 int actualDpi() { 1866 version(X11) bool useFallbackDpi = false; 1867 if(!actualDpiLoadAttempted) { 1868 // FIXME: do the actual monitor we are on 1869 // and on X this is a good chance to load the monitor map. 1870 version(Windows) { 1871 if(GetDpiForWindow) 1872 actualDpi_ = GetDpiForWindow(impl.hwnd); 1873 } else version(X11) { 1874 if(!xRandrInfoLoadAttemped) { 1875 xRandrInfoLoadAttemped = true; 1876 if(!XRandrLibrary.attempted) { 1877 XRandrLibrary.loadDynamicLibrary(); 1878 } 1879 1880 if(XRandrLibrary.loadSuccessful) { 1881 auto display = XDisplayConnection.get; 1882 int scratch; 1883 int major, minor; 1884 if(!XRRQueryExtension(display, &xrrEventBase, &scratch)) 1885 goto fallback; 1886 1887 XRRQueryVersion(display, &major, &minor); 1888 if(major <= 1 && minor < 5) 1889 goto fallback; 1890 1891 int count; 1892 XRRMonitorInfo *monitors = XRRGetMonitors(display, RootWindow(display, DefaultScreen(display)), true, &count); 1893 if(monitors is null) 1894 goto fallback; 1895 scope(exit) XRRFreeMonitors(monitors); 1896 1897 MonitorInfo.info = MonitorInfo.info[0 .. 0]; 1898 MonitorInfo.info.assumeSafeAppend(); 1899 foreach(idx, monitor; monitors[0 .. count]) { 1900 MonitorInfo.info ~= MonitorInfo( 1901 Rectangle(Point(monitor.x, monitor.y), Size(monitor.width, monitor.height)), 1902 Size(monitor.mwidth, monitor.mheight), 1903 cast(int) (customScalingFactorForMonitor(cast(int) idx) * getDpi()[0]) 1904 ); 1905 1906 /+ 1907 if(monitor.mwidth == 0 || monitor.mheight == 0) 1908 // unknown physical size, just guess 96 to avoid divide by zero 1909 MonitorInfo.info ~= MonitorInfo( 1910 Rectangle(Point(monitor.x, monitor.y), Size(monitor.width, monitor.height)), 1911 Size(monitor.mwidth, monitor.mheight), 1912 96 1913 ); 1914 else 1915 // and actual thing 1916 MonitorInfo.info ~= MonitorInfo( 1917 Rectangle(Point(monitor.x, monitor.y), Size(monitor.width, monitor.height)), 1918 Size(monitor.mwidth, monitor.mheight), 1919 minInternal( 1920 // millimeter to int then rounding up. 1921 cast(int)(monitor.width * 25.4 / monitor.mwidth + 0.5), 1922 cast(int)(monitor.height * 25.4 / monitor.mheight + 0.5) 1923 ) 1924 ); 1925 +/ 1926 } 1927 // writeln("Here", MonitorInfo.info); 1928 } 1929 } 1930 1931 if(XRandrLibrary.loadSuccessful) { 1932 updateActualDpi(true); 1933 // writeln("updated"); 1934 1935 if(!requestedInput) { 1936 // this is what requests live updates should the configuration change 1937 // each time you select input, it sends an initial event, so very important 1938 // to not get into a loop of selecting input, getting event, updating data, 1939 // and reselecting input... 1940 requestedInput = true; 1941 XRRSelectInput(display, impl.window, RRScreenChangeNotifyMask); 1942 // writeln("requested input"); 1943 } 1944 } else { 1945 fallback: 1946 // make sure we disable events that aren't coming 1947 xrrEventBase = -1; 1948 // best guess... respect the custom scaling user command to some extent at least though 1949 useFallbackDpi = true; 1950 } 1951 } 1952 actualDpiLoadAttempted = true; 1953 } else version(X11) if(MonitorInfo.info.length == 0) { 1954 useFallbackDpi = true; 1955 } 1956 1957 version(X11) 1958 if(useFallbackDpi) 1959 actualDpi_ = cast(int) (getDpi()[0] * customScalingFactorForMonitor(0)); 1960 1961 return actualDpi_; 1962 } 1963 1964 private int actualDpi_; 1965 private bool actualDpiLoadAttempted; 1966 1967 version(X11) private { 1968 bool requestedInput; 1969 static bool xRandrInfoLoadAttemped; 1970 struct MonitorInfo { 1971 Rectangle position; 1972 Size size; 1973 int dpi; 1974 1975 static MonitorInfo[] info; 1976 } 1977 bool screenPositionKnown; 1978 int screenPositionX; 1979 int screenPositionY; 1980 void updateActualDpi(bool loadingNow = false) { 1981 if(!loadingNow && !actualDpiLoadAttempted) 1982 actualDpi(); // just to make it do the load 1983 foreach(idx, m; MonitorInfo.info) { 1984 if(m.position.contains(Point(screenPositionX + this.width / 2, screenPositionY + this.height / 2))) { 1985 bool changed = actualDpi_ && actualDpi_ != m.dpi; 1986 actualDpi_ = m.dpi; 1987 // writeln("monitor ", idx); 1988 if(changed && onDpiChanged) 1989 onDpiChanged(); 1990 break; 1991 } 1992 } 1993 } 1994 } 1995 1996 /++ 1997 Sent when the window is moved to a new DPI context, for example, when it is dragged between monitors 1998 or if the window is moved to a new remote connection or a monitor is hot-swapped. 1999 2000 History: 2001 Added November 26, 2021 (dub v10.4) 2002 2003 See_Also: 2004 [actualDpi] 2005 +/ 2006 void delegate() onDpiChanged; 2007 2008 version(X11) { 2009 void recreateAfterDisconnect() { 2010 if(!stateDiscarded) return; 2011 2012 if(_parent !is null && _parent.stateDiscarded) 2013 _parent.recreateAfterDisconnect(); 2014 2015 bool wasHidden = hidden; 2016 2017 activeScreenPainter = null; // should already be done but just to confirm 2018 2019 actualDpi_ = 0; 2020 actualDpiLoadAttempted = false; 2021 xRandrInfoLoadAttemped = false; 2022 2023 impl.createWindow(_width, _height, _title, openglMode, _parent); 2024 2025 if(auto dh = dropHandler) { 2026 dropHandler = null; 2027 enableDragAndDrop(this, dh); 2028 } 2029 2030 if(recreateAdditionalConnectionState) 2031 recreateAdditionalConnectionState(); 2032 2033 hidden = wasHidden; 2034 stateDiscarded = false; 2035 } 2036 2037 bool stateDiscarded; 2038 void discardConnectionState() { 2039 if(XDisplayConnection.display) 2040 impl.dispose(); // if display is already null, it is hopeless to try to destroy stuff on it anyway 2041 if(discardAdditionalConnectionState) 2042 discardAdditionalConnectionState(); 2043 stateDiscarded = true; 2044 } 2045 2046 void delegate() discardAdditionalConnectionState; 2047 void delegate() recreateAdditionalConnectionState; 2048 2049 } 2050 2051 private DropHandler dropHandler; 2052 2053 SimpleWindow _parent; 2054 bool beingOpenKeepsAppOpen = true; 2055 /++ 2056 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. 2057 2058 The constructor tries to have sane default arguments, so for many cases, you only need to provide a few of them. 2059 2060 Params: 2061 2062 width = the width of the window's client area, in pixels 2063 height = the height of the window's client area, in pixels 2064 title = the title of the window (seen in the title bar, taskbar, etc.). You can change it after construction with the [SimpleWindow.title] property. 2065 opengl = [OpenGlOptions] are yes and no. If yes, it creates an OpenGL context on the window. 2066 resizable = [Resizability] has three options: 2067 $(P `allowResizing`, which allows the window to be resized by the user. The `windowResized` delegate will be called when the size is changed.) 2068 $(P `fixedSize` will not allow the user to resize the window.) 2069 $(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.) 2070 windowType = The type of window you want to make. 2071 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. 2072 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". 2073 +/ 2074 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) { 2075 claimGuiThread(); 2076 version(sdpy_thread_checks) assert(thisIsGuiThread); 2077 this._width = this._virtualWidth = width; 2078 this._height = this._virtualHeight = height; 2079 this.openglMode = opengl; 2080 version(X11) { 2081 // auto scale not implemented except with opengl and even there it is kinda weird 2082 if(resizable == Resizability.automaticallyScaleIfPossible && opengl == OpenGlOptions.no) 2083 resizable = Resizability.fixedSize; 2084 } 2085 this.resizability = resizable; 2086 this.windowType = windowType; 2087 this.customizationFlags = customizationFlags; 2088 this._title = (title is null ? "D Application" : title); 2089 this._parent = parent; 2090 impl.createWindow(width, height, this._title, opengl, parent); 2091 2092 if(windowType == WindowTypes.dropdownMenu || windowType == WindowTypes.popupMenu || windowType == WindowTypes.nestedChild || (customizationFlags & WindowFlags.transient)) 2093 beingOpenKeepsAppOpen = false; 2094 } 2095 2096 /// ditto 2097 this(int width, int height, string title, Resizability resizable, OpenGlOptions opengl = OpenGlOptions.no, WindowTypes windowType = WindowTypes.normal, int customizationFlags = WindowFlags.normal, SimpleWindow parent = null) { 2098 this(width, height, title, opengl, resizable, windowType, customizationFlags, parent); 2099 } 2100 2101 /// Same as above, except using the `Size` struct instead of separate width and height. 2102 this(Size size, string title = null, OpenGlOptions opengl = OpenGlOptions.no, Resizability resizable = Resizability.automaticallyScaleIfPossible) { 2103 this(size.width, size.height, title, opengl, resizable); 2104 } 2105 2106 /// ditto 2107 this(Size size, string title, Resizability resizable, OpenGlOptions opengl = OpenGlOptions.no) { 2108 this(size, title, opengl, resizable); 2109 } 2110 2111 2112 /++ 2113 Creates a window based on the given [Image]. It's client area 2114 width and height is equal to the image. (A window's client area 2115 is the drawable space inside; it excludes the title bar, etc.) 2116 2117 Windows based on images will not be resizable and do not use OpenGL. 2118 2119 It will draw the image in upon creation, but this will be overwritten 2120 upon any draws, including the initial window visible event. 2121 2122 You probably do not want to use this and it may be removed from 2123 the library eventually, or I might change it to be a "permanent" 2124 background image; one that is automatically drawn on it before any 2125 other drawing event. idk. 2126 +/ 2127 this(Image image, string title = null) { 2128 this(image.width, image.height, title); 2129 this.image = image; 2130 } 2131 2132 /++ 2133 Wraps a native window handle with very little additional processing - notably no destruction 2134 this is incomplete so don't use it for much right now. The purpose of this is to make native 2135 windows created through the low level API (so you can use platform-specific options and 2136 other details SimpleWindow does not expose) available to the event loop wrappers. 2137 +/ 2138 this(NativeWindowHandle nativeWindow) { 2139 windowType = WindowTypes.minimallyWrapped; 2140 version(Windows) 2141 impl.hwnd = nativeWindow; 2142 else version(X11) { 2143 impl.window = nativeWindow; 2144 if(nativeWindow) 2145 display = XDisplayConnection.get(); // get initial display to not segfault 2146 } else version(OSXCocoa) 2147 throw new NotYetImplementedException(); 2148 else featureNotImplemented(); 2149 // FIXME: set the size correctly 2150 _width = 1; 2151 _height = 1; 2152 if(nativeWindow) 2153 nativeMapping[nativeWindow] = this; 2154 2155 beingOpenKeepsAppOpen = false; 2156 2157 if(nativeWindow) 2158 CapableOfHandlingNativeEvent.nativeHandleMapping[nativeWindow] = this; 2159 _suppressDestruction = true; // so it doesn't try to close 2160 } 2161 2162 /++ 2163 Used iff [WindowFlags.managesChildWindowFocus] is set when the window is created. 2164 The delegate will be called when the window manager asks you to take focus. 2165 2166 This is currently only used for the WM_TAKE_FOCUS protocol on X11 at this time. 2167 2168 History: 2169 Added April 1, 2022 (dub v10.8) 2170 +/ 2171 SimpleWindow delegate() setRequestedInputFocus; 2172 2173 /// Experimental, do not use yet 2174 /++ 2175 Grabs exclusive input from the user until you release it with 2176 [releaseInputGrab]. 2177 2178 2179 Note: it is extremely rude to do this without good reason. 2180 Reasons may include doing some kind of mouse drag operation 2181 or popping up a temporary menu that should get events and will 2182 be dismissed at ease by the user clicking away. 2183 2184 Params: 2185 keyboard = do you want to grab keyboard input? 2186 mouse = grab mouse input? 2187 confine = confine the mouse cursor to inside this window? 2188 2189 History: 2190 Prior to March 11, 2021, grabbing the keyboard would always also 2191 set the X input focus. Now, it only focuses if it is a non-transient 2192 window and otherwise manages the input direction internally. 2193 2194 This means spurious focus/blur events will no longer be sent and the 2195 application will not steal focus from other applications (which the 2196 window manager may have rejected anyway). 2197 +/ 2198 void grabInput(bool keyboard = true, bool mouse = true, bool confine = false) { 2199 static if(UsingSimpledisplayX11) { 2200 XSync(XDisplayConnection.get, 0); 2201 if(keyboard) { 2202 if(isTransient && _parent) { 2203 /* 2204 FIXME: 2205 setting the keyboard focus is not actually that helpful, what I more likely want 2206 is the events from the parent window to be sent over here if we're transient. 2207 */ 2208 2209 _parent.inputProxy = this; 2210 } else { 2211 2212 SimpleWindow setTo; 2213 if(setRequestedInputFocus !is null) 2214 setTo = setRequestedInputFocus(); 2215 if(setTo is null) 2216 setTo = this; 2217 XSetInputFocus(XDisplayConnection.get, setTo.impl.window, RevertToParent, CurrentTime); 2218 } 2219 } 2220 if(mouse) { 2221 if(auto res = XGrabPointer(XDisplayConnection.get, this.impl.window, false /* owner_events */, 2222 EventMask.PointerMotionMask // FIXME: not efficient 2223 | EventMask.ButtonPressMask 2224 | EventMask.ButtonReleaseMask 2225 /* event mask */, GrabMode.GrabModeAsync, GrabMode.GrabModeAsync, confine ? this.impl.window : None, None, CurrentTime) 2226 ) 2227 { 2228 XSync(XDisplayConnection.get, 0); 2229 import core.stdc.stdio; 2230 printf("Grab input failed %d\n", res); 2231 //throw new Exception("Grab input failed"); 2232 } else { 2233 // cool 2234 } 2235 } 2236 2237 } else version(Windows) { 2238 // FIXME: keyboard? 2239 SetCapture(impl.hwnd); 2240 if(confine) { 2241 RECT rcClip; 2242 //RECT rcOldClip; 2243 //GetClipCursor(&rcOldClip); 2244 GetWindowRect(hwnd, &rcClip); 2245 ClipCursor(&rcClip); 2246 } 2247 } else version(OSXCocoa) { 2248 throw new NotYetImplementedException(); 2249 } else static assert(0); 2250 } 2251 2252 private Point imePopupLocation = Point(0, 0); 2253 2254 /++ 2255 Sets the location for the IME (input method editor) to pop up when the user activates it. 2256 2257 Bugs: 2258 Not implemented outside X11. 2259 +/ 2260 void setIMEPopupLocation(Point location) { 2261 static if(UsingSimpledisplayX11) { 2262 imePopupLocation = location; 2263 updateIMEPopupLocation(); 2264 } else { 2265 // this is non-fatal at this point... but still wanna find it when i search for NotYetImplementedException at least 2266 // throw new NotYetImplementedException(); 2267 } 2268 } 2269 2270 /// ditto 2271 void setIMEPopupLocation(int x, int y) { 2272 return setIMEPopupLocation(Point(x, y)); 2273 } 2274 2275 // we need to remind XIM of where we wanted to place the IME whenever the window moves 2276 // so this function gets called in setIMEPopupLocation as well as whenever the window 2277 // receives a ConfigureNotify event 2278 private void updateIMEPopupLocation() { 2279 static if(UsingSimpledisplayX11) { 2280 if (xic is null) { 2281 return; 2282 } 2283 2284 XPoint nspot; 2285 nspot.x = cast(short) imePopupLocation.x; 2286 nspot.y = cast(short) imePopupLocation.y; 2287 XVaNestedList preeditAttr = XVaCreateNestedList(0, /*XNSpotLocation*/"spotLocation".ptr, &nspot, null); 2288 XSetICValues(xic, /*XNPreeditAttributes*/"preeditAttributes".ptr, preeditAttr, null); 2289 XFree(preeditAttr); 2290 } 2291 } 2292 2293 private bool imeFocused = true; 2294 2295 /++ 2296 Tells the IME whether or not an input field is currently focused in the window. 2297 2298 Bugs: 2299 Not implemented outside X11. 2300 +/ 2301 void setIMEFocused(bool value) { 2302 imeFocused = value; 2303 updateIMEFocused(); 2304 } 2305 2306 // used to focus/unfocus the IC if necessary when the window gains/loses focus 2307 private void updateIMEFocused() { 2308 static if(UsingSimpledisplayX11) { 2309 if (xic is null) { 2310 return; 2311 } 2312 2313 if (focused && imeFocused) { 2314 XSetICFocus(xic); 2315 } else { 2316 XUnsetICFocus(xic); 2317 } 2318 } 2319 } 2320 2321 /++ 2322 Returns the native window. 2323 2324 History: 2325 Added November 5, 2021 (dub v10.4). Prior to that, you'd have 2326 to access it through the `impl` member (which is semi-supported 2327 but platform specific and here it is simple enough to offer an accessor). 2328 2329 Bugs: 2330 Not implemented outside Windows or X11. 2331 +/ 2332 NativeWindowHandle nativeWindowHandle() { 2333 version(X11) 2334 return impl.window; 2335 else version(Windows) 2336 return impl.hwnd; 2337 else 2338 throw new NotYetImplementedException(); 2339 } 2340 2341 private bool isTransient() { 2342 with(WindowTypes) 2343 final switch(windowType) { 2344 case normal, undecorated, eventOnly: 2345 case nestedChild, minimallyWrapped: 2346 return (customizationFlags & WindowFlags.transient) ? true : false; 2347 case dropdownMenu, popupMenu, notification: 2348 return true; 2349 } 2350 } 2351 2352 private SimpleWindow inputProxy; 2353 2354 /++ 2355 Releases the grab acquired by [grabInput]. 2356 +/ 2357 void releaseInputGrab() { 2358 static if(UsingSimpledisplayX11) { 2359 XUngrabPointer(XDisplayConnection.get, CurrentTime); 2360 if(_parent) 2361 _parent.inputProxy = null; 2362 } else version(Windows) { 2363 ReleaseCapture(); 2364 ClipCursor(null); 2365 } else version(OSXCocoa) { 2366 throw new NotYetImplementedException(); 2367 } else static assert(0); 2368 } 2369 2370 /++ 2371 Sets the input focus to this window. 2372 2373 You shouldn't call this very often - please let the user control the input focus. 2374 +/ 2375 void focus() { 2376 static if(UsingSimpledisplayX11) { 2377 SimpleWindow setTo; 2378 if(setRequestedInputFocus !is null) 2379 setTo = setRequestedInputFocus(); 2380 if(setTo is null) 2381 setTo = this; 2382 XSetInputFocus(XDisplayConnection.get, setTo.impl.window, RevertToParent, CurrentTime); 2383 } else version(Windows) { 2384 SetFocus(this.impl.hwnd); 2385 } else version(OSXCocoa) { 2386 throw new NotYetImplementedException(); 2387 } else static assert(0); 2388 } 2389 2390 /++ 2391 Requests attention from the user for this window. 2392 2393 2394 The typical result of this function is to change the color 2395 of the taskbar icon, though it may be tweaked on specific 2396 platforms. 2397 2398 It is meant to unobtrusively tell the user that something 2399 relevant to them happened in the background and they should 2400 check the window when they get a chance. Upon receiving the 2401 keyboard focus, the window will automatically return to its 2402 natural state. 2403 2404 If the window already has the keyboard focus, this function 2405 may do nothing, because the user is presumed to already be 2406 giving the window attention. 2407 2408 Implementation_note: 2409 2410 `requestAttention` uses the _NET_WM_STATE_DEMANDS_ATTENTION 2411 atom on X11 and the FlashWindow function on Windows. 2412 +/ 2413 void requestAttention() { 2414 if(_focused) 2415 return; 2416 2417 version(Windows) { 2418 FLASHWINFO info; 2419 info.cbSize = info.sizeof; 2420 info.hwnd = impl.hwnd; 2421 info.dwFlags = FLASHW_TRAY; 2422 info.uCount = 1; 2423 2424 FlashWindowEx(&info); 2425 2426 } else version(X11) { 2427 demandingAttention = true; 2428 demandAttention(this, true); 2429 } else version(OSXCocoa) { 2430 throw new NotYetImplementedException(); 2431 } else static assert(0); 2432 } 2433 2434 private bool _focused; 2435 2436 version(X11) private bool demandingAttention; 2437 2438 /// This will be called when WM wants to close your window (i.e. user clicked "close" icon, for example). 2439 /// You'll have to call `close()` manually if you set this delegate. 2440 void delegate () closeQuery; 2441 2442 /// This will be called when window visibility was changed. 2443 void delegate (bool becomesVisible) visibilityChanged; 2444 2445 /// This will be called when window becomes visible for the first time. 2446 /// You can do OpenGL initialization here. Note that in X11 you can't call 2447 /// [setAsCurrentOpenGlContext] right after window creation, or X11 may 2448 /// fail to send reparent and map events (hit that with proprietary NVidia drivers). 2449 /// So you need to wait until this is called and call setAsCurrentOpenGlContext in there, then do the OpenGL initialization. 2450 private bool _visibleForTheFirstTimeCalled; 2451 void delegate () visibleForTheFirstTime; 2452 2453 /// Returns true if the window has been closed. 2454 final @property bool closed() { return _closed; } 2455 2456 private final @property bool notClosed() { return !_closed; } 2457 2458 /// Returns true if the window is focused. 2459 final @property bool focused() { return _focused; } 2460 2461 private bool _visible; 2462 /// Returns true if the window is visible (mapped). 2463 final @property bool visible() { return _visible; } 2464 2465 /// Closes the window. If there are no more open windows, the event loop will terminate. 2466 void close() { 2467 if (!_closed) { 2468 runInGuiThread( { 2469 if(_closed) return; // another thread got to it first. this isn't a big deal, it just means our message was queued 2470 if (onClosing !is null) onClosing(); 2471 impl.closeWindow(); 2472 _closed = true; 2473 } ); 2474 } 2475 } 2476 2477 /++ 2478 `close` is one of the few methods that can be called from other threads. This `shared` overload reflects that. 2479 2480 History: 2481 Overload added on March 7, 2021. 2482 +/ 2483 void close() shared { 2484 (cast() this).close(); 2485 } 2486 2487 /++ 2488 2489 +/ 2490 void maximize() { 2491 version(Windows) 2492 ShowWindow(impl.hwnd, SW_MAXIMIZE); 2493 else version(X11) { 2494 setNetWmStateAtom(this.impl.window, GetAtom!("_NET_WM_STATE_MAXIMIZED_VERT", false)(XDisplayConnection.get), true, GetAtom!("_NET_WM_STATE_MAXIMIZED_HORZ", false)(XDisplayConnection.get)); 2495 2496 // also note _NET_WM_STATE_FULLSCREEN 2497 } 2498 2499 } 2500 2501 private bool _fullscreen; 2502 version(Windows) 2503 private WINDOWPLACEMENT g_wpPrev; 2504 2505 /// not fully implemented but planned for a future release 2506 void fullscreen(bool yes) { 2507 version(Windows) { 2508 g_wpPrev.length = WINDOWPLACEMENT.sizeof; 2509 DWORD dwStyle = GetWindowLong(hwnd, GWL_STYLE); 2510 if (dwStyle & WS_OVERLAPPEDWINDOW) { 2511 MONITORINFO mi; 2512 mi.cbSize = MONITORINFO.sizeof; 2513 if (GetWindowPlacement(hwnd, &g_wpPrev) && 2514 GetMonitorInfo(MonitorFromWindow(hwnd, 2515 MONITOR_DEFAULTTOPRIMARY), &mi)) { 2516 SetWindowLong(hwnd, GWL_STYLE, 2517 dwStyle & ~WS_OVERLAPPEDWINDOW); 2518 SetWindowPos(hwnd, HWND_TOP, 2519 mi.rcMonitor.left, mi.rcMonitor.top, 2520 mi.rcMonitor.right - mi.rcMonitor.left, 2521 mi.rcMonitor.bottom - mi.rcMonitor.top, 2522 SWP_NOOWNERZORDER | SWP_FRAMECHANGED); 2523 } 2524 } else { 2525 SetWindowLong(hwnd, GWL_STYLE, 2526 dwStyle | WS_OVERLAPPEDWINDOW); 2527 SetWindowPlacement(hwnd, &g_wpPrev); 2528 SetWindowPos(hwnd, null, 0, 0, 0, 0, 2529 SWP_NOMOVE | SWP_NOSIZE | SWP_NOZORDER | 2530 SWP_NOOWNERZORDER | SWP_FRAMECHANGED); 2531 } 2532 2533 } else version(X11) { 2534 setNetWmStateAtom(this.impl.window, GetAtom!("_NET_WM_STATE_FULLSCREEN", false)(XDisplayConnection.get), yes); 2535 } 2536 2537 _fullscreen = yes; 2538 2539 } 2540 2541 bool fullscreen() { 2542 return _fullscreen; 2543 } 2544 2545 /++ 2546 Note: only implemented on Windows. No-op on other platforms. You may want to use [hide] instead. 2547 2548 +/ 2549 void minimize() { 2550 version(Windows) 2551 ShowWindow(impl.hwnd, SW_MINIMIZE); 2552 //else version(X11) 2553 //setNetWmStateAtom(this, GetAtom!("_NET_WM_STATE_MINIMIZED", false)(XDisplayConnection.get), true); 2554 } 2555 2556 /// Alias for `hidden = false` 2557 void show() { 2558 hidden = false; 2559 } 2560 2561 /// Alias for `hidden = true` 2562 void hide() { 2563 hidden = true; 2564 } 2565 2566 /// Hide cursor when it enters the window. 2567 void hideCursor() { 2568 version(OSXCocoa) throw new NotYetImplementedException(); else 2569 if (!_closed) impl.hideCursor(); 2570 } 2571 2572 /// Don't hide cursor when it enters the window. 2573 void showCursor() { 2574 version(OSXCocoa) throw new NotYetImplementedException(); else 2575 if (!_closed) impl.showCursor(); 2576 } 2577 2578 /** "Warp" mouse pointer to coordinates relative to window top-left corner. Return "success" flag. 2579 * 2580 * Please remember that the cursor is a shared resource that should usually be left to the user's 2581 * control. Try to think for other approaches before using this function. 2582 * 2583 * Note: "warping" pointer will not send any synthesised mouse events, so you probably doesn't want 2584 * to use it to move mouse pointer to some active GUI area, for example, as your window won't 2585 * receive "mouse moved here" event. 2586 */ 2587 bool warpMouse (int x, int y) { 2588 version(X11) { 2589 if (!_closed) { impl.warpMouse(x, y); return true; } 2590 } else version(Windows) { 2591 if (!_closed) { 2592 POINT point; 2593 point.x = x; 2594 point.y = y; 2595 if(ClientToScreen(impl.hwnd, &point)) { 2596 SetCursorPos(point.x, point.y); 2597 return true; 2598 } 2599 } 2600 } 2601 return false; 2602 } 2603 2604 /// Send dummy window event to ping event loop. Required to process NotificationIcon on X11, for example. 2605 void sendDummyEvent () { 2606 version(X11) { 2607 if (!_closed) { impl.sendDummyEvent(); } 2608 } 2609 } 2610 2611 /// Set window minimal size. 2612 void setMinSize (int minwidth, int minheight) { 2613 version(OSXCocoa) throw new NotYetImplementedException(); else 2614 if (!_closed) impl.setMinSize(minwidth, minheight); 2615 } 2616 2617 /// Set window maximal size. 2618 void setMaxSize (int maxwidth, int maxheight) { 2619 version(OSXCocoa) throw new NotYetImplementedException(); else 2620 if (!_closed) impl.setMaxSize(maxwidth, maxheight); 2621 } 2622 2623 /// Set window resize step (window size will be changed with the given granularity on supported platforms). 2624 /// Currently only supported on X11. 2625 void setResizeGranularity (int granx, int grany) { 2626 version(OSXCocoa) throw new NotYetImplementedException(); else 2627 if (!_closed) impl.setResizeGranularity(granx, grany); 2628 } 2629 2630 /// Move window. 2631 void move(int x, int y) { 2632 version(OSXCocoa) throw new NotYetImplementedException(); else 2633 if (!_closed) impl.move(x, y); 2634 } 2635 2636 /// ditto 2637 void move(Point p) { 2638 version(OSXCocoa) throw new NotYetImplementedException(); else 2639 if (!_closed) impl.move(p.x, p.y); 2640 } 2641 2642 /++ 2643 Resize window. 2644 2645 Note that the width and height of the window are NOT instantly 2646 updated - it waits for the window manager to approve the resize 2647 request, which means you must return to the event loop before the 2648 width and height are actually changed. 2649 +/ 2650 void resize(int w, int h) { 2651 if(!_closed && _fullscreen) fullscreen = false; 2652 version(OSXCocoa) throw new NotYetImplementedException(); else 2653 if (!_closed) impl.resize(w, h); 2654 } 2655 2656 /// Move and resize window (this can be faster and more visually pleasant than doing it separately). 2657 void moveResize (int x, int y, int w, int h) { 2658 if(!_closed && _fullscreen) fullscreen = false; 2659 version(OSXCocoa) throw new NotYetImplementedException(); else 2660 if (!_closed) impl.moveResize(x, y, w, h); 2661 } 2662 2663 private bool _hidden; 2664 2665 /// Returns true if the window is hidden. 2666 final @property bool hidden() { 2667 return _hidden; 2668 } 2669 2670 /// Shows or hides the window based on the bool argument. 2671 final @property void hidden(bool b) { 2672 _hidden = b; 2673 version(Windows) { 2674 ShowWindow(impl.hwnd, b ? SW_HIDE : SW_SHOW); 2675 } else version(X11) { 2676 if(b) 2677 //XUnmapWindow(impl.display, impl.window); 2678 XWithdrawWindow(impl.display, impl.window, DefaultScreen(impl.display)); 2679 else 2680 XMapWindow(impl.display, impl.window); 2681 } else version(OSXCocoa) { 2682 throw new NotYetImplementedException(); 2683 } else static assert(0); 2684 } 2685 2686 /// Sets the window opacity. On X11 this requires a compositor to be running. On windows the WindowFlags.extraComposite must be set at window creation. 2687 void opacity(double opacity) @property 2688 in { 2689 assert(opacity >= 0 && opacity <= 1); 2690 } do { 2691 version (Windows) { 2692 impl.setOpacity(cast(ubyte)(255 * opacity)); 2693 } else version (X11) { 2694 impl.setOpacity(cast(uint)(uint.max * opacity)); 2695 } else throw new NotYetImplementedException(); 2696 } 2697 2698 /++ 2699 Sets your event handlers, without entering the event loop. Useful if you 2700 have multiple windows - set the handlers on each window, then only do 2701 [eventLoop] on your main window or call `EventLoop.get.run();`. 2702 2703 This assigns the given handlers to [handleKeyEvent], [handleCharEvent], 2704 [handlePulse], and [handleMouseEvent] automatically based on the provide 2705 delegate signatures. 2706 +/ 2707 void setEventHandlers(T...)(T eventHandlers) { 2708 // FIXME: add more events 2709 foreach(handler; eventHandlers) { 2710 static if(__traits(compiles, handleKeyEvent = handler)) { 2711 handleKeyEvent = handler; 2712 } else static if(__traits(compiles, handleCharEvent = handler)) { 2713 handleCharEvent = handler; 2714 } else static if(__traits(compiles, handlePulse = handler)) { 2715 handlePulse = handler; 2716 } else static if(__traits(compiles, handleMouseEvent = handler)) { 2717 handleMouseEvent = handler; 2718 } else static assert(0, "I can't use this event handler " ~ typeof(handler).stringof ~ "\nHave you tried using the delegate keyword?"); 2719 } 2720 } 2721 2722 /++ 2723 The event loop automatically returns when the window is closed 2724 pulseTimeout is given in milliseconds. If pulseTimeout == 0, no 2725 pulse timer is created. The event loop will block until an event 2726 arrives or the pulse timer goes off. 2727 2728 The given `eventHandlers` are passed to [setEventHandlers], which in turn 2729 assigns them to [handleKeyEvent], [handleCharEvent], [handlePulse], and 2730 [handleMouseEvent], based on the signature of delegates you provide. 2731 2732 Give one with no parameters to set a timer pulse handler. Give one that 2733 takes [KeyEvent] for a key handler, [MouseEvent], for a mouse handler, 2734 and one that takes `dchar` for a char event handler. You can use as many 2735 or as few handlers as you need for your application. 2736 2737 Bugs: 2738 2739 $(PITFALL 2740 You should always have one event loop live for your application. 2741 If you make two windows in sequence, the second call to eventLoop 2742 might fail: 2743 2744 --- 2745 // don't do this! 2746 auto window = new SimpleWindow(); 2747 window.eventLoop(0); 2748 2749 auto window2 = new SimpleWindow(); 2750 window2.eventLoop(0); // problematic! might crash 2751 --- 2752 2753 simpledisplay's current implementation assumes that final cleanup is 2754 done when the event loop refcount reaches zero. So after the first 2755 eventLoop returns, when there isn't already another one active, it assumes 2756 the program will exit soon and cleans up. 2757 2758 This is arguably a bug that it doesn't reinitialize, and I'll probably change 2759 it eventually, but in the mean time, there's an easy solution: 2760 2761 --- 2762 // do this 2763 EventLoop mainEventLoop = EventLoop.get; // just add this line 2764 2765 auto window = new SimpleWindow(); 2766 window.eventLoop(0); 2767 2768 auto window2 = new SimpleWindow(); 2769 window2.eventLoop(0); // perfectly fine since mainEventLoop still alive 2770 --- 2771 2772 By adding a top-level reference to the event loop, it ensures the final cleanup 2773 is not performed until it goes out of scope too, letting the individual window loops 2774 work without trouble despite the bug. 2775 ) 2776 2777 History: 2778 The overload without `pulseTimeout` was added on December 8, 2021. 2779 2780 On December 9, 2021, the default blocking mode (which is now configurable 2781 because [eventLoopWithBlockingMode] was added) switched from 2782 [BlockingMode.untilApplicationQuits] over to [BlockingMode.automatic]. This 2783 should almost never be noticeable to you since the typical simpledisplay 2784 paradigm has been (and I still recommend) to have one `eventLoop` call. 2785 2786 See_Also: 2787 [eventLoopWithBlockingMode] 2788 +/ 2789 final int eventLoop(T...)( 2790 long pulseTimeout, /// set to zero if you don't want a pulse. 2791 T eventHandlers) /// delegate list like std.concurrency.receive 2792 { 2793 return eventLoopWithBlockingMode(BlockingMode.automatic, pulseTimeout, eventHandlers); 2794 } 2795 2796 /// ditto 2797 final int eventLoop(T...)(T eventHandlers) if(T.length == 0 || is(T[0] == delegate)) 2798 { 2799 return eventLoopWithBlockingMode(BlockingMode.automatic, 0, eventHandlers); 2800 } 2801 2802 /++ 2803 This is the function [eventLoop] forwards to. It, in turn, forwards to `EventLoop.get.run`. 2804 2805 History: 2806 Added December 8, 2021 (dub v10.5) 2807 2808 Previously, this implementation was right inside [eventLoop], but when I wanted 2809 to add the new [BlockingMode] parameter, the compiler got in a trouble loop so I 2810 just renamed it instead of adding as an overload. Besides, the new name makes it 2811 easier to remember the order and avoids ambiguity between two int-like params anyway. 2812 2813 See_Also: 2814 [SimpleWindow.eventLoop], [EventLoop] 2815 2816 Bugs: 2817 The blocking mode is not implemented on OSX Cocoa nor on the (deprecated) arsd.eventloop. 2818 +/ 2819 final int eventLoopWithBlockingMode(T...)( 2820 BlockingMode blockingMode, /// when you want this function to block until 2821 long pulseTimeout, /// set to zero if you don't want a pulse. 2822 T eventHandlers) /// delegate list like std.concurrency.receive 2823 { 2824 setEventHandlers(eventHandlers); 2825 2826 version(with_eventloop) { 2827 // delegates event loop to my other module 2828 version(X11) 2829 XFlush(display); 2830 2831 import arsd.eventloop; 2832 auto handle = setInterval(handlePulse, cast(int) pulseTimeout); 2833 scope(exit) clearInterval(handle); 2834 2835 loop(); 2836 return 0; 2837 } else version(OSXCocoa) { 2838 // FIXME 2839 if (handlePulse !is null && pulseTimeout != 0) { 2840 timer = scheduledTimer(pulseTimeout*1e-3, 2841 view, sel_registerName("simpledisplay_pulse"), 2842 null, true); 2843 } 2844 2845 setNeedsDisplay(view, true); 2846 run(NSApp); 2847 return 0; 2848 } else { 2849 EventLoop el = EventLoop(pulseTimeout, handlePulse); 2850 2851 if((blockingMode & BlockingMode.onlyIfNotNested) && el.impl.refcount > 1) 2852 return 0; 2853 2854 return el.run( 2855 ((blockingMode & 0x0f) == BlockingMode.untilApplicationQuits) ? 2856 null : 2857 &this.notClosed 2858 ); 2859 } 2860 } 2861 2862 /++ 2863 This lets you draw on the window (or its backing buffer) using basic 2864 2D primitives. 2865 2866 Be sure to call this in a limited scope because your changes will not 2867 actually appear on the window until ScreenPainter's destructor runs. 2868 2869 Returns: an instance of [ScreenPainter], which has the drawing methods 2870 on it to draw on this window. 2871 2872 Params: 2873 manualInvalidations = if you set this to true, you will need to 2874 set the invalid rectangle on the painter yourself. If false, it 2875 assumes the whole window has been redrawn each time you draw. 2876 2877 Only invalidated rectangles are blitted back to the window when 2878 the destructor runs. Doing this yourself can reduce flickering 2879 of child windows. 2880 2881 History: 2882 The `manualInvalidations` parameter overload was added on 2883 December 30, 2021 (dub v10.5) 2884 +/ 2885 ScreenPainter draw() { 2886 return draw(false); 2887 } 2888 /// ditto 2889 ScreenPainter draw(bool manualInvalidations) { 2890 return impl.getPainter(manualInvalidations); 2891 } 2892 2893 // This is here to implement the interface we use for various native handlers. 2894 NativeEventHandler getNativeEventHandler() { return handleNativeEvent; } 2895 2896 // maps native window handles to SimpleWindow instances, if there are any 2897 // you shouldn't need this, but it is public in case you do in a native event handler or something 2898 public __gshared SimpleWindow[NativeWindowHandle] nativeMapping; 2899 2900 // the size the user requested in the constructor, in automatic scale modes it always pretends to be this size 2901 private int _virtualWidth; 2902 private int _virtualHeight; 2903 2904 /// Width of the window's drawable client area, in pixels. 2905 @scriptable 2906 final @property int width() const pure nothrow @safe @nogc { 2907 if(resizability == Resizability.automaticallyScaleIfPossible) 2908 return _virtualWidth; 2909 else 2910 return _width; 2911 } 2912 2913 /// Height of the window's drawable client area, in pixels. 2914 @scriptable 2915 final @property int height() const pure nothrow @safe @nogc { 2916 if(resizability == Resizability.automaticallyScaleIfPossible) 2917 return _virtualHeight; 2918 else 2919 return _height; 2920 } 2921 2922 /++ 2923 Returns the actual size of the window, bypassing the logical 2924 illusions of [Resizability.automaticallyScaleIfPossible]. 2925 2926 History: 2927 Added November 11, 2022 (dub v10.10) 2928 +/ 2929 final @property Size actualWindowSize() const pure nothrow @safe @nogc { 2930 return Size(_width, _height); 2931 } 2932 2933 2934 private int _width; 2935 private int _height; 2936 2937 // HACK: making the best of some copy constructor woes with refcounting 2938 private ScreenPainterImplementation* activeScreenPainter_; 2939 2940 protected ScreenPainterImplementation* activeScreenPainter() { return activeScreenPainter_; } 2941 protected void activeScreenPainter(ScreenPainterImplementation* i) { activeScreenPainter_ = i; } 2942 2943 private OpenGlOptions openglMode; 2944 private Resizability resizability; 2945 private WindowTypes windowType; 2946 private int customizationFlags; 2947 2948 /// `true` if OpenGL was initialized for this window. 2949 @property bool isOpenGL () const pure nothrow @safe @nogc { 2950 version(without_opengl) 2951 return false; 2952 else 2953 return (openglMode == OpenGlOptions.yes); 2954 } 2955 @property Resizability resizingMode () const pure nothrow @safe @nogc { return resizability; } /// Original resizability. 2956 @property WindowTypes type () const pure nothrow @safe @nogc { return windowType; } /// Original window type. 2957 @property int customFlags () const pure nothrow @safe @nogc { return customizationFlags; } /// Original customization flags. 2958 2959 /// "Lock" this window handle, to do multithreaded synchronization. You probably won't need 2960 /// to call this, as it's not recommended to share window between threads. 2961 void mtLock () { 2962 version(X11) { 2963 XLockDisplay(this.display); 2964 } 2965 } 2966 2967 /// "Unlock" this window handle, to do multithreaded synchronization. You probably won't need 2968 /// to call this, as it's not recommended to share window between threads. 2969 void mtUnlock () { 2970 version(X11) { 2971 XUnlockDisplay(this.display); 2972 } 2973 } 2974 2975 /// Emit a beep to get user's attention. 2976 void beep () { 2977 version(X11) { 2978 XBell(this.display, 100); 2979 } else version(Windows) { 2980 MessageBeep(0xFFFFFFFF); 2981 } 2982 } 2983 2984 2985 2986 version(without_opengl) {} else { 2987 2988 /// 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`. 2989 void delegate() redrawOpenGlScene; 2990 2991 /// This will allow you to change OpenGL vsync state. 2992 final @property void vsync (bool wait) { 2993 if (this._closed) return; // window may be closed, but timer is still firing; avoid GLXBadDrawable error 2994 version(X11) { 2995 setAsCurrentOpenGlContext(); 2996 glxSetVSync(display, impl.window, wait); 2997 } else version(Windows) { 2998 setAsCurrentOpenGlContext(); 2999 wglSetVSync(wait); 3000 } 3001 } 3002 3003 /// Set this to `false` if you don't need to do `glFinish()` after `swapOpenGlBuffers()`. 3004 /// Note that at least NVidia proprietary driver may segfault if you will modify texture fast 3005 /// enough without waiting 'em to finish their frame business. 3006 bool useGLFinish = true; 3007 3008 // FIXME: it should schedule it for the end of the current iteration of the event loop... 3009 /// 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. 3010 void redrawOpenGlSceneNow() { 3011 version(X11) if (!this._visible) return; // no need to do this if window is invisible 3012 if (this._closed) return; // window may be closed, but timer is still firing; avoid GLXBadDrawable error 3013 if(redrawOpenGlScene is null) 3014 return; 3015 3016 this.mtLock(); 3017 scope(exit) this.mtUnlock(); 3018 3019 this.setAsCurrentOpenGlContext(); 3020 3021 redrawOpenGlScene(); 3022 3023 this.swapOpenGlBuffers(); 3024 // 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. 3025 if (useGLFinish) glFinish(); 3026 } 3027 3028 private bool redrawOpenGlSceneSoonSet = false; 3029 private static class RedrawOpenGlSceneEvent { 3030 SimpleWindow w; 3031 this(SimpleWindow w) { this.w = w; } 3032 } 3033 private RedrawOpenGlSceneEvent redrawOpenGlSceneEvent; 3034 /++ 3035 Queues an opengl redraw as soon as the other pending events are cleared. 3036 +/ 3037 void redrawOpenGlSceneSoon() { 3038 if(redrawOpenGlScene is null) 3039 return; 3040 3041 if(!redrawOpenGlSceneSoonSet) { 3042 redrawOpenGlSceneEvent = new RedrawOpenGlSceneEvent(this); 3043 this.addEventListener((RedrawOpenGlSceneEvent e) { e.w.redrawOpenGlSceneNow(); }); 3044 redrawOpenGlSceneSoonSet = true; 3045 } 3046 this.postEvent(redrawOpenGlSceneEvent, true); 3047 } 3048 3049 3050 /// Makes all gl* functions target this window until changed. This is only valid if you passed `OpenGlOptions.yes` to the constructor. 3051 void setAsCurrentOpenGlContext() { 3052 assert(openglMode == OpenGlOptions.yes); 3053 version(X11) { 3054 if(glXMakeCurrent(display, impl.window, impl.glc) == 0) 3055 throw new Exception("glXMakeCurrent"); 3056 } else version(Windows) { 3057 static if (SdpyIsUsingIVGLBinds) import iv.glbinds; // override druntime windows imports 3058 if (!wglMakeCurrent(ghDC, ghRC)) 3059 throw new Exception("wglMakeCurrent " ~ toInternal!int(GetLastError())); // let windows users suffer too 3060 } 3061 } 3062 3063 /// Makes all gl* functions target this window until changed. This is only valid if you passed `OpenGlOptions.yes` to the constructor. 3064 /// This doesn't throw, returning success flag instead. 3065 bool setAsCurrentOpenGlContextNT() nothrow { 3066 assert(openglMode == OpenGlOptions.yes); 3067 version(X11) { 3068 return (glXMakeCurrent(display, impl.window, impl.glc) != 0); 3069 } else version(Windows) { 3070 static if (SdpyIsUsingIVGLBinds) import iv.glbinds; // override druntime windows imports 3071 return wglMakeCurrent(ghDC, ghRC) ? true : false; 3072 } 3073 } 3074 3075 /// 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. 3076 /// This doesn't throw, returning success flag instead. 3077 bool releaseCurrentOpenGlContext() nothrow { 3078 assert(openglMode == OpenGlOptions.yes); 3079 version(X11) { 3080 return (glXMakeCurrent(display, 0, null) != 0); 3081 } else version(Windows) { 3082 static if (SdpyIsUsingIVGLBinds) import iv.glbinds; // override druntime windows imports 3083 return wglMakeCurrent(ghDC, null) ? true : false; 3084 } 3085 } 3086 3087 /++ 3088 simpledisplay always uses double buffering, usually automatically. This 3089 manually swaps the OpenGL buffers. 3090 3091 3092 You should not need to call this yourself because simpledisplay will do it 3093 for you after calling your `redrawOpenGlScene`. 3094 3095 Remember that this may throw an exception, which you can catch in a multithreaded 3096 application to keep your thread from dying from an unhandled exception. 3097 +/ 3098 void swapOpenGlBuffers() { 3099 assert(openglMode == OpenGlOptions.yes); 3100 version(X11) { 3101 if (!this._visible) return; // no need to do this if window is invisible 3102 if (this._closed) return; // window may be closed, but timer is still firing; avoid GLXBadDrawable error 3103 glXSwapBuffers(display, impl.window); 3104 } else version(Windows) { 3105 SwapBuffers(ghDC); 3106 } 3107 } 3108 } 3109 3110 /++ 3111 Set the window title, which is visible on the window manager title bar, operating system taskbar, etc. 3112 3113 3114 --- 3115 auto window = new SimpleWindow(100, 100, "First title"); 3116 window.title = "A new title"; 3117 --- 3118 3119 You may call this function at any time. 3120 +/ 3121 @property void title(string title) { 3122 _title = title; 3123 version(OSXCocoa) throw new NotYetImplementedException(); else 3124 impl.setTitle(title); 3125 } 3126 3127 private string _title; 3128 3129 /// Gets the title 3130 @property string title() { 3131 if(_title is null) 3132 _title = getRealTitle(); 3133 return _title; 3134 } 3135 3136 /++ 3137 Get the title as set by the window manager. 3138 May not match what you attempted to set. 3139 +/ 3140 string getRealTitle() { 3141 static if(is(typeof(impl.getTitle()))) 3142 return impl.getTitle(); 3143 else 3144 return null; 3145 } 3146 3147 // don't use this generally it is not yet really released 3148 version(X11) 3149 @property Image secret_icon() { 3150 return secret_icon_inner; 3151 } 3152 private Image secret_icon_inner; 3153 3154 3155 /// Set the icon that is seen in the title bar or taskbar, etc., for the user. If passed `null`, does nothing. 3156 @property void icon(MemoryImage icon) { 3157 if(icon is null) 3158 return; 3159 auto tci = icon.getAsTrueColorImage(); 3160 version(Windows) { 3161 winIcon = new WindowsIcon(icon); 3162 SendMessageA(impl.hwnd, 0x0080 /*WM_SETICON*/, 0 /*ICON_SMALL*/, cast(LPARAM) winIcon.hIcon); // there is also 1 == ICON_BIG 3163 } else version(X11) { 3164 secret_icon_inner = Image.fromMemoryImage(icon); 3165 // FIXME: ensure this is correct 3166 auto display = XDisplayConnection.get; 3167 arch_ulong[] buffer; 3168 buffer ~= icon.width; 3169 buffer ~= icon.height; 3170 foreach(c; tci.imageData.colors) { 3171 arch_ulong b; 3172 b |= c.a << 24; 3173 b |= c.r << 16; 3174 b |= c.g << 8; 3175 b |= c.b; 3176 buffer ~= b; 3177 } 3178 3179 XChangeProperty( 3180 display, 3181 impl.window, 3182 GetAtom!("_NET_WM_ICON", true)(display), 3183 GetAtom!"CARDINAL"(display), 3184 32 /* bits */, 3185 0 /*PropModeReplace*/, 3186 buffer.ptr, 3187 cast(int) buffer.length); 3188 } else version(OSXCocoa) { 3189 throw new NotYetImplementedException(); 3190 } else static assert(0); 3191 } 3192 3193 version(Windows) 3194 private WindowsIcon winIcon; 3195 3196 bool _suppressDestruction; 3197 3198 ~this() { 3199 if(!thisIsGuiThread) return; // FIXME: leaks if multithreaded gc 3200 if(_suppressDestruction) 3201 return; 3202 impl.dispose(); 3203 } 3204 3205 private bool _closed; 3206 3207 // the idea here is to draw something temporary on top of the main picture e.g. a blinking cursor 3208 /* 3209 ScreenPainter drawTransiently() { 3210 return impl.getPainter(); 3211 } 3212 */ 3213 3214 /// Draws an image on the window. This is meant to provide quick look 3215 /// of a static image generated elsewhere. 3216 @property void image(Image i) { 3217 /+ 3218 version(Windows) { 3219 BITMAP bm; 3220 HDC hdc = GetDC(hwnd); 3221 HDC hdcMem = CreateCompatibleDC(hdc); 3222 HBITMAP hbmOld = SelectObject(hdcMem, i.handle); 3223 3224 GetObject(i.handle, bm.sizeof, &bm); 3225 3226 BitBlt(hdc, 0, 0, bm.bmWidth, bm.bmHeight, hdcMem, 0, 0, SRCCOPY); 3227 3228 SelectObject(hdcMem, hbmOld); 3229 DeleteDC(hdcMem); 3230 ReleaseDC(hwnd, hdc); 3231 3232 /* 3233 RECT r; 3234 r.right = i.width; 3235 r.bottom = i.height; 3236 InvalidateRect(hwnd, &r, false); 3237 */ 3238 } else 3239 version(X11) { 3240 if(!destroyed) { 3241 if(i.usingXshm) 3242 XShmPutImage(display, cast(Drawable) window, gc, i.handle, 0, 0, 0, 0, i.width, i.height, false); 3243 else 3244 XPutImage(display, cast(Drawable) window, gc, i.handle, 0, 0, 0, 0, i.width, i.height); 3245 } 3246 } else 3247 version(OSXCocoa) { 3248 draw().drawImage(Point(0, 0), i); 3249 setNeedsDisplay(view, true); 3250 } else static assert(0); 3251 +/ 3252 auto painter = this.draw; 3253 painter.drawImage(Point(0, 0), i); 3254 } 3255 3256 /++ 3257 Changes the cursor for the window. If the cursor is hidden via [hideCursor], this has no effect. 3258 3259 --- 3260 window.cursor = GenericCursor.Help; 3261 // now the window mouse cursor is set to a generic help 3262 --- 3263 3264 +/ 3265 @property void cursor(MouseCursor cursor) { 3266 version(OSXCocoa) 3267 featureNotImplemented(); 3268 else 3269 if(this.impl.curHidden <= 0) { 3270 static if(UsingSimpledisplayX11) { 3271 auto ch = cursor.cursorHandle; 3272 XDefineCursor(XDisplayConnection.get(), this.impl.window, ch); 3273 } else version(Windows) { 3274 auto ch = cursor.cursorHandle; 3275 impl.currentCursor = ch; 3276 SetCursor(ch); // redraw without waiting for mouse movement to update 3277 } else featureNotImplemented(); 3278 } 3279 3280 } 3281 3282 /// What follows are the event handlers. These are set automatically 3283 /// by the eventLoop function, but are still public so you can change 3284 /// them later. wasPressed == true means key down. false == key up. 3285 3286 /// Handles a low-level keyboard event. Settable through setEventHandlers. 3287 void delegate(KeyEvent ke) handleKeyEvent; 3288 3289 /// Handles a higher level keyboard event - c is the character just pressed. Settable through setEventHandlers. 3290 void delegate(dchar c) handleCharEvent; 3291 3292 /// Handles a timer pulse. Settable through setEventHandlers. 3293 void delegate() handlePulse; 3294 3295 /// Called when the focus changes, param is if we have it (true) or are losing it (false). 3296 void delegate(bool) onFocusChange; 3297 3298 /** Called inside `close()` method. Our window is still alive, and we can free various resources. 3299 * Sometimes it is easier to setup the delegate instead of subclassing. */ 3300 void delegate() onClosing; 3301 3302 /** Called when we received destroy notification. At this stage we cannot do much with our window 3303 * (as it is already dead, and it's native handle cannot be used), but we still can do some 3304 * last minute cleanup. */ 3305 void delegate() onDestroyed; 3306 3307 static if (UsingSimpledisplayX11) 3308 /** Called when Expose event comes. See Xlib manual to understand the arguments. 3309 * Return `false` if you want Simpledisplay to copy backbuffer, or `true` if you did it yourself. 3310 * You will probably never need to setup this handler, it is for very low-level stuff. 3311 * 3312 * WARNING! Xlib is multithread-locked when this handles is called! */ 3313 bool delegate(int x, int y, int width, int height, int eventsLeft) handleExpose; 3314 3315 //version(Windows) 3316 //bool delegate(WPARAM wParam, LPARAM lParam) handleWM_PAINT; 3317 3318 private { 3319 int lastMouseX = int.min; 3320 int lastMouseY = int.min; 3321 void mdx(ref MouseEvent ev) { 3322 if(lastMouseX == int.min || lastMouseY == int.min) { 3323 ev.dx = 0; 3324 ev.dy = 0; 3325 } else { 3326 ev.dx = ev.x - lastMouseX; 3327 ev.dy = ev.y - lastMouseY; 3328 } 3329 3330 lastMouseX = ev.x; 3331 lastMouseY = ev.y; 3332 } 3333 } 3334 3335 /// Mouse event handler. Settable through setEventHandlers. 3336 void delegate(MouseEvent) handleMouseEvent; 3337 3338 /// use to redraw child widgets if you use system apis to add stuff 3339 void delegate() paintingFinished; 3340 3341 void delegate() paintingFinishedDg() { 3342 return paintingFinished; 3343 } 3344 3345 /// handle a resize, after it happens. You must construct the window with Resizability.allowResizing 3346 /// for this to ever happen. 3347 void delegate(int width, int height) windowResized; 3348 3349 /++ 3350 Platform specific - handle any native message this window gets. 3351 3352 Note: this is called *in addition to* other event handlers, unless you either: 3353 3354 1) On X11, return 0 indicating that you handled it. Any other return value is simply discarded. 3355 3356 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. 3357 3358 On Windows, your delegate takes the form of `int delegate(HWND hwnd, UINT message, WPARAM wParam, LPARAM lParam, out int mustReturn)`. 3359 3360 On X, it takes the form of `int delegate(XEvent)`. 3361 3362 History: 3363 In ancient versions, this was `static`. If you want a global hook, use [handleNativeGlobalEvent] instead. 3364 3365 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. 3366 +/ 3367 NativeEventHandler handleNativeEvent_; 3368 3369 @property NativeEventHandler handleNativeEvent() nothrow pure @nogc const @safe { 3370 return handleNativeEvent_; 3371 } 3372 @property void handleNativeEvent(NativeEventHandler neh) nothrow pure @nogc @safe { 3373 handleNativeEvent_ = neh; 3374 } 3375 3376 version(Windows) 3377 // compatibility shim with the old deprecated way 3378 // in this one, if you return 0, it means you must return. otherwise the ret value is ignored. 3379 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) { 3380 handleNativeEvent_ = delegate int(HWND h, UINT m, WPARAM w, LPARAM l, out int r) { 3381 auto ret = dg(h, m, w, l); 3382 if(ret == 0) 3383 r = 1; 3384 return ret; 3385 }; 3386 } 3387 3388 /// This is the same as handleNativeEvent, but static so it can hook ALL events in the loop. 3389 /// If you used to use handleNativeEvent depending on it being static, just change it to use 3390 /// this instead and it will work the same way. 3391 __gshared NativeEventHandler handleNativeGlobalEvent; 3392 3393 // private: 3394 /// The native implementation is available, but you shouldn't use it unless you are 3395 /// familiar with the underlying operating system, don't mind depending on it, and 3396 /// know simpledisplay.d's internals too. It is virtually private; you can hopefully 3397 /// do what you need to do with handleNativeEvent instead. 3398 /// 3399 /// This is likely to eventually change to be just a struct holding platform-specific 3400 /// handles instead of a template mixin at some point because I'm not happy with the 3401 /// code duplication here (ironically). 3402 mixin NativeSimpleWindowImplementation!() impl; 3403 3404 /** 3405 This is in-process one-way (from anything to window) event sending mechanics. 3406 It is thread-safe, so it can be used in multi-threaded applications to send, 3407 for example, "wake up and repaint" events when thread completed some operation. 3408 This will allow to avoid using timer pulse to check events with synchronization, 3409 'cause event handler will be called in UI thread. You can stop guessing which 3410 pulse frequency will be enough for your app. 3411 Note that events handlers may be called in arbitrary order, i.e. last registered 3412 handler can be called first, and vice versa. 3413 */ 3414 public: 3415 /** Is our custom event queue empty? Can be used in simple cases to prevent 3416 * "spamming" window with events it can't cope with. 3417 * It is safe to call this from non-UI threads. 3418 */ 3419 @property bool eventQueueEmpty() () { 3420 synchronized(this) { 3421 foreach (const ref o; eventQueue[0..eventQueueUsed]) if (!o.doProcess) return false; 3422 } 3423 return true; 3424 } 3425 3426 /** Does our custom event queue contains at least one with the given type? 3427 * Can be used in simple cases to prevent "spamming" window with events 3428 * it can't cope with. 3429 * It is safe to call this from non-UI threads. 3430 */ 3431 @property bool eventQueued(ET:Object) () { 3432 synchronized(this) { 3433 foreach (const ref o; eventQueue[0..eventQueueUsed]) { 3434 if (!o.doProcess) { 3435 if (cast(ET)(o.evt)) return true; 3436 } 3437 } 3438 } 3439 return false; 3440 } 3441 3442 /++ 3443 Event listeners added with [addEventListener] have their exceptions swallowed by the event loop. This delegate can handle them again before it proceeds. 3444 3445 History: 3446 Added May 12, 2021 3447 +/ 3448 void delegate(Exception e) nothrow eventUncaughtException; 3449 3450 /** Add listener for custom event. Can be used like this: 3451 * 3452 * --------------------- 3453 * auto eid = win.addEventListener((MyStruct evt) { ... }); 3454 * ... 3455 * win.removeEventListener(eid); 3456 * --------------------- 3457 * 3458 * Returns: 0 on failure (should never happen, so ignore it) 3459 * 3460 * $(WARNING Don't use this method in object destructors!) 3461 * 3462 * $(WARNING It is better to register all event handlers and don't remove 'em, 3463 * 'cause if event handler id counter will overflow, you won't be able 3464 * to register any more events.) 3465 */ 3466 uint addEventListener(ET:Object) (void delegate (ET) dg) { 3467 if (dg is null) return 0; // ignore empty handlers 3468 synchronized(this) { 3469 //FIXME: abort on overflow? 3470 if (++lastUsedHandlerId == 0) { --lastUsedHandlerId; return 0; } // alas, can't register more events. at all. 3471 EventHandlerEntry e; 3472 e.dg = delegate (Object o) { 3473 if (auto co = cast(ET)o) { 3474 try { 3475 dg(co); 3476 } catch (Exception e) { 3477 // sorry! 3478 if(eventUncaughtException) 3479 eventUncaughtException(e); 3480 } 3481 return true; 3482 } 3483 return false; 3484 }; 3485 e.id = lastUsedHandlerId; 3486 auto optr = eventHandlers.ptr; 3487 eventHandlers ~= e; 3488 if (eventHandlers.ptr !is optr) { 3489 import core.memory : GC; 3490 if (eventHandlers.ptr is GC.addrOf(eventHandlers.ptr)) GC.setAttr(eventHandlers.ptr, GC.BlkAttr.NO_INTERIOR); 3491 } 3492 return lastUsedHandlerId; 3493 } 3494 } 3495 3496 /// Remove event listener. It is safe to pass invalid event id here. 3497 /// $(WARNING Don't use this method in object destructors!) 3498 void removeEventListener() (uint id) { 3499 if (id == 0 || id > lastUsedHandlerId) return; 3500 synchronized(this) { 3501 foreach (immutable idx; 0..eventHandlers.length) { 3502 if (eventHandlers[idx].id == id) { 3503 foreach (immutable c; idx+1..eventHandlers.length) eventHandlers[c-1] = eventHandlers[c]; 3504 eventHandlers[$-1].dg = null; 3505 eventHandlers.length -= 1; 3506 eventHandlers.assumeSafeAppend; 3507 return; 3508 } 3509 } 3510 } 3511 } 3512 3513 /// Post event to queue. It is safe to call this from non-UI threads. 3514 /// If `timeoutmsecs` is greater than zero, the event will be delayed for at least `timeoutmsecs` milliseconds. 3515 /// if `replace` is `true`, replace all existing events typed `ET` with the new one (if `evt` is empty, remove 'em all) 3516 /// Returns `true` if event was queued. Always returns `false` if `evt` is null. 3517 bool postTimeout(ET:Object) (ET evt, uint timeoutmsecs, bool replace=false) { 3518 if (this.closed) return false; // closed windows can't handle events 3519 3520 // remove all events of type `ET` 3521 void removeAllET () { 3522 uint eidx = 0, ec = eventQueueUsed; 3523 auto eptr = eventQueue.ptr; 3524 while (eidx < ec) { 3525 if (eptr.doProcess) { ++eidx; ++eptr; continue; } 3526 if (cast(ET)eptr.evt !is null) { 3527 // i found her! 3528 if (inCustomEventProcessor) { 3529 // if we're in custom event processing loop, processor will clear it for us 3530 eptr.evt = null; 3531 ++eidx; 3532 ++eptr; 3533 } else { 3534 foreach (immutable c; eidx+1..ec) eventQueue.ptr[c-1] = eventQueue.ptr[c]; 3535 ec = --eventQueueUsed; 3536 // clear last event (it is already copied) 3537 eventQueue.ptr[ec].evt = null; 3538 } 3539 } else { 3540 ++eidx; 3541 ++eptr; 3542 } 3543 } 3544 } 3545 3546 if (evt is null) { 3547 if (replace) { synchronized(this) removeAllET(); } 3548 // ignore empty events, they can't be handled anyway 3549 return false; 3550 } 3551 3552 // add events even if no event FD/event object created yet 3553 synchronized(this) { 3554 if (replace) removeAllET(); 3555 if (eventQueueUsed == uint.max) return false; // just in case 3556 if (eventQueueUsed < eventQueue.length) { 3557 eventQueue[eventQueueUsed++] = QueuedEvent(evt, timeoutmsecs); 3558 } else { 3559 if (eventQueue.capacity == eventQueue.length) { 3560 // need to reallocate; do a trick to ensure that old array is cleared 3561 auto oarr = eventQueue; 3562 eventQueue ~= QueuedEvent(evt, timeoutmsecs); 3563 // just in case, do yet another check 3564 if (oarr.length != 0 && oarr.ptr !is eventQueue.ptr) foreach (ref e; oarr[0..eventQueueUsed]) e.evt = null; 3565 import core.memory : GC; 3566 if (eventQueue.ptr is GC.addrOf(eventQueue.ptr)) GC.setAttr(eventQueue.ptr, GC.BlkAttr.NO_INTERIOR); 3567 } else { 3568 auto optr = eventQueue.ptr; 3569 eventQueue ~= QueuedEvent(evt, timeoutmsecs); 3570 assert(eventQueue.ptr is optr); 3571 } 3572 ++eventQueueUsed; 3573 assert(eventQueueUsed == eventQueue.length); 3574 } 3575 if (!eventWakeUp()) { 3576 // can't wake up event processor, so there is no reason to keep the event 3577 assert(eventQueueUsed > 0); 3578 eventQueue[--eventQueueUsed].evt = null; 3579 return false; 3580 } 3581 return true; 3582 } 3583 } 3584 3585 /// Post event to queue. It is safe to call this from non-UI threads. 3586 /// if `replace` is `true`, replace all existing events typed `ET` with the new one (if `evt` is empty, remove 'em all) 3587 /// Returns `true` if event was queued. Always returns `false` if `evt` is null. 3588 bool postEvent(ET:Object) (ET evt, bool replace=false) { 3589 return postTimeout!ET(evt, 0, replace); 3590 } 3591 3592 private: 3593 private import core.time : MonoTime; 3594 3595 version(Posix) { 3596 __gshared int customEventFDRead = -1; 3597 __gshared int customEventFDWrite = -1; 3598 __gshared int customSignalFD = -1; 3599 } else version(Windows) { 3600 __gshared HANDLE customEventH = null; 3601 } 3602 3603 // wake up event processor 3604 static bool eventWakeUp () { 3605 version(X11) { 3606 import core.sys.posix.unistd : write; 3607 ulong n = 1; 3608 if (customEventFDWrite >= 0) write(customEventFDWrite, &n, n.sizeof); 3609 return true; 3610 } else version(Windows) { 3611 if (customEventH !is null) SetEvent(customEventH); 3612 return true; 3613 } else { 3614 // not implemented for other OSes 3615 return false; 3616 } 3617 } 3618 3619 static struct QueuedEvent { 3620 Object evt; 3621 bool timed = false; 3622 MonoTime hittime = MonoTime.zero; 3623 bool doProcess = false; // process event at the current iteration (internal flag) 3624 3625 this (Object aevt, uint toutmsecs) { 3626 evt = aevt; 3627 if (toutmsecs > 0) { 3628 import core.time : msecs; 3629 timed = true; 3630 hittime = MonoTime.currTime+toutmsecs.msecs; 3631 } 3632 } 3633 } 3634 3635 alias CustomEventHandler = bool delegate (Object o) nothrow; 3636 static struct EventHandlerEntry { 3637 CustomEventHandler dg; 3638 uint id; 3639 } 3640 3641 uint lastUsedHandlerId; 3642 EventHandlerEntry[] eventHandlers; 3643 QueuedEvent[] eventQueue = null; 3644 uint eventQueueUsed = 0; // to avoid `.assumeSafeAppend` and length changes 3645 bool inCustomEventProcessor = false; // required to properly remove events 3646 3647 // process queued events and call custom event handlers 3648 // this will not process events posted from called handlers (such events are postponed for the next iteration) 3649 void processCustomEvents () { 3650 bool hasSomethingToDo = false; 3651 uint ecount; 3652 bool ocep; 3653 synchronized(this) { 3654 ocep = inCustomEventProcessor; 3655 inCustomEventProcessor = true; 3656 ecount = eventQueueUsed; // user may want to post new events from an event handler; process 'em on next iteration 3657 auto ctt = MonoTime.currTime; 3658 bool hasEmpty = false; 3659 // mark events to process (this is required for `eventQueued()`) 3660 foreach (ref qe; eventQueue[0..ecount]) { 3661 if (qe.evt is null) { hasEmpty = true; continue; } 3662 if (qe.timed) { 3663 qe.doProcess = (qe.hittime <= ctt); 3664 } else { 3665 qe.doProcess = true; 3666 } 3667 hasSomethingToDo = (hasSomethingToDo || qe.doProcess); 3668 } 3669 if (!hasSomethingToDo) { 3670 // remove empty events 3671 if (hasEmpty) { 3672 uint eidx = 0, ec = eventQueueUsed; 3673 auto eptr = eventQueue.ptr; 3674 while (eidx < ec) { 3675 if (eptr.evt is null) { 3676 foreach (immutable c; eidx+1..ec) eventQueue.ptr[c-1] = eventQueue.ptr[c]; 3677 ec = --eventQueueUsed; 3678 eventQueue.ptr[ec].evt = null; // make GC life easier 3679 } else { 3680 ++eidx; 3681 ++eptr; 3682 } 3683 } 3684 } 3685 inCustomEventProcessor = ocep; 3686 return; 3687 } 3688 } 3689 // process marked events 3690 uint efree = 0; // non-processed events will be put at this index 3691 EventHandlerEntry[] eh; 3692 Object evt; 3693 foreach (immutable eidx; 0..ecount) { 3694 synchronized(this) { 3695 if (!eventQueue[eidx].doProcess) { 3696 // skip this event 3697 assert(efree <= eidx); 3698 if (efree != eidx) { 3699 // copy this event to queue start 3700 eventQueue[efree] = eventQueue[eidx]; 3701 eventQueue[eidx].evt = null; // just in case 3702 } 3703 ++efree; 3704 continue; 3705 } 3706 evt = eventQueue[eidx].evt; 3707 eventQueue[eidx].evt = null; // in case event handler will hit GC 3708 if (evt is null) continue; // just in case 3709 // try all handlers; this can be slow, but meh... 3710 eh = eventHandlers; 3711 } 3712 foreach (ref evhan; eh) if (evhan.dg !is null) evhan.dg(evt); 3713 evt = null; 3714 eh = null; 3715 } 3716 synchronized(this) { 3717 // move all unprocessed events to queue top; efree holds first "free index" 3718 foreach (immutable eidx; ecount..eventQueueUsed) { 3719 assert(efree <= eidx); 3720 if (efree != eidx) eventQueue[efree] = eventQueue[eidx]; 3721 ++efree; 3722 } 3723 eventQueueUsed = efree; 3724 // wake up event processor on next event loop iteration if we have more queued events 3725 // also, remove empty events 3726 bool awaken = false; 3727 uint eidx = 0, ec = eventQueueUsed; 3728 auto eptr = eventQueue.ptr; 3729 while (eidx < ec) { 3730 if (eptr.evt is null) { 3731 foreach (immutable c; eidx+1..ec) eventQueue.ptr[c-1] = eventQueue.ptr[c]; 3732 ec = --eventQueueUsed; 3733 eventQueue.ptr[ec].evt = null; // make GC life easier 3734 } else { 3735 if (!awaken && !eptr.timed) { eventWakeUp(); awaken = true; } 3736 ++eidx; 3737 ++eptr; 3738 } 3739 } 3740 inCustomEventProcessor = ocep; 3741 } 3742 } 3743 3744 // for all windows in nativeMapping 3745 package static void processAllCustomEvents () { 3746 3747 cleanupQueue.process(); 3748 3749 justCommunication.processCustomEvents(); 3750 3751 foreach (SimpleWindow sw; SimpleWindow.nativeMapping.byValue) { 3752 if (sw is null || sw.closed) continue; 3753 sw.processCustomEvents(); 3754 } 3755 3756 runPendingRunInGuiThreadDelegates(); 3757 } 3758 3759 // 0: infinite (i.e. no scheduled events in queue) 3760 uint eventQueueTimeoutMSecs () { 3761 synchronized(this) { 3762 if (eventQueueUsed == 0) return 0; 3763 if (inCustomEventProcessor) assert(0, "WUTAFUUUUUUU..."); // the thing that should not be. ABSOLUTELY! (c) 3764 uint res = int.max; 3765 auto ctt = MonoTime.currTime; 3766 foreach (const ref qe; eventQueue[0..eventQueueUsed]) { 3767 if (qe.evt is null) assert(0, "WUTAFUUUUUUU..."); // the thing that should not be. ABSOLUTELY! (c) 3768 if (qe.doProcess) continue; // just in case 3769 if (!qe.timed) return 1; // minimal 3770 if (qe.hittime <= ctt) return 1; // minimal 3771 auto tms = (qe.hittime-ctt).total!"msecs"; 3772 if (tms < 1) tms = 1; // safety net 3773 if (tms >= int.max) tms = int.max-1; // and another safety net 3774 if (res > tms) res = cast(uint)tms; 3775 } 3776 return (res >= int.max ? 0 : res); 3777 } 3778 } 3779 3780 // for all windows in nativeMapping 3781 static uint eventAllQueueTimeoutMSecs () { 3782 uint res = uint.max; 3783 foreach (SimpleWindow sw; SimpleWindow.nativeMapping.byValue) { 3784 if (sw is null || sw.closed) continue; 3785 uint to = sw.eventQueueTimeoutMSecs(); 3786 if (to && to < res) { 3787 res = to; 3788 if (to == 1) break; // can't have less than this 3789 } 3790 } 3791 return (res >= int.max ? 0 : res); 3792 } 3793 3794 version(X11) { 3795 ResizeEvent pendingResizeEvent; 3796 } 3797 3798 /++ 3799 When in opengl mode and automatically resizing, it will set the opengl viewport to stretch. 3800 3801 If you work with multiple opengl contexts and/or threads, this might be more trouble than it is 3802 worth so you can disable it by setting this to `true`. 3803 3804 History: 3805 Added November 13, 2022. 3806 +/ 3807 public bool suppressAutoOpenglViewport = false; 3808 private void updateOpenglViewportIfNeeded(int width, int height) { 3809 if(suppressAutoOpenglViewport) return; 3810 3811 version(without_opengl) {} else 3812 if(openglMode == OpenGlOptions.yes && resizability == Resizability.automaticallyScaleIfPossible) { 3813 // writeln(width, " ", height); 3814 setAsCurrentOpenGlContextNT(); 3815 glViewport(0, 0, width, height); 3816 } 3817 } 3818 } 3819 3820 /++ 3821 Magic pseudo-window for just posting events to a global queue. 3822 3823 Not entirely supported, I might delete it at any time. 3824 3825 Added Nov 5, 2021. 3826 +/ 3827 __gshared SimpleWindow justCommunication = new SimpleWindow(NativeWindowHandle.init); 3828 3829 /* Drag and drop support { */ 3830 version(X11) { 3831 3832 } else version(Windows) { 3833 import core.sys.windows.uuid; 3834 import core.sys.windows.ole2; 3835 import core.sys.windows.oleidl; 3836 import core.sys.windows.objidl; 3837 import core.sys.windows.wtypes; 3838 3839 pragma(lib, "ole32"); 3840 void initDnd() { 3841 auto err = OleInitialize(null); 3842 if(err != S_OK && err != S_FALSE) 3843 throw new Exception("init");//err); 3844 } 3845 } 3846 /* } End drag and drop support */ 3847 3848 3849 /// Represents a mouse cursor (aka the mouse pointer, the image seen on screen that indicates where the mouse is pointing). 3850 /// See [GenericCursor]. 3851 class MouseCursor { 3852 int osId; 3853 bool isStockCursor; 3854 private this(int osId) { 3855 this.osId = osId; 3856 this.isStockCursor = true; 3857 } 3858 3859 // https://msdn.microsoft.com/en-us/library/windows/desktop/ms648385(v=vs.85).aspx 3860 this(int xHotSpot, int yHotSpot, ubyte[] andMask, ubyte[] xorMask) {} 3861 3862 version(Windows) { 3863 HCURSOR cursor_; 3864 HCURSOR cursorHandle() { 3865 if(cursor_ is null) 3866 cursor_ = LoadCursor(null, MAKEINTRESOURCE(osId)); 3867 return cursor_; 3868 } 3869 3870 } else static if(UsingSimpledisplayX11) { 3871 Cursor cursor_ = None; 3872 int xDisplaySequence; 3873 3874 Cursor cursorHandle() { 3875 if(this.osId == None) 3876 return None; 3877 3878 // we need to reload if we on a new X connection 3879 if(cursor_ == None || XDisplayConnection.connectionSequenceNumber != xDisplaySequence) { 3880 cursor_ = XCreateFontCursor(XDisplayConnection.get(), this.osId); 3881 xDisplaySequence = XDisplayConnection.connectionSequenceNumber; 3882 } 3883 return cursor_; 3884 } 3885 } 3886 } 3887 3888 // https://developer.mozilla.org/en-US/docs/Web/CSS/cursor 3889 // https://tronche.com/gui/x/xlib/appendix/b/ 3890 // https://msdn.microsoft.com/en-us/library/windows/desktop/ms648391(v=vs.85).aspx 3891 /// 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. 3892 enum GenericCursorType { 3893 Default, /// The default arrow pointer. 3894 Wait, /// A cursor indicating something is loading and the user must wait. 3895 Hand, /// A pointing finger, like the one used hovering over hyperlinks in a web browser. 3896 Help, /// A cursor indicating the user can get help about the pointer location. 3897 Cross, /// A crosshair. 3898 Text, /// An i-beam shape, typically used to indicate text selection is possible. 3899 Move, /// Pointer indicating movement is possible. May also be used as SizeAll. 3900 UpArrow, /// An arrow pointing straight up. 3901 Progress, /// The hourglass and arrow, indicating the computer is working but the user can still work. Not great results on X11. 3902 NotAllowed, /// Indicates the current operation is not allowed. Not great results on X11. 3903 SizeNesw, /// Arrow pointing northeast and southwest (lower-left corner resize indicator). 3904 SizeNs, /// Arrow pointing north and south (upper/lower edge resize indicator). 3905 SizeNwse, /// Arrow pointing northwest and southeast (upper-left corner resize indicator). 3906 SizeWe, /// Arrow pointing west and east (left/right edge resize indicator). 3907 3908 } 3909 3910 /* 3911 X_plus == css cell == Windows ? 3912 */ 3913 3914 /// You get one by `GenericCursor.SomeTime`. See [GenericCursorType] for a list of types. 3915 static struct GenericCursor { 3916 static: 3917 /// 3918 MouseCursor opDispatch(string str)() if(__traits(hasMember, GenericCursorType, str)) { 3919 static MouseCursor mc; 3920 3921 auto type = __traits(getMember, GenericCursorType, str); 3922 3923 if(mc is null) { 3924 3925 version(Windows) { 3926 int osId; 3927 final switch(type) { 3928 case GenericCursorType.Default: osId = IDC_ARROW; break; 3929 case GenericCursorType.Wait: osId = IDC_WAIT; break; 3930 case GenericCursorType.Hand: osId = IDC_HAND; break; 3931 case GenericCursorType.Help: osId = IDC_HELP; break; 3932 case GenericCursorType.Cross: osId = IDC_CROSS; break; 3933 case GenericCursorType.Text: osId = IDC_IBEAM; break; 3934 case GenericCursorType.Move: osId = IDC_SIZEALL; break; 3935 case GenericCursorType.UpArrow: osId = IDC_UPARROW; break; 3936 case GenericCursorType.Progress: osId = IDC_APPSTARTING; break; 3937 case GenericCursorType.NotAllowed: osId = IDC_NO; break; 3938 case GenericCursorType.SizeNesw: osId = IDC_SIZENESW; break; 3939 case GenericCursorType.SizeNs: osId = IDC_SIZENS; break; 3940 case GenericCursorType.SizeNwse: osId = IDC_SIZENWSE; break; 3941 case GenericCursorType.SizeWe: osId = IDC_SIZEWE; break; 3942 } 3943 } else static if(UsingSimpledisplayX11) { 3944 int osId; 3945 final switch(type) { 3946 case GenericCursorType.Default: osId = None; break; 3947 case GenericCursorType.Wait: osId = 150 /* XC_watch */; break; 3948 case GenericCursorType.Hand: osId = 60 /* XC_hand2 */; break; 3949 case GenericCursorType.Help: osId = 92 /* XC_question_arrow */; break; 3950 case GenericCursorType.Cross: osId = 34 /* XC_crosshair */; break; 3951 case GenericCursorType.Text: osId = 152 /* XC_xterm */; break; 3952 case GenericCursorType.Move: osId = 52 /* XC_fleur */; break; 3953 case GenericCursorType.UpArrow: osId = 22 /* XC_center_ptr */; break; 3954 case GenericCursorType.Progress: osId = 150 /* XC_watch, best i can do i think */; break; 3955 3956 case GenericCursorType.NotAllowed: osId = 24 /* XC_circle. not great */; break; 3957 case GenericCursorType.SizeNesw: osId = 12 /* XC_bottom_left_corner */ ; break; 3958 case GenericCursorType.SizeNs: osId = 116 /* XC_sb_v_double_arrow */; break; 3959 case GenericCursorType.SizeNwse: osId = 14 /* XC_bottom_right_corner */; break; 3960 case GenericCursorType.SizeWe: osId = 108 /* XC_sb_h_double_arrow */; break; 3961 } 3962 3963 } else featureNotImplemented(); 3964 3965 mc = new MouseCursor(osId); 3966 } 3967 return mc; 3968 } 3969 } 3970 3971 3972 /++ 3973 If you want to get more control over the event loop, you can use this. 3974 3975 Typically though, you can just call [SimpleWindow.eventLoop] which forwards 3976 to `EventLoop.get.run`. 3977 +/ 3978 struct EventLoop { 3979 @disable this(); 3980 3981 /// Gets a reference to an existing event loop 3982 static EventLoop get() { 3983 return EventLoop(0, null); 3984 } 3985 3986 static void quitApplication() { 3987 EventLoop.get().exit(); 3988 } 3989 3990 private __gshared static Object monitor = new Object(); // deliberate CTFE usage here fyi 3991 3992 /// Construct an application-global event loop for yourself 3993 /// See_Also: [SimpleWindow.setEventHandlers] 3994 this(long pulseTimeout, void delegate() handlePulse) { 3995 synchronized(monitor) { 3996 if(impl is null) { 3997 claimGuiThread(); 3998 version(sdpy_thread_checks) assert(thisIsGuiThread); 3999 impl = new EventLoopImpl(pulseTimeout, handlePulse); 4000 } else { 4001 if(pulseTimeout) { 4002 impl.pulseTimeout = pulseTimeout; 4003 impl.handlePulse = handlePulse; 4004 } 4005 } 4006 impl.refcount++; 4007 } 4008 } 4009 4010 ~this() { 4011 if(impl is null) 4012 return; 4013 impl.refcount--; 4014 if(impl.refcount == 0) { 4015 impl.dispose(); 4016 if(thisIsGuiThread) 4017 guiThreadFinalize(); 4018 } 4019 4020 } 4021 4022 this(this) { 4023 if(impl is null) 4024 return; 4025 impl.refcount++; 4026 } 4027 4028 /// Runs the event loop until the whileCondition, if present, returns false 4029 int run(bool delegate() whileCondition = null) { 4030 assert(impl !is null); 4031 impl.notExited = true; 4032 return impl.run(whileCondition); 4033 } 4034 4035 /// Exits the event loop 4036 void exit() { 4037 assert(impl !is null); 4038 impl.notExited = false; 4039 } 4040 4041 version(linux) 4042 ref void delegate(int) signalHandler() { 4043 assert(impl !is null); 4044 return impl.signalHandler; 4045 } 4046 4047 __gshared static EventLoopImpl* impl; 4048 } 4049 4050 version(linux) 4051 void delegate(int, int) globalHupHandler; 4052 4053 version(Posix) 4054 void makeNonBlocking(int fd) { 4055 import fcntl = core.sys.posix.fcntl; 4056 auto flags = fcntl.fcntl(fd, fcntl.F_GETFL, 0); 4057 if(flags == -1) 4058 throw new Exception("fcntl get"); 4059 flags |= fcntl.O_NONBLOCK; 4060 auto s = fcntl.fcntl(fd, fcntl.F_SETFL, flags); 4061 if(s == -1) 4062 throw new Exception("fcntl set"); 4063 } 4064 4065 struct EventLoopImpl { 4066 int refcount; 4067 4068 bool notExited = true; 4069 4070 version(linux) { 4071 static import ep = core.sys.linux.epoll; 4072 static import unix = core.sys.posix.unistd; 4073 static import err = core.stdc.errno; 4074 import core.sys.linux.timerfd; 4075 4076 void delegate(int) signalHandler; 4077 } 4078 4079 version(X11) { 4080 int pulseFd = -1; 4081 version(linux) ep.epoll_event[16] events = void; 4082 } else version(Windows) { 4083 Timer pulser; 4084 HANDLE[] handles; 4085 } 4086 4087 4088 /// "Lock" this window handle, to do multithreaded synchronization. You probably won't need 4089 /// to call this, as it's not recommended to share window between threads. 4090 void mtLock () { 4091 version(X11) { 4092 XLockDisplay(this.display); 4093 } 4094 } 4095 4096 version(X11) 4097 auto display() { return XDisplayConnection.get; } 4098 4099 /// "Unlock" this window handle, to do multithreaded synchronization. You probably won't need 4100 /// to call this, as it's not recommended to share window between threads. 4101 void mtUnlock () { 4102 version(X11) { 4103 XUnlockDisplay(this.display); 4104 } 4105 } 4106 4107 version(with_eventloop) 4108 void initialize(long pulseTimeout) {} 4109 else 4110 void initialize(long pulseTimeout) { 4111 version(Windows) { 4112 if(pulseTimeout && handlePulse !is null) 4113 pulser = new Timer(cast(int) pulseTimeout, handlePulse); 4114 4115 if (customEventH is null) { 4116 customEventH = CreateEvent(null, FALSE/*autoreset*/, FALSE/*initial state*/, null); 4117 if (customEventH !is null) { 4118 handles ~= customEventH; 4119 } else { 4120 // this is something that should not be; better be safe than sorry 4121 throw new Exception("can't create eventfd for custom event processing"); 4122 } 4123 } 4124 4125 SimpleWindow.processAllCustomEvents(); // process events added before event object creation 4126 } 4127 4128 version(linux) { 4129 prepareEventLoop(); 4130 { 4131 auto display = XDisplayConnection.get; 4132 // adding Xlib file 4133 ep.epoll_event ev = void; 4134 { import core.stdc.string : memset; memset(&ev, 0, ev.sizeof); } // this makes valgrind happy 4135 ev.events = ep.EPOLLIN; 4136 ev.data.fd = display.fd; 4137 if(ep.epoll_ctl(epollFd, ep.EPOLL_CTL_ADD, display.fd, &ev) == -1) 4138 throw new Exception("add x fd");// ~ to!string(epollFd)); 4139 displayFd = display.fd; 4140 } 4141 4142 if(pulseTimeout && handlePulse !is null) { 4143 pulseFd = timerfd_create(CLOCK_MONOTONIC, 0); 4144 if(pulseFd == -1) 4145 throw new Exception("pulse timer create failed"); 4146 4147 itimerspec value; 4148 value.it_value.tv_sec = cast(int) (pulseTimeout / 1000); 4149 value.it_value.tv_nsec = (pulseTimeout % 1000) * 1000_000; 4150 4151 value.it_interval.tv_sec = cast(int) (pulseTimeout / 1000); 4152 value.it_interval.tv_nsec = (pulseTimeout % 1000) * 1000_000; 4153 4154 if(timerfd_settime(pulseFd, 0, &value, null) == -1) 4155 throw new Exception("couldn't make pulse timer"); 4156 4157 ep.epoll_event ev = void; 4158 { import core.stdc.string : memset; memset(&ev, 0, ev.sizeof); } // this makes valgrind happy 4159 ev.events = ep.EPOLLIN; 4160 ev.data.fd = pulseFd; 4161 ep.epoll_ctl(epollFd, ep.EPOLL_CTL_ADD, pulseFd, &ev); 4162 } 4163 4164 // eventfd for custom events 4165 if (customEventFDWrite == -1) { 4166 customEventFDWrite = eventfd(0, 0); 4167 customEventFDRead = customEventFDWrite; 4168 if (customEventFDRead >= 0) { 4169 ep.epoll_event ev = void; 4170 { import core.stdc.string : memset; memset(&ev, 0, ev.sizeof); } // this makes valgrind happy 4171 ev.events = ep.EPOLLIN; 4172 ev.data.fd = customEventFDRead; 4173 ep.epoll_ctl(epollFd, ep.EPOLL_CTL_ADD, customEventFDRead, &ev); 4174 } else { 4175 // this is something that should not be; better be safe than sorry 4176 throw new Exception("can't create eventfd for custom event processing"); 4177 } 4178 } 4179 4180 if (customSignalFD == -1) { 4181 import core.sys.linux.sys.signalfd; 4182 4183 sigset_t sigset; 4184 auto err = sigemptyset(&sigset); 4185 assert(!err); 4186 err = sigaddset(&sigset, SIGINT); 4187 assert(!err); 4188 err = sigaddset(&sigset, SIGHUP); 4189 assert(!err); 4190 err = sigprocmask(SIG_BLOCK, &sigset, null); 4191 assert(!err); 4192 4193 customSignalFD = signalfd(-1, &sigset, SFD_NONBLOCK); 4194 assert(customSignalFD != -1); 4195 4196 ep.epoll_event ev = void; 4197 { import core.stdc.string : memset; memset(&ev, 0, ev.sizeof); } // this makes valgrind happy 4198 ev.events = ep.EPOLLIN; 4199 ev.data.fd = customSignalFD; 4200 ep.epoll_ctl(epollFd, ep.EPOLL_CTL_ADD, customSignalFD, &ev); 4201 } 4202 } else version(Posix) { 4203 prepareEventLoop(); 4204 if (customEventFDRead == -1) { 4205 int[2] bfr; 4206 import core.sys.posix.unistd; 4207 auto ret = pipe(bfr); 4208 if(ret == -1) throw new Exception("pipe"); 4209 customEventFDRead = bfr[0]; 4210 customEventFDWrite = bfr[1]; 4211 } 4212 4213 } 4214 4215 SimpleWindow.processAllCustomEvents(); // process events added before event FD creation 4216 4217 version(linux) { 4218 this.mtLock(); 4219 scope(exit) this.mtUnlock(); 4220 XPending(display); // no, really 4221 } 4222 4223 disposed = false; 4224 } 4225 4226 bool disposed = true; 4227 version(X11) 4228 int displayFd = -1; 4229 4230 version(with_eventloop) 4231 void dispose() {} 4232 else 4233 void dispose() { 4234 disposed = true; 4235 version(X11) { 4236 if(pulseFd != -1) { 4237 import unix = core.sys.posix.unistd; 4238 unix.close(pulseFd); 4239 pulseFd = -1; 4240 } 4241 4242 version(linux) 4243 if(displayFd != -1) { 4244 // 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 4245 ep.epoll_event ev = void; 4246 { import core.stdc.string : memset; memset(&ev, 0, ev.sizeof); } // this makes valgrind happy 4247 ev.events = ep.EPOLLIN; 4248 ev.data.fd = displayFd; 4249 ep.epoll_ctl(epollFd, ep.EPOLL_CTL_DEL, displayFd, &ev); 4250 displayFd = -1; 4251 } 4252 4253 } else version(Windows) { 4254 if(pulser !is null) { 4255 pulser.destroy(); 4256 pulser = null; 4257 } 4258 if (customEventH !is null) { 4259 CloseHandle(customEventH); 4260 customEventH = null; 4261 } 4262 } 4263 } 4264 4265 this(long pulseTimeout, void delegate() handlePulse) { 4266 this.pulseTimeout = pulseTimeout; 4267 this.handlePulse = handlePulse; 4268 initialize(pulseTimeout); 4269 } 4270 4271 private long pulseTimeout; 4272 void delegate() handlePulse; 4273 4274 ~this() { 4275 dispose(); 4276 } 4277 4278 version(Posix) 4279 ref int customEventFDRead() { return SimpleWindow.customEventFDRead; } 4280 version(Posix) 4281 ref int customEventFDWrite() { return SimpleWindow.customEventFDWrite; } 4282 version(linux) 4283 ref int customSignalFD() { return SimpleWindow.customSignalFD; } 4284 version(Windows) 4285 ref auto customEventH() { return SimpleWindow.customEventH; } 4286 4287 version(with_eventloop) { 4288 int loopHelper(bool delegate() whileCondition) { 4289 // FIXME: whileCondition 4290 import arsd.eventloop; 4291 loop(); 4292 return 0; 4293 } 4294 } else 4295 int loopHelper(bool delegate() whileCondition) { 4296 version(X11) { 4297 bool done = false; 4298 4299 XFlush(display); 4300 insideXEventLoop = true; 4301 scope(exit) insideXEventLoop = false; 4302 4303 version(linux) { 4304 while(!done && (whileCondition is null || whileCondition() == true) && notExited) { 4305 bool forceXPending = false; 4306 auto wto = SimpleWindow.eventAllQueueTimeoutMSecs(); 4307 // eh... some events may be queued for "squashing" (or "late delivery"), so we have to do the following magic 4308 { 4309 this.mtLock(); 4310 scope(exit) this.mtUnlock(); 4311 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 4312 } 4313 //{ import core.stdc.stdio; printf("*** wto=%d; force=%d\n", wto, (forceXPending ? 1 : 0)); } 4314 auto nfds = ep.epoll_wait(epollFd, events.ptr, events.length, (wto == 0 || wto >= int.max ? -1 : cast(int)wto)); 4315 if(nfds == -1) { 4316 if(err.errno == err.EINTR) { 4317 //if(forceXPending) goto xpending; 4318 continue; // interrupted by signal, just try again 4319 } 4320 throw new Exception("epoll wait failure"); 4321 } 4322 // writeln(nfds, " ", events[0].data.fd); 4323 4324 SimpleWindow.processAllCustomEvents(); // anyway 4325 //version(sdddd) { writeln("nfds=", nfds, "; [0]=", events[0].data.fd); } 4326 foreach(idx; 0 .. nfds) { 4327 if(done) break; 4328 auto fd = events[idx].data.fd; 4329 assert(fd != -1); // should never happen cuz the api doesn't do that but better to assert than assume. 4330 auto flags = events[idx].events; 4331 if(flags & ep.EPOLLIN) { 4332 if (fd == customSignalFD) { 4333 version(linux) { 4334 import core.sys.linux.sys.signalfd; 4335 import core.sys.posix.unistd : read; 4336 signalfd_siginfo info; 4337 read(customSignalFD, &info, info.sizeof); 4338 4339 auto sig = info.ssi_signo; 4340 4341 if(EventLoop.get.signalHandler !is null) { 4342 EventLoop.get.signalHandler()(sig); 4343 } else { 4344 EventLoop.get.exit(); 4345 } 4346 } 4347 } else if(fd == display.fd) { 4348 version(sdddd) { writeln("X EVENT PENDING!"); } 4349 this.mtLock(); 4350 scope(exit) this.mtUnlock(); 4351 while(!done && XPending(display)) { 4352 done = doXNextEvent(this.display); 4353 } 4354 forceXPending = false; 4355 } else if(fd == pulseFd) { 4356 long expirationCount; 4357 // 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... 4358 4359 handlePulse(); 4360 4361 // read just to clear the buffer so poll doesn't trigger again 4362 // BTW I read AFTER the pulse because if the pulse handler takes 4363 // a lot of time to execute, we don't want the app to get stuck 4364 // in a loop of timer hits without a chance to do anything else 4365 // 4366 // IOW handlePulse happens at most once per pulse interval. 4367 unix.read(pulseFd, &expirationCount, expirationCount.sizeof); 4368 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 4369 } else if (fd == customEventFDRead) { 4370 // we have some custom events; process 'em 4371 import core.sys.posix.unistd : read; 4372 ulong n; 4373 read(customEventFDRead, &n, n.sizeof); // reset counter value to zero again 4374 //{ import core.stdc.stdio; printf("custom event! count=%u\n", eventQueueUsed); } 4375 //SimpleWindow.processAllCustomEvents(); 4376 4377 forceXPending = true; 4378 } else { 4379 // some other timer 4380 version(sdddd) { writeln("unknown fd: ", fd); } 4381 4382 if(Timer* t = fd in Timer.mapping) 4383 (*t).trigger(); 4384 4385 if(PosixFdReader* pfr = fd in PosixFdReader.mapping) 4386 (*pfr).ready(flags); 4387 4388 // or i might add support for other FDs too 4389 // but for now it is just timer 4390 // (if you want other fds, use arsd.eventloop and compile with -version=with_eventloop), it offers a fuller api for arbitrary stuff. 4391 } 4392 } 4393 if(flags & ep.EPOLLHUP) { 4394 if(PosixFdReader* pfr = fd in PosixFdReader.mapping) 4395 (*pfr).hup(flags); 4396 if(globalHupHandler) 4397 globalHupHandler(fd, flags); 4398 } 4399 /+ 4400 } else { 4401 // not interested in OUT, we are just reading here. 4402 // 4403 // error or hup might also be reported 4404 // but it shouldn't here since we are only 4405 // using a few types of FD and Xlib will report 4406 // if it dies. 4407 // so instead of thoughtfully handling it, I'll 4408 // just throw. for now at least 4409 4410 throw new Exception("epoll did something else"); 4411 } 4412 +/ 4413 } 4414 // if we won't call `XPending()` here, libX may delay some internal event delivery. 4415 // i.e. we HAVE to repeatedly call `XPending()` even if libX fd wasn't signalled! 4416 xpending: 4417 if (!done && forceXPending) { 4418 this.mtLock(); 4419 scope(exit) this.mtUnlock(); 4420 //{ import core.stdc.stdio; printf("*** queued: %d\n", XEventsQueued(this.display, QueueMode.QueuedAlready)); } 4421 while(!done && XPending(display)) { 4422 done = doXNextEvent(this.display); 4423 } 4424 } 4425 } 4426 } else { 4427 // Generic fallback: yes to simple pulse support, 4428 // but NO timer support! 4429 4430 // FIXME: we could probably support the POSIX timer_create 4431 // signal-based option, but I'm in no rush to write it since 4432 // I prefer the fd-based functions. 4433 while (!done && (whileCondition is null || whileCondition() == true) && notExited) { 4434 4435 import core.sys.posix.poll; 4436 4437 pollfd[] pfds; 4438 pollfd[32] pfdsBuffer; 4439 auto len = PosixFdReader.mapping.length + 2; 4440 // FIXME: i should just reuse the buffer 4441 if(len < pfdsBuffer.length) 4442 pfds = pfdsBuffer[0 .. len]; 4443 else 4444 pfds = new pollfd[](len); 4445 4446 pfds[0].fd = display.fd; 4447 pfds[0].events = POLLIN; 4448 pfds[0].revents = 0; 4449 4450 int slot = 1; 4451 4452 if(customEventFDRead != -1) { 4453 pfds[slot].fd = customEventFDRead; 4454 pfds[slot].events = POLLIN; 4455 pfds[slot].revents = 0; 4456 4457 slot++; 4458 } 4459 4460 foreach(fd, obj; PosixFdReader.mapping) { 4461 if(!obj.enabled) continue; 4462 pfds[slot].fd = fd; 4463 pfds[slot].events = POLLIN; 4464 pfds[slot].revents = 0; 4465 4466 slot++; 4467 } 4468 4469 auto ret = poll(pfds.ptr, slot, pulseTimeout > 0 ? cast(int) pulseTimeout : -1); 4470 if(ret == -1) throw new Exception("poll"); 4471 4472 if(ret == 0) { 4473 // FIXME it may not necessarily time out if events keep coming 4474 if(handlePulse !is null) 4475 handlePulse(); 4476 } else { 4477 foreach(s; 0 .. slot) { 4478 if(pfds[s].revents == 0) continue; 4479 4480 if(pfds[s].fd == display.fd) { 4481 while(!done && XPending(display)) { 4482 this.mtLock(); 4483 scope(exit) this.mtUnlock(); 4484 done = doXNextEvent(this.display); 4485 } 4486 } else if(customEventFDRead != -1 && pfds[s].fd == customEventFDRead) { 4487 4488 import core.sys.posix.unistd : read; 4489 ulong n; 4490 read(customEventFDRead, &n, n.sizeof); 4491 SimpleWindow.processAllCustomEvents(); 4492 } else { 4493 auto obj = PosixFdReader.mapping[pfds[s].fd]; 4494 if(pfds[s].revents & POLLNVAL) { 4495 obj.dispose(); 4496 } else { 4497 obj.ready(pfds[s].revents); 4498 } 4499 } 4500 4501 ret--; 4502 if(ret == 0) break; 4503 } 4504 } 4505 } 4506 } 4507 } 4508 4509 version(Windows) { 4510 int ret = -1; 4511 MSG message; 4512 while(ret != 0 && (whileCondition is null || whileCondition() == true) && notExited) { 4513 eventLoopRound++; 4514 auto wto = SimpleWindow.eventAllQueueTimeoutMSecs(); 4515 auto waitResult = MsgWaitForMultipleObjectsEx( 4516 cast(int) handles.length, handles.ptr, 4517 (wto == 0 ? INFINITE : wto), /* timeout */ 4518 0x04FF, /* QS_ALLINPUT */ 4519 0x0002 /* MWMO_ALERTABLE */ | 0x0004 /* MWMO_INPUTAVAILABLE */); 4520 4521 SimpleWindow.processAllCustomEvents(); // anyway 4522 enum WAIT_OBJECT_0 = 0; 4523 if(waitResult >= WAIT_OBJECT_0 && waitResult < handles.length + WAIT_OBJECT_0) { 4524 auto h = handles[waitResult - WAIT_OBJECT_0]; 4525 if(auto e = h in WindowsHandleReader.mapping) { 4526 (*e).ready(); 4527 } 4528 } else if(waitResult == handles.length + WAIT_OBJECT_0) { 4529 // message ready 4530 int count; 4531 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 4532 ret = GetMessage(&message, null, 0, 0); 4533 if(ret == -1) 4534 throw new WindowsApiException("GetMessage", GetLastError()); 4535 TranslateMessage(&message); 4536 DispatchMessage(&message); 4537 4538 count++; 4539 if(count > 10) 4540 break; // take the opportunity to catch up on other events 4541 4542 if(ret == 0) { // WM_QUIT 4543 EventLoop.quitApplication(); 4544 break; 4545 } 4546 } 4547 } else if(waitResult == 0x000000C0L /* WAIT_IO_COMPLETION */) { 4548 SleepEx(0, true); // I call this to give it a chance to do stuff like async io 4549 } else if(waitResult == 258L /* WAIT_TIMEOUT */) { 4550 // timeout, should never happen since we aren't using it 4551 } else if(waitResult == 0xFFFFFFFF) { 4552 // failed 4553 throw new WindowsApiException("MsgWaitForMultipleObjectsEx", GetLastError()); 4554 } else { 4555 // idk.... 4556 } 4557 } 4558 4559 // return message.wParam; 4560 return 0; 4561 } else { 4562 return 0; 4563 } 4564 } 4565 4566 int run(bool delegate() whileCondition = null) { 4567 if(disposed) 4568 initialize(this.pulseTimeout); 4569 4570 version(X11) { 4571 try { 4572 return loopHelper(whileCondition); 4573 } catch(XDisconnectException e) { 4574 if(e.userRequested) { 4575 foreach(item; CapableOfHandlingNativeEvent.nativeHandleMapping) 4576 item.discardConnectionState(); 4577 XCloseDisplay(XDisplayConnection.display); 4578 } 4579 4580 XDisplayConnection.display = null; 4581 4582 this.dispose(); 4583 4584 throw e; 4585 } 4586 } else { 4587 return loopHelper(whileCondition); 4588 } 4589 } 4590 } 4591 4592 4593 /++ 4594 Provides an icon on the system notification area (also known as the system tray). 4595 4596 4597 If a notification area is not available with the NotificationIcon object is created, 4598 it will silently succeed and simply attempt to create one when an area becomes available. 4599 4600 4601 NotificationAreaIcon on Windows assumes you are on Windows Vista or later. 4602 If this is wrong, pass -version=WindowsXP to dmd when compiling and it will 4603 use the older version. 4604 +/ 4605 version(OSXCocoa) {} else // NotYetImplementedException 4606 class NotificationAreaIcon : CapableOfHandlingNativeEvent { 4607 4608 version(X11) { 4609 void recreateAfterDisconnect() { 4610 stateDiscarded = false; 4611 clippixmap = None; 4612 throw new Exception("NOT IMPLEMENTED"); 4613 } 4614 4615 bool stateDiscarded; 4616 void discardConnectionState() { 4617 stateDiscarded = true; 4618 } 4619 } 4620 4621 4622 version(X11) { 4623 Image img; 4624 4625 NativeEventHandler getNativeEventHandler() { 4626 return delegate int(XEvent e) { 4627 switch(e.type) { 4628 case EventType.Expose: 4629 //case EventType.VisibilityNotify: 4630 redraw(); 4631 break; 4632 case EventType.ClientMessage: 4633 version(sddddd) { 4634 writeln("\t", e.xclient.message_type == GetAtom!("_XEMBED")(XDisplayConnection.get)); 4635 writeln("\t", e.xclient.format); 4636 writeln("\t", e.xclient.data.l); 4637 } 4638 break; 4639 case EventType.ButtonPress: 4640 auto event = e.xbutton; 4641 if (onClick !is null || onClickEx !is null) { 4642 MouseButton mb = cast(MouseButton)0; 4643 switch (event.button) { 4644 case 1: mb = MouseButton.left; break; // left 4645 case 2: mb = MouseButton.middle; break; // middle 4646 case 3: mb = MouseButton.right; break; // right 4647 case 4: mb = MouseButton.wheelUp; break; // scroll up 4648 case 5: mb = MouseButton.wheelDown; break; // scroll down 4649 case 6: break; // scroll left... 4650 case 7: break; // scroll right... 4651 case 8: mb = MouseButton.backButton; break; 4652 case 9: mb = MouseButton.forwardButton; break; 4653 default: 4654 } 4655 if (mb) { 4656 try { onClick()(mb); } catch (Exception) {} 4657 if (onClickEx !is null) try { onClickEx(event.x_root, event.y_root, mb, cast(ModifierState)event.state); } catch (Exception) {} 4658 } 4659 } 4660 break; 4661 case EventType.EnterNotify: 4662 if (onEnter !is null) { 4663 onEnter(e.xcrossing.x_root, e.xcrossing.y_root, cast(ModifierState)e.xcrossing.state); 4664 } 4665 break; 4666 case EventType.LeaveNotify: 4667 if (onLeave !is null) try { onLeave(); } catch (Exception) {} 4668 break; 4669 case EventType.DestroyNotify: 4670 active = false; 4671 CapableOfHandlingNativeEvent.nativeHandleMapping.remove(nativeHandle); 4672 break; 4673 case EventType.ConfigureNotify: 4674 auto event = e.xconfigure; 4675 this.width = event.width; 4676 this.height = event.height; 4677 // writeln(width, " x " , height, " @ ", event.x, " ", event.y); 4678 redraw(); 4679 break; 4680 default: return 1; 4681 } 4682 return 1; 4683 }; 4684 } 4685 4686 /* private */ void hideBalloon() { 4687 balloon.close(); 4688 version(with_timer) 4689 timer.destroy(); 4690 balloon = null; 4691 version(with_timer) 4692 timer = null; 4693 } 4694 4695 void redraw() { 4696 if (!active) return; 4697 4698 auto display = XDisplayConnection.get; 4699 auto gc = DefaultGC(display, DefaultScreen(display)); 4700 XClearWindow(display, nativeHandle); 4701 4702 XSetClipMask(display, gc, clippixmap); 4703 4704 XSetForeground(display, gc, 4705 cast(uint) 0 << 16 | 4706 cast(uint) 0 << 8 | 4707 cast(uint) 0); 4708 XFillRectangle(display, nativeHandle, gc, 0, 0, width, height); 4709 4710 if (img is null) { 4711 XSetForeground(display, gc, 4712 cast(uint) 0 << 16 | 4713 cast(uint) 127 << 8 | 4714 cast(uint) 0); 4715 XFillArc(display, nativeHandle, 4716 gc, width / 4, height / 4, width * 2 / 4, height * 2 / 4, 0 * 64, 360 * 64); 4717 } else { 4718 int dx = 0; 4719 int dy = 0; 4720 if(width > img.width) 4721 dx = (width - img.width) / 2; 4722 if(height > img.height) 4723 dy = (height - img.height) / 2; 4724 XSetClipOrigin(display, gc, dx, dy); 4725 4726 if (img.usingXshm) 4727 XShmPutImage(display, cast(Drawable)nativeHandle, gc, img.handle, 0, 0, dx, dy, img.width, img.height, false); 4728 else 4729 XPutImage(display, cast(Drawable)nativeHandle, gc, img.handle, 0, 0, dx, dy, img.width, img.height); 4730 } 4731 XSetClipMask(display, gc, None); 4732 flushGui(); 4733 } 4734 4735 static Window getTrayOwner() { 4736 auto display = XDisplayConnection.get; 4737 auto i = cast(int) DefaultScreen(display); 4738 if(i < 10 && i >= 0) { 4739 static Atom atom; 4740 if(atom == None) 4741 atom = XInternAtom(display, cast(char*) ("_NET_SYSTEM_TRAY_S"~(cast(char) (i + '0')) ~ '\0').ptr, false); 4742 return XGetSelectionOwner(display, atom); 4743 } 4744 return None; 4745 } 4746 4747 static void sendTrayMessage(arch_long message, arch_long d1, arch_long d2, arch_long d3) { 4748 auto to = getTrayOwner(); 4749 auto display = XDisplayConnection.get; 4750 XEvent ev; 4751 ev.xclient.type = EventType.ClientMessage; 4752 ev.xclient.window = to; 4753 ev.xclient.message_type = GetAtom!("_NET_SYSTEM_TRAY_OPCODE", true)(display); 4754 ev.xclient.format = 32; 4755 ev.xclient.data.l[0] = CurrentTime; 4756 ev.xclient.data.l[1] = message; 4757 ev.xclient.data.l[2] = d1; 4758 ev.xclient.data.l[3] = d2; 4759 ev.xclient.data.l[4] = d3; 4760 4761 XSendEvent(XDisplayConnection.get, to, false, EventMask.NoEventMask, &ev); 4762 } 4763 4764 private static NotificationAreaIcon[] activeIcons; 4765 4766 // FIXME: possible leak with this stuff, should be able to clear it and stuff. 4767 private void newManager() { 4768 close(); 4769 createXWin(); 4770 4771 if(this.clippixmap) 4772 XFreePixmap(XDisplayConnection.get, clippixmap); 4773 if(this.originalMemoryImage) 4774 this.icon = this.originalMemoryImage; 4775 else if(this.img) 4776 this.icon = this.img; 4777 } 4778 4779 private void createXWin () { 4780 // create window 4781 auto display = XDisplayConnection.get; 4782 4783 // to check for MANAGER on root window to catch new/changed tray owners 4784 XDisplayConnection.addRootInput(EventMask.StructureNotifyMask); 4785 // so if a thing does appear, we can handle it 4786 foreach(ai; activeIcons) 4787 if(ai is this) 4788 goto alreadythere; 4789 activeIcons ~= this; 4790 alreadythere: 4791 4792 // and check for an existing tray 4793 auto trayOwner = getTrayOwner(); 4794 if(trayOwner == None) 4795 return; 4796 //throw new Exception("No notification area found"); 4797 4798 Visual* v = cast(Visual*) CopyFromParent; 4799 /+ 4800 auto visualProp = getX11PropertyData(trayOwner, GetAtom!("_NET_SYSTEM_TRAY_VISUAL", true)(display)); 4801 if(visualProp !is null) { 4802 c_ulong[] info = cast(c_ulong[]) visualProp; 4803 if(info.length == 1) { 4804 auto vid = info[0]; 4805 int returned; 4806 XVisualInfo t; 4807 t.visualid = vid; 4808 auto got = XGetVisualInfo(display, VisualIDMask, &t, &returned); 4809 if(got !is null) { 4810 if(returned == 1) { 4811 v = got.visual; 4812 writeln("using special visual ", *got); 4813 } 4814 XFree(got); 4815 } 4816 } 4817 } 4818 +/ 4819 4820 auto nativeWindow = XCreateWindow(display, RootWindow(display, DefaultScreen(display)), 0, 0, 16, 16, 0, 24, InputOutput, v, 0, null); 4821 assert(nativeWindow); 4822 4823 XSetWindowBackgroundPixmap(display, nativeWindow, 1 /* ParentRelative */); 4824 4825 nativeHandle = nativeWindow; 4826 4827 ///+ 4828 arch_ulong[2] info; 4829 info[0] = 0; 4830 info[1] = 1; 4831 4832 string title = this.name is null ? "simpledisplay.d program" : this.name; 4833 auto XA_UTF8 = XInternAtom(display, "UTF8_STRING".ptr, false); 4834 auto XA_NETWM_NAME = XInternAtom(display, "_NET_WM_NAME".ptr, false); 4835 XChangeProperty(display, nativeWindow, XA_NETWM_NAME, XA_UTF8, 8, PropModeReplace, title.ptr, cast(uint)title.length); 4836 4837 XChangeProperty( 4838 display, 4839 nativeWindow, 4840 GetAtom!("_XEMBED_INFO", true)(display), 4841 GetAtom!("_XEMBED_INFO", true)(display), 4842 32 /* bits */, 4843 0 /*PropModeReplace*/, 4844 info.ptr, 4845 2); 4846 4847 import core.sys.posix.unistd; 4848 arch_ulong pid = getpid(); 4849 4850 XChangeProperty( 4851 display, 4852 nativeWindow, 4853 GetAtom!("_NET_WM_PID", true)(display), 4854 XA_CARDINAL, 4855 32 /* bits */, 4856 0 /*PropModeReplace*/, 4857 &pid, 4858 1); 4859 4860 updateNetWmIcon(); 4861 4862 if (sdpyWindowClassStr !is null && sdpyWindowClassStr[0]) { 4863 //{ import core.stdc.stdio; printf("winclass: [%s]\n", sdpyWindowClassStr); } 4864 XClassHint klass; 4865 XWMHints wh; 4866 XSizeHints size; 4867 klass.res_name = sdpyWindowClassStr; 4868 klass.res_class = sdpyWindowClassStr; 4869 XSetWMProperties(display, nativeWindow, null, null, null, 0, &size, &wh, &klass); 4870 } 4871 4872 // believe it or not, THIS is what xfce needed for the 9999 issue 4873 XSizeHints sh; 4874 c_long spr; 4875 XGetWMNormalHints(display, nativeWindow, &sh, &spr); 4876 sh.flags |= PMaxSize | PMinSize; 4877 // FIXME maybe nicer resizing 4878 sh.min_width = 16; 4879 sh.min_height = 16; 4880 sh.max_width = 16; 4881 sh.max_height = 16; 4882 XSetWMNormalHints(display, nativeWindow, &sh); 4883 4884 4885 //+/ 4886 4887 4888 XSelectInput(display, nativeWindow, 4889 EventMask.ButtonPressMask | EventMask.ExposureMask | EventMask.StructureNotifyMask | EventMask.VisibilityChangeMask | 4890 EventMask.EnterWindowMask | EventMask.LeaveWindowMask); 4891 4892 sendTrayMessage(SYSTEM_TRAY_REQUEST_DOCK, nativeWindow, 0, 0); 4893 CapableOfHandlingNativeEvent.nativeHandleMapping[nativeWindow] = this; 4894 active = true; 4895 } 4896 4897 void updateNetWmIcon() { 4898 if(img is null) return; 4899 auto display = XDisplayConnection.get; 4900 // FIXME: ensure this is correct 4901 arch_ulong[] buffer; 4902 auto imgMi = img.toTrueColorImage; 4903 buffer ~= imgMi.width; 4904 buffer ~= imgMi.height; 4905 foreach(c; imgMi.imageData.colors) { 4906 arch_ulong b; 4907 b |= c.a << 24; 4908 b |= c.r << 16; 4909 b |= c.g << 8; 4910 b |= c.b; 4911 buffer ~= b; 4912 } 4913 4914 XChangeProperty( 4915 display, 4916 nativeHandle, 4917 GetAtom!"_NET_WM_ICON"(display), 4918 GetAtom!"CARDINAL"(display), 4919 32 /* bits */, 4920 0 /*PropModeReplace*/, 4921 buffer.ptr, 4922 cast(int) buffer.length); 4923 } 4924 4925 4926 4927 private SimpleWindow balloon; 4928 version(with_timer) 4929 private Timer timer; 4930 4931 private Window nativeHandle; 4932 private Pixmap clippixmap = None; 4933 private int width = 16; 4934 private int height = 16; 4935 private bool active = false; 4936 4937 void delegate (int x, int y, MouseButton button, ModifierState mods) onClickEx; /// x and y are globals (relative to root window). X11 only. 4938 void delegate (int x, int y, ModifierState mods) onEnter; /// x and y are global window coordinates. X11 only. 4939 void delegate () onLeave; /// X11 only. 4940 4941 @property bool closed () const pure nothrow @safe @nogc { return !active; } /// 4942 4943 /// X11 only. Get global window coordinates and size. This can be used to show various notifications. 4944 void getWindowRect (out int x, out int y, out int width, out int height) { 4945 if (!active) { width = 1; height = 1; return; } // 1: just in case 4946 Window dummyw; 4947 auto dpy = XDisplayConnection.get; 4948 //XWindowAttributes xwa; 4949 //XGetWindowAttributes(dpy, nativeHandle, &xwa); 4950 //XTranslateCoordinates(dpy, nativeHandle, RootWindow(dpy, DefaultScreen(dpy)), xwa.x, xwa.y, &x, &y, &dummyw); 4951 XTranslateCoordinates(dpy, nativeHandle, RootWindow(dpy, DefaultScreen(dpy)), x, y, &x, &y, &dummyw); 4952 width = this.width; 4953 height = this.height; 4954 } 4955 } 4956 4957 /+ 4958 What I actually want from this: 4959 4960 * set / change: icon, tooltip 4961 * handle: mouse click, right click 4962 * show: notification bubble. 4963 +/ 4964 4965 version(Windows) { 4966 WindowsIcon win32Icon; 4967 HWND hwnd; 4968 4969 NOTIFYICONDATAW data; 4970 4971 NativeEventHandler getNativeEventHandler() { 4972 return delegate int(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam, out int mustReturn) { 4973 if(msg == WM_USER) { 4974 auto event = LOWORD(lParam); 4975 auto iconId = HIWORD(lParam); 4976 //auto x = GET_X_LPARAM(wParam); 4977 //auto y = GET_Y_LPARAM(wParam); 4978 switch(event) { 4979 case WM_LBUTTONDOWN: 4980 onClick()(MouseButton.left); 4981 break; 4982 case WM_RBUTTONDOWN: 4983 onClick()(MouseButton.right); 4984 break; 4985 case WM_MBUTTONDOWN: 4986 onClick()(MouseButton.middle); 4987 break; 4988 case WM_MOUSEMOVE: 4989 // sent, we could use it. 4990 break; 4991 case WM_MOUSEWHEEL: 4992 // NOT SENT 4993 break; 4994 //case NIN_KEYSELECT: 4995 //case NIN_SELECT: 4996 //break; 4997 default: {} 4998 } 4999 } 5000 return 0; 5001 }; 5002 } 5003 5004 enum NIF_SHOWTIP = 0x00000080; 5005 5006 private static struct NOTIFYICONDATAW { 5007 DWORD cbSize; 5008 HWND hWnd; 5009 UINT uID; 5010 UINT uFlags; 5011 UINT uCallbackMessage; 5012 HICON hIcon; 5013 WCHAR[128] szTip; 5014 DWORD dwState; 5015 DWORD dwStateMask; 5016 WCHAR[256] szInfo; 5017 union { 5018 UINT uTimeout; 5019 UINT uVersion; 5020 } 5021 WCHAR[64] szInfoTitle; 5022 DWORD dwInfoFlags; 5023 GUID guidItem; 5024 HICON hBalloonIcon; 5025 } 5026 5027 } 5028 5029 /++ 5030 Note that on Windows, only left, right, and middle buttons are sent. 5031 Mouse wheel buttons are NOT set, so don't rely on those events if your 5032 program is meant to be used on Windows too. 5033 +/ 5034 this(string name, MemoryImage icon, void delegate(MouseButton button) onClick) { 5035 // The canonical constructor for Windows needs the MemoryImage, so it is here, 5036 // but on X, we need an Image, so its canonical ctor is there. They should 5037 // forward to each other though. 5038 version(X11) { 5039 this.name = name; 5040 this.onClick = onClick; 5041 createXWin(); 5042 this.icon = icon; 5043 } else version(Windows) { 5044 this.onClick = onClick; 5045 this.win32Icon = new WindowsIcon(icon); 5046 5047 HINSTANCE hInstance = cast(HINSTANCE) GetModuleHandle(null); 5048 5049 static bool registered = false; 5050 if(!registered) { 5051 WNDCLASSEX wc; 5052 wc.cbSize = wc.sizeof; 5053 wc.hInstance = hInstance; 5054 wc.lpfnWndProc = &WndProc; 5055 wc.lpszClassName = "arsd_simpledisplay_notification_icon"w.ptr; 5056 if(!RegisterClassExW(&wc)) 5057 throw new WindowsApiException("RegisterClass", GetLastError()); 5058 registered = true; 5059 } 5060 5061 this.hwnd = CreateWindowW("arsd_simpledisplay_notification_icon"w.ptr, "test"w.ptr /* name */, 0 /* dwStyle */, 0, 0, 0, 0, HWND_MESSAGE, null, hInstance, null); 5062 if(hwnd is null) 5063 throw new WindowsApiException("CreateWindow", GetLastError()); 5064 5065 data.cbSize = data.sizeof; 5066 data.hWnd = hwnd; 5067 data.uID = cast(uint) cast(void*) this; 5068 data.uFlags = NIF_MESSAGE | NIF_ICON | NIF_TIP | NIF_STATE | NIF_SHOWTIP /* use default tooltip, for now. */; 5069 // NIF_INFO means show balloon 5070 data.uCallbackMessage = WM_USER; 5071 data.hIcon = this.win32Icon.hIcon; 5072 data.szTip = ""; // FIXME 5073 data.dwState = 0; // NIS_HIDDEN; // windows vista 5074 data.dwStateMask = NIS_HIDDEN; // windows vista 5075 5076 data.uVersion = 4; // NOTIFYICON_VERSION_4; // Windows Vista and up 5077 5078 5079 Shell_NotifyIcon(NIM_ADD, cast(NOTIFYICONDATA*) &data); 5080 5081 CapableOfHandlingNativeEvent.nativeHandleMapping[this.hwnd] = this; 5082 } else version(OSXCocoa) { 5083 throw new NotYetImplementedException(); 5084 } else static assert(0); 5085 } 5086 5087 /// ditto 5088 this(string name, Image icon, void delegate(MouseButton button) onClick) { 5089 version(X11) { 5090 this.onClick = onClick; 5091 this.name = name; 5092 createXWin(); 5093 this.icon = icon; 5094 } else version(Windows) { 5095 this(name, icon is null ? null : icon.toTrueColorImage(), onClick); 5096 } else version(OSXCocoa) { 5097 throw new NotYetImplementedException(); 5098 } else static assert(0); 5099 } 5100 5101 version(X11) { 5102 /++ 5103 X-specific extension (for now at least) 5104 +/ 5105 this(string name, MemoryImage icon, void delegate(int x, int y, MouseButton button, ModifierState mods) onClickEx) { 5106 this.onClickEx = onClickEx; 5107 createXWin(); 5108 if (icon !is null) this.icon = icon; 5109 } 5110 5111 /// ditto 5112 this(string name, Image icon, void delegate(int x, int y, MouseButton button, ModifierState mods) onClickEx) { 5113 this.onClickEx = onClickEx; 5114 createXWin(); 5115 this.icon = icon; 5116 } 5117 } 5118 5119 private void delegate (MouseButton button) onClick_; 5120 5121 /// 5122 @property final void delegate(MouseButton) onClick() { 5123 if(onClick_ is null) 5124 onClick_ = delegate void(MouseButton) {}; 5125 return onClick_; 5126 } 5127 5128 /// ditto 5129 @property final void onClick(void delegate(MouseButton) handler) { 5130 // I made this a property setter so we can wrap smaller arg 5131 // delegates and just forward all to onClickEx or something. 5132 onClick_ = handler; 5133 } 5134 5135 5136 string name_; 5137 @property void name(string n) { 5138 name_ = n; 5139 } 5140 5141 @property string name() { 5142 return name_; 5143 } 5144 5145 private MemoryImage originalMemoryImage; 5146 5147 /// 5148 @property void icon(MemoryImage i) { 5149 version(X11) { 5150 this.originalMemoryImage = i; 5151 if (!active) return; 5152 if (i !is null) { 5153 this.img = Image.fromMemoryImage(i); 5154 this.clippixmap = transparencyMaskFromMemoryImage(i, nativeHandle); 5155 // writeln("using pixmap ", clippixmap); 5156 updateNetWmIcon(); 5157 redraw(); 5158 } else { 5159 if (this.img !is null) { 5160 this.img = null; 5161 redraw(); 5162 } 5163 } 5164 } else version(Windows) { 5165 this.win32Icon = new WindowsIcon(i); 5166 5167 data.uFlags = NIF_ICON; 5168 data.hIcon = this.win32Icon.hIcon; 5169 5170 Shell_NotifyIcon(NIM_MODIFY, cast(NOTIFYICONDATA*) &data); 5171 } else version(OSXCocoa) { 5172 throw new NotYetImplementedException(); 5173 } else static assert(0); 5174 } 5175 5176 /// ditto 5177 @property void icon (Image i) { 5178 version(X11) { 5179 if (!active) return; 5180 if (i !is img) { 5181 originalMemoryImage = null; 5182 img = i; 5183 redraw(); 5184 } 5185 } else version(Windows) { 5186 this.icon(i is null ? null : i.toTrueColorImage()); 5187 } else version(OSXCocoa) { 5188 throw new NotYetImplementedException(); 5189 } else static assert(0); 5190 } 5191 5192 /++ 5193 Shows a balloon notification. You can only show one balloon at a time, if you call 5194 it twice while one is already up, the first balloon will be replaced. 5195 5196 5197 The user is free to block notifications and they will automatically disappear after 5198 a timeout period. 5199 5200 Params: 5201 title = Title of the notification. Must be 40 chars or less or the OS may truncate it. 5202 message = The message to pop up. Must be 220 chars or less or the OS may truncate it. 5203 icon = the icon to display with the notification. If null, it uses your existing icon. 5204 onclick = delegate called if the user clicks the balloon. (not yet implemented) 5205 timeout = your suggested timeout period. The operating system is free to ignore your suggestion. 5206 +/ 5207 void showBalloon(string title, string message, MemoryImage icon = null, void delegate() onclick = null, int timeout = 2_500) { 5208 bool useCustom = true; 5209 version(libnotify) { 5210 if(onclick is null) // libnotify impl doesn't support callbacks yet because it doesn't do a dbus message loop 5211 try { 5212 if(!active) return; 5213 5214 if(libnotify is null) { 5215 libnotify = new C_DynamicLibrary("libnotify.so"); 5216 libnotify.call!("notify_init", int, const char*)()((ApplicationName ~ "\0").ptr); 5217 } 5218 5219 auto n = libnotify.call!("notify_notification_new", void*, const char*, const char*, const char*)()((title~"\0").ptr, (message~"\0").ptr, null /* icon */); 5220 5221 libnotify.call!("notify_notification_set_timeout", void, void*, int)()(n, timeout); 5222 5223 if(onclick) { 5224 libnotify_action_delegates[libnotify_action_delegates_count] = onclick; 5225 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); 5226 libnotify_action_delegates_count++; 5227 } 5228 5229 // FIXME icon 5230 5231 // set hint image-data 5232 // set default action for onclick 5233 5234 void* error; 5235 libnotify.call!("notify_notification_show", bool, void*, void**)()(n, &error); 5236 5237 useCustom = false; 5238 } catch(Exception e) { 5239 5240 } 5241 } 5242 5243 version(X11) { 5244 if(useCustom) { 5245 if(!active) return; 5246 if(balloon) { 5247 hideBalloon(); 5248 } 5249 // I know there are two specs for this, but one is never 5250 // implemented by any window manager I have ever seen, and 5251 // the other is a bloated mess and too complicated for simpledisplay... 5252 // so doing my own little window instead. 5253 balloon = new SimpleWindow(380, 120, null, OpenGlOptions.no, Resizability.fixedSize, WindowTypes.notification, WindowFlags.dontAutoShow/*, window*/); 5254 5255 int x, y, width, height; 5256 getWindowRect(x, y, width, height); 5257 5258 int bx = x - balloon.width; 5259 int by = y - balloon.height; 5260 if(bx < 0) 5261 bx = x + width + balloon.width; 5262 if(by < 0) 5263 by = y + height; 5264 5265 // just in case, make sure it is actually on scren 5266 if(bx < 0) 5267 bx = 0; 5268 if(by < 0) 5269 by = 0; 5270 5271 balloon.move(bx, by); 5272 auto painter = balloon.draw(); 5273 painter.fillColor = Color(220, 220, 220); 5274 painter.outlineColor = Color.black; 5275 painter.drawRectangle(Point(0, 0), balloon.width, balloon.height); 5276 auto iconWidth = icon is null ? 0 : icon.width; 5277 if(icon) 5278 painter.drawImage(Point(4, 4), Image.fromMemoryImage(icon)); 5279 iconWidth += 6; // margin around the icon 5280 5281 // draw a close button 5282 painter.outlineColor = Color(44, 44, 44); 5283 painter.fillColor = Color(255, 255, 255); 5284 painter.drawRectangle(Point(balloon.width - 15, 3), 13, 13); 5285 painter.pen = Pen(Color.black, 3); 5286 painter.drawLine(Point(balloon.width - 14, 4), Point(balloon.width - 4, 14)); 5287 painter.drawLine(Point(balloon.width - 4, 4), Point(balloon.width - 14, 13)); 5288 painter.pen = Pen(Color.black, 1); 5289 painter.fillColor = Color(220, 220, 220); 5290 5291 // Draw the title and message 5292 painter.drawText(Point(4 + iconWidth, 4), title); 5293 painter.drawLine( 5294 Point(4 + iconWidth, 4 + painter.fontHeight + 1), 5295 Point(balloon.width - 4, 4 + painter.fontHeight + 1), 5296 ); 5297 painter.drawText(Point(4 + iconWidth, 4 + painter.fontHeight + 4), message); 5298 5299 balloon.setEventHandlers( 5300 (MouseEvent ev) { 5301 if(ev.type == MouseEventType.buttonPressed) { 5302 if(ev.x > balloon.width - 16 && ev.y < 16) 5303 hideBalloon(); 5304 else if(onclick) 5305 onclick(); 5306 } 5307 } 5308 ); 5309 balloon.show(); 5310 5311 version(with_timer) 5312 timer = new Timer(timeout, &hideBalloon); 5313 else {} // FIXME 5314 } 5315 } else version(Windows) { 5316 enum NIF_INFO = 0x00000010; 5317 5318 data.uFlags = NIF_INFO; 5319 5320 // FIXME: go back to the last valid unicode code point 5321 if(title.length > 40) 5322 title = title[0 .. 40]; 5323 if(message.length > 220) 5324 message = message[0 .. 220]; 5325 5326 enum NIIF_RESPECT_QUIET_TIME = 0x00000080; 5327 enum NIIF_LARGE_ICON = 0x00000020; 5328 enum NIIF_NOSOUND = 0x00000010; 5329 enum NIIF_USER = 0x00000004; 5330 enum NIIF_ERROR = 0x00000003; 5331 enum NIIF_WARNING = 0x00000002; 5332 enum NIIF_INFO = 0x00000001; 5333 enum NIIF_NONE = 0; 5334 5335 WCharzBuffer t = WCharzBuffer(title); 5336 WCharzBuffer m = WCharzBuffer(message); 5337 5338 t.copyInto(data.szInfoTitle); 5339 m.copyInto(data.szInfo); 5340 data.dwInfoFlags = NIIF_RESPECT_QUIET_TIME; 5341 5342 if(icon !is null) { 5343 auto i = new WindowsIcon(icon); 5344 data.hBalloonIcon = i.hIcon; 5345 data.dwInfoFlags |= NIIF_USER; 5346 } 5347 5348 Shell_NotifyIcon(NIM_MODIFY, cast(NOTIFYICONDATA*) &data); 5349 } else version(OSXCocoa) { 5350 throw new NotYetImplementedException(); 5351 } else static assert(0); 5352 } 5353 5354 /// 5355 //version(Windows) 5356 void show() { 5357 version(X11) { 5358 if(!hidden) 5359 return; 5360 sendTrayMessage(SYSTEM_TRAY_REQUEST_DOCK, nativeHandle, 0, 0); 5361 hidden = false; 5362 } else version(Windows) { 5363 data.uFlags = NIF_STATE; 5364 data.dwState = 0; // NIS_HIDDEN; // windows vista 5365 data.dwStateMask = NIS_HIDDEN; // windows vista 5366 Shell_NotifyIcon(NIM_MODIFY, cast(NOTIFYICONDATA*) &data); 5367 } else version(OSXCocoa) { 5368 throw new NotYetImplementedException(); 5369 } else static assert(0); 5370 } 5371 5372 version(X11) 5373 bool hidden = false; 5374 5375 /// 5376 //version(Windows) 5377 void hide() { 5378 version(X11) { 5379 if(hidden) 5380 return; 5381 hidden = true; 5382 XUnmapWindow(XDisplayConnection.get, nativeHandle); 5383 } else version(Windows) { 5384 data.uFlags = NIF_STATE; 5385 data.dwState = NIS_HIDDEN; // windows vista 5386 data.dwStateMask = NIS_HIDDEN; // windows vista 5387 Shell_NotifyIcon(NIM_MODIFY, cast(NOTIFYICONDATA*) &data); 5388 } else version(OSXCocoa) { 5389 throw new NotYetImplementedException(); 5390 } else static assert(0); 5391 } 5392 5393 /// 5394 void close () { 5395 version(X11) { 5396 if (active) { 5397 active = false; // event handler will set this too, but meh 5398 XUnmapWindow(XDisplayConnection.get, nativeHandle); // 'cause why not; let's be polite 5399 XDestroyWindow(XDisplayConnection.get, nativeHandle); 5400 flushGui(); 5401 } 5402 } else version(Windows) { 5403 Shell_NotifyIcon(NIM_DELETE, cast(NOTIFYICONDATA*) &data); 5404 } else version(OSXCocoa) { 5405 throw new NotYetImplementedException(); 5406 } else static assert(0); 5407 } 5408 5409 ~this() { 5410 if(!thisIsGuiThread) return; // FIXME: leaks if multithreaded gc 5411 version(X11) 5412 if(clippixmap != None) 5413 XFreePixmap(XDisplayConnection.get, clippixmap); 5414 close(); 5415 } 5416 } 5417 5418 version(X11) 5419 /// Call `XFreePixmap` on the return value. 5420 Pixmap transparencyMaskFromMemoryImage(MemoryImage i, Window window) { 5421 char[] data = new char[](i.width * i.height / 8 + 2); 5422 data[] = 0; 5423 5424 int bitOffset = 0; 5425 foreach(c; i.getAsTrueColorImage().imageData.colors) { // FIXME inefficient unnecessary conversion in palette cases 5426 ubyte v = c.a > 128 ? 1 : 0; 5427 data[bitOffset / 8] |= v << (bitOffset%8); 5428 bitOffset++; 5429 } 5430 auto handle = XCreateBitmapFromData(XDisplayConnection.get, cast(Drawable) window, data.ptr, i.width, i.height); 5431 return handle; 5432 } 5433 5434 5435 // basic functions to make timers 5436 /** 5437 A timer that will trigger your function on a given interval. 5438 5439 5440 You create a timer with an interval and a callback. It will continue 5441 to fire on the interval until it is destroyed. 5442 5443 There are currently no one-off timers (instead, just create one and 5444 destroy it when it is triggered) nor are there pause/resume functions - 5445 the timer must again be destroyed and recreated if you want to pause it. 5446 5447 --- 5448 auto timer = new Timer(50, { it happened!; }); 5449 timer.destroy(); 5450 --- 5451 5452 Timers can only be expected to fire when the event loop is running and only 5453 once per iteration through the event loop. 5454 5455 History: 5456 Prior to December 9, 2020, a timer pulse set too high with a handler too 5457 slow could lock up the event loop. It now guarantees other things will 5458 get a chance to run between timer calls, even if that means not keeping up 5459 with the requested interval. 5460 */ 5461 version(with_timer) { 5462 class Timer { 5463 // FIXME: needs pause and unpause 5464 // FIXME: I might add overloads for ones that take a count of 5465 // how many elapsed since last time (on Windows, it will divide 5466 // the ticks thing given, on Linux it is just available) and 5467 // maybe one that takes an instance of the Timer itself too 5468 /// Create a timer with a callback when it triggers. 5469 this(int intervalInMilliseconds, void delegate() onPulse) { 5470 assert(onPulse !is null); 5471 5472 this.intervalInMilliseconds = intervalInMilliseconds; 5473 this.onPulse = onPulse; 5474 5475 version(Windows) { 5476 /* 5477 handle = SetTimer(null, handle, intervalInMilliseconds, &timerCallback); 5478 if(handle == 0) 5479 throw new WindowsApiException("SetTimer", GetLastError()); 5480 */ 5481 5482 // thanks to Archival 998 for the WaitableTimer blocks 5483 handle = CreateWaitableTimer(null, false, null); 5484 long initialTime = -intervalInMilliseconds; 5485 if(handle is null || !SetWaitableTimer(handle, cast(LARGE_INTEGER*)&initialTime, intervalInMilliseconds, &timerCallback, handle, false)) 5486 throw new WindowsApiException("SetWaitableTimer", GetLastError()); 5487 5488 mapping[handle] = this; 5489 5490 } else version(linux) { 5491 static import ep = core.sys.linux.epoll; 5492 5493 import core.sys.linux.timerfd; 5494 5495 fd = timerfd_create(CLOCK_MONOTONIC, 0); 5496 if(fd == -1) 5497 throw new Exception("timer create failed"); 5498 5499 mapping[fd] = this; 5500 5501 itimerspec value; 5502 value.it_value.tv_sec = cast(int) (intervalInMilliseconds / 1000); 5503 value.it_value.tv_nsec = (intervalInMilliseconds % 1000) * 1000_000; 5504 5505 value.it_interval.tv_sec = cast(int) (intervalInMilliseconds / 1000); 5506 value.it_interval.tv_nsec = (intervalInMilliseconds % 1000) * 1000_000; 5507 5508 if(timerfd_settime(fd, 0, &value, null) == -1) 5509 throw new Exception("couldn't make pulse timer"); 5510 5511 version(with_eventloop) { 5512 import arsd.eventloop; 5513 addFileEventListeners(fd, &trigger, null, null); 5514 } else { 5515 prepareEventLoop(); 5516 5517 ep.epoll_event ev = void; 5518 ev.events = ep.EPOLLIN; 5519 ev.data.fd = fd; 5520 ep.epoll_ctl(epollFd, ep.EPOLL_CTL_ADD, fd, &ev); 5521 } 5522 } else featureNotImplemented(); 5523 } 5524 5525 private int intervalInMilliseconds; 5526 5527 // just cuz I sometimes call it this. 5528 alias dispose = destroy; 5529 5530 /// Stop and destroy the timer object. 5531 void destroy() { 5532 version(Windows) { 5533 staticDestroy(handle); 5534 handle = null; 5535 } else version(linux) { 5536 staticDestroy(fd); 5537 fd = -1; 5538 } else featureNotImplemented(); 5539 } 5540 5541 version(Windows) 5542 static void staticDestroy(HANDLE handle) { 5543 if(handle) { 5544 // KillTimer(null, handle); 5545 CancelWaitableTimer(cast(void*)handle); 5546 mapping.remove(handle); 5547 CloseHandle(handle); 5548 } 5549 } 5550 else version(linux) 5551 static void staticDestroy(int fd) { 5552 if(fd != -1) { 5553 import unix = core.sys.posix.unistd; 5554 static import ep = core.sys.linux.epoll; 5555 5556 version(with_eventloop) { 5557 import arsd.eventloop; 5558 removeFileEventListeners(fd); 5559 } else { 5560 ep.epoll_event ev = void; 5561 ev.events = ep.EPOLLIN; 5562 ev.data.fd = fd; 5563 5564 ep.epoll_ctl(epollFd, ep.EPOLL_CTL_DEL, fd, &ev); 5565 } 5566 unix.close(fd); 5567 mapping.remove(fd); 5568 } 5569 } 5570 5571 ~this() { 5572 version(Windows) { if(handle) 5573 cleanupQueue.queue!staticDestroy(handle); 5574 } else version(linux) { if(fd != -1) 5575 cleanupQueue.queue!staticDestroy(fd); 5576 } 5577 } 5578 5579 5580 void changeTime(int intervalInMilliseconds) 5581 { 5582 this.intervalInMilliseconds = intervalInMilliseconds; 5583 version(Windows) 5584 { 5585 if(handle) 5586 { 5587 //handle = SetTimer(null, handle, intervalInMilliseconds, &timerCallback); 5588 long initialTime = -intervalInMilliseconds; 5589 if(handle is null || !SetWaitableTimer(handle, cast(LARGE_INTEGER*)&initialTime, intervalInMilliseconds, &timerCallback, handle, false)) 5590 throw new WindowsApiException("couldn't change pulse timer", GetLastError()); 5591 } 5592 } 5593 } 5594 5595 5596 private: 5597 5598 void delegate() onPulse; 5599 5600 int lastEventLoopRoundTriggered; 5601 5602 void trigger() { 5603 version(linux) { 5604 import unix = core.sys.posix.unistd; 5605 long val; 5606 unix.read(fd, &val, val.sizeof); // gotta clear the pipe 5607 } else version(Windows) { 5608 if(this.lastEventLoopRoundTriggered == eventLoopRound) 5609 return; // never try to actually run faster than the event loop 5610 lastEventLoopRoundTriggered = eventLoopRound; 5611 } else featureNotImplemented(); 5612 5613 onPulse(); 5614 } 5615 5616 version(Windows) 5617 void rearm() { 5618 5619 } 5620 5621 version(Windows) 5622 extern(Windows) 5623 //static void timerCallback(HWND, UINT, UINT_PTR timer, DWORD dwTime) nothrow { 5624 static void timerCallback(HANDLE timer, DWORD lowTime, DWORD hiTime) nothrow { 5625 if(Timer* t = timer in mapping) { 5626 try 5627 (*t).trigger(); 5628 catch(Exception e) { sdpy_abort(e); assert(0); } 5629 } 5630 } 5631 5632 version(Windows) { 5633 //UINT_PTR handle; 5634 //static Timer[UINT_PTR] mapping; 5635 HANDLE handle; 5636 __gshared Timer[HANDLE] mapping; 5637 } else version(linux) { 5638 int fd = -1; 5639 __gshared Timer[int] mapping; 5640 } else static assert(0, "timer not supported"); 5641 } 5642 } 5643 5644 version(Windows) 5645 private int eventLoopRound; 5646 5647 version(Windows) 5648 /// 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 5649 class WindowsHandleReader { 5650 /// 5651 this(void delegate() onReady, HANDLE handle) { 5652 this.onReady = onReady; 5653 this.handle = handle; 5654 5655 mapping[handle] = this; 5656 5657 enable(); 5658 } 5659 5660 /// 5661 void enable() { 5662 auto el = EventLoop.get().impl; 5663 el.handles ~= handle; 5664 } 5665 5666 /// 5667 void disable() { 5668 auto el = EventLoop.get().impl; 5669 for(int i = 0; i < el.handles.length; i++) { 5670 if(el.handles[i] is handle) { 5671 el.handles[i] = el.handles[$-1]; 5672 el.handles = el.handles[0 .. $-1]; 5673 return; 5674 } 5675 } 5676 } 5677 5678 void dispose() { 5679 disable(); 5680 if(handle) 5681 mapping.remove(handle); 5682 handle = null; 5683 } 5684 5685 void ready() { 5686 if(onReady) 5687 onReady(); 5688 } 5689 5690 HANDLE handle; 5691 void delegate() onReady; 5692 5693 __gshared WindowsHandleReader[HANDLE] mapping; 5694 } 5695 5696 version(Posix) 5697 /// Lets you add files to the event loop for reading. Use at your own risk. 5698 class PosixFdReader { 5699 /// 5700 this(void delegate() onReady, int fd, bool captureReads = true, bool captureWrites = false) { 5701 this((int, bool, bool) { onReady(); }, fd, captureReads, captureWrites); 5702 } 5703 5704 /// 5705 this(void delegate(int) onReady, int fd, bool captureReads = true, bool captureWrites = false) { 5706 this((int fd, bool, bool) { onReady(fd); }, fd, captureReads, captureWrites); 5707 } 5708 5709 /// 5710 this(void delegate(int fd, bool read, bool write) onReady, int fd, bool captureReads = true, bool captureWrites = false) { 5711 this.onReady = onReady; 5712 this.fd = fd; 5713 this.captureWrites = captureWrites; 5714 this.captureReads = captureReads; 5715 5716 mapping[fd] = this; 5717 5718 version(with_eventloop) { 5719 import arsd.eventloop; 5720 addFileEventListeners(fd, &readyel); 5721 } else { 5722 enable(); 5723 } 5724 } 5725 5726 bool captureReads; 5727 bool captureWrites; 5728 5729 version(with_eventloop) {} else 5730 /// 5731 void enable() { 5732 prepareEventLoop(); 5733 5734 enabled = true; 5735 5736 version(linux) { 5737 static import ep = core.sys.linux.epoll; 5738 ep.epoll_event ev = void; 5739 ev.events = (captureReads ? ep.EPOLLIN : 0) | (captureWrites ? ep.EPOLLOUT : 0); 5740 // writeln("enable ", fd, " ", captureReads, " ", captureWrites); 5741 ev.data.fd = fd; 5742 ep.epoll_ctl(epollFd, ep.EPOLL_CTL_ADD, fd, &ev); 5743 } else { 5744 5745 } 5746 } 5747 5748 version(with_eventloop) {} else 5749 /// 5750 void disable() { 5751 prepareEventLoop(); 5752 5753 enabled = false; 5754 5755 version(linux) { 5756 static import ep = core.sys.linux.epoll; 5757 ep.epoll_event ev = void; 5758 ev.events = (captureReads ? ep.EPOLLIN : 0) | (captureWrites ? ep.EPOLLOUT : 0); 5759 // writeln("disable ", fd, " ", captureReads, " ", captureWrites); 5760 ev.data.fd = fd; 5761 ep.epoll_ctl(epollFd, ep.EPOLL_CTL_DEL, fd, &ev); 5762 } 5763 } 5764 5765 version(with_eventloop) {} else 5766 /// 5767 void dispose() { 5768 if(enabled) 5769 disable(); 5770 if(fd != -1) 5771 mapping.remove(fd); 5772 fd = -1; 5773 } 5774 5775 void delegate(int, bool, bool) onReady; 5776 5777 version(with_eventloop) 5778 void readyel() { 5779 onReady(fd, true, true); 5780 } 5781 5782 void ready(uint flags) { 5783 version(linux) { 5784 static import ep = core.sys.linux.epoll; 5785 onReady(fd, (flags & ep.EPOLLIN) ? true : false, (flags & ep.EPOLLOUT) ? true : false); 5786 } else { 5787 import core.sys.posix.poll; 5788 onReady(fd, (flags & POLLIN) ? true : false, (flags & POLLOUT) ? true : false); 5789 } 5790 } 5791 5792 void hup(uint flags) { 5793 if(onHup) 5794 onHup(); 5795 } 5796 5797 void delegate() onHup; 5798 5799 int fd = -1; 5800 private bool enabled; 5801 __gshared PosixFdReader[int] mapping; 5802 } 5803 5804 // basic functions to access the clipboard 5805 /+ 5806 5807 5808 http://msdn.microsoft.com/en-us/library/windows/desktop/ff729168%28v=vs.85%29.aspx 5809 http://msdn.microsoft.com/en-us/library/windows/desktop/ms649039%28v=vs.85%29.aspx 5810 http://msdn.microsoft.com/en-us/library/windows/desktop/ms649035%28v=vs.85%29.aspx 5811 http://msdn.microsoft.com/en-us/library/windows/desktop/ms649051%28v=vs.85%29.aspx 5812 http://msdn.microsoft.com/en-us/library/windows/desktop/ms649037%28v=vs.85%29.aspx 5813 http://msdn.microsoft.com/en-us/library/windows/desktop/ms649035%28v=vs.85%29.aspx 5814 http://msdn.microsoft.com/en-us/library/windows/desktop/ms649016%28v=vs.85%29.aspx 5815 5816 +/ 5817 5818 /++ 5819 this does a delegate because it is actually an async call on X... 5820 the receiver may never be called if the clipboard is empty or unavailable 5821 gets plain text from the clipboard. 5822 +/ 5823 void getClipboardText(SimpleWindow clipboardOwner, void delegate(in char[]) receiver) { 5824 version(Windows) { 5825 HWND hwndOwner = clipboardOwner ? clipboardOwner.impl.hwnd : null; 5826 if(OpenClipboard(hwndOwner) == 0) 5827 throw new WindowsApiException("OpenClipboard", GetLastError()); 5828 scope(exit) 5829 CloseClipboard(); 5830 // see: https://docs.microsoft.com/en-us/windows/win32/api/winuser/nf-winuser-getpriorityclipboardformat 5831 if(auto dataHandle = GetClipboardData(CF_UNICODETEXT)) { 5832 5833 if(auto data = cast(wchar*) GlobalLock(dataHandle)) { 5834 scope(exit) 5835 GlobalUnlock(dataHandle); 5836 5837 // FIXME: CR/LF conversions 5838 // FIXME: I might not have to copy it now that the receiver is in char[] instead of string 5839 int len = 0; 5840 auto d = data; 5841 while(*d) { 5842 d++; 5843 len++; 5844 } 5845 string s; 5846 s.reserve(len); 5847 foreach(dchar ch; data[0 .. len]) { 5848 s ~= ch; 5849 } 5850 receiver(s); 5851 } 5852 } 5853 } else version(X11) { 5854 getX11Selection!"CLIPBOARD"(clipboardOwner, receiver); 5855 } else version(OSXCocoa) { 5856 throw new NotYetImplementedException(); 5857 } else static assert(0); 5858 } 5859 5860 // FIXME: a clipboard listener might be cool btw 5861 5862 /++ 5863 this does a delegate because it is actually an async call on X... 5864 the receiver may never be called if the clipboard is empty or unavailable 5865 gets image from the clipboard. 5866 5867 templated because it introduces an optional dependency on arsd.bmp 5868 +/ 5869 void getClipboardImage()(SimpleWindow clipboardOwner, void delegate(MemoryImage) receiver) { 5870 version(Windows) { 5871 HWND hwndOwner = clipboardOwner ? clipboardOwner.impl.hwnd : null; 5872 if(OpenClipboard(hwndOwner) == 0) 5873 throw new WindowsApiException("OpenClipboard", GetLastError()); 5874 scope(exit) 5875 CloseClipboard(); 5876 if(auto dataHandle = GetClipboardData(CF_DIBV5)) { 5877 if(auto data = cast(ubyte*) GlobalLock(dataHandle)) { 5878 scope(exit) 5879 GlobalUnlock(dataHandle); 5880 5881 auto len = GlobalSize(dataHandle); 5882 5883 import arsd.bmp; 5884 auto img = readBmp(data[0 .. len], false); 5885 receiver(img); 5886 } 5887 } 5888 } else version(X11) { 5889 getX11Selection!"CLIPBOARD"(clipboardOwner, receiver); 5890 } else version(OSXCocoa) { 5891 throw new NotYetImplementedException(); 5892 } else static assert(0); 5893 } 5894 5895 /// Copies some text to the clipboard. 5896 void setClipboardText(SimpleWindow clipboardOwner, string text) { 5897 assert(clipboardOwner !is null); 5898 version(Windows) { 5899 if(OpenClipboard(clipboardOwner.impl.hwnd) == 0) 5900 throw new WindowsApiException("OpenClipboard", GetLastError()); 5901 scope(exit) 5902 CloseClipboard(); 5903 EmptyClipboard(); 5904 auto sz = sizeOfConvertedWstring(text, WindowsStringConversionFlags.convertNewLines | WindowsStringConversionFlags.zeroTerminate); 5905 auto handle = GlobalAlloc(GMEM_MOVEABLE, sz * 2); // zero terminated wchars 5906 if(handle is null) throw new WindowsApiException("GlobalAlloc", GetLastError()); 5907 if(auto data = cast(wchar*) GlobalLock(handle)) { 5908 auto slice = data[0 .. sz]; 5909 scope(failure) 5910 GlobalUnlock(handle); 5911 5912 auto str = makeWindowsString(text, slice, WindowsStringConversionFlags.convertNewLines | WindowsStringConversionFlags.zeroTerminate); 5913 5914 GlobalUnlock(handle); 5915 SetClipboardData(CF_UNICODETEXT, handle); 5916 } 5917 } else version(X11) { 5918 setX11Selection!"CLIPBOARD"(clipboardOwner, text); 5919 } else version(OSXCocoa) { 5920 throw new NotYetImplementedException(); 5921 } else static assert(0); 5922 } 5923 5924 void setClipboardImage()(SimpleWindow clipboardOwner, MemoryImage img) { 5925 assert(clipboardOwner !is null); 5926 version(Windows) { 5927 if(OpenClipboard(clipboardOwner.impl.hwnd) == 0) 5928 throw new WindowsApiException("OpenClipboard", GetLastError()); 5929 scope(exit) 5930 CloseClipboard(); 5931 EmptyClipboard(); 5932 5933 5934 import arsd.bmp; 5935 ubyte[] mdata; 5936 mdata.reserve(img.width * img.height); 5937 void sink(ubyte b) { 5938 mdata ~= b; 5939 } 5940 writeBmpIndirect(img, &sink, false); 5941 5942 auto handle = GlobalAlloc(GMEM_MOVEABLE, mdata.length); 5943 if(handle is null) throw new WindowsApiException("GlobalAlloc", GetLastError()); 5944 if(auto data = cast(ubyte*) GlobalLock(handle)) { 5945 auto slice = data[0 .. mdata.length]; 5946 scope(failure) 5947 GlobalUnlock(handle); 5948 5949 slice[] = mdata[]; 5950 5951 GlobalUnlock(handle); 5952 SetClipboardData(CF_DIB, handle); 5953 } 5954 } else version(X11) { 5955 static class X11SetSelectionHandler_Image : X11SetSelectionHandler { 5956 mixin X11SetSelectionHandler_Basics; 5957 private const(ubyte)[] mdata; 5958 private const(ubyte)[] mdata_original; 5959 this(MemoryImage img) { 5960 import arsd.bmp; 5961 5962 mdata.reserve(img.width * img.height); 5963 void sink(ubyte b) { 5964 mdata ~= b; 5965 } 5966 writeBmpIndirect(img, &sink, true); 5967 5968 mdata_original = mdata; 5969 } 5970 5971 Atom[] availableFormats() { 5972 auto display = XDisplayConnection.get; 5973 return [ 5974 GetAtom!"image/bmp"(display), 5975 GetAtom!"TARGETS"(display) 5976 ]; 5977 } 5978 5979 ubyte[] getData(Atom format, return scope ubyte[] data) { 5980 if(mdata.length < data.length) { 5981 data[0 .. mdata.length] = mdata[]; 5982 auto ret = data[0 .. mdata.length]; 5983 mdata = mdata[$..$]; 5984 return ret; 5985 } else { 5986 data[] = mdata[0 .. data.length]; 5987 mdata = mdata[data.length .. $]; 5988 return data[]; 5989 } 5990 } 5991 5992 void done() { 5993 mdata = mdata_original; 5994 } 5995 } 5996 5997 setX11Selection!"CLIPBOARD"(clipboardOwner, new X11SetSelectionHandler_Image(img)); 5998 } else version(OSXCocoa) { 5999 throw new NotYetImplementedException(); 6000 } else static assert(0); 6001 } 6002 6003 6004 version(X11) { 6005 // and the PRIMARY on X, be sure to put these in static if(UsingSimpledisplayX11) 6006 6007 private Atom*[] interredAtoms; // for discardAndRecreate 6008 6009 // FIXME: do a GetAtomUpfront too that just queues all at CT and combines it all. 6010 /// Platform-specific for X11. 6011 /// History: On February 21, 2021, I changed the default value of `create` to be true. 6012 @property Atom GetAtom(string name, bool create = true)(Display* display) { 6013 static Atom a; 6014 if(!a) { 6015 a = XInternAtom(display, name, !create); 6016 interredAtoms ~= &a; 6017 } 6018 if(a == None) 6019 throw new Exception("XInternAtom " ~ name ~ " " ~ (create ? "true":"false")); 6020 return a; 6021 } 6022 6023 /// Platform-specific for X11 - gets atom names as a string. 6024 string getAtomName(Atom atom, Display* display) { 6025 auto got = XGetAtomName(display, atom); 6026 scope(exit) XFree(got); 6027 import core.stdc.string; 6028 string s = got[0 .. strlen(got)].idup; 6029 return s; 6030 } 6031 6032 /// Asserts ownership of PRIMARY and copies the text into a buffer that clients can request later. 6033 void setPrimarySelection(SimpleWindow window, string text) { 6034 setX11Selection!"PRIMARY"(window, text); 6035 } 6036 6037 /// Asserts ownership of SECONDARY and copies the text into a buffer that clients can request later. 6038 void setSecondarySelection(SimpleWindow window, string text) { 6039 setX11Selection!"SECONDARY"(window, text); 6040 } 6041 6042 interface X11SetSelectionHandler { 6043 // should include TARGETS right now 6044 Atom[] availableFormats(); 6045 // Return the slice of data you filled, empty slice if done. 6046 // this is to support the incremental thing 6047 ubyte[] getData(Atom format, return scope ubyte[] data); 6048 6049 void done(); 6050 6051 void handleRequest(XEvent); 6052 6053 bool matchesIncr(Window, Atom); 6054 void sendMoreIncr(XPropertyEvent*); 6055 } 6056 6057 mixin template X11SetSelectionHandler_Basics() { 6058 Window incrWindow; 6059 Atom incrAtom; 6060 Atom selectionAtom; 6061 Atom formatAtom; 6062 ubyte[] toSend; 6063 bool matchesIncr(Window w, Atom a) { 6064 return incrAtom && incrAtom == a && w == incrWindow; 6065 } 6066 void sendMoreIncr(XPropertyEvent* event) { 6067 auto display = XDisplayConnection.get; 6068 6069 XChangeProperty (display, 6070 incrWindow, 6071 incrAtom, 6072 formatAtom, 6073 8 /* bits */, PropModeReplace, 6074 toSend.ptr, cast(int) toSend.length); 6075 6076 if(toSend.length != 0) { 6077 toSend = this.getData(formatAtom, toSend[]); 6078 } else { 6079 this.done(); 6080 incrWindow = None; 6081 incrAtom = None; 6082 selectionAtom = None; 6083 formatAtom = None; 6084 toSend = null; 6085 } 6086 } 6087 void handleRequest(XEvent ev) { 6088 6089 auto display = XDisplayConnection.get; 6090 6091 XSelectionRequestEvent* event = &ev.xselectionrequest; 6092 XSelectionEvent selectionEvent; 6093 selectionEvent.type = EventType.SelectionNotify; 6094 selectionEvent.display = event.display; 6095 selectionEvent.requestor = event.requestor; 6096 selectionEvent.selection = event.selection; 6097 selectionEvent.time = event.time; 6098 selectionEvent.target = event.target; 6099 6100 bool supportedType() { 6101 foreach(t; this.availableFormats()) 6102 if(t == event.target) 6103 return true; 6104 return false; 6105 } 6106 6107 if(event.property == None) { 6108 selectionEvent.property = event.target; 6109 6110 XSendEvent(display, selectionEvent.requestor, false, 0, cast(XEvent*) &selectionEvent); 6111 XFlush(display); 6112 } if(event.target == GetAtom!"TARGETS"(display)) { 6113 /* respond with the supported types */ 6114 auto tlist = this.availableFormats(); 6115 XChangeProperty(display, event.requestor, event.property, XA_ATOM, 32, PropModeReplace, cast(void*)tlist.ptr, cast(int) tlist.length); 6116 selectionEvent.property = event.property; 6117 6118 XSendEvent(display, selectionEvent.requestor, false, 0, cast(XEvent*) &selectionEvent); 6119 XFlush(display); 6120 } else if(supportedType()) { 6121 auto buffer = new ubyte[](1024 * 64); 6122 auto toSend = this.getData(event.target, buffer[]); 6123 6124 if(toSend.length < 32 * 1024) { 6125 // small enough to send directly... 6126 selectionEvent.property = event.property; 6127 XChangeProperty (display, 6128 selectionEvent.requestor, 6129 selectionEvent.property, 6130 event.target, 6131 8 /* bits */, 0 /* PropModeReplace */, 6132 toSend.ptr, cast(int) toSend.length); 6133 6134 XSendEvent(display, selectionEvent.requestor, false, 0, cast(XEvent*) &selectionEvent); 6135 XFlush(display); 6136 } else { 6137 // large, let's send incrementally 6138 arch_ulong l = toSend.length; 6139 6140 // if I wanted other events from this window don't want to clear that out.... 6141 XWindowAttributes xwa; 6142 XGetWindowAttributes(display, selectionEvent.requestor, &xwa); 6143 6144 XSelectInput(display, selectionEvent.requestor, cast(EventMask) (xwa.your_event_mask | EventMask.PropertyChangeMask)); 6145 6146 incrWindow = event.requestor; 6147 incrAtom = event.property; 6148 formatAtom = event.target; 6149 selectionAtom = event.selection; 6150 this.toSend = toSend; 6151 6152 selectionEvent.property = event.property; 6153 XChangeProperty (display, 6154 selectionEvent.requestor, 6155 selectionEvent.property, 6156 GetAtom!"INCR"(display), 6157 32 /* bits */, PropModeReplace, 6158 &l, 1); 6159 6160 XSendEvent(display, selectionEvent.requestor, false, 0, cast(XEvent*) &selectionEvent); 6161 XFlush(display); 6162 } 6163 //if(after) 6164 //after(); 6165 } else { 6166 debug(sdpy_clip) { 6167 writeln("Unsupported data ", getAtomName(event.target, display)); 6168 } 6169 selectionEvent.property = None; // I don't know how to handle this type... 6170 XSendEvent(display, selectionEvent.requestor, false, EventMask.NoEventMask, cast(XEvent*) &selectionEvent); 6171 XFlush(display); 6172 } 6173 } 6174 } 6175 6176 class X11SetSelectionHandler_Text : X11SetSelectionHandler { 6177 mixin X11SetSelectionHandler_Basics; 6178 private const(ubyte)[] text; 6179 private const(ubyte)[] text_original; 6180 this(string text) { 6181 this.text = cast(const ubyte[]) text; 6182 this.text_original = this.text; 6183 } 6184 Atom[] availableFormats() { 6185 auto display = XDisplayConnection.get; 6186 return [ 6187 GetAtom!"UTF8_STRING"(display), 6188 GetAtom!"text/plain"(display), 6189 XA_STRING, 6190 GetAtom!"TARGETS"(display) 6191 ]; 6192 } 6193 6194 ubyte[] getData(Atom format, return scope ubyte[] data) { 6195 if(text.length < data.length) { 6196 data[0 .. text.length] = text[]; 6197 return data[0 .. text.length]; 6198 } else { 6199 data[] = text[0 .. data.length]; 6200 text = text[data.length .. $]; 6201 return data[]; 6202 } 6203 } 6204 6205 void done() { 6206 text = text_original; 6207 } 6208 } 6209 6210 /// 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?!) 6211 void setX11Selection(string atomName)(SimpleWindow window, string text, void delegate() after = null) { 6212 setX11Selection!atomName(window, new X11SetSelectionHandler_Text(text), after); 6213 } 6214 6215 void setX11Selection(string atomName)(SimpleWindow window, X11SetSelectionHandler data, void delegate() after = null) { 6216 assert(window !is null); 6217 6218 auto display = XDisplayConnection.get(); 6219 static if (atomName == "PRIMARY") Atom a = XA_PRIMARY; 6220 else static if (atomName == "SECONDARY") Atom a = XA_SECONDARY; 6221 else Atom a = GetAtom!atomName(display); 6222 6223 XSetSelectionOwner(display, a, window.impl.window, 0 /* CurrentTime */); 6224 6225 window.impl.setSelectionHandlers[a] = data; 6226 } 6227 6228 /// 6229 void getPrimarySelection(SimpleWindow window, void delegate(in char[]) handler) { 6230 getX11Selection!"PRIMARY"(window, handler); 6231 } 6232 6233 // added July 28, 2020 6234 // undocumented as experimental tho 6235 interface X11GetSelectionHandler { 6236 void handleData(Atom target, in ubyte[] data); 6237 Atom findBestFormat(Atom[] answer); 6238 6239 void prepareIncremental(Window, Atom); 6240 bool matchesIncr(Window, Atom); 6241 void handleIncrData(Atom, in ubyte[] data); 6242 } 6243 6244 mixin template X11GetSelectionHandler_Basics() { 6245 Window incrWindow; 6246 Atom incrAtom; 6247 6248 void prepareIncremental(Window w, Atom a) { 6249 incrWindow = w; 6250 incrAtom = a; 6251 } 6252 bool matchesIncr(Window w, Atom a) { 6253 return incrWindow == w && incrAtom == a; 6254 } 6255 6256 Atom incrFormatAtom; 6257 ubyte[] incrData; 6258 void handleIncrData(Atom format, in ubyte[] data) { 6259 incrFormatAtom = format; 6260 6261 if(data.length) 6262 incrData ~= data; 6263 else 6264 handleData(incrFormatAtom, incrData); 6265 6266 } 6267 } 6268 6269 /// 6270 void getX11Selection(string atomName)(SimpleWindow window, void delegate(in char[]) handler, Time timestamp = 0 /* CurrentTime */) { 6271 assert(window !is null); 6272 6273 auto display = XDisplayConnection.get(); 6274 auto atom = GetAtom!atomName(display); 6275 6276 static class X11GetSelectionHandler_Text : X11GetSelectionHandler { 6277 this(void delegate(in char[]) handler) { 6278 this.handler = handler; 6279 } 6280 6281 mixin X11GetSelectionHandler_Basics; 6282 6283 void delegate(in char[]) handler; 6284 6285 void handleData(Atom target, in ubyte[] data) { 6286 if(target == GetAtom!"UTF8_STRING"(XDisplayConnection.get) || target == XA_STRING || target == GetAtom!"text/plain"(XDisplayConnection.get)) 6287 handler(cast(const char[]) data); 6288 } 6289 6290 Atom findBestFormat(Atom[] answer) { 6291 Atom best = None; 6292 foreach(option; answer) { 6293 if(option == GetAtom!"UTF8_STRING"(XDisplayConnection.get)) { 6294 best = option; 6295 break; 6296 } else if(option == XA_STRING) { 6297 best = option; 6298 } else if(option == GetAtom!"text/plain"(XDisplayConnection.get)) { 6299 best = option; 6300 } 6301 } 6302 return best; 6303 } 6304 } 6305 6306 window.impl.getSelectionHandlers[atom] = new X11GetSelectionHandler_Text(handler); 6307 6308 auto target = GetAtom!"TARGETS"(display); 6309 6310 // SDD_DATA is "simpledisplay.d data" 6311 XConvertSelection(display, atom, target, GetAtom!("SDD_DATA", true)(display), window.impl.window, timestamp); 6312 } 6313 6314 /// Gets the image on the clipboard, if there is one. Added July 2020. 6315 void getX11Selection(string atomName)(SimpleWindow window, void delegate(MemoryImage) handler) { 6316 assert(window !is null); 6317 6318 auto display = XDisplayConnection.get(); 6319 auto atom = GetAtom!atomName(display); 6320 6321 static class X11GetSelectionHandler_Image : X11GetSelectionHandler { 6322 this(void delegate(MemoryImage) handler) { 6323 this.handler = handler; 6324 } 6325 6326 mixin X11GetSelectionHandler_Basics; 6327 6328 void delegate(MemoryImage) handler; 6329 6330 void handleData(Atom target, in ubyte[] data) { 6331 if(target == GetAtom!"image/bmp"(XDisplayConnection.get)) { 6332 import arsd.bmp; 6333 handler(readBmp(data)); 6334 } 6335 } 6336 6337 Atom findBestFormat(Atom[] answer) { 6338 Atom best = None; 6339 foreach(option; answer) { 6340 if(option == GetAtom!"image/bmp"(XDisplayConnection.get)) { 6341 best = option; 6342 } 6343 } 6344 return best; 6345 } 6346 6347 } 6348 6349 6350 window.impl.getSelectionHandlers[atom] = new X11GetSelectionHandler_Image(handler); 6351 6352 auto target = GetAtom!"TARGETS"(display); 6353 6354 // SDD_DATA is "simpledisplay.d data" 6355 XConvertSelection(display, atom, target, GetAtom!("SDD_DATA", true)(display), window.impl.window, 0 /*CurrentTime*/); 6356 } 6357 6358 6359 /// 6360 void[] getX11PropertyData(Window window, Atom property, Atom type = AnyPropertyType) { 6361 Atom actualType; 6362 int actualFormat; 6363 arch_ulong actualItems; 6364 arch_ulong bytesRemaining; 6365 void* data; 6366 6367 auto display = XDisplayConnection.get(); 6368 if(XGetWindowProperty(display, window, property, 0, 0x7fffffff, false, type, &actualType, &actualFormat, &actualItems, &bytesRemaining, &data) == Success) { 6369 if(actualFormat == 0) 6370 return null; 6371 else { 6372 int byteLength; 6373 if(actualFormat == 32) { 6374 // 32 means it is a C long... which is variable length 6375 actualFormat = cast(int) arch_long.sizeof * 8; 6376 } 6377 6378 // then it is just a bit count 6379 byteLength = cast(int) (actualItems * actualFormat / 8); 6380 6381 auto d = new ubyte[](byteLength); 6382 d[] = cast(ubyte[]) data[0 .. byteLength]; 6383 XFree(data); 6384 return d; 6385 } 6386 } 6387 return null; 6388 } 6389 6390 /* defined in the systray spec */ 6391 enum SYSTEM_TRAY_REQUEST_DOCK = 0; 6392 enum SYSTEM_TRAY_BEGIN_MESSAGE = 1; 6393 enum SYSTEM_TRAY_CANCEL_MESSAGE = 2; 6394 6395 6396 /** Global hotkey handler. Simpledisplay will usually create one for you, but if you want to use subclassing 6397 * instead of delegates, you can subclass this, and override `doHandle()` method. */ 6398 public class GlobalHotkey { 6399 KeyEvent key; 6400 void delegate () handler; 6401 6402 void doHandle () { if (handler !is null) handler(); } /// this will be called by hotkey manager 6403 6404 /// Create from initialzed KeyEvent object 6405 this (KeyEvent akey, void delegate () ahandler=null) { 6406 if (akey.key == 0 || !GlobalHotkeyManager.isGoodModifierMask(akey.modifierState)) throw new Exception("invalid global hotkey"); 6407 key = akey; 6408 handler = ahandler; 6409 } 6410 6411 /// Create from emacs-like key name ("C-M-Y", etc.) 6412 this (const(char)[] akey, void delegate () ahandler=null) { 6413 key = KeyEvent.parse(akey); 6414 if (key.key == 0 || !GlobalHotkeyManager.isGoodModifierMask(key.modifierState)) throw new Exception("invalid global hotkey"); 6415 handler = ahandler; 6416 } 6417 6418 } 6419 6420 private extern(C) int XGrabErrorHandler (Display* dpy, XErrorEvent* evt) nothrow @nogc { 6421 //conwriteln("failed to grab key"); 6422 GlobalHotkeyManager.ghfailed = true; 6423 return 0; 6424 } 6425 6426 private extern(C) int XShmErrorHandler (Display* dpy, XErrorEvent* evt) nothrow @nogc { 6427 Image.impl.xshmfailed = true; 6428 return 0; 6429 } 6430 6431 private __gshared int errorHappened; 6432 private extern(C) int adrlogger (Display* dpy, XErrorEvent* evt) nothrow @nogc { 6433 import core.stdc.stdio; 6434 char[265] buffer; 6435 XGetErrorText(dpy, evt.error_code, buffer.ptr, cast(int) buffer.length); 6436 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); 6437 errorHappened = true; 6438 return 0; 6439 } 6440 6441 /++ 6442 Global hotkey manager. It contains static methods to manage global hotkeys. 6443 6444 --- 6445 try { 6446 GlobalHotkeyManager.register("M-H-A", delegate () { hideShowWindows(); }); 6447 } catch (Exception e) { 6448 conwriteln("ERROR registering hotkey!"); 6449 } 6450 EventLoop.get.run(); 6451 --- 6452 6453 The key strings are based on Emacs. In practical terms, 6454 `M` means `alt` and `H` means the Windows logo key. `C` 6455 is `ctrl`. 6456 6457 $(WARNING 6458 This is X-specific right now. If you are on 6459 Windows, try [registerHotKey] instead. 6460 6461 We will probably merge these into a single 6462 interface later. 6463 ) 6464 +/ 6465 public class GlobalHotkeyManager : CapableOfHandlingNativeEvent { 6466 version(X11) { 6467 void recreateAfterDisconnect() { 6468 throw new Exception("NOT IMPLEMENTED"); 6469 } 6470 void discardConnectionState() { 6471 throw new Exception("NOT IMPLEMENTED"); 6472 } 6473 } 6474 6475 private static immutable uint[8] masklist = [ 0, 6476 KeyOrButtonMask.LockMask, 6477 KeyOrButtonMask.Mod2Mask, 6478 KeyOrButtonMask.Mod3Mask, 6479 KeyOrButtonMask.LockMask|KeyOrButtonMask.Mod2Mask, 6480 KeyOrButtonMask.LockMask|KeyOrButtonMask.Mod3Mask, 6481 KeyOrButtonMask.LockMask|KeyOrButtonMask.Mod2Mask|KeyOrButtonMask.Mod3Mask, 6482 KeyOrButtonMask.Mod2Mask|KeyOrButtonMask.Mod3Mask, 6483 ]; 6484 private __gshared GlobalHotkeyManager ghmanager; 6485 private __gshared bool ghfailed = false; 6486 6487 private static bool isGoodModifierMask (uint modmask) pure nothrow @safe @nogc { 6488 if (modmask == 0) return false; 6489 if (modmask&(KeyOrButtonMask.LockMask|KeyOrButtonMask.Mod2Mask|KeyOrButtonMask.Mod3Mask)) return false; 6490 if (modmask&~(KeyOrButtonMask.Mod5Mask-1)) return false; 6491 return true; 6492 } 6493 6494 private static uint cleanupModifiers (uint modmask) pure nothrow @safe @nogc { 6495 modmask &= ~(KeyOrButtonMask.LockMask|KeyOrButtonMask.Mod2Mask|KeyOrButtonMask.Mod3Mask); // remove caps, num, scroll 6496 modmask &= (KeyOrButtonMask.Mod5Mask-1); // and other modifiers 6497 return modmask; 6498 } 6499 6500 private static uint keyEvent2KeyCode() (scope auto ref const KeyEvent ke) { 6501 uint keycode = cast(uint)ke.key; 6502 auto dpy = XDisplayConnection.get; 6503 return XKeysymToKeycode(dpy, keycode); 6504 } 6505 6506 private static ulong keyCode2Hash() (uint keycode, uint modstate) pure nothrow @safe @nogc { return ((cast(ulong)modstate)<<32)|keycode; } 6507 6508 private __gshared GlobalHotkey[ulong] globalHotkeyList; 6509 6510 NativeEventHandler getNativeEventHandler () { 6511 return delegate int (XEvent e) { 6512 if (e.type != EventType.KeyPress) return 1; 6513 auto kev = cast(const(XKeyEvent)*)&e; 6514 auto hash = keyCode2Hash(e.xkey.keycode, cleanupModifiers(e.xkey.state)); 6515 if (auto ghkp = hash in globalHotkeyList) { 6516 try { 6517 ghkp.doHandle(); 6518 } catch (Exception e) { 6519 import core.stdc.stdio : stderr, fprintf; 6520 stderr.fprintf("HOTKEY HANDLER EXCEPTION: %.*s", cast(uint)e.msg.length, e.msg.ptr); 6521 } 6522 } 6523 return 1; 6524 }; 6525 } 6526 6527 private this () { 6528 auto dpy = XDisplayConnection.get; 6529 auto root = RootWindow(dpy, DefaultScreen(dpy)); 6530 CapableOfHandlingNativeEvent.nativeHandleMapping[root] = this; 6531 XDisplayConnection.addRootInput(EventMask.KeyPressMask); 6532 } 6533 6534 /// Register new global hotkey with initialized `GlobalHotkey` object. 6535 /// This function will throw if it failed to register hotkey (i.e. hotkey is invalid or already taken). 6536 static void register (GlobalHotkey gh) { 6537 if (gh is null) return; 6538 if (gh.key.key == 0 || !isGoodModifierMask(gh.key.modifierState)) throw new Exception("invalid global hotkey"); 6539 6540 auto dpy = XDisplayConnection.get; 6541 immutable keycode = keyEvent2KeyCode(gh.key); 6542 6543 auto hash = keyCode2Hash(keycode, gh.key.modifierState); 6544 if (hash in globalHotkeyList) throw new Exception("duplicate global hotkey"); 6545 if (ghmanager is null) ghmanager = new GlobalHotkeyManager(); 6546 XSync(dpy, 0/*False*/); 6547 6548 Window root = RootWindow(dpy, DefaultScreen(dpy)); 6549 XErrorHandler savedErrorHandler = XSetErrorHandler(&XGrabErrorHandler); 6550 ghfailed = false; 6551 foreach (immutable uint ormask; masklist[]) { 6552 XGrabKey(dpy, keycode, gh.key.modifierState|ormask, /*grab_window*/root, /*owner_events*/0/*False*/, GrabMode.GrabModeAsync, GrabMode.GrabModeAsync); 6553 } 6554 XSync(dpy, 0/*False*/); 6555 XSetErrorHandler(savedErrorHandler); 6556 6557 if (ghfailed) { 6558 savedErrorHandler = XSetErrorHandler(&XGrabErrorHandler); 6559 foreach (immutable uint ormask; masklist[]) XUngrabKey(dpy, keycode, gh.key.modifierState|ormask, /*grab_window*/root); 6560 XSync(dpy, 0/*False*/); 6561 XSetErrorHandler(savedErrorHandler); 6562 throw new Exception("cannot register global hotkey"); 6563 } 6564 6565 globalHotkeyList[hash] = gh; 6566 } 6567 6568 /// Ditto 6569 static void register (const(char)[] akey, void delegate () ahandler) { 6570 register(new GlobalHotkey(akey, ahandler)); 6571 } 6572 6573 private static void removeByHash (ulong hash) { 6574 if (auto ghp = hash in globalHotkeyList) { 6575 auto dpy = XDisplayConnection.get; 6576 immutable keycode = keyEvent2KeyCode(ghp.key); 6577 Window root = RootWindow(dpy, DefaultScreen(dpy)); 6578 XSync(dpy, 0/*False*/); 6579 XErrorHandler savedErrorHandler = XSetErrorHandler(&XGrabErrorHandler); 6580 foreach (immutable uint ormask; masklist[]) XUngrabKey(dpy, keycode, ghp.key.modifierState|ormask, /*grab_window*/root); 6581 XSync(dpy, 0/*False*/); 6582 XSetErrorHandler(savedErrorHandler); 6583 globalHotkeyList.remove(hash); 6584 } 6585 } 6586 6587 /// Register new global hotkey with previously used `GlobalHotkey` object. 6588 /// It is safe to unregister unknown or invalid hotkey. 6589 static void unregister (GlobalHotkey gh) { 6590 //TODO: add second AA for faster search? prolly doesn't worth it. 6591 if (gh is null) return; 6592 foreach (const ref kv; globalHotkeyList.byKeyValue) { 6593 if (kv.value is gh) { 6594 removeByHash(kv.key); 6595 return; 6596 } 6597 } 6598 } 6599 6600 /// Ditto. 6601 static void unregister (const(char)[] key) { 6602 auto kev = KeyEvent.parse(key); 6603 immutable keycode = keyEvent2KeyCode(kev); 6604 removeByHash(keyCode2Hash(keycode, kev.modifierState)); 6605 } 6606 } 6607 } 6608 6609 version(Windows) { 6610 /++ 6611 See [SyntheticInput.sendSyntheticInput] instead for cross-platform applications. 6612 6613 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). 6614 +/ 6615 void sendSyntheticInput(wstring s) { 6616 INPUT[] inputs; 6617 inputs.reserve(s.length * 2); 6618 6619 foreach(wchar c; s) { 6620 INPUT input; 6621 input.type = INPUT_KEYBOARD; 6622 input.ki.wScan = c; 6623 input.ki.dwFlags = KEYEVENTF_UNICODE; 6624 inputs ~= input; 6625 6626 input.ki.dwFlags |= KEYEVENTF_KEYUP; 6627 inputs ~= input; 6628 } 6629 6630 if(SendInput(cast(int) inputs.length, inputs.ptr, INPUT.sizeof) != inputs.length) { 6631 throw new WindowsApiException("SendInput", GetLastError()); 6632 } 6633 6634 } 6635 6636 6637 // global hotkey helper function 6638 6639 /// Platform-specific for Windows. Registers a global hotkey. Returns a registration ID. See [GlobalHotkeyManager] for Linux. Maybe some day I will merge these. 6640 int registerHotKey(SimpleWindow window, UINT modifiers, UINT vk, void delegate() handler) { 6641 __gshared int hotkeyId = 0; 6642 int id = ++hotkeyId; 6643 if(!RegisterHotKey(window.impl.hwnd, id, modifiers, vk)) 6644 throw new Exception("RegisterHotKey"); 6645 6646 __gshared void delegate()[WPARAM][HWND] handlers; 6647 6648 handlers[window.impl.hwnd][id] = handler; 6649 6650 int delegate(HWND, UINT, WPARAM, LPARAM, out int) oldHandler; 6651 6652 auto nativeEventHandler = delegate int(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam, out int mustReturn) { 6653 switch(msg) { 6654 // http://msdn.microsoft.com/en-us/library/windows/desktop/ms646279%28v=vs.85%29.aspx 6655 case WM_HOTKEY: 6656 if(auto list = hwnd in handlers) { 6657 if(auto h = wParam in *list) { 6658 (*h)(); 6659 return 0; 6660 } 6661 } 6662 goto default; 6663 default: 6664 } 6665 if(oldHandler) 6666 return oldHandler(hwnd, msg, wParam, lParam, mustReturn); 6667 return 1; // pass it on 6668 }; 6669 6670 if(window.handleNativeEvent.funcptr !is nativeEventHandler.funcptr) { 6671 oldHandler = window.handleNativeEvent; 6672 window.handleNativeEvent = nativeEventHandler; 6673 } 6674 6675 return id; 6676 } 6677 6678 /// Platform-specific for Windows. Unregisters a key. The id is the value returned by [registerHotKey]. 6679 void unregisterHotKey(SimpleWindow window, int id) { 6680 if(!UnregisterHotKey(window.impl.hwnd, id)) 6681 throw new WindowsApiException("UnregisterHotKey", GetLastError()); 6682 } 6683 } 6684 6685 version (X11) { 6686 pragma(lib, "dl"); 6687 import core.sys.posix.dlfcn; 6688 } 6689 6690 /++ 6691 Allows for sending synthetic input to the X server via the Xtst 6692 extension or on Windows using SendInput. 6693 6694 Please remember user input is meant to be user - don't use this 6695 if you have some other alternative! 6696 6697 History: 6698 Added May 17, 2020 with the X implementation. 6699 6700 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.) 6701 Bugs: 6702 All methods on OSX Cocoa will throw not yet implemented exceptions. 6703 +/ 6704 struct SyntheticInput { 6705 @disable this(); 6706 6707 private int* refcount; 6708 6709 version(X11) { 6710 private void* lib; 6711 6712 private extern(C) { 6713 void function(Display*, uint keycode, bool press, arch_ulong delay) XTestFakeKeyEvent; 6714 void function(Display*, uint button, bool press, arch_ulong delay) XTestFakeButtonEvent; 6715 } 6716 } 6717 6718 /// The dummy param must be 0. 6719 this(int dummy) { 6720 version(X11) { 6721 lib = dlopen("libXtst.so", RTLD_NOW); 6722 if(lib is null) 6723 throw new Exception("cannot load xtest lib extension"); 6724 scope(failure) 6725 dlclose(lib); 6726 6727 XTestFakeButtonEvent = cast(typeof(XTestFakeButtonEvent)) dlsym(lib, "XTestFakeButtonEvent"); 6728 XTestFakeKeyEvent = cast(typeof(XTestFakeKeyEvent)) dlsym(lib, "XTestFakeKeyEvent"); 6729 6730 if(XTestFakeKeyEvent is null) 6731 throw new Exception("No XTestFakeKeyEvent"); 6732 if(XTestFakeButtonEvent is null) 6733 throw new Exception("No XTestFakeButtonEvent"); 6734 } 6735 6736 refcount = new int; 6737 *refcount = 1; 6738 } 6739 6740 this(this) { 6741 if(refcount) 6742 *refcount += 1; 6743 } 6744 6745 ~this() { 6746 if(refcount) { 6747 *refcount -= 1; 6748 if(*refcount == 0) 6749 // I commented this because if I close the lib before 6750 // XCloseDisplay, it is liable to segfault... so just 6751 // gonna keep it loaded if it is loaded, no big deal 6752 // anyway. 6753 {} // dlclose(lib); 6754 } 6755 } 6756 6757 /++ 6758 Simulates typing a string into the keyboard. 6759 6760 Bugs: 6761 On X11, this ONLY works with basic ascii! On Windows, it can handle more. 6762 6763 Not implemented except on Windows and X11. 6764 +/ 6765 void sendSyntheticInput(string s) { 6766 version(Windows) { 6767 INPUT[] inputs; 6768 inputs.reserve(s.length * 2); 6769 6770 auto ei = GetMessageExtraInfo(); 6771 6772 foreach(wchar c; s) { 6773 INPUT input; 6774 input.type = INPUT_KEYBOARD; 6775 input.ki.wScan = c; 6776 input.ki.dwFlags = KEYEVENTF_UNICODE; 6777 input.ki.dwExtraInfo = ei; 6778 inputs ~= input; 6779 6780 input.ki.dwFlags |= KEYEVENTF_KEYUP; 6781 inputs ~= input; 6782 } 6783 6784 if(SendInput(cast(int) inputs.length, inputs.ptr, INPUT.sizeof) != inputs.length) { 6785 throw new WindowsApiException("SendInput", GetLastError()); 6786 } 6787 } else version(X11) { 6788 int delay = 0; 6789 foreach(ch; s) { 6790 pressKey(cast(Key) ch, true, delay); 6791 pressKey(cast(Key) ch, false, delay); 6792 delay += 5; 6793 } 6794 } else throw new NotYetImplementedException(); 6795 } 6796 6797 /++ 6798 Sends a fake press or release key event. 6799 6800 Please note you need to call [flushGui] or return to the event loop for this to actually be sent on X11. 6801 6802 Bugs: 6803 The `delay` parameter is not implemented yet on Windows. 6804 6805 Not implemented except on Windows and X11. 6806 +/ 6807 void pressKey(Key key, bool pressed, int delay = 0) { 6808 version(Windows) { 6809 INPUT input; 6810 input.type = INPUT_KEYBOARD; 6811 input.ki.wVk = cast(ushort) key; 6812 6813 input.ki.dwFlags = pressed ? 0 : KEYEVENTF_KEYUP; 6814 input.ki.dwExtraInfo = GetMessageExtraInfo(); 6815 6816 if(SendInput(1, &input, INPUT.sizeof) != 1) { 6817 throw new WindowsApiException("SendInput", GetLastError()); 6818 } 6819 } else version(X11) { 6820 XTestFakeKeyEvent(XDisplayConnection.get, XKeysymToKeycode(XDisplayConnection.get, key), pressed, delay + pressed ? 0 : 5); 6821 } else throw new NotYetImplementedException(); 6822 } 6823 6824 /++ 6825 Sends a fake mouse button press or release event. 6826 6827 Please note you need to call [flushGui] or return to the event loop for this to actually be sent on X11. 6828 6829 `pressed` param must be `true` if button is `wheelUp` or `wheelDown`. 6830 6831 Bugs: 6832 The `delay` parameter is not implemented yet on Windows. 6833 6834 The backButton and forwardButton will throw NotYetImplementedException on Windows. 6835 6836 All arguments will throw NotYetImplementedException on OSX Cocoa. 6837 +/ 6838 void pressMouseButton(MouseButton button, bool pressed, int delay = 0) { 6839 version(Windows) { 6840 INPUT input; 6841 input.type = INPUT_MOUSE; 6842 input.mi.dwExtraInfo = GetMessageExtraInfo(); 6843 6844 // input.mi.mouseData for a wheel event 6845 6846 switch(button) { 6847 case MouseButton.left: input.mi.dwFlags = pressed ? MOUSEEVENTF_LEFTDOWN : MOUSEEVENTF_LEFTUP; break; 6848 case MouseButton.middle: input.mi.dwFlags = pressed ? MOUSEEVENTF_MIDDLEDOWN : MOUSEEVENTF_MIDDLEUP; break; 6849 case MouseButton.right: input.mi.dwFlags = pressed ? MOUSEEVENTF_RIGHTDOWN : MOUSEEVENTF_RIGHTUP; break; 6850 case MouseButton.wheelUp: 6851 case MouseButton.wheelDown: 6852 input.mi.dwFlags = MOUSEEVENTF_WHEEL; 6853 input.mi.mouseData = button == MouseButton.wheelUp ? 120 : -120; 6854 break; 6855 case MouseButton.backButton: throw new NotYetImplementedException(); 6856 case MouseButton.forwardButton: throw new NotYetImplementedException(); 6857 default: 6858 } 6859 6860 if(SendInput(1, &input, INPUT.sizeof) != 1) { 6861 throw new WindowsApiException("SendInput", GetLastError()); 6862 } 6863 } else version(X11) { 6864 int btn; 6865 6866 switch(button) { 6867 case MouseButton.left: btn = 1; break; 6868 case MouseButton.middle: btn = 2; break; 6869 case MouseButton.right: btn = 3; break; 6870 case MouseButton.wheelUp: btn = 4; break; 6871 case MouseButton.wheelDown: btn = 5; break; 6872 case MouseButton.backButton: btn = 8; break; 6873 case MouseButton.forwardButton: btn = 9; break; 6874 default: 6875 } 6876 6877 assert(btn); 6878 6879 XTestFakeButtonEvent(XDisplayConnection.get, btn, pressed, delay); 6880 } else throw new NotYetImplementedException(); 6881 } 6882 6883 /// 6884 static void moveMouseArrowBy(int dx, int dy) { 6885 version(Windows) { 6886 INPUT input; 6887 input.type = INPUT_MOUSE; 6888 input.mi.dwExtraInfo = GetMessageExtraInfo(); 6889 input.mi.dx = dx; 6890 input.mi.dy = dy; 6891 input.mi.dwFlags = MOUSEEVENTF_MOVE; 6892 6893 if(SendInput(1, &input, INPUT.sizeof) != 1) { 6894 throw new WindowsApiException("SendInput", GetLastError()); 6895 } 6896 } else version(X11) { 6897 auto disp = XDisplayConnection.get(); 6898 XWarpPointer(disp, None, None, 0, 0, 0, 0, dx, dy); 6899 XFlush(disp); 6900 } else throw new NotYetImplementedException(); 6901 } 6902 6903 /// 6904 static void moveMouseArrowTo(int x, int y) { 6905 version(Windows) { 6906 INPUT input; 6907 input.type = INPUT_MOUSE; 6908 input.mi.dwExtraInfo = GetMessageExtraInfo(); 6909 input.mi.dx = x; 6910 input.mi.dy = y; 6911 input.mi.dwFlags = MOUSEEVENTF_MOVE | MOUSEEVENTF_ABSOLUTE; 6912 6913 if(SendInput(1, &input, INPUT.sizeof) != 1) { 6914 throw new WindowsApiException("SendInput", GetLastError()); 6915 } 6916 } else version(X11) { 6917 auto disp = XDisplayConnection.get(); 6918 auto root = RootWindow(disp, DefaultScreen(disp)); 6919 XWarpPointer(disp, None, root, 0, 0, 0, 0, x, y); 6920 XFlush(disp); 6921 } else throw new NotYetImplementedException(); 6922 } 6923 } 6924 6925 6926 6927 /++ 6928 [ScreenPainter] operations can use different operations to combine the color with the color on screen. 6929 6930 See_Also: 6931 $(LIST 6932 *[ScreenPainter] 6933 *[ScreenPainter.rasterOp] 6934 ) 6935 +/ 6936 enum RasterOp { 6937 normal, /// Replaces the pixel. 6938 xor, /// Uses bitwise xor to draw. 6939 } 6940 6941 // being phobos-free keeps the size WAY down 6942 private const(char)* toStringz(string s) { return (s ~ '\0').ptr; } 6943 package(arsd) const(wchar)* toWStringz(wstring s) { return (s ~ '\0').ptr; } 6944 package(arsd) const(wchar)* toWStringz(string s) { 6945 wstring r; 6946 foreach(dchar c; s) 6947 r ~= c; 6948 r ~= '\0'; 6949 return r.ptr; 6950 } 6951 private string[] split(in void[] a, char c) { 6952 string[] ret; 6953 size_t previous = 0; 6954 foreach(i, char ch; cast(ubyte[]) a) { 6955 if(ch == c) { 6956 ret ~= cast(string) a[previous .. i]; 6957 previous = i + 1; 6958 } 6959 } 6960 if(previous != a.length) 6961 ret ~= cast(string) a[previous .. $]; 6962 return ret; 6963 } 6964 6965 version(without_opengl) { 6966 enum OpenGlOptions { 6967 no, 6968 } 6969 } else { 6970 /++ 6971 Determines if you want an OpenGL context created on the new window. 6972 6973 6974 See more: [#topics-3d|in the 3d topic]. 6975 6976 --- 6977 import arsd.simpledisplay; 6978 void main() { 6979 auto window = new SimpleWindow(500, 500, "OpenGL Test", OpenGlOptions.yes); 6980 6981 // Set up the matrix 6982 window.setAsCurrentOpenGlContext(); // make this window active 6983 6984 // This is called on each frame, we will draw our scene 6985 window.redrawOpenGlScene = delegate() { 6986 6987 }; 6988 6989 window.eventLoop(0); 6990 } 6991 --- 6992 +/ 6993 enum OpenGlOptions { 6994 no, /// No OpenGL context is created 6995 yes, /// Yes, create an OpenGL context 6996 } 6997 6998 version(X11) { 6999 static if (!SdpyIsUsingIVGLBinds) { 7000 7001 7002 struct __GLXFBConfigRec {} 7003 alias GLXFBConfig = __GLXFBConfigRec*; 7004 7005 //pragma(lib, "GL"); 7006 //pragma(lib, "GLU"); 7007 interface GLX { 7008 extern(C) nothrow @nogc { 7009 XVisualInfo* glXChooseVisual(Display *dpy, int screen, 7010 const int *attrib_list); 7011 7012 void glXCopyContext(Display *dpy, GLXContext src, 7013 GLXContext dst, arch_ulong mask); 7014 7015 GLXContext glXCreateContext(Display *dpy, XVisualInfo *vis, 7016 GLXContext share_list, Bool direct); 7017 7018 GLXPixmap glXCreateGLXPixmap(Display *dpy, XVisualInfo *vis, 7019 Pixmap pixmap); 7020 7021 void glXDestroyContext(Display *dpy, GLXContext ctx); 7022 7023 void glXDestroyGLXPixmap(Display *dpy, GLXPixmap pix); 7024 7025 int glXGetConfig(Display *dpy, XVisualInfo *vis, 7026 int attrib, int *value); 7027 7028 GLXContext glXGetCurrentContext(); 7029 7030 GLXDrawable glXGetCurrentDrawable(); 7031 7032 Bool glXIsDirect(Display *dpy, GLXContext ctx); 7033 7034 Bool glXMakeCurrent(Display *dpy, GLXDrawable drawable, 7035 GLXContext ctx); 7036 7037 Bool glXQueryExtension(Display *dpy, int *error_base, int *event_base); 7038 7039 Bool glXQueryVersion(Display *dpy, int *major, int *minor); 7040 7041 void glXSwapBuffers(Display *dpy, GLXDrawable drawable); 7042 7043 void glXUseXFont(Font font, int first, int count, int list_base); 7044 7045 void glXWaitGL(); 7046 7047 void glXWaitX(); 7048 7049 7050 GLXFBConfig* glXChooseFBConfig (Display*, int, int*, int*); 7051 int glXGetFBConfigAttrib (Display*, GLXFBConfig, int, int*); 7052 XVisualInfo* glXGetVisualFromFBConfig (Display*, GLXFBConfig); 7053 7054 char* glXQueryExtensionsString (Display*, int); 7055 void* glXGetProcAddress (const(char)*); 7056 7057 } 7058 } 7059 7060 version(OSX) 7061 mixin DynamicLoad!(GLX, "GL", 0, openGlLibrariesSuccessfullyLoaded) glx; 7062 else 7063 mixin DynamicLoad!(GLX, "GLX", 0, openGlLibrariesSuccessfullyLoaded) glx; 7064 shared static this() { 7065 glx.loadDynamicLibrary(); 7066 } 7067 7068 alias glbindGetProcAddress = glXGetProcAddress; 7069 } 7070 } else version(Windows) { 7071 /* it is done below by interface GL */ 7072 } else 7073 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."); 7074 } 7075 7076 deprecated("Sorry, I misspelled it in the first version! Use `Resizability` instead.") 7077 alias Resizablity = Resizability; 7078 7079 /// When you create a SimpleWindow, you can see its resizability to be one of these via the constructor... 7080 enum Resizability { 7081 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. 7082 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. 7083 /++ 7084 $(PITFALL 7085 Planned for the future but not implemented. 7086 ) 7087 7088 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. 7089 7090 History: 7091 Added November 11, 2022, but not yet implemented and may not be for some time. 7092 +/ 7093 /*@__future*/ allowResizingMaintainingAspectRatio, 7094 /++ 7095 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. 7096 7097 History: 7098 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. 7099 7100 Your programs should not be affected, as they will continue to function as if the user simply never resized the window at all. 7101 +/ 7102 automaticallyScaleIfPossible, 7103 } 7104 7105 7106 /++ 7107 Alignment for [ScreenPainter.drawText]. Left, Center, or Right may be combined with VerticalTop, VerticalCenter, or VerticalBottom via bitwise or. 7108 +/ 7109 enum TextAlignment : uint { 7110 Left = 0, /// 7111 Center = 1, /// 7112 Right = 2, /// 7113 7114 VerticalTop = 0, /// 7115 VerticalCenter = 4, /// 7116 VerticalBottom = 8, /// 7117 } 7118 7119 public import arsd.color; // no longer stand alone... :-( but i need a common type for this to work with images easily. 7120 alias Rectangle = arsd.color.Rectangle; 7121 7122 7123 /++ 7124 Keyboard press and release events. 7125 +/ 7126 struct KeyEvent { 7127 /// see table below. Always use the symbolic names, even for ASCII characters, since the actual numbers vary across platforms. See [Key] 7128 Key key; 7129 ubyte hardwareCode; /// A platform and hardware specific code for the key 7130 bool pressed; /// true if the key was just pressed, false if it was just released. note: released events aren't always sent... 7131 7132 deprecated("This never actually worked anyway, you should do a character event handler instead.") dchar character; 7133 7134 uint modifierState; /// see enum [ModifierState]. They are bitwise combined together. 7135 7136 SimpleWindow window; /// associated Window 7137 7138 /++ 7139 A view into the upcoming buffer holding coming character events that are sent if and only if neither 7140 the alt or super modifier keys are pressed (check this with `!(modifierState & (ModifierState.window | ModifierState.alt))` 7141 to predict if char events are actually coming.. 7142 7143 Only available on X systems since this information is not given ahead of time elsewhere. 7144 (Well, you COULD probably dig it up, but as far as I know right now, it isn't terribly pretty.) 7145 7146 I'm adding this because it is useful to the terminal emulator, but given its platform specificness 7147 and potential quirks I'd recommend avoiding it. 7148 7149 History: 7150 Added April 26, 2021 (dub v9.5) 7151 +/ 7152 version(X11) 7153 dchar[] charsPossible; 7154 7155 // convert key event to simplified string representation a-la emacs 7156 const(char)[] toStrBuf(bool growdest=false) (char[] dest) const nothrow @trusted { 7157 uint dpos = 0; 7158 void put (const(char)[] s...) nothrow @trusted { 7159 static if (growdest) { 7160 foreach (char ch; s) if (dpos < dest.length) dest.ptr[dpos++] = ch; else { dest ~= ch; ++dpos; } 7161 } else { 7162 foreach (char ch; s) if (dpos < dest.length) dest.ptr[dpos++] = ch; 7163 } 7164 } 7165 7166 void putMod (ModifierState mod, Key key, string text) nothrow @trusted { 7167 if ((this.modifierState&mod) != 0 && (this.pressed || this.key != key)) put(text); 7168 } 7169 7170 if (!this.key && !(this.modifierState&(ModifierState.ctrl|ModifierState.alt|ModifierState.shift|ModifierState.windows))) return null; 7171 7172 // put modifiers 7173 // releasing modifier keys can produce bizarre things like "Ctrl+Ctrl", so hack around it 7174 putMod(ModifierState.ctrl, Key.Ctrl, "Ctrl+"); 7175 putMod(ModifierState.alt, Key.Alt, "Alt+"); 7176 putMod(ModifierState.windows, Key.Shift, "Windows+"); 7177 putMod(ModifierState.shift, Key.Shift, "Shift+"); 7178 7179 if (this.key) { 7180 foreach (string kn; __traits(allMembers, Key)) { 7181 if (this.key == __traits(getMember, Key, kn)) { 7182 // HACK! 7183 static if (kn == "N0") put("0"); 7184 else static if (kn == "N1") put("1"); 7185 else static if (kn == "N2") put("2"); 7186 else static if (kn == "N3") put("3"); 7187 else static if (kn == "N4") put("4"); 7188 else static if (kn == "N5") put("5"); 7189 else static if (kn == "N6") put("6"); 7190 else static if (kn == "N7") put("7"); 7191 else static if (kn == "N8") put("8"); 7192 else static if (kn == "N9") put("9"); 7193 else put(kn); 7194 return dest[0..dpos]; 7195 } 7196 } 7197 put("Unknown"); 7198 } else { 7199 if (dpos && dest[dpos-1] == '+') --dpos; 7200 } 7201 return dest[0..dpos]; 7202 } 7203 7204 string toStr() () { return cast(string)toStrBuf!true(null); } // it is safe to cast here 7205 7206 /** Parse string into key name with modifiers. It accepts things like: 7207 * 7208 * C-H-1 -- emacs style (ctrl, and windows, and 1) 7209 * 7210 * Ctrl+Win+1 -- windows style 7211 * 7212 * Ctrl-Win-1 -- '-' is a valid delimiter too 7213 * 7214 * Ctrl Win 1 -- and space 7215 * 7216 * and even "Win + 1 + Ctrl". 7217 */ 7218 static KeyEvent parse (const(char)[] name, bool* ignoreModsOut=null, int* updown=null) nothrow @trusted @nogc { 7219 auto nanchor = name; // keep it anchored, 'cause `name` may have NO_INTERIOR set 7220 7221 // remove trailing spaces 7222 while (name.length && name[$-1] <= ' ') name = name[0..$-1]; 7223 7224 // tokens delimited by blank, '+', or '-' 7225 // null on eol 7226 const(char)[] getToken () nothrow @trusted @nogc { 7227 // remove leading spaces and delimiters 7228 while (name.length && (name[0] <= ' ' || name[0] == '+' || name[0] == '-')) name = name[1..$]; 7229 if (name.length == 0) return null; // oops, no more tokens 7230 // get token 7231 size_t epos = 0; 7232 while (epos < name.length && name[epos] > ' ' && name[epos] != '+' && name[epos] != '-') ++epos; 7233 assert(epos > 0 && epos <= name.length); 7234 auto res = name[0..epos]; 7235 name = name[epos..$]; 7236 return res; 7237 } 7238 7239 static bool strEquCI (const(char)[] s0, const(char)[] s1) pure nothrow @trusted @nogc { 7240 if (s0.length != s1.length) return false; 7241 foreach (immutable ci, char c0; s0) { 7242 if (c0 >= 'A' && c0 <= 'Z') c0 += 32; // poor man's tolower 7243 char c1 = s1[ci]; 7244 if (c1 >= 'A' && c1 <= 'Z') c1 += 32; // poor man's tolower 7245 if (c0 != c1) return false; 7246 } 7247 return true; 7248 } 7249 7250 if (ignoreModsOut !is null) *ignoreModsOut = false; 7251 if (updown !is null) *updown = -1; 7252 KeyEvent res; 7253 res.key = cast(Key)0; // just in case 7254 const(char)[] tk, tkn; // last token 7255 bool allowEmascStyle = true; 7256 bool ignoreModifiers = false; 7257 tokenloop: for (;;) { 7258 tk = tkn; 7259 tkn = getToken(); 7260 //k8: yay, i took "Bloody Mess" trait from Fallout! 7261 if (tkn.length != 0 && tk.length == 0) { tk = tkn; continue tokenloop; } 7262 if (tkn.length == 0 && tk.length == 0) break; // no more tokens 7263 if (allowEmascStyle && tkn.length != 0) { 7264 if (tk.length == 1) { 7265 char mdc = tk[0]; 7266 if (mdc >= 'a' && mdc <= 'z') mdc -= 32; // poor man's toupper() 7267 if (mdc == 'C' && (res.modifierState&ModifierState.ctrl) == 0) {res.modifierState |= ModifierState.ctrl; continue tokenloop; } 7268 if (mdc == 'M' && (res.modifierState&ModifierState.alt) == 0) { res.modifierState |= ModifierState.alt; continue tokenloop; } 7269 if (mdc == 'H' && (res.modifierState&ModifierState.windows) == 0) { res.modifierState |= ModifierState.windows; continue tokenloop; } 7270 if (mdc == 'S' && (res.modifierState&ModifierState.shift) == 0) { res.modifierState |= ModifierState.shift; continue tokenloop; } 7271 if (mdc == '*') { ignoreModifiers = true; continue tokenloop; } 7272 if (mdc == 'U' || mdc == 'R') { if (updown !is null) *updown = 0; continue tokenloop; } 7273 if (mdc == 'D' || mdc == 'P') { if (updown !is null) *updown = 1; continue tokenloop; } 7274 } 7275 } 7276 allowEmascStyle = false; 7277 if (strEquCI(tk, "Ctrl")) { res.modifierState |= ModifierState.ctrl; continue tokenloop; } 7278 if (strEquCI(tk, "Alt")) { res.modifierState |= ModifierState.alt; continue tokenloop; } 7279 if (strEquCI(tk, "Win") || strEquCI(tk, "Windows")) { res.modifierState |= ModifierState.windows; continue tokenloop; } 7280 if (strEquCI(tk, "Shift")) { res.modifierState |= ModifierState.shift; continue tokenloop; } 7281 if (strEquCI(tk, "Release")) { if (updown !is null) *updown = 0; continue tokenloop; } 7282 if (strEquCI(tk, "Press")) { if (updown !is null) *updown = 1; continue tokenloop; } 7283 if (tk == "*") { ignoreModifiers = true; continue tokenloop; } 7284 if (tk.length == 0) continue; 7285 // try key name 7286 if (res.key == 0) { 7287 // little hack 7288 if (tk.length == 1 && tk[0] >= '0' && tk[0] <= '9') { 7289 final switch (tk[0]) { 7290 case '0': tk = "N0"; break; 7291 case '1': tk = "N1"; break; 7292 case '2': tk = "N2"; break; 7293 case '3': tk = "N3"; break; 7294 case '4': tk = "N4"; break; 7295 case '5': tk = "N5"; break; 7296 case '6': tk = "N6"; break; 7297 case '7': tk = "N7"; break; 7298 case '8': tk = "N8"; break; 7299 case '9': tk = "N9"; break; 7300 } 7301 } 7302 foreach (string kn; __traits(allMembers, Key)) { 7303 if (strEquCI(tk, kn)) { res.key = __traits(getMember, Key, kn); continue tokenloop; } 7304 } 7305 } 7306 // unknown or duplicate key name, get out of here 7307 break; 7308 } 7309 if (ignoreModsOut !is null) *ignoreModsOut = ignoreModifiers; 7310 return res; // something 7311 } 7312 7313 bool opEquals() (const(char)[] name) const nothrow @trusted @nogc { 7314 enum modmask = (ModifierState.ctrl|ModifierState.alt|ModifierState.shift|ModifierState.windows); 7315 void doModKey (ref uint mask, ref Key kk, Key k, ModifierState mst) { 7316 if (kk == k) { mask |= mst; kk = cast(Key)0; } 7317 } 7318 bool ignoreMods; 7319 int updown; 7320 auto ke = KeyEvent.parse(name, &ignoreMods, &updown); 7321 if ((updown == 0 && this.pressed) || (updown == 1 && !this.pressed)) return false; 7322 if (this.key != ke.key) { 7323 // things like "ctrl+alt" are complicated 7324 uint tkm = this.modifierState&modmask; 7325 uint kkm = ke.modifierState&modmask; 7326 Key tk = this.key; 7327 // ke 7328 doModKey(kkm, ke.key, Key.Ctrl, ModifierState.ctrl); 7329 doModKey(kkm, ke.key, Key.Alt, ModifierState.alt); 7330 doModKey(kkm, ke.key, Key.Windows, ModifierState.windows); 7331 doModKey(kkm, ke.key, Key.Shift, ModifierState.shift); 7332 // this 7333 doModKey(tkm, tk, Key.Ctrl, ModifierState.ctrl); 7334 doModKey(tkm, tk, Key.Alt, ModifierState.alt); 7335 doModKey(tkm, tk, Key.Windows, ModifierState.windows); 7336 doModKey(tkm, tk, Key.Shift, ModifierState.shift); 7337 return (tk == ke.key && tkm == kkm); 7338 } 7339 return (ignoreMods || ((this.modifierState&modmask) == (ke.modifierState&modmask))); 7340 } 7341 } 7342 7343 /// Sets the application name. 7344 @property string ApplicationName(string name) { 7345 return _applicationName = name; 7346 } 7347 7348 string _applicationName; 7349 7350 /// ditto 7351 @property string ApplicationName() { 7352 if(_applicationName is null) { 7353 import core.runtime; 7354 return Runtime.args[0]; 7355 } 7356 return _applicationName; 7357 } 7358 7359 7360 /// Type of a [MouseEvent]. 7361 enum MouseEventType : int { 7362 motion = 0, /// The mouse moved inside the window 7363 buttonPressed = 1, /// A mouse button was pressed or the wheel was spun 7364 buttonReleased = 2, /// A mouse button was released 7365 } 7366 7367 // FIXME: mouse move should be distinct from presses+releases, so we can avoid subscribing to those events in X unnecessarily 7368 /++ 7369 Listen for this on your event listeners if you are interested in mouse action. 7370 7371 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. 7372 7373 Examples: 7374 7375 This will draw boxes on the window with the mouse as you hold the left button. 7376 --- 7377 import arsd.simpledisplay; 7378 7379 void main() { 7380 auto window = new SimpleWindow(); 7381 7382 window.eventLoop(0, 7383 (MouseEvent ev) { 7384 if(ev.modifierState & ModifierState.leftButtonDown) { 7385 auto painter = window.draw(); 7386 painter.fillColor = Color.red; 7387 painter.outlineColor = Color.black; 7388 painter.drawRectangle(Point(ev.x / 16 * 16, ev.y / 16 * 16), 16, 16); 7389 } 7390 } 7391 ); 7392 } 7393 --- 7394 +/ 7395 struct MouseEvent { 7396 MouseEventType type; /// movement, press, release, double click. See [MouseEventType] 7397 7398 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. 7399 int y; /// Current Y position of the cursor when the event fired. 7400 7401 int dx; /// Change in X position since last report 7402 int dy; /// Change in Y position since last report 7403 7404 MouseButton button; /// See [MouseButton] 7405 int modifierState; /// See [ModifierState] 7406 7407 version(X11) 7408 private Time timestamp; 7409 7410 /// Returns a linear representation of mouse button, 7411 /// for use with static arrays. Guaranteed to be >= 0 && <= 15 7412 /// 7413 /// Its implementation is based on range-limiting `core.bitop.bsf(button) + 1`. 7414 @property ubyte buttonLinear() const { 7415 import core.bitop; 7416 if(button == 0) 7417 return 0; 7418 return (bsf(button) + 1) & 0b1111; 7419 } 7420 7421 bool doubleClick; /// was it a double click? Only set on type == [MouseEventType.buttonPressed] 7422 7423 SimpleWindow window; /// The window in which the event happened. 7424 7425 Point globalCoordinates() { 7426 Point p; 7427 if(window is null) 7428 throw new Exception("wtf"); 7429 static if(UsingSimpledisplayX11) { 7430 Window child; 7431 XTranslateCoordinates( 7432 XDisplayConnection.get, 7433 window.impl.window, 7434 RootWindow(XDisplayConnection.get, DefaultScreen(XDisplayConnection.get)), 7435 x, y, &p.x, &p.y, &child); 7436 return p; 7437 } else version(Windows) { 7438 POINT[1] points; 7439 points[0].x = x; 7440 points[0].y = y; 7441 MapWindowPoints( 7442 window.impl.hwnd, 7443 null, 7444 points.ptr, 7445 points.length 7446 ); 7447 p.x = points[0].x; 7448 p.y = points[0].y; 7449 7450 return p; 7451 } else version(OSXCocoa) { 7452 throw new NotYetImplementedException(); 7453 } else static assert(0); 7454 } 7455 7456 bool opEquals() (const(char)[] str) pure nothrow @trusted @nogc { return equStr(this, str); } 7457 7458 /** 7459 can contain emacs-like modifier prefix 7460 case-insensitive names: 7461 lmbX/leftX 7462 rmbX/rightX 7463 mmbX/middleX 7464 wheelX 7465 motion (no prefix allowed) 7466 'X' is either "up" or "down" (or "-up"/"-down"); if omited, means "down" 7467 */ 7468 static bool equStr() (scope auto ref const MouseEvent event, const(char)[] str) pure nothrow @trusted @nogc { 7469 if (str.length == 0) return false; // just in case 7470 debug(arsd_mevent_strcmp) { import iv.cmdcon; conwriteln("str=<", str, ">"); } 7471 enum Flag : uint { Up = 0x8000_0000U, Down = 0x4000_0000U, Any = 0x1000_0000U } 7472 auto anchor = str; 7473 uint mods = 0; // uint.max == any 7474 // interesting bits in kmod 7475 uint kmodmask = 7476 ModifierState.shift| 7477 ModifierState.ctrl| 7478 ModifierState.alt| 7479 ModifierState.windows| 7480 ModifierState.leftButtonDown| 7481 ModifierState.middleButtonDown| 7482 ModifierState.rightButtonDown| 7483 0; 7484 uint lastButt = uint.max; // otherwise, bit 31 means "down" 7485 bool wasButtons = false; 7486 while (str.length) { 7487 if (str.ptr[0] <= ' ') { 7488 while (str.length && str.ptr[0] <= ' ') str = str[1..$]; 7489 continue; 7490 } 7491 // one-letter modifier? 7492 if (str.length >= 2 && str.ptr[1] == '-') { 7493 switch (str.ptr[0]) { 7494 case '*': // "any" modifier (cannot be undone) 7495 mods = mods.max; 7496 break; 7497 case 'C': case 'c': // emacs "ctrl" 7498 if (mods != mods.max) mods |= ModifierState.ctrl; 7499 break; 7500 case 'M': case 'm': // emacs "meta" 7501 if (mods != mods.max) mods |= ModifierState.alt; 7502 break; 7503 case 'S': case 's': // emacs "shift" 7504 if (mods != mods.max) mods |= ModifierState.shift; 7505 break; 7506 case 'H': case 'h': // emacs "hyper" (aka winkey) 7507 if (mods != mods.max) mods |= ModifierState.windows; 7508 break; 7509 default: 7510 return false; // unknown modifier 7511 } 7512 str = str[2..$]; 7513 continue; 7514 } 7515 // word 7516 char[16] buf = void; // locased 7517 auto wep = 0; 7518 while (str.length) { 7519 immutable char ch = str.ptr[0]; 7520 if (ch <= ' ' || ch == '-') break; 7521 str = str[1..$]; 7522 if (wep > buf.length) return false; // too long 7523 if (ch >= 'A' && ch <= 'Z') buf.ptr[wep++] = cast(char)(ch+32); // poor man tolower 7524 else if (ch >= 'a' && ch <= 'z') buf.ptr[wep++] = ch; 7525 else return false; // invalid char 7526 } 7527 if (wep == 0) return false; // just in case 7528 uint bnum; 7529 enum UpDown { None = -1, Up, Down, Any } 7530 auto updown = UpDown.None; // 0: up; 1: down 7531 switch (buf[0..wep]) { 7532 // left button 7533 case "lmbup": case "leftup": updown = UpDown.Up; goto case "lmb"; 7534 case "lmbdown": case "leftdown": updown = UpDown.Down; goto case "lmb"; 7535 case "lmbany": case "leftany": updown = UpDown.Any; goto case "lmb"; 7536 case "lmb": case "left": bnum = 0; break; 7537 // middle button 7538 case "mmbup": case "middleup": updown = UpDown.Up; goto case "mmb"; 7539 case "mmbdown": case "middledown": updown = UpDown.Down; goto case "mmb"; 7540 case "mmbany": case "middleany": updown = UpDown.Any; goto case "mmb"; 7541 case "mmb": case "middle": bnum = 1; break; 7542 // right button 7543 case "rmbup": case "rightup": updown = UpDown.Up; goto case "rmb"; 7544 case "rmbdown": case "rightdown": updown = UpDown.Down; goto case "rmb"; 7545 case "rmbany": case "rightany": updown = UpDown.Any; goto case "rmb"; 7546 case "rmb": case "right": bnum = 2; break; 7547 // wheel 7548 case "wheelup": updown = UpDown.Up; goto case "wheel"; 7549 case "wheeldown": updown = UpDown.Down; goto case "wheel"; 7550 case "wheelany": updown = UpDown.Any; goto case "wheel"; 7551 case "wheel": bnum = 3; break; 7552 // motion 7553 case "motion": bnum = 7; break; 7554 // unknown 7555 default: return false; 7556 } 7557 debug(arsd_mevent_strcmp) { import iv.cmdcon; conprintfln(" 0: mods=0x%08x; bnum=%u; updown=%s [%s]", mods, bnum, updown, str); } 7558 // parse possible "-up" or "-down" 7559 if (updown == UpDown.None && bnum < 7 && str.length > 0 && str.ptr[0] == '-') { 7560 wep = 0; 7561 foreach (immutable idx, immutable char ch; str[1..$]) { 7562 if (ch <= ' ' || ch == '-') break; 7563 assert(idx == wep); // for now; trick 7564 if (wep > buf.length) { wep = 0; break; } // too long 7565 if (ch >= 'A' && ch <= 'Z') buf.ptr[wep++] = cast(char)(ch+32); // poor man tolower 7566 else if (ch >= 'a' && ch <= 'z') buf.ptr[wep++] = ch; 7567 else { wep = 0; break; } // invalid char 7568 } 7569 if (wep == 2 && buf[0..wep] == "up") updown = UpDown.Up; 7570 else if (wep == 4 && buf[0..wep] == "down") updown = UpDown.Down; 7571 else if (wep == 3 && buf[0..wep] == "any") updown = UpDown.Any; 7572 // remove parsed part 7573 if (updown != UpDown.None) str = str[wep+1..$]; 7574 } 7575 if (updown == UpDown.None) { 7576 updown = UpDown.Down; 7577 } 7578 wasButtons = wasButtons || (bnum <= 2); 7579 //assert(updown != UpDown.None); 7580 debug(arsd_mevent_strcmp) { import iv.cmdcon; conprintfln(" 1: mods=0x%08x; bnum=%u; updown=%s [%s]", mods, bnum, updown, str); } 7581 // if we have a previous button, it goes to modifiers (unless it is a wheel or motion) 7582 if (lastButt != lastButt.max) { 7583 if ((lastButt&0xff) >= 3) return false; // wheel or motion 7584 if (mods != mods.max) { 7585 uint butbit = 0; 7586 final switch (lastButt&0x03) { 7587 case 0: butbit = ModifierState.leftButtonDown; break; 7588 case 1: butbit = ModifierState.middleButtonDown; break; 7589 case 2: butbit = ModifierState.rightButtonDown; break; 7590 } 7591 if (lastButt&Flag.Down) mods |= butbit; 7592 else if (lastButt&Flag.Up) mods &= ~butbit; 7593 else if (lastButt&Flag.Any) kmodmask &= ~butbit; 7594 } 7595 } 7596 // remember last button 7597 lastButt = bnum|(updown == UpDown.Up ? Flag.Up : updown == UpDown.Any ? Flag.Any : Flag.Down); 7598 } 7599 // no button -- nothing to do 7600 if (lastButt == lastButt.max) return false; 7601 // done parsing, check if something's left 7602 foreach (immutable char ch; str) if (ch > ' ') return false; // oops 7603 // remove action button from mask 7604 if ((lastButt&0xff) < 3) { 7605 final switch (lastButt&0x03) { 7606 case 0: kmodmask &= ~cast(uint)ModifierState.leftButtonDown; break; 7607 case 1: kmodmask &= ~cast(uint)ModifierState.middleButtonDown; break; 7608 case 2: kmodmask &= ~cast(uint)ModifierState.rightButtonDown; break; 7609 } 7610 } 7611 // special case: "Motion" means "ignore buttons" 7612 if ((lastButt&0xff) == 7 && !wasButtons) { 7613 debug(arsd_mevent_strcmp) { import iv.cmdcon; conwriteln(" *: special motion"); } 7614 kmodmask &= ~cast(uint)(ModifierState.leftButtonDown|ModifierState.middleButtonDown|ModifierState.rightButtonDown); 7615 } 7616 uint kmod = event.modifierState&kmodmask; 7617 debug(arsd_mevent_strcmp) { import iv.cmdcon; conprintfln(" *: mods=0x%08x; lastButt=0x%08x; kmod=0x%08x; type=%s", mods, lastButt, kmod, event.type); } 7618 // check modifier state 7619 if (mods != mods.max) { 7620 if (kmod != mods) return false; 7621 } 7622 // now check type 7623 if ((lastButt&0xff) == 7) { 7624 // motion 7625 if (event.type != MouseEventType.motion) return false; 7626 } else if ((lastButt&0xff) == 3) { 7627 // wheel 7628 if (lastButt&Flag.Up) return (event.type == MouseEventType.buttonPressed && event.button == MouseButton.wheelUp); 7629 if (lastButt&Flag.Down) return (event.type == MouseEventType.buttonPressed && event.button == MouseButton.wheelDown); 7630 if (lastButt&Flag.Any) return (event.type == MouseEventType.buttonPressed && (event.button == MouseButton.wheelUp || event.button == MouseButton.wheelUp)); 7631 return false; 7632 } else { 7633 // buttons 7634 if (((lastButt&Flag.Down) != 0 && event.type != MouseEventType.buttonPressed) || 7635 ((lastButt&Flag.Up) != 0 && event.type != MouseEventType.buttonReleased)) 7636 { 7637 return false; 7638 } 7639 // button number 7640 switch (lastButt&0x03) { 7641 case 0: if (event.button != MouseButton.left) return false; break; 7642 case 1: if (event.button != MouseButton.middle) return false; break; 7643 case 2: if (event.button != MouseButton.right) return false; break; 7644 default: return false; 7645 } 7646 } 7647 return true; 7648 } 7649 } 7650 7651 version(arsd_mevent_strcmp_test) unittest { 7652 MouseEvent event; 7653 event.type = MouseEventType.buttonPressed; 7654 event.button = MouseButton.left; 7655 event.modifierState = ModifierState.ctrl; 7656 assert(event == "C-LMB"); 7657 assert(event != "C-LMBUP"); 7658 assert(event != "C-LMB-UP"); 7659 assert(event != "C-S-LMB"); 7660 assert(event == "*-LMB"); 7661 assert(event != "*-LMB-UP"); 7662 7663 event.type = MouseEventType.buttonReleased; 7664 assert(event != "C-LMB"); 7665 assert(event == "C-LMBUP"); 7666 assert(event == "C-LMB-UP"); 7667 assert(event != "C-S-LMB"); 7668 assert(event != "*-LMB"); 7669 assert(event == "*-LMB-UP"); 7670 7671 event.button = MouseButton.right; 7672 event.modifierState |= ModifierState.shift; 7673 event.type = MouseEventType.buttonPressed; 7674 assert(event != "C-LMB"); 7675 assert(event != "C-LMBUP"); 7676 assert(event != "C-LMB-UP"); 7677 assert(event != "C-S-LMB"); 7678 assert(event != "*-LMB"); 7679 assert(event != "*-LMB-UP"); 7680 7681 assert(event != "C-RMB"); 7682 assert(event != "C-RMBUP"); 7683 assert(event != "C-RMB-UP"); 7684 assert(event == "C-S-RMB"); 7685 assert(event == "*-RMB"); 7686 assert(event != "*-RMB-UP"); 7687 } 7688 7689 /// This gives a few more options to drawing lines and such 7690 struct Pen { 7691 Color color; /// the foreground color 7692 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. 7693 Style style; /// See [Style] 7694 /+ 7695 // From X.h 7696 7697 #define LineSolid 0 7698 #define LineOnOffDash 1 7699 #define LineDoubleDash 2 7700 LineDou- The full path of the line is drawn, but the 7701 bleDash even dashes are filled differently from the 7702 odd dashes (see fill-style) with CapButt 7703 style used where even and odd dashes meet. 7704 7705 7706 7707 /* capStyle */ 7708 7709 #define CapNotLast 0 7710 #define CapButt 1 7711 #define CapRound 2 7712 #define CapProjecting 3 7713 7714 /* joinStyle */ 7715 7716 #define JoinMiter 0 7717 #define JoinRound 1 7718 #define JoinBevel 2 7719 7720 /* fillStyle */ 7721 7722 #define FillSolid 0 7723 #define FillTiled 1 7724 #define FillStippled 2 7725 #define FillOpaqueStippled 3 7726 7727 7728 +/ 7729 /// Style of lines drawn 7730 enum Style { 7731 Solid, /// a solid line 7732 Dashed, /// a dashed line 7733 Dotted, /// a dotted line 7734 } 7735 } 7736 7737 7738 /++ 7739 Represents an in-memory image in the format that the GUI expects, but with its raw data available to your program. 7740 7741 7742 On Windows, this means a device-independent bitmap. On X11, it is an XImage. 7743 7744 $(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.) 7745 7746 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. 7747 7748 If you intend to draw an image to screen several times, you will want to convert it into a [Sprite]. 7749 7750 $(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. 7751 7752 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! 7753 7754 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!) 7755 7756 Please call `destroy(image);` when you are done with it. The easiest way to do this is with scope: 7757 7758 --- 7759 auto image = new Image(256, 256); 7760 scope(exit) destroy(image); 7761 --- 7762 7763 As long as you don't hold on to it outside the scope. 7764 7765 I might change it to be an owned pointer at some point in the future. 7766 7767 ) 7768 7769 Drawing pixels on the image may be simple, using the `opIndexAssign` function, but 7770 you can also often get a fair amount of speedup by getting the raw data format and 7771 writing some custom code. 7772 7773 FIXME INSERT EXAMPLES HERE 7774 7775 7776 +/ 7777 final class Image { 7778 /// 7779 this(int width, int height, bool forcexshm=false, bool enableAlpha = false) { 7780 this.width = width; 7781 this.height = height; 7782 this.enableAlpha = enableAlpha; 7783 7784 impl.createImage(width, height, forcexshm, enableAlpha); 7785 } 7786 7787 /// 7788 this(Size size, bool forcexshm=false, bool enableAlpha = false) { 7789 this(size.width, size.height, forcexshm, enableAlpha); 7790 } 7791 7792 private bool suppressDestruction; 7793 7794 version(X11) 7795 this(XImage* handle) { 7796 this.handle = handle; 7797 this.rawData = cast(ubyte*) handle.data; 7798 this.width = handle.width; 7799 this.height = handle.height; 7800 this.enableAlpha = handle.depth == 32; 7801 suppressDestruction = true; 7802 } 7803 7804 ~this() { 7805 if(suppressDestruction) return; 7806 impl.dispose(); 7807 } 7808 7809 // these numbers are used for working with rawData itself, skipping putPixel and getPixel 7810 /// if you do the math yourself you might be able to optimize it. Call these functions only once and cache the value. 7811 pure const @system nothrow { 7812 /* 7813 To use these to draw a blue rectangle with size WxH at position X,Y... 7814 7815 // make certain that it will fit before we proceed 7816 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! 7817 7818 // gather all the values you'll need up front. These can be kept until the image changes size if you want 7819 // (though calculating them isn't really that expensive). 7820 auto nextLineAdjustment = img.adjustmentForNextLine(); 7821 auto offR = img.redByteOffset(); 7822 auto offB = img.blueByteOffset(); 7823 auto offG = img.greenByteOffset(); 7824 auto bpp = img.bytesPerPixel(); 7825 7826 auto data = img.getDataPointer(); 7827 7828 // figure out the starting byte offset 7829 auto offset = img.offsetForTopLeftPixel() + nextLineAdjustment*Y + bpp * X; 7830 7831 auto startOfLine = data + offset; // get our pointer lined up on the first pixel 7832 7833 // and now our drawing loop for the rectangle 7834 foreach(y; 0 .. H) { 7835 auto data = startOfLine; // we keep the start of line separately so moving to the next line is simple and portable 7836 foreach(x; 0 .. W) { 7837 // write our color 7838 data[offR] = 0; 7839 data[offG] = 0; 7840 data[offB] = 255; 7841 7842 data += bpp; // moving to the next pixel is just an addition... 7843 } 7844 startOfLine += nextLineAdjustment; 7845 } 7846 7847 7848 As you can see, the loop itself was very simple thanks to the calculations being moved outside. 7849 7850 FIXME: I wonder if I can make the pixel formats consistently 32 bit across platforms, so the color offsets 7851 can be made into a bitmask or something so we can write them as *uint... 7852 */ 7853 7854 /// 7855 int offsetForTopLeftPixel() { 7856 version(X11) { 7857 return 0; 7858 } else version(Windows) { 7859 if(enableAlpha) { 7860 return (width * 4) * (height - 1); 7861 } else { 7862 return (((cast(int) width * 3 + 3) / 4) * 4) * (height - 1); 7863 } 7864 } else version(OSXCocoa) { 7865 return 0 ; //throw new NotYetImplementedException(); 7866 } else static assert(0, "fill in this info for other OSes"); 7867 } 7868 7869 /// 7870 int offsetForPixel(int x, int y) { 7871 version(X11) { 7872 auto offset = (y * width + x) * 4; 7873 return offset; 7874 } else version(Windows) { 7875 if(enableAlpha) { 7876 auto itemsPerLine = width * 4; 7877 // remember, bmps are upside down 7878 auto offset = itemsPerLine * (height - y - 1) + x * 4; 7879 return offset; 7880 } else { 7881 auto itemsPerLine = ((cast(int) width * 3 + 3) / 4) * 4; 7882 // remember, bmps are upside down 7883 auto offset = itemsPerLine * (height - y - 1) + x * 3; 7884 return offset; 7885 } 7886 } else version(OSXCocoa) { 7887 return 0 ; //throw new NotYetImplementedException(); 7888 } else static assert(0, "fill in this info for other OSes"); 7889 } 7890 7891 /// 7892 int adjustmentForNextLine() { 7893 version(X11) { 7894 return width * 4; 7895 } else version(Windows) { 7896 // windows bmps are upside down, so the adjustment is actually negative 7897 if(enableAlpha) 7898 return - (cast(int) width * 4); 7899 else 7900 return -((cast(int) width * 3 + 3) / 4) * 4; 7901 } else version(OSXCocoa) { 7902 return 0 ; //throw new NotYetImplementedException(); 7903 } else static assert(0, "fill in this info for other OSes"); 7904 } 7905 7906 /// once you have the position of a pixel, use these to get to the proper color 7907 int redByteOffset() { 7908 version(X11) { 7909 return 2; 7910 } else version(Windows) { 7911 return 2; 7912 } else version(OSXCocoa) { 7913 return 0 ; //throw new NotYetImplementedException(); 7914 } else static assert(0, "fill in this info for other OSes"); 7915 } 7916 7917 /// 7918 int greenByteOffset() { 7919 version(X11) { 7920 return 1; 7921 } else version(Windows) { 7922 return 1; 7923 } else version(OSXCocoa) { 7924 return 0 ; //throw new NotYetImplementedException(); 7925 } else static assert(0, "fill in this info for other OSes"); 7926 } 7927 7928 /// 7929 int blueByteOffset() { 7930 version(X11) { 7931 return 0; 7932 } else version(Windows) { 7933 return 0; 7934 } else version(OSXCocoa) { 7935 return 0 ; //throw new NotYetImplementedException(); 7936 } else static assert(0, "fill in this info for other OSes"); 7937 } 7938 7939 /// Only valid if [enableAlpha] is true 7940 int alphaByteOffset() { 7941 version(X11) { 7942 return 3; 7943 } else version(Windows) { 7944 return 3; 7945 } else version(OSXCocoa) { 7946 return 3; //throw new NotYetImplementedException(); 7947 } else static assert(0, "fill in this info for other OSes"); 7948 } 7949 } 7950 7951 /// 7952 final void putPixel(int x, int y, Color c) { 7953 if(x < 0 || x >= width) 7954 return; 7955 if(y < 0 || y >= height) 7956 return; 7957 7958 impl.setPixel(x, y, c); 7959 } 7960 7961 /// 7962 final Color getPixel(int x, int y) { 7963 if(x < 0 || x >= width) 7964 return Color.transparent; 7965 if(y < 0 || y >= height) 7966 return Color.transparent; 7967 7968 version(OSXCocoa) throw new NotYetImplementedException(); else 7969 return impl.getPixel(x, y); 7970 } 7971 7972 /// 7973 final void opIndexAssign(Color c, int x, int y) { 7974 putPixel(x, y, c); 7975 } 7976 7977 /// 7978 TrueColorImage toTrueColorImage() { 7979 auto tci = new TrueColorImage(width, height); 7980 convertToRgbaBytes(tci.imageData.bytes); 7981 return tci; 7982 } 7983 7984 /// 7985 static Image fromMemoryImage(MemoryImage i, bool enableAlpha = false) { 7986 auto tci = i.getAsTrueColorImage(); 7987 auto img = new Image(tci.width, tci.height, false, enableAlpha); 7988 img.setRgbaBytes(tci.imageData.bytes); 7989 return img; 7990 } 7991 7992 /// this is here for interop with arsd.image. where can be a TrueColorImage's data member 7993 /// if you pass in a buffer, it will put it right there. length must be width*height*4 already 7994 /// if you pass null, it will allocate a new one. 7995 ubyte[] getRgbaBytes(ubyte[] where = null) { 7996 if(where is null) 7997 where = new ubyte[this.width*this.height*4]; 7998 convertToRgbaBytes(where); 7999 return where; 8000 } 8001 8002 /// this is here for interop with arsd.image. from can be a TrueColorImage's data member 8003 void setRgbaBytes(in ubyte[] from ) { 8004 assert(from.length == this.width * this.height * 4); 8005 setFromRgbaBytes(from); 8006 } 8007 8008 // FIXME: make properly cross platform by getting rgba right 8009 8010 /// warning: this is not portable across platforms because the data format can change 8011 ubyte* getDataPointer() { 8012 return impl.rawData; 8013 } 8014 8015 /// for use with getDataPointer 8016 final int bytesPerLine() const pure @safe nothrow { 8017 version(Windows) 8018 return enableAlpha ? (width * 4) : (((cast(int) width * 3 + 3) / 4) * 4); 8019 else version(X11) 8020 return 4 * width; 8021 else version(OSXCocoa) 8022 return 4 * width; 8023 else static assert(0); 8024 } 8025 8026 /// for use with getDataPointer 8027 final int bytesPerPixel() const pure @safe nothrow { 8028 version(Windows) 8029 return enableAlpha ? 4 : 3; 8030 else version(X11) 8031 return 4; 8032 else version(OSXCocoa) 8033 return 4; 8034 else static assert(0); 8035 } 8036 8037 /// 8038 immutable int width; 8039 8040 /// 8041 immutable int height; 8042 8043 /// 8044 immutable bool enableAlpha; 8045 //private: 8046 mixin NativeImageImplementation!() impl; 8047 } 8048 8049 /++ 8050 A convenience function to pop up a window displaying the image. 8051 If you pass a win, it will draw the image in it. Otherwise, it will 8052 create a window with the size of the image and run its event loop, closing 8053 when a key is pressed. 8054 8055 History: 8056 `BlockingMode` parameter added on December 8, 2021. Previously, it would 8057 always block until the application quit which could cause bizarre behavior 8058 inside a more complex application. Now, the default is to block until 8059 this window closes if it is the only event loop running, and otherwise, 8060 not to block at all and just pop up the display window asynchronously. 8061 +/ 8062 void displayImage(Image image, SimpleWindow win = null, BlockingMode bm = BlockingMode.untilWindowCloses | BlockingMode.onlyIfNotNested) { 8063 if(win is null) { 8064 win = new SimpleWindow(image); 8065 { 8066 auto p = win.draw; 8067 p.drawImage(Point(0, 0), image); 8068 } 8069 win.eventLoopWithBlockingMode( 8070 bm, 0, 8071 (KeyEvent ev) { 8072 if (ev.pressed && (ev.key == Key.Escape || ev.key == Key.Space)) win.close(); 8073 } ); 8074 } else { 8075 win.image = image; 8076 } 8077 } 8078 8079 enum FontWeight : int { 8080 dontcare = 0, 8081 thin = 100, 8082 extralight = 200, 8083 light = 300, 8084 regular = 400, 8085 medium = 500, 8086 semibold = 600, 8087 bold = 700, 8088 extrabold = 800, 8089 heavy = 900 8090 } 8091 8092 /++ 8093 Interface with the common functionality for font measurements between [OperatingSystemFont] and [DrawableFont]. 8094 8095 History: 8096 Added October 24, 2022. The methods were already on [OperatingSystemFont] before that. 8097 +/ 8098 interface MeasurableFont { 8099 /++ 8100 Returns true if it is a monospace font, meaning each of the 8101 glyphs (at least the ascii characters) have matching width 8102 and no kerning, so you can determine the display width of some 8103 strings by simply multiplying the string width by [averageWidth]. 8104 8105 (Please note that multiply doesn't $(I actually) work in general, 8106 consider characters like tab and newline, but it does sometimes.) 8107 +/ 8108 bool isMonospace(); 8109 8110 /++ 8111 The average width of glyphs in the font, traditionally equal to the 8112 width of the lowercase x. Can be used to estimate bounding boxes, 8113 especially if the font [isMonospace]. 8114 8115 Given in pixels. 8116 +/ 8117 int averageWidth(); 8118 /++ 8119 The height of the bounding box of a line. 8120 +/ 8121 int height(); 8122 /++ 8123 The maximum ascent of a glyph above the baseline. 8124 8125 Given in pixels. 8126 +/ 8127 int ascent(); 8128 /++ 8129 The maximum descent of a glyph below the baseline. For example, how low the g might go. 8130 8131 Given in pixels. 8132 +/ 8133 int descent(); 8134 /++ 8135 The display width of the given string, and if you provide a window, it will use it to 8136 make the pixel count on screen more accurate too, but this shouldn't generally be necessary. 8137 8138 Given in pixels. 8139 +/ 8140 int stringWidth(scope const(char)[] s, SimpleWindow window = null); 8141 8142 } 8143 8144 // FIXME: i need a font cache and it needs to handle disconnects. 8145 8146 /++ 8147 Represents a font loaded off the operating system or the X server. 8148 8149 8150 While the api here is unified cross platform, the fonts are not necessarily 8151 available, even across machines of the same platform, so be sure to always check 8152 for null (using [isNull]) and have a fallback plan. 8153 8154 When you have a font you like, use [ScreenPainter.setFont] to load it for drawing. 8155 8156 Worst case, a null font will automatically fall back to the default font loaded 8157 for your system. 8158 +/ 8159 class OperatingSystemFont : MeasurableFont { 8160 // FIXME: when the X Connection is lost, these need to be invalidated! 8161 // that means I need to store the original stuff again to reconstruct it too. 8162 8163 version(X11) { 8164 XFontStruct* font; 8165 XFontSet fontset; 8166 8167 version(with_xft) { 8168 XftFont* xftFont; 8169 bool isXft; 8170 } 8171 } else version(Windows) { 8172 HFONT font; 8173 int width_; 8174 int height_; 8175 } else version(OSXCocoa) { 8176 // FIXME 8177 } else static assert(0); 8178 8179 /++ 8180 Constructs the class and immediately calls [load]. 8181 +/ 8182 this(string name, int size = 0, FontWeight weight = FontWeight.dontcare, bool italic = false) { 8183 load(name, size, weight, italic); 8184 } 8185 8186 /++ 8187 Constructs the object, but does nothing. Call one of [load] or [loadDefault] to populate the object. 8188 8189 You can also call the platform-specific [loadXft], [loadCoreX], or [loadWin32] functions if appropriate for you. 8190 8191 History: 8192 Added January 24, 2021. 8193 +/ 8194 this() { 8195 // this space intentionally left blank 8196 } 8197 8198 /++ 8199 Constructs a copy of the given font object. 8200 8201 History: 8202 Added January 7, 2023. 8203 +/ 8204 this(OperatingSystemFont font) { 8205 if(font is null || font.loadedInfo is LoadedInfo.init) 8206 loadDefault(); 8207 else 8208 load(font.loadedInfo.tupleof); 8209 } 8210 8211 /++ 8212 Loads specifically with the Xft library - a freetype font from a fontconfig string. 8213 8214 History: 8215 Added November 13, 2020. 8216 +/ 8217 version(with_xft) 8218 bool loadXft(string name, int size = 0, FontWeight weight = FontWeight.dontcare, bool italic = false) { 8219 unload(); 8220 8221 if(!XftLibrary.attempted) { 8222 XftLibrary.loadDynamicLibrary(); 8223 } 8224 8225 if(!XftLibrary.loadSuccessful) 8226 return false; 8227 8228 auto display = XDisplayConnection.get; 8229 8230 char[256] nameBuffer = void; 8231 int nbp = 0; 8232 8233 void add(in char[] a) { 8234 nameBuffer[nbp .. nbp + a.length] = a[]; 8235 nbp += a.length; 8236 } 8237 add(name); 8238 8239 if(size) { 8240 add(":size="); 8241 add(toInternal!string(size)); 8242 } 8243 if(weight != FontWeight.dontcare) { 8244 add(":weight="); 8245 add(weightToString(weight)); 8246 } 8247 if(italic) 8248 add(":slant=100"); 8249 8250 nameBuffer[nbp] = 0; 8251 8252 this.xftFont = XftFontOpenName( 8253 display, 8254 DefaultScreen(display), 8255 nameBuffer.ptr 8256 ); 8257 8258 this.isXft = true; 8259 8260 if(xftFont !is null) { 8261 isMonospace_ = stringWidth("x") == stringWidth("M"); 8262 ascent_ = xftFont.ascent; 8263 descent_ = xftFont.descent; 8264 } 8265 8266 return !isNull(); 8267 } 8268 8269 /++ 8270 Lists available fonts from the system that match the given pattern, finding names that are suitable for passing to [OperatingSystemFont]'s constructor. 8271 8272 8273 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. 8274 8275 If `pattern` is null, it returns all available font families. 8276 8277 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. 8278 8279 The format of the pattern is platform-specific. 8280 8281 History: 8282 Added May 1, 2021 (dub v9.5) 8283 +/ 8284 static void listFonts(string pattern, bool delegate(in char[] name) handler) { 8285 version(Windows) { 8286 auto hdc = GetDC(null); 8287 scope(exit) ReleaseDC(null, hdc); 8288 LOGFONT logfont; 8289 static extern(Windows) int proc(const LOGFONT* lf, const TEXTMETRIC* tm, DWORD type, LPARAM p) { 8290 auto localHandler = *(cast(typeof(handler)*) p); 8291 return localHandler(lf.lfFaceName[].sliceCString) ? 1 : 0; 8292 } 8293 EnumFontFamiliesEx(hdc, &logfont, &proc, cast(LPARAM) &handler, 0); 8294 } else version(X11) { 8295 //import core.stdc.stdio; 8296 bool done = false; 8297 version(with_xft) { 8298 if(!XftLibrary.attempted) { 8299 XftLibrary.loadDynamicLibrary(); 8300 } 8301 8302 if(!XftLibrary.loadSuccessful) 8303 goto skipXft; 8304 8305 if(!FontConfigLibrary.attempted) 8306 FontConfigLibrary.loadDynamicLibrary(); 8307 if(!FontConfigLibrary.loadSuccessful) 8308 goto skipXft; 8309 8310 { 8311 auto got = XftListFonts(XDisplayConnection.get, 0, null, "family".ptr, "style".ptr, null); 8312 if(got is null) 8313 goto skipXft; 8314 scope(exit) FcFontSetDestroy(got); 8315 8316 auto fontPatterns = got.fonts[0 .. got.nfont]; 8317 foreach(candidate; fontPatterns) { 8318 char* where, whereStyle; 8319 8320 char* pmg = FcNameUnparse(candidate); 8321 8322 //FcPatternGetString(candidate, "family", 0, &where); 8323 //FcPatternGetString(candidate, "style", 0, &whereStyle); 8324 //if(where && whereStyle) { 8325 if(pmg) { 8326 if(!handler(pmg.sliceCString)) 8327 return; 8328 //printf("%s || %s %s\n", pmg, where, whereStyle); 8329 } 8330 } 8331 } 8332 } 8333 8334 skipXft: 8335 8336 if(pattern is null) 8337 pattern = "*"; 8338 8339 int count; 8340 auto coreFontsRaw = XListFonts(XDisplayConnection.get, pattern.toStringz, 10000 /* max return */, &count); 8341 scope(exit) XFreeFontNames(coreFontsRaw); 8342 8343 auto coreFonts = coreFontsRaw[0 .. count]; 8344 8345 foreach(font; coreFonts) { 8346 char[128] tmp; 8347 tmp[0 ..5] = "core:"; 8348 auto cf = font.sliceCString; 8349 if(5 + cf.length > tmp.length) 8350 assert(0, "a font name was too long, sorry i didn't bother implementing a fallback"); 8351 tmp[5 .. 5 + cf.length] = cf; 8352 if(!handler(tmp[0 .. 5 + cf.length])) 8353 return; 8354 } 8355 } 8356 } 8357 8358 /++ 8359 Returns the raw content of the ttf file, if possible. This allows you to use OperatingSystemFont 8360 to look up fonts that you then pass to things like [arsd.ttf.OpenGlLimitedFont] or [arsd.nanovega]. 8361 8362 Returns null if impossible. It is impossible if the loaded font is not a local TTF file or if the 8363 underlying system doesn't support returning the raw bytes. 8364 8365 History: 8366 Added September 10, 2021 (dub v10.3) 8367 +/ 8368 ubyte[] getTtfBytes() { 8369 if(isNull) 8370 return null; 8371 8372 version(Windows) { 8373 auto dc = GetDC(null); 8374 auto orig = SelectObject(dc, font); 8375 8376 scope(exit) { 8377 SelectObject(dc, orig); 8378 ReleaseDC(null, dc); 8379 } 8380 8381 auto res = GetFontData(dc, 0 /* whole file */, 0 /* offset */, null, 0); 8382 if(res == GDI_ERROR) 8383 return null; 8384 8385 ubyte[] buffer = new ubyte[](res); 8386 res = GetFontData(dc, 0 /* whole file */, 0 /* offset */, buffer.ptr, cast(DWORD) buffer.length); 8387 if(res == GDI_ERROR) 8388 return null; // wtf really tbh 8389 8390 return buffer; 8391 } else version(with_xft) { 8392 if(isXft && xftFont) { 8393 if(!FontConfigLibrary.attempted) 8394 FontConfigLibrary.loadDynamicLibrary(); 8395 if(!FontConfigLibrary.loadSuccessful) 8396 return null; 8397 8398 char* file; 8399 if (FcPatternGetString(xftFont.pattern, "file", 0, &file) == 0 /*FcResultMatch*/) { 8400 if (file !is null && file[0]) { 8401 import core.stdc.stdio; 8402 auto fp = fopen(file, "rb"); 8403 if(fp is null) 8404 return null; 8405 scope(exit) 8406 fclose(fp); 8407 fseek(fp, 0, SEEK_END); 8408 ubyte[] buffer = new ubyte[](ftell(fp)); 8409 fseek(fp, 0, SEEK_SET); 8410 8411 auto got = fread(buffer.ptr, 1, buffer.length, fp); 8412 if(got != buffer.length) 8413 return null; 8414 8415 return buffer; 8416 } 8417 } 8418 } 8419 return null; 8420 } 8421 } 8422 8423 // see also: XftLockFace(font) which gives a FT_Face. from /usr/include/X11/Xft/Xft.h line 352 8424 8425 private string weightToString(FontWeight weight) { 8426 with(FontWeight) 8427 final switch(weight) { 8428 case dontcare: return "*"; 8429 case thin: return "extralight"; 8430 case extralight: return "extralight"; 8431 case light: return "light"; 8432 case regular: return "regular"; 8433 case medium: return "medium"; 8434 case semibold: return "demibold"; 8435 case bold: return "bold"; 8436 case extrabold: return "demibold"; 8437 case heavy: return "black"; 8438 } 8439 } 8440 8441 /++ 8442 Loads specifically a Core X font - rendered on the X server without antialiasing. Best performance. 8443 8444 History: 8445 Added November 13, 2020. Before then, this code was integrated in the [load] function. 8446 +/ 8447 version(X11) 8448 bool loadCoreX(string name, int size = 0, FontWeight weight = FontWeight.dontcare, bool italic = false) { 8449 unload(); 8450 8451 string xfontstr; 8452 8453 if(name.length > 3 && name[0 .. 3] == "-*-") { 8454 // this is kinda a disgusting hack but if the user sends an exact 8455 // string I'd like to honor it... 8456 xfontstr = name; 8457 } else { 8458 string weightstr = weightToString(weight); 8459 string sizestr; 8460 if(size == 0) 8461 sizestr = "*"; 8462 else 8463 sizestr = toInternal!string(size); 8464 xfontstr = "-*-"~name~"-"~weightstr~"-"~(italic ? "i" : "r")~"-*-*-"~sizestr~"-*-*-*-*-*-*-*\0"; 8465 } 8466 8467 // writeln(xfontstr); 8468 8469 auto display = XDisplayConnection.get; 8470 8471 font = XLoadQueryFont(display, xfontstr.ptr); 8472 if(font is null) 8473 return false; 8474 8475 char** lol; 8476 int lol2; 8477 char* lol3; 8478 fontset = XCreateFontSet(display, xfontstr.ptr, &lol, &lol2, &lol3); 8479 8480 prepareFontInfo(); 8481 8482 return !isNull(); 8483 } 8484 8485 version(X11) 8486 private void prepareFontInfo() { 8487 if(font !is null) { 8488 isMonospace_ = stringWidth("l") == stringWidth("M"); 8489 ascent_ = font.max_bounds.ascent; 8490 descent_ = font.max_bounds.descent; 8491 } 8492 } 8493 8494 /++ 8495 Loads a Windows font. You probably want to use [load] instead to be more generic. 8496 8497 History: 8498 Added November 13, 2020. Before then, this code was integrated in the [load] function. 8499 +/ 8500 version(Windows) 8501 bool loadWin32(string name, int size = 0, FontWeight weight = FontWeight.dontcare, bool italic = false, HDC hdc = null) { 8502 unload(); 8503 8504 WCharzBuffer buffer = WCharzBuffer(name); 8505 font = CreateFont(size, 0, 0, 0, cast(int) weight, italic, 0, 0, 0, 0, 0, 0, 0, buffer.ptr); 8506 8507 prepareFontInfo(hdc); 8508 8509 return !isNull(); 8510 } 8511 8512 version(Windows) 8513 void prepareFontInfo(HDC hdc = null) { 8514 if(font is null) 8515 return; 8516 8517 TEXTMETRIC tm; 8518 auto dc = hdc ? hdc : GetDC(null); 8519 auto orig = SelectObject(dc, font); 8520 GetTextMetrics(dc, &tm); 8521 SelectObject(dc, orig); 8522 if(hdc is null) 8523 ReleaseDC(null, dc); 8524 8525 width_ = tm.tmAveCharWidth; 8526 height_ = tm.tmHeight; 8527 ascent_ = tm.tmAscent; 8528 descent_ = tm.tmDescent; 8529 // 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. 8530 isMonospace_ = (tm.tmPitchAndFamily & TMPF_FIXED_PITCH) == 0; 8531 } 8532 8533 8534 /++ 8535 `name` is a font name, but it can also be a more complicated string parsed in an OS-specific way. 8536 8537 On X, you may prefix a name with `core:` to bypass the freetype engine causing this function to forward to [loadCoreX]. Otherwise, 8538 it calls [loadXft] if the library is available. If the library or font is not available on Xft, it falls back on [loadCoreX]. 8539 8540 On Windows, it forwards directly to [loadWin32]. 8541 8542 Params: 8543 name = font name. This is looked up by the operating system and may be interpreted differently across platforms or user machines and their preferences. 8544 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. 8545 weight = approximate boldness, results may vary. 8546 italic = try to get a slanted version of the given font. 8547 8548 History: 8549 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. 8550 +/ 8551 bool load(string name, int size = 0, FontWeight weight = FontWeight.dontcare, bool italic = false) { 8552 this.loadedInfo = LoadedInfo(name, size, weight, italic); 8553 version(X11) { 8554 version(with_xft) { 8555 if(name.length > 5 && name[0 .. 5] == "core:") { 8556 goto core; 8557 } 8558 8559 if(loadXft(name, size, weight, italic)) 8560 return true; 8561 // if xft fails, fallback to core to avoid breaking 8562 // code that already depended on this. 8563 } 8564 8565 core: 8566 8567 if(name.length > 5 && name[0 .. 5] == "core:") { 8568 name = name[5 .. $]; 8569 } 8570 8571 return loadCoreX(name, size, weight, italic); 8572 } else version(Windows) { 8573 return loadWin32(name, size, weight, italic); 8574 } else version(OSXCocoa) { 8575 // FIXME 8576 return false; 8577 } else static assert(0); 8578 } 8579 8580 private struct LoadedInfo { 8581 string name; 8582 int size; 8583 FontWeight weight; 8584 bool italic; 8585 } 8586 private LoadedInfo loadedInfo; 8587 8588 /// 8589 void unload() { 8590 if(isNull()) 8591 return; 8592 8593 version(X11) { 8594 auto display = XDisplayConnection.display; 8595 8596 if(display is null) 8597 return; 8598 8599 version(with_xft) { 8600 if(isXft) { 8601 if(xftFont) 8602 XftFontClose(display, xftFont); 8603 isXft = false; 8604 xftFont = null; 8605 return; 8606 } 8607 } 8608 8609 if(font && font !is ScreenPainterImplementation.defaultfont) 8610 XFreeFont(display, font); 8611 if(fontset && fontset !is ScreenPainterImplementation.defaultfontset) 8612 XFreeFontSet(display, fontset); 8613 8614 font = null; 8615 fontset = null; 8616 } else version(Windows) { 8617 DeleteObject(font); 8618 font = null; 8619 } else version(OSXCocoa) { 8620 // FIXME 8621 } else static assert(0); 8622 } 8623 8624 private bool isMonospace_; 8625 8626 /++ 8627 History: 8628 Added January 16, 2021 8629 +/ 8630 bool isMonospace() { 8631 return isMonospace_; 8632 } 8633 8634 /++ 8635 Returns the average width of the font, conventionally defined as the width of the lowercase 'x' character. 8636 8637 History: 8638 Added March 26, 2020 8639 Documented January 16, 2021 8640 +/ 8641 int averageWidth() { 8642 version(X11) { 8643 return stringWidth("x"); 8644 } else version(Windows) 8645 return width_; 8646 else assert(0); 8647 } 8648 8649 /++ 8650 Returns the width of the string as drawn on the specified window, or the default screen if the window is null. 8651 8652 History: 8653 Added January 16, 2021 8654 +/ 8655 int stringWidth(scope const(char)[] s, SimpleWindow window = null) { 8656 // FIXME: what about tab? 8657 if(isNull) 8658 return 0; 8659 8660 version(X11) { 8661 version(with_xft) 8662 if(isXft && xftFont !is null) { 8663 //return xftFont.max_advance_width; 8664 XGlyphInfo extents; 8665 XftTextExtentsUtf8(XDisplayConnection.get, xftFont, s.ptr, cast(int) s.length, &extents); 8666 // writeln(extents); 8667 return extents.xOff; 8668 } 8669 if(font is null) 8670 return 0; 8671 else if(fontset) { 8672 XRectangle rect; 8673 Xutf8TextExtents(fontset, s.ptr, cast(int) s.length, null, &rect); 8674 8675 return rect.width; 8676 } else { 8677 return XTextWidth(font, s.ptr, cast(int) s.length); 8678 } 8679 } else version(Windows) { 8680 WCharzBuffer buffer = WCharzBuffer(s); 8681 8682 return stringWidth(buffer.slice, window); 8683 } 8684 else assert(0); 8685 } 8686 8687 version(Windows) 8688 /// ditto 8689 int stringWidth(scope const(wchar)[] s, SimpleWindow window = null) { 8690 if(isNull) 8691 return 0; 8692 version(Windows) { 8693 SIZE size; 8694 8695 prepareContext(window); 8696 scope(exit) releaseContext(); 8697 8698 GetTextExtentPoint32W(dc, s.ptr, cast(int) s.length, &size); 8699 8700 return size.cx; 8701 } else { 8702 // std.conv can do this easily but it is slow to import and i don't think it is worth it 8703 static assert(0, "not implemented yet"); 8704 //return stringWidth(s, window); 8705 } 8706 } 8707 8708 private { 8709 int prepRefcount; 8710 8711 version(Windows) { 8712 HDC dc; 8713 HANDLE orig; 8714 HWND hwnd; 8715 } 8716 } 8717 /++ 8718 [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. 8719 8720 History: 8721 Added January 23, 2021 8722 +/ 8723 void prepareContext(SimpleWindow window = null) { 8724 prepRefcount++; 8725 if(prepRefcount == 1) { 8726 version(Windows) { 8727 hwnd = window is null ? null : window.impl.hwnd; 8728 dc = GetDC(hwnd); 8729 orig = SelectObject(dc, font); 8730 } 8731 } 8732 } 8733 /// ditto 8734 void releaseContext() { 8735 prepRefcount--; 8736 if(prepRefcount == 0) { 8737 version(Windows) { 8738 SelectObject(dc, orig); 8739 ReleaseDC(hwnd, dc); 8740 hwnd = null; 8741 dc = null; 8742 orig = null; 8743 } 8744 } 8745 } 8746 8747 /+ 8748 FIXME: I think I need advance and kerning pair 8749 8750 int advance(dchar from, dchar to) { } // use dchar.init for first item in string 8751 +/ 8752 8753 /++ 8754 Returns the height of the font. 8755 8756 History: 8757 Added March 26, 2020 8758 Documented January 16, 2021 8759 +/ 8760 int height() { 8761 version(X11) { 8762 version(with_xft) 8763 if(isXft && xftFont !is null) { 8764 return xftFont.ascent + xftFont.descent; // i don't use height here because it doesn't include the baseline pixel 8765 } 8766 if(font is null) 8767 return 0; 8768 return font.max_bounds.ascent + font.max_bounds.descent; 8769 } else version(Windows) 8770 return height_; 8771 else assert(0); 8772 } 8773 8774 private int ascent_; 8775 private int descent_; 8776 8777 /++ 8778 Max ascent above the baseline. 8779 8780 History: 8781 Added January 22, 2021 8782 +/ 8783 int ascent() { 8784 return ascent_; 8785 } 8786 8787 /++ 8788 Max descent below the baseline. 8789 8790 History: 8791 Added January 22, 2021 8792 +/ 8793 int descent() { 8794 return descent_; 8795 } 8796 8797 /++ 8798 Loads the default font used by [ScreenPainter] if none others are loaded. 8799 8800 Returns: 8801 This method mutates the `this` object, but then returns `this` for 8802 easy chaining like: 8803 8804 --- 8805 auto font = foo.isNull ? foo : foo.loadDefault 8806 --- 8807 8808 History: 8809 Added previously, but left unimplemented until January 24, 2021. 8810 +/ 8811 OperatingSystemFont loadDefault() { 8812 unload(); 8813 8814 loadedInfo = LoadedInfo.init; 8815 8816 version(X11) { 8817 // another option would be https://tronche.com/gui/x/xlib/graphics/font-metrics/XQueryFont.html 8818 // but meh since sdpy does its own thing, this should be ok too 8819 8820 ScreenPainterImplementation.ensureDefaultFontLoaded(); 8821 this.font = ScreenPainterImplementation.defaultfont; 8822 this.fontset = ScreenPainterImplementation.defaultfontset; 8823 8824 prepareFontInfo(); 8825 } else version(Windows) { 8826 ScreenPainterImplementation.ensureDefaultFontLoaded(); 8827 this.font = ScreenPainterImplementation.defaultGuiFont; 8828 8829 prepareFontInfo(); 8830 } else throw new NotYetImplementedException(); 8831 8832 return this; 8833 } 8834 8835 /// 8836 bool isNull() { 8837 version(OSXCocoa) throw new NotYetImplementedException(); else { 8838 version(with_xft) 8839 if(isXft) 8840 return xftFont is null; 8841 return font is null; 8842 } 8843 } 8844 8845 /* Metrics */ 8846 /+ 8847 GetABCWidth 8848 GetKerningPairs 8849 8850 if I do it right, I can size it all here, and match 8851 what happens when I draw the full string with the OS functions. 8852 8853 subclasses might do the same thing while getting the glyphs on images 8854 struct GlyphInfo { 8855 int glyph; 8856 8857 size_t stringIdxStart; 8858 size_t stringIdxEnd; 8859 8860 Rectangle boundingBox; 8861 } 8862 GlyphInfo[] getCharBoxes() { 8863 // XftTextExtentsUtf8 8864 return null; 8865 8866 } 8867 +/ 8868 8869 ~this() { 8870 if(!thisIsGuiThread) return; // FIXME: leaks if multithreaded gc 8871 unload(); 8872 } 8873 } 8874 8875 version(Windows) 8876 private string sliceCString(const(wchar)[] w) { 8877 return makeUtf8StringFromWindowsString(cast(wchar*) w.ptr); 8878 } 8879 8880 private inout(char)[] sliceCString(inout(char)* s) { 8881 import core.stdc.string; 8882 auto len = strlen(s); 8883 return s[0 .. len]; 8884 } 8885 8886 /** 8887 The 2D drawing proxy. You acquire one of these with [SimpleWindow.draw] rather 8888 than constructing it directly. Then, it is reference counted so you can pass it 8889 at around and when the last ref goes out of scope, the buffered drawing activities 8890 are all carried out. 8891 8892 8893 Most functions use the outlineColor instead of taking a color themselves. 8894 ScreenPainter is reference counted and draws its buffer to the screen when its 8895 final reference goes out of scope. 8896 */ 8897 struct ScreenPainter { 8898 CapableOfBeingDrawnUpon window; 8899 this(CapableOfBeingDrawnUpon window, NativeWindowHandle handle, bool manualInvalidations) { 8900 this.window = window; 8901 if(window.closed) 8902 return; // null painter is now allowed so no need to throw anymore, this likely happens at the end of a program anyway 8903 //currentClipRectangle = arsd.color.Rectangle(0, 0, window.width, window.height); 8904 currentClipRectangle = arsd.color.Rectangle(short.min, short.min, short.max, short.max); 8905 if(window.activeScreenPainter !is null) { 8906 impl = window.activeScreenPainter; 8907 if(impl.referenceCount == 0) { 8908 impl.window = window; 8909 impl.create(handle); 8910 } 8911 impl.manualInvalidations = manualInvalidations; 8912 impl.referenceCount++; 8913 // writeln("refcount ++ ", impl.referenceCount); 8914 } else { 8915 impl = new ScreenPainterImplementation; 8916 impl.window = window; 8917 impl.create(handle); 8918 impl.referenceCount = 1; 8919 impl.manualInvalidations = manualInvalidations; 8920 window.activeScreenPainter = impl; 8921 // writeln("constructed"); 8922 } 8923 8924 copyActiveOriginals(); 8925 } 8926 8927 /++ 8928 EXPERIMENTAL. subject to change. 8929 8930 When you draw a cursor, you can draw this to notify your window of where it is, 8931 for IME systems to use. 8932 +/ 8933 void notifyCursorPosition(int x, int y, int width, int height) { 8934 if(auto w = cast(SimpleWindow) window) { 8935 w.setIMEPopupLocation(x + _originX + width, y + _originY + height); 8936 } 8937 } 8938 8939 /++ 8940 If you are using manual invalidations, this informs the 8941 window system that a section needs to be redrawn. 8942 8943 If you didn't opt into manual invalidation, you don't 8944 have to call this. 8945 8946 History: 8947 Added December 30, 2021 (dub v10.5) 8948 +/ 8949 void invalidateRect(Rectangle rect) { 8950 if(impl is null) return; 8951 8952 // transform(rect) 8953 rect.left += _originX; 8954 rect.right += _originX; 8955 rect.top += _originY; 8956 rect.bottom += _originY; 8957 8958 impl.invalidateRect(rect); 8959 } 8960 8961 private Pen originalPen; 8962 private Color originalFillColor; 8963 private arsd.color.Rectangle originalClipRectangle; 8964 private OperatingSystemFont originalFont; 8965 void copyActiveOriginals() { 8966 if(impl is null) return; 8967 originalPen = impl._activePen; 8968 originalFillColor = impl._fillColor; 8969 originalClipRectangle = impl._clipRectangle; 8970 originalFont = impl._activeFont; 8971 } 8972 8973 ~this() { 8974 if(impl is null) return; 8975 impl.referenceCount--; 8976 //writeln("refcount -- ", impl.referenceCount); 8977 if(impl.referenceCount == 0) { 8978 // writeln("destructed"); 8979 impl.dispose(); 8980 *window.activeScreenPainter = ScreenPainterImplementation.init; 8981 // writeln("paint finished"); 8982 } else { 8983 // there is still an active reference, reset stuff so the 8984 // next user doesn't get weirdness via the reference 8985 this.rasterOp = RasterOp.normal; 8986 pen = originalPen; 8987 fillColor = originalFillColor; 8988 if(originalFont) 8989 setFont(originalFont); 8990 impl.setClipRectangle(originalClipRectangle.left, originalClipRectangle.top, originalClipRectangle.width, originalClipRectangle.height); 8991 } 8992 } 8993 8994 this(this) { 8995 if(impl is null) return; 8996 impl.referenceCount++; 8997 //writeln("refcount ++ ", impl.referenceCount); 8998 8999 copyActiveOriginals(); 9000 } 9001 9002 private int _originX; 9003 private int _originY; 9004 @property int originX() { return _originX; } 9005 @property int originY() { return _originY; } 9006 @property int originX(int a) { 9007 _originX = a; 9008 return _originX; 9009 } 9010 @property int originY(int a) { 9011 _originY = a; 9012 return _originY; 9013 } 9014 arsd.color.Rectangle currentClipRectangle; // set BEFORE doing any transformations 9015 private void transform(ref Point p) { 9016 if(impl is null) return; 9017 p.x += _originX; 9018 p.y += _originY; 9019 } 9020 9021 // this needs to be checked BEFORE the originX/Y transformation 9022 private bool isClipped(Point p) { 9023 return !currentClipRectangle.contains(p); 9024 } 9025 private bool isClipped(Point p, int width, int height) { 9026 return !currentClipRectangle.overlaps(arsd.color.Rectangle(p, Size(width + 1, height + 1))); 9027 } 9028 private bool isClipped(Point p, Size s) { 9029 return !currentClipRectangle.overlaps(arsd.color.Rectangle(p, Size(s.width + 1, s.height + 1))); 9030 } 9031 private bool isClipped(Point p, Point p2) { 9032 // need to ensure the end points are actually included inside, so the +1 does that 9033 return !currentClipRectangle.overlaps(arsd.color.Rectangle(p, p2 + Point(1, 1))); 9034 } 9035 9036 9037 /++ 9038 Sets the clipping region for drawing. If width == 0 && height == 0, disabled clipping. 9039 9040 Returns: 9041 The old clip rectangle. 9042 9043 History: 9044 Return value was `void` prior to May 10, 2021. 9045 9046 +/ 9047 arsd.color.Rectangle setClipRectangle(Point pt, int width, int height) { 9048 if(impl is null) return currentClipRectangle; 9049 if(pt == currentClipRectangle.upperLeft && width == currentClipRectangle.width && height == currentClipRectangle.height) 9050 return currentClipRectangle; // no need to do anything 9051 auto old = currentClipRectangle; 9052 currentClipRectangle = arsd.color.Rectangle(pt, Size(width, height)); 9053 transform(pt); 9054 9055 impl.setClipRectangle(pt.x, pt.y, width, height); 9056 9057 return old; 9058 } 9059 9060 /// ditto 9061 arsd.color.Rectangle setClipRectangle(arsd.color.Rectangle rect) { 9062 if(impl is null) return currentClipRectangle; 9063 return setClipRectangle(rect.upperLeft, rect.width, rect.height); 9064 } 9065 9066 /// 9067 void setFont(OperatingSystemFont font) { 9068 if(impl is null) return; 9069 impl.setFont(font); 9070 } 9071 9072 /// 9073 int fontHeight() { 9074 if(impl is null) return 0; 9075 return impl.fontHeight(); 9076 } 9077 9078 private Pen activePen; 9079 9080 /// 9081 @property void pen(Pen p) { 9082 if(impl is null) return; 9083 activePen = p; 9084 impl.pen(p); 9085 } 9086 9087 /// 9088 @scriptable 9089 @property void outlineColor(Color c) { 9090 if(impl is null) return; 9091 if(activePen.color == c) 9092 return; 9093 activePen.color = c; 9094 impl.pen(activePen); 9095 } 9096 9097 /// 9098 @scriptable 9099 @property void fillColor(Color c) { 9100 if(impl is null) return; 9101 impl.fillColor(c); 9102 } 9103 9104 /// 9105 @property void rasterOp(RasterOp op) { 9106 if(impl is null) return; 9107 impl.rasterOp(op); 9108 } 9109 9110 9111 void updateDisplay() { 9112 // FIXME this should do what the dtor does 9113 } 9114 9115 /// 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) 9116 void scrollArea(Point upperLeft, int width, int height, int dx, int dy) { 9117 if(impl is null) return; 9118 if(isClipped(upperLeft, width, height)) return; 9119 transform(upperLeft); 9120 version(Windows) { 9121 // http://msdn.microsoft.com/en-us/library/windows/desktop/bb787589%28v=vs.85%29.aspx 9122 RECT scroll = RECT(upperLeft.x, upperLeft.y, upperLeft.x + width, upperLeft.y + height); 9123 RECT clip = scroll; 9124 RECT uncovered; 9125 HRGN hrgn; 9126 if(!ScrollDC(impl.hdc, -dx, -dy, &scroll, &clip, hrgn, &uncovered)) 9127 throw new WindowsApiException("ScrollDC", GetLastError()); 9128 9129 } else version(X11) { 9130 // FIXME: clip stuff outside this rectangle 9131 XCopyArea(impl.display, impl.d, impl.d, impl.gc, upperLeft.x, upperLeft.y, width, height, upperLeft.x - dx, upperLeft.y - dy); 9132 } else version(OSXCocoa) { 9133 throw new NotYetImplementedException(); 9134 } else static assert(0); 9135 } 9136 9137 /// 9138 void clear(Color color = Color.white()) { 9139 if(impl is null) return; 9140 fillColor = color; 9141 outlineColor = color; 9142 drawRectangle(Point(0, 0), window.width, window.height); 9143 } 9144 9145 /++ 9146 Draws a pixmap (represented by the [Sprite] class) on the drawable. 9147 9148 Params: 9149 upperLeft = point on the window where the upper left corner of the image will be drawn 9150 imageUpperLeft = point on the image to start the slice to draw 9151 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. 9152 History: 9153 The `imageUpperLeft` and `sliceSize` parameters were added on March 11, 2021 (dub v9.3.0) 9154 +/ 9155 version(OSXCocoa) {} else // NotYetImplementedException 9156 void drawPixmap(Sprite s, Point upperLeft, Point imageUpperLeft = Point(0, 0), Size sliceSize = Size(0, 0)) { 9157 if(impl is null) return; 9158 if(isClipped(upperLeft, s.width, s.height)) return; 9159 transform(upperLeft); 9160 impl.drawPixmap(s, upperLeft.x, upperLeft.y, imageUpperLeft.x, imageUpperLeft.y, sliceSize.width, sliceSize.height); 9161 } 9162 9163 /// 9164 void drawImage(Point upperLeft, Image i, Point upperLeftOfImage = Point(0, 0), int w = 0, int h = 0) { 9165 if(impl is null) return; 9166 //if(isClipped(upperLeft, w, h)) return; // FIXME 9167 transform(upperLeft); 9168 if(w == 0 || w > i.width) 9169 w = i.width; 9170 if(h == 0 || h > i.height) 9171 h = i.height; 9172 if(upperLeftOfImage.x < 0) 9173 upperLeftOfImage.x = 0; 9174 if(upperLeftOfImage.y < 0) 9175 upperLeftOfImage.y = 0; 9176 9177 impl.drawImage(upperLeft.x, upperLeft.y, i, upperLeftOfImage.x, upperLeftOfImage.y, w, h); 9178 } 9179 9180 /// 9181 Size textSize(in char[] text) { 9182 if(impl is null) return Size(0, 0); 9183 return impl.textSize(text); 9184 } 9185 9186 /++ 9187 Draws a string in the window with the set font (see [setFont] to change it). 9188 9189 Params: 9190 upperLeft = the upper left point of the bounding box of the text 9191 text = the string to draw 9192 lowerRight = the lower right point of the bounding box of the text. If 0, 0, there is no lower right bound. 9193 alignment = A [arsd.docs.general_concepts#bitflags|combination] of [TextAlignment] flags 9194 +/ 9195 @scriptable 9196 void drawText(Point upperLeft, in char[] text, Point lowerRight = Point(0, 0), uint alignment = 0) { 9197 if(impl is null) return; 9198 if(lowerRight.x != 0 || lowerRight.y != 0) { 9199 if(isClipped(upperLeft, lowerRight)) return; 9200 transform(lowerRight); 9201 } else { 9202 if(isClipped(upperLeft, textSize(text))) return; 9203 } 9204 transform(upperLeft); 9205 impl.drawText(upperLeft.x, upperLeft.y, lowerRight.x, lowerRight.y, text, alignment); 9206 } 9207 9208 /++ 9209 Draws text using a custom font. 9210 9211 This is still MAJOR work in progress. 9212 9213 Creating a [DrawableFont] can be tricky and require additional dependencies. 9214 +/ 9215 void drawText(DrawableFont font, Point upperLeft, in char[] text) { 9216 if(impl is null) return; 9217 if(isClipped(upperLeft, Point(int.max, int.max))) return; 9218 transform(upperLeft); 9219 font.drawString(this, upperLeft, text); 9220 } 9221 9222 version(Windows) 9223 void drawText(Point upperLeft, scope const(wchar)[] text) { 9224 if(impl is null) return; 9225 if(isClipped(upperLeft, Point(int.max, int.max))) return; 9226 transform(upperLeft); 9227 9228 if(text.length && text[$-1] == '\n') 9229 text = text[0 .. $-1]; // tailing newlines are weird on windows... 9230 9231 TextOutW(impl.hdc, upperLeft.x, upperLeft.y, text.ptr, cast(int) text.length); 9232 } 9233 9234 static struct TextDrawingContext { 9235 Point boundingBoxUpperLeft; 9236 Point boundingBoxLowerRight; 9237 9238 Point currentLocation; 9239 9240 Point lastDrewUpperLeft; 9241 Point lastDrewLowerRight; 9242 9243 // how do i do right aligned rich text? 9244 // i kinda want to do a pre-made drawing then right align 9245 // draw the whole block. 9246 // 9247 // That's exactly the diff: inline vs block stuff. 9248 9249 // I need to get coordinates of an inline section out too, 9250 // not just a bounding box, but a series of bounding boxes 9251 // should be ok. Consider what's needed to detect a click 9252 // on a link in the middle of a paragraph breaking a line. 9253 // 9254 // Generally, we should be able to get the rectangles of 9255 // any portion we draw. 9256 // 9257 // It also needs to tell what text is left if it overflows 9258 // out of the box, so we can do stuff like float images around 9259 // it. It should not attempt to draw a letter that would be 9260 // clipped. 9261 // 9262 // I might also turn off word wrap stuff. 9263 } 9264 9265 void drawText(TextDrawingContext context, in char[] text, uint alignment = 0) { 9266 if(impl is null) return; 9267 // FIXME 9268 } 9269 9270 /// Drawing an individual pixel is slow. Avoid it if possible. 9271 void drawPixel(Point where) { 9272 if(impl is null) return; 9273 if(isClipped(where)) return; 9274 transform(where); 9275 impl.drawPixel(where.x, where.y); 9276 } 9277 9278 9279 /// Draws a pen using the current pen / outlineColor 9280 @scriptable 9281 void drawLine(Point starting, Point ending) { 9282 if(impl is null) return; 9283 if(isClipped(starting, ending)) return; 9284 transform(starting); 9285 transform(ending); 9286 impl.drawLine(starting.x, starting.y, ending.x, ending.y); 9287 } 9288 9289 /// Draws a rectangle using the current pen/outline color for the border and brush/fill color for the insides 9290 /// The outer lines, inclusive of x = 0, y = 0, x = width - 1, and y = height - 1 are drawn with the outlineColor 9291 /// The rest of the pixels are drawn with the fillColor. If fillColor is transparent, those pixels are not drawn. 9292 @scriptable 9293 void drawRectangle(Point upperLeft, int width, int height) { 9294 if(impl is null) return; 9295 if(isClipped(upperLeft, width, height)) return; 9296 transform(upperLeft); 9297 impl.drawRectangle(upperLeft.x, upperLeft.y, width, height); 9298 } 9299 9300 /// ditto 9301 void drawRectangle(Point upperLeft, Size size) { 9302 if(impl is null) return; 9303 if(isClipped(upperLeft, size.width, size.height)) return; 9304 transform(upperLeft); 9305 impl.drawRectangle(upperLeft.x, upperLeft.y, size.width, size.height); 9306 } 9307 9308 /// ditto 9309 void drawRectangle(Point upperLeft, Point lowerRightInclusive) { 9310 if(impl is null) return; 9311 if(isClipped(upperLeft, lowerRightInclusive + Point(1, 1))) return; 9312 transform(upperLeft); 9313 transform(lowerRightInclusive); 9314 impl.drawRectangle(upperLeft.x, upperLeft.y, 9315 lowerRightInclusive.x - upperLeft.x + 1, lowerRightInclusive.y - upperLeft.y + 1); 9316 } 9317 9318 // overload added on May 12, 2021 9319 /// ditto 9320 void drawRectangle(Rectangle rect) { 9321 drawRectangle(rect.upperLeft, rect.size); 9322 } 9323 9324 /// Arguments are the points of the bounding rectangle 9325 void drawEllipse(Point upperLeft, Point lowerRight) { 9326 if(impl is null) return; 9327 if(isClipped(upperLeft, lowerRight)) return; 9328 transform(upperLeft); 9329 transform(lowerRight); 9330 impl.drawEllipse(upperLeft.x, upperLeft.y, lowerRight.x, lowerRight.y); 9331 } 9332 9333 /++ 9334 start and finish are units of degrees * 64 9335 9336 History: 9337 The Windows implementation didn't match the Linux implementation until September 24, 2021. 9338 9339 They still don't exactly match in outlining the arc with straight lines (Windows does, Linux doesn't for now). 9340 +/ 9341 void drawArc(Point upperLeft, int width, int height, int start, int finish) { 9342 if(impl is null) return; 9343 // FIXME: not actually implemented 9344 if(isClipped(upperLeft, width, height)) return; 9345 transform(upperLeft); 9346 impl.drawArc(upperLeft.x, upperLeft.y, width, height, start, finish); 9347 } 9348 9349 /// this function draws a circle with the drawEllipse() function above, it requires the upper left point and the radius 9350 void drawCircle(Point upperLeft, int diameter) { 9351 drawEllipse(upperLeft, Point(upperLeft.x + diameter, upperLeft.y + diameter)); 9352 } 9353 9354 /// . 9355 void drawPolygon(Point[] vertexes) { 9356 if(impl is null) return; 9357 assert(vertexes.length); 9358 int minX = int.max, minY = int.max, maxX = int.min, maxY = int.min; 9359 foreach(ref vertex; vertexes) { 9360 if(vertex.x < minX) 9361 minX = vertex.x; 9362 if(vertex.y < minY) 9363 minY = vertex.y; 9364 if(vertex.x > maxX) 9365 maxX = vertex.x; 9366 if(vertex.y > maxY) 9367 maxY = vertex.y; 9368 transform(vertex); 9369 } 9370 if(isClipped(Point(minX, maxY), Point(maxX + 1, maxY + 1))) return; 9371 impl.drawPolygon(vertexes); 9372 } 9373 9374 /// ditto 9375 void drawPolygon(Point[] vertexes...) { 9376 if(impl is null) return; 9377 drawPolygon(vertexes); 9378 } 9379 9380 9381 // and do a draw/fill in a single call maybe. Windows can do it... but X can't, though it could do two calls. 9382 9383 //mixin NativeScreenPainterImplementation!() impl; 9384 9385 9386 // HACK: if I mixin the impl directly, it won't let me override the copy 9387 // constructor! The linker complains about there being multiple definitions. 9388 // I'll make the best of it and reference count it though. 9389 ScreenPainterImplementation* impl; 9390 } 9391 9392 // HACK: I need a pointer to the implementation so it's separate 9393 struct ScreenPainterImplementation { 9394 CapableOfBeingDrawnUpon window; 9395 int referenceCount; 9396 mixin NativeScreenPainterImplementation!(); 9397 } 9398 9399 // FIXME: i haven't actually tested the sprite class on MS Windows 9400 9401 /** 9402 Sprites are optimized for fast drawing on the screen, but slow for direct pixel 9403 access. They are best for drawing a relatively unchanging image repeatedly on the screen. 9404 9405 9406 On X11, this corresponds to an `XPixmap`. On Windows, it still uses a bitmap, 9407 though I'm not sure that's ideal and the implementation might change. 9408 9409 You create one by giving a window and an image. It optimizes for that window, 9410 and copies the image into it to use as the initial picture. Creating a sprite 9411 can be quite slow (especially over a network connection) so you should do it 9412 as little as possible and just hold on to your sprite handles after making them. 9413 simpledisplay does try to do its best though, using the XSHM extension if available, 9414 but you should still write your code as if it will always be slow. 9415 9416 Then you can use `sprite.drawAt(painter, point);` to draw it, which should be 9417 a fast operation - much faster than drawing the Image itself every time. 9418 9419 `Sprite` represents a scarce resource which should be freed when you 9420 are done with it. Use the `dispose` method to do this. Do not use a `Sprite` 9421 after it has been disposed. If you are unsure about this, don't take chances, 9422 just let the garbage collector do it for you. But ideally, you can manage its 9423 lifetime more efficiently. 9424 9425 $(NOTE `Sprite`, like the rest of simpledisplay's `ScreenPainter`, does not 9426 support alpha blending in its drawing at this time. That might change in the 9427 future, but if you need alpha blending right now, use OpenGL instead. See 9428 `gamehelpers.d` for a similar class to `Sprite` that uses OpenGL: `OpenGlTexture`.) 9429 9430 Update: on April 23, 2021, I finally added alpha blending support. You must opt 9431 in by setting the enableAlpha = true in the constructor. 9432 */ 9433 version(OSXCocoa) {} else // NotYetImplementedException 9434 class Sprite : CapableOfBeingDrawnUpon { 9435 9436 /// 9437 ScreenPainter draw() { 9438 return ScreenPainter(this, handle, false); 9439 } 9440 9441 /++ 9442 Copies the sprite's current state into a [TrueColorImage]. 9443 9444 Be warned: this can be a very slow operation 9445 9446 History: 9447 Actually implemented on March 14, 2021 9448 +/ 9449 TrueColorImage takeScreenshot() { 9450 return trueColorImageFromNativeHandle(handle, width, height); 9451 } 9452 9453 void delegate() paintingFinishedDg() { return null; } 9454 bool closed() { return false; } 9455 ScreenPainterImplementation* activeScreenPainter_; 9456 protected ScreenPainterImplementation* activeScreenPainter() { return activeScreenPainter_; } 9457 protected void activeScreenPainter(ScreenPainterImplementation* i) { activeScreenPainter_ = i; } 9458 9459 version(Windows) 9460 private ubyte* rawData; 9461 // FIXME: sprites are lost when disconnecting from X! We need some way to invalidate them... 9462 // ditto on the XPicture stuff 9463 9464 version(X11) { 9465 private static XRenderPictFormat* RGB24; 9466 private static XRenderPictFormat* ARGB32; 9467 9468 private Picture xrenderPicture; 9469 } 9470 9471 version(X11) 9472 private static void requireXRender() { 9473 if(!XRenderLibrary.loadAttempted) { 9474 XRenderLibrary.loadDynamicLibrary(); 9475 } 9476 9477 if(!XRenderLibrary.loadSuccessful) 9478 throw new Exception("XRender library load failure"); 9479 9480 auto display = XDisplayConnection.get; 9481 9482 // FIXME: if we migrate X displays, these need to be changed 9483 if(RGB24 is null) 9484 RGB24 = XRenderFindStandardFormat(display, PictStandardRGB24); 9485 if(ARGB32 is null) 9486 ARGB32 = XRenderFindStandardFormat(display, PictStandardARGB32); 9487 } 9488 9489 protected this() {} 9490 9491 this(SimpleWindow win, int width, int height, bool enableAlpha = false) { 9492 this._width = width; 9493 this._height = height; 9494 this.enableAlpha = enableAlpha; 9495 9496 version(X11) { 9497 auto display = XDisplayConnection.get(); 9498 9499 if(enableAlpha) { 9500 requireXRender(); 9501 } 9502 9503 handle = XCreatePixmap(display, cast(Drawable) win.window, width, height, enableAlpha ? 32 : DefaultDepthOfDisplay(display)); 9504 9505 if(enableAlpha) { 9506 XRenderPictureAttributes attrs; 9507 xrenderPicture = XRenderCreatePicture(display, handle, ARGB32, 0, &attrs); 9508 } 9509 } else version(Windows) { 9510 version(CRuntime_DigitalMars) { 9511 //if(enableAlpha) 9512 //throw new Exception("Alpha support not available, try recompiling with -m32mscoff"); 9513 } 9514 9515 BITMAPINFO infoheader; 9516 infoheader.bmiHeader.biSize = infoheader.bmiHeader.sizeof; 9517 infoheader.bmiHeader.biWidth = width; 9518 infoheader.bmiHeader.biHeight = height; 9519 infoheader.bmiHeader.biPlanes = 1; 9520 infoheader.bmiHeader.biBitCount = enableAlpha ? 32 : 24; 9521 infoheader.bmiHeader.biCompression = BI_RGB; 9522 9523 // FIXME: this should prolly be a device dependent bitmap... 9524 handle = CreateDIBSection( 9525 null, 9526 &infoheader, 9527 DIB_RGB_COLORS, 9528 cast(void**) &rawData, 9529 null, 9530 0); 9531 9532 if(handle is null) 9533 throw new WindowsApiException("couldn't create pixmap", GetLastError()); 9534 } 9535 } 9536 9537 /// Makes a sprite based on the image with the initial contents from the Image 9538 this(SimpleWindow win, Image i) { 9539 this(win, i.width, i.height, i.enableAlpha); 9540 9541 version(X11) { 9542 auto display = XDisplayConnection.get(); 9543 auto gc = XCreateGC(display, this.handle, 0, null); 9544 scope(exit) XFreeGC(display, gc); 9545 if(i.usingXshm) 9546 XShmPutImage(display, cast(Drawable) handle, gc, i.handle, 0, 0, 0, 0, i.width, i.height, false); 9547 else 9548 XPutImage(display, cast(Drawable) handle, gc, i.handle, 0, 0, 0, 0, i.width, i.height); 9549 } else version(Windows) { 9550 auto itemsPerLine = enableAlpha ? (4 * width) : (((cast(int) width * 3 + 3) / 4) * 4); 9551 auto arrLength = itemsPerLine * height; 9552 rawData[0..arrLength] = i.rawData[0..arrLength]; 9553 } else version(OSXCocoa) { 9554 // FIXME: I have no idea if this is even any good 9555 ubyte* rawData; 9556 9557 auto colorSpace = CGColorSpaceCreateDeviceRGB(); 9558 context = CGBitmapContextCreate(null, width, height, 8, 4*width, 9559 colorSpace, 9560 kCGImageAlphaPremultipliedLast 9561 |kCGBitmapByteOrder32Big); 9562 CGColorSpaceRelease(colorSpace); 9563 rawData = CGBitmapContextGetData(context); 9564 9565 auto rdl = (width * height * 4); 9566 rawData[0 .. rdl] = i.rawData[0 .. rdl]; 9567 } else static assert(0); 9568 } 9569 9570 /++ 9571 Draws the image on the specified painter at the specified point. The point is the upper-left point where the image will be drawn. 9572 9573 Params: 9574 where = point on the window where the upper left corner of the image will be drawn 9575 imageUpperLeft = point on the image to start the slice to draw 9576 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. 9577 History: 9578 The `imageUpperLeft` and `sliceSize` parameters were added on March 11, 2021 (dub v9.3.0) 9579 +/ 9580 void drawAt(ScreenPainter painter, Point where, Point imageUpperLeft = Point(0, 0), Size sliceSize = Size(0, 0)) { 9581 painter.drawPixmap(this, where, imageUpperLeft, sliceSize); 9582 } 9583 9584 /// Call this when you're ready to get rid of it 9585 void dispose() { 9586 version(X11) { 9587 staticDispose(xrenderPicture, handle); 9588 xrenderPicture = None; 9589 handle = None; 9590 } else version(Windows) { 9591 staticDispose(handle); 9592 handle = null; 9593 } else version(OSXCocoa) { 9594 staticDispose(context); 9595 context = null; 9596 } else static assert(0); 9597 9598 } 9599 9600 version(X11) 9601 static void staticDispose(Picture xrenderPicture, Pixmap handle) { 9602 if(xrenderPicture) 9603 XRenderFreePicture(XDisplayConnection.get, xrenderPicture); 9604 if(handle) 9605 XFreePixmap(XDisplayConnection.get(), handle); 9606 } 9607 else version(Windows) 9608 static void staticDispose(HBITMAP handle) { 9609 if(handle) 9610 DeleteObject(handle); 9611 } 9612 else version(OSXCocoa) 9613 static void staticDispose(CGContextRef context) { 9614 if(context) 9615 CGContextRelease(context); 9616 } 9617 9618 ~this() { 9619 version(X11) { if(xrenderPicture || handle) 9620 cleanupQueue.queue!staticDispose(xrenderPicture, handle); 9621 } else version(Windows) { if(handle) 9622 cleanupQueue.queue!staticDispose(handle); 9623 } else version(OSXCocoa) { if(context) 9624 cleanupQueue.queue!staticDispose(context); 9625 } else static assert(0); 9626 } 9627 9628 /// 9629 final @property int width() { return _width; } 9630 9631 /// 9632 final @property int height() { return _height; } 9633 9634 /// 9635 static Sprite fromMemoryImage(SimpleWindow win, MemoryImage img, bool enableAlpha = false) { 9636 return new Sprite(win, Image.fromMemoryImage(img, enableAlpha)); 9637 } 9638 9639 auto nativeHandle() { 9640 return handle; 9641 } 9642 9643 private: 9644 9645 int _width; 9646 int _height; 9647 bool enableAlpha; 9648 version(X11) 9649 Pixmap handle; 9650 else version(Windows) 9651 HBITMAP handle; 9652 else version(OSXCocoa) 9653 CGContextRef context; 9654 else static assert(0); 9655 } 9656 9657 /++ 9658 Represents a display-side gradient pseudo-image. Actually construct it with [LinearGradient], [RadialGradient], or [ConicalGradient]. 9659 9660 History: 9661 Added November 20, 2021 (dub v10.4) 9662 +/ 9663 abstract class Gradient : Sprite { 9664 protected this(int w, int h) { 9665 version(X11) { 9666 Sprite.requireXRender(); 9667 9668 super(); 9669 enableAlpha = true; 9670 _width = w; 9671 _height = h; 9672 } else version(Windows) { 9673 super(null, w, h, true); // on Windows i'm just making a bitmap myself 9674 } 9675 } 9676 9677 version(Windows) 9678 final void forEachPixel(scope Color delegate(int x, int y) dg) { 9679 auto ptr = rawData; 9680 foreach(j; 0 .. _height) 9681 foreach(i; 0 .. _width) { 9682 auto color = dg(i, _height - j - 1); // cuz of upside down bitmap 9683 *rawData = (color.a * color.b) / 255; rawData++; 9684 *rawData = (color.a * color.g) / 255; rawData++; 9685 *rawData = (color.a * color.r) / 255; rawData++; 9686 *rawData = color.a; rawData++; 9687 } 9688 } 9689 9690 version(X11) 9691 protected void helper(scope Stop[] stops, scope Picture delegate(scope XFixed[] stopsPositions, scope XRenderColor[] colors) dg) { 9692 assert(stops.length > 0); 9693 assert(stops.length <= 16, "I got lazy with buffers"); 9694 9695 XFixed[16] stopsPositions = void; 9696 XRenderColor[16] colors = void; 9697 9698 foreach(idx, stop; stops) { 9699 stopsPositions[idx] = cast(int)(stop.percentage * ushort.max); 9700 auto c = stop.c; 9701 colors[idx] = XRenderColor( 9702 cast(ushort)(c.r * ushort.max / 255), 9703 cast(ushort)(c.g * ushort.max / 255), 9704 cast(ushort)(c.b * ushort.max / 255), 9705 cast(ushort)(c.a * ubyte.max) // max value here is fractional 9706 ); 9707 } 9708 9709 xrenderPicture = dg(stopsPositions, colors); 9710 } 9711 9712 /// 9713 static struct Stop { 9714 float percentage; /// between 0 and 1.0 9715 Color c; 9716 } 9717 } 9718 9719 /++ 9720 Creates a linear gradient between p1 and p2. 9721 9722 X ONLY RIGHT NOW 9723 9724 History: 9725 Added November 20, 2021 (dub v10.4) 9726 9727 Bugs: 9728 Not yet implemented on Windows. 9729 +/ 9730 class LinearGradient : Gradient { 9731 /++ 9732 9733 +/ 9734 this(Point p1, Point p2, Stop[] stops...) { 9735 super(p2.x, p2.y); 9736 9737 version(X11) { 9738 XLinearGradient gradient; 9739 gradient.p1 = XPointFixed(p1.x * ushort.max, p1.y * ushort.max); 9740 gradient.p2 = XPointFixed(p2.x * ushort.max, p2.y * ushort.max); 9741 9742 helper(stops, (scope XFixed[] stopsPositions, scope XRenderColor[] colors) { 9743 return XRenderCreateLinearGradient( 9744 XDisplayConnection.get, 9745 &gradient, 9746 stopsPositions.ptr, 9747 colors.ptr, 9748 cast(int) stops.length); 9749 }); 9750 } else version(Windows) { 9751 // FIXME 9752 forEachPixel((int x, int y) { 9753 import core.stdc.math; 9754 9755 //sqrtf( 9756 9757 return Color.transparent; 9758 // return Color(x * 2, y * 2, 0, 128); // this result is so beautiful 9759 }); 9760 } 9761 } 9762 } 9763 9764 /++ 9765 A conical gradient goes from color to color around a circumference from a center point. 9766 9767 X ONLY RIGHT NOW 9768 9769 History: 9770 Added November 20, 2021 (dub v10.4) 9771 9772 Bugs: 9773 Not yet implemented on Windows. 9774 +/ 9775 class ConicalGradient : Gradient { 9776 /++ 9777 9778 +/ 9779 this(Point center, float angleInDegrees, Stop[] stops...) { 9780 super(center.x * 2, center.y * 2); 9781 9782 version(X11) { 9783 XConicalGradient gradient; 9784 gradient.center = XPointFixed(center.x * ushort.max, center.y * ushort.max); 9785 gradient.angle = cast(int)(angleInDegrees * ushort.max); 9786 9787 helper(stops, (scope XFixed[] stopsPositions, scope XRenderColor[] colors) { 9788 return XRenderCreateConicalGradient( 9789 XDisplayConnection.get, 9790 &gradient, 9791 stopsPositions.ptr, 9792 colors.ptr, 9793 cast(int) stops.length); 9794 }); 9795 } else version(Windows) { 9796 // FIXME 9797 forEachPixel((int x, int y) { 9798 import core.stdc.math; 9799 9800 //sqrtf( 9801 9802 return Color.transparent; 9803 // return Color(x * 2, y * 2, 0, 128); // this result is so beautiful 9804 }); 9805 9806 } 9807 } 9808 } 9809 9810 /++ 9811 A radial gradient goes from color to color based on distance from the center. 9812 It is like rings of color. 9813 9814 X ONLY RIGHT NOW 9815 9816 9817 More specifically, you create two circles: an inner circle and an outer circle. 9818 The gradient is only drawn in the area outside the inner circle but inside the outer 9819 circle. The closest line between those two circles forms the line for the gradient 9820 and the stops are calculated the same as the [LinearGradient]. Then it just sweeps around. 9821 9822 History: 9823 Added November 20, 2021 (dub v10.4) 9824 9825 Bugs: 9826 Not yet implemented on Windows. 9827 +/ 9828 class RadialGradient : Gradient { 9829 /++ 9830 9831 +/ 9832 this(Point innerCenter, float innerRadius, Point outerCenter, float outerRadius, Stop[] stops...) { 9833 super(cast(int)(outerCenter.x + outerRadius + 0.5), cast(int)(outerCenter.y + outerRadius + 0.5)); 9834 9835 version(X11) { 9836 XRadialGradient gradient; 9837 gradient.inner = XCircle(innerCenter.x * ushort.max, innerCenter.y * ushort.max, cast(int) (innerRadius * ushort.max)); 9838 gradient.outer = XCircle(outerCenter.x * ushort.max, outerCenter.y * ushort.max, cast(int) (outerRadius * ushort.max)); 9839 9840 helper(stops, (scope XFixed[] stopsPositions, scope XRenderColor[] colors) { 9841 return XRenderCreateRadialGradient( 9842 XDisplayConnection.get, 9843 &gradient, 9844 stopsPositions.ptr, 9845 colors.ptr, 9846 cast(int) stops.length); 9847 }); 9848 } else version(Windows) { 9849 // FIXME 9850 forEachPixel((int x, int y) { 9851 import core.stdc.math; 9852 9853 //sqrtf( 9854 9855 return Color.transparent; 9856 // return Color(x * 2, y * 2, 0, 128); // this result is so beautiful 9857 }); 9858 } 9859 } 9860 } 9861 9862 9863 9864 /+ 9865 NOT IMPLEMENTED 9866 9867 A display-stored image optimized for relatively quick drawing, like 9868 [Sprite], but this one supports alpha channel blending and does NOT 9869 support direct drawing upon it with a [ScreenPainter]. 9870 9871 You can think of it as an [arsd.game.OpenGlTexture] for usage with a 9872 plain [ScreenPainter]... sort of. 9873 9874 On X11, it requires the Xrender extension and library. This is available 9875 almost everywhere though. 9876 9877 History: 9878 Added November 14, 2020 but NOT ACTUALLY IMPLEMENTED 9879 +/ 9880 version(none) 9881 class AlphaSprite { 9882 /++ 9883 Copies the given image into it. 9884 +/ 9885 this(MemoryImage img) { 9886 9887 if(!XRenderLibrary.loadAttempted) { 9888 XRenderLibrary.loadDynamicLibrary(); 9889 9890 // FIXME: this needs to be reconstructed when the X server changes 9891 repopulateX(); 9892 } 9893 if(!XRenderLibrary.loadSuccessful) 9894 throw new Exception("XRender library load failure"); 9895 9896 // I probably need to put the alpha mask in a separate Picture 9897 // ugh 9898 // maybe the Sprite itself can have an alpha bitmask anyway 9899 9900 9901 auto display = XDisplayConnection.get(); 9902 pixmap = XCreatePixmap(display, cast(Drawable) win.window, width, height, DefaultDepthOfDisplay(display)); 9903 9904 9905 XRenderPictureAttributes attrs; 9906 9907 handle = XRenderCreatePicture( 9908 XDisplayConnection.get, 9909 pixmap, 9910 RGBA, 9911 0, 9912 &attrs 9913 ); 9914 9915 } 9916 9917 // maybe i'll use the create gradient functions too with static factories.. 9918 9919 void drawAt(ScreenPainter painter, Point where) { 9920 //painter.drawPixmap(this, where); 9921 9922 XRenderPictureAttributes attrs; 9923 9924 auto pic = XRenderCreatePicture( 9925 XDisplayConnection.get, 9926 painter.impl.d, 9927 RGB, 9928 0, 9929 &attrs 9930 ); 9931 9932 XRenderComposite( 9933 XDisplayConnection.get, 9934 3, // PictOpOver 9935 handle, 9936 None, 9937 pic, 9938 0, // src 9939 0, 9940 0, // mask 9941 0, 9942 10, // dest 9943 10, 9944 100, // width 9945 100 9946 ); 9947 9948 /+ 9949 XRenderFreePicture( 9950 XDisplayConnection.get, 9951 pic 9952 ); 9953 9954 XRenderFreePicture( 9955 XDisplayConnection.get, 9956 fill 9957 ); 9958 +/ 9959 // on Windows you can stretch but Xrender still can't :( 9960 } 9961 9962 static XRenderPictFormat* RGB; 9963 static XRenderPictFormat* RGBA; 9964 static void repopulateX() { 9965 auto display = XDisplayConnection.get; 9966 RGB = XRenderFindStandardFormat(display, PictStandardRGB24); 9967 RGBA = XRenderFindStandardFormat(display, PictStandardARGB32); 9968 } 9969 9970 XPixmap pixmap; 9971 Picture handle; 9972 } 9973 9974 /// 9975 interface CapableOfBeingDrawnUpon { 9976 /// 9977 ScreenPainter draw(); 9978 /// 9979 int width(); 9980 /// 9981 int height(); 9982 protected ScreenPainterImplementation* activeScreenPainter(); 9983 protected void activeScreenPainter(ScreenPainterImplementation*); 9984 bool closed(); 9985 9986 void delegate() paintingFinishedDg(); 9987 9988 /// Be warned: this can be a very slow operation 9989 TrueColorImage takeScreenshot(); 9990 } 9991 9992 /// 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]. 9993 void flushGui() { 9994 version(X11) { 9995 auto dpy = XDisplayConnection.get(); 9996 XLockDisplay(dpy); 9997 scope(exit) XUnlockDisplay(dpy); 9998 XFlush(dpy); 9999 } 10000 } 10001 10002 /++ 10003 Runs the given code in the GUI thread when its event loop 10004 is available, blocking until it completes. This allows you 10005 to create and manipulate windows from another thread without 10006 invoking undefined behavior. 10007 10008 If this is the gui thread, it runs the code immediately. 10009 10010 If no gui thread exists yet, the current thread is assumed 10011 to be it. Attempting to create windows or run the event loop 10012 in any other thread will cause an assertion failure. 10013 10014 10015 $(TIP 10016 Did you know you can use UFCS on delegate literals? 10017 10018 () { 10019 // code here 10020 }.runInGuiThread; 10021 ) 10022 10023 Returns: 10024 `true` if the function was called, `false` if it was not. 10025 The function may not be called because the gui thread had 10026 already terminated by the time you called this. 10027 10028 History: 10029 Added April 10, 2020 (v7.2.0) 10030 10031 Return value added and implementation tweaked to avoid locking 10032 at program termination on February 24, 2021 (v9.2.1). 10033 +/ 10034 bool runInGuiThread(scope void delegate() dg) @trusted { 10035 claimGuiThread(); 10036 10037 if(thisIsGuiThread) { 10038 dg(); 10039 return true; 10040 } 10041 10042 if(guiThreadTerminating) 10043 return false; 10044 10045 import core.sync.semaphore; 10046 static Semaphore sc; 10047 if(sc is null) 10048 sc = new Semaphore(); 10049 10050 static RunQueueMember* rqm; 10051 if(rqm is null) 10052 rqm = new RunQueueMember; 10053 rqm.dg = cast(typeof(rqm.dg)) dg; 10054 rqm.signal = sc; 10055 rqm.thrown = null; 10056 10057 synchronized(runInGuiThreadLock) { 10058 runInGuiThreadQueue ~= rqm; 10059 } 10060 10061 if(!SimpleWindow.eventWakeUp()) 10062 throw new Error("runInGuiThread impossible; eventWakeUp failed"); 10063 10064 rqm.signal.wait(); 10065 auto t = rqm.thrown; 10066 10067 if(t) 10068 throw t; 10069 10070 return true; 10071 } 10072 10073 // note it runs sync if this is the gui thread.... 10074 void runInGuiThreadAsync(void delegate() dg, void delegate(Exception) nothrow handleError = null) nothrow { 10075 claimGuiThread(); 10076 10077 try { 10078 10079 if(thisIsGuiThread) { 10080 dg(); 10081 return; 10082 } 10083 10084 if(guiThreadTerminating) 10085 return; 10086 10087 RunQueueMember* rqm = new RunQueueMember; 10088 rqm.dg = cast(typeof(rqm.dg)) dg; 10089 rqm.signal = null; 10090 rqm.thrown = null; 10091 10092 synchronized(runInGuiThreadLock) { 10093 runInGuiThreadQueue ~= rqm; 10094 } 10095 10096 if(!SimpleWindow.eventWakeUp()) 10097 throw new Error("runInGuiThread impossible; eventWakeUp failed"); 10098 } catch(Exception e) { 10099 if(handleError) 10100 handleError(e); 10101 } 10102 } 10103 10104 private void runPendingRunInGuiThreadDelegates() { 10105 more: 10106 RunQueueMember* next; 10107 synchronized(runInGuiThreadLock) { 10108 if(runInGuiThreadQueue.length) { 10109 next = runInGuiThreadQueue[0]; 10110 runInGuiThreadQueue = runInGuiThreadQueue[1 .. $]; 10111 } else { 10112 next = null; 10113 } 10114 } 10115 10116 if(next) { 10117 try { 10118 next.dg(); 10119 next.thrown = null; 10120 } catch(Throwable t) { 10121 next.thrown = t; 10122 } 10123 10124 if(next.signal) 10125 next.signal.notify(); 10126 10127 goto more; 10128 } 10129 } 10130 10131 private void claimGuiThread() nothrow { 10132 import core.atomic; 10133 if(cas(&guiThreadExists_, false, true)) 10134 thisIsGuiThread = true; 10135 } 10136 10137 private struct RunQueueMember { 10138 void delegate() dg; 10139 import core.sync.semaphore; 10140 Semaphore signal; 10141 Throwable thrown; 10142 } 10143 10144 private __gshared RunQueueMember*[] runInGuiThreadQueue; 10145 private __gshared Object runInGuiThreadLock = new Object; // intentional CTFE 10146 private bool thisIsGuiThread = false; 10147 private shared bool guiThreadExists_ = false; 10148 private shared bool guiThreadTerminating = false; 10149 10150 /++ 10151 Returns `true` if a gui thread exists, that is, a thread running the simpledisplay.d 10152 event loop. All windows must be exclusively created and managed by a single thread. 10153 10154 If no gui thread exists, simpledisplay.d will automatically adopt the current thread 10155 when you call one of its constructors. 10156 10157 If a gui thread exists, you should check [thisThreadRunningGui] to see if it is this 10158 one. If so, you can run gui functions on it. If not, don't. The helper functions 10159 [runInGuiThread] and [runInGuiThreadAsync] can be used to help you with this automatically. 10160 10161 The reason this function is available is in case you want to message pass between a gui 10162 thread and your current thread. If no gui thread exists or if this is the gui thread, 10163 you're liable to deadlock when trying to communicate since you'd end up talking to yourself. 10164 10165 History: 10166 Added December 3, 2021 (dub v10.5) 10167 +/ 10168 public bool guiThreadExists() { 10169 return guiThreadExists_; 10170 } 10171 10172 /++ 10173 Returns `true` if this thread is either running or set to be running the 10174 simpledisplay.d gui core event loop because it owns windows. 10175 10176 It is important to keep gui-related functionality in the right thread, so you will 10177 want to `runInGuiThread` when you call them (with some specific exceptions called 10178 out in those specific functions' documentation). Notably, all windows must be 10179 created and managed only from the gui thread. 10180 10181 Will return false if simpledisplay's other functions haven't been called 10182 yet; check [guiThreadExists] in addition to this. 10183 10184 History: 10185 Added December 3, 2021 (dub v10.5) 10186 +/ 10187 public bool thisThreadRunningGui() { 10188 return thisIsGuiThread; 10189 } 10190 10191 /++ 10192 Function to help temporarily print debugging info. It will bypass any stdout/err redirection 10193 and go to the controlling tty or console (attaching to the parent and/or allocating one as 10194 needed on Windows. Please note it may overwrite output from other programs in the parent and the 10195 allocated one will not survive if your program crashes. Use the `fileOverride` to print to a log 10196 file instead if you are in one of those situations). 10197 10198 It does not support outputting very many types; just strings and ints are likely to actually work. 10199 10200 It will perform very slowly and swallows any errors that may occur. Moreover, the specific output 10201 is unspecified meaning I can change it at any time. The only point of this function is to help 10202 in temporary use for printf-style debugging. It is NOT nogc, but you can use the `debug` keyword 10203 and the compiler will cheat for you. It is, however, formally nothrow and trusted to ease its use 10204 in those contexts. 10205 10206 $(WARNING 10207 I reserve the right to change this function at any time. You can use it if it helps you 10208 but do not rely on it for anything permanent. 10209 ) 10210 10211 History: 10212 Added December 3, 2021. Not formally supported under any stable tag. 10213 +/ 10214 void sdpyPrintDebugString(string fileOverride = null, T...)(T t) nothrow @trusted { 10215 try { 10216 version(Windows) { 10217 import core.sys.windows.wincon; 10218 if(!AttachConsole(ATTACH_PARENT_PROCESS)) 10219 AllocConsole(); 10220 const(char)* fn = "CONOUT$"; 10221 } else version(Posix) { 10222 const(char)* fn = "/dev/tty"; 10223 } else static assert(0, "Function not implemented for your system"); 10224 10225 if(fileOverride.length) 10226 fn = fileOverride.ptr; 10227 10228 import core.stdc.stdio; 10229 auto fp = fopen(fn, "wt"); 10230 if(fp is null) return; 10231 scope(exit) fclose(fp); 10232 10233 string str; 10234 foreach(item; t) { 10235 static if(is(typeof(item) : const(char)[])) 10236 str ~= item; 10237 else 10238 str ~= toInternal!string(item); 10239 str ~= " "; 10240 } 10241 str ~= "\n"; 10242 10243 fwrite(str.ptr, 1, str.length, fp); 10244 fflush(fp); 10245 } catch(Exception e) { 10246 // sorry no hope 10247 } 10248 } 10249 10250 private void guiThreadFinalize() { 10251 assert(thisIsGuiThread); 10252 10253 guiThreadTerminating = true; // don't add any more from this point on 10254 runPendingRunInGuiThreadDelegates(); 10255 } 10256 10257 /+ 10258 interface IPromise { 10259 void reportProgress(int current, int max, string message); 10260 10261 /+ // not formally in cuz of templates but still 10262 IPromise Then(); 10263 IPromise Catch(); 10264 IPromise Finally(); 10265 +/ 10266 } 10267 10268 /+ 10269 auto promise = async({ ... }); 10270 promise.Then(whatever). 10271 Then(whateverelse). 10272 Catch((exception) { }); 10273 10274 10275 A promise is run inside a fiber and it looks something like: 10276 10277 try { 10278 auto res = whatever(); 10279 auto res2 = whateverelse(res); 10280 } catch(Exception e) { 10281 { }(e); 10282 } 10283 10284 When a thing succeeds, it is passed as an arg to the next 10285 +/ 10286 class Promise(T) : IPromise { 10287 auto Then() { return null; } 10288 auto Catch() { return null; } 10289 auto Finally() { return null; } 10290 10291 // wait for it to resolve and return the value, or rethrow the error if that occurred. 10292 // cannot be called from the gui thread, but this is caught at runtime instead of compile time. 10293 T await(); 10294 } 10295 10296 interface Task { 10297 } 10298 10299 interface Resolvable(T) : Task { 10300 void run(); 10301 10302 void resolve(T); 10303 10304 Resolvable!T then(void delegate(T)); // returns a new promise 10305 Resolvable!T error(Throwable); // js catch 10306 Resolvable!T completed(); // js finally 10307 10308 } 10309 10310 /++ 10311 Runs `work` in a helper thread and sends its return value back to the main gui 10312 thread as the argument to `uponCompletion`. If `work` throws, the exception is 10313 sent to the `uponThrown` if given, or if null, rethrown from the event loop to 10314 kill the program. 10315 10316 You can call reportProgress(position, max, message) to update your parent window 10317 on your progress. 10318 10319 I should also use `shared` methods. FIXME 10320 10321 History: 10322 Added March 6, 2021 (dub version 9.3). 10323 +/ 10324 void runInWorkerThread(T)(T delegate(Task) work, void delegate(T) uponCompletion) { 10325 uponCompletion(work(null)); 10326 } 10327 10328 +/ 10329 10330 /// Used internal to dispatch events to various classes. 10331 interface CapableOfHandlingNativeEvent { 10332 NativeEventHandler getNativeEventHandler(); 10333 10334 /*private*//*protected*/ __gshared CapableOfHandlingNativeEvent[NativeWindowHandle] nativeHandleMapping; 10335 10336 version(X11) { 10337 // if this is impossible, you are allowed to just throw from it 10338 // Note: if you call it from another object, set a flag cuz the manger will call you again 10339 void recreateAfterDisconnect(); 10340 // discard any *connection specific* state, but keep enough that you 10341 // can be recreated if possible. discardConnectionState() is always called immediately 10342 // before recreateAfterDisconnect(), so you can set a flag there to decide if 10343 // you need initialization order 10344 void discardConnectionState(); 10345 } 10346 } 10347 10348 version(X11) 10349 /++ 10350 State of keys on mouse events, especially motion. 10351 10352 Do not trust the actual integer values in this, they are platform-specific. Always use the names. 10353 +/ 10354 enum ModifierState : uint { 10355 shift = 1, /// 10356 capsLock = 2, /// 10357 ctrl = 4, /// 10358 alt = 8, /// Not always available on Windows 10359 windows = 64, /// ditto 10360 numLock = 16, /// 10361 10362 leftButtonDown = 256, /// these aren't available on Windows for key events, so don't use them for that unless your app is X only. 10363 middleButtonDown = 512, /// ditto 10364 rightButtonDown = 1024, /// ditto 10365 } 10366 else version(Windows) 10367 /// ditto 10368 enum ModifierState : uint { 10369 shift = 4, /// 10370 ctrl = 8, /// 10371 10372 // i'm not sure if the next two are available 10373 alt = 256, /// not always available on Windows 10374 windows = 512, /// ditto 10375 10376 capsLock = 1024, /// 10377 numLock = 2048, /// 10378 10379 leftButtonDown = 1, /// not available on key events 10380 middleButtonDown = 16, /// ditto 10381 rightButtonDown = 2, /// ditto 10382 10383 backButtonDown = 0x20, /// not available on X 10384 forwardButtonDown = 0x40, /// ditto 10385 } 10386 else version(OSXCocoa) 10387 // FIXME FIXME NotYetImplementedException 10388 enum ModifierState : uint { 10389 shift = 1, /// 10390 capsLock = 2, /// 10391 ctrl = 4, /// 10392 alt = 8, /// Not always available on Windows 10393 windows = 64, /// ditto 10394 numLock = 16, /// 10395 10396 leftButtonDown = 256, /// these aren't available on Windows for key events, so don't use them for that unless your app is X only. 10397 middleButtonDown = 512, /// ditto 10398 rightButtonDown = 1024, /// ditto 10399 } 10400 10401 /// The names assume a right-handed mouse. These are bitwise combined on the events that use them. 10402 enum MouseButton : int { 10403 none = 0, 10404 left = 1, /// 10405 right = 2, /// 10406 middle = 4, /// 10407 wheelUp = 8, /// 10408 wheelDown = 16, /// 10409 backButton = 32, /// often found on the thumb and used for back in browsers 10410 forwardButton = 64, /// often found on the thumb and used for forward in browsers 10411 } 10412 10413 version(X11) { 10414 // FIXME: match ASCII whenever we can. Most of it is already there, 10415 // but there's a few exceptions and mismatches with Windows 10416 10417 /// Do not trust the numeric values as they are platform-specific. Always use the symbolic name. 10418 enum Key { 10419 Escape = 0xff1b, /// 10420 F1 = 0xffbe, /// 10421 F2 = 0xffbf, /// 10422 F3 = 0xffc0, /// 10423 F4 = 0xffc1, /// 10424 F5 = 0xffc2, /// 10425 F6 = 0xffc3, /// 10426 F7 = 0xffc4, /// 10427 F8 = 0xffc5, /// 10428 F9 = 0xffc6, /// 10429 F10 = 0xffc7, /// 10430 F11 = 0xffc8, /// 10431 F12 = 0xffc9, /// 10432 PrintScreen = 0xff61, /// 10433 ScrollLock = 0xff14, /// 10434 Pause = 0xff13, /// 10435 Grave = 0x60, /// The $(BACKTICK) ~ key 10436 // number keys across the top of the keyboard 10437 N1 = 0x31, /// Number key atop the keyboard 10438 N2 = 0x32, /// 10439 N3 = 0x33, /// 10440 N4 = 0x34, /// 10441 N5 = 0x35, /// 10442 N6 = 0x36, /// 10443 N7 = 0x37, /// 10444 N8 = 0x38, /// 10445 N9 = 0x39, /// 10446 N0 = 0x30, /// 10447 Dash = 0x2d, /// 10448 Equals = 0x3d, /// 10449 Backslash = 0x5c, /// The \ | key 10450 Backspace = 0xff08, /// 10451 Insert = 0xff63, /// 10452 Home = 0xff50, /// 10453 PageUp = 0xff55, /// 10454 Delete = 0xffff, /// 10455 End = 0xff57, /// 10456 PageDown = 0xff56, /// 10457 Up = 0xff52, /// 10458 Down = 0xff54, /// 10459 Left = 0xff51, /// 10460 Right = 0xff53, /// 10461 10462 Tab = 0xff09, /// 10463 Q = 0x71, /// 10464 W = 0x77, /// 10465 E = 0x65, /// 10466 R = 0x72, /// 10467 T = 0x74, /// 10468 Y = 0x79, /// 10469 U = 0x75, /// 10470 I = 0x69, /// 10471 O = 0x6f, /// 10472 P = 0x70, /// 10473 LeftBracket = 0x5b, /// the [ { key 10474 RightBracket = 0x5d, /// the ] } key 10475 CapsLock = 0xffe5, /// 10476 A = 0x61, /// 10477 S = 0x73, /// 10478 D = 0x64, /// 10479 F = 0x66, /// 10480 G = 0x67, /// 10481 H = 0x68, /// 10482 J = 0x6a, /// 10483 K = 0x6b, /// 10484 L = 0x6c, /// 10485 Semicolon = 0x3b, /// 10486 Apostrophe = 0x27, /// 10487 Enter = 0xff0d, /// 10488 Shift = 0xffe1, /// 10489 Z = 0x7a, /// 10490 X = 0x78, /// 10491 C = 0x63, /// 10492 V = 0x76, /// 10493 B = 0x62, /// 10494 N = 0x6e, /// 10495 M = 0x6d, /// 10496 Comma = 0x2c, /// 10497 Period = 0x2e, /// 10498 Slash = 0x2f, /// the / ? key 10499 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 10500 Ctrl = 0xffe3, /// 10501 Windows = 0xffeb, /// 10502 Alt = 0xffe9, /// 10503 Space = 0x20, /// 10504 Alt_r = 0xffea, /// ditto of shift_r 10505 Windows_r = 0xffec, /// 10506 Menu = 0xff67, /// 10507 Ctrl_r = 0xffe4, /// 10508 10509 NumLock = 0xff7f, /// 10510 Divide = 0xffaf, /// The / key on the number pad 10511 Multiply = 0xffaa, /// The * key on the number pad 10512 Minus = 0xffad, /// The - key on the number pad 10513 Plus = 0xffab, /// The + key on the number pad 10514 PadEnter = 0xff8d, /// Numberpad enter key 10515 Pad1 = 0xff9c, /// Numberpad keys 10516 Pad2 = 0xff99, /// 10517 Pad3 = 0xff9b, /// 10518 Pad4 = 0xff96, /// 10519 Pad5 = 0xff9d, /// 10520 Pad6 = 0xff98, /// 10521 Pad7 = 0xff95, /// 10522 Pad8 = 0xff97, /// 10523 Pad9 = 0xff9a, /// 10524 Pad0 = 0xff9e, /// 10525 PadDot = 0xff9f, /// 10526 } 10527 } else version(Windows) { 10528 // the character here is for en-us layouts and for illustration only 10529 // if you actually want to get characters, wait for character events 10530 // (the argument to your event handler is simply a dchar) 10531 // those will be converted by the OS for the right locale. 10532 10533 enum Key { 10534 Escape = 0x1b, 10535 F1 = 0x70, 10536 F2 = 0x71, 10537 F3 = 0x72, 10538 F4 = 0x73, 10539 F5 = 0x74, 10540 F6 = 0x75, 10541 F7 = 0x76, 10542 F8 = 0x77, 10543 F9 = 0x78, 10544 F10 = 0x79, 10545 F11 = 0x7a, 10546 F12 = 0x7b, 10547 PrintScreen = 0x2c, 10548 ScrollLock = 0x91, 10549 Pause = 0x13, 10550 Grave = 0xc0, 10551 // number keys across the top of the keyboard 10552 N1 = 0x31, 10553 N2 = 0x32, 10554 N3 = 0x33, 10555 N4 = 0x34, 10556 N5 = 0x35, 10557 N6 = 0x36, 10558 N7 = 0x37, 10559 N8 = 0x38, 10560 N9 = 0x39, 10561 N0 = 0x30, 10562 Dash = 0xbd, 10563 Equals = 0xbb, 10564 Backslash = 0xdc, 10565 Backspace = 0x08, 10566 Insert = 0x2d, 10567 Home = 0x24, 10568 PageUp = 0x21, 10569 Delete = 0x2e, 10570 End = 0x23, 10571 PageDown = 0x22, 10572 Up = 0x26, 10573 Down = 0x28, 10574 Left = 0x25, 10575 Right = 0x27, 10576 10577 Tab = 0x09, 10578 Q = 0x51, 10579 W = 0x57, 10580 E = 0x45, 10581 R = 0x52, 10582 T = 0x54, 10583 Y = 0x59, 10584 U = 0x55, 10585 I = 0x49, 10586 O = 0x4f, 10587 P = 0x50, 10588 LeftBracket = 0xdb, 10589 RightBracket = 0xdd, 10590 CapsLock = 0x14, 10591 A = 0x41, 10592 S = 0x53, 10593 D = 0x44, 10594 F = 0x46, 10595 G = 0x47, 10596 H = 0x48, 10597 J = 0x4a, 10598 K = 0x4b, 10599 L = 0x4c, 10600 Semicolon = 0xba, 10601 Apostrophe = 0xde, 10602 Enter = 0x0d, 10603 Shift = 0x10, 10604 Z = 0x5a, 10605 X = 0x58, 10606 C = 0x43, 10607 V = 0x56, 10608 B = 0x42, 10609 N = 0x4e, 10610 M = 0x4d, 10611 Comma = 0xbc, 10612 Period = 0xbe, 10613 Slash = 0xbf, 10614 Shift_r = 0xa1, // FIXME Note: this isn't sent on all computers, sometimes it just sends Shift, so don't rely on it 10615 Ctrl = 0x11, 10616 Windows = 0x5b, 10617 Alt = -5, // FIXME 10618 Space = 0x20, 10619 Alt_r = 0xffea, // ditto of shift_r 10620 Windows_r = 0x5c, // ditto of shift_r 10621 Menu = 0x5d, 10622 Ctrl_r = 0xa3, // ditto of shift_r 10623 10624 NumLock = 0x90, 10625 Divide = 0x6f, 10626 Multiply = 0x6a, 10627 Minus = 0x6d, 10628 Plus = 0x6b, 10629 PadEnter = -8, // FIXME 10630 Pad1 = 0x61, 10631 Pad2 = 0x62, 10632 Pad3 = 0x63, 10633 Pad4 = 0x64, 10634 Pad5 = 0x65, 10635 Pad6 = 0x66, 10636 Pad7 = 0x67, 10637 Pad8 = 0x68, 10638 Pad9 = 0x69, 10639 Pad0 = 0x60, 10640 PadDot = 0x6e, 10641 } 10642 10643 // I'm keeping this around for reference purposes 10644 // ideally all these buttons will be listed for all platforms, 10645 // but now now I'm just focusing on my US keyboard 10646 version(none) 10647 enum Key { 10648 LBUTTON = 0x01, 10649 RBUTTON = 0x02, 10650 CANCEL = 0x03, 10651 MBUTTON = 0x04, 10652 //static if (_WIN32_WINNT > = 0x500) { 10653 XBUTTON1 = 0x05, 10654 XBUTTON2 = 0x06, 10655 //} 10656 BACK = 0x08, 10657 TAB = 0x09, 10658 CLEAR = 0x0C, 10659 RETURN = 0x0D, 10660 SHIFT = 0x10, 10661 CONTROL = 0x11, 10662 MENU = 0x12, 10663 PAUSE = 0x13, 10664 CAPITAL = 0x14, 10665 KANA = 0x15, 10666 HANGEUL = 0x15, 10667 HANGUL = 0x15, 10668 JUNJA = 0x17, 10669 FINAL = 0x18, 10670 HANJA = 0x19, 10671 KANJI = 0x19, 10672 ESCAPE = 0x1B, 10673 CONVERT = 0x1C, 10674 NONCONVERT = 0x1D, 10675 ACCEPT = 0x1E, 10676 MODECHANGE = 0x1F, 10677 SPACE = 0x20, 10678 PRIOR = 0x21, 10679 NEXT = 0x22, 10680 END = 0x23, 10681 HOME = 0x24, 10682 LEFT = 0x25, 10683 UP = 0x26, 10684 RIGHT = 0x27, 10685 DOWN = 0x28, 10686 SELECT = 0x29, 10687 PRINT = 0x2A, 10688 EXECUTE = 0x2B, 10689 SNAPSHOT = 0x2C, 10690 INSERT = 0x2D, 10691 DELETE = 0x2E, 10692 HELP = 0x2F, 10693 LWIN = 0x5B, 10694 RWIN = 0x5C, 10695 APPS = 0x5D, 10696 SLEEP = 0x5F, 10697 NUMPAD0 = 0x60, 10698 NUMPAD1 = 0x61, 10699 NUMPAD2 = 0x62, 10700 NUMPAD3 = 0x63, 10701 NUMPAD4 = 0x64, 10702 NUMPAD5 = 0x65, 10703 NUMPAD6 = 0x66, 10704 NUMPAD7 = 0x67, 10705 NUMPAD8 = 0x68, 10706 NUMPAD9 = 0x69, 10707 MULTIPLY = 0x6A, 10708 ADD = 0x6B, 10709 SEPARATOR = 0x6C, 10710 SUBTRACT = 0x6D, 10711 DECIMAL = 0x6E, 10712 DIVIDE = 0x6F, 10713 F1 = 0x70, 10714 F2 = 0x71, 10715 F3 = 0x72, 10716 F4 = 0x73, 10717 F5 = 0x74, 10718 F6 = 0x75, 10719 F7 = 0x76, 10720 F8 = 0x77, 10721 F9 = 0x78, 10722 F10 = 0x79, 10723 F11 = 0x7A, 10724 F12 = 0x7B, 10725 F13 = 0x7C, 10726 F14 = 0x7D, 10727 F15 = 0x7E, 10728 F16 = 0x7F, 10729 F17 = 0x80, 10730 F18 = 0x81, 10731 F19 = 0x82, 10732 F20 = 0x83, 10733 F21 = 0x84, 10734 F22 = 0x85, 10735 F23 = 0x86, 10736 F24 = 0x87, 10737 NUMLOCK = 0x90, 10738 SCROLL = 0x91, 10739 LSHIFT = 0xA0, 10740 RSHIFT = 0xA1, 10741 LCONTROL = 0xA2, 10742 RCONTROL = 0xA3, 10743 LMENU = 0xA4, 10744 RMENU = 0xA5, 10745 //static if (_WIN32_WINNT > = 0x500) { 10746 BROWSER_BACK = 0xA6, 10747 BROWSER_FORWARD = 0xA7, 10748 BROWSER_REFRESH = 0xA8, 10749 BROWSER_STOP = 0xA9, 10750 BROWSER_SEARCH = 0xAA, 10751 BROWSER_FAVORITES = 0xAB, 10752 BROWSER_HOME = 0xAC, 10753 VOLUME_MUTE = 0xAD, 10754 VOLUME_DOWN = 0xAE, 10755 VOLUME_UP = 0xAF, 10756 MEDIA_NEXT_TRACK = 0xB0, 10757 MEDIA_PREV_TRACK = 0xB1, 10758 MEDIA_STOP = 0xB2, 10759 MEDIA_PLAY_PAUSE = 0xB3, 10760 LAUNCH_MAIL = 0xB4, 10761 LAUNCH_MEDIA_SELECT = 0xB5, 10762 LAUNCH_APP1 = 0xB6, 10763 LAUNCH_APP2 = 0xB7, 10764 //} 10765 OEM_1 = 0xBA, 10766 //static if (_WIN32_WINNT > = 0x500) { 10767 OEM_PLUS = 0xBB, 10768 OEM_COMMA = 0xBC, 10769 OEM_MINUS = 0xBD, 10770 OEM_PERIOD = 0xBE, 10771 //} 10772 OEM_2 = 0xBF, 10773 OEM_3 = 0xC0, 10774 OEM_4 = 0xDB, 10775 OEM_5 = 0xDC, 10776 OEM_6 = 0xDD, 10777 OEM_7 = 0xDE, 10778 OEM_8 = 0xDF, 10779 //static if (_WIN32_WINNT > = 0x500) { 10780 OEM_102 = 0xE2, 10781 //} 10782 PROCESSKEY = 0xE5, 10783 //static if (_WIN32_WINNT > = 0x500) { 10784 PACKET = 0xE7, 10785 //} 10786 ATTN = 0xF6, 10787 CRSEL = 0xF7, 10788 EXSEL = 0xF8, 10789 EREOF = 0xF9, 10790 PLAY = 0xFA, 10791 ZOOM = 0xFB, 10792 NONAME = 0xFC, 10793 PA1 = 0xFD, 10794 OEM_CLEAR = 0xFE, 10795 } 10796 10797 } else version(OSXCocoa) { 10798 // FIXME 10799 enum Key { 10800 Escape = 0x1b, 10801 F1 = 0x70, 10802 F2 = 0x71, 10803 F3 = 0x72, 10804 F4 = 0x73, 10805 F5 = 0x74, 10806 F6 = 0x75, 10807 F7 = 0x76, 10808 F8 = 0x77, 10809 F9 = 0x78, 10810 F10 = 0x79, 10811 F11 = 0x7a, 10812 F12 = 0x7b, 10813 PrintScreen = 0x2c, 10814 ScrollLock = -2, // FIXME 10815 Pause = -3, // FIXME 10816 Grave = 0xc0, 10817 // number keys across the top of the keyboard 10818 N1 = 0x31, 10819 N2 = 0x32, 10820 N3 = 0x33, 10821 N4 = 0x34, 10822 N5 = 0x35, 10823 N6 = 0x36, 10824 N7 = 0x37, 10825 N8 = 0x38, 10826 N9 = 0x39, 10827 N0 = 0x30, 10828 Dash = 0xbd, 10829 Equals = 0xbb, 10830 Backslash = 0xdc, 10831 Backspace = 0x08, 10832 Insert = 0x2d, 10833 Home = 0x24, 10834 PageUp = 0x21, 10835 Delete = 0x2e, 10836 End = 0x23, 10837 PageDown = 0x22, 10838 Up = 0x26, 10839 Down = 0x28, 10840 Left = 0x25, 10841 Right = 0x27, 10842 10843 Tab = 0x09, 10844 Q = 0x51, 10845 W = 0x57, 10846 E = 0x45, 10847 R = 0x52, 10848 T = 0x54, 10849 Y = 0x59, 10850 U = 0x55, 10851 I = 0x49, 10852 O = 0x4f, 10853 P = 0x50, 10854 LeftBracket = 0xdb, 10855 RightBracket = 0xdd, 10856 CapsLock = 0x14, 10857 A = 0x41, 10858 S = 0x53, 10859 D = 0x44, 10860 F = 0x46, 10861 G = 0x47, 10862 H = 0x48, 10863 J = 0x4a, 10864 K = 0x4b, 10865 L = 0x4c, 10866 Semicolon = 0xba, 10867 Apostrophe = 0xde, 10868 Enter = 0x0d, 10869 Shift = 0x10, 10870 Z = 0x5a, 10871 X = 0x58, 10872 C = 0x43, 10873 V = 0x56, 10874 B = 0x42, 10875 N = 0x4e, 10876 M = 0x4d, 10877 Comma = 0xbc, 10878 Period = 0xbe, 10879 Slash = 0xbf, 10880 Shift_r = -4, // FIXME Note: this isn't sent on all computers, sometimes it just sends Shift, so don't rely on it 10881 Ctrl = 0x11, 10882 Windows = 0x5b, 10883 Alt = -5, // FIXME 10884 Space = 0x20, 10885 Alt_r = 0xffea, // ditto of shift_r 10886 Windows_r = -6, // FIXME 10887 Menu = 0x5d, 10888 Ctrl_r = -7, // FIXME 10889 10890 NumLock = 0x90, 10891 Divide = 0x6f, 10892 Multiply = 0x6a, 10893 Minus = 0x6d, 10894 Plus = 0x6b, 10895 PadEnter = -8, // FIXME 10896 // FIXME for the rest of these: 10897 Pad1 = 0xff9c, 10898 Pad2 = 0xff99, 10899 Pad3 = 0xff9b, 10900 Pad4 = 0xff96, 10901 Pad5 = 0xff9d, 10902 Pad6 = 0xff98, 10903 Pad7 = 0xff95, 10904 Pad8 = 0xff97, 10905 Pad9 = 0xff9a, 10906 Pad0 = 0xff9e, 10907 PadDot = 0xff9f, 10908 } 10909 10910 } 10911 10912 /* Additional utilities */ 10913 10914 10915 Color fromHsl(real h, real s, real l) { 10916 return arsd.color.fromHsl([h,s,l]); 10917 } 10918 10919 10920 10921 /* ********** What follows is the system-specific implementations *********/ 10922 version(Windows) { 10923 10924 10925 // helpers for making HICONs from MemoryImages 10926 class WindowsIcon { 10927 struct Win32Icon(int colorCount) { 10928 align(1): 10929 uint biSize; 10930 int biWidth; 10931 int biHeight; 10932 ushort biPlanes; 10933 ushort biBitCount; 10934 uint biCompression; 10935 uint biSizeImage; 10936 int biXPelsPerMeter; 10937 int biYPelsPerMeter; 10938 uint biClrUsed; 10939 uint biClrImportant; 10940 RGBQUAD[colorCount] biColors; 10941 /* Pixels: 10942 Uint8 pixels[] 10943 */ 10944 /* Mask: 10945 Uint8 mask[] 10946 */ 10947 10948 // FIXME: this sux, needs to be dynamic 10949 ubyte[/*4096*/ 256*256*4 + 256*256/8] data; 10950 10951 void fromMemoryImage(MemoryImage mi, out int icon_len, out int width, out int height) { 10952 width = mi.width; 10953 height = mi.height; 10954 10955 auto indexedImage = cast(IndexedImage) mi; 10956 if(indexedImage is null) 10957 indexedImage = quantize(mi.getAsTrueColorImage()); 10958 10959 assert(width <= 256, "image too wide"); 10960 assert(height <= 256, "image too tall"); 10961 assert(width %8 == 0, "image not multiple of 8 width"); // i don't want padding nor do i want the and mask to get fancy 10962 assert(height %4 == 0, "image not multiple of 4 height"); 10963 10964 int icon_plen = height*((width+3)&~3); 10965 int icon_mlen = height*((((width+7)/8)+3)&~3); 10966 icon_len = 40+icon_plen+icon_mlen + cast(int) RGBQUAD.sizeof * colorCount; 10967 10968 biSize = 40; 10969 biWidth = width; 10970 biHeight = height*2; 10971 biPlanes = 1; 10972 biBitCount = 8; 10973 biSizeImage = icon_plen+icon_mlen; 10974 10975 int offset = 0; 10976 int andOff = icon_plen * 8; // the and offset is in bits 10977 for(int y = height - 1; y >= 0; y--) { 10978 int off2 = y * width; 10979 foreach(x; 0 .. width) { 10980 const b = indexedImage.data[off2 + x]; 10981 data[offset] = b; 10982 offset++; 10983 10984 const andBit = andOff % 8; 10985 const andIdx = andOff / 8; 10986 assert(b < indexedImage.palette.length); 10987 // this is anded to the destination, since and 0 means erase, 10988 // we want that to be opaque, and 1 for transparent 10989 auto transparent = (indexedImage.palette[b].a <= 127); 10990 data[andIdx] |= (transparent ? (1 << (7-andBit)) : 0); 10991 10992 andOff++; 10993 } 10994 10995 andOff += andOff % 32; 10996 } 10997 10998 foreach(idx, entry; indexedImage.palette) { 10999 if(entry.a > 127) { 11000 biColors[idx].rgbBlue = entry.b; 11001 biColors[idx].rgbGreen = entry.g; 11002 biColors[idx].rgbRed = entry.r; 11003 } else { 11004 biColors[idx].rgbBlue = 255; 11005 biColors[idx].rgbGreen = 255; 11006 biColors[idx].rgbRed = 255; 11007 } 11008 } 11009 11010 /* 11011 data[0..icon_plen] = getFlippedUnfilteredDatastream(png); 11012 data[icon_plen..icon_plen+icon_mlen] = getANDMask(png); 11013 //icon_win32.biColors[1] = Win32Icon.RGBQUAD(0,255,0,0); 11014 auto pngMap = fetchPaletteWin32(png); 11015 biColors[0..pngMap.length] = pngMap[]; 11016 */ 11017 } 11018 } 11019 11020 11021 Win32Icon!(256) icon_win32; 11022 11023 11024 this(MemoryImage mi) { 11025 int icon_len, width, height; 11026 11027 icon_win32.fromMemoryImage(mi, icon_len, width, height); 11028 11029 /* 11030 PNG* png = readPnpngData); 11031 PNGHeader pngh = getHeader(png); 11032 void* icon_win32; 11033 if(pngh.depth == 4) { 11034 auto i = new Win32Icon!(16); 11035 i.fromPNG(png, pngh, icon_len, width, height); 11036 icon_win32 = i; 11037 } 11038 else if(pngh.depth == 8) { 11039 auto i = new Win32Icon!(256); 11040 i.fromPNG(png, pngh, icon_len, width, height); 11041 icon_win32 = i; 11042 } else assert(0); 11043 */ 11044 11045 hIcon = CreateIconFromResourceEx(cast(ubyte*) &icon_win32, icon_len, true, 0x00030000, width, height, 0); 11046 11047 if(hIcon is null) throw new WindowsApiException("CreateIconFromResourceEx", GetLastError()); 11048 } 11049 11050 ~this() { 11051 if(!thisIsGuiThread) return; // FIXME: leaks if multithreaded gc 11052 DestroyIcon(hIcon); 11053 } 11054 11055 HICON hIcon; 11056 } 11057 11058 11059 11060 11061 11062 11063 alias int delegate(HWND, UINT, WPARAM, LPARAM, out int) NativeEventHandler; 11064 alias HWND NativeWindowHandle; 11065 11066 extern(Windows) 11067 LRESULT WndProc(HWND hWnd, UINT iMessage, WPARAM wParam, LPARAM lParam) nothrow { 11068 try { 11069 if(SimpleWindow.handleNativeGlobalEvent !is null) { 11070 // it returns zero if the message is handled, so we won't do anything more there 11071 // do I like that though? 11072 int mustReturn; 11073 auto ret = SimpleWindow.handleNativeGlobalEvent(hWnd, iMessage, wParam, lParam, mustReturn); 11074 if(mustReturn) 11075 return ret; 11076 } 11077 11078 if(auto window = hWnd in CapableOfHandlingNativeEvent.nativeHandleMapping) { 11079 if(window.getNativeEventHandler !is null) { 11080 int mustReturn; 11081 auto ret = window.getNativeEventHandler()(hWnd, iMessage, wParam, lParam, mustReturn); 11082 if(mustReturn) 11083 return ret; 11084 } 11085 if(auto w = cast(SimpleWindow) (*window)) 11086 return w.windowProcedure(hWnd, iMessage, wParam, lParam); 11087 else 11088 return DefWindowProc(hWnd, iMessage, wParam, lParam); 11089 } else { 11090 return DefWindowProc(hWnd, iMessage, wParam, lParam); 11091 } 11092 } catch (Exception e) { 11093 try { 11094 sdpy_abort(e); 11095 return 0; 11096 } catch(Exception e) { assert(0); } 11097 } 11098 } 11099 11100 void sdpy_abort(Throwable e) nothrow { 11101 try 11102 MessageBoxA(null, (e.toString() ~ "\0").ptr, "Exception caught in WndProc", 0); 11103 catch(Exception e) 11104 MessageBoxA(null, "Exception.toString threw too!", "Exception caught in WndProc", 0); 11105 ExitProcess(1); 11106 } 11107 11108 mixin template NativeScreenPainterImplementation() { 11109 HDC hdc; 11110 HWND hwnd; 11111 //HDC windowHdc; 11112 HBITMAP oldBmp; 11113 11114 void create(NativeWindowHandle window) { 11115 hwnd = window; 11116 11117 if(auto sw = cast(SimpleWindow) this.window) { 11118 // drawing on a window, double buffer 11119 auto windowHdc = GetDC(hwnd); 11120 11121 auto buffer = sw.impl.buffer; 11122 if(buffer is null) { 11123 hdc = windowHdc; 11124 windowDc = true; 11125 } else { 11126 hdc = CreateCompatibleDC(windowHdc); 11127 11128 ReleaseDC(hwnd, windowHdc); 11129 11130 oldBmp = SelectObject(hdc, buffer); 11131 } 11132 } else { 11133 // drawing on something else, draw directly 11134 hdc = CreateCompatibleDC(null); 11135 SelectObject(hdc, window); 11136 } 11137 11138 // X doesn't draw a text background, so neither should we 11139 SetBkMode(hdc, TRANSPARENT); 11140 11141 ensureDefaultFontLoaded(); 11142 11143 if(defaultGuiFont) { 11144 SelectObject(hdc, defaultGuiFont); 11145 // DeleteObject(defaultGuiFont); 11146 } 11147 } 11148 11149 static HFONT defaultGuiFont; 11150 static void ensureDefaultFontLoaded() { 11151 static bool triedDefaultGuiFont = false; 11152 if(!triedDefaultGuiFont) { 11153 NONCLIENTMETRICS params; 11154 params.cbSize = params.sizeof; 11155 if(SystemParametersInfo(SPI_GETNONCLIENTMETRICS, params.sizeof, ¶ms, 0)) { 11156 defaultGuiFont = CreateFontIndirect(¶ms.lfMessageFont); 11157 } 11158 triedDefaultGuiFont = true; 11159 } 11160 } 11161 11162 private OperatingSystemFont _activeFont; 11163 11164 void setFont(OperatingSystemFont font) { 11165 _activeFont = font; 11166 if(font && font.font) { 11167 if(SelectObject(hdc, font.font) == HGDI_ERROR) { 11168 // error... how to handle tho? 11169 } else { 11170 11171 } 11172 } 11173 else if(defaultGuiFont) 11174 SelectObject(hdc, defaultGuiFont); 11175 } 11176 11177 arsd.color.Rectangle _clipRectangle; 11178 11179 void setClipRectangle(int x, int y, int width, int height) { 11180 auto old = _clipRectangle; 11181 _clipRectangle = arsd.color.Rectangle(Point(x, y), Size(width, height)); 11182 if(old == _clipRectangle) 11183 return; 11184 11185 if(width == 0 || height == 0) { 11186 SelectClipRgn(hdc, null); 11187 } else { 11188 auto region = CreateRectRgn(x, y, x + width, y + height); 11189 SelectClipRgn(hdc, region); 11190 DeleteObject(region); 11191 } 11192 } 11193 11194 11195 // just because we can on Windows... 11196 //void create(Image image); 11197 11198 void invalidateRect(Rectangle invalidRect) { 11199 RECT rect; 11200 rect.left = invalidRect.left; 11201 rect.right = invalidRect.right; 11202 rect.top = invalidRect.top; 11203 rect.bottom = invalidRect.bottom; 11204 InvalidateRect(hwnd, &rect, false); 11205 } 11206 bool manualInvalidations; 11207 11208 void dispose() { 11209 // FIXME: this.window.width/height is probably wrong 11210 // BitBlt(windowHdc, 0, 0, this.window.width, this.window.height, hdc, 0, 0, SRCCOPY); 11211 // ReleaseDC(hwnd, windowHdc); 11212 11213 // FIXME: it shouldn't invalidate the whole thing in all cases... it would be ideal to do this right 11214 if(cast(SimpleWindow) this.window) { 11215 if(!manualInvalidations) 11216 InvalidateRect(hwnd, cast(RECT*)null, false); // no need to erase bg as the whole thing gets bitblt'd ove 11217 } 11218 11219 if(originalPen !is null) 11220 SelectObject(hdc, originalPen); 11221 if(currentPen !is null) 11222 DeleteObject(currentPen); 11223 if(originalBrush !is null) 11224 SelectObject(hdc, originalBrush); 11225 if(currentBrush !is null) 11226 DeleteObject(currentBrush); 11227 11228 SelectObject(hdc, oldBmp); 11229 11230 if(windowDc) 11231 ReleaseDC(hwnd, hdc); 11232 else 11233 DeleteDC(hdc); 11234 11235 if(window.paintingFinishedDg !is null) 11236 window.paintingFinishedDg()(); 11237 } 11238 11239 bool windowDc; 11240 HPEN originalPen; 11241 HPEN currentPen; 11242 11243 Pen _activePen; 11244 11245 Color _outlineColor; 11246 11247 @property void pen(Pen p) { 11248 _activePen = p; 11249 _outlineColor = p.color; 11250 11251 HPEN pen; 11252 if(p.color.a == 0) { 11253 pen = GetStockObject(NULL_PEN); 11254 } else { 11255 int style = PS_SOLID; 11256 final switch(p.style) { 11257 case Pen.Style.Solid: 11258 style = PS_SOLID; 11259 break; 11260 case Pen.Style.Dashed: 11261 style = PS_DASH; 11262 break; 11263 case Pen.Style.Dotted: 11264 style = PS_DOT; 11265 break; 11266 } 11267 pen = CreatePen(style, p.width, RGB(p.color.r, p.color.g, p.color.b)); 11268 } 11269 auto orig = SelectObject(hdc, pen); 11270 if(originalPen is null) 11271 originalPen = orig; 11272 11273 if(currentPen !is null) 11274 DeleteObject(currentPen); 11275 11276 currentPen = pen; 11277 11278 // the outline is like a foreground since it's done that way on X 11279 SetTextColor(hdc, RGB(p.color.r, p.color.g, p.color.b)); 11280 11281 } 11282 11283 @property void rasterOp(RasterOp op) { 11284 int mode; 11285 final switch(op) { 11286 case RasterOp.normal: 11287 mode = R2_COPYPEN; 11288 break; 11289 case RasterOp.xor: 11290 mode = R2_XORPEN; 11291 break; 11292 } 11293 SetROP2(hdc, mode); 11294 } 11295 11296 HBRUSH originalBrush; 11297 HBRUSH currentBrush; 11298 Color _fillColor = Color(1, 1, 1, 1); // what are the odds that they'd set this?? 11299 @property void fillColor(Color c) { 11300 if(c == _fillColor) 11301 return; 11302 _fillColor = c; 11303 HBRUSH brush; 11304 if(c.a == 0) { 11305 brush = GetStockObject(HOLLOW_BRUSH); 11306 } else { 11307 brush = CreateSolidBrush(RGB(c.r, c.g, c.b)); 11308 } 11309 auto orig = SelectObject(hdc, brush); 11310 if(originalBrush is null) 11311 originalBrush = orig; 11312 11313 if(currentBrush !is null) 11314 DeleteObject(currentBrush); 11315 11316 currentBrush = brush; 11317 11318 // background color is NOT set because X doesn't draw text backgrounds 11319 // SetBkColor(hdc, RGB(255, 255, 255)); 11320 } 11321 11322 void drawImage(int x, int y, Image i, int ix, int iy, int w, int h) { 11323 BITMAP bm; 11324 11325 HDC hdcMem = CreateCompatibleDC(hdc); 11326 HBITMAP hbmOld = SelectObject(hdcMem, i.handle); 11327 11328 GetObject(i.handle, bm.sizeof, &bm); 11329 11330 // or should I AlphaBlend!??!?! 11331 BitBlt(hdc, x, y, w /* bm.bmWidth */, /*bm.bmHeight*/ h, hdcMem, ix, iy, SRCCOPY); 11332 11333 SelectObject(hdcMem, hbmOld); 11334 DeleteDC(hdcMem); 11335 } 11336 11337 void drawPixmap(Sprite s, int x, int y, int ix, int iy, int w, int h) { 11338 BITMAP bm; 11339 11340 HDC hdcMem = CreateCompatibleDC(hdc); 11341 HBITMAP hbmOld = SelectObject(hdcMem, s.handle); 11342 11343 GetObject(s.handle, bm.sizeof, &bm); 11344 11345 version(CRuntime_DigitalMars) goto noalpha; 11346 11347 // or should I AlphaBlend!??!?! note it is supposed to be premultiplied http://www.fengyuan.com/article/alphablend.html 11348 if(s.enableAlpha) { 11349 auto dw = w ? w : bm.bmWidth; 11350 auto dh = h ? h : bm.bmHeight; 11351 BLENDFUNCTION bf; 11352 bf.BlendOp = AC_SRC_OVER; 11353 bf.SourceConstantAlpha = 255; 11354 bf.AlphaFormat = AC_SRC_ALPHA; 11355 AlphaBlend(hdc, x, y, dw, dh, hdcMem, ix, iy, dw, dh, bf); 11356 } else { 11357 noalpha: 11358 BitBlt(hdc, x, y, w ? w : bm.bmWidth, h ? h : bm.bmHeight, hdcMem, ix, iy, SRCCOPY); 11359 } 11360 11361 SelectObject(hdcMem, hbmOld); 11362 DeleteDC(hdcMem); 11363 } 11364 11365 Size textSize(scope const(char)[] text) { 11366 bool dummyX; 11367 if(text.length == 0) { 11368 text = " "; 11369 dummyX = true; 11370 } 11371 RECT rect; 11372 WCharzBuffer buffer = WCharzBuffer(text); 11373 DrawTextW(hdc, buffer.ptr, cast(int) buffer.length, &rect, DT_CALCRECT | DT_NOPREFIX); 11374 return Size(dummyX ? 0 : rect.right, rect.bottom); 11375 } 11376 11377 void drawText(int x, int y, int x2, int y2, scope const(char)[] text, uint alignment) { 11378 if(text.length && text[$-1] == '\n') 11379 text = text[0 .. $-1]; // tailing newlines are weird on windows... 11380 if(text.length && text[$-1] == '\r') 11381 text = text[0 .. $-1]; 11382 11383 WCharzBuffer buffer = WCharzBuffer(text, WindowsStringConversionFlags.convertNewLines); 11384 if(x2 == 0 && y2 == 0) { 11385 TextOutW(hdc, x, y, buffer.ptr, cast(int) buffer.length); 11386 } else { 11387 RECT rect; 11388 rect.left = x; 11389 rect.top = y; 11390 rect.right = x2; 11391 rect.bottom = y2; 11392 11393 uint mode = DT_LEFT; 11394 if(alignment & TextAlignment.Right) 11395 mode = DT_RIGHT; 11396 else if(alignment & TextAlignment.Center) 11397 mode = DT_CENTER; 11398 11399 // FIXME: vcenter on windows only works with single line, but I want it to work in all cases 11400 if(alignment & TextAlignment.VerticalCenter) 11401 mode |= DT_VCENTER | DT_SINGLELINE; 11402 11403 DrawTextW(hdc, buffer.ptr, cast(int) buffer.length, &rect, mode | DT_NOPREFIX); 11404 } 11405 11406 /* 11407 uint mode; 11408 11409 if(alignment & TextAlignment.Center) 11410 mode = TA_CENTER; 11411 11412 SetTextAlign(hdc, mode); 11413 */ 11414 } 11415 11416 int fontHeight() { 11417 TEXTMETRIC metric; 11418 if(GetTextMetricsW(hdc, &metric)) { 11419 return metric.tmHeight; 11420 } 11421 11422 return 16; // idk just guessing here, maybe we should throw 11423 } 11424 11425 void drawPixel(int x, int y) { 11426 SetPixel(hdc, x, y, RGB(_activePen.color.r, _activePen.color.g, _activePen.color.b)); 11427 } 11428 11429 // The basic shapes, outlined 11430 11431 void drawLine(int x1, int y1, int x2, int y2) { 11432 MoveToEx(hdc, x1, y1, null); 11433 LineTo(hdc, x2, y2); 11434 } 11435 11436 void drawRectangle(int x, int y, int width, int height) { 11437 // FIXME: with a wider pen this might not draw quite right. im not sure. 11438 gdi.Rectangle(hdc, x, y, x + width, y + height); 11439 } 11440 11441 /// Arguments are the points of the bounding rectangle 11442 void drawEllipse(int x1, int y1, int x2, int y2) { 11443 Ellipse(hdc, x1, y1, x2, y2); 11444 } 11445 11446 void drawArc(int x1, int y1, int width, int height, int start, int finish) { 11447 if((start % (360*64)) == (finish % (360*64))) 11448 drawEllipse(x1, y1, x1 + width, y1 + height); 11449 else { 11450 import core.stdc.math; 11451 float startAngle = cast(float) start / 64.0 / 180.0 * 3.14159265358979323; 11452 float endAngle = cast(float) finish / 64.0 / 180.0 * 3.14159265358979323; 11453 11454 auto c1 = cast(int) roundf(cos(startAngle) * width / 2 + x1 + width / 2); 11455 auto c2 = cast(int) roundf(-sin(startAngle) * height / 2 + y1 + height / 2); 11456 auto c3 = cast(int) roundf(cos(endAngle) * width / 2 + x1 + width / 2); 11457 auto c4 = cast(int) roundf(-sin(endAngle) * height / 2 + y1 + height / 2); 11458 11459 if(_activePen.color.a) 11460 Arc(hdc, x1, y1, x1 + width + 1, y1 + height + 1, c1, c2, c3, c4); 11461 if(_fillColor.a) 11462 Pie(hdc, x1, y1, x1 + width + 1, y1 + height + 1, c1, c2, c3, c4); 11463 } 11464 } 11465 11466 void drawPolygon(Point[] vertexes) { 11467 POINT[] points; 11468 points.length = vertexes.length; 11469 11470 foreach(i, p; vertexes) { 11471 points[i].x = p.x; 11472 points[i].y = p.y; 11473 } 11474 11475 Polygon(hdc, points.ptr, cast(int) points.length); 11476 } 11477 } 11478 11479 11480 // Mix this into the SimpleWindow class 11481 mixin template NativeSimpleWindowImplementation() { 11482 int curHidden = 0; // counter 11483 __gshared static bool[string] knownWinClasses; 11484 static bool altPressed = false; 11485 11486 HANDLE oldCursor; 11487 11488 void hideCursor () { 11489 if(curHidden == 0) 11490 oldCursor = SetCursor(null); 11491 ++curHidden; 11492 } 11493 11494 void showCursor () { 11495 --curHidden; 11496 if(curHidden == 0) { 11497 SetCursor(currentCursor is null ? oldCursor : currentCursor); // show it immediately without waiting for mouse movement 11498 } 11499 } 11500 11501 11502 int minWidth = 0, minHeight = 0, maxWidth = int.max, maxHeight = int.max; 11503 11504 void setMinSize (int minwidth, int minheight) { 11505 minWidth = minwidth; 11506 minHeight = minheight; 11507 } 11508 void setMaxSize (int maxwidth, int maxheight) { 11509 maxWidth = maxwidth; 11510 maxHeight = maxheight; 11511 } 11512 11513 // FIXME i'm not sure that Windows has this functionality 11514 // though it is nonessential anyway. 11515 void setResizeGranularity (int granx, int grany) {} 11516 11517 ScreenPainter getPainter(bool manualInvalidations) { 11518 return ScreenPainter(this, hwnd, manualInvalidations); 11519 } 11520 11521 HBITMAP buffer; 11522 11523 void setTitle(string title) { 11524 WCharzBuffer bfr = WCharzBuffer(title); 11525 SetWindowTextW(hwnd, bfr.ptr); 11526 } 11527 11528 string getTitle() { 11529 auto len = GetWindowTextLengthW(hwnd); 11530 if (!len) 11531 return null; 11532 wchar[256] tmpBuffer; 11533 wchar[] buffer = (len <= tmpBuffer.length) ? tmpBuffer[] : new wchar[len]; 11534 auto len2 = GetWindowTextW(hwnd, buffer.ptr, cast(int) buffer.length); 11535 auto str = buffer[0 .. len2]; 11536 return makeUtf8StringFromWindowsString(str); 11537 } 11538 11539 void move(int x, int y) { 11540 RECT rect; 11541 GetWindowRect(hwnd, &rect); 11542 // move it while maintaining the same size... 11543 MoveWindow(hwnd, x, y, rect.right - rect.left, rect.bottom - rect.top, true); 11544 } 11545 11546 void resize(int w, int h) { 11547 RECT rect; 11548 GetWindowRect(hwnd, &rect); 11549 11550 RECT client; 11551 GetClientRect(hwnd, &client); 11552 11553 rect.right = rect.right - client.right + w; 11554 rect.bottom = rect.bottom - client.bottom + h; 11555 11556 // same position, new size for the client rectangle 11557 MoveWindow(hwnd, rect.left, rect.top, rect.right, rect.bottom, true); 11558 11559 updateOpenglViewportIfNeeded(w, h); 11560 } 11561 11562 void moveResize (int x, int y, int w, int h) { 11563 // what's given is the client rectangle, we need to adjust 11564 11565 RECT rect; 11566 rect.left = x; 11567 rect.top = y; 11568 rect.right = w + x; 11569 rect.bottom = h + y; 11570 if(!AdjustWindowRect(&rect, GetWindowLong(hwnd, GWL_STYLE), GetMenu(hwnd) !is null)) 11571 throw new WindowsApiException("AdjustWindowRect", GetLastError()); 11572 11573 MoveWindow(hwnd, rect.left, rect.top, rect.right - rect.left, rect.bottom - rect.top, true); 11574 updateOpenglViewportIfNeeded(w, h); 11575 if (windowResized !is null) windowResized(w, h); 11576 } 11577 11578 version(without_opengl) {} else { 11579 HGLRC ghRC; 11580 HDC ghDC; 11581 } 11582 11583 void createWindow(int width, int height, string title, OpenGlOptions opengl, SimpleWindow parent) { 11584 string cnamec; 11585 if (sdpyWindowClassStr is null) loadBinNameToWindowClassName(); 11586 if (sdpyWindowClassStr is null || sdpyWindowClassStr[0] == 0) { 11587 cnamec = "DSimpleWindow"; 11588 } else { 11589 cnamec = sdpyWindowClass; 11590 } 11591 11592 WCharzBuffer cn = WCharzBuffer(cnamec); 11593 11594 HINSTANCE hInstance = cast(HINSTANCE) GetModuleHandle(null); 11595 11596 if(cnamec !in knownWinClasses) { 11597 WNDCLASSEX wc; 11598 11599 // FIXME: I might be able to use cbWndExtra to hold the pointer back 11600 // to the object. Maybe. 11601 wc.cbSize = wc.sizeof; 11602 wc.cbClsExtra = 0; 11603 wc.cbWndExtra = 0; 11604 wc.hbrBackground = cast(HBRUSH) (COLOR_WINDOW+1); // GetStockObject(WHITE_BRUSH); 11605 wc.hCursor = LoadCursorW(null, IDC_ARROW); 11606 wc.hIcon = LoadIcon(hInstance, null); 11607 wc.hInstance = hInstance; 11608 wc.lpfnWndProc = &WndProc; 11609 wc.lpszClassName = cn.ptr; 11610 wc.hIconSm = null; 11611 wc.style = CS_HREDRAW | CS_VREDRAW | CS_DBLCLKS; 11612 if(!RegisterClassExW(&wc)) 11613 throw new WindowsApiException("RegisterClassExW", GetLastError()); 11614 knownWinClasses[cnamec] = true; 11615 } 11616 11617 int style; 11618 uint flags = WS_EX_ACCEPTFILES; // accept drag-drop files 11619 11620 // FIXME: windowType and customizationFlags 11621 final switch(windowType) { 11622 case WindowTypes.normal: 11623 if(resizability == Resizability.fixedSize) { 11624 style = WS_SYSMENU | WS_OVERLAPPED | WS_CAPTION; 11625 } else { 11626 style = WS_OVERLAPPEDWINDOW; 11627 } 11628 break; 11629 case WindowTypes.undecorated: 11630 style = WS_POPUP | WS_SYSMENU; 11631 break; 11632 case WindowTypes.eventOnly: 11633 _hidden = true; 11634 break; 11635 case WindowTypes.dropdownMenu: 11636 case WindowTypes.popupMenu: 11637 case WindowTypes.notification: 11638 style = WS_POPUP; 11639 flags |= WS_EX_NOACTIVATE; 11640 break; 11641 case WindowTypes.nestedChild: 11642 style = WS_CHILD; 11643 break; 11644 case WindowTypes.minimallyWrapped: 11645 assert(0, "construct minimally wrapped through the other ctor overlad"); 11646 } 11647 11648 if ((customizationFlags & WindowFlags.extraComposite) != 0) 11649 flags |= WS_EX_LAYERED; // composite window for better performance and effects support 11650 11651 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 11652 CW_USEDEFAULT, CW_USEDEFAULT, width, height, 11653 parent is null ? null : parent.impl.hwnd, null, hInstance, null); 11654 11655 if ((customizationFlags & WindowFlags.extraComposite) != 0) 11656 setOpacity(255); 11657 11658 SimpleWindow.nativeMapping[hwnd] = this; 11659 CapableOfHandlingNativeEvent.nativeHandleMapping[hwnd] = this; 11660 11661 if(windowType == WindowTypes.eventOnly) 11662 return; 11663 11664 HDC hdc = GetDC(hwnd); 11665 11666 11667 version(without_opengl) {} 11668 else { 11669 if(opengl == OpenGlOptions.yes) { 11670 if(!openGlLibrariesSuccessfullyLoaded) throw new Exception("OpenGL libraries did not load"); 11671 static if (SdpyIsUsingIVGLBinds) {if (glbindGetProcAddress("glHint") is null) assert(0, "GL: error loading OpenGL"); } // loads all necessary functions 11672 static if (SdpyIsUsingIVGLBinds) import iv.glbinds; // override druntime windows imports 11673 ghDC = hdc; 11674 PIXELFORMATDESCRIPTOR pfd; 11675 11676 pfd.nSize = PIXELFORMATDESCRIPTOR.sizeof; 11677 pfd.nVersion = 1; 11678 pfd.dwFlags = PFD_DRAW_TO_WINDOW | PFD_SUPPORT_OPENGL | PFD_DOUBLEBUFFER; 11679 pfd.dwLayerMask = PFD_MAIN_PLANE; 11680 pfd.iPixelType = PFD_TYPE_RGBA; 11681 pfd.cColorBits = 24; 11682 pfd.cDepthBits = 24; 11683 pfd.cAccumBits = 0; 11684 pfd.cStencilBits = 8; // any reasonable OpenGL implementation should support this anyway 11685 11686 auto pixelformat = ChoosePixelFormat(hdc, &pfd); 11687 11688 if (pixelformat == 0) 11689 throw new WindowsApiException("ChoosePixelFormat", GetLastError()); 11690 11691 if (SetPixelFormat(hdc, pixelformat, &pfd) == 0) 11692 throw new WindowsApiException("SetPixelFormat", GetLastError()); 11693 11694 if (sdpyOpenGLContextVersion && wglCreateContextAttribsARB is null) { 11695 // windoze is idiotic: we have to have OpenGL context to get function addresses 11696 // so we will create fake context to get that stupid address 11697 auto tmpcc = wglCreateContext(ghDC); 11698 if (tmpcc !is null) { 11699 scope(exit) { wglMakeCurrent(ghDC, null); wglDeleteContext(tmpcc); } 11700 wglMakeCurrent(ghDC, tmpcc); 11701 wglInitOtherFunctions(); 11702 } 11703 } 11704 11705 if (wglCreateContextAttribsARB !is null && sdpyOpenGLContextVersion) { 11706 int[9] contextAttribs = [ 11707 WGL_CONTEXT_MAJOR_VERSION_ARB, (sdpyOpenGLContextVersion>>8), 11708 WGL_CONTEXT_MINOR_VERSION_ARB, (sdpyOpenGLContextVersion&0xff), 11709 WGL_CONTEXT_PROFILE_MASK_ARB, (sdpyOpenGLContextCompatible ? WGL_CONTEXT_COMPATIBILITY_PROFILE_BIT_ARB : WGL_CONTEXT_CORE_PROFILE_BIT_ARB), 11710 // for modern context, set "forward compatibility" flag too 11711 (sdpyOpenGLContextCompatible ? 0/*None*/ : WGL_CONTEXT_FLAGS_ARB), WGL_CONTEXT_FORWARD_COMPATIBLE_BIT_ARB, 11712 0/*None*/, 11713 ]; 11714 ghRC = wglCreateContextAttribsARB(ghDC, null, contextAttribs.ptr); 11715 if (ghRC is null && sdpyOpenGLContextAllowFallback) { 11716 // activate fallback mode 11717 // 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; 11718 ghRC = wglCreateContext(ghDC); 11719 } 11720 if (ghRC is null) 11721 throw new WindowsApiException("wglCreateContextAttribsARB", GetLastError()); 11722 } else { 11723 // try to do at least something 11724 if (sdpyOpenGLContextAllowFallback || sdpyOpenGLContextVersion == 0) { 11725 sdpyOpenGLContextVersion = 0; 11726 ghRC = wglCreateContext(ghDC); 11727 } 11728 if (ghRC is null) 11729 throw new WindowsApiException("wglCreateContext", GetLastError()); 11730 } 11731 } 11732 } 11733 11734 if(opengl == OpenGlOptions.no) { 11735 buffer = CreateCompatibleBitmap(hdc, width, height); 11736 11737 auto hdcBmp = CreateCompatibleDC(hdc); 11738 // make sure it's filled with a blank slate 11739 auto oldBmp = SelectObject(hdcBmp, buffer); 11740 auto oldBrush = SelectObject(hdcBmp, GetStockObject(WHITE_BRUSH)); 11741 auto oldPen = SelectObject(hdcBmp, GetStockObject(WHITE_PEN)); 11742 gdi.Rectangle(hdcBmp, 0, 0, width, height); 11743 SelectObject(hdcBmp, oldBmp); 11744 SelectObject(hdcBmp, oldBrush); 11745 SelectObject(hdcBmp, oldPen); 11746 DeleteDC(hdcBmp); 11747 11748 bmpWidth = width; 11749 bmpHeight = height; 11750 11751 ReleaseDC(hwnd, hdc); // we keep this in opengl mode since it is a class member now 11752 } 11753 11754 // We want the window's client area to match the image size 11755 RECT rcClient, rcWindow; 11756 POINT ptDiff; 11757 GetClientRect(hwnd, &rcClient); 11758 GetWindowRect(hwnd, &rcWindow); 11759 ptDiff.x = (rcWindow.right - rcWindow.left) - rcClient.right; 11760 ptDiff.y = (rcWindow.bottom - rcWindow.top) - rcClient.bottom; 11761 MoveWindow(hwnd,rcWindow.left, rcWindow.top, width + ptDiff.x, height + ptDiff.y, true); 11762 11763 if ((customizationFlags&WindowFlags.dontAutoShow) == 0) { 11764 ShowWindow(hwnd, SW_SHOWNORMAL); 11765 } else { 11766 _hidden = true; 11767 } 11768 this._visibleForTheFirstTimeCalled = false; // hack! 11769 } 11770 11771 11772 void dispose() { 11773 if(buffer) 11774 DeleteObject(buffer); 11775 } 11776 11777 void closeWindow() { 11778 if(ghRC) { 11779 wglDeleteContext(ghRC); 11780 ghRC = null; 11781 } 11782 DestroyWindow(hwnd); 11783 } 11784 11785 bool setOpacity(ubyte alpha) { 11786 return SetLayeredWindowAttributes(hwnd, 0, alpha, LWA_ALPHA) == TRUE; 11787 } 11788 11789 HANDLE currentCursor; 11790 11791 // returns zero if it recognized the event 11792 static int triggerEvents(HWND hwnd, uint msg, WPARAM wParam, LPARAM lParam, int offsetX, int offsetY, SimpleWindow wind) { 11793 MouseEvent mouse; 11794 11795 void mouseEvent(bool isScreen, ulong mods) { 11796 auto x = LOWORD(lParam); 11797 auto y = HIWORD(lParam); 11798 if(isScreen) { 11799 POINT p; 11800 p.x = x; 11801 p.y = y; 11802 ScreenToClient(hwnd, &p); 11803 x = cast(ushort) p.x; 11804 y = cast(ushort) p.y; 11805 } 11806 11807 if(wind.resizability == Resizability.automaticallyScaleIfPossible) { 11808 x = cast(ushort)( x * wind._virtualWidth / wind._width ); 11809 y = cast(ushort)( y * wind._virtualHeight / wind._height ); 11810 } 11811 11812 mouse.x = x + offsetX; 11813 mouse.y = y + offsetY; 11814 11815 wind.mdx(mouse); 11816 mouse.modifierState = cast(int) mods; 11817 mouse.window = wind; 11818 11819 if(wind.handleMouseEvent) 11820 wind.handleMouseEvent(mouse); 11821 } 11822 11823 switch(msg) { 11824 case WM_GETMINMAXINFO: 11825 MINMAXINFO* mmi = cast(MINMAXINFO*) lParam; 11826 11827 if(wind.minWidth > 0) { 11828 RECT rect; 11829 rect.left = 100; 11830 rect.top = 100; 11831 rect.right = wind.minWidth + 100; 11832 rect.bottom = wind.minHeight + 100; 11833 if(!AdjustWindowRect(&rect, GetWindowLong(wind.hwnd, GWL_STYLE), GetMenu(wind.hwnd) !is null)) 11834 throw new WindowsApiException("AdjustWindowRect", GetLastError()); 11835 11836 mmi.ptMinTrackSize.x = rect.right - rect.left; 11837 mmi.ptMinTrackSize.y = rect.bottom - rect.top; 11838 } 11839 11840 if(wind.maxWidth < int.max) { 11841 RECT rect; 11842 rect.left = 100; 11843 rect.top = 100; 11844 rect.right = wind.maxWidth + 100; 11845 rect.bottom = wind.maxHeight + 100; 11846 if(!AdjustWindowRect(&rect, GetWindowLong(wind.hwnd, GWL_STYLE), GetMenu(wind.hwnd) !is null)) 11847 throw new WindowsApiException("AdjustWindowRect", GetLastError()); 11848 11849 mmi.ptMaxTrackSize.x = rect.right - rect.left; 11850 mmi.ptMaxTrackSize.y = rect.bottom - rect.top; 11851 } 11852 break; 11853 case WM_CHAR: 11854 wchar c = cast(wchar) wParam; 11855 if(wind.handleCharEvent) 11856 wind.handleCharEvent(cast(dchar) c); 11857 break; 11858 case WM_SETFOCUS: 11859 case WM_KILLFOCUS: 11860 wind._focused = (msg == WM_SETFOCUS); 11861 if (msg == WM_SETFOCUS) altPressed = false; //k8: reset alt state on defocus (it is better than nothing...) 11862 if(wind.onFocusChange) 11863 wind.onFocusChange(msg == WM_SETFOCUS); 11864 break; 11865 11866 case WM_SYSKEYDOWN: 11867 goto case; 11868 case WM_SYSKEYUP: 11869 if(lParam & (1 << 29)) { 11870 goto case; 11871 } else { 11872 // no window has keyboard focus 11873 goto default; 11874 } 11875 case WM_KEYDOWN: 11876 case WM_KEYUP: 11877 KeyEvent ev; 11878 ev.key = cast(Key) wParam; 11879 ev.pressed = (msg == WM_KEYDOWN || msg == WM_SYSKEYDOWN); 11880 if (wParam == 0x12) ev.key = Key.Alt; // windows does it this way 11881 11882 ev.hardwareCode = (lParam & 0xff0000) >> 16; 11883 11884 if(GetKeyState(Key.Shift)&0x8000 || GetKeyState(Key.Shift_r)&0x8000) 11885 ev.modifierState |= ModifierState.shift; 11886 //k8: this doesn't work; thanks for nothing, windows 11887 /*if(GetKeyState(Key.Alt)&0x8000 || GetKeyState(Key.Alt_r)&0x8000) 11888 ev.modifierState |= ModifierState.alt;*/ 11889 // this never seems to actually be set 11890 // if (lParam & 0x2000 /* KF_ALTDOWN */) ev.modifierState |= ModifierState.alt; else ev.modifierState &= ~ModifierState.alt; 11891 11892 if (wParam == 0x12) { 11893 altPressed = (msg == WM_SYSKEYDOWN); 11894 } 11895 11896 if(msg == WM_KEYDOWN || msg == WM_KEYUP) { 11897 altPressed = false; 11898 } 11899 // sdpyPrintDebugString(altPressed ? "alt down" : " up "); 11900 11901 if (altPressed) ev.modifierState |= ModifierState.alt; else ev.modifierState &= ~ModifierState.alt; 11902 if(GetKeyState(Key.Ctrl)&0x8000 || GetKeyState(Key.Ctrl_r)&0x8000) 11903 ev.modifierState |= ModifierState.ctrl; 11904 if(GetKeyState(Key.Windows)&0x8000 || GetKeyState(Key.Windows_r)&0x8000) 11905 ev.modifierState |= ModifierState.windows; 11906 if(GetKeyState(Key.NumLock)) 11907 ev.modifierState |= ModifierState.numLock; 11908 if(GetKeyState(Key.CapsLock)) 11909 ev.modifierState |= ModifierState.capsLock; 11910 11911 /+ 11912 // we always want to send the character too, so let's convert it 11913 ubyte[256] state; 11914 wchar[16] buffer; 11915 GetKeyboardState(state.ptr); 11916 ToUnicodeEx(wParam, lParam, state.ptr, buffer.ptr, buffer.length, 0, null); 11917 11918 foreach(dchar d; buffer) { 11919 ev.character = d; 11920 break; 11921 } 11922 +/ 11923 11924 ev.window = wind; 11925 if(wind.handleKeyEvent) 11926 wind.handleKeyEvent(ev); 11927 break; 11928 case 0x020a /*WM_MOUSEWHEEL*/: 11929 // send click 11930 mouse.type = cast(MouseEventType) 1; 11931 mouse.button = ((GET_WHEEL_DELTA_WPARAM(wParam) > 0) ? MouseButton.wheelUp : MouseButton.wheelDown); 11932 mouseEvent(true, LOWORD(wParam)); 11933 11934 // also send release 11935 mouse.type = cast(MouseEventType) 2; 11936 mouse.button = ((GET_WHEEL_DELTA_WPARAM(wParam) > 0) ? MouseButton.wheelUp : MouseButton.wheelDown); 11937 mouseEvent(true, LOWORD(wParam)); 11938 break; 11939 case WM_MOUSEMOVE: 11940 mouse.type = cast(MouseEventType) 0; 11941 mouseEvent(false, wParam); 11942 break; 11943 case WM_LBUTTONDOWN: 11944 case WM_LBUTTONDBLCLK: 11945 mouse.type = cast(MouseEventType) 1; 11946 mouse.button = MouseButton.left; 11947 mouse.doubleClick = msg == WM_LBUTTONDBLCLK; 11948 mouseEvent(false, wParam); 11949 break; 11950 case WM_LBUTTONUP: 11951 mouse.type = cast(MouseEventType) 2; 11952 mouse.button = MouseButton.left; 11953 mouseEvent(false, wParam); 11954 break; 11955 case WM_RBUTTONDOWN: 11956 case WM_RBUTTONDBLCLK: 11957 mouse.type = cast(MouseEventType) 1; 11958 mouse.button = MouseButton.right; 11959 mouse.doubleClick = msg == WM_RBUTTONDBLCLK; 11960 mouseEvent(false, wParam); 11961 break; 11962 case WM_RBUTTONUP: 11963 mouse.type = cast(MouseEventType) 2; 11964 mouse.button = MouseButton.right; 11965 mouseEvent(false, wParam); 11966 break; 11967 case WM_MBUTTONDOWN: 11968 case WM_MBUTTONDBLCLK: 11969 mouse.type = cast(MouseEventType) 1; 11970 mouse.button = MouseButton.middle; 11971 mouse.doubleClick = msg == WM_MBUTTONDBLCLK; 11972 mouseEvent(false, wParam); 11973 break; 11974 case WM_MBUTTONUP: 11975 mouse.type = cast(MouseEventType) 2; 11976 mouse.button = MouseButton.middle; 11977 mouseEvent(false, wParam); 11978 break; 11979 case WM_XBUTTONDOWN: 11980 case WM_XBUTTONDBLCLK: 11981 mouse.type = cast(MouseEventType) 1; 11982 mouse.button = HIWORD(wParam) == 1 ? MouseButton.backButton : MouseButton.forwardButton; 11983 mouse.doubleClick = msg == WM_XBUTTONDBLCLK; 11984 mouseEvent(false, wParam); 11985 return 1; // MSDN says special treatment here, return TRUE to bypass simulation programs 11986 case WM_XBUTTONUP: 11987 mouse.type = cast(MouseEventType) 2; 11988 mouse.button = HIWORD(wParam) == 1 ? MouseButton.backButton : MouseButton.forwardButton; 11989 mouseEvent(false, wParam); 11990 return 1; // see: https://msdn.microsoft.com/en-us/library/windows/desktop/ms646246(v=vs.85).aspx 11991 11992 default: return 1; 11993 } 11994 return 0; 11995 } 11996 11997 HWND hwnd; 11998 private int oldWidth; 11999 private int oldHeight; 12000 private bool inSizeMove; 12001 12002 /++ 12003 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. 12004 12005 History: 12006 Added November 23, 2021 12007 12008 Not fully stable, may be moved out of the impl struct. 12009 12010 Default value changed to `true` on February 15, 2021 12011 +/ 12012 bool doLiveResizing = true; 12013 12014 package int bmpWidth; 12015 package int bmpHeight; 12016 12017 // the extern(Windows) wndproc should just forward to this 12018 LRESULT windowProcedure(HWND hwnd, uint msg, WPARAM wParam, LPARAM lParam) { 12019 try { 12020 assert(hwnd is this.hwnd); 12021 12022 if(triggerEvents(hwnd, msg, wParam, lParam, 0, 0, this)) 12023 switch(msg) { 12024 case WM_MENUCHAR: // menu active but key not associated with a thing. 12025 // you would ideally use this for like a search function but sdpy not that ideally designed. alas. 12026 // The main things we can do are select, execute, close, or ignore 12027 // the default is ignore, but it doesn't *just* ignore it - it also dings an audio alert to 12028 // the user. This can be a bit annoying for sdpy things so instead im overriding and setting it 12029 // to close, which can be really annoying when you hit the wrong button. but meh i think for sdpy 12030 // that's the lesser bad choice rn. Can always override by returning true in triggerEvents.... 12031 12032 // returns the value in the *high order word* of the return value 12033 // hence the << 16 12034 return 1 << 16; // MNC_CLOSE, close the menu without dinging at the user 12035 case WM_SETCURSOR: 12036 if(cast(HWND) wParam !is hwnd) 12037 return 0; // further processing elsewhere 12038 12039 if(LOWORD(lParam) == HTCLIENT && (this.curHidden > 0 || currentCursor !is null)) { 12040 SetCursor(this.curHidden > 0 ? null : currentCursor); 12041 return 1; 12042 } else { 12043 return DefWindowProc(hwnd, msg, wParam, lParam); 12044 } 12045 //break; 12046 12047 case WM_CLOSE: 12048 if (this.closeQuery !is null) this.closeQuery(); else this.close(); 12049 break; 12050 case WM_DESTROY: 12051 if (this.onDestroyed !is null) try { this.onDestroyed(); } catch (Exception e) {} // sorry 12052 SimpleWindow.nativeMapping.remove(hwnd); 12053 CapableOfHandlingNativeEvent.nativeHandleMapping.remove(hwnd); 12054 12055 bool anyImportant = false; 12056 foreach(SimpleWindow w; SimpleWindow.nativeMapping) 12057 if(w.beingOpenKeepsAppOpen) { 12058 anyImportant = true; 12059 break; 12060 } 12061 if(!anyImportant) { 12062 PostQuitMessage(0); 12063 } 12064 break; 12065 case 0x02E0 /*WM_DPICHANGED*/: 12066 this.actualDpi_ = LOWORD(wParam); // hiword is the y param but it is the same per docs 12067 12068 RECT* prcNewWindow = cast(RECT*)lParam; 12069 // docs say this is the recommended position and we should honor it 12070 SetWindowPos(hwnd, 12071 null, 12072 prcNewWindow.left, 12073 prcNewWindow.top, 12074 prcNewWindow.right - prcNewWindow.left, 12075 prcNewWindow.bottom - prcNewWindow.top, 12076 SWP_NOZORDER | SWP_NOACTIVATE); 12077 12078 // doing this because of https://github.com/microsoft/Windows-classic-samples/blob/main/Samples/DPIAwarenessPerWindow/client/DpiAwarenessContext.cpp 12079 // im not sure it is completely correct 12080 // but without it the tabs and such do look weird as things change. 12081 if(SystemParametersInfoForDpi) { 12082 LOGFONT lfText; 12083 SystemParametersInfoForDpi(SPI_GETICONTITLELOGFONT, lfText.sizeof, &lfText, FALSE, this.actualDpi_); 12084 HFONT hFontNew = CreateFontIndirect(&lfText); 12085 if (hFontNew) 12086 { 12087 //DeleteObject(hFontOld); 12088 static extern(Windows) BOOL helper(HWND hWnd, LPARAM lParam) { 12089 SendMessage(hWnd, WM_SETFONT, cast(WPARAM)lParam, MAKELPARAM(TRUE, 0)); 12090 return TRUE; 12091 } 12092 EnumChildWindows(hwnd, &helper, cast(LPARAM) hFontNew); 12093 } 12094 } 12095 12096 if(this.onDpiChanged) 12097 this.onDpiChanged(); 12098 break; 12099 case WM_ENTERIDLE: 12100 // when a menu is up, it stops normal event processing (modal message loop) 12101 // but this at least gives us a chance to SOMETIMES catch up 12102 // FIXME: I can use SetTimer while idle to keep working i think... but idk when i'd destroy it. 12103 SimpleWindow.processAllCustomEvents; 12104 SimpleWindow.processAllCustomEvents; 12105 SleepEx(0, true); 12106 break; 12107 case WM_SIZE: 12108 if(wParam == 1 /* SIZE_MINIMIZED */) 12109 break; 12110 _width = LOWORD(lParam); 12111 _height = HIWORD(lParam); 12112 12113 // I want to avoid tearing in the windows (my code is inefficient 12114 // so this is a hack around that) so while sizing, we don't trigger, 12115 // but we do want to trigger on events like mazimize. 12116 if(!inSizeMove || doLiveResizing) 12117 goto size_changed; 12118 break; 12119 /+ 12120 case WM_SIZING: 12121 writeln("size"); 12122 break; 12123 +/ 12124 // I don't like the tearing I get when redrawing on WM_SIZE 12125 // (I know there's other ways to fix that but I don't like that behavior anyway) 12126 // so instead it is going to redraw only at the end of a size. 12127 case 0x0231: /* WM_ENTERSIZEMOVE */ 12128 inSizeMove = true; 12129 break; 12130 case 0x0232: /* WM_EXITSIZEMOVE */ 12131 inSizeMove = false; 12132 12133 size_changed: 12134 12135 // nothing relevant changed, don't bother redrawing 12136 if(oldWidth == _width && oldHeight == _height) { 12137 break; 12138 } 12139 12140 // note: OpenGL windows don't use a backing bmp, so no need to change them 12141 // if resizability is anything other than allowResizing, it is meant to either stretch the one image or just do nothing 12142 if(openglMode == OpenGlOptions.no) { // && resizability == Resizability.allowResizing) { 12143 // gotta get the double buffer bmp to match the window 12144 // FIXME: could this be more efficient? it never relinquishes a large bitmap 12145 12146 // if it is auto-scaled, we keep the backing bitmap the same size all the time 12147 if(resizability != Resizability.automaticallyScaleIfPossible) 12148 if(_width > bmpWidth || _height > bmpHeight) { 12149 auto hdc = GetDC(hwnd); 12150 auto oldBuffer = buffer; 12151 buffer = CreateCompatibleBitmap(hdc, _width, _height); 12152 12153 auto hdcBmp = CreateCompatibleDC(hdc); 12154 auto oldBmp = SelectObject(hdcBmp, buffer); 12155 12156 auto hdcOldBmp = CreateCompatibleDC(hdc); 12157 auto oldOldBmp = SelectObject(hdcOldBmp, oldBuffer); 12158 12159 /+ 12160 RECT r; 12161 r.left = 0; 12162 r.top = 0; 12163 r.right = width; 12164 r.bottom = height; 12165 auto c = Color.green; 12166 auto brush = CreateSolidBrush(RGB(c.r, c.g, c.b)); 12167 FillRect(hdcBmp, &r, brush); 12168 DeleteObject(brush); 12169 +/ 12170 12171 BitBlt(hdcBmp, 0, 0, bmpWidth, bmpHeight, hdcOldBmp, 0, 0, SRCCOPY); 12172 12173 bmpWidth = _width; 12174 bmpHeight = _height; 12175 12176 SelectObject(hdcOldBmp, oldOldBmp); 12177 DeleteDC(hdcOldBmp); 12178 12179 SelectObject(hdcBmp, oldBmp); 12180 DeleteDC(hdcBmp); 12181 12182 ReleaseDC(hwnd, hdc); 12183 12184 DeleteObject(oldBuffer); 12185 } 12186 } 12187 12188 updateOpenglViewportIfNeeded(_width, _height); 12189 12190 if(resizability != Resizability.automaticallyScaleIfPossible) 12191 if(windowResized !is null) 12192 windowResized(_width, _height); 12193 12194 if(inSizeMove) { 12195 SimpleWindow.processAllCustomEvents(); 12196 SimpleWindow.processAllCustomEvents(); 12197 } else { 12198 // when it is all done, make sure everything is freshly drawn or there might be 12199 // weird bugs left. 12200 RedrawWindow(hwnd, null, null, RDW_ERASE | RDW_INVALIDATE | RDW_ALLCHILDREN); 12201 } 12202 12203 oldWidth = this._width; 12204 oldHeight = this._height; 12205 break; 12206 case WM_ERASEBKGND: 12207 // call `visibleForTheFirstTime` here, so we can do initialization as early as possible 12208 if (!this._visibleForTheFirstTimeCalled) { 12209 this._visibleForTheFirstTimeCalled = true; 12210 if (this.visibleForTheFirstTime !is null) { 12211 this.visibleForTheFirstTime(); 12212 } 12213 } 12214 // block it in OpenGL mode, 'cause no sane person will (or should) draw windows controls over OpenGL scene 12215 version(without_opengl) {} else { 12216 if (openglMode == OpenGlOptions.yes) return 1; 12217 } 12218 // call windows default handler, so it can paint standard controls 12219 goto default; 12220 case WM_CTLCOLORBTN: 12221 case WM_CTLCOLORSTATIC: 12222 SetBkMode(cast(HDC) wParam, TRANSPARENT); 12223 return cast(typeof(return)) //GetStockObject(NULL_BRUSH); 12224 GetSysColorBrush(COLOR_3DFACE); 12225 //break; 12226 case WM_SHOWWINDOW: 12227 this._visible = (wParam != 0); 12228 if (!this._visibleForTheFirstTimeCalled && this._visible) { 12229 this._visibleForTheFirstTimeCalled = true; 12230 if (this.visibleForTheFirstTime !is null) { 12231 this.visibleForTheFirstTime(); 12232 } 12233 } 12234 if (this.visibilityChanged !is null) this.visibilityChanged(this._visible); 12235 break; 12236 case WM_PAINT: { 12237 if (!this._visibleForTheFirstTimeCalled) { 12238 this._visibleForTheFirstTimeCalled = true; 12239 if (this.visibleForTheFirstTime !is null) { 12240 this.visibleForTheFirstTime(); 12241 } 12242 } 12243 12244 BITMAP bm; 12245 PAINTSTRUCT ps; 12246 12247 HDC hdc = BeginPaint(hwnd, &ps); 12248 12249 if(openglMode == OpenGlOptions.no) { 12250 12251 HDC hdcMem = CreateCompatibleDC(hdc); 12252 HBITMAP hbmOld = SelectObject(hdcMem, buffer); 12253 12254 GetObject(buffer, bm.sizeof, &bm); 12255 12256 // FIXME: only BitBlt the invalidated rectangle, not the whole thing 12257 if(resizability == Resizability.automaticallyScaleIfPossible) 12258 StretchBlt(hdc, 0, 0, this._width, this._height, hdcMem, 0, 0, bm.bmWidth, bm.bmHeight, SRCCOPY); 12259 else 12260 BitBlt(hdc, 0, 0, bm.bmWidth, bm.bmHeight, hdcMem, 0, 0, SRCCOPY); 12261 //BitBlt(hdc, ps.rcPaint.left, ps.rcPaint.top, ps.rcPaint.right - ps.rcPaint.left, ps.rcPaint.top - ps.rcPaint.bottom, hdcMem, 0, 0, SRCCOPY); 12262 12263 SelectObject(hdcMem, hbmOld); 12264 DeleteDC(hdcMem); 12265 EndPaint(hwnd, &ps); 12266 } else { 12267 EndPaint(hwnd, &ps); 12268 version(without_opengl) {} else 12269 redrawOpenGlSceneSoon(); 12270 } 12271 } break; 12272 default: 12273 return DefWindowProc(hwnd, msg, wParam, lParam); 12274 } 12275 return 0; 12276 12277 } 12278 catch(Throwable t) { 12279 sdpyPrintDebugString(t.toString); 12280 return 0; 12281 } 12282 } 12283 } 12284 12285 mixin template NativeImageImplementation() { 12286 HBITMAP handle; 12287 ubyte* rawData; 12288 12289 final: 12290 12291 Color getPixel(int x, int y) { 12292 auto itemsPerLine = ((cast(int) width * 3 + 3) / 4) * 4; 12293 // remember, bmps are upside down 12294 auto offset = itemsPerLine * (height - y - 1) + x * 3; 12295 12296 Color c; 12297 if(enableAlpha) 12298 c.a = rawData[offset + 3]; 12299 else 12300 c.a = 255; 12301 c.b = rawData[offset + 0]; 12302 c.g = rawData[offset + 1]; 12303 c.r = rawData[offset + 2]; 12304 c.unPremultiply(); 12305 return c; 12306 } 12307 12308 void setPixel(int x, int y, Color c) { 12309 auto itemsPerLine = ((cast(int) width * 3 + 3) / 4) * 4; 12310 // remember, bmps are upside down 12311 auto offset = itemsPerLine * (height - y - 1) + x * 3; 12312 12313 if(enableAlpha) 12314 c.premultiply(); 12315 12316 rawData[offset + 0] = c.b; 12317 rawData[offset + 1] = c.g; 12318 rawData[offset + 2] = c.r; 12319 if(enableAlpha) 12320 rawData[offset + 3] = c.a; 12321 } 12322 12323 void convertToRgbaBytes(ubyte[] where) { 12324 assert(where.length == this.width * this.height * 4); 12325 12326 auto itemsPerLine = ((cast(int) width * 3 + 3) / 4) * 4; 12327 int idx = 0; 12328 int offset = itemsPerLine * (height - 1); 12329 // remember, bmps are upside down 12330 for(int y = height - 1; y >= 0; y--) { 12331 auto offsetStart = offset; 12332 for(int x = 0; x < width; x++) { 12333 where[idx + 0] = rawData[offset + 2]; // r 12334 where[idx + 1] = rawData[offset + 1]; // g 12335 where[idx + 2] = rawData[offset + 0]; // b 12336 if(enableAlpha) { 12337 where[idx + 3] = rawData[offset + 3]; // a 12338 unPremultiplyRgba(where[idx .. idx + 4]); 12339 offset++; 12340 } else 12341 where[idx + 3] = 255; // a 12342 idx += 4; 12343 offset += 3; 12344 } 12345 12346 offset = offsetStart - itemsPerLine; 12347 } 12348 } 12349 12350 void setFromRgbaBytes(in ubyte[] what) { 12351 assert(what.length == this.width * this.height * 4); 12352 12353 auto itemsPerLine = enableAlpha ? (width * 4) : (((cast(int) width * 3 + 3) / 4) * 4); 12354 int idx = 0; 12355 int offset = itemsPerLine * (height - 1); 12356 // remember, bmps are upside down 12357 for(int y = height - 1; y >= 0; y--) { 12358 auto offsetStart = offset; 12359 for(int x = 0; x < width; x++) { 12360 if(enableAlpha) { 12361 auto a = what[idx + 3]; 12362 12363 rawData[offset + 2] = (a * what[idx + 0]) / 255; // r 12364 rawData[offset + 1] = (a * what[idx + 1]) / 255; // g 12365 rawData[offset + 0] = (a * what[idx + 2]) / 255; // b 12366 rawData[offset + 3] = a; // a 12367 //premultiplyBgra(rawData[offset .. offset + 4]); 12368 offset++; 12369 } else { 12370 rawData[offset + 2] = what[idx + 0]; // r 12371 rawData[offset + 1] = what[idx + 1]; // g 12372 rawData[offset + 0] = what[idx + 2]; // b 12373 } 12374 idx += 4; 12375 offset += 3; 12376 } 12377 12378 offset = offsetStart - itemsPerLine; 12379 } 12380 } 12381 12382 12383 void createImage(int width, int height, bool forcexshm=false, bool enableAlpha = false) { 12384 BITMAPINFO infoheader; 12385 infoheader.bmiHeader.biSize = infoheader.bmiHeader.sizeof; 12386 infoheader.bmiHeader.biWidth = width; 12387 infoheader.bmiHeader.biHeight = height; 12388 infoheader.bmiHeader.biPlanes = 1; 12389 infoheader.bmiHeader.biBitCount = enableAlpha ? 32: 24; 12390 infoheader.bmiHeader.biCompression = BI_RGB; 12391 12392 handle = CreateDIBSection( 12393 null, 12394 &infoheader, 12395 DIB_RGB_COLORS, 12396 cast(void**) &rawData, 12397 null, 12398 0); 12399 if(handle is null) 12400 throw new WindowsApiException("create image failed", GetLastError()); 12401 12402 } 12403 12404 void dispose() { 12405 DeleteObject(handle); 12406 } 12407 } 12408 12409 enum KEY_ESCAPE = 27; 12410 } 12411 version(X11) { 12412 /// This is the default font used. You might change this before doing anything else with 12413 /// the library if you want to try something else. Surround that in `static if(UsingSimpledisplayX11)` 12414 /// for cross-platform compatibility. 12415 //__gshared string xfontstr = "-*-dejavu sans-medium-r-*-*-12-*-*-*-*-*-*-*"; 12416 //__gshared string xfontstr = "-*-dejavu sans-medium-r-*-*-12-*-*-*-*-*-*-*"; 12417 __gshared string xfontstr = "-*-lucida-medium-r-normal-sans-12-*-*-*-*-*-*-*"; 12418 //__gshared string xfontstr = "-*-fixed-medium-r-*-*-14-*-*-*-*-*-*-*"; 12419 12420 alias int delegate(XEvent) NativeEventHandler; 12421 alias Window NativeWindowHandle; 12422 12423 enum KEY_ESCAPE = 9; 12424 12425 mixin template NativeScreenPainterImplementation() { 12426 Display* display; 12427 Drawable d; 12428 Drawable destiny; 12429 12430 // FIXME: should the gc be static too so it isn't recreated every time draw is called? 12431 GC gc; 12432 12433 __gshared bool fontAttempted; 12434 12435 __gshared XFontStruct* defaultfont; 12436 __gshared XFontSet defaultfontset; 12437 12438 XFontStruct* font; 12439 XFontSet fontset; 12440 12441 void create(NativeWindowHandle window) { 12442 this.display = XDisplayConnection.get(); 12443 12444 Drawable buffer = None; 12445 if(auto sw = cast(SimpleWindow) this.window) { 12446 buffer = sw.impl.buffer; 12447 this.destiny = cast(Drawable) window; 12448 } else { 12449 buffer = cast(Drawable) window; 12450 this.destiny = None; 12451 } 12452 12453 this.d = cast(Drawable) buffer; 12454 12455 auto dgc = DefaultGC(display, DefaultScreen(display)); 12456 12457 this.gc = XCreateGC(display, d, 0, null); 12458 12459 XCopyGC(display, dgc, 0xffffffff, this.gc); 12460 12461 ensureDefaultFontLoaded(); 12462 12463 font = defaultfont; 12464 fontset = defaultfontset; 12465 12466 if(font) { 12467 XSetFont(display, gc, font.fid); 12468 } 12469 } 12470 12471 static void ensureDefaultFontLoaded() { 12472 if(!fontAttempted) { 12473 auto display = XDisplayConnection.get; 12474 auto font = XLoadQueryFont(display, xfontstr.ptr); 12475 // if the user font choice fails, fixed is pretty reliable (required by X to start!) and not bad either 12476 if(font is null) { 12477 xfontstr = "-*-fixed-medium-r-*-*-13-*-*-*-*-*-*-*"; 12478 font = XLoadQueryFont(display, xfontstr.ptr); 12479 } 12480 12481 char** lol; 12482 int lol2; 12483 char* lol3; 12484 auto fontset = XCreateFontSet(display, xfontstr.ptr, &lol, &lol2, &lol3); 12485 12486 fontAttempted = true; 12487 12488 defaultfont = font; 12489 defaultfontset = fontset; 12490 } 12491 } 12492 12493 arsd.color.Rectangle _clipRectangle; 12494 void setClipRectangle(int x, int y, int width, int height) { 12495 auto old = _clipRectangle; 12496 _clipRectangle = arsd.color.Rectangle(Point(x, y), Size(width, height)); 12497 if(old == _clipRectangle) 12498 return; 12499 12500 if(width == 0 || height == 0) { 12501 XSetClipMask(display, gc, None); 12502 12503 if(xrenderPicturePainter) { 12504 12505 XRectangle[1] rects; 12506 rects[0] = XRectangle(short.min, short.min, short.max, short.max); 12507 XRenderSetPictureClipRectangles(display, xrenderPicturePainter, 0, 0, rects.ptr, cast(int) rects.length); 12508 } 12509 12510 version(with_xft) { 12511 if(xftFont is null || xftDraw is null) 12512 return; 12513 XftDrawSetClip(xftDraw, null); 12514 } 12515 } else { 12516 XRectangle[1] rects; 12517 rects[0] = XRectangle(cast(short)(x), cast(short)(y), cast(short) width, cast(short) height); 12518 XSetClipRectangles(XDisplayConnection.get, gc, 0, 0, rects.ptr, 1, 0); 12519 12520 if(xrenderPicturePainter) 12521 XRenderSetPictureClipRectangles(display, xrenderPicturePainter, 0, 0, rects.ptr, cast(int) rects.length); 12522 12523 version(with_xft) { 12524 if(xftFont is null || xftDraw is null) 12525 return; 12526 XftDrawSetClipRectangles(xftDraw, 0, 0, rects.ptr, 1); 12527 } 12528 } 12529 } 12530 12531 version(with_xft) { 12532 XftFont* xftFont; 12533 XftDraw* xftDraw; 12534 12535 XftColor xftColor; 12536 12537 void updateXftColor() { 12538 if(xftFont is null) 12539 return; 12540 12541 // not bothering with XftColorFree since p sure i don't need it on 24 bit displays.... 12542 XRenderColor colorIn = XRenderColor(_outlineColor.r * 255, _outlineColor.g * 255, _outlineColor.b * 255, _outlineColor.a * 255); 12543 12544 XftColorAllocValue( 12545 display, 12546 DefaultVisual(display, DefaultScreen(display)), 12547 DefaultColormap(display, 0), 12548 &colorIn, 12549 &xftColor 12550 ); 12551 } 12552 } 12553 12554 private OperatingSystemFont _activeFont; 12555 void setFont(OperatingSystemFont font) { 12556 _activeFont = font; 12557 version(with_xft) { 12558 if(font && font.isXft && font.xftFont) 12559 this.xftFont = font.xftFont; 12560 else 12561 this.xftFont = null; 12562 12563 if(this.xftFont) { 12564 if(xftDraw is null) { 12565 xftDraw = XftDrawCreate( 12566 display, 12567 d, 12568 DefaultVisual(display, DefaultScreen(display)), 12569 DefaultColormap(display, 0) 12570 ); 12571 12572 updateXftColor(); 12573 } 12574 12575 return; 12576 } 12577 } 12578 12579 if(font && font.font) { 12580 this.font = font.font; 12581 this.fontset = font.fontset; 12582 XSetFont(display, gc, font.font.fid); 12583 } else { 12584 this.font = defaultfont; 12585 this.fontset = defaultfontset; 12586 } 12587 12588 } 12589 12590 private Picture xrenderPicturePainter; 12591 12592 bool manualInvalidations; 12593 void invalidateRect(Rectangle invalidRect) { 12594 // FIXME if manualInvalidations 12595 } 12596 12597 void dispose() { 12598 this.rasterOp = RasterOp.normal; 12599 12600 if(xrenderPicturePainter) { 12601 XRenderFreePicture(display, xrenderPicturePainter); 12602 xrenderPicturePainter = None; 12603 } 12604 12605 // FIXME: this.window.width/height is probably wrong 12606 12607 // src x,y then dest x, y 12608 if(destiny != None) { 12609 // FIXME: if manual invalidations we can actually only copy some of the area. 12610 // if(manualInvalidations) 12611 XSetClipMask(display, gc, None); 12612 XCopyArea(display, d, destiny, gc, 0, 0, this.window.width, this.window.height, 0, 0); 12613 } 12614 12615 XFreeGC(display, gc); 12616 12617 version(with_xft) 12618 if(xftDraw) { 12619 XftDrawDestroy(xftDraw); 12620 xftDraw = null; 12621 } 12622 12623 /+ 12624 // this should prolly legit never be used since if it destroys the font handle from a OperatingSystemFont, it also ruins a reusable resource. 12625 if(font && font !is defaultfont) { 12626 XFreeFont(display, font); 12627 font = null; 12628 } 12629 if(fontset && fontset !is defaultfontset) { 12630 XFreeFontSet(display, fontset); 12631 fontset = null; 12632 } 12633 +/ 12634 XFlush(display); 12635 12636 if(window.paintingFinishedDg !is null) 12637 window.paintingFinishedDg()(); 12638 } 12639 12640 bool backgroundIsNotTransparent = true; 12641 bool foregroundIsNotTransparent = true; 12642 12643 bool _penInitialized = false; 12644 Pen _activePen; 12645 12646 Color _outlineColor; 12647 Color _fillColor; 12648 12649 @property void pen(Pen p) { 12650 if(_penInitialized && p == _activePen) { 12651 return; 12652 } 12653 _penInitialized = true; 12654 _activePen = p; 12655 _outlineColor = p.color; 12656 12657 int style; 12658 12659 byte dashLength; 12660 12661 final switch(p.style) { 12662 case Pen.Style.Solid: 12663 style = 0 /*LineSolid*/; 12664 break; 12665 case Pen.Style.Dashed: 12666 style = 1 /*LineOnOffDash*/; 12667 dashLength = 4; 12668 break; 12669 case Pen.Style.Dotted: 12670 style = 1 /*LineOnOffDash*/; 12671 dashLength = 1; 12672 break; 12673 } 12674 12675 XSetLineAttributes(display, gc, p.width, style, style == 0 ? 3 : 0, 0); 12676 if(dashLength) 12677 XSetDashes(display, gc, 0, &dashLength, 1); 12678 12679 if(p.color.a == 0) { 12680 foregroundIsNotTransparent = false; 12681 return; 12682 } 12683 12684 foregroundIsNotTransparent = true; 12685 12686 XSetForeground(display, gc, colorToX(p.color, display)); 12687 12688 version(with_xft) 12689 updateXftColor(); 12690 } 12691 12692 RasterOp _currentRasterOp; 12693 bool _currentRasterOpInitialized = false; 12694 @property void rasterOp(RasterOp op) { 12695 if(_currentRasterOpInitialized && _currentRasterOp == op) 12696 return; 12697 _currentRasterOp = op; 12698 _currentRasterOpInitialized = true; 12699 int mode; 12700 final switch(op) { 12701 case RasterOp.normal: 12702 mode = GXcopy; 12703 break; 12704 case RasterOp.xor: 12705 mode = GXxor; 12706 break; 12707 } 12708 XSetFunction(display, gc, mode); 12709 } 12710 12711 12712 bool _fillColorInitialized = false; 12713 12714 @property void fillColor(Color c) { 12715 if(_fillColorInitialized && _fillColor == c) 12716 return; // already good, no need to waste time calling it 12717 _fillColor = c; 12718 _fillColorInitialized = true; 12719 if(c.a == 0) { 12720 backgroundIsNotTransparent = false; 12721 return; 12722 } 12723 12724 backgroundIsNotTransparent = true; 12725 12726 XSetBackground(display, gc, colorToX(c, display)); 12727 12728 } 12729 12730 void swapColors() { 12731 auto tmp = _fillColor; 12732 fillColor = _outlineColor; 12733 auto newPen = _activePen; 12734 newPen.color = tmp; 12735 pen(newPen); 12736 } 12737 12738 uint colorToX(Color c, Display* display) { 12739 auto visual = DefaultVisual(display, DefaultScreen(display)); 12740 import core.bitop; 12741 uint color = 0; 12742 { 12743 auto startBit = bsf(visual.red_mask); 12744 auto lastBit = bsr(visual.red_mask); 12745 auto r = cast(uint) c.r; 12746 r >>= 7 - (lastBit - startBit); 12747 r <<= startBit; 12748 color |= r; 12749 } 12750 { 12751 auto startBit = bsf(visual.green_mask); 12752 auto lastBit = bsr(visual.green_mask); 12753 auto g = cast(uint) c.g; 12754 g >>= 7 - (lastBit - startBit); 12755 g <<= startBit; 12756 color |= g; 12757 } 12758 { 12759 auto startBit = bsf(visual.blue_mask); 12760 auto lastBit = bsr(visual.blue_mask); 12761 auto b = cast(uint) c.b; 12762 b >>= 7 - (lastBit - startBit); 12763 b <<= startBit; 12764 color |= b; 12765 } 12766 12767 12768 12769 return color; 12770 } 12771 12772 void drawImage(int x, int y, Image i, int ix, int iy, int w, int h) { 12773 // source x, source y 12774 if(ix >= i.width) return; 12775 if(iy >= i.height) return; 12776 if(ix + w > i.width) w = i.width - ix; 12777 if(iy + h > i.height) h = i.height - iy; 12778 if(i.usingXshm) 12779 XShmPutImage(display, d, gc, i.handle, ix, iy, x, y, w, h, false); 12780 else 12781 XPutImage(display, d, gc, i.handle, ix, iy, x, y, w, h); 12782 } 12783 12784 void drawPixmap(Sprite s, int x, int y, int ix, int iy, int w, int h) { 12785 if(s.enableAlpha) { 12786 // the Sprite must be created first, meaning if we're here, XRender is already loaded 12787 if(this.xrenderPicturePainter == None) { 12788 XRenderPictureAttributes attrs; 12789 // FIXME: I can prolly reuse this as long as the pixmap itself is valid. 12790 xrenderPicturePainter = XRenderCreatePicture(display, d, Sprite.RGB24, 0, &attrs); 12791 12792 // need to initialize the clip 12793 XRectangle[1] rects; 12794 rects[0] = XRectangle(cast(short)(_clipRectangle.left), cast(short)(_clipRectangle.top), cast(short) _clipRectangle.width, cast(short) _clipRectangle.height); 12795 12796 if(_clipRectangle != Rectangle.init) 12797 XRenderSetPictureClipRectangles(display, xrenderPicturePainter, 0, 0, rects.ptr, cast(int) rects.length); 12798 } 12799 12800 XRenderComposite( 12801 display, 12802 3, // PicOpOver 12803 s.xrenderPicture, 12804 None, 12805 this.xrenderPicturePainter, 12806 ix, 12807 iy, 12808 0, 12809 0, 12810 x, 12811 y, 12812 w ? w : s.width, 12813 h ? h : s.height 12814 ); 12815 } else { 12816 XCopyArea(display, s.handle, d, gc, ix, iy, w ? w : s.width, h ? h : s.height, x, y); 12817 } 12818 } 12819 12820 int fontHeight() { 12821 version(with_xft) 12822 if(xftFont !is null) 12823 return xftFont.height; 12824 if(font) 12825 return font.max_bounds.ascent + font.max_bounds.descent; 12826 return 12; // pretty common default... 12827 } 12828 12829 int textWidth(in char[] line) { 12830 version(with_xft) 12831 if(xftFont) { 12832 if(line.length == 0) 12833 return 0; 12834 XGlyphInfo extents; 12835 XftTextExtentsUtf8(display, xftFont, line.ptr, cast(int) line.length, &extents); 12836 return extents.width; 12837 } 12838 12839 if(fontset) { 12840 if(line.length == 0) 12841 return 0; 12842 XRectangle rect; 12843 Xutf8TextExtents(fontset, line.ptr, cast(int) line.length, null, &rect); 12844 12845 return rect.width; 12846 } 12847 12848 if(font) 12849 // FIXME: unicode 12850 return XTextWidth( font, line.ptr, cast(int) line.length); 12851 else 12852 return fontHeight / 2 * cast(int) line.length; // if no font is loaded, it is prolly Fixed, which is a 2:1 ratio 12853 } 12854 12855 Size textSize(in char[] text) { 12856 auto maxWidth = 0; 12857 auto lineHeight = fontHeight; 12858 int h = text.length ? 0 : lineHeight + 4; // if text is empty, it still gives the line height 12859 foreach(line; text.split('\n')) { 12860 int textWidth = this.textWidth(line); 12861 if(textWidth > maxWidth) 12862 maxWidth = textWidth; 12863 h += lineHeight + 4; 12864 } 12865 return Size(maxWidth, h); 12866 } 12867 12868 void drawText(in int x, in int y, in int x2, in int y2, in char[] originalText, in uint alignment) { 12869 const(char)[] text; 12870 version(with_xft) 12871 if(xftFont) { 12872 text = originalText; 12873 goto loaded; 12874 } 12875 12876 if(fontset) 12877 text = originalText; 12878 else { 12879 text.reserve(originalText.length); 12880 // the first 256 unicode codepoints are the same as ascii and latin-1, which is what X expects, so we can keep all those 12881 // then strip the rest so there isn't garbage 12882 foreach(dchar ch; originalText) 12883 if(ch < 256) 12884 text ~= cast(ubyte) ch; 12885 else 12886 text ~= 191; // FIXME: using a random character (upside down question mark) to fill the space 12887 } 12888 loaded: 12889 if(text.length == 0) 12890 return; 12891 12892 // FIXME: should we clip it to the bounding box? 12893 int textHeight = fontHeight; 12894 12895 auto lines = text.split('\n'); 12896 12897 const lineHeight = textHeight; 12898 textHeight *= lines.length; 12899 12900 int cy = y; 12901 12902 if(alignment & TextAlignment.VerticalBottom) { 12903 if(y2 <= 0) 12904 return; 12905 auto h = y2 - y; 12906 if(h > textHeight) { 12907 cy += h - textHeight; 12908 cy -= lineHeight / 2; 12909 } 12910 } else if(alignment & TextAlignment.VerticalCenter) { 12911 if(y2 <= 0) 12912 return; 12913 auto h = y2 - y; 12914 if(textHeight < h) { 12915 cy += (h - textHeight) / 2; 12916 //cy -= lineHeight / 4; 12917 } 12918 } 12919 12920 foreach(line; text.split('\n')) { 12921 int textWidth = this.textWidth(line); 12922 12923 int px = x, py = cy; 12924 12925 if(alignment & TextAlignment.Center) { 12926 if(x2 <= 0) 12927 return; 12928 auto w = x2 - x; 12929 if(w > textWidth) 12930 px += (w - textWidth) / 2; 12931 } else if(alignment & TextAlignment.Right) { 12932 if(x2 <= 0) 12933 return; 12934 auto pos = x2 - textWidth; 12935 if(pos > x) 12936 px = pos; 12937 } 12938 12939 version(with_xft) 12940 if(xftFont) { 12941 XftDrawStringUtf8(xftDraw, &xftColor, xftFont, px, py + xftFont.ascent, line.ptr, cast(int) line.length); 12942 12943 goto carry_on; 12944 } 12945 12946 if(fontset) 12947 Xutf8DrawString(display, d, fontset, gc, px, py + (font ? font.max_bounds.ascent : lineHeight), line.ptr, cast(int) line.length); 12948 else 12949 XDrawString(display, d, gc, px, py + (font ? font.max_bounds.ascent : lineHeight), line.ptr, cast(int) line.length); 12950 carry_on: 12951 cy += lineHeight + 4; 12952 } 12953 } 12954 12955 void drawPixel(int x, int y) { 12956 XDrawPoint(display, d, gc, x, y); 12957 } 12958 12959 // The basic shapes, outlined 12960 12961 void drawLine(int x1, int y1, int x2, int y2) { 12962 if(foregroundIsNotTransparent) 12963 XDrawLine(display, d, gc, x1, y1, x2, y2); 12964 } 12965 12966 void drawRectangle(int x, int y, int width, int height) { 12967 if(backgroundIsNotTransparent) { 12968 swapColors(); 12969 XFillRectangle(display, d, gc, x+1, y+1, width-2, height-2); // Need to ensure pixels are only drawn once... 12970 swapColors(); 12971 } 12972 // 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 12973 if(foregroundIsNotTransparent) 12974 XDrawRectangle(display, d, gc, x + _activePen.width / 2, y + _activePen.width / 2, width - 1 - _activePen.width / 2, height - 1 - _activePen.width / 2); 12975 } 12976 12977 /// Arguments are the points of the bounding rectangle 12978 void drawEllipse(int x1, int y1, int x2, int y2) { 12979 drawArc(x1, y1, x2 - x1, y2 - y1, 0, 360 * 64); 12980 } 12981 12982 // NOTE: start and finish are in units of degrees * 64 12983 void drawArc(int x1, int y1, int width, int height, int start, int finish) { 12984 if(backgroundIsNotTransparent) { 12985 swapColors(); 12986 XFillArc(display, d, gc, x1, y1, width, height, start, finish); 12987 swapColors(); 12988 } 12989 if(foregroundIsNotTransparent) { 12990 XDrawArc(display, d, gc, x1, y1, width, height, start, finish); 12991 12992 // Windows draws the straight lines on the edges too so FIXME sort of 12993 } 12994 } 12995 12996 void drawPolygon(Point[] vertexes) { 12997 XPoint[16] pointsBuffer; 12998 XPoint[] points; 12999 if(vertexes.length <= pointsBuffer.length) 13000 points = pointsBuffer[0 .. vertexes.length]; 13001 else 13002 points.length = vertexes.length; 13003 13004 foreach(i, p; vertexes) { 13005 points[i].x = cast(short) p.x; 13006 points[i].y = cast(short) p.y; 13007 } 13008 13009 if(backgroundIsNotTransparent) { 13010 swapColors(); 13011 XFillPolygon(display, d, gc, points.ptr, cast(int) points.length, PolygonShape.Complex, CoordMode.CoordModeOrigin); 13012 swapColors(); 13013 } 13014 if(foregroundIsNotTransparent) { 13015 XDrawLines(display, d, gc, points.ptr, cast(int) points.length, CoordMode.CoordModeOrigin); 13016 } 13017 } 13018 } 13019 13020 /* XRender { */ 13021 13022 struct XRenderColor { 13023 ushort red; 13024 ushort green; 13025 ushort blue; 13026 ushort alpha; 13027 } 13028 13029 alias Picture = XID; 13030 alias PictFormat = XID; 13031 13032 struct XGlyphInfo { 13033 ushort width; 13034 ushort height; 13035 short x; 13036 short y; 13037 short xOff; 13038 short yOff; 13039 } 13040 13041 struct XRenderDirectFormat { 13042 short red; 13043 short redMask; 13044 short green; 13045 short greenMask; 13046 short blue; 13047 short blueMask; 13048 short alpha; 13049 short alphaMask; 13050 } 13051 13052 struct XRenderPictFormat { 13053 PictFormat id; 13054 int type; 13055 int depth; 13056 XRenderDirectFormat direct; 13057 Colormap colormap; 13058 } 13059 13060 enum PictFormatID = (1 << 0); 13061 enum PictFormatType = (1 << 1); 13062 enum PictFormatDepth = (1 << 2); 13063 enum PictFormatRed = (1 << 3); 13064 enum PictFormatRedMask =(1 << 4); 13065 enum PictFormatGreen = (1 << 5); 13066 enum PictFormatGreenMask=(1 << 6); 13067 enum PictFormatBlue = (1 << 7); 13068 enum PictFormatBlueMask =(1 << 8); 13069 enum PictFormatAlpha = (1 << 9); 13070 enum PictFormatAlphaMask=(1 << 10); 13071 enum PictFormatColormap =(1 << 11); 13072 13073 struct XRenderPictureAttributes { 13074 int repeat; 13075 Picture alpha_map; 13076 int alpha_x_origin; 13077 int alpha_y_origin; 13078 int clip_x_origin; 13079 int clip_y_origin; 13080 Pixmap clip_mask; 13081 Bool graphics_exposures; 13082 int subwindow_mode; 13083 int poly_edge; 13084 int poly_mode; 13085 Atom dither; 13086 Bool component_alpha; 13087 } 13088 13089 alias int XFixed; 13090 13091 struct XPointFixed { 13092 XFixed x, y; 13093 } 13094 13095 struct XCircle { 13096 XFixed x; 13097 XFixed y; 13098 XFixed radius; 13099 } 13100 13101 struct XTransform { 13102 XFixed[3][3] matrix; 13103 } 13104 13105 struct XFilters { 13106 int nfilter; 13107 char **filter; 13108 int nalias; 13109 short *alias_; 13110 } 13111 13112 struct XIndexValue { 13113 c_ulong pixel; 13114 ushort red, green, blue, alpha; 13115 } 13116 13117 struct XAnimCursor { 13118 Cursor cursor; 13119 c_ulong delay; 13120 } 13121 13122 struct XLinearGradient { 13123 XPointFixed p1; 13124 XPointFixed p2; 13125 } 13126 13127 struct XRadialGradient { 13128 XCircle inner; 13129 XCircle outer; 13130 } 13131 13132 struct XConicalGradient { 13133 XPointFixed center; 13134 XFixed angle; /* in degrees */ 13135 } 13136 13137 enum PictStandardARGB32 = 0; 13138 enum PictStandardRGB24 = 1; 13139 enum PictStandardA8 = 2; 13140 enum PictStandardA4 = 3; 13141 enum PictStandardA1 = 4; 13142 enum PictStandardNUM = 5; 13143 13144 interface XRender { 13145 extern(C) @nogc: 13146 13147 Bool XRenderQueryExtension (Display *dpy, int *event_basep, int *error_basep); 13148 13149 Status XRenderQueryVersion (Display *dpy, 13150 int *major_versionp, 13151 int *minor_versionp); 13152 13153 Status XRenderQueryFormats (Display *dpy); 13154 13155 int XRenderQuerySubpixelOrder (Display *dpy, int screen); 13156 13157 Bool XRenderSetSubpixelOrder (Display *dpy, int screen, int subpixel); 13158 13159 XRenderPictFormat * 13160 XRenderFindVisualFormat (Display *dpy, const Visual *visual); 13161 13162 XRenderPictFormat * 13163 XRenderFindFormat (Display *dpy, 13164 c_ulong mask, 13165 const XRenderPictFormat *templ, 13166 int count); 13167 XRenderPictFormat * 13168 XRenderFindStandardFormat (Display *dpy, 13169 int format); 13170 13171 XIndexValue * 13172 XRenderQueryPictIndexValues(Display *dpy, 13173 const XRenderPictFormat *format, 13174 int *num); 13175 13176 Picture XRenderCreatePicture( 13177 Display *dpy, 13178 Drawable drawable, 13179 const XRenderPictFormat *format, 13180 c_ulong valuemask, 13181 const XRenderPictureAttributes *attributes); 13182 13183 void XRenderChangePicture (Display *dpy, 13184 Picture picture, 13185 c_ulong valuemask, 13186 const XRenderPictureAttributes *attributes); 13187 13188 void 13189 XRenderSetPictureClipRectangles (Display *dpy, 13190 Picture picture, 13191 int xOrigin, 13192 int yOrigin, 13193 const XRectangle *rects, 13194 int n); 13195 13196 void 13197 XRenderSetPictureClipRegion (Display *dpy, 13198 Picture picture, 13199 Region r); 13200 13201 void 13202 XRenderSetPictureTransform (Display *dpy, 13203 Picture picture, 13204 XTransform *transform); 13205 13206 void 13207 XRenderFreePicture (Display *dpy, 13208 Picture picture); 13209 13210 void 13211 XRenderComposite (Display *dpy, 13212 int op, 13213 Picture src, 13214 Picture mask, 13215 Picture dst, 13216 int src_x, 13217 int src_y, 13218 int mask_x, 13219 int mask_y, 13220 int dst_x, 13221 int dst_y, 13222 uint width, 13223 uint height); 13224 13225 13226 Picture XRenderCreateSolidFill (Display *dpy, 13227 const XRenderColor *color); 13228 13229 Picture XRenderCreateLinearGradient (Display *dpy, 13230 const XLinearGradient *gradient, 13231 const XFixed *stops, 13232 const XRenderColor *colors, 13233 int nstops); 13234 13235 Picture XRenderCreateRadialGradient (Display *dpy, 13236 const XRadialGradient *gradient, 13237 const XFixed *stops, 13238 const XRenderColor *colors, 13239 int nstops); 13240 13241 Picture XRenderCreateConicalGradient (Display *dpy, 13242 const XConicalGradient *gradient, 13243 const XFixed *stops, 13244 const XRenderColor *colors, 13245 int nstops); 13246 13247 13248 13249 Cursor 13250 XRenderCreateCursor (Display *dpy, 13251 Picture source, 13252 uint x, 13253 uint y); 13254 13255 XFilters * 13256 XRenderQueryFilters (Display *dpy, Drawable drawable); 13257 13258 void 13259 XRenderSetPictureFilter (Display *dpy, 13260 Picture picture, 13261 const char *filter, 13262 XFixed *params, 13263 int nparams); 13264 13265 Cursor 13266 XRenderCreateAnimCursor (Display *dpy, 13267 int ncursor, 13268 XAnimCursor *cursors); 13269 } 13270 13271 __gshared bool XRenderLibrarySuccessfullyLoaded = true; 13272 mixin DynamicLoad!(XRender, "Xrender", 1, XRenderLibrarySuccessfullyLoaded) XRenderLibrary; 13273 13274 /* XRender } */ 13275 13276 /* Xrandr { */ 13277 13278 struct XRRMonitorInfo { 13279 Atom name; 13280 Bool primary; 13281 Bool automatic; 13282 int noutput; 13283 int x; 13284 int y; 13285 int width; 13286 int height; 13287 int mwidth; 13288 int mheight; 13289 /*RROutput*/ void *outputs; 13290 } 13291 13292 struct XRRScreenChangeNotifyEvent { 13293 int type; /* event base */ 13294 c_ulong serial; /* # of last request processed by server */ 13295 Bool send_event; /* true if this came from a SendEvent request */ 13296 Display *display; /* Display the event was read from */ 13297 Window window; /* window which selected for this event */ 13298 Window root; /* Root window for changed screen */ 13299 Time timestamp; /* when the screen change occurred */ 13300 Time config_timestamp; /* when the last configuration change */ 13301 ushort/*SizeID*/ size_index; 13302 ushort/*SubpixelOrder*/ subpixel_order; 13303 ushort/*Rotation*/ rotation; 13304 int width; 13305 int height; 13306 int mwidth; 13307 int mheight; 13308 } 13309 13310 enum RRScreenChangeNotify = 0; 13311 13312 enum RRScreenChangeNotifyMask = 1; 13313 13314 __gshared int xrrEventBase = -1; 13315 13316 13317 interface XRandr { 13318 extern(C) @nogc: 13319 Bool XRRQueryExtension (Display *dpy, int *event_base_return, int *error_base_return); 13320 Status XRRQueryVersion (Display *dpy, int *major_version_return, int *minor_version_return); 13321 13322 XRRMonitorInfo * XRRGetMonitors(Display *dpy, Window window, Bool get_active, int *nmonitors); 13323 void XRRFreeMonitors(XRRMonitorInfo *monitors); 13324 13325 void XRRSelectInput(Display *dpy, Window window, int mask); 13326 } 13327 13328 __gshared bool XRandrLibrarySuccessfullyLoaded = true; 13329 mixin DynamicLoad!(XRandr, "Xrandr", 2, XRandrLibrarySuccessfullyLoaded) XRandrLibrary; 13330 /* Xrandr } */ 13331 13332 /* Xft { */ 13333 13334 // actually freetype 13335 alias void FT_Face; 13336 13337 // actually fontconfig 13338 private alias FcBool = int; 13339 alias void FcCharSet; 13340 alias void FcPattern; 13341 alias void FcResult; 13342 enum FcEndian { FcEndianBig, FcEndianLittle } 13343 struct FcFontSet { 13344 int nfont; 13345 int sfont; 13346 FcPattern** fonts; 13347 } 13348 13349 // actually XRegion 13350 struct BOX { 13351 short x1, x2, y1, y2; 13352 } 13353 struct _XRegion { 13354 c_long size; 13355 c_long numRects; 13356 BOX* rects; 13357 BOX extents; 13358 } 13359 13360 alias Region = _XRegion*; 13361 13362 // ok actually Xft 13363 13364 struct XftFontInfo; 13365 13366 struct XftFont { 13367 int ascent; 13368 int descent; 13369 int height; 13370 int max_advance_width; 13371 FcCharSet* charset; 13372 FcPattern* pattern; 13373 } 13374 13375 struct XftDraw; 13376 13377 struct XftColor { 13378 c_ulong pixel; 13379 XRenderColor color; 13380 } 13381 13382 struct XftCharSpec { 13383 dchar ucs4; 13384 short x; 13385 short y; 13386 } 13387 13388 struct XftCharFontSpec { 13389 XftFont *font; 13390 dchar ucs4; 13391 short x; 13392 short y; 13393 } 13394 13395 struct XftGlyphSpec { 13396 uint glyph; 13397 short x; 13398 short y; 13399 } 13400 13401 struct XftGlyphFontSpec { 13402 XftFont *font; 13403 uint glyph; 13404 short x; 13405 short y; 13406 } 13407 13408 interface Xft { 13409 extern(C) @nogc pure: 13410 13411 Bool XftColorAllocName (Display *dpy, 13412 const Visual *visual, 13413 Colormap cmap, 13414 const char *name, 13415 XftColor *result); 13416 13417 Bool XftColorAllocValue (Display *dpy, 13418 Visual *visual, 13419 Colormap cmap, 13420 const XRenderColor *color, 13421 XftColor *result); 13422 13423 void XftColorFree (Display *dpy, 13424 Visual *visual, 13425 Colormap cmap, 13426 XftColor *color); 13427 13428 Bool XftDefaultHasRender (Display *dpy); 13429 13430 Bool XftDefaultSet (Display *dpy, FcPattern *defaults); 13431 13432 void XftDefaultSubstitute (Display *dpy, int screen, FcPattern *pattern); 13433 13434 XftDraw * XftDrawCreate (Display *dpy, 13435 Drawable drawable, 13436 Visual *visual, 13437 Colormap colormap); 13438 13439 XftDraw * XftDrawCreateBitmap (Display *dpy, 13440 Pixmap bitmap); 13441 13442 XftDraw * XftDrawCreateAlpha (Display *dpy, 13443 Pixmap pixmap, 13444 int depth); 13445 13446 void XftDrawChange (XftDraw *draw, 13447 Drawable drawable); 13448 13449 Display * XftDrawDisplay (XftDraw *draw); 13450 13451 Drawable XftDrawDrawable (XftDraw *draw); 13452 13453 Colormap XftDrawColormap (XftDraw *draw); 13454 13455 Visual * XftDrawVisual (XftDraw *draw); 13456 13457 void XftDrawDestroy (XftDraw *draw); 13458 13459 Picture XftDrawPicture (XftDraw *draw); 13460 13461 Picture XftDrawSrcPicture (XftDraw *draw, const XftColor *color); 13462 13463 void XftDrawGlyphs (XftDraw *draw, 13464 const XftColor *color, 13465 XftFont *pub, 13466 int x, 13467 int y, 13468 const uint *glyphs, 13469 int nglyphs); 13470 13471 void XftDrawString8 (XftDraw *draw, 13472 const XftColor *color, 13473 XftFont *pub, 13474 int x, 13475 int y, 13476 const char *string, 13477 int len); 13478 13479 void XftDrawString16 (XftDraw *draw, 13480 const XftColor *color, 13481 XftFont *pub, 13482 int x, 13483 int y, 13484 const wchar *string, 13485 int len); 13486 13487 void XftDrawString32 (XftDraw *draw, 13488 const XftColor *color, 13489 XftFont *pub, 13490 int x, 13491 int y, 13492 const dchar *string, 13493 int len); 13494 13495 void XftDrawStringUtf8 (XftDraw *draw, 13496 const XftColor *color, 13497 XftFont *pub, 13498 int x, 13499 int y, 13500 const char *string, 13501 int len); 13502 void XftDrawStringUtf16 (XftDraw *draw, 13503 const XftColor *color, 13504 XftFont *pub, 13505 int x, 13506 int y, 13507 const char *string, 13508 FcEndian endian, 13509 int len); 13510 13511 void XftDrawCharSpec (XftDraw *draw, 13512 const XftColor *color, 13513 XftFont *pub, 13514 const XftCharSpec *chars, 13515 int len); 13516 13517 void XftDrawCharFontSpec (XftDraw *draw, 13518 const XftColor *color, 13519 const XftCharFontSpec *chars, 13520 int len); 13521 13522 void XftDrawGlyphSpec (XftDraw *draw, 13523 const XftColor *color, 13524 XftFont *pub, 13525 const XftGlyphSpec *glyphs, 13526 int len); 13527 13528 void XftDrawGlyphFontSpec (XftDraw *draw, 13529 const XftColor *color, 13530 const XftGlyphFontSpec *glyphs, 13531 int len); 13532 13533 void XftDrawRect (XftDraw *draw, 13534 const XftColor *color, 13535 int x, 13536 int y, 13537 uint width, 13538 uint height); 13539 13540 Bool XftDrawSetClip (XftDraw *draw, 13541 Region r); 13542 13543 13544 Bool XftDrawSetClipRectangles (XftDraw *draw, 13545 int xOrigin, 13546 int yOrigin, 13547 const XRectangle *rects, 13548 int n); 13549 13550 void XftDrawSetSubwindowMode (XftDraw *draw, 13551 int mode); 13552 13553 void XftGlyphExtents (Display *dpy, 13554 XftFont *pub, 13555 const uint *glyphs, 13556 int nglyphs, 13557 XGlyphInfo *extents); 13558 13559 void XftTextExtents8 (Display *dpy, 13560 XftFont *pub, 13561 const char *string, 13562 int len, 13563 XGlyphInfo *extents); 13564 13565 void XftTextExtents16 (Display *dpy, 13566 XftFont *pub, 13567 const wchar *string, 13568 int len, 13569 XGlyphInfo *extents); 13570 13571 void XftTextExtents32 (Display *dpy, 13572 XftFont *pub, 13573 const dchar *string, 13574 int len, 13575 XGlyphInfo *extents); 13576 13577 void XftTextExtentsUtf8 (Display *dpy, 13578 XftFont *pub, 13579 const char *string, 13580 int len, 13581 XGlyphInfo *extents); 13582 13583 void XftTextExtentsUtf16 (Display *dpy, 13584 XftFont *pub, 13585 const char *string, 13586 FcEndian endian, 13587 int len, 13588 XGlyphInfo *extents); 13589 13590 FcPattern * XftFontMatch (Display *dpy, 13591 int screen, 13592 const FcPattern *pattern, 13593 FcResult *result); 13594 13595 XftFont * XftFontOpen (Display *dpy, int screen, ...); 13596 13597 XftFont * XftFontOpenName (Display *dpy, int screen, const char *name); 13598 13599 XftFont * XftFontOpenXlfd (Display *dpy, int screen, const char *xlfd); 13600 13601 FT_Face XftLockFace (XftFont *pub); 13602 13603 void XftUnlockFace (XftFont *pub); 13604 13605 XftFontInfo * XftFontInfoCreate (Display *dpy, const FcPattern *pattern); 13606 13607 void XftFontInfoDestroy (Display *dpy, XftFontInfo *fi); 13608 13609 dchar XftFontInfoHash (const XftFontInfo *fi); 13610 13611 FcBool XftFontInfoEqual (const XftFontInfo *a, const XftFontInfo *b); 13612 13613 XftFont * XftFontOpenInfo (Display *dpy, 13614 FcPattern *pattern, 13615 XftFontInfo *fi); 13616 13617 XftFont * XftFontOpenPattern (Display *dpy, FcPattern *pattern); 13618 13619 XftFont * XftFontCopy (Display *dpy, XftFont *pub); 13620 13621 void XftFontClose (Display *dpy, XftFont *pub); 13622 13623 FcBool XftInitFtLibrary(); 13624 void XftFontLoadGlyphs (Display *dpy, 13625 XftFont *pub, 13626 FcBool need_bitmaps, 13627 const uint *glyphs, 13628 int nglyph); 13629 13630 void XftFontUnloadGlyphs (Display *dpy, 13631 XftFont *pub, 13632 const uint *glyphs, 13633 int nglyph); 13634 13635 FcBool XftFontCheckGlyph (Display *dpy, 13636 XftFont *pub, 13637 FcBool need_bitmaps, 13638 uint glyph, 13639 uint *missing, 13640 int *nmissing); 13641 13642 FcBool XftCharExists (Display *dpy, 13643 XftFont *pub, 13644 dchar ucs4); 13645 13646 uint XftCharIndex (Display *dpy, 13647 XftFont *pub, 13648 dchar ucs4); 13649 FcBool XftInit (const char *config); 13650 13651 int XftGetVersion (); 13652 13653 FcFontSet * XftListFonts (Display *dpy, 13654 int screen, 13655 ...); 13656 13657 FcPattern *XftNameParse (const char *name); 13658 13659 void XftGlyphRender (Display *dpy, 13660 int op, 13661 Picture src, 13662 XftFont *pub, 13663 Picture dst, 13664 int srcx, 13665 int srcy, 13666 int x, 13667 int y, 13668 const uint *glyphs, 13669 int nglyphs); 13670 13671 void XftGlyphSpecRender (Display *dpy, 13672 int op, 13673 Picture src, 13674 XftFont *pub, 13675 Picture dst, 13676 int srcx, 13677 int srcy, 13678 const XftGlyphSpec *glyphs, 13679 int nglyphs); 13680 13681 void XftCharSpecRender (Display *dpy, 13682 int op, 13683 Picture src, 13684 XftFont *pub, 13685 Picture dst, 13686 int srcx, 13687 int srcy, 13688 const XftCharSpec *chars, 13689 int len); 13690 void XftGlyphFontSpecRender (Display *dpy, 13691 int op, 13692 Picture src, 13693 Picture dst, 13694 int srcx, 13695 int srcy, 13696 const XftGlyphFontSpec *glyphs, 13697 int nglyphs); 13698 13699 void XftCharFontSpecRender (Display *dpy, 13700 int op, 13701 Picture src, 13702 Picture dst, 13703 int srcx, 13704 int srcy, 13705 const XftCharFontSpec *chars, 13706 int len); 13707 13708 void XftTextRender8 (Display *dpy, 13709 int op, 13710 Picture src, 13711 XftFont *pub, 13712 Picture dst, 13713 int srcx, 13714 int srcy, 13715 int x, 13716 int y, 13717 const char *string, 13718 int len); 13719 void XftTextRender16 (Display *dpy, 13720 int op, 13721 Picture src, 13722 XftFont *pub, 13723 Picture dst, 13724 int srcx, 13725 int srcy, 13726 int x, 13727 int y, 13728 const wchar *string, 13729 int len); 13730 13731 void XftTextRender16BE (Display *dpy, 13732 int op, 13733 Picture src, 13734 XftFont *pub, 13735 Picture dst, 13736 int srcx, 13737 int srcy, 13738 int x, 13739 int y, 13740 const char *string, 13741 int len); 13742 13743 void XftTextRender16LE (Display *dpy, 13744 int op, 13745 Picture src, 13746 XftFont *pub, 13747 Picture dst, 13748 int srcx, 13749 int srcy, 13750 int x, 13751 int y, 13752 const char *string, 13753 int len); 13754 13755 void XftTextRender32 (Display *dpy, 13756 int op, 13757 Picture src, 13758 XftFont *pub, 13759 Picture dst, 13760 int srcx, 13761 int srcy, 13762 int x, 13763 int y, 13764 const dchar *string, 13765 int len); 13766 13767 void XftTextRender32BE (Display *dpy, 13768 int op, 13769 Picture src, 13770 XftFont *pub, 13771 Picture dst, 13772 int srcx, 13773 int srcy, 13774 int x, 13775 int y, 13776 const char *string, 13777 int len); 13778 13779 void XftTextRender32LE (Display *dpy, 13780 int op, 13781 Picture src, 13782 XftFont *pub, 13783 Picture dst, 13784 int srcx, 13785 int srcy, 13786 int x, 13787 int y, 13788 const char *string, 13789 int len); 13790 13791 void XftTextRenderUtf8 (Display *dpy, 13792 int op, 13793 Picture src, 13794 XftFont *pub, 13795 Picture dst, 13796 int srcx, 13797 int srcy, 13798 int x, 13799 int y, 13800 const char *string, 13801 int len); 13802 13803 void XftTextRenderUtf16 (Display *dpy, 13804 int op, 13805 Picture src, 13806 XftFont *pub, 13807 Picture dst, 13808 int srcx, 13809 int srcy, 13810 int x, 13811 int y, 13812 const char *string, 13813 FcEndian endian, 13814 int len); 13815 FcPattern * XftXlfdParse (const char *xlfd_orig, Bool ignore_scalable, Bool complete); 13816 13817 } 13818 13819 interface FontConfig { 13820 extern(C) @nogc pure: 13821 int FcPatternGetString(const FcPattern *p, const char *object, int n, char ** s); 13822 void FcFontSetDestroy(FcFontSet*); 13823 char* FcNameUnparse(const FcPattern *); 13824 } 13825 13826 mixin DynamicLoad!(Xft, "Xft", 2, librariesSuccessfullyLoaded) XftLibrary; 13827 mixin DynamicLoad!(FontConfig, "fontconfig", 1, librariesSuccessfullyLoaded) FontConfigLibrary; 13828 13829 13830 /* Xft } */ 13831 13832 class XDisconnectException : Exception { 13833 bool userRequested; 13834 this(bool userRequested = true) { 13835 this.userRequested = userRequested; 13836 super("X disconnected"); 13837 } 13838 } 13839 13840 /++ 13841 Platform-specific for X11. Traps errors for the duration of `dg`. Avoid calling this from inside a call to this. 13842 13843 Please note that it returns 13844 +/ 13845 XErrorEvent[] trapXErrors(scope void delegate() dg) { 13846 13847 static XErrorEvent[] errorBuffer; 13848 13849 static extern(C) int handler (Display* dpy, XErrorEvent* evt) nothrow { 13850 errorBuffer ~= *evt; 13851 return 0; 13852 } 13853 13854 auto savedErrorHandler = XSetErrorHandler(&handler); 13855 13856 try { 13857 dg(); 13858 } finally { 13859 XSync(XDisplayConnection.get, 0/*False*/); 13860 XSetErrorHandler(savedErrorHandler); 13861 } 13862 13863 auto bfr = errorBuffer; 13864 errorBuffer = null; 13865 13866 return bfr; 13867 } 13868 13869 /// Platform-specific for X11. A singleton class (well, all its methods are actually static... so more like a namespace) wrapping a `Display*`. 13870 class XDisplayConnection { 13871 private __gshared Display* display; 13872 private __gshared XIM xim; 13873 private __gshared char* displayName; 13874 13875 private __gshared int connectionSequence_; 13876 private __gshared bool isLocal_; 13877 13878 /// use this for lazy caching when reconnection 13879 static int connectionSequenceNumber() { return connectionSequence_; } 13880 13881 /++ 13882 Guesses if the connection appears to be local. 13883 13884 History: 13885 Added June 3, 2021 13886 +/ 13887 static @property bool isLocal() nothrow @trusted @nogc { 13888 return isLocal_; 13889 } 13890 13891 /// Attempts recreation of state, may require application assistance 13892 /// You MUST call this OUTSIDE the event loop. Let the exception kill the loop, 13893 /// then call this, and if successful, reenter the loop. 13894 static void discardAndRecreate(string newDisplayString = null) { 13895 if(insideXEventLoop) 13896 throw new Error("You MUST call discardAndRecreate from OUTSIDE the event loop"); 13897 13898 // 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 13899 auto chnenhm = CapableOfHandlingNativeEvent.nativeHandleMapping.dup; 13900 13901 foreach(handle; chnenhm) { 13902 handle.discardConnectionState(); 13903 } 13904 13905 discardState(); 13906 13907 if(newDisplayString !is null) 13908 setDisplayName(newDisplayString); 13909 13910 auto display = get(); 13911 13912 foreach(handle; chnenhm) { 13913 handle.recreateAfterDisconnect(); 13914 } 13915 } 13916 13917 private __gshared EventMask rootEventMask; 13918 13919 /++ 13920 Requests the specified input from the root window on the connection, in addition to any other request. 13921 13922 13923 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. 13924 13925 $(WARNING it calls XSelectInput itself, which will override any other root window input you have!) 13926 +/ 13927 static void addRootInput(EventMask mask) { 13928 auto old = rootEventMask; 13929 rootEventMask |= mask; 13930 get(); // to ensure display connected 13931 if(display !is null && rootEventMask != old) 13932 XSelectInput(display, RootWindow(display, DefaultScreen(display)), rootEventMask); 13933 } 13934 13935 static void discardState() { 13936 freeImages(); 13937 13938 foreach(atomPtr; interredAtoms) 13939 *atomPtr = 0; 13940 interredAtoms = null; 13941 interredAtoms.assumeSafeAppend(); 13942 13943 ScreenPainterImplementation.fontAttempted = false; 13944 ScreenPainterImplementation.defaultfont = null; 13945 ScreenPainterImplementation.defaultfontset = null; 13946 13947 Image.impl.xshmQueryCompleted = false; 13948 Image.impl._xshmAvailable = false; 13949 13950 SimpleWindow.nativeMapping = null; 13951 CapableOfHandlingNativeEvent.nativeHandleMapping = null; 13952 // GlobalHotkeyManager 13953 13954 display = null; 13955 xim = null; 13956 } 13957 13958 // Do you want to know why do we need all this horrible-looking code? See comment at the bottom. 13959 private static void createXIM () { 13960 import core.stdc.locale : setlocale, LC_ALL; 13961 import core.stdc.stdio : stderr, fprintf; 13962 import core.stdc.stdlib : free; 13963 import core.stdc.string : strdup; 13964 13965 static immutable string[3] mtry = [ "", "@im=local", "@im=" ]; 13966 13967 auto olocale = strdup(setlocale(LC_ALL, null)); 13968 setlocale(LC_ALL, (sdx_isUTF8Locale ? "" : "en_US.UTF-8")); 13969 scope(exit) { setlocale(LC_ALL, olocale); free(olocale); } 13970 13971 //fprintf(stderr, "opening IM...\n"); 13972 foreach (string s; mtry) { 13973 XSetLocaleModifiers(s.ptr); // it's safe, as `s` is string literal 13974 if ((xim = XOpenIM(display, null, null, null)) !is null) return; 13975 } 13976 fprintf(stderr, "createXIM: XOpenIM failed!\n"); 13977 } 13978 13979 // for X11 we will keep all XShm-allocated images in this list, so we can free 'em on connection closing. 13980 // we'll use glibc malloc()/free(), 'cause `unregisterImage()` can be called from object dtor. 13981 static struct ImgList { 13982 size_t img; // class; hide it from GC 13983 ImgList* next; 13984 } 13985 13986 static __gshared ImgList* imglist = null; 13987 static __gshared bool imglistLocked = false; // true: don't register and unregister images 13988 13989 static void registerImage (Image img) { 13990 if (!imglistLocked && img !is null) { 13991 import core.stdc.stdlib : malloc; 13992 auto it = cast(ImgList*)malloc(ImgList.sizeof); 13993 assert(it !is null); // do proper checks 13994 it.img = cast(size_t)cast(void*)img; 13995 it.next = imglist; 13996 imglist = it; 13997 version(sdpy_debug_xshm) { import core.stdc.stdio : printf; printf("registering image %p\n", cast(void*)img); } 13998 } 13999 } 14000 14001 static void unregisterImage (Image img) { 14002 if (!imglistLocked && img !is null) { 14003 import core.stdc.stdlib : free; 14004 ImgList* prev = null; 14005 ImgList* cur = imglist; 14006 while (cur !is null) { 14007 if (cur.img == cast(size_t)cast(void*)img) break; // i found her! 14008 prev = cur; 14009 cur = cur.next; 14010 } 14011 if (cur !is null) { 14012 if (prev is null) imglist = cur.next; else prev.next = cur.next; 14013 free(cur); 14014 version(sdpy_debug_xshm) { import core.stdc.stdio : printf; printf("unregistering image %p\n", cast(void*)img); } 14015 } else { 14016 version(sdpy_debug_xshm) { import core.stdc.stdio : printf; printf("trying to unregister unknown image %p\n", cast(void*)img); } 14017 } 14018 } 14019 } 14020 14021 static void freeImages () { // needed for discardAndRecreate 14022 imglistLocked = true; 14023 scope(exit) imglistLocked = false; 14024 ImgList* cur = imglist; 14025 ImgList* next = null; 14026 while (cur !is null) { 14027 import core.stdc.stdlib : free; 14028 next = cur.next; 14029 version(sdpy_debug_xshm) { import core.stdc.stdio : printf; printf("disposing image %p\n", cast(void*)cur.img); } 14030 (cast(Image)cast(void*)cur.img).dispose(); 14031 free(cur); 14032 cur = next; 14033 } 14034 imglist = null; 14035 } 14036 14037 /// can be used to override normal handling of display name 14038 /// from environment and/or command line 14039 static setDisplayName(string newDisplayName) { 14040 displayName = cast(char*) (newDisplayName ~ '\0'); 14041 } 14042 14043 /// resets to the default display string 14044 static resetDisplayName() { 14045 displayName = null; 14046 } 14047 14048 /// 14049 static Display* get() { 14050 if(display is null) { 14051 if(!librariesSuccessfullyLoaded) 14052 throw new Exception("Unable to load X11 client libraries"); 14053 display = XOpenDisplay(displayName); 14054 14055 isLocal_ = false; 14056 14057 connectionSequence_++; 14058 if(display is null) 14059 throw new Exception("Unable to open X display"); 14060 14061 auto str = display.display_name; 14062 // this is a bit of a hack but like if it looks like a unix socket we assume it is local 14063 // and otherwise it probably isn't 14064 if(str is null || (str[0] != ':' && str[0] != '/')) 14065 isLocal_ = false; 14066 else 14067 isLocal_ = true; 14068 14069 debug(sdpy_x_errors) { 14070 XSetErrorHandler(&adrlogger); 14071 XSynchronize(display, true); 14072 14073 extern(C) int wtf() { 14074 if(errorHappened) { 14075 asm { int 3; } 14076 errorHappened = false; 14077 } 14078 return 0; 14079 } 14080 XSetAfterFunction(display, &wtf); 14081 } 14082 14083 14084 XSetIOErrorHandler(&x11ioerrCB); 14085 Bool sup; 14086 XkbSetDetectableAutoRepeat(display, 1, &sup); // so we will not receive KeyRelease until key is really released 14087 createXIM(); 14088 version(with_eventloop) { 14089 import arsd.eventloop; 14090 addFileEventListeners(display.fd, &eventListener, null, null); 14091 } 14092 } 14093 14094 return display; 14095 } 14096 14097 extern(C) 14098 static int x11ioerrCB(Display* dpy) { 14099 throw new XDisconnectException(false); 14100 } 14101 14102 version(with_eventloop) { 14103 import arsd.eventloop; 14104 static void eventListener(OsFileHandle fd) { 14105 //this.mtLock(); 14106 //scope(exit) this.mtUnlock(); 14107 while(XPending(display)) 14108 doXNextEvent(display); 14109 } 14110 } 14111 14112 // close connection on program exit -- we need this to properly free all images 14113 static ~this () { 14114 // the gui thread must clean up after itself or else Xlib might deadlock 14115 // using this flag on any thread destruction is the easiest way i know of 14116 // (shared static this is run by the LAST thread to exit, which may not be 14117 // the gui thread, and normal static this run by ALL threads, so we gotta check.) 14118 if(thisIsGuiThread) 14119 close(); 14120 } 14121 14122 /// 14123 static void close() { 14124 if(display is null) 14125 return; 14126 14127 version(with_eventloop) { 14128 import arsd.eventloop; 14129 removeFileEventListeners(display.fd); 14130 } 14131 14132 // now remove all registered images to prevent shared memory leaks 14133 freeImages(); 14134 14135 // tbh I don't know why it is doing this but like if this happens to run 14136 // from the other thread there's frequent hanging inside here. 14137 if(thisIsGuiThread) 14138 XCloseDisplay(display); 14139 display = null; 14140 } 14141 } 14142 14143 mixin template NativeImageImplementation() { 14144 XImage* handle; 14145 ubyte* rawData; 14146 14147 XShmSegmentInfo shminfo; 14148 14149 __gshared bool xshmQueryCompleted; 14150 __gshared bool _xshmAvailable; 14151 public static @property bool xshmAvailable() { 14152 if(!xshmQueryCompleted) { 14153 int i1, i2, i3; 14154 xshmQueryCompleted = true; 14155 14156 if(!XDisplayConnection.isLocal) 14157 _xshmAvailable = false; 14158 else 14159 _xshmAvailable = XQueryExtension(XDisplayConnection.get(), "MIT-SHM", &i1, &i2, &i3) != 0; 14160 } 14161 return _xshmAvailable; 14162 } 14163 14164 bool usingXshm; 14165 final: 14166 14167 private __gshared bool xshmfailed; 14168 14169 void createImage(int width, int height, bool forcexshm=false, bool enableAlpha = false) { 14170 auto display = XDisplayConnection.get(); 14171 assert(display !is null); 14172 auto screen = DefaultScreen(display); 14173 14174 // it will only use shared memory for somewhat largish images, 14175 // since otherwise we risk wasting shared memory handles on a lot of little ones 14176 if (xshmAvailable && (forcexshm || (width > 100 && height > 100))) { 14177 14178 14179 // it is possible for the query extension to return true, the DISPLAY check to pass, yet 14180 // the actual use still fails. For example, if the program is in a container and permission denied 14181 // on shared memory, or if it is a local thing forwarded to a remote server, etc. 14182 // 14183 // If it does fail, we need to detect it now, abort the xshm and fall back to core protocol. 14184 14185 14186 // synchronize so preexisting buffers are clear 14187 XSync(display, false); 14188 xshmfailed = false; 14189 14190 auto oldErrorHandler = XSetErrorHandler(&XShmErrorHandler); 14191 14192 14193 usingXshm = true; 14194 handle = XShmCreateImage( 14195 display, 14196 DefaultVisual(display, screen), 14197 enableAlpha ? 32: 24, 14198 ImageFormat.ZPixmap, 14199 null, 14200 &shminfo, 14201 width, height); 14202 if(handle is null) 14203 goto abortXshm1; 14204 14205 if(handle.bytes_per_line != 4 * width) 14206 goto abortXshm2; 14207 14208 shminfo.shmid = shmget(IPC_PRIVATE, handle.bytes_per_line * height, IPC_CREAT | 511 /* 0777 */); 14209 if(shminfo.shmid < 0) 14210 goto abortXshm3; 14211 handle.data = shminfo.shmaddr = rawData = cast(ubyte*) shmat(shminfo.shmid, null, 0); 14212 if(rawData == cast(ubyte*) -1) 14213 goto abortXshm4; 14214 shminfo.readOnly = 0; 14215 XShmAttach(display, &shminfo); 14216 14217 // and now to the final error check to ensure it actually worked. 14218 XSync(display, false); 14219 if(xshmfailed) 14220 goto abortXshm5; 14221 14222 XSetErrorHandler(oldErrorHandler); 14223 14224 XDisplayConnection.registerImage(this); 14225 // if I don't flush here there's a chance the dtor will run before the 14226 // ctor and lead to a bad value X error. While this hurts the efficiency 14227 // it is local anyway so prolly better to keep it simple 14228 XFlush(display); 14229 14230 return; 14231 14232 abortXshm5: 14233 shmdt(shminfo.shmaddr); 14234 rawData = null; 14235 14236 abortXshm4: 14237 shmctl(shminfo.shmid, IPC_RMID, null); 14238 14239 abortXshm3: 14240 // nothing needed, the shmget failed so there's nothing to free 14241 14242 abortXshm2: 14243 XDestroyImage(handle); 14244 handle = null; 14245 14246 abortXshm1: 14247 XSetErrorHandler(oldErrorHandler); 14248 usingXshm = false; 14249 handle = null; 14250 14251 shminfo = typeof(shminfo).init; 14252 14253 _xshmAvailable = false; // don't try again in the future 14254 14255 // writeln("fallingback"); 14256 14257 goto fallback; 14258 14259 } else { 14260 fallback: 14261 14262 if (forcexshm) throw new Exception("can't create XShm Image"); 14263 // This actually needs to be malloc to avoid a double free error when XDestroyImage is called 14264 import core.stdc.stdlib : malloc; 14265 rawData = cast(ubyte*) malloc(width * height * 4); 14266 14267 handle = XCreateImage( 14268 display, 14269 DefaultVisual(display, screen), 14270 enableAlpha ? 32 : 24, // bpp 14271 ImageFormat.ZPixmap, 14272 0, // offset 14273 rawData, 14274 width, height, 14275 enableAlpha ? 32 : 8 /* FIXME */, 4 * width); // padding, bytes per line 14276 } 14277 } 14278 14279 void dispose() { 14280 // note: this calls free(rawData) for us 14281 if(handle) { 14282 if (usingXshm) { 14283 XDisplayConnection.unregisterImage(this); 14284 if (XDisplayConnection.get()) XShmDetach(XDisplayConnection.get(), &shminfo); 14285 } 14286 XDestroyImage(handle); 14287 if(usingXshm) { 14288 shmdt(shminfo.shmaddr); 14289 shmctl(shminfo.shmid, IPC_RMID, null); 14290 } 14291 handle = null; 14292 } 14293 } 14294 14295 Color getPixel(int x, int y) { 14296 auto offset = (y * width + x) * 4; 14297 Color c; 14298 c.a = enableAlpha ? rawData[offset + 3] : 255; 14299 c.b = rawData[offset + 0]; 14300 c.g = rawData[offset + 1]; 14301 c.r = rawData[offset + 2]; 14302 if(enableAlpha) 14303 c.unPremultiply; 14304 return c; 14305 } 14306 14307 void setPixel(int x, int y, Color c) { 14308 if(enableAlpha) 14309 c.premultiply(); 14310 auto offset = (y * width + x) * 4; 14311 rawData[offset + 0] = c.b; 14312 rawData[offset + 1] = c.g; 14313 rawData[offset + 2] = c.r; 14314 if(enableAlpha) 14315 rawData[offset + 3] = c.a; 14316 } 14317 14318 void convertToRgbaBytes(ubyte[] where) { 14319 assert(where.length == this.width * this.height * 4); 14320 14321 // if rawData had a length.... 14322 //assert(rawData.length == where.length); 14323 for(int idx = 0; idx < where.length; idx += 4) { 14324 where[idx + 0] = rawData[idx + 2]; // r 14325 where[idx + 1] = rawData[idx + 1]; // g 14326 where[idx + 2] = rawData[idx + 0]; // b 14327 where[idx + 3] = enableAlpha ? rawData[idx + 3] : 255; // a 14328 14329 if(enableAlpha) 14330 unPremultiplyRgba(where[idx .. idx + 4]); 14331 } 14332 } 14333 14334 void setFromRgbaBytes(in ubyte[] where) { 14335 assert(where.length == this.width * this.height * 4); 14336 14337 // if rawData had a length.... 14338 //assert(rawData.length == where.length); 14339 for(int idx = 0; idx < where.length; idx += 4) { 14340 rawData[idx + 2] = where[idx + 0]; // r 14341 rawData[idx + 1] = where[idx + 1]; // g 14342 rawData[idx + 0] = where[idx + 2]; // b 14343 if(enableAlpha) { 14344 rawData[idx + 3] = where[idx + 3]; // a 14345 premultiplyBgra(rawData[idx .. idx + 4]); 14346 } 14347 } 14348 } 14349 14350 } 14351 14352 mixin template NativeSimpleWindowImplementation() { 14353 GC gc; 14354 Window window; 14355 Display* display; 14356 14357 Pixmap buffer; 14358 int bufferw, bufferh; // size of the buffer; can be bigger than window 14359 XIC xic; // input context 14360 int curHidden = 0; // counter 14361 Cursor blankCurPtr = 0; 14362 int cursorSequenceNumber = 0; 14363 int warpEventCount = 0; // number of mouse movement events to eat 14364 14365 __gshared X11SetSelectionHandler[Atom] setSelectionHandlers; 14366 X11GetSelectionHandler[Atom] getSelectionHandlers; 14367 14368 version(without_opengl) {} else 14369 GLXContext glc; 14370 14371 private void fixFixedSize(bool forced=false) (int width, int height) { 14372 if (forced || this.resizability == Resizability.fixedSize) { 14373 //{ import core.stdc.stdio; printf("fixing size to: %dx%d\n", width, height); } 14374 XSizeHints sh; 14375 static if (!forced) { 14376 c_long spr; 14377 XGetWMNormalHints(display, window, &sh, &spr); 14378 sh.flags |= PMaxSize | PMinSize; 14379 } else { 14380 sh.flags = PMaxSize | PMinSize; 14381 } 14382 sh.min_width = width; 14383 sh.min_height = height; 14384 sh.max_width = width; 14385 sh.max_height = height; 14386 XSetWMNormalHints(display, window, &sh); 14387 //XFlush(display); 14388 } 14389 } 14390 14391 ScreenPainter getPainter(bool manualInvalidations) { 14392 return ScreenPainter(this, window, manualInvalidations); 14393 } 14394 14395 void move(int x, int y) { 14396 XMoveWindow(display, window, x, y); 14397 } 14398 14399 void resize(int w, int h) { 14400 if (w < 1) w = 1; 14401 if (h < 1) h = 1; 14402 XResizeWindow(display, window, w, h); 14403 14404 // calling this now to avoid waiting for the server to 14405 // acknowledge the resize; draws without returning to the 14406 // event loop will thus actually work. the server's event 14407 // btw might overrule this and resize it again 14408 recordX11Resize(display, this, w, h); 14409 14410 updateOpenglViewportIfNeeded(w, h); 14411 } 14412 14413 void moveResize (int x, int y, int w, int h) { 14414 if (w < 1) w = 1; 14415 if (h < 1) h = 1; 14416 XMoveResizeWindow(display, window, x, y, w, h); 14417 updateOpenglViewportIfNeeded(w, h); 14418 } 14419 14420 void hideCursor () { 14421 if (curHidden++ == 0) { 14422 if (!blankCurPtr || cursorSequenceNumber != XDisplayConnection.connectionSequenceNumber) { 14423 static const(char)[1] cmbmp = 0; 14424 XColor blackcolor = { 0, 0, 0, 0, 0, 0 }; 14425 Pixmap pm = XCreateBitmapFromData(display, window, cmbmp.ptr, 1, 1); 14426 blankCurPtr = XCreatePixmapCursor(display, pm, pm, &blackcolor, &blackcolor, 0, 0); 14427 cursorSequenceNumber = XDisplayConnection.connectionSequenceNumber; 14428 XFreePixmap(display, pm); 14429 } 14430 XDefineCursor(display, window, blankCurPtr); 14431 } 14432 } 14433 14434 void showCursor () { 14435 if (--curHidden == 0) XUndefineCursor(display, window); 14436 } 14437 14438 void warpMouse (int x, int y) { 14439 // here i will send dummy "ignore next mouse motion" event, 14440 // 'cause `XWarpPointer()` sends synthesised mouse motion, 14441 // and we don't need to report it to the user (as warping is 14442 // used when the user needs movement deltas). 14443 //XClientMessageEvent xclient; 14444 XEvent e; 14445 e.xclient.type = EventType.ClientMessage; 14446 e.xclient.window = window; 14447 e.xclient.message_type = GetAtom!("_X11SDPY_INSMME_FLAG_EVENT_", true)(display); // let's hope nobody else will use such stupid name ;-) 14448 e.xclient.format = 32; 14449 e.xclient.data.l[0] = 0; 14450 debug(x11sdpy_warp_debug) { import core.stdc.stdio : printf; printf("X11: sending \"INSMME\"...\n"); } 14451 //{ 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]); } 14452 XSendEvent(display, window, false, EventMask.NoEventMask, /*cast(XEvent*)&xclient*/&e); 14453 // now warp pointer... 14454 debug(x11sdpy_warp_debug) { import core.stdc.stdio : printf; printf("X11: sending \"warp\"...\n"); } 14455 XWarpPointer(display, None, window, 0, 0, 0, 0, x, y); 14456 // ...and flush 14457 debug(x11sdpy_warp_debug) { import core.stdc.stdio : printf; printf("X11: flushing...\n"); } 14458 XFlush(display); 14459 } 14460 14461 void sendDummyEvent () { 14462 // here i will send dummy event to ping event queue 14463 XEvent e; 14464 e.xclient.type = EventType.ClientMessage; 14465 e.xclient.window = window; 14466 e.xclient.message_type = GetAtom!("_X11SDPY_DUMMY_EVENT_", true)(display); // let's hope nobody else will use such stupid name ;-) 14467 e.xclient.format = 32; 14468 e.xclient.data.l[0] = 0; 14469 XSendEvent(display, window, false, EventMask.NoEventMask, /*cast(XEvent*)&xclient*/&e); 14470 XFlush(display); 14471 } 14472 14473 void setTitle(string title) { 14474 if (title.ptr is null) title = ""; 14475 auto XA_UTF8 = XInternAtom(display, "UTF8_STRING".ptr, false); 14476 auto XA_NETWM_NAME = XInternAtom(display, "_NET_WM_NAME".ptr, false); 14477 XTextProperty windowName; 14478 windowName.value = title.ptr; 14479 windowName.encoding = XA_UTF8; //XA_STRING; 14480 windowName.format = 8; 14481 windowName.nitems = cast(uint)title.length; 14482 XSetWMName(display, window, &windowName); 14483 char[1024] namebuf = 0; 14484 auto maxlen = namebuf.length-1; 14485 if (maxlen > title.length) maxlen = title.length; 14486 namebuf[0..maxlen] = title[0..maxlen]; 14487 XStoreName(display, window, namebuf.ptr); 14488 XChangeProperty(display, window, XA_NETWM_NAME, XA_UTF8, 8, PropModeReplace, title.ptr, cast(uint)title.length); 14489 flushGui(); // without this OpenGL windows has a LONG delay before changing title 14490 } 14491 14492 string[] getTitles() { 14493 auto XA_UTF8 = XInternAtom(display, "UTF8_STRING".ptr, false); 14494 auto XA_NETWM_NAME = XInternAtom(display, "_NET_WM_NAME".ptr, false); 14495 XTextProperty textProp; 14496 if (XGetTextProperty(display, window, &textProp, XA_NETWM_NAME) != 0 || XGetWMName(display, window, &textProp) != 0) { 14497 if ((textProp.encoding == XA_UTF8 || textProp.encoding == XA_STRING) && textProp.format == 8) { 14498 return textProp.value[0 .. textProp.nitems].idup.split('\0'); 14499 } else 14500 return []; 14501 } else 14502 return null; 14503 } 14504 14505 string getTitle() { 14506 auto titles = getTitles(); 14507 return titles.length ? titles[0] : null; 14508 } 14509 14510 void setMinSize (int minwidth, int minheight) { 14511 import core.stdc.config : c_long; 14512 if (minwidth < 1) minwidth = 1; 14513 if (minheight < 1) minheight = 1; 14514 XSizeHints sh; 14515 c_long spr; 14516 XGetWMNormalHints(display, window, &sh, &spr); 14517 sh.min_width = minwidth; 14518 sh.min_height = minheight; 14519 sh.flags |= PMinSize; 14520 XSetWMNormalHints(display, window, &sh); 14521 flushGui(); 14522 } 14523 14524 void setMaxSize (int maxwidth, int maxheight) { 14525 import core.stdc.config : c_long; 14526 if (maxwidth < 1) maxwidth = 1; 14527 if (maxheight < 1) maxheight = 1; 14528 XSizeHints sh; 14529 c_long spr; 14530 XGetWMNormalHints(display, window, &sh, &spr); 14531 sh.max_width = maxwidth; 14532 sh.max_height = maxheight; 14533 sh.flags |= PMaxSize; 14534 XSetWMNormalHints(display, window, &sh); 14535 flushGui(); 14536 } 14537 14538 void setResizeGranularity (int granx, int grany) { 14539 import core.stdc.config : c_long; 14540 if (granx < 1) granx = 1; 14541 if (grany < 1) grany = 1; 14542 XSizeHints sh; 14543 c_long spr; 14544 XGetWMNormalHints(display, window, &sh, &spr); 14545 sh.width_inc = granx; 14546 sh.height_inc = grany; 14547 sh.flags |= PResizeInc; 14548 XSetWMNormalHints(display, window, &sh); 14549 flushGui(); 14550 } 14551 14552 void setOpacity (uint opacity) { 14553 arch_ulong o = opacity; 14554 if (opacity == uint.max) 14555 XDeleteProperty(display, window, XInternAtom(display, "_NET_WM_WINDOW_OPACITY".ptr, false)); 14556 else 14557 XChangeProperty(display, window, XInternAtom(display, "_NET_WM_WINDOW_OPACITY".ptr, false), 14558 XA_CARDINAL, 32, PropModeReplace, &o, 1); 14559 } 14560 14561 void createWindow(int width, int height, string title, in OpenGlOptions opengl, SimpleWindow parent) { 14562 version(without_opengl) {} else if(opengl == OpenGlOptions.yes && !openGlLibrariesSuccessfullyLoaded) throw new Exception("OpenGL libraries did not load"); 14563 display = XDisplayConnection.get(); 14564 auto screen = DefaultScreen(display); 14565 14566 bool overrideRedirect = false; 14567 if(windowType == WindowTypes.dropdownMenu || windowType == WindowTypes.popupMenu || windowType == WindowTypes.notification)// || windowType == WindowTypes.nestedChild) 14568 overrideRedirect = true; 14569 14570 version(without_opengl) {} 14571 else { 14572 if(opengl == OpenGlOptions.yes) { 14573 GLXFBConfig fbconf = null; 14574 XVisualInfo* vi = null; 14575 bool useLegacy = false; 14576 static if (SdpyIsUsingIVGLBinds) {if (glbindGetProcAddress("glHint") is null) assert(0, "GL: error loading OpenGL"); } // loads all necessary functions 14577 if (sdpyOpenGLContextVersion != 0 && glXCreateContextAttribsARB_present()) { 14578 int[23] visualAttribs = [ 14579 GLX_X_RENDERABLE , 1/*True*/, 14580 GLX_DRAWABLE_TYPE, GLX_WINDOW_BIT, 14581 GLX_RENDER_TYPE , GLX_RGBA_BIT, 14582 GLX_X_VISUAL_TYPE, GLX_TRUE_COLOR, 14583 GLX_RED_SIZE , 8, 14584 GLX_GREEN_SIZE , 8, 14585 GLX_BLUE_SIZE , 8, 14586 GLX_ALPHA_SIZE , 8, 14587 GLX_DEPTH_SIZE , 24, 14588 GLX_STENCIL_SIZE , 8, 14589 GLX_DOUBLEBUFFER , 1/*True*/, 14590 0/*None*/, 14591 ]; 14592 int fbcount; 14593 GLXFBConfig* fbc = glXChooseFBConfig(display, screen, visualAttribs.ptr, &fbcount); 14594 if (fbcount == 0) { 14595 useLegacy = true; // try to do at least something 14596 } else { 14597 // pick the FB config/visual with the most samples per pixel 14598 int bestidx = -1, bestns = -1; 14599 foreach (int fbi; 0..fbcount) { 14600 int sb, samples; 14601 glXGetFBConfigAttrib(display, fbc[fbi], GLX_SAMPLE_BUFFERS, &sb); 14602 glXGetFBConfigAttrib(display, fbc[fbi], GLX_SAMPLES, &samples); 14603 if (bestidx < 0 || sb && samples > bestns) { bestidx = fbi; bestns = samples; } 14604 } 14605 //{ import core.stdc.stdio; printf("found gl visual with %d samples\n", bestns); } 14606 fbconf = fbc[bestidx]; 14607 // Be sure to free the FBConfig list allocated by glXChooseFBConfig() 14608 XFree(fbc); 14609 vi = cast(XVisualInfo*)glXGetVisualFromFBConfig(display, fbconf); 14610 } 14611 } 14612 if (vi is null || useLegacy) { 14613 static immutable GLint[5] attrs = [ GLX_RGBA, GLX_DEPTH_SIZE, 24, GLX_DOUBLEBUFFER, None ]; 14614 vi = cast(XVisualInfo*)glXChooseVisual(display, 0, attrs.ptr); 14615 useLegacy = true; 14616 } 14617 if (vi is null) throw new Exception("no open gl visual found"); 14618 14619 XSetWindowAttributes swa; 14620 auto root = RootWindow(display, screen); 14621 swa.colormap = XCreateColormap(display, root, vi.visual, AllocNone); 14622 14623 swa.override_redirect = overrideRedirect; 14624 14625 window = XCreateWindow(display, (windowType != WindowTypes.nestedChild || parent is null) ? root : parent.impl.window, 14626 0, 0, width, height, 14627 0, vi.depth, 1 /* InputOutput */, vi.visual, CWColormap | CWOverrideRedirect, &swa); 14628 14629 // now try to use `glXCreateContextAttribsARB()` if it's here 14630 if (!useLegacy) { 14631 // request fairly advanced context, even with stencil buffer! 14632 int[9] contextAttribs = [ 14633 GLX_CONTEXT_MAJOR_VERSION_ARB, (sdpyOpenGLContextVersion>>8), 14634 GLX_CONTEXT_MINOR_VERSION_ARB, (sdpyOpenGLContextVersion&0xff), 14635 /*GLX_CONTEXT_PROFILE_MASK_ARB*/0x9126, (sdpyOpenGLContextCompatible ? /*GLX_CONTEXT_COMPATIBILITY_PROFILE_BIT_ARB*/0x02 : /*GLX_CONTEXT_CORE_PROFILE_BIT_ARB*/ 0x01), 14636 // for modern context, set "forward compatibility" flag too 14637 (sdpyOpenGLContextCompatible ? None : /*GLX_CONTEXT_FLAGS_ARB*/ 0x2094), /*GLX_CONTEXT_FORWARD_COMPATIBLE_BIT_ARB*/ 0x02, 14638 0/*None*/, 14639 ]; 14640 glc = glXCreateContextAttribsARB(display, fbconf, null, 1/*True*/, contextAttribs.ptr); 14641 if (glc is null && sdpyOpenGLContextAllowFallback) { 14642 sdpyOpenGLContextVersion = 0; 14643 glc = glXCreateContext(display, vi, null, /*GL_TRUE*/1); 14644 } 14645 //{ import core.stdc.stdio; printf("using modern ogl v%d.%d\n", contextAttribs[1], contextAttribs[3]); } 14646 } else { 14647 // fallback to old GLX call 14648 if (sdpyOpenGLContextAllowFallback || sdpyOpenGLContextVersion == 0) { 14649 sdpyOpenGLContextVersion = 0; 14650 glc = glXCreateContext(display, vi, null, /*GL_TRUE*/1); 14651 } 14652 } 14653 // sync to ensure any errors generated are processed 14654 XSync(display, 0/*False*/); 14655 //{ import core.stdc.stdio; printf("ogl is here\n"); } 14656 if(glc is null) 14657 throw new Exception("glc"); 14658 } 14659 } 14660 14661 if(opengl == OpenGlOptions.no) { 14662 14663 XSetWindowAttributes swa; 14664 swa.background_pixel = WhitePixel(display, screen); 14665 swa.border_pixel = BlackPixel(display, screen); 14666 swa.override_redirect = overrideRedirect; 14667 auto root = RootWindow(display, screen); 14668 swa.colormap = XCreateColormap(display, root, DefaultVisual(display, screen), AllocNone); 14669 14670 window = XCreateWindow(display, (windowType != WindowTypes.nestedChild || parent is null) ? root : parent.impl.window, 14671 0, 0, width, height, 14672 // I'm commenting that CWBackPixel thing just because it actually causes flicker for no apparent benefit. 14673 0, CopyFromParent, 1 /* InputOutput */, cast(Visual*) CopyFromParent, CWColormap /*| CWBackPixel*/ | CWBorderPixel | CWOverrideRedirect, &swa); 14674 14675 14676 14677 /* 14678 window = XCreateSimpleWindow( 14679 display, 14680 parent is null ? RootWindow(display, screen) : parent.impl.window, 14681 0, 0, // x, y 14682 width, height, 14683 1, // border width 14684 BlackPixel(display, screen), // border 14685 WhitePixel(display, screen)); // background 14686 */ 14687 14688 buffer = XCreatePixmap(display, cast(Drawable) window, width, height, DefaultDepthOfDisplay(display)); 14689 bufferw = width; 14690 bufferh = height; 14691 14692 gc = DefaultGC(display, screen); 14693 14694 // clear out the buffer to get us started... 14695 XSetForeground(display, gc, WhitePixel(display, screen)); 14696 XFillRectangle(display, cast(Drawable) buffer, gc, 0, 0, width, height); 14697 XSetForeground(display, gc, BlackPixel(display, screen)); 14698 } 14699 14700 // input context 14701 //TODO: create this only for top-level windows, and reuse that? 14702 populateXic(); 14703 14704 if (sdpyWindowClassStr is null) loadBinNameToWindowClassName(); 14705 if (sdpyWindowClassStr is null) sdpyWindowClass = "DSimpleWindow"; 14706 // window class 14707 if (sdpyWindowClassStr !is null && sdpyWindowClassStr[0]) { 14708 //{ import core.stdc.stdio; printf("winclass: [%s]\n", sdpyWindowClassStr); } 14709 XClassHint klass; 14710 XWMHints wh; 14711 if(this.customizationFlags & WindowFlags.managesChildWindowFocus) { 14712 wh.input = true; 14713 wh.flags |= InputHint; 14714 } 14715 XSizeHints size; 14716 klass.res_name = sdpyWindowClassStr; 14717 klass.res_class = sdpyWindowClassStr; 14718 XSetWMProperties(display, window, null, null, null, 0, &size, &wh, &klass); 14719 } 14720 14721 setTitle(title); 14722 SimpleWindow.nativeMapping[window] = this; 14723 CapableOfHandlingNativeEvent.nativeHandleMapping[window] = this; 14724 14725 // This gives our window a close button 14726 if (windowType != WindowTypes.eventOnly) { 14727 Atom[2] atoms = [GetAtom!"WM_DELETE_WINDOW"(display), GetAtom!"WM_TAKE_FOCUS"(display)]; 14728 int useAtoms; 14729 if(this.customizationFlags & WindowFlags.managesChildWindowFocus) { 14730 useAtoms = 2; 14731 } else { 14732 useAtoms = 1; 14733 } 14734 assert(useAtoms <= atoms.length); 14735 XSetWMProtocols(display, window, atoms.ptr, useAtoms); 14736 } 14737 14738 // FIXME: windowType and customizationFlags 14739 Atom[8] wsatoms; // here, due to goto 14740 int wmsacount = 0; // here, due to goto 14741 14742 try 14743 final switch(windowType) { 14744 case WindowTypes.normal: 14745 setNetWMWindowType(GetAtom!"_NET_WM_WINDOW_TYPE_NORMAL"(display)); 14746 break; 14747 case WindowTypes.undecorated: 14748 motifHideDecorations(); 14749 setNetWMWindowType(GetAtom!"_NET_WM_WINDOW_TYPE_NORMAL"(display)); 14750 break; 14751 case WindowTypes.eventOnly: 14752 _hidden = true; 14753 XSelectInput(display, window, EventMask.StructureNotifyMask); // without this, we won't get destroy notification 14754 goto hiddenWindow; 14755 //break; 14756 case WindowTypes.nestedChild: 14757 // handled in XCreateWindow calls 14758 break; 14759 14760 case WindowTypes.dropdownMenu: 14761 motifHideDecorations(); 14762 setNetWMWindowType(GetAtom!"_NET_WM_WINDOW_TYPE_DROPDOWN_MENU"(display)); 14763 customizationFlags |= WindowFlags.skipTaskbar | WindowFlags.alwaysOnTop; 14764 break; 14765 case WindowTypes.popupMenu: 14766 motifHideDecorations(); 14767 setNetWMWindowType(GetAtom!"_NET_WM_WINDOW_TYPE_POPUP_MENU"(display)); 14768 customizationFlags |= WindowFlags.skipTaskbar | WindowFlags.alwaysOnTop; 14769 break; 14770 case WindowTypes.notification: 14771 motifHideDecorations(); 14772 setNetWMWindowType(GetAtom!"_NET_WM_WINDOW_TYPE_NOTIFICATION"(display)); 14773 customizationFlags |= WindowFlags.skipTaskbar | WindowFlags.alwaysOnTop; 14774 break; 14775 case WindowTypes.minimallyWrapped: 14776 assert(0, "don't create a minimallyWrapped thing explicitly!"); 14777 /+ 14778 case WindowTypes.menu: 14779 atoms[0] = GetAtom!"_NET_WM_WINDOW_TYPE_MENU"(display); 14780 motifHideDecorations(); 14781 break; 14782 case WindowTypes.desktop: 14783 atoms[0] = GetAtom!"_NET_WM_WINDOW_TYPE_DESKTOP"(display); 14784 break; 14785 case WindowTypes.dock: 14786 atoms[0] = GetAtom!"_NET_WM_WINDOW_TYPE_DOCK"(display); 14787 break; 14788 case WindowTypes.toolbar: 14789 atoms[0] = GetAtom!"_NET_WM_WINDOW_TYPE_TOOLBAR"(display); 14790 break; 14791 case WindowTypes.menu: 14792 atoms[0] = GetAtom!"_NET_WM_WINDOW_TYPE_MENU"(display); 14793 break; 14794 case WindowTypes.utility: 14795 atoms[0] = GetAtom!"_NET_WM_WINDOW_TYPE_UTILITY"(display); 14796 break; 14797 case WindowTypes.splash: 14798 atoms[0] = GetAtom!"_NET_WM_WINDOW_TYPE_SPLASH"(display); 14799 break; 14800 case WindowTypes.dialog: 14801 atoms[0] = GetAtom!"_NET_WM_WINDOW_TYPE_DIALOG"(display); 14802 break; 14803 case WindowTypes.tooltip: 14804 atoms[0] = GetAtom!"_NET_WM_WINDOW_TYPE_TOOLTIP"(display); 14805 break; 14806 case WindowTypes.notification: 14807 atoms[0] = GetAtom!"_NET_WM_WINDOW_TYPE_NOTIFICATION"(display); 14808 break; 14809 case WindowTypes.combo: 14810 atoms[0] = GetAtom!"_NET_WM_WINDOW_TYPE_COMBO"(display); 14811 break; 14812 case WindowTypes.dnd: 14813 atoms[0] = GetAtom!"_NET_WM_WINDOW_TYPE_DND"(display); 14814 break; 14815 +/ 14816 } 14817 catch(Exception e) { 14818 // XInternAtom failed, prolly a WM 14819 // that doesn't support these things 14820 } 14821 14822 if (customizationFlags&WindowFlags.skipTaskbar) wsatoms[wmsacount++] = GetAtom!("_NET_WM_STATE_SKIP_TASKBAR", true)(display); 14823 // the two following flags may be ignored by WM 14824 if (customizationFlags&WindowFlags.alwaysOnTop) wsatoms[wmsacount++] = GetAtom!("_NET_WM_STATE_ABOVE", true)(display); 14825 if (customizationFlags&WindowFlags.alwaysOnBottom) wsatoms[wmsacount++] = GetAtom!("_NET_WM_STATE_BELOW", true)(display); 14826 14827 if (wmsacount != 0) XChangeProperty(display, window, GetAtom!("_NET_WM_STATE", true)(display), XA_ATOM, 32 /* bits */,0 /*PropModeReplace*/, wsatoms.ptr, wmsacount); 14828 14829 if (this.resizability == Resizability.fixedSize || (opengl == OpenGlOptions.no && this.resizability != Resizability.allowResizing)) fixFixedSize!true(width, height); 14830 14831 // What would be ideal here is if they only were 14832 // selected if there was actually an event handler 14833 // for them... 14834 14835 selectDefaultInput((customizationFlags & WindowFlags.alwaysRequestMouseMotionEvents)?true:false); 14836 14837 hiddenWindow: 14838 14839 // set the pid property for lookup later by window managers 14840 // a standard convenience 14841 import core.sys.posix.unistd; 14842 arch_ulong pid = getpid(); 14843 14844 XChangeProperty( 14845 display, 14846 impl.window, 14847 GetAtom!("_NET_WM_PID", true)(display), 14848 XA_CARDINAL, 14849 32 /* bits */, 14850 0 /*PropModeReplace*/, 14851 &pid, 14852 1); 14853 14854 if(isTransient && parent) { // customizationFlags & WindowFlags.transient) { 14855 if(parent is null) assert(0); 14856 XChangeProperty( 14857 display, 14858 impl.window, 14859 GetAtom!("WM_TRANSIENT_FOR", true)(display), 14860 XA_WINDOW, 14861 32 /* bits */, 14862 0 /*PropModeReplace*/, 14863 &parent.impl.window, 14864 1); 14865 14866 } 14867 14868 if(windowType != WindowTypes.eventOnly && (customizationFlags&WindowFlags.dontAutoShow) == 0) { 14869 XMapWindow(display, window); 14870 } else { 14871 _hidden = true; 14872 } 14873 } 14874 14875 void populateXic() { 14876 if (XDisplayConnection.xim !is null) { 14877 xic = XCreateIC(XDisplayConnection.xim, 14878 /*XNInputStyle*/"inputStyle".ptr, XIMPreeditNothing|XIMStatusNothing, 14879 /*XNClientWindow*/"clientWindow".ptr, window, 14880 /*XNFocusWindow*/"focusWindow".ptr, window, 14881 null); 14882 if (xic is null) { 14883 import core.stdc.stdio : stderr, fprintf; 14884 fprintf(stderr, "XCreateIC failed for window %u\n", cast(uint)window); 14885 } 14886 } 14887 } 14888 14889 void selectDefaultInput(bool forceIncludeMouseMotion) { 14890 auto mask = EventMask.ExposureMask | 14891 EventMask.KeyPressMask | 14892 EventMask.KeyReleaseMask | 14893 EventMask.PropertyChangeMask | 14894 EventMask.FocusChangeMask | 14895 EventMask.StructureNotifyMask | 14896 EventMask.SubstructureNotifyMask | 14897 EventMask.VisibilityChangeMask 14898 | EventMask.ButtonPressMask 14899 | EventMask.ButtonReleaseMask 14900 ; 14901 14902 // xshm is our shortcut for local connections 14903 if(XDisplayConnection.isLocal || forceIncludeMouseMotion) 14904 mask |= EventMask.PointerMotionMask; 14905 else 14906 mask |= EventMask.ButtonMotionMask; 14907 14908 XSelectInput(display, window, mask); 14909 } 14910 14911 14912 void setNetWMWindowType(Atom type) { 14913 Atom[2] atoms; 14914 14915 atoms[0] = type; 14916 // generic fallback 14917 atoms[1] = GetAtom!"_NET_WM_WINDOW_TYPE_NORMAL"(display); 14918 14919 XChangeProperty( 14920 display, 14921 impl.window, 14922 GetAtom!"_NET_WM_WINDOW_TYPE"(display), 14923 XA_ATOM, 14924 32 /* bits */, 14925 0 /*PropModeReplace*/, 14926 atoms.ptr, 14927 cast(int) atoms.length); 14928 } 14929 14930 void motifHideDecorations(bool hide = true) { 14931 MwmHints hints; 14932 hints.flags = MWM_HINTS_DECORATIONS; 14933 hints.decorations = hide ? 0 : 1; 14934 14935 XChangeProperty( 14936 display, 14937 impl.window, 14938 GetAtom!"_MOTIF_WM_HINTS"(display), 14939 GetAtom!"_MOTIF_WM_HINTS"(display), 14940 32 /* bits */, 14941 0 /*PropModeReplace*/, 14942 &hints, 14943 hints.sizeof / 4); 14944 } 14945 14946 /*k8: unused 14947 void createOpenGlContext() { 14948 14949 } 14950 */ 14951 14952 void closeWindow() { 14953 // I can't close this or a child window closing will 14954 // break events for everyone. So I'm just leaking it right 14955 // now and that is probably perfectly fine... 14956 version(none) 14957 if (customEventFDRead != -1) { 14958 import core.sys.posix.unistd : close; 14959 auto same = customEventFDRead == customEventFDWrite; 14960 14961 close(customEventFDRead); 14962 if(!same) 14963 close(customEventFDWrite); 14964 customEventFDRead = -1; 14965 customEventFDWrite = -1; 14966 } 14967 14968 if(glc !is null) { 14969 glXDestroyContext(display, glc); 14970 glc = null; 14971 } 14972 14973 if(buffer) 14974 XFreePixmap(display, buffer); 14975 bufferw = bufferh = 0; 14976 if (blankCurPtr && cursorSequenceNumber == XDisplayConnection.connectionSequenceNumber) XFreeCursor(display, blankCurPtr); 14977 XDestroyWindow(display, window); 14978 XFlush(display); 14979 } 14980 14981 void dispose() { 14982 } 14983 14984 bool destroyed = false; 14985 } 14986 14987 bool insideXEventLoop; 14988 } 14989 14990 version(X11) { 14991 14992 int mouseDoubleClickTimeout = 350; /// Double click timeout. X only, you probably shouldn't change this. 14993 14994 private class ResizeEvent { 14995 int width, height; 14996 } 14997 14998 void recordX11ResizeAsync(Display* display, SimpleWindow win, int width, int height) { 14999 if(win.windowType == WindowTypes.minimallyWrapped) 15000 return; 15001 15002 if(win.pendingResizeEvent is null) { 15003 win.pendingResizeEvent = new ResizeEvent(); 15004 win.addEventListener((ResizeEvent re) { 15005 recordX11Resize(XDisplayConnection.get, win, re.width, re.height); 15006 }); 15007 } 15008 win.pendingResizeEvent.width = width; 15009 win.pendingResizeEvent.height = height; 15010 if(!win.eventQueued!ResizeEvent) { 15011 win.postEvent(win.pendingResizeEvent); 15012 } 15013 } 15014 15015 void recordX11Resize(Display* display, SimpleWindow win, int width, int height) { 15016 if(win.windowType == WindowTypes.minimallyWrapped) 15017 return; 15018 if(win.closed) 15019 return; 15020 15021 if(width != win.width || height != win.height) { 15022 15023 // writeln("RESIZE: ", width, "x", height, " was ", win._width, "x", win._height, " window: ", win.windowType, "-", win.title, " ", win.window); 15024 win._width = width; 15025 win._height = height; 15026 15027 if(win.openglMode == OpenGlOptions.no) { 15028 // FIXME: could this be more efficient? 15029 15030 if (win.bufferw < width || win.bufferh < height) { 15031 //{ 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); } 15032 // grow the internal buffer to match the window... 15033 auto newPixmap = XCreatePixmap(display, cast(Drawable) win.window, width, height, DefaultDepthOfDisplay(display)); 15034 { 15035 GC xgc = XCreateGC(win.display, cast(Drawable)win.window, 0, null); 15036 XCopyGC(win.display, win.gc, 0xffffffff, xgc); 15037 scope(exit) XFreeGC(win.display, xgc); 15038 XSetClipMask(win.display, xgc, None); 15039 XSetForeground(win.display, xgc, 0); 15040 XFillRectangle(display, cast(Drawable)newPixmap, xgc, 0, 0, width, height); 15041 } 15042 XCopyArea(display, 15043 cast(Drawable) win.buffer, 15044 cast(Drawable) newPixmap, 15045 win.gc, 0, 0, 15046 win.bufferw < width ? win.bufferw : win.width, 15047 win.bufferh < height ? win.bufferh : win.height, 15048 0, 0); 15049 15050 XFreePixmap(display, win.buffer); 15051 win.buffer = newPixmap; 15052 win.bufferw = width; 15053 win.bufferh = height; 15054 } 15055 15056 // clear unused parts of the buffer 15057 if (win.bufferw > width || win.bufferh > height) { 15058 GC xgc = XCreateGC(win.display, cast(Drawable)win.window, 0, null); 15059 XCopyGC(win.display, win.gc, 0xffffffff, xgc); 15060 scope(exit) XFreeGC(win.display, xgc); 15061 XSetClipMask(win.display, xgc, None); 15062 XSetForeground(win.display, xgc, 0); 15063 immutable int maxw = (win.bufferw > width ? win.bufferw : width); 15064 immutable int maxh = (win.bufferh > height ? win.bufferh : height); 15065 XFillRectangle(win.display, cast(Drawable)win.buffer, xgc, width, 0, maxw, maxh); // let X11 do clipping 15066 XFillRectangle(win.display, cast(Drawable)win.buffer, xgc, 0, height, maxw, maxh); // let X11 do clipping 15067 } 15068 15069 } 15070 15071 win.updateOpenglViewportIfNeeded(width, height); 15072 15073 win.fixFixedSize(width, height); //k8: this does nothing on my FluxBox; wtf?! 15074 15075 if(win.resizability != Resizability.automaticallyScaleIfPossible) 15076 if(win.windowResized !is null) { 15077 XUnlockDisplay(display); 15078 scope(exit) XLockDisplay(display); 15079 win.windowResized(width, height); 15080 } 15081 } 15082 } 15083 15084 15085 /// Platform-specific, you might use it when doing a custom event loop. 15086 bool doXNextEvent(Display* display) { 15087 bool done; 15088 XEvent e; 15089 XNextEvent(display, &e); 15090 version(sddddd) { 15091 if(auto win = e.xany.window in CapableOfHandlingNativeEvent.nativeHandleMapping) { 15092 if(typeid(cast(Object) *win) == NotificationAreaIcon.classinfo) 15093 writeln("event for: ", e.xany.window, "; type is ", to!string(cast(EventType)e.type)); 15094 } 15095 } 15096 15097 // filter out compose events 15098 if (XFilterEvent(&e, None)) { 15099 //{ import core.stdc.stdio : printf; printf("XFilterEvent filtered!\n"); } 15100 //NOTE: we should ungrab keyboard here, but simpledisplay doesn't use keyboard grabbing (yet) 15101 return false; 15102 } 15103 // process keyboard mapping changes 15104 if (e.type == EventType.KeymapNotify) { 15105 //{ import core.stdc.stdio : printf; printf("KeymapNotify processed!\n"); } 15106 XRefreshKeyboardMapping(&e.xmapping); 15107 return false; 15108 } 15109 15110 version(with_eventloop) 15111 import arsd.eventloop; 15112 15113 if(SimpleWindow.handleNativeGlobalEvent !is null) { 15114 // see windows impl's comments 15115 XUnlockDisplay(display); 15116 scope(exit) XLockDisplay(display); 15117 auto ret = SimpleWindow.handleNativeGlobalEvent(e); 15118 if(ret == 0) 15119 return done; 15120 } 15121 15122 15123 if(auto win = e.xany.window in CapableOfHandlingNativeEvent.nativeHandleMapping) { 15124 if(win.getNativeEventHandler !is null) { 15125 XUnlockDisplay(display); 15126 scope(exit) XLockDisplay(display); 15127 auto ret = win.getNativeEventHandler()(e); 15128 if(ret == 0) 15129 return done; 15130 } 15131 } 15132 15133 if(xrrEventBase != -1 && e.type == xrrEventBase + RRScreenChangeNotify) { 15134 if(auto win = e.xany.window in SimpleWindow.nativeMapping) { 15135 // we get this because of the RRScreenChangeNotifyMask 15136 15137 // this isn't actually an ideal way to do it since it wastes time 15138 // but meh it is simple and it works. 15139 win.actualDpiLoadAttempted = false; 15140 SimpleWindow.xRandrInfoLoadAttemped = false; 15141 win.updateActualDpi(); // trigger a reload 15142 } 15143 } 15144 15145 switch(e.type) { 15146 case EventType.SelectionClear: 15147 if(auto win = e.xselectionclear.window in SimpleWindow.nativeMapping) { 15148 // FIXME so it is supposed to finish any in progress transfers... but idk... 15149 // writeln("SelectionClear"); 15150 SimpleWindow.impl.setSelectionHandlers.remove(e.xselectionclear.selection); 15151 } 15152 break; 15153 case EventType.SelectionRequest: 15154 if(auto win = e.xselectionrequest.owner in SimpleWindow.nativeMapping) 15155 if(auto ssh = e.xselectionrequest.selection in SimpleWindow.impl.setSelectionHandlers) { 15156 // printf("SelectionRequest %s\n", XGetAtomName(e.xselectionrequest.display, e.xselectionrequest.target)); 15157 XUnlockDisplay(display); 15158 scope(exit) XLockDisplay(display); 15159 (*ssh).handleRequest(e); 15160 } 15161 break; 15162 case EventType.PropertyNotify: 15163 // printf("PropertyNotify %s %d\n", XGetAtomName(e.xproperty.display, e.xproperty.atom), e.xproperty.state); 15164 15165 foreach(ssh; SimpleWindow.impl.setSelectionHandlers) { 15166 if(ssh.matchesIncr(e.xproperty.window, e.xproperty.atom) && e.xproperty.state == PropertyNotification.PropertyDelete) 15167 ssh.sendMoreIncr(&e.xproperty); 15168 } 15169 15170 15171 if(auto win = e.xproperty.window in SimpleWindow.nativeMapping) 15172 if(auto handler = e.xproperty.atom in win.getSelectionHandlers) { 15173 if(handler.matchesIncr(e.xproperty.window, e.xproperty.atom) && e.xproperty.state == PropertyNotification.PropertyNewValue) { 15174 Atom target; 15175 int format; 15176 arch_ulong bytesafter, length; 15177 void* value; 15178 15179 ubyte[] s; 15180 Atom targetToKeep; 15181 15182 XGetWindowProperty( 15183 e.xproperty.display, 15184 e.xproperty.window, 15185 e.xproperty.atom, 15186 0, 15187 100000 /* length */, 15188 true, /* erase it to signal we got it and want more */ 15189 0 /*AnyPropertyType*/, 15190 &target, &format, &length, &bytesafter, &value); 15191 15192 if(!targetToKeep) 15193 targetToKeep = target; 15194 15195 auto id = (cast(ubyte*) value)[0 .. length]; 15196 15197 handler.handleIncrData(targetToKeep, id); 15198 15199 XFree(value); 15200 } 15201 } 15202 break; 15203 case EventType.SelectionNotify: 15204 if(auto win = e.xselection.requestor in SimpleWindow.nativeMapping) 15205 if(auto handler = e.xproperty.atom in win.getSelectionHandlers) { 15206 if(e.xselection.property == None) { // || e.xselection.property == GetAtom!("NULL", true)(e.xselection.display)) { 15207 XUnlockDisplay(display); 15208 scope(exit) XLockDisplay(display); 15209 handler.handleData(None, null); 15210 } else { 15211 Atom target; 15212 int format; 15213 arch_ulong bytesafter, length; 15214 void* value; 15215 XGetWindowProperty( 15216 e.xselection.display, 15217 e.xselection.requestor, 15218 e.xselection.property, 15219 0, 15220 100000 /* length */, 15221 //false, /* don't erase it */ 15222 true, /* do erase it lol */ 15223 0 /*AnyPropertyType*/, 15224 &target, &format, &length, &bytesafter, &value); 15225 15226 // FIXME: I don't have to copy it now since it is in char[] instead of string 15227 15228 { 15229 XUnlockDisplay(display); 15230 scope(exit) XLockDisplay(display); 15231 15232 if(target == XA_ATOM) { 15233 // initial request, see what they are able to work with and request the best one 15234 // we can handle, if available 15235 15236 Atom[] answer = (cast(Atom*) value)[0 .. length]; 15237 Atom best = handler.findBestFormat(answer); 15238 15239 /+ 15240 writeln("got ", answer); 15241 foreach(a; answer) 15242 printf("%s\n", XGetAtomName(display, a)); 15243 writeln("best ", best); 15244 +/ 15245 15246 if(best != None) { 15247 // actually request the best format 15248 XConvertSelection(e.xselection.display, e.xselection.selection, best, GetAtom!("SDD_DATA", true)(display), e.xselection.requestor, 0 /*CurrentTime*/); 15249 } 15250 } else if(target == GetAtom!"INCR"(display)) { 15251 // incremental 15252 15253 handler.prepareIncremental(e.xselection.requestor, e.xselection.property); 15254 15255 // signal the sending program that we see 15256 // the incr and are ready to receive more. 15257 XDeleteProperty( 15258 e.xselection.display, 15259 e.xselection.requestor, 15260 e.xselection.property); 15261 } else { 15262 // unsupported type... maybe, forward 15263 handler.handleData(target, cast(ubyte[]) value[0 .. length]); 15264 } 15265 } 15266 XFree(value); 15267 /* 15268 XDeleteProperty( 15269 e.xselection.display, 15270 e.xselection.requestor, 15271 e.xselection.property); 15272 */ 15273 } 15274 } 15275 break; 15276 case EventType.ConfigureNotify: 15277 auto event = e.xconfigure; 15278 if(auto win = event.window in SimpleWindow.nativeMapping) { 15279 if(win.windowType == WindowTypes.minimallyWrapped) 15280 break; 15281 //version(sdddd) { writeln(" w=", event.width, "; h=", event.height); } 15282 15283 /+ 15284 The ICCCM says window managers must send a synthetic event when the window 15285 is moved but NOT when it is resized. In the resize case, an event is sent 15286 with position (0, 0) which can be wrong and break the dpi calculations. 15287 15288 So we only consider the synthetic events from the WM and otherwise 15289 need to wait for some other event to get the position which... sucks. 15290 15291 I'd rather not have windows changing their layout on mouse motion after 15292 switching monitors... might be forced to but for now just ignoring it. 15293 15294 Easiest way to switch monitors without sending a size position is by 15295 maximize or fullscreen in a setup like mine, but on most setups those 15296 work on the monitor it is already living on, so it should be ok most the 15297 time. 15298 +/ 15299 if(event.send_event) { 15300 win.screenPositionKnown = true; 15301 win.screenPositionX = event.x; 15302 win.screenPositionY = event.y; 15303 win.updateActualDpi(); 15304 } 15305 15306 win.updateIMEPopupLocation(); 15307 recordX11ResizeAsync(display, *win, event.width, event.height); 15308 } 15309 break; 15310 case EventType.Expose: 15311 if(auto win = e.xexpose.window in SimpleWindow.nativeMapping) { 15312 if(win.windowType == WindowTypes.minimallyWrapped) 15313 break; 15314 // if it is closing from a popup menu, it can get 15315 // an Expose event right by the end and trigger a 15316 // BadDrawable error ... we'll just check 15317 // closed to handle that. 15318 if((*win).closed) break; 15319 if((*win).openglMode == OpenGlOptions.no) { 15320 bool doCopy = true;// e.xexpose.count == 0; // the count is better if we copy all area but meh 15321 if (win.handleExpose !is null) doCopy = !win.handleExpose(e.xexpose.x, e.xexpose.y, e.xexpose.width, e.xexpose.height, e.xexpose.count); 15322 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); 15323 } else { 15324 // need to redraw the scene somehow 15325 if(e.xexpose.count == 0) { // only do the last one since redrawOpenGlSceneNow always does it all 15326 XUnlockDisplay(display); 15327 scope(exit) XLockDisplay(display); 15328 version(without_opengl) {} else 15329 win.redrawOpenGlSceneSoon(); 15330 } 15331 } 15332 } 15333 break; 15334 case EventType.FocusIn: 15335 case EventType.FocusOut: 15336 15337 if(auto win = e.xfocus.window in SimpleWindow.nativeMapping) { 15338 /+ 15339 15340 void info(string detail) { 15341 string s; 15342 // import std.conv; 15343 // import std.datetime; 15344 s ~= to!string(Clock.currTime); 15345 s ~= " "; 15346 s ~= e.type == EventType.FocusIn ? "in " : "out"; 15347 s ~= " "; 15348 s ~= win.windowType == WindowTypes.nestedChild ? "child " : "main "; 15349 s ~= e.xfocus.mode == NotifyModes.NotifyNormal ? " normal ": " grabbed "; 15350 s ~= detail; 15351 s ~= " "; 15352 15353 sdpyPrintDebugString(s); 15354 15355 } 15356 15357 switch(e.xfocus.detail) { 15358 case NotifyDetail.NotifyAncestor: info("Ancestor"); break; 15359 case NotifyDetail.NotifyVirtual: info("Virtual"); break; 15360 case NotifyDetail.NotifyInferior: info("Inferior"); break; 15361 case NotifyDetail.NotifyNonlinear: info("Nonlinear"); break; 15362 case NotifyDetail.NotifyNonlinearVirtual: info("nlinearvirtual"); break; 15363 case NotifyDetail.NotifyPointer: info("pointer"); break; 15364 case NotifyDetail.NotifyPointerRoot: info("pointerroot"); break; 15365 case NotifyDetail.NotifyDetailNone: info("none"); break; 15366 default: 15367 15368 } 15369 +/ 15370 15371 15372 if(e.xfocus.detail == NotifyDetail.NotifyPointer) 15373 break; // just ignore these they seem irrelevant 15374 15375 auto old = win._focused; 15376 win._focused = e.type == EventType.FocusIn; 15377 15378 // yes, we are losing the focus, but to our own child. that's actually kinda keeping it. 15379 if(e.type == EventType.FocusOut && e.xfocus.detail == NotifyDetail.NotifyInferior) 15380 win._focused = true; 15381 15382 if(win.demandingAttention) 15383 demandAttention(*win, false); 15384 15385 win.updateIMEFocused(); 15386 15387 if(old != win._focused && win.onFocusChange) { 15388 XUnlockDisplay(display); 15389 scope(exit) XLockDisplay(display); 15390 win.onFocusChange(win._focused); 15391 } 15392 } 15393 break; 15394 case EventType.VisibilityNotify: 15395 if(auto win = e.xfocus.window in SimpleWindow.nativeMapping) { 15396 if (e.xvisibility.state == VisibilityNotify.VisibilityFullyObscured) { 15397 if (win.visibilityChanged !is null) { 15398 XUnlockDisplay(display); 15399 scope(exit) XLockDisplay(display); 15400 win.visibilityChanged(false); 15401 } 15402 } else { 15403 if (win.visibilityChanged !is null) { 15404 XUnlockDisplay(display); 15405 scope(exit) XLockDisplay(display); 15406 win.visibilityChanged(true); 15407 } 15408 } 15409 } 15410 break; 15411 case EventType.ClientMessage: 15412 if (e.xclient.message_type == GetAtom!("_X11SDPY_INSMME_FLAG_EVENT_", true)(e.xany.display)) { 15413 // "ignore next mouse motion" event, increment ignore counter for teh window 15414 if (auto win = e.xclient.window in SimpleWindow.nativeMapping) { 15415 ++(*win).warpEventCount; 15416 debug(x11sdpy_warp_debug) { import core.stdc.stdio : printf; printf("X11: got \"INSMME\" message, new count=%d\n", (*win).warpEventCount); } 15417 } else { 15418 debug(x11sdpy_warp_debug) { import core.stdc.stdio : printf; printf("X11: got \"INSMME\" WTF?!!\n"); } 15419 } 15420 } else if(e.xclient.data.l[0] == GetAtom!"WM_DELETE_WINDOW"(e.xany.display)) { 15421 // user clicked the close button on the window manager 15422 if(auto win = e.xclient.window in SimpleWindow.nativeMapping) { 15423 XUnlockDisplay(display); 15424 scope(exit) XLockDisplay(display); 15425 if ((*win).closeQuery !is null) (*win).closeQuery(); else (*win).close(); 15426 } 15427 15428 } else if(e.xclient.data.l[0] == GetAtom!"WM_TAKE_FOCUS"(e.xany.display)) { 15429 // writeln("HAPPENED"); 15430 // user clicked the close button on the window manager 15431 if(auto win = e.xclient.window in SimpleWindow.nativeMapping) { 15432 XUnlockDisplay(display); 15433 scope(exit) XLockDisplay(display); 15434 15435 auto setTo = *win; 15436 15437 if(win.setRequestedInputFocus !is null) { 15438 auto s = win.setRequestedInputFocus(); 15439 if(s !is null) { 15440 setTo = s; 15441 } 15442 } 15443 15444 assert(setTo !is null); 15445 15446 // FIXME: so this is actually supposed to focus to a relevant child window if appropriate 15447 15448 XSetInputFocus(display, setTo.impl.window, RevertToParent, e.xclient.data.l[1]); 15449 } 15450 } else if(e.xclient.message_type == GetAtom!"MANAGER"(e.xany.display)) { 15451 foreach(nai; NotificationAreaIcon.activeIcons) 15452 nai.newManager(); 15453 } else if(auto win = e.xclient.window in SimpleWindow.nativeMapping) { 15454 15455 bool xDragWindow = true; 15456 if(xDragWindow && e.xclient.message_type == GetAtom!"XdndStatus"(e.xany.display)) { 15457 //XDefineCursor(display, xDragWindow.impl.window, 15458 //writeln("XdndStatus ", e.xclient.data.l); 15459 } 15460 if(auto dh = win.dropHandler) { 15461 15462 static Atom[3] xFormatsBuffer; 15463 static Atom[] xFormats; 15464 15465 void resetXFormats() { 15466 xFormatsBuffer[] = 0; 15467 xFormats = xFormatsBuffer[]; 15468 } 15469 15470 if(e.xclient.message_type == GetAtom!"XdndEnter"(e.xany.display)) { 15471 // on Windows it is supposed to return the effect you actually do FIXME 15472 15473 auto sourceWindow = e.xclient.data.l[0]; 15474 15475 xFormatsBuffer[0] = e.xclient.data.l[2]; 15476 xFormatsBuffer[1] = e.xclient.data.l[3]; 15477 xFormatsBuffer[2] = e.xclient.data.l[4]; 15478 15479 if(e.xclient.data.l[1] & 1) { 15480 // can just grab it all but like we don't necessarily need them... 15481 xFormats = cast(Atom[]) getX11PropertyData(sourceWindow, GetAtom!"XdndTypeList"(display), XA_ATOM); 15482 } else { 15483 int len; 15484 foreach(fmt; xFormatsBuffer) 15485 if(fmt) len++; 15486 xFormats = xFormatsBuffer[0 .. len]; 15487 } 15488 15489 auto pkg = DropPackage(*win, e.xclient.data.l[0], 0, xFormats); 15490 15491 dh.dragEnter(&pkg); 15492 } else if(e.xclient.message_type == GetAtom!"XdndPosition"(e.xany.display)) { 15493 15494 auto pack = e.xclient.data.l[2]; 15495 15496 auto result = dh.dragOver(Point((pack & 0xffff0000) >> 16, pack & 0xffff)); // FIXME: translate screen coordinates back to window coords 15497 15498 15499 XClientMessageEvent xclient; 15500 15501 xclient.type = EventType.ClientMessage; 15502 xclient.window = e.xclient.data.l[0]; 15503 xclient.message_type = GetAtom!"XdndStatus"(display); 15504 xclient.format = 32; 15505 xclient.data.l[0] = win.impl.window; 15506 xclient.data.l[1] = (result.action != DragAndDropAction.none) ? 1 : 0; // will accept 15507 auto r = result.consistentWithin; 15508 xclient.data.l[2] = ((cast(short) r.left) << 16) | (cast(short) r.top); 15509 xclient.data.l[3] = ((cast(short) r.width) << 16) | (cast(short) r.height); 15510 xclient.data.l[4] = dndActionAtom(e.xany.display, result.action); 15511 15512 XSendEvent( 15513 display, 15514 e.xclient.data.l[0], 15515 false, 15516 EventMask.NoEventMask, 15517 cast(XEvent*) &xclient 15518 ); 15519 15520 15521 } else if(e.xclient.message_type == GetAtom!"XdndLeave"(e.xany.display)) { 15522 //writeln("XdndLeave"); 15523 // drop cancelled. 15524 // data.l[0] is the source window 15525 dh.dragLeave(); 15526 15527 resetXFormats(); 15528 } else if(e.xclient.message_type == GetAtom!"XdndDrop"(e.xany.display)) { 15529 // drop happening, should fetch data, then send finished 15530 // writeln("XdndDrop"); 15531 15532 auto pkg = DropPackage(*win, e.xclient.data.l[0], e.xclient.data.l[2], xFormats); 15533 15534 dh.drop(&pkg); 15535 15536 resetXFormats(); 15537 } else if(e.xclient.message_type == GetAtom!"XdndFinished"(e.xany.display)) { 15538 // writeln("XdndFinished"); 15539 15540 dh.finish(); 15541 } 15542 15543 } 15544 } 15545 break; 15546 case EventType.MapNotify: 15547 if(auto win = e.xmap.window in SimpleWindow.nativeMapping) { 15548 (*win)._visible = true; 15549 if (!(*win)._visibleForTheFirstTimeCalled) { 15550 (*win)._visibleForTheFirstTimeCalled = true; 15551 if ((*win).visibleForTheFirstTime !is null) { 15552 XUnlockDisplay(display); 15553 scope(exit) XLockDisplay(display); 15554 (*win).visibleForTheFirstTime(); 15555 } 15556 } 15557 if ((*win).visibilityChanged !is null) { 15558 XUnlockDisplay(display); 15559 scope(exit) XLockDisplay(display); 15560 (*win).visibilityChanged(true); 15561 } 15562 } 15563 break; 15564 case EventType.UnmapNotify: 15565 if(auto win = e.xunmap.window in SimpleWindow.nativeMapping) { 15566 win._visible = false; 15567 if (win.visibilityChanged !is null) { 15568 XUnlockDisplay(display); 15569 scope(exit) XLockDisplay(display); 15570 win.visibilityChanged(false); 15571 } 15572 } 15573 break; 15574 case EventType.DestroyNotify: 15575 if(auto win = e.xdestroywindow.window in SimpleWindow.nativeMapping) { 15576 if(win.destroyed) 15577 break; // might get a notification both for itself and from its parent 15578 if (win.onDestroyed !is null) try { win.onDestroyed(); } catch (Exception e) {} // sorry 15579 win._closed = true; // just in case 15580 win.destroyed = true; 15581 if (win.xic !is null) { 15582 XDestroyIC(win.xic); 15583 win.xic = null; // just in case 15584 } 15585 SimpleWindow.nativeMapping.remove(e.xdestroywindow.window); 15586 bool anyImportant = false; 15587 foreach(SimpleWindow w; SimpleWindow.nativeMapping) 15588 if(w.beingOpenKeepsAppOpen) { 15589 anyImportant = true; 15590 break; 15591 } 15592 if(!anyImportant) { 15593 EventLoop.quitApplication(); 15594 done = true; 15595 } 15596 } 15597 auto window = e.xdestroywindow.window; 15598 if(window in CapableOfHandlingNativeEvent.nativeHandleMapping) 15599 CapableOfHandlingNativeEvent.nativeHandleMapping.remove(window); 15600 15601 version(with_eventloop) { 15602 if(done) exit(); 15603 } 15604 break; 15605 15606 case EventType.MotionNotify: 15607 MouseEvent mouse; 15608 auto event = e.xmotion; 15609 15610 mouse.type = MouseEventType.motion; 15611 mouse.x = event.x; 15612 mouse.y = event.y; 15613 mouse.modifierState = event.state; 15614 15615 mouse.timestamp = event.time; 15616 15617 if(auto win = e.xmotion.window in SimpleWindow.nativeMapping) { 15618 mouse.window = *win; 15619 if (win.warpEventCount > 0) { 15620 debug(x11sdpy_warp_debug) { import core.stdc.stdio : printf; printf("X11: got \"warp motion\" message, current count=%d\n", (*win).warpEventCount); } 15621 --(*win).warpEventCount; 15622 (*win).mdx(mouse); // so deltas will be correctly updated 15623 } else { 15624 win.warpEventCount = 0; // just in case 15625 (*win).mdx(mouse); 15626 if((*win).handleMouseEvent) { 15627 XUnlockDisplay(display); 15628 scope(exit) XLockDisplay(display); 15629 (*win).handleMouseEvent(mouse); 15630 } 15631 } 15632 } 15633 15634 version(with_eventloop) 15635 send(mouse); 15636 break; 15637 case EventType.ButtonPress: 15638 case EventType.ButtonRelease: 15639 MouseEvent mouse; 15640 auto event = e.xbutton; 15641 15642 mouse.timestamp = event.time; 15643 15644 mouse.type = cast(MouseEventType) (e.type == EventType.ButtonPress ? 1 : 2); 15645 mouse.x = event.x; 15646 mouse.y = event.y; 15647 15648 static Time lastMouseDownTime = 0; 15649 static int lastMouseDownButton = -1; 15650 15651 mouse.doubleClick = e.type == EventType.ButtonPress && event.button == lastMouseDownButton && (event.time - lastMouseDownTime) < mouseDoubleClickTimeout; 15652 if(e.type == EventType.ButtonPress) { 15653 lastMouseDownTime = event.time; 15654 lastMouseDownButton = event.button; 15655 } 15656 15657 switch(event.button) { 15658 case 1: mouse.button = MouseButton.left; break; // left 15659 case 2: mouse.button = MouseButton.middle; break; // middle 15660 case 3: mouse.button = MouseButton.right; break; // right 15661 case 4: mouse.button = MouseButton.wheelUp; break; // scroll up 15662 case 5: mouse.button = MouseButton.wheelDown; break; // scroll down 15663 case 6: break; // idk 15664 case 7: break; // idk 15665 case 8: mouse.button = MouseButton.backButton; break; 15666 case 9: mouse.button = MouseButton.forwardButton; break; 15667 default: 15668 } 15669 15670 // FIXME: double check this 15671 mouse.modifierState = event.state; 15672 15673 //mouse.modifierState = event.detail; 15674 15675 if(auto win = e.xbutton.window in SimpleWindow.nativeMapping) { 15676 mouse.window = *win; 15677 (*win).mdx(mouse); 15678 if((*win).handleMouseEvent) { 15679 XUnlockDisplay(display); 15680 scope(exit) XLockDisplay(display); 15681 (*win).handleMouseEvent(mouse); 15682 } 15683 } 15684 version(with_eventloop) 15685 send(mouse); 15686 break; 15687 15688 case EventType.KeyPress: 15689 case EventType.KeyRelease: 15690 //if (e.type == EventType.KeyPress) { import core.stdc.stdio : stderr, fprintf; fprintf(stderr, "X11 keyboard event!\n"); } 15691 KeyEvent ke; 15692 ke.pressed = e.type == EventType.KeyPress; 15693 ke.hardwareCode = cast(ubyte) e.xkey.keycode; 15694 15695 auto sym = XKeycodeToKeysym( 15696 XDisplayConnection.get(), 15697 e.xkey.keycode, 15698 0); 15699 15700 ke.key = cast(Key) sym;//e.xkey.keycode; 15701 15702 ke.modifierState = e.xkey.state; 15703 15704 // writefln("%x", sym); 15705 wchar_t[128] charbuf = void; // buffer for XwcLookupString; composed value can consist of many chars! 15706 int charbuflen = 0; // return value of XwcLookupString 15707 if (ke.pressed) { 15708 auto win = e.xkey.window in SimpleWindow.nativeMapping; 15709 if (win !is null && win.xic !is null) { 15710 //{ import core.stdc.stdio : printf; printf("using xic!\n"); } 15711 Status status; 15712 charbuflen = XwcLookupString(win.xic, &e.xkey, charbuf.ptr, cast(int)charbuf.length, &sym, &status); 15713 //{ import core.stdc.stdio : printf; printf("charbuflen=%d\n", charbuflen); } 15714 } else { 15715 //{ import core.stdc.stdio : printf; printf("NOT using xic!\n"); } 15716 // If XIM initialization failed, don't process intl chars. Sorry, boys and girls. 15717 char[16] buffer; 15718 auto res = XLookupString(&e.xkey, buffer.ptr, buffer.length, null, null); 15719 if (res && buffer[0] < 128) charbuf[charbuflen++] = cast(wchar_t)buffer[0]; 15720 } 15721 } 15722 15723 // if there's no char, subst one 15724 if (charbuflen == 0) { 15725 switch (sym) { 15726 case 0xff09: charbuf[charbuflen++] = '\t'; break; 15727 case 0xff8d: // keypad enter 15728 case 0xff0d: charbuf[charbuflen++] = '\n'; break; 15729 default : // ignore 15730 } 15731 } 15732 15733 if (auto win = e.xkey.window in SimpleWindow.nativeMapping) { 15734 ke.window = *win; 15735 15736 15737 if(win.inputProxy) 15738 win = &win.inputProxy; 15739 15740 // char events are separate since they are on Windows too 15741 // also, xcompose can generate long char sequences 15742 // don't send char events if Meta and/or Hyper is pressed 15743 // TODO: ctrl+char should only send control chars; not yet 15744 if ((e.xkey.state&ModifierState.ctrl) != 0) { 15745 if (charbuflen > 1 || charbuf[0] >= ' ') charbuflen = 0; 15746 } 15747 15748 dchar[32] charsComingBuffer; 15749 int charsComingPosition; 15750 dchar[] charsComing = charsComingBuffer[]; 15751 15752 if (ke.pressed && charbuflen > 0) { 15753 // FIXME: I think Windows sends these on releases... we should try to match that, but idk about repeats. 15754 foreach (immutable dchar ch; charbuf[0..charbuflen]) { 15755 if(charsComingPosition >= charsComing.length) 15756 charsComing.length = charsComingPosition + 8; 15757 15758 charsComing[charsComingPosition++] = ch; 15759 } 15760 15761 charsComing = charsComing[0 .. charsComingPosition]; 15762 } else { 15763 charsComing = null; 15764 } 15765 15766 ke.charsPossible = charsComing; 15767 15768 if (win.handleKeyEvent) { 15769 XUnlockDisplay(display); 15770 scope(exit) XLockDisplay(display); 15771 win.handleKeyEvent(ke); 15772 } 15773 15774 // Super and alt modifier keys never actually send the chars, they are assumed to be special. 15775 if ((e.xkey.state&(ModifierState.alt|ModifierState.windows)) == 0 && win.handleCharEvent) { 15776 XUnlockDisplay(display); 15777 scope(exit) XLockDisplay(display); 15778 foreach(ch; charsComing) 15779 win.handleCharEvent(ch); 15780 } 15781 } 15782 15783 version(with_eventloop) 15784 send(ke); 15785 break; 15786 default: 15787 } 15788 15789 return done; 15790 } 15791 } 15792 15793 /* *************************************** */ 15794 /* Done with simpledisplay stuff */ 15795 /* *************************************** */ 15796 15797 // Necessary C library bindings follow 15798 version(Windows) {} else 15799 version(X11) { 15800 15801 extern(C) int eventfd (uint initval, int flags) nothrow @trusted @nogc; 15802 15803 // X11 bindings needed here 15804 /* 15805 A little of this is from the bindings project on 15806 D Source and some of it is copy/paste from the C 15807 header. 15808 15809 The DSource listing consistently used D's long 15810 where C used long. That's wrong - C long is 32 bit, so 15811 it should be int in D. I changed that here. 15812 15813 Note: 15814 This isn't complete, just took what I needed for myself. 15815 */ 15816 15817 import core.stdc.stddef : wchar_t; 15818 15819 interface XLib { 15820 extern(C) nothrow @nogc { 15821 char* XResourceManagerString(Display*); 15822 void XrmInitialize(); 15823 XrmDatabase XrmGetStringDatabase(char* data); 15824 bool XrmGetResource(XrmDatabase, const char*, const char*, char**, XrmValue*); 15825 15826 Cursor XCreateFontCursor(Display*, uint shape); 15827 int XDefineCursor(Display* display, Window w, Cursor cursor); 15828 int XUndefineCursor(Display* display, Window w); 15829 15830 Pixmap XCreateBitmapFromData(Display* display, Drawable d, const(char)* data, uint width, uint height); 15831 Cursor XCreatePixmapCursor(Display* display, Pixmap source, Pixmap mask, XColor* foreground_color, XColor* background_color, uint x, uint y); 15832 int XFreeCursor(Display* display, Cursor cursor); 15833 15834 int XLookupString(XKeyEvent *event_struct, char *buffer_return, int bytes_buffer, KeySym *keysym_return, void *status_in_out); 15835 15836 int XwcLookupString(XIC ic, XKeyPressedEvent* event, wchar_t* buffer_return, int wchars_buffer, KeySym* keysym_return, Status* status_return); 15837 15838 XVaNestedList XVaCreateNestedList(int unused, ...); 15839 15840 char *XKeysymToString(KeySym keysym); 15841 KeySym XKeycodeToKeysym( 15842 Display* /* display */, 15843 KeyCode /* keycode */, 15844 int /* index */ 15845 ); 15846 15847 int XConvertSelection(Display *display, Atom selection, Atom target, Atom property, Window requestor, Time time); 15848 15849 int XFree(void*); 15850 int XDeleteProperty(Display *display, Window w, Atom property); 15851 15852 int XChangeProperty(Display *display, Window w, Atom property, Atom type, int format, int mode, scope const void *data, int nelements); 15853 15854 int XGetWindowProperty(Display *display, Window w, Atom property, arch_long 15855 long_offset, arch_long long_length, Bool del, Atom req_type, Atom 15856 *actual_type_return, int *actual_format_return, arch_ulong 15857 *nitems_return, arch_ulong *bytes_after_return, void** prop_return); 15858 Atom* XListProperties(Display *display, Window w, int *num_prop_return); 15859 Status XGetTextProperty(Display *display, Window w, XTextProperty *text_prop_return, Atom property); 15860 Status XQueryTree(Display *display, Window w, Window *root_return, Window *parent_return, Window **children_return, uint *nchildren_return); 15861 15862 int XSetSelectionOwner(Display *display, Atom selection, Window owner, Time time); 15863 15864 Window XGetSelectionOwner(Display *display, Atom selection); 15865 15866 XVisualInfo* XGetVisualInfo(Display*, c_long, XVisualInfo*, int*); 15867 15868 char** XListFonts(Display*, const char*, int, int*); 15869 void XFreeFontNames(char**); 15870 15871 Display* XOpenDisplay(const char*); 15872 int XCloseDisplay(Display*); 15873 15874 int function() XSynchronize(Display*, bool); 15875 int function() XSetAfterFunction(Display*, int function() proc); 15876 15877 Bool XQueryExtension(Display*, const char*, int*, int*, int*); 15878 15879 Bool XSupportsLocale(); 15880 char* XSetLocaleModifiers(const(char)* modifier_list); 15881 XOM XOpenOM(Display* display, _XrmHashBucketRec* rdb, const(char)* res_name, const(char)* res_class); 15882 Status XCloseOM(XOM om); 15883 15884 XIM XOpenIM(Display* dpy, _XrmHashBucketRec* rdb, const(char)* res_name, const(char)* res_class); 15885 Status XCloseIM(XIM im); 15886 15887 char* XGetIMValues(XIM im, ...) /*_X_SENTINEL(0)*/; 15888 char* XSetIMValues(XIM im, ...) /*_X_SENTINEL(0)*/; 15889 Display* XDisplayOfIM(XIM im); 15890 char* XLocaleOfIM(XIM im); 15891 XIC XCreateIC(XIM im, ...) /*_X_SENTINEL(0)*/; 15892 void XDestroyIC(XIC ic); 15893 void XSetICFocus(XIC ic); 15894 void XUnsetICFocus(XIC ic); 15895 //wchar_t* XwcResetIC(XIC ic); 15896 char* XmbResetIC(XIC ic); 15897 char* Xutf8ResetIC(XIC ic); 15898 char* XSetICValues(XIC ic, ...) /*_X_SENTINEL(0)*/; 15899 char* XGetICValues(XIC ic, ...) /*_X_SENTINEL(0)*/; 15900 XIM XIMOfIC(XIC ic); 15901 15902 uint XSendEvent(Display* display, Window w, Bool propagate, arch_long event_mask, XEvent* event_send); 15903 15904 15905 XFontStruct *XLoadQueryFont(Display *display, scope const char *name); 15906 int XFreeFont(Display *display, XFontStruct *font_struct); 15907 int XSetFont(Display* display, GC gc, Font font); 15908 int XTextWidth(XFontStruct*, scope const char*, int); 15909 15910 int XSetLineAttributes(Display *display, GC gc, uint line_width, int line_style, int cap_style, int join_style); 15911 int XSetDashes(Display *display, GC gc, int dash_offset, scope const byte* dash_list, int n); 15912 15913 Window XCreateSimpleWindow( 15914 Display* /* display */, 15915 Window /* parent */, 15916 int /* x */, 15917 int /* y */, 15918 uint /* width */, 15919 uint /* height */, 15920 uint /* border_width */, 15921 uint /* border */, 15922 uint /* background */ 15923 ); 15924 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); 15925 15926 int XReparentWindow(Display*, Window, Window, int, int); 15927 int XClearWindow(Display*, Window); 15928 int XMoveResizeWindow(Display*, Window, int, int, uint, uint); 15929 int XMoveWindow(Display*, Window, int, int); 15930 int XResizeWindow(Display *display, Window w, uint width, uint height); 15931 15932 Colormap XCreateColormap(Display *display, Window w, Visual *visual, int alloc); 15933 15934 Status XGetWindowAttributes(Display*, Window, XWindowAttributes*); 15935 15936 XImage *XCreateImage( 15937 Display* /* display */, 15938 Visual* /* visual */, 15939 uint /* depth */, 15940 int /* format */, 15941 int /* offset */, 15942 ubyte* /* data */, 15943 uint /* width */, 15944 uint /* height */, 15945 int /* bitmap_pad */, 15946 int /* bytes_per_line */ 15947 ); 15948 15949 Status XInitImage (XImage* image); 15950 15951 Atom XInternAtom( 15952 Display* /* display */, 15953 const char* /* atom_name */, 15954 Bool /* only_if_exists */ 15955 ); 15956 15957 Status XInternAtoms(Display*, const char**, int, Bool, Atom*); 15958 char* XGetAtomName(Display*, Atom); 15959 Status XGetAtomNames(Display*, Atom*, int count, char**); 15960 15961 int XPutImage( 15962 Display* /* display */, 15963 Drawable /* d */, 15964 GC /* gc */, 15965 XImage* /* image */, 15966 int /* src_x */, 15967 int /* src_y */, 15968 int /* dest_x */, 15969 int /* dest_y */, 15970 uint /* width */, 15971 uint /* height */ 15972 ); 15973 15974 XImage *XGetImage(Display *display, Drawable d, int x, int y, uint width, uint height, c_ulong plane_mask, int format); 15975 15976 15977 int XDestroyWindow( 15978 Display* /* display */, 15979 Window /* w */ 15980 ); 15981 15982 int XDestroyImage(XImage*); 15983 15984 int XSelectInput( 15985 Display* /* display */, 15986 Window /* w */, 15987 EventMask /* event_mask */ 15988 ); 15989 15990 int XMapWindow( 15991 Display* /* display */, 15992 Window /* w */ 15993 ); 15994 15995 Status XIconifyWindow(Display*, Window, int); 15996 int XMapRaised(Display*, Window); 15997 int XMapSubwindows(Display*, Window); 15998 15999 int XNextEvent( 16000 Display* /* display */, 16001 XEvent* /* event_return */ 16002 ); 16003 16004 int XMaskEvent(Display*, arch_long, XEvent*); 16005 16006 Bool XFilterEvent(XEvent *event, Window window); 16007 int XRefreshKeyboardMapping(XMappingEvent *event_map); 16008 16009 Status XSetWMProtocols( 16010 Display* /* display */, 16011 Window /* w */, 16012 Atom* /* protocols */, 16013 int /* count */ 16014 ); 16015 16016 void XSetWMNormalHints(Display *display, Window w, XSizeHints *hints); 16017 Status XGetWMNormalHints(Display *display, Window w, XSizeHints *hints, c_long* supplied_return); 16018 16019 16020 Status XInitThreads(); 16021 void XLockDisplay (Display* display); 16022 void XUnlockDisplay (Display* display); 16023 16024 void XSetWMProperties(Display*, Window, XTextProperty*, XTextProperty*, char**, int, XSizeHints*, XWMHints*, XClassHint*); 16025 16026 int XSetWindowBackground (Display* display, Window w, c_ulong background_pixel); 16027 int XSetWindowBackgroundPixmap (Display* display, Window w, Pixmap background_pixmap); 16028 //int XSetWindowBorder (Display* display, Window w, c_ulong border_pixel); 16029 //int XSetWindowBorderPixmap (Display* display, Window w, Pixmap border_pixmap); 16030 //int XSetWindowBorderWidth (Display* display, Window w, uint width); 16031 16032 16033 // check out Xft too: http://www.keithp.com/~keithp/render/Xft.tutorial 16034 int XDrawString(Display*, Drawable, GC, int, int, scope const char*, int); 16035 int XDrawLine(Display*, Drawable, GC, int, int, int, int); 16036 int XDrawRectangle(Display*, Drawable, GC, int, int, uint, uint); 16037 int XDrawArc(Display*, Drawable, GC, int, int, uint, uint, int, int); 16038 int XFillRectangle(Display*, Drawable, GC, int, int, uint, uint); 16039 int XFillArc(Display*, Drawable, GC, int, int, uint, uint, int, int); 16040 int XDrawPoint(Display*, Drawable, GC, int, int); 16041 int XSetForeground(Display*, GC, uint); 16042 int XSetBackground(Display*, GC, uint); 16043 16044 XFontSet XCreateFontSet(Display*, const char*, char***, int*, char**); 16045 void XFreeFontSet(Display*, XFontSet); 16046 void Xutf8DrawString(Display*, Drawable, XFontSet, GC, int, int, scope const char*, int); 16047 void Xutf8DrawText(Display*, Drawable, GC, int, int, XmbTextItem*, int); 16048 16049 int Xutf8TextExtents(XFontSet font_set, const char *, int num_bytes, XRectangle *overall_ink_return, XRectangle *overall_logical_return); 16050 16051 16052 //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); 16053 16054 void XDrawText(Display*, Drawable, GC, int, int, XTextItem*, int); 16055 int XSetFunction(Display*, GC, int); 16056 16057 GC XCreateGC(Display*, Drawable, uint, void*); 16058 int XCopyGC(Display*, GC, uint, GC); 16059 int XFreeGC(Display*, GC); 16060 16061 bool XCheckWindowEvent(Display*, Window, int, XEvent*); 16062 bool XCheckMaskEvent(Display*, int, XEvent*); 16063 16064 int XPending(Display*); 16065 int XEventsQueued(Display* display, int mode); 16066 16067 Pixmap XCreatePixmap(Display*, Drawable, uint, uint, uint); 16068 int XFreePixmap(Display*, Pixmap); 16069 int XCopyArea(Display*, Drawable, Drawable, GC, int, int, uint, uint, int, int); 16070 int XFlush(Display*); 16071 int XBell(Display*, int); 16072 int XSync(Display*, bool); 16073 16074 int XGrabKey (Display* display, int keycode, uint modifiers, Window grab_window, Bool owner_events, int pointer_mode, int keyboard_mode); 16075 int XUngrabKey (Display* display, int keycode, uint modifiers, Window grab_window); 16076 16077 int XGrabKeyboard(Display*, Window, Bool, int, int, Time); 16078 int XUngrabKeyboard(Display*, Time); 16079 16080 KeyCode XKeysymToKeycode (Display* display, KeySym keysym); 16081 16082 KeySym XStringToKeysym(const char *string); 16083 16084 Bool XCheckTypedEvent(Display *display, int event_type, XEvent *event_return); 16085 16086 Window XDefaultRootWindow(Display*); 16087 16088 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); 16089 16090 int XUngrabButton(Display *display, uint button, uint modifiers, Window grab_window); 16091 16092 int XDrawLines(Display*, Drawable, GC, XPoint*, int, CoordMode); 16093 int XFillPolygon(Display*, Drawable, GC, XPoint*, int, PolygonShape, CoordMode); 16094 16095 Status XAllocColor(Display*, Colormap, XColor*); 16096 16097 int XWithdrawWindow(Display*, Window, int); 16098 int XUnmapWindow(Display*, Window); 16099 int XLowerWindow(Display*, Window); 16100 int XRaiseWindow(Display*, Window); 16101 16102 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); 16103 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); 16104 16105 int XGetInputFocus(Display*, Window*, int*); 16106 int XSetInputFocus(Display*, Window, int, Time); 16107 16108 XErrorHandler XSetErrorHandler(XErrorHandler); 16109 16110 int XGetErrorText(Display*, int, char*, int); 16111 16112 Bool XkbSetDetectableAutoRepeat(Display* dpy, Bool detectable, Bool* supported); 16113 16114 16115 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); 16116 int XUngrabPointer(Display *display, Time time); 16117 int XChangeActivePointerGrab(Display *display, uint event_mask, Cursor cursor, Time time); 16118 16119 int XCopyPlane(Display*, Drawable, Drawable, GC, int, int, uint, uint, int, int, arch_ulong); 16120 16121 Status XGetGeometry(Display*, Drawable, Window*, int*, int*, uint*, uint*, uint*, uint*); 16122 int XSetClipMask(Display*, GC, Pixmap); 16123 int XSetClipOrigin(Display*, GC, int, int); 16124 16125 void XSetClipRectangles(Display*, GC, int, int, XRectangle*, int, int); 16126 16127 void XSetWMName(Display*, Window, XTextProperty*); 16128 Status XGetWMName(Display*, Window, XTextProperty*); 16129 int XStoreName(Display* display, Window w, const(char)* window_name); 16130 16131 XIOErrorHandler XSetIOErrorHandler (XIOErrorHandler handler); 16132 16133 } 16134 } 16135 16136 interface Xext { 16137 extern(C) nothrow @nogc { 16138 Status XShmAttach(Display*, XShmSegmentInfo*); 16139 Status XShmDetach(Display*, XShmSegmentInfo*); 16140 Status XShmPutImage( 16141 Display* /* dpy */, 16142 Drawable /* d */, 16143 GC /* gc */, 16144 XImage* /* image */, 16145 int /* src_x */, 16146 int /* src_y */, 16147 int /* dst_x */, 16148 int /* dst_y */, 16149 uint /* src_width */, 16150 uint /* src_height */, 16151 Bool /* send_event */ 16152 ); 16153 16154 Status XShmQueryExtension(Display*); 16155 16156 XImage *XShmCreateImage( 16157 Display* /* dpy */, 16158 Visual* /* visual */, 16159 uint /* depth */, 16160 int /* format */, 16161 char* /* data */, 16162 XShmSegmentInfo* /* shminfo */, 16163 uint /* width */, 16164 uint /* height */ 16165 ); 16166 16167 Pixmap XShmCreatePixmap( 16168 Display* /* dpy */, 16169 Drawable /* d */, 16170 char* /* data */, 16171 XShmSegmentInfo* /* shminfo */, 16172 uint /* width */, 16173 uint /* height */, 16174 uint /* depth */ 16175 ); 16176 16177 } 16178 } 16179 16180 // this requires -lXpm 16181 //int XpmCreatePixmapFromData(Display*, Drawable, scope const char**, Pixmap*, Pixmap*, void*); // FIXME: void* should be XpmAttributes 16182 16183 16184 mixin DynamicLoad!(XLib, "X11", 6, librariesSuccessfullyLoaded) xlib; 16185 mixin DynamicLoad!(Xext, "Xext", 6, librariesSuccessfullyLoaded) xext; 16186 shared static this() { 16187 xlib.loadDynamicLibrary(); 16188 xext.loadDynamicLibrary(); 16189 } 16190 16191 16192 extern(C) nothrow @nogc { 16193 16194 alias XrmDatabase = void*; 16195 struct XrmValue { 16196 uint size; 16197 void* addr; 16198 } 16199 16200 struct XVisualInfo { 16201 Visual* visual; 16202 VisualID visualid; 16203 int screen; 16204 uint depth; 16205 int c_class; 16206 c_ulong red_mask; 16207 c_ulong green_mask; 16208 c_ulong blue_mask; 16209 int colormap_size; 16210 int bits_per_rgb; 16211 } 16212 16213 enum VisualNoMask= 0x0; 16214 enum VisualIDMask= 0x1; 16215 enum VisualScreenMask=0x2; 16216 enum VisualDepthMask= 0x4; 16217 enum VisualClassMask= 0x8; 16218 enum VisualRedMaskMask=0x10; 16219 enum VisualGreenMaskMask=0x20; 16220 enum VisualBlueMaskMask=0x40; 16221 enum VisualColormapSizeMask=0x80; 16222 enum VisualBitsPerRGBMask=0x100; 16223 enum VisualAllMask= 0x1FF; 16224 16225 enum AnyKey = 0; 16226 enum AnyModifier = 1 << 15; 16227 16228 // XIM and other crap 16229 struct _XOM {} 16230 struct _XIM {} 16231 struct _XIC {} 16232 alias XOM = _XOM*; 16233 alias XIM = _XIM*; 16234 alias XIC = _XIC*; 16235 16236 alias XVaNestedList = void*; 16237 16238 alias XIMStyle = arch_ulong; 16239 enum : arch_ulong { 16240 XIMPreeditArea = 0x0001, 16241 XIMPreeditCallbacks = 0x0002, 16242 XIMPreeditPosition = 0x0004, 16243 XIMPreeditNothing = 0x0008, 16244 XIMPreeditNone = 0x0010, 16245 XIMStatusArea = 0x0100, 16246 XIMStatusCallbacks = 0x0200, 16247 XIMStatusNothing = 0x0400, 16248 XIMStatusNone = 0x0800, 16249 } 16250 16251 16252 /* X Shared Memory Extension functions */ 16253 //pragma(lib, "Xshm"); 16254 alias arch_ulong ShmSeg; 16255 struct XShmSegmentInfo { 16256 ShmSeg shmseg; 16257 int shmid; 16258 ubyte* shmaddr; 16259 Bool readOnly; 16260 } 16261 16262 // and the necessary OS functions 16263 int shmget(int, size_t, int); 16264 void* shmat(int, scope const void*, int); 16265 int shmdt(scope const void*); 16266 int shmctl (int shmid, int cmd, void* ptr /*struct shmid_ds *buf*/); 16267 16268 enum IPC_PRIVATE = 0; 16269 enum IPC_CREAT = 512; 16270 enum IPC_RMID = 0; 16271 16272 /* MIT-SHM end */ 16273 16274 16275 enum MappingType:int { 16276 MappingModifier =0, 16277 MappingKeyboard =1, 16278 MappingPointer =2 16279 } 16280 16281 /* ImageFormat -- PutImage, GetImage */ 16282 enum ImageFormat:int { 16283 XYBitmap =0, /* depth 1, XYFormat */ 16284 XYPixmap =1, /* depth == drawable depth */ 16285 ZPixmap =2 /* depth == drawable depth */ 16286 } 16287 16288 enum ModifierName:int { 16289 ShiftMapIndex =0, 16290 LockMapIndex =1, 16291 ControlMapIndex =2, 16292 Mod1MapIndex =3, 16293 Mod2MapIndex =4, 16294 Mod3MapIndex =5, 16295 Mod4MapIndex =6, 16296 Mod5MapIndex =7 16297 } 16298 16299 enum ButtonMask:int { 16300 Button1Mask =1<<8, 16301 Button2Mask =1<<9, 16302 Button3Mask =1<<10, 16303 Button4Mask =1<<11, 16304 Button5Mask =1<<12, 16305 AnyModifier =1<<15/* used in GrabButton, GrabKey */ 16306 } 16307 16308 enum KeyOrButtonMask:uint { 16309 ShiftMask =1<<0, 16310 LockMask =1<<1, 16311 ControlMask =1<<2, 16312 Mod1Mask =1<<3, 16313 Mod2Mask =1<<4, 16314 Mod3Mask =1<<5, 16315 Mod4Mask =1<<6, 16316 Mod5Mask =1<<7, 16317 Button1Mask =1<<8, 16318 Button2Mask =1<<9, 16319 Button3Mask =1<<10, 16320 Button4Mask =1<<11, 16321 Button5Mask =1<<12, 16322 AnyModifier =1<<15/* used in GrabButton, GrabKey */ 16323 } 16324 16325 enum ButtonName:int { 16326 Button1 =1, 16327 Button2 =2, 16328 Button3 =3, 16329 Button4 =4, 16330 Button5 =5 16331 } 16332 16333 /* Notify modes */ 16334 enum NotifyModes:int 16335 { 16336 NotifyNormal =0, 16337 NotifyGrab =1, 16338 NotifyUngrab =2, 16339 NotifyWhileGrabbed =3 16340 } 16341 enum NotifyHint = 1; /* for MotionNotify events */ 16342 16343 /* Notify detail */ 16344 enum NotifyDetail:int 16345 { 16346 NotifyAncestor =0, 16347 NotifyVirtual =1, 16348 NotifyInferior =2, 16349 NotifyNonlinear =3, 16350 NotifyNonlinearVirtual =4, 16351 NotifyPointer =5, 16352 NotifyPointerRoot =6, 16353 NotifyDetailNone =7 16354 } 16355 16356 /* Visibility notify */ 16357 16358 enum VisibilityNotify:int 16359 { 16360 VisibilityUnobscured =0, 16361 VisibilityPartiallyObscured =1, 16362 VisibilityFullyObscured =2 16363 } 16364 16365 16366 enum WindowStackingMethod:int 16367 { 16368 Above =0, 16369 Below =1, 16370 TopIf =2, 16371 BottomIf =3, 16372 Opposite =4 16373 } 16374 16375 /* Circulation request */ 16376 enum CirculationRequest:int 16377 { 16378 PlaceOnTop =0, 16379 PlaceOnBottom =1 16380 } 16381 16382 enum PropertyNotification:int 16383 { 16384 PropertyNewValue =0, 16385 PropertyDelete =1 16386 } 16387 16388 enum ColorMapNotification:int 16389 { 16390 ColormapUninstalled =0, 16391 ColormapInstalled =1 16392 } 16393 16394 16395 struct _XPrivate {} 16396 struct _XrmHashBucketRec {} 16397 16398 alias void* XPointer; 16399 alias void* XExtData; 16400 16401 version( X86_64 ) { 16402 alias ulong XID; 16403 alias ulong arch_ulong; 16404 alias long arch_long; 16405 } else version (AArch64) { 16406 alias ulong XID; 16407 alias ulong arch_ulong; 16408 alias long arch_long; 16409 } else { 16410 alias uint XID; 16411 alias uint arch_ulong; 16412 alias int arch_long; 16413 } 16414 16415 alias XID Window; 16416 alias XID Drawable; 16417 alias XID Pixmap; 16418 16419 alias arch_ulong Atom; 16420 alias int Bool; 16421 alias Display XDisplay; 16422 16423 alias int ByteOrder; 16424 alias arch_ulong Time; 16425 alias void ScreenFormat; 16426 16427 struct XImage { 16428 int width, height; /* size of image */ 16429 int xoffset; /* number of pixels offset in X direction */ 16430 ImageFormat format; /* XYBitmap, XYPixmap, ZPixmap */ 16431 void *data; /* pointer to image data */ 16432 ByteOrder byte_order; /* data byte order, LSBFirst, MSBFirst */ 16433 int bitmap_unit; /* quant. of scanline 8, 16, 32 */ 16434 int bitmap_bit_order; /* LSBFirst, MSBFirst */ 16435 int bitmap_pad; /* 8, 16, 32 either XY or ZPixmap */ 16436 int depth; /* depth of image */ 16437 int bytes_per_line; /* accelarator to next line */ 16438 int bits_per_pixel; /* bits per pixel (ZPixmap) */ 16439 arch_ulong red_mask; /* bits in z arrangment */ 16440 arch_ulong green_mask; 16441 arch_ulong blue_mask; 16442 XPointer obdata; /* hook for the object routines to hang on */ 16443 static struct F { /* image manipulation routines */ 16444 XImage* function( 16445 XDisplay* /* display */, 16446 Visual* /* visual */, 16447 uint /* depth */, 16448 int /* format */, 16449 int /* offset */, 16450 ubyte* /* data */, 16451 uint /* width */, 16452 uint /* height */, 16453 int /* bitmap_pad */, 16454 int /* bytes_per_line */) create_image; 16455 int function(XImage *) destroy_image; 16456 arch_ulong function(XImage *, int, int) get_pixel; 16457 int function(XImage *, int, int, arch_ulong) put_pixel; 16458 XImage* function(XImage *, int, int, uint, uint) sub_image; 16459 int function(XImage *, arch_long) add_pixel; 16460 } 16461 F f; 16462 } 16463 version(X86_64) static assert(XImage.sizeof == 136); 16464 else version(X86) static assert(XImage.sizeof == 88); 16465 16466 struct XCharStruct { 16467 short lbearing; /* origin to left edge of raster */ 16468 short rbearing; /* origin to right edge of raster */ 16469 short width; /* advance to next char's origin */ 16470 short ascent; /* baseline to top edge of raster */ 16471 short descent; /* baseline to bottom edge of raster */ 16472 ushort attributes; /* per char flags (not predefined) */ 16473 } 16474 16475 /* 16476 * To allow arbitrary information with fonts, there are additional properties 16477 * returned. 16478 */ 16479 struct XFontProp { 16480 Atom name; 16481 arch_ulong card32; 16482 } 16483 16484 alias Atom Font; 16485 16486 struct XFontStruct { 16487 XExtData *ext_data; /* Hook for extension to hang data */ 16488 Font fid; /* Font ID for this font */ 16489 uint direction; /* Direction the font is painted */ 16490 uint min_char_or_byte2; /* First character */ 16491 uint max_char_or_byte2; /* Last character */ 16492 uint min_byte1; /* First row that exists (for two-byte fonts) */ 16493 uint max_byte1; /* Last row that exists (for two-byte fonts) */ 16494 Bool all_chars_exist; /* Flag if all characters have nonzero size */ 16495 uint default_char; /* Char to print for undefined character */ 16496 int n_properties; /* How many properties there are */ 16497 XFontProp *properties; /* Pointer to array of additional properties*/ 16498 XCharStruct min_bounds; /* Minimum bounds over all existing char*/ 16499 XCharStruct max_bounds; /* Maximum bounds over all existing char*/ 16500 XCharStruct *per_char; /* first_char to last_char information */ 16501 int ascent; /* Max extent above baseline for spacing */ 16502 int descent; /* Max descent below baseline for spacing */ 16503 } 16504 16505 16506 /* 16507 * Definitions of specific events. 16508 */ 16509 struct XKeyEvent 16510 { 16511 int type; /* of event */ 16512 arch_ulong serial; /* # of last request processed by server */ 16513 Bool send_event; /* true if this came from a SendEvent request */ 16514 Display *display; /* Display the event was read from */ 16515 Window window; /* "event" window it is reported relative to */ 16516 Window root; /* root window that the event occurred on */ 16517 Window subwindow; /* child window */ 16518 Time time; /* milliseconds */ 16519 int x, y; /* pointer x, y coordinates in event window */ 16520 int x_root, y_root; /* coordinates relative to root */ 16521 KeyOrButtonMask state; /* key or button mask */ 16522 uint keycode; /* detail */ 16523 Bool same_screen; /* same screen flag */ 16524 } 16525 version(X86_64) static assert(XKeyEvent.sizeof == 96); 16526 alias XKeyEvent XKeyPressedEvent; 16527 alias XKeyEvent XKeyReleasedEvent; 16528 16529 struct XButtonEvent 16530 { 16531 int type; /* of event */ 16532 arch_ulong serial; /* # of last request processed by server */ 16533 Bool send_event; /* true if this came from a SendEvent request */ 16534 Display *display; /* Display the event was read from */ 16535 Window window; /* "event" window it is reported relative to */ 16536 Window root; /* root window that the event occurred on */ 16537 Window subwindow; /* child window */ 16538 Time time; /* milliseconds */ 16539 int x, y; /* pointer x, y coordinates in event window */ 16540 int x_root, y_root; /* coordinates relative to root */ 16541 KeyOrButtonMask state; /* key or button mask */ 16542 uint button; /* detail */ 16543 Bool same_screen; /* same screen flag */ 16544 } 16545 alias XButtonEvent XButtonPressedEvent; 16546 alias XButtonEvent XButtonReleasedEvent; 16547 16548 struct XMotionEvent{ 16549 int type; /* of event */ 16550 arch_ulong serial; /* # of last request processed by server */ 16551 Bool send_event; /* true if this came from a SendEvent request */ 16552 Display *display; /* Display the event was read from */ 16553 Window window; /* "event" window reported relative to */ 16554 Window root; /* root window that the event occurred on */ 16555 Window subwindow; /* child window */ 16556 Time time; /* milliseconds */ 16557 int x, y; /* pointer x, y coordinates in event window */ 16558 int x_root, y_root; /* coordinates relative to root */ 16559 KeyOrButtonMask state; /* key or button mask */ 16560 byte is_hint; /* detail */ 16561 Bool same_screen; /* same screen flag */ 16562 } 16563 alias XMotionEvent XPointerMovedEvent; 16564 16565 struct XCrossingEvent{ 16566 int type; /* of event */ 16567 arch_ulong serial; /* # of last request processed by server */ 16568 Bool send_event; /* true if this came from a SendEvent request */ 16569 Display *display; /* Display the event was read from */ 16570 Window window; /* "event" window reported relative to */ 16571 Window root; /* root window that the event occurred on */ 16572 Window subwindow; /* child window */ 16573 Time time; /* milliseconds */ 16574 int x, y; /* pointer x, y coordinates in event window */ 16575 int x_root, y_root; /* coordinates relative to root */ 16576 NotifyModes mode; /* NotifyNormal, NotifyGrab, NotifyUngrab */ 16577 NotifyDetail detail; 16578 /* 16579 * NotifyAncestor, NotifyVirtual, NotifyInferior, 16580 * NotifyNonlinear,NotifyNonlinearVirtual 16581 */ 16582 Bool same_screen; /* same screen flag */ 16583 Bool focus; /* Boolean focus */ 16584 KeyOrButtonMask state; /* key or button mask */ 16585 } 16586 alias XCrossingEvent XEnterWindowEvent; 16587 alias XCrossingEvent XLeaveWindowEvent; 16588 16589 struct XFocusChangeEvent{ 16590 int type; /* FocusIn or FocusOut */ 16591 arch_ulong serial; /* # of last request processed by server */ 16592 Bool send_event; /* true if this came from a SendEvent request */ 16593 Display *display; /* Display the event was read from */ 16594 Window window; /* window of event */ 16595 NotifyModes mode; /* NotifyNormal, NotifyWhileGrabbed, 16596 NotifyGrab, NotifyUngrab */ 16597 NotifyDetail detail; 16598 /* 16599 * NotifyAncestor, NotifyVirtual, NotifyInferior, 16600 * NotifyNonlinear,NotifyNonlinearVirtual, NotifyPointer, 16601 * NotifyPointerRoot, NotifyDetailNone 16602 */ 16603 } 16604 alias XFocusChangeEvent XFocusInEvent; 16605 alias XFocusChangeEvent XFocusOutEvent; 16606 16607 enum CWBackPixmap = (1L<<0); 16608 enum CWBackPixel = (1L<<1); 16609 enum CWBorderPixmap = (1L<<2); 16610 enum CWBorderPixel = (1L<<3); 16611 enum CWBitGravity = (1L<<4); 16612 enum CWWinGravity = (1L<<5); 16613 enum CWBackingStore = (1L<<6); 16614 enum CWBackingPlanes = (1L<<7); 16615 enum CWBackingPixel = (1L<<8); 16616 enum CWOverrideRedirect = (1L<<9); 16617 enum CWSaveUnder = (1L<<10); 16618 enum CWEventMask = (1L<<11); 16619 enum CWDontPropagate = (1L<<12); 16620 enum CWColormap = (1L<<13); 16621 enum CWCursor = (1L<<14); 16622 16623 struct XWindowAttributes { 16624 int x, y; /* location of window */ 16625 int width, height; /* width and height of window */ 16626 int border_width; /* border width of window */ 16627 int depth; /* depth of window */ 16628 Visual *visual; /* the associated visual structure */ 16629 Window root; /* root of screen containing window */ 16630 int class_; /* InputOutput, InputOnly*/ 16631 int bit_gravity; /* one of the bit gravity values */ 16632 int win_gravity; /* one of the window gravity values */ 16633 int backing_store; /* NotUseful, WhenMapped, Always */ 16634 arch_ulong backing_planes; /* planes to be preserved if possible */ 16635 arch_ulong backing_pixel; /* value to be used when restoring planes */ 16636 Bool save_under; /* boolean, should bits under be saved? */ 16637 Colormap colormap; /* color map to be associated with window */ 16638 Bool map_installed; /* boolean, is color map currently installed*/ 16639 int map_state; /* IsUnmapped, IsUnviewable, IsViewable */ 16640 arch_long all_event_masks; /* set of events all people have interest in*/ 16641 arch_long your_event_mask; /* my event mask */ 16642 arch_long do_not_propagate_mask; /* set of events that should not propagate */ 16643 Bool override_redirect; /* boolean value for override-redirect */ 16644 Screen *screen; /* back pointer to correct screen */ 16645 } 16646 16647 enum IsUnmapped = 0; 16648 enum IsUnviewable = 1; 16649 enum IsViewable = 2; 16650 16651 struct XSetWindowAttributes { 16652 Pixmap background_pixmap;/* background, None, or ParentRelative */ 16653 arch_ulong background_pixel;/* background pixel */ 16654 Pixmap border_pixmap; /* border of the window or CopyFromParent */ 16655 arch_ulong border_pixel;/* border pixel value */ 16656 int bit_gravity; /* one of bit gravity values */ 16657 int win_gravity; /* one of the window gravity values */ 16658 int backing_store; /* NotUseful, WhenMapped, Always */ 16659 arch_ulong backing_planes;/* planes to be preserved if possible */ 16660 arch_ulong backing_pixel;/* value to use in restoring planes */ 16661 Bool save_under; /* should bits under be saved? (popups) */ 16662 arch_long event_mask; /* set of events that should be saved */ 16663 arch_long do_not_propagate_mask;/* set of events that should not propagate */ 16664 Bool override_redirect; /* boolean value for override_redirect */ 16665 Colormap colormap; /* color map to be associated with window */ 16666 Cursor cursor; /* cursor to be displayed (or None) */ 16667 } 16668 16669 16670 alias int Status; 16671 16672 16673 enum EventMask:int 16674 { 16675 NoEventMask =0, 16676 KeyPressMask =1<<0, 16677 KeyReleaseMask =1<<1, 16678 ButtonPressMask =1<<2, 16679 ButtonReleaseMask =1<<3, 16680 EnterWindowMask =1<<4, 16681 LeaveWindowMask =1<<5, 16682 PointerMotionMask =1<<6, 16683 PointerMotionHintMask =1<<7, 16684 Button1MotionMask =1<<8, 16685 Button2MotionMask =1<<9, 16686 Button3MotionMask =1<<10, 16687 Button4MotionMask =1<<11, 16688 Button5MotionMask =1<<12, 16689 ButtonMotionMask =1<<13, 16690 KeymapStateMask =1<<14, 16691 ExposureMask =1<<15, 16692 VisibilityChangeMask =1<<16, 16693 StructureNotifyMask =1<<17, 16694 ResizeRedirectMask =1<<18, 16695 SubstructureNotifyMask =1<<19, 16696 SubstructureRedirectMask=1<<20, 16697 FocusChangeMask =1<<21, 16698 PropertyChangeMask =1<<22, 16699 ColormapChangeMask =1<<23, 16700 OwnerGrabButtonMask =1<<24 16701 } 16702 16703 struct MwmHints { 16704 c_ulong flags; 16705 c_ulong functions; 16706 c_ulong decorations; 16707 c_long input_mode; 16708 c_ulong status; 16709 } 16710 16711 enum { 16712 MWM_HINTS_FUNCTIONS = (1L << 0), 16713 MWM_HINTS_DECORATIONS = (1L << 1), 16714 16715 MWM_FUNC_ALL = (1L << 0), 16716 MWM_FUNC_RESIZE = (1L << 1), 16717 MWM_FUNC_MOVE = (1L << 2), 16718 MWM_FUNC_MINIMIZE = (1L << 3), 16719 MWM_FUNC_MAXIMIZE = (1L << 4), 16720 MWM_FUNC_CLOSE = (1L << 5), 16721 16722 MWM_DECOR_ALL = (1L << 0), 16723 MWM_DECOR_BORDER = (1L << 1), 16724 MWM_DECOR_RESIZEH = (1L << 2), 16725 MWM_DECOR_TITLE = (1L << 3), 16726 MWM_DECOR_MENU = (1L << 4), 16727 MWM_DECOR_MINIMIZE = (1L << 5), 16728 MWM_DECOR_MAXIMIZE = (1L << 6), 16729 } 16730 16731 import core.stdc.config : c_long, c_ulong; 16732 16733 /* Size hints mask bits */ 16734 16735 enum USPosition = (1L << 0) /* user specified x, y */; 16736 enum USSize = (1L << 1) /* user specified width, height */; 16737 enum PPosition = (1L << 2) /* program specified position */; 16738 enum PSize = (1L << 3) /* program specified size */; 16739 enum PMinSize = (1L << 4) /* program specified minimum size */; 16740 enum PMaxSize = (1L << 5) /* program specified maximum size */; 16741 enum PResizeInc = (1L << 6) /* program specified resize increments */; 16742 enum PAspect = (1L << 7) /* program specified min and max aspect ratios */; 16743 enum PBaseSize = (1L << 8); 16744 enum PWinGravity = (1L << 9); 16745 enum PAllHints = (PPosition|PSize| PMinSize|PMaxSize| PResizeInc|PAspect); 16746 struct XSizeHints { 16747 arch_long flags; /* marks which fields in this structure are defined */ 16748 int x, y; /* Obsolete */ 16749 int width, height; /* Obsolete */ 16750 int min_width, min_height; 16751 int max_width, max_height; 16752 int width_inc, height_inc; 16753 struct Aspect { 16754 int x; /* numerator */ 16755 int y; /* denominator */ 16756 } 16757 16758 Aspect min_aspect; 16759 Aspect max_aspect; 16760 int base_width, base_height; 16761 int win_gravity; 16762 /* this structure may be extended in the future */ 16763 } 16764 16765 16766 16767 enum EventType:int 16768 { 16769 KeyPress =2, 16770 KeyRelease =3, 16771 ButtonPress =4, 16772 ButtonRelease =5, 16773 MotionNotify =6, 16774 EnterNotify =7, 16775 LeaveNotify =8, 16776 FocusIn =9, 16777 FocusOut =10, 16778 KeymapNotify =11, 16779 Expose =12, 16780 GraphicsExpose =13, 16781 NoExpose =14, 16782 VisibilityNotify =15, 16783 CreateNotify =16, 16784 DestroyNotify =17, 16785 UnmapNotify =18, 16786 MapNotify =19, 16787 MapRequest =20, 16788 ReparentNotify =21, 16789 ConfigureNotify =22, 16790 ConfigureRequest =23, 16791 GravityNotify =24, 16792 ResizeRequest =25, 16793 CirculateNotify =26, 16794 CirculateRequest =27, 16795 PropertyNotify =28, 16796 SelectionClear =29, 16797 SelectionRequest =30, 16798 SelectionNotify =31, 16799 ColormapNotify =32, 16800 ClientMessage =33, 16801 MappingNotify =34, 16802 LASTEvent =35 /* must be bigger than any event # */ 16803 } 16804 /* generated on EnterWindow and FocusIn when KeyMapState selected */ 16805 struct XKeymapEvent 16806 { 16807 int type; 16808 arch_ulong serial; /* # of last request processed by server */ 16809 Bool send_event; /* true if this came from a SendEvent request */ 16810 Display *display; /* Display the event was read from */ 16811 Window window; 16812 byte[32] key_vector; 16813 } 16814 16815 struct XExposeEvent 16816 { 16817 int type; 16818 arch_ulong serial; /* # of last request processed by server */ 16819 Bool send_event; /* true if this came from a SendEvent request */ 16820 Display *display; /* Display the event was read from */ 16821 Window window; 16822 int x, y; 16823 int width, height; 16824 int count; /* if non-zero, at least this many more */ 16825 } 16826 16827 struct XGraphicsExposeEvent{ 16828 int type; 16829 arch_ulong serial; /* # of last request processed by server */ 16830 Bool send_event; /* true if this came from a SendEvent request */ 16831 Display *display; /* Display the event was read from */ 16832 Drawable drawable; 16833 int x, y; 16834 int width, height; 16835 int count; /* if non-zero, at least this many more */ 16836 int major_code; /* core is CopyArea or CopyPlane */ 16837 int minor_code; /* not defined in the core */ 16838 } 16839 16840 struct XNoExposeEvent{ 16841 int type; 16842 arch_ulong serial; /* # of last request processed by server */ 16843 Bool send_event; /* true if this came from a SendEvent request */ 16844 Display *display; /* Display the event was read from */ 16845 Drawable drawable; 16846 int major_code; /* core is CopyArea or CopyPlane */ 16847 int minor_code; /* not defined in the core */ 16848 } 16849 16850 struct XVisibilityEvent{ 16851 int type; 16852 arch_ulong serial; /* # of last request processed by server */ 16853 Bool send_event; /* true if this came from a SendEvent request */ 16854 Display *display; /* Display the event was read from */ 16855 Window window; 16856 VisibilityNotify state; /* Visibility state */ 16857 } 16858 16859 struct XCreateWindowEvent{ 16860 int type; 16861 arch_ulong serial; /* # of last request processed by server */ 16862 Bool send_event; /* true if this came from a SendEvent request */ 16863 Display *display; /* Display the event was read from */ 16864 Window parent; /* parent of the window */ 16865 Window window; /* window id of window created */ 16866 int x, y; /* window location */ 16867 int width, height; /* size of window */ 16868 int border_width; /* border width */ 16869 Bool override_redirect; /* creation should be overridden */ 16870 } 16871 16872 struct XDestroyWindowEvent 16873 { 16874 int type; 16875 arch_ulong serial; /* # of last request processed by server */ 16876 Bool send_event; /* true if this came from a SendEvent request */ 16877 Display *display; /* Display the event was read from */ 16878 Window event; 16879 Window window; 16880 } 16881 16882 struct XUnmapEvent 16883 { 16884 int type; 16885 arch_ulong serial; /* # of last request processed by server */ 16886 Bool send_event; /* true if this came from a SendEvent request */ 16887 Display *display; /* Display the event was read from */ 16888 Window event; 16889 Window window; 16890 Bool from_configure; 16891 } 16892 16893 struct XMapEvent 16894 { 16895 int type; 16896 arch_ulong serial; /* # of last request processed by server */ 16897 Bool send_event; /* true if this came from a SendEvent request */ 16898 Display *display; /* Display the event was read from */ 16899 Window event; 16900 Window window; 16901 Bool override_redirect; /* Boolean, is override set... */ 16902 } 16903 16904 struct XMapRequestEvent 16905 { 16906 int type; 16907 arch_ulong serial; /* # of last request processed by server */ 16908 Bool send_event; /* true if this came from a SendEvent request */ 16909 Display *display; /* Display the event was read from */ 16910 Window parent; 16911 Window window; 16912 } 16913 16914 struct XReparentEvent 16915 { 16916 int type; 16917 arch_ulong serial; /* # of last request processed by server */ 16918 Bool send_event; /* true if this came from a SendEvent request */ 16919 Display *display; /* Display the event was read from */ 16920 Window event; 16921 Window window; 16922 Window parent; 16923 int x, y; 16924 Bool override_redirect; 16925 } 16926 16927 struct XConfigureEvent 16928 { 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 event; 16934 Window window; 16935 int x, y; 16936 int width, height; 16937 int border_width; 16938 Window above; 16939 Bool override_redirect; 16940 } 16941 16942 struct XGravityEvent 16943 { 16944 int type; 16945 arch_ulong serial; /* # of last request processed by server */ 16946 Bool send_event; /* true if this came from a SendEvent request */ 16947 Display *display; /* Display the event was read from */ 16948 Window event; 16949 Window window; 16950 int x, y; 16951 } 16952 16953 struct XResizeRequestEvent 16954 { 16955 int type; 16956 arch_ulong serial; /* # of last request processed by server */ 16957 Bool send_event; /* true if this came from a SendEvent request */ 16958 Display *display; /* Display the event was read from */ 16959 Window window; 16960 int width, height; 16961 } 16962 16963 struct XConfigureRequestEvent 16964 { 16965 int type; 16966 arch_ulong serial; /* # of last request processed by server */ 16967 Bool send_event; /* true if this came from a SendEvent request */ 16968 Display *display; /* Display the event was read from */ 16969 Window parent; 16970 Window window; 16971 int x, y; 16972 int width, height; 16973 int border_width; 16974 Window above; 16975 WindowStackingMethod detail; /* Above, Below, TopIf, BottomIf, Opposite */ 16976 arch_ulong value_mask; 16977 } 16978 16979 struct XCirculateEvent 16980 { 16981 int type; 16982 arch_ulong serial; /* # of last request processed by server */ 16983 Bool send_event; /* true if this came from a SendEvent request */ 16984 Display *display; /* Display the event was read from */ 16985 Window event; 16986 Window window; 16987 CirculationRequest place; /* PlaceOnTop, PlaceOnBottom */ 16988 } 16989 16990 struct XCirculateRequestEvent 16991 { 16992 int type; 16993 arch_ulong serial; /* # of last request processed by server */ 16994 Bool send_event; /* true if this came from a SendEvent request */ 16995 Display *display; /* Display the event was read from */ 16996 Window parent; 16997 Window window; 16998 CirculationRequest place; /* PlaceOnTop, PlaceOnBottom */ 16999 } 17000 17001 struct XPropertyEvent 17002 { 17003 int type; 17004 arch_ulong serial; /* # of last request processed by server */ 17005 Bool send_event; /* true if this came from a SendEvent request */ 17006 Display *display; /* Display the event was read from */ 17007 Window window; 17008 Atom atom; 17009 Time time; 17010 PropertyNotification state; /* NewValue, Deleted */ 17011 } 17012 17013 struct XSelectionClearEvent 17014 { 17015 int type; 17016 arch_ulong serial; /* # of last request processed by server */ 17017 Bool send_event; /* true if this came from a SendEvent request */ 17018 Display *display; /* Display the event was read from */ 17019 Window window; 17020 Atom selection; 17021 Time time; 17022 } 17023 17024 struct XSelectionRequestEvent 17025 { 17026 int type; 17027 arch_ulong serial; /* # of last request processed by server */ 17028 Bool send_event; /* true if this came from a SendEvent request */ 17029 Display *display; /* Display the event was read from */ 17030 Window owner; 17031 Window requestor; 17032 Atom selection; 17033 Atom target; 17034 Atom property; 17035 Time time; 17036 } 17037 17038 struct XSelectionEvent 17039 { 17040 int type; 17041 arch_ulong serial; /* # of last request processed by server */ 17042 Bool send_event; /* true if this came from a SendEvent request */ 17043 Display *display; /* Display the event was read from */ 17044 Window requestor; 17045 Atom selection; 17046 Atom target; 17047 Atom property; /* ATOM or None */ 17048 Time time; 17049 } 17050 version(X86_64) static assert(XSelectionClearEvent.sizeof == 56); 17051 17052 struct XColormapEvent 17053 { 17054 int type; 17055 arch_ulong serial; /* # of last request processed by server */ 17056 Bool send_event; /* true if this came from a SendEvent request */ 17057 Display *display; /* Display the event was read from */ 17058 Window window; 17059 Colormap colormap; /* COLORMAP or None */ 17060 Bool new_; /* C++ */ 17061 ColorMapNotification state; /* ColormapInstalled, ColormapUninstalled */ 17062 } 17063 version(X86_64) static assert(XColormapEvent.sizeof == 56); 17064 17065 struct XClientMessageEvent 17066 { 17067 int type; 17068 arch_ulong serial; /* # of last request processed by server */ 17069 Bool send_event; /* true if this came from a SendEvent request */ 17070 Display *display; /* Display the event was read from */ 17071 Window window; 17072 Atom message_type; 17073 int format; 17074 union Data{ 17075 byte[20] b; 17076 short[10] s; 17077 arch_ulong[5] l; 17078 } 17079 Data data; 17080 17081 } 17082 version(X86_64) static assert(XClientMessageEvent.sizeof == 96); 17083 17084 struct XMappingEvent 17085 { 17086 int type; 17087 arch_ulong serial; /* # of last request processed by server */ 17088 Bool send_event; /* true if this came from a SendEvent request */ 17089 Display *display; /* Display the event was read from */ 17090 Window window; /* unused */ 17091 MappingType request; /* one of MappingModifier, MappingKeyboard, 17092 MappingPointer */ 17093 int first_keycode; /* first keycode */ 17094 int count; /* defines range of change w. first_keycode*/ 17095 } 17096 17097 struct XErrorEvent 17098 { 17099 int type; 17100 Display *display; /* Display the event was read from */ 17101 XID resourceid; /* resource id */ 17102 arch_ulong serial; /* serial number of failed request */ 17103 ubyte error_code; /* error code of failed request */ 17104 ubyte request_code; /* Major op-code of failed request */ 17105 ubyte minor_code; /* Minor op-code of failed request */ 17106 } 17107 17108 struct XAnyEvent 17109 { 17110 int type; 17111 arch_ulong serial; /* # of last request processed by server */ 17112 Bool send_event; /* true if this came from a SendEvent request */ 17113 Display *display;/* Display the event was read from */ 17114 Window window; /* window on which event was requested in event mask */ 17115 } 17116 17117 union XEvent{ 17118 int type; /* must not be changed; first element */ 17119 XAnyEvent xany; 17120 XKeyEvent xkey; 17121 XButtonEvent xbutton; 17122 XMotionEvent xmotion; 17123 XCrossingEvent xcrossing; 17124 XFocusChangeEvent xfocus; 17125 XExposeEvent xexpose; 17126 XGraphicsExposeEvent xgraphicsexpose; 17127 XNoExposeEvent xnoexpose; 17128 XVisibilityEvent xvisibility; 17129 XCreateWindowEvent xcreatewindow; 17130 XDestroyWindowEvent xdestroywindow; 17131 XUnmapEvent xunmap; 17132 XMapEvent xmap; 17133 XMapRequestEvent xmaprequest; 17134 XReparentEvent xreparent; 17135 XConfigureEvent xconfigure; 17136 XGravityEvent xgravity; 17137 XResizeRequestEvent xresizerequest; 17138 XConfigureRequestEvent xconfigurerequest; 17139 XCirculateEvent xcirculate; 17140 XCirculateRequestEvent xcirculaterequest; 17141 XPropertyEvent xproperty; 17142 XSelectionClearEvent xselectionclear; 17143 XSelectionRequestEvent xselectionrequest; 17144 XSelectionEvent xselection; 17145 XColormapEvent xcolormap; 17146 XClientMessageEvent xclient; 17147 XMappingEvent xmapping; 17148 XErrorEvent xerror; 17149 XKeymapEvent xkeymap; 17150 arch_ulong[24] pad; 17151 } 17152 17153 17154 struct Display { 17155 XExtData *ext_data; /* hook for extension to hang data */ 17156 _XPrivate *private1; 17157 int fd; /* Network socket. */ 17158 int private2; 17159 int proto_major_version;/* major version of server's X protocol */ 17160 int proto_minor_version;/* minor version of servers X protocol */ 17161 char *vendor; /* vendor of the server hardware */ 17162 XID private3; 17163 XID private4; 17164 XID private5; 17165 int private6; 17166 XID function(Display*)resource_alloc;/* allocator function */ 17167 ByteOrder byte_order; /* screen byte order, LSBFirst, MSBFirst */ 17168 int bitmap_unit; /* padding and data requirements */ 17169 int bitmap_pad; /* padding requirements on bitmaps */ 17170 ByteOrder bitmap_bit_order; /* LeastSignificant or MostSignificant */ 17171 int nformats; /* number of pixmap formats in list */ 17172 ScreenFormat *pixmap_format; /* pixmap format list */ 17173 int private8; 17174 int release; /* release of the server */ 17175 _XPrivate *private9; 17176 _XPrivate *private10; 17177 int qlen; /* Length of input event queue */ 17178 arch_ulong last_request_read; /* seq number of last event read */ 17179 arch_ulong request; /* sequence number of last request. */ 17180 XPointer private11; 17181 XPointer private12; 17182 XPointer private13; 17183 XPointer private14; 17184 uint max_request_size; /* maximum number 32 bit words in request*/ 17185 _XrmHashBucketRec *db; 17186 int function (Display*)private15; 17187 char *display_name; /* "host:display" string used on this connect*/ 17188 int default_screen; /* default screen for operations */ 17189 int nscreens; /* number of screens on this server*/ 17190 Screen *screens; /* pointer to list of screens */ 17191 arch_ulong motion_buffer; /* size of motion buffer */ 17192 arch_ulong private16; 17193 int min_keycode; /* minimum defined keycode */ 17194 int max_keycode; /* maximum defined keycode */ 17195 XPointer private17; 17196 XPointer private18; 17197 int private19; 17198 byte *xdefaults; /* contents of defaults from server */ 17199 /* there is more to this structure, but it is private to Xlib */ 17200 } 17201 17202 // I got these numbers from a C program as a sanity test 17203 version(X86_64) { 17204 static assert(Display.sizeof == 296); 17205 static assert(XPointer.sizeof == 8); 17206 static assert(XErrorEvent.sizeof == 40); 17207 static assert(XAnyEvent.sizeof == 40); 17208 static assert(XMappingEvent.sizeof == 56); 17209 static assert(XEvent.sizeof == 192); 17210 } else version (AArch64) { 17211 // omit check for aarch64 17212 } else { 17213 static assert(Display.sizeof == 176); 17214 static assert(XPointer.sizeof == 4); 17215 static assert(XEvent.sizeof == 96); 17216 } 17217 17218 struct Depth 17219 { 17220 int depth; /* this depth (Z) of the depth */ 17221 int nvisuals; /* number of Visual types at this depth */ 17222 Visual *visuals; /* list of visuals possible at this depth */ 17223 } 17224 17225 alias void* GC; 17226 alias c_ulong VisualID; 17227 alias XID Colormap; 17228 alias XID Cursor; 17229 alias XID KeySym; 17230 alias uint KeyCode; 17231 enum None = 0; 17232 } 17233 17234 version(without_opengl) {} 17235 else { 17236 extern(C) nothrow @nogc { 17237 17238 17239 static if(!SdpyIsUsingIVGLBinds) { 17240 enum GLX_USE_GL= 1; /* support GLX rendering */ 17241 enum GLX_BUFFER_SIZE= 2; /* depth of the color buffer */ 17242 enum GLX_LEVEL= 3; /* level in plane stacking */ 17243 enum GLX_RGBA= 4; /* true if RGBA mode */ 17244 enum GLX_DOUBLEBUFFER= 5; /* double buffering supported */ 17245 enum GLX_STEREO= 6; /* stereo buffering supported */ 17246 enum GLX_AUX_BUFFERS= 7; /* number of aux buffers */ 17247 enum GLX_RED_SIZE= 8; /* number of red component bits */ 17248 enum GLX_GREEN_SIZE= 9; /* number of green component bits */ 17249 enum GLX_BLUE_SIZE= 10; /* number of blue component bits */ 17250 enum GLX_ALPHA_SIZE= 11; /* number of alpha component bits */ 17251 enum GLX_DEPTH_SIZE= 12; /* number of depth bits */ 17252 enum GLX_STENCIL_SIZE= 13; /* number of stencil bits */ 17253 enum GLX_ACCUM_RED_SIZE= 14; /* number of red accum bits */ 17254 enum GLX_ACCUM_GREEN_SIZE= 15; /* number of green accum bits */ 17255 enum GLX_ACCUM_BLUE_SIZE= 16; /* number of blue accum bits */ 17256 enum GLX_ACCUM_ALPHA_SIZE= 17; /* number of alpha accum bits */ 17257 17258 17259 //XVisualInfo* glXChooseVisual(Display *dpy, int screen, in int *attrib_list); 17260 17261 17262 17263 enum GL_TRUE = 1; 17264 enum GL_FALSE = 0; 17265 alias int GLint; 17266 } 17267 17268 alias XID GLXContextID; 17269 alias XID GLXPixmap; 17270 alias XID GLXDrawable; 17271 alias XID GLXPbuffer; 17272 alias XID GLXWindow; 17273 alias XID GLXFBConfigID; 17274 alias void* GLXContext; 17275 17276 } 17277 } 17278 17279 enum AllocNone = 0; 17280 17281 extern(C) { 17282 /* WARNING, this type not in Xlib spec */ 17283 extern(C) alias XIOErrorHandler = int function (Display* display); 17284 } 17285 17286 extern(C) nothrow 17287 alias XErrorHandler = int function(Display*, XErrorEvent*); 17288 17289 extern(C) nothrow @nogc { 17290 struct Screen{ 17291 XExtData *ext_data; /* hook for extension to hang data */ 17292 Display *display; /* back pointer to display structure */ 17293 Window root; /* Root window id. */ 17294 int width, height; /* width and height of screen */ 17295 int mwidth, mheight; /* width and height of in millimeters */ 17296 int ndepths; /* number of depths possible */ 17297 Depth *depths; /* list of allowable depths on the screen */ 17298 int root_depth; /* bits per pixel */ 17299 Visual *root_visual; /* root visual */ 17300 GC default_gc; /* GC for the root root visual */ 17301 Colormap cmap; /* default color map */ 17302 uint white_pixel; 17303 uint black_pixel; /* White and Black pixel values */ 17304 int max_maps, min_maps; /* max and min color maps */ 17305 int backing_store; /* Never, WhenMapped, Always */ 17306 bool save_unders; 17307 int root_input_mask; /* initial root input mask */ 17308 } 17309 17310 struct Visual 17311 { 17312 XExtData *ext_data; /* hook for extension to hang data */ 17313 VisualID visualid; /* visual id of this visual */ 17314 int class_; /* class of screen (monochrome, etc.) */ 17315 c_ulong red_mask, green_mask, blue_mask; /* mask values */ 17316 int bits_per_rgb; /* log base 2 of distinct color values */ 17317 int map_entries; /* color map entries */ 17318 } 17319 17320 alias Display* _XPrivDisplay; 17321 17322 extern(D) Screen* ScreenOfDisplay(Display* dpy, int scr) { 17323 assert(dpy !is null); 17324 return &dpy.screens[scr]; 17325 } 17326 17327 extern(D) Window RootWindow(Display *dpy,int scr) { 17328 return ScreenOfDisplay(dpy,scr).root; 17329 } 17330 17331 struct XWMHints { 17332 arch_long flags; 17333 Bool input; 17334 int initial_state; 17335 Pixmap icon_pixmap; 17336 Window icon_window; 17337 int icon_x, icon_y; 17338 Pixmap icon_mask; 17339 XID window_group; 17340 } 17341 17342 struct XClassHint { 17343 char* res_name; 17344 char* res_class; 17345 } 17346 17347 extern(D) int DefaultScreen(Display *dpy) { 17348 return dpy.default_screen; 17349 } 17350 17351 extern(D) int DefaultDepth(Display* dpy, int scr) { return ScreenOfDisplay(dpy, scr).root_depth; } 17352 extern(D) int DisplayWidth(Display* dpy, int scr) { return ScreenOfDisplay(dpy, scr).width; } 17353 extern(D) int DisplayHeight(Display* dpy, int scr) { return ScreenOfDisplay(dpy, scr).height; } 17354 extern(D) int DisplayWidthMM(Display* dpy, int scr) { return ScreenOfDisplay(dpy, scr).mwidth; } 17355 extern(D) int DisplayHeightMM(Display* dpy, int scr) { return ScreenOfDisplay(dpy, scr).mheight; } 17356 extern(D) auto DefaultColormap(Display* dpy, int scr) { return ScreenOfDisplay(dpy, scr).cmap; } 17357 17358 extern(D) int ConnectionNumber(Display* dpy) { return dpy.fd; } 17359 17360 enum int AnyPropertyType = 0; 17361 enum int Success = 0; 17362 17363 enum int RevertToNone = None; 17364 enum int PointerRoot = 1; 17365 enum Time CurrentTime = 0; 17366 enum int RevertToPointerRoot = PointerRoot; 17367 enum int RevertToParent = 2; 17368 17369 extern(D) int DefaultDepthOfDisplay(Display* dpy) { 17370 return ScreenOfDisplay(dpy, DefaultScreen(dpy)).root_depth; 17371 } 17372 17373 extern(D) Visual* DefaultVisual(Display *dpy,int scr) { 17374 return ScreenOfDisplay(dpy,scr).root_visual; 17375 } 17376 17377 extern(D) GC DefaultGC(Display *dpy,int scr) { 17378 return ScreenOfDisplay(dpy,scr).default_gc; 17379 } 17380 17381 extern(D) uint BlackPixel(Display *dpy,int scr) { 17382 return ScreenOfDisplay(dpy,scr).black_pixel; 17383 } 17384 17385 extern(D) uint WhitePixel(Display *dpy,int scr) { 17386 return ScreenOfDisplay(dpy,scr).white_pixel; 17387 } 17388 17389 alias void* XFontSet; // i think 17390 struct XmbTextItem { 17391 char* chars; 17392 int nchars; 17393 int delta; 17394 XFontSet font_set; 17395 } 17396 17397 struct XTextItem { 17398 char* chars; 17399 int nchars; 17400 int delta; 17401 Font font; 17402 } 17403 17404 enum { 17405 GXclear = 0x0, /* 0 */ 17406 GXand = 0x1, /* src AND dst */ 17407 GXandReverse = 0x2, /* src AND NOT dst */ 17408 GXcopy = 0x3, /* src */ 17409 GXandInverted = 0x4, /* NOT src AND dst */ 17410 GXnoop = 0x5, /* dst */ 17411 GXxor = 0x6, /* src XOR dst */ 17412 GXor = 0x7, /* src OR dst */ 17413 GXnor = 0x8, /* NOT src AND NOT dst */ 17414 GXequiv = 0x9, /* NOT src XOR dst */ 17415 GXinvert = 0xa, /* NOT dst */ 17416 GXorReverse = 0xb, /* src OR NOT dst */ 17417 GXcopyInverted = 0xc, /* NOT src */ 17418 GXorInverted = 0xd, /* NOT src OR dst */ 17419 GXnand = 0xe, /* NOT src OR NOT dst */ 17420 GXset = 0xf, /* 1 */ 17421 } 17422 enum QueueMode : int { 17423 QueuedAlready, 17424 QueuedAfterReading, 17425 QueuedAfterFlush 17426 } 17427 17428 enum GrabMode { GrabModeSync = 0, GrabModeAsync = 1 } 17429 17430 struct XPoint { 17431 short x; 17432 short y; 17433 } 17434 17435 enum CoordMode:int { 17436 CoordModeOrigin = 0, 17437 CoordModePrevious = 1 17438 } 17439 17440 enum PolygonShape:int { 17441 Complex = 0, 17442 Nonconvex = 1, 17443 Convex = 2 17444 } 17445 17446 struct XTextProperty { 17447 const(char)* value; /* same as Property routines */ 17448 Atom encoding; /* prop type */ 17449 int format; /* prop data format: 8, 16, or 32 */ 17450 arch_ulong nitems; /* number of data items in value */ 17451 } 17452 17453 version( X86_64 ) { 17454 static assert(XTextProperty.sizeof == 32); 17455 } 17456 17457 17458 struct XGCValues { 17459 int function_; /* logical operation */ 17460 arch_ulong plane_mask;/* plane mask */ 17461 arch_ulong foreground;/* foreground pixel */ 17462 arch_ulong background;/* background pixel */ 17463 int line_width; /* line width */ 17464 int line_style; /* LineSolid, LineOnOffDash, LineDoubleDash */ 17465 int cap_style; /* CapNotLast, CapButt, 17466 CapRound, CapProjecting */ 17467 int join_style; /* JoinMiter, JoinRound, JoinBevel */ 17468 int fill_style; /* FillSolid, FillTiled, 17469 FillStippled, FillOpaeueStippled */ 17470 int fill_rule; /* EvenOddRule, WindingRule */ 17471 int arc_mode; /* ArcChord, ArcPieSlice */ 17472 Pixmap tile; /* tile pixmap for tiling operations */ 17473 Pixmap stipple; /* stipple 1 plane pixmap for stipping */ 17474 int ts_x_origin; /* offset for tile or stipple operations */ 17475 int ts_y_origin; 17476 Font font; /* default text font for text operations */ 17477 int subwindow_mode; /* ClipByChildren, IncludeInferiors */ 17478 Bool graphics_exposures;/* boolean, should exposures be generated */ 17479 int clip_x_origin; /* origin for clipping */ 17480 int clip_y_origin; 17481 Pixmap clip_mask; /* bitmap clipping; other calls for rects */ 17482 int dash_offset; /* patterned/dashed line information */ 17483 char dashes; 17484 } 17485 17486 struct XColor { 17487 arch_ulong pixel; 17488 ushort red, green, blue; 17489 byte flags; 17490 byte pad; 17491 } 17492 17493 struct XRectangle { 17494 short x; 17495 short y; 17496 ushort width; 17497 ushort height; 17498 } 17499 17500 enum ClipByChildren = 0; 17501 enum IncludeInferiors = 1; 17502 17503 enum Atom XA_PRIMARY = 1; 17504 enum Atom XA_SECONDARY = 2; 17505 enum Atom XA_STRING = 31; 17506 enum Atom XA_CARDINAL = 6; 17507 enum Atom XA_WM_NAME = 39; 17508 enum Atom XA_ATOM = 4; 17509 enum Atom XA_WINDOW = 33; 17510 enum Atom XA_WM_HINTS = 35; 17511 enum int PropModeAppend = 2; 17512 enum int PropModeReplace = 0; 17513 enum int PropModePrepend = 1; 17514 17515 enum int CopyFromParent = 0; 17516 enum int InputOutput = 1; 17517 17518 // XWMHints 17519 enum InputHint = 1 << 0; 17520 enum StateHint = 1 << 1; 17521 enum IconPixmapHint = (1L << 2); 17522 enum IconWindowHint = (1L << 3); 17523 enum IconPositionHint = (1L << 4); 17524 enum IconMaskHint = (1L << 5); 17525 enum WindowGroupHint = (1L << 6); 17526 enum AllHints = (InputHint|StateHint|IconPixmapHint|IconWindowHint|IconPositionHint|IconMaskHint|WindowGroupHint); 17527 enum XUrgencyHint = (1L << 8); 17528 17529 // GC Components 17530 enum GCFunction = (1L<<0); 17531 enum GCPlaneMask = (1L<<1); 17532 enum GCForeground = (1L<<2); 17533 enum GCBackground = (1L<<3); 17534 enum GCLineWidth = (1L<<4); 17535 enum GCLineStyle = (1L<<5); 17536 enum GCCapStyle = (1L<<6); 17537 enum GCJoinStyle = (1L<<7); 17538 enum GCFillStyle = (1L<<8); 17539 enum GCFillRule = (1L<<9); 17540 enum GCTile = (1L<<10); 17541 enum GCStipple = (1L<<11); 17542 enum GCTileStipXOrigin = (1L<<12); 17543 enum GCTileStipYOrigin = (1L<<13); 17544 enum GCFont = (1L<<14); 17545 enum GCSubwindowMode = (1L<<15); 17546 enum GCGraphicsExposures= (1L<<16); 17547 enum GCClipXOrigin = (1L<<17); 17548 enum GCClipYOrigin = (1L<<18); 17549 enum GCClipMask = (1L<<19); 17550 enum GCDashOffset = (1L<<20); 17551 enum GCDashList = (1L<<21); 17552 enum GCArcMode = (1L<<22); 17553 enum GCLastBit = 22; 17554 17555 17556 enum int WithdrawnState = 0; 17557 enum int NormalState = 1; 17558 enum int IconicState = 3; 17559 17560 } 17561 } else version (OSXCocoa) { 17562 private: 17563 alias void* id; 17564 alias void* Class; 17565 alias void* SEL; 17566 alias void* IMP; 17567 alias void* Ivar; 17568 alias byte BOOL; 17569 alias const(void)* CFStringRef; 17570 alias const(void)* CFAllocatorRef; 17571 alias const(void)* CFTypeRef; 17572 alias const(void)* CGContextRef; 17573 alias const(void)* CGColorSpaceRef; 17574 alias const(void)* CGImageRef; 17575 alias ulong CGBitmapInfo; 17576 17577 struct objc_super { 17578 id self; 17579 Class superclass; 17580 } 17581 17582 struct CFRange { 17583 long location, length; 17584 } 17585 17586 struct NSPoint { 17587 double x, y; 17588 17589 static fromTuple(T)(T tupl) { 17590 return NSPoint(tupl.tupleof); 17591 } 17592 } 17593 struct NSSize { 17594 double width, height; 17595 } 17596 struct NSRect { 17597 NSPoint origin; 17598 NSSize size; 17599 } 17600 alias NSPoint CGPoint; 17601 alias NSSize CGSize; 17602 alias NSRect CGRect; 17603 17604 struct CGAffineTransform { 17605 double a, b, c, d, tx, ty; 17606 } 17607 17608 enum NSApplicationActivationPolicyRegular = 0; 17609 enum NSBackingStoreBuffered = 2; 17610 enum kCFStringEncodingUTF8 = 0x08000100; 17611 17612 enum : size_t { 17613 NSBorderlessWindowMask = 0, 17614 NSTitledWindowMask = 1 << 0, 17615 NSClosableWindowMask = 1 << 1, 17616 NSMiniaturizableWindowMask = 1 << 2, 17617 NSResizableWindowMask = 1 << 3, 17618 NSTexturedBackgroundWindowMask = 1 << 8 17619 } 17620 17621 enum : ulong { 17622 kCGImageAlphaNone, 17623 kCGImageAlphaPremultipliedLast, 17624 kCGImageAlphaPremultipliedFirst, 17625 kCGImageAlphaLast, 17626 kCGImageAlphaFirst, 17627 kCGImageAlphaNoneSkipLast, 17628 kCGImageAlphaNoneSkipFirst 17629 } 17630 enum : ulong { 17631 kCGBitmapAlphaInfoMask = 0x1F, 17632 kCGBitmapFloatComponents = (1 << 8), 17633 kCGBitmapByteOrderMask = 0x7000, 17634 kCGBitmapByteOrderDefault = (0 << 12), 17635 kCGBitmapByteOrder16Little = (1 << 12), 17636 kCGBitmapByteOrder32Little = (2 << 12), 17637 kCGBitmapByteOrder16Big = (3 << 12), 17638 kCGBitmapByteOrder32Big = (4 << 12) 17639 } 17640 enum CGPathDrawingMode { 17641 kCGPathFill, 17642 kCGPathEOFill, 17643 kCGPathStroke, 17644 kCGPathFillStroke, 17645 kCGPathEOFillStroke 17646 } 17647 enum objc_AssociationPolicy : size_t { 17648 OBJC_ASSOCIATION_ASSIGN = 0, 17649 OBJC_ASSOCIATION_RETAIN_NONATOMIC = 1, 17650 OBJC_ASSOCIATION_COPY_NONATOMIC = 3, 17651 OBJC_ASSOCIATION_RETAIN = 0x301, //01401, 17652 OBJC_ASSOCIATION_COPY = 0x303 //01403 17653 } 17654 17655 extern(C) { 17656 id objc_msgSend(id receiver, SEL selector, ...); 17657 id objc_msgSendSuper(objc_super* superStruct, SEL selector, ...); 17658 id objc_getClass(const(char)* name); 17659 SEL sel_registerName(const(char)* str); 17660 Class objc_allocateClassPair(Class superclass, const(char)* name, 17661 size_t extra_bytes); 17662 void objc_registerClassPair(Class cls); 17663 BOOL class_addMethod(Class cls, SEL name, IMP imp, const(char)* types); 17664 id objc_getAssociatedObject(id object, void* key); 17665 void objc_setAssociatedObject(id object, void* key, id value, 17666 objc_AssociationPolicy policy); 17667 Ivar class_getInstanceVariable(Class cls, const(char)* name); 17668 id object_getIvar(id object, Ivar ivar); 17669 void object_setIvar(id object, Ivar ivar, id value); 17670 BOOL class_addIvar(Class cls, const(char)* name, 17671 size_t size, ubyte alignment, const(char)* types); 17672 17673 extern __gshared id NSApp; 17674 17675 void CFRelease(CFTypeRef obj); 17676 17677 CFStringRef CFStringCreateWithBytes(CFAllocatorRef allocator, 17678 const(char)* bytes, long numBytes, 17679 long encoding, 17680 BOOL isExternalRepresentation); 17681 long CFStringGetBytes(CFStringRef theString, CFRange range, long encoding, 17682 char lossByte, bool isExternalRepresentation, 17683 char* buffer, long maxBufLen, long* usedBufLen); 17684 long CFStringGetLength(CFStringRef theString); 17685 17686 CGContextRef CGBitmapContextCreate(void* data, 17687 size_t width, size_t height, 17688 size_t bitsPerComponent, 17689 size_t bytesPerRow, 17690 CGColorSpaceRef colorspace, 17691 CGBitmapInfo bitmapInfo); 17692 void CGContextRelease(CGContextRef c); 17693 ubyte* CGBitmapContextGetData(CGContextRef c); 17694 CGImageRef CGBitmapContextCreateImage(CGContextRef c); 17695 size_t CGBitmapContextGetWidth(CGContextRef c); 17696 size_t CGBitmapContextGetHeight(CGContextRef c); 17697 17698 CGColorSpaceRef CGColorSpaceCreateDeviceRGB(); 17699 void CGColorSpaceRelease(CGColorSpaceRef cs); 17700 17701 void CGContextSetRGBStrokeColor(CGContextRef c, 17702 double red, double green, double blue, 17703 double alpha); 17704 void CGContextSetRGBFillColor(CGContextRef c, 17705 double red, double green, double blue, 17706 double alpha); 17707 void CGContextDrawImage(CGContextRef c, CGRect rect, CGImageRef image); 17708 void CGContextShowTextAtPoint(CGContextRef c, double x, double y, 17709 const(char)* str, size_t length); 17710 void CGContextStrokeLineSegments(CGContextRef c, 17711 const(CGPoint)* points, size_t count); 17712 17713 void CGContextBeginPath(CGContextRef c); 17714 void CGContextDrawPath(CGContextRef c, CGPathDrawingMode mode); 17715 void CGContextAddEllipseInRect(CGContextRef c, CGRect rect); 17716 void CGContextAddArc(CGContextRef c, double x, double y, double radius, 17717 double startAngle, double endAngle, long clockwise); 17718 void CGContextAddRect(CGContextRef c, CGRect rect); 17719 void CGContextAddLines(CGContextRef c, 17720 const(CGPoint)* points, size_t count); 17721 void CGContextSaveGState(CGContextRef c); 17722 void CGContextRestoreGState(CGContextRef c); 17723 void CGContextSelectFont(CGContextRef c, const(char)* name, double size, 17724 ulong textEncoding); 17725 CGAffineTransform CGContextGetTextMatrix(CGContextRef c); 17726 void CGContextSetTextMatrix(CGContextRef c, CGAffineTransform t); 17727 17728 void CGImageRelease(CGImageRef image); 17729 } 17730 17731 private: 17732 // A convenient method to create a CFString (=NSString) from a D string. 17733 CFStringRef createCFString(string str) { 17734 return CFStringCreateWithBytes(null, str.ptr, cast(long) str.length, 17735 kCFStringEncodingUTF8, false); 17736 } 17737 17738 // Objective-C calls. 17739 RetType objc_msgSend_specialized(string selector, RetType, T...)(id self, T args) { 17740 auto _cmd = sel_registerName(selector.ptr); 17741 alias extern(C) RetType function(id, SEL, T) ExpectedType; 17742 return (cast(ExpectedType)&objc_msgSend)(self, _cmd, args); 17743 } 17744 RetType objc_msgSend_classMethod(string selector, RetType, T...)(const(char)* className, T args) { 17745 auto _cmd = sel_registerName(selector.ptr); 17746 auto cls = objc_getClass(className); 17747 alias extern(C) RetType function(id, SEL, T) ExpectedType; 17748 return (cast(ExpectedType)&objc_msgSend)(cls, _cmd, args); 17749 } 17750 RetType objc_msgSend_classMethod(string className, string selector, RetType, T...)(T args) { 17751 return objc_msgSend_classMethod!(selector, RetType, T)(className.ptr, args); 17752 } 17753 17754 alias objc_msgSend_specialized!("setNeedsDisplay:", void, BOOL) setNeedsDisplay; 17755 alias objc_msgSend_classMethod!("alloc", id) alloc; 17756 alias objc_msgSend_specialized!("initWithContentRect:styleMask:backing:defer:", 17757 id, NSRect, size_t, size_t, BOOL) initWithContentRect; 17758 alias objc_msgSend_specialized!("setTitle:", void, CFStringRef) setTitle; 17759 alias objc_msgSend_specialized!("center", void) center; 17760 alias objc_msgSend_specialized!("initWithFrame:", id, NSRect) initWithFrame; 17761 alias objc_msgSend_specialized!("setContentView:", void, id) setContentView; 17762 alias objc_msgSend_specialized!("release", void) release; 17763 alias objc_msgSend_classMethod!("NSColor", "whiteColor", id) whiteNSColor; 17764 alias objc_msgSend_specialized!("setBackgroundColor:", void, id) setBackgroundColor; 17765 alias objc_msgSend_specialized!("makeKeyAndOrderFront:", void, id) makeKeyAndOrderFront; 17766 alias objc_msgSend_specialized!("invalidate", void) invalidate; 17767 alias objc_msgSend_specialized!("close", void) close; 17768 alias objc_msgSend_classMethod!("NSTimer", "scheduledTimerWithTimeInterval:target:selector:userInfo:repeats:", 17769 id, double, id, SEL, id, BOOL) scheduledTimer; 17770 alias objc_msgSend_specialized!("run", void) run; 17771 alias objc_msgSend_classMethod!("NSGraphicsContext", "currentContext", 17772 id) currentNSGraphicsContext; 17773 alias objc_msgSend_specialized!("graphicsPort", CGContextRef) graphicsPort; 17774 alias objc_msgSend_specialized!("characters", CFStringRef) characters; 17775 alias objc_msgSend_specialized!("superclass", Class) superclass; 17776 alias objc_msgSend_specialized!("init", id) init; 17777 alias objc_msgSend_specialized!("addItem:", void, id) addItem; 17778 alias objc_msgSend_specialized!("setMainMenu:", void, id) setMainMenu; 17779 alias objc_msgSend_specialized!("initWithTitle:action:keyEquivalent:", 17780 id, CFStringRef, SEL, CFStringRef) initWithTitle; 17781 alias objc_msgSend_specialized!("setSubmenu:", void, id) setSubmenu; 17782 alias objc_msgSend_specialized!("setDelegate:", void, id) setDelegate; 17783 alias objc_msgSend_specialized!("activateIgnoringOtherApps:", 17784 void, BOOL) activateIgnoringOtherApps; 17785 alias objc_msgSend_classMethod!("NSApplication", "sharedApplication", 17786 id) sharedNSApplication; 17787 alias objc_msgSend_specialized!("setActivationPolicy:", void, ptrdiff_t) setActivationPolicy; 17788 } else static assert(0, "Unsupported operating system"); 17789 17790 17791 version(OSXCocoa) { 17792 // I don't know anything about the Mac, but a couple years ago, KennyTM on the newsgroup wrote this for me 17793 // 17794 // http://forum.dlang.org/thread/innr0v$1deh$1@digitalmars.com?page=4#post-int88l:24uaf:241:40digitalmars.com 17795 // https://github.com/kennytm/simpledisplay.d/blob/osx/simpledisplay.d 17796 // 17797 // and it is about time I merged it in here. It is available with -version=OSXCocoa until someone tests it for me! 17798 // Probably won't even fully compile right now 17799 17800 import std.math : PI; // OSX Only 17801 import std.algorithm : map; // OSX Only 17802 import std.array : array; // OSX Only 17803 17804 alias SimpleWindow NativeWindowHandle; 17805 alias void delegate(id) NativeEventHandler; 17806 17807 __gshared Ivar simpleWindowIvar; 17808 17809 enum KEY_ESCAPE = 27; 17810 17811 mixin template NativeImageImplementation() { 17812 CGContextRef context; 17813 ubyte* rawData; 17814 final: 17815 17816 void convertToRgbaBytes(ubyte[] where) { 17817 assert(where.length == this.width * this.height * 4); 17818 17819 // if rawData had a length.... 17820 //assert(rawData.length == where.length); 17821 for(long idx = 0; idx < where.length; idx += 4) { 17822 auto alpha = rawData[idx + 3]; 17823 if(alpha == 255) { 17824 where[idx + 0] = rawData[idx + 0]; // r 17825 where[idx + 1] = rawData[idx + 1]; // g 17826 where[idx + 2] = rawData[idx + 2]; // b 17827 where[idx + 3] = rawData[idx + 3]; // a 17828 } else { 17829 where[idx + 0] = cast(ubyte)(rawData[idx + 0] * 255 / alpha); // r 17830 where[idx + 1] = cast(ubyte)(rawData[idx + 1] * 255 / alpha); // g 17831 where[idx + 2] = cast(ubyte)(rawData[idx + 2] * 255 / alpha); // b 17832 where[idx + 3] = rawData[idx + 3]; // a 17833 17834 } 17835 } 17836 } 17837 17838 void setFromRgbaBytes(in ubyte[] where) { 17839 // FIXME: this is probably wrong 17840 assert(where.length == this.width * this.height * 4); 17841 17842 // if rawData had a length.... 17843 //assert(rawData.length == where.length); 17844 for(long idx = 0; idx < where.length; idx += 4) { 17845 auto alpha = rawData[idx + 3]; 17846 if(alpha == 255) { 17847 rawData[idx + 0] = where[idx + 0]; // r 17848 rawData[idx + 1] = where[idx + 1]; // g 17849 rawData[idx + 2] = where[idx + 2]; // b 17850 rawData[idx + 3] = where[idx + 3]; // a 17851 } else { 17852 rawData[idx + 0] = cast(ubyte)(where[idx + 0] * 255 / alpha); // r 17853 rawData[idx + 1] = cast(ubyte)(where[idx + 1] * 255 / alpha); // g 17854 rawData[idx + 2] = cast(ubyte)(where[idx + 2] * 255 / alpha); // b 17855 rawData[idx + 3] = where[idx + 3]; // a 17856 17857 } 17858 } 17859 } 17860 17861 17862 void createImage(int width, int height, bool forcexshm=false) { 17863 auto colorSpace = CGColorSpaceCreateDeviceRGB(); 17864 context = CGBitmapContextCreate(null, width, height, 8, 4*width, 17865 colorSpace, 17866 kCGImageAlphaPremultipliedLast 17867 |kCGBitmapByteOrder32Big); 17868 CGColorSpaceRelease(colorSpace); 17869 rawData = CGBitmapContextGetData(context); 17870 } 17871 void dispose() { 17872 CGContextRelease(context); 17873 } 17874 17875 void setPixel(int x, int y, Color c) { 17876 auto offset = (y * width + x) * 4; 17877 if (c.a == 255) { 17878 rawData[offset + 0] = c.r; 17879 rawData[offset + 1] = c.g; 17880 rawData[offset + 2] = c.b; 17881 rawData[offset + 3] = c.a; 17882 } else { 17883 rawData[offset + 0] = cast(ubyte)(c.r*c.a/255); 17884 rawData[offset + 1] = cast(ubyte)(c.g*c.a/255); 17885 rawData[offset + 2] = cast(ubyte)(c.b*c.a/255); 17886 rawData[offset + 3] = c.a; 17887 } 17888 } 17889 } 17890 17891 mixin template NativeScreenPainterImplementation() { 17892 CGContextRef context; 17893 ubyte[4] _outlineComponents; 17894 id view; 17895 17896 void create(NativeWindowHandle window) { 17897 context = window.drawingContext; 17898 view = window.view; 17899 } 17900 17901 void dispose() { 17902 setNeedsDisplay(view, true); 17903 } 17904 17905 bool manualInvalidations; 17906 void invalidateRect(Rectangle invalidRect) { } 17907 17908 // NotYetImplementedException 17909 Size textSize(in char[] txt) { return Size(32, 16); throw new NotYetImplementedException(); } 17910 void rasterOp(RasterOp op) {} 17911 Pen _activePen; 17912 Color _fillColor; 17913 Rectangle _clipRectangle; 17914 void setClipRectangle(int, int, int, int) {} 17915 void setFont(OperatingSystemFont) {} 17916 int fontHeight() { return 14; } 17917 17918 // end 17919 17920 void pen(Pen pen) { 17921 _activePen = pen; 17922 auto color = pen.color; // FIXME 17923 double alphaComponent = color.a/255.0f; 17924 CGContextSetRGBStrokeColor(context, 17925 color.r/255.0f, color.g/255.0f, color.b/255.0f, alphaComponent); 17926 17927 if (color.a != 255) { 17928 _outlineComponents[0] = cast(ubyte)(color.r*color.a/255); 17929 _outlineComponents[1] = cast(ubyte)(color.g*color.a/255); 17930 _outlineComponents[2] = cast(ubyte)(color.b*color.a/255); 17931 _outlineComponents[3] = color.a; 17932 } else { 17933 _outlineComponents[0] = color.r; 17934 _outlineComponents[1] = color.g; 17935 _outlineComponents[2] = color.b; 17936 _outlineComponents[3] = color.a; 17937 } 17938 } 17939 17940 @property void fillColor(Color color) { 17941 CGContextSetRGBFillColor(context, 17942 color.r/255.0f, color.g/255.0f, color.b/255.0f, color.a/255.0f); 17943 } 17944 17945 void drawImage(int x, int y, Image image, int ulx, int upy, int width, int height) { 17946 // NotYetImplementedException for upper left/width/height 17947 auto cgImage = CGBitmapContextCreateImage(image.context); 17948 auto size = CGSize(CGBitmapContextGetWidth(image.context), 17949 CGBitmapContextGetHeight(image.context)); 17950 CGContextDrawImage(context, CGRect(CGPoint(x, y), size), cgImage); 17951 CGImageRelease(cgImage); 17952 } 17953 17954 version(OSXCocoa) {} else // NotYetImplementedException 17955 void drawPixmap(Sprite image, int x, int y) { 17956 // FIXME: is this efficient? 17957 auto cgImage = CGBitmapContextCreateImage(image.context); 17958 auto size = CGSize(CGBitmapContextGetWidth(image.context), 17959 CGBitmapContextGetHeight(image.context)); 17960 CGContextDrawImage(context, CGRect(CGPoint(x, y), size), cgImage); 17961 CGImageRelease(cgImage); 17962 } 17963 17964 17965 void drawText(int x, int y, int x2, int y2, in char[] text, uint alignment) { 17966 // FIXME: alignment 17967 if (_outlineComponents[3] != 0) { 17968 CGContextSaveGState(context); 17969 auto invAlpha = 1.0f/_outlineComponents[3]; 17970 CGContextSetRGBFillColor(context, _outlineComponents[0]*invAlpha, 17971 _outlineComponents[1]*invAlpha, 17972 _outlineComponents[2]*invAlpha, 17973 _outlineComponents[3]/255.0f); 17974 CGContextShowTextAtPoint(context, x, y + 12 /* this is cuz this picks baseline but i want bounding box */, text.ptr, text.length); 17975 // auto cfstr = cast(id)createCFString(text); 17976 // objc_msgSend(cfstr, sel_registerName("drawAtPoint:withAttributes:"), 17977 // NSPoint(x, y), null); 17978 // CFRelease(cfstr); 17979 CGContextRestoreGState(context); 17980 } 17981 } 17982 17983 void drawPixel(int x, int y) { 17984 auto rawData = CGBitmapContextGetData(context); 17985 auto width = CGBitmapContextGetWidth(context); 17986 auto height = CGBitmapContextGetHeight(context); 17987 auto offset = ((height - y - 1) * width + x) * 4; 17988 rawData[offset .. offset+4] = _outlineComponents; 17989 } 17990 17991 void drawLine(int x1, int y1, int x2, int y2) { 17992 CGPoint[2] linePoints; 17993 linePoints[0] = CGPoint(x1, y1); 17994 linePoints[1] = CGPoint(x2, y2); 17995 CGContextStrokeLineSegments(context, linePoints.ptr, linePoints.length); 17996 } 17997 17998 void drawRectangle(int x, int y, int width, int height) { 17999 CGContextBeginPath(context); 18000 auto rect = CGRect(CGPoint(x, y), CGSize(width, height)); 18001 CGContextAddRect(context, rect); 18002 CGContextDrawPath(context, CGPathDrawingMode.kCGPathFillStroke); 18003 } 18004 18005 void drawEllipse(int x1, int y1, int x2, int y2) { 18006 CGContextBeginPath(context); 18007 auto rect = CGRect(CGPoint(x1, y1), CGSize(x2-x1, y2-y1)); 18008 CGContextAddEllipseInRect(context, rect); 18009 CGContextDrawPath(context, CGPathDrawingMode.kCGPathFillStroke); 18010 } 18011 18012 void drawArc(int x1, int y1, int width, int height, int start, int finish) { 18013 // @@@BUG@@@ Does not support elliptic arc (width != height). 18014 CGContextBeginPath(context); 18015 CGContextAddArc(context, x1+width*0.5f, y1+height*0.5f, width, 18016 start*PI/(180*64), finish*PI/(180*64), 0); 18017 CGContextDrawPath(context, CGPathDrawingMode.kCGPathFillStroke); 18018 } 18019 18020 void drawPolygon(Point[] intPoints) { 18021 CGContextBeginPath(context); 18022 auto points = array(map!(CGPoint.fromTuple)(intPoints)); 18023 CGContextAddLines(context, points.ptr, points.length); 18024 CGContextDrawPath(context, CGPathDrawingMode.kCGPathFillStroke); 18025 } 18026 } 18027 18028 mixin template NativeSimpleWindowImplementation() { 18029 void createWindow(int width, int height, string title, OpenGlOptions opengl, SimpleWindow parent) { 18030 synchronized { 18031 if (NSApp == null) initializeApp(); 18032 } 18033 18034 auto contentRect = NSRect(NSPoint(0, 0), NSSize(width, height)); 18035 18036 // create the window. 18037 window = initWithContentRect(alloc("NSWindow"), 18038 contentRect, 18039 NSTitledWindowMask 18040 |NSClosableWindowMask 18041 |NSMiniaturizableWindowMask 18042 |NSResizableWindowMask, 18043 NSBackingStoreBuffered, 18044 true); 18045 18046 // set the title & move the window to center. 18047 auto windowTitle = createCFString(title); 18048 setTitle(window, windowTitle); 18049 CFRelease(windowTitle); 18050 center(window); 18051 18052 // create area to draw on. 18053 auto colorSpace = CGColorSpaceCreateDeviceRGB(); 18054 drawingContext = CGBitmapContextCreate(null, width, height, 18055 8, 4*width, colorSpace, 18056 kCGImageAlphaPremultipliedLast 18057 |kCGBitmapByteOrder32Big); 18058 CGColorSpaceRelease(colorSpace); 18059 CGContextSelectFont(drawingContext, "Lucida Grande", 12.0f, 1); 18060 auto matrix = CGContextGetTextMatrix(drawingContext); 18061 matrix.c = -matrix.c; 18062 matrix.d = -matrix.d; 18063 CGContextSetTextMatrix(drawingContext, matrix); 18064 18065 // create the subview that things will be drawn on. 18066 view = initWithFrame(alloc("SDGraphicsView"), contentRect); 18067 setContentView(window, view); 18068 object_setIvar(view, simpleWindowIvar, cast(id)this); 18069 release(view); 18070 18071 setBackgroundColor(window, whiteNSColor); 18072 makeKeyAndOrderFront(window, null); 18073 } 18074 void dispose() { 18075 closeWindow(); 18076 release(window); 18077 } 18078 void closeWindow() { 18079 invalidate(timer); 18080 .close(window); 18081 } 18082 18083 ScreenPainter getPainter(bool manualInvalidations) { 18084 return ScreenPainter(this, this, manualInvalidations); 18085 } 18086 18087 id window; 18088 id timer; 18089 id view; 18090 CGContextRef drawingContext; 18091 } 18092 18093 extern(C) { 18094 private: 18095 BOOL returnTrue3(id self, SEL _cmd, id app) { 18096 return true; 18097 } 18098 BOOL returnTrue2(id self, SEL _cmd) { 18099 return true; 18100 } 18101 18102 void pulse(id self, SEL _cmd) { 18103 auto simpleWindow = cast(SimpleWindow)object_getIvar(self, simpleWindowIvar); 18104 simpleWindow.handlePulse(); 18105 setNeedsDisplay(self, true); 18106 } 18107 void drawRect(id self, SEL _cmd, NSRect rect) { 18108 auto simpleWindow = cast(SimpleWindow)object_getIvar(self, simpleWindowIvar); 18109 auto curCtx = graphicsPort(currentNSGraphicsContext); 18110 auto cgImage = CGBitmapContextCreateImage(simpleWindow.drawingContext); 18111 auto size = CGSize(CGBitmapContextGetWidth(simpleWindow.drawingContext), 18112 CGBitmapContextGetHeight(simpleWindow.drawingContext)); 18113 CGContextDrawImage(curCtx, CGRect(CGPoint(0, 0), size), cgImage); 18114 CGImageRelease(cgImage); 18115 } 18116 void keyDown(id self, SEL _cmd, id event) { 18117 auto simpleWindow = cast(SimpleWindow)object_getIvar(self, simpleWindowIvar); 18118 18119 // the event may have multiple characters, and we send them all at 18120 // once. 18121 if (simpleWindow.handleCharEvent || simpleWindow.handleKeyEvent) { 18122 auto chars = characters(event); 18123 auto range = CFRange(0, CFStringGetLength(chars)); 18124 auto buffer = new char[range.length*3]; 18125 long actualLength; 18126 CFStringGetBytes(chars, range, kCFStringEncodingUTF8, 0, false, 18127 buffer.ptr, cast(int) buffer.length, &actualLength); 18128 foreach (dchar dc; buffer[0..actualLength]) { 18129 if (simpleWindow.handleCharEvent) 18130 simpleWindow.handleCharEvent(dc); 18131 // NotYetImplementedException 18132 //if (simpleWindow.handleKeyEvent) 18133 //simpleWindow.handleKeyEvent(KeyEvent(dc)); // FIXME: what about keyUp? 18134 } 18135 } 18136 18137 // the event's 'keyCode' is hardware-dependent. I don't think people 18138 // will like it. Let's leave it to the native handler. 18139 18140 // perform the default action. 18141 18142 // so the default action is to make a bomp sound and i dont want that 18143 // sooooooooo yeah not gonna do that. 18144 18145 //auto superData = objc_super(self, superclass(self)); 18146 //alias extern(C) void function(objc_super*, SEL, id) T; 18147 //(cast(T)&objc_msgSendSuper)(&superData, _cmd, event); 18148 } 18149 } 18150 18151 // initialize the app so that it can be interacted with the user. 18152 // based on http://cocoawithlove.com/2010/09/minimalist-cocoa-programming.html 18153 private void initializeApp() { 18154 // push an autorelease pool to avoid leaking. 18155 init(alloc("NSAutoreleasePool")); 18156 18157 // create a new NSApp instance 18158 sharedNSApplication; 18159 setActivationPolicy(NSApp, NSApplicationActivationPolicyRegular); 18160 18161 // create the "Quit" menu. 18162 auto menuBar = init(alloc("NSMenu")); 18163 auto appMenuItem = init(alloc("NSMenuItem")); 18164 addItem(menuBar, appMenuItem); 18165 setMainMenu(NSApp, menuBar); 18166 release(appMenuItem); 18167 release(menuBar); 18168 18169 auto appMenu = init(alloc("NSMenu")); 18170 auto quitTitle = createCFString("Quit"); 18171 auto q = createCFString("q"); 18172 auto quitItem = initWithTitle(alloc("NSMenuItem"), 18173 quitTitle, sel_registerName("terminate:"), q); 18174 addItem(appMenu, quitItem); 18175 setSubmenu(appMenuItem, appMenu); 18176 release(quitItem); 18177 release(appMenu); 18178 CFRelease(q); 18179 CFRelease(quitTitle); 18180 18181 // assign a delegate for the application, allow it to quit when the last 18182 // window is closed. 18183 auto delegateClass = objc_allocateClassPair(objc_getClass("NSObject"), 18184 "SDWindowCloseDelegate", 0); 18185 class_addMethod(delegateClass, 18186 sel_registerName("applicationShouldTerminateAfterLastWindowClosed:"), 18187 &returnTrue3, "c@:@"); 18188 objc_registerClassPair(delegateClass); 18189 18190 auto appDelegate = init(alloc("SDWindowCloseDelegate")); 18191 setDelegate(NSApp, appDelegate); 18192 activateIgnoringOtherApps(NSApp, true); 18193 18194 // create a new view that draws the graphics and respond to keyDown 18195 // events. 18196 auto viewClass = objc_allocateClassPair(objc_getClass("NSView"), 18197 "SDGraphicsView", (void*).sizeof); 18198 class_addIvar(viewClass, "simpledisplay_simpleWindow", 18199 (void*).sizeof, (void*).alignof, "^v"); 18200 class_addMethod(viewClass, sel_registerName("simpledisplay_pulse"), 18201 &pulse, "v@:"); 18202 class_addMethod(viewClass, sel_registerName("drawRect:"), 18203 &drawRect, "v@:{NSRect={NSPoint=ff}{NSSize=ff}}"); 18204 class_addMethod(viewClass, sel_registerName("isFlipped"), 18205 &returnTrue2, "c@:"); 18206 class_addMethod(viewClass, sel_registerName("acceptsFirstResponder"), 18207 &returnTrue2, "c@:"); 18208 class_addMethod(viewClass, sel_registerName("keyDown:"), 18209 &keyDown, "v@:@"); 18210 objc_registerClassPair(viewClass); 18211 simpleWindowIvar = class_getInstanceVariable(viewClass, 18212 "simpledisplay_simpleWindow"); 18213 } 18214 } 18215 18216 version(without_opengl) {} else 18217 extern(System) nothrow @nogc { 18218 //enum uint GL_VERSION = 0x1F02; 18219 //const(char)* glGetString (/*GLenum*/uint); 18220 version(X11) { 18221 static if (!SdpyIsUsingIVGLBinds) { 18222 18223 enum GLX_X_RENDERABLE = 0x8012; 18224 enum GLX_DRAWABLE_TYPE = 0x8010; 18225 enum GLX_RENDER_TYPE = 0x8011; 18226 enum GLX_X_VISUAL_TYPE = 0x22; 18227 enum GLX_TRUE_COLOR = 0x8002; 18228 enum GLX_WINDOW_BIT = 0x00000001; 18229 enum GLX_RGBA_BIT = 0x00000001; 18230 enum GLX_COLOR_INDEX_BIT = 0x00000002; 18231 enum GLX_SAMPLE_BUFFERS = 0x186a0; 18232 enum GLX_SAMPLES = 0x186a1; 18233 enum GLX_CONTEXT_MAJOR_VERSION_ARB = 0x2091; 18234 enum GLX_CONTEXT_MINOR_VERSION_ARB = 0x2092; 18235 } 18236 18237 // GLX_EXT_swap_control 18238 alias glXSwapIntervalEXT = void function (Display* dpy, /*GLXDrawable*/Drawable drawable, int interval); 18239 private __gshared glXSwapIntervalEXT _glx_swapInterval_fn = null; 18240 18241 //k8: ugly code to prevent warnings when sdpy is compiled into .a 18242 extern(System) { 18243 alias glXCreateContextAttribsARB_fna = GLXContext function (Display *dpy, GLXFBConfig config, GLXContext share_context, /*Bool*/int direct, const(int)* attrib_list); 18244 } 18245 private __gshared /*glXCreateContextAttribsARB_fna*/void* glXCreateContextAttribsARBFn = cast(void*)1; //HACK! 18246 18247 // this made public so we don't have to get it again and again 18248 public bool glXCreateContextAttribsARB_present () { 18249 if (glXCreateContextAttribsARBFn is cast(void*)1) { 18250 // get it 18251 glXCreateContextAttribsARBFn = cast(void*)glbindGetProcAddress("glXCreateContextAttribsARB"); 18252 //{ import core.stdc.stdio; printf("checking glXCreateContextAttribsARB: %shere\n", (glXCreateContextAttribsARBFn !is null ? "".ptr : "not ".ptr)); } 18253 } 18254 return (glXCreateContextAttribsARBFn !is null); 18255 } 18256 18257 // this made public so we don't have to get it again and again 18258 public GLXContext glXCreateContextAttribsARB (Display *dpy, GLXFBConfig config, GLXContext share_context, /*Bool*/int direct, const(int)* attrib_list) { 18259 if (!glXCreateContextAttribsARB_present()) assert(0, "glXCreateContextAttribsARB is not present"); 18260 return (cast(glXCreateContextAttribsARB_fna)glXCreateContextAttribsARBFn)(dpy, config, share_context, direct, attrib_list); 18261 } 18262 18263 // extern(C) private __gshared int function(int) glXSwapIntervalSGI; // seems totally redundant to the tohers 18264 extern(C) private __gshared int function(int) glXSwapIntervalMESA; 18265 18266 void glxSetVSync (Display* dpy, /*GLXDrawable*/Drawable drawable, bool wait) { 18267 if (cast(void*)_glx_swapInterval_fn is cast(void*)1) return; 18268 if (_glx_swapInterval_fn is null) { 18269 _glx_swapInterval_fn = cast(glXSwapIntervalEXT)glXGetProcAddress("glXSwapIntervalEXT"); 18270 if (_glx_swapInterval_fn is null) { 18271 _glx_swapInterval_fn = cast(glXSwapIntervalEXT)1; 18272 return; 18273 } 18274 version(sdddd) { debug writeln("glXSwapIntervalEXT found!"); } 18275 } 18276 18277 if(glXSwapIntervalMESA is null) { 18278 // it seems to require both to actually take effect on many computers 18279 // idk why 18280 glXSwapIntervalMESA = cast(typeof(glXSwapIntervalMESA)) glXGetProcAddress("glXSwapIntervalMESA"); 18281 if(glXSwapIntervalMESA is null) 18282 glXSwapIntervalMESA = cast(typeof(glXSwapIntervalMESA)) 1; 18283 } 18284 18285 if(cast(void*) glXSwapIntervalMESA > cast(void*) 1) 18286 glXSwapIntervalMESA(wait ? 1 : 0); 18287 18288 _glx_swapInterval_fn(dpy, drawable, (wait ? 1 : 0)); 18289 } 18290 } else version(Windows) { 18291 static if (!SdpyIsUsingIVGLBinds) { 18292 enum GL_TRUE = 1; 18293 enum GL_FALSE = 0; 18294 alias int GLint; 18295 18296 public void* glbindGetProcAddress (const(char)* name) { 18297 void* res = wglGetProcAddress(name); 18298 if (res is null) { 18299 /+ 18300 //{ import core.stdc.stdio; printf("GL: '%s' not found (0)\n", name); } 18301 import core.sys.windows.windef, core.sys.windows.winbase; 18302 __gshared HINSTANCE dll = null; 18303 if (dll is null) { 18304 dll = LoadLibraryA("opengl32.dll"); 18305 if (dll is null) return null; // <32, but idc 18306 } 18307 res = GetProcAddress(dll, name); 18308 +/ 18309 res = GetProcAddress(gl.libHandle, name); 18310 } 18311 //{ import core.stdc.stdio; printf(" GL: '%s' is 0x%08x\n", name, cast(uint)res); } 18312 return res; 18313 } 18314 } 18315 18316 18317 private __gshared extern(System) BOOL function(int) wglSwapIntervalEXT; 18318 void wglSetVSync(bool wait) { 18319 if(wglSwapIntervalEXT is null) { 18320 wglSwapIntervalEXT = cast(typeof(wglSwapIntervalEXT)) wglGetProcAddress("wglSwapIntervalEXT"); 18321 if(wglSwapIntervalEXT is null) 18322 wglSwapIntervalEXT = cast(typeof(wglSwapIntervalEXT)) 1; 18323 } 18324 if(cast(void*) wglSwapIntervalEXT is cast(void*) 1) 18325 return; 18326 18327 wglSwapIntervalEXT(wait ? 1 : 0); 18328 } 18329 18330 enum WGL_CONTEXT_MAJOR_VERSION_ARB = 0x2091; 18331 enum WGL_CONTEXT_MINOR_VERSION_ARB = 0x2092; 18332 enum WGL_CONTEXT_LAYER_PLANE_ARB = 0x2093; 18333 enum WGL_CONTEXT_FLAGS_ARB = 0x2094; 18334 enum WGL_CONTEXT_PROFILE_MASK_ARB = 0x9126; 18335 18336 enum WGL_CONTEXT_DEBUG_BIT_ARB = 0x0001; 18337 enum WGL_CONTEXT_FORWARD_COMPATIBLE_BIT_ARB = 0x0002; 18338 18339 enum WGL_CONTEXT_CORE_PROFILE_BIT_ARB = 0x00000001; 18340 enum WGL_CONTEXT_COMPATIBILITY_PROFILE_BIT_ARB = 0x00000002; 18341 18342 alias wglCreateContextAttribsARB_fna = HGLRC function (HDC hDC, HGLRC hShareContext, const(int)* attribList); 18343 __gshared wglCreateContextAttribsARB_fna wglCreateContextAttribsARB = null; 18344 18345 void wglInitOtherFunctions () { 18346 if (wglCreateContextAttribsARB is null) { 18347 wglCreateContextAttribsARB = cast(wglCreateContextAttribsARB_fna)glbindGetProcAddress("wglCreateContextAttribsARB"); 18348 } 18349 } 18350 } 18351 18352 static if (!SdpyIsUsingIVGLBinds) { 18353 18354 interface GL { 18355 extern(System) @nogc nothrow: 18356 18357 void glGetIntegerv(int, void*); 18358 void glMatrixMode(int); 18359 void glPushMatrix(); 18360 void glLoadIdentity(); 18361 void glOrtho(double, double, double, double, double, double); 18362 void glFrustum(double, double, double, double, double, double); 18363 18364 void glPopMatrix(); 18365 void glEnable(int); 18366 void glDisable(int); 18367 void glClear(int); 18368 void glBegin(int); 18369 void glVertex2f(float, float); 18370 void glVertex3f(float, float, float); 18371 void glEnd(); 18372 void glColor3b(byte, byte, byte); 18373 void glColor3ub(ubyte, ubyte, ubyte); 18374 void glColor4b(byte, byte, byte, byte); 18375 void glColor4ub(ubyte, ubyte, ubyte, ubyte); 18376 void glColor3i(int, int, int); 18377 void glColor3ui(uint, uint, uint); 18378 void glColor4i(int, int, int, int); 18379 void glColor4ui(uint, uint, uint, uint); 18380 void glColor3f(float, float, float); 18381 void glColor4f(float, float, float, float); 18382 void glTranslatef(float, float, float); 18383 void glScalef(float, float, float); 18384 version(X11) { 18385 void glSecondaryColor3b(byte, byte, byte); 18386 void glSecondaryColor3ub(ubyte, ubyte, ubyte); 18387 void glSecondaryColor3i(int, int, int); 18388 void glSecondaryColor3ui(uint, uint, uint); 18389 void glSecondaryColor3f(float, float, float); 18390 } 18391 18392 void glDrawElements(int, int, int, void*); 18393 18394 void glRotatef(float, float, float, float); 18395 18396 uint glGetError(); 18397 18398 void glDeleteTextures(int, uint*); 18399 18400 18401 void glRasterPos2i(int, int); 18402 void glDrawPixels(int, int, uint, uint, void*); 18403 void glClearColor(float, float, float, float); 18404 18405 18406 void glPixelStorei(uint, int); 18407 18408 void glGenTextures(uint, uint*); 18409 void glBindTexture(int, int); 18410 void glTexParameteri(uint, uint, int); 18411 void glTexParameterf(uint/*GLenum*/ target, uint/*GLenum*/ pname, float param); 18412 void glTexImage2D(int, int, int, int, int, int, int, int, scope const void*); 18413 void glTexSubImage2D(uint/*GLenum*/ target, int level, int xoffset, int yoffset, 18414 /*GLsizei*/int width, /*GLsizei*/int height, 18415 uint/*GLenum*/ format, uint/*GLenum*/ type, scope const void* pixels); 18416 void glTexEnvf(uint/*GLenum*/ target, uint/*GLenum*/ pname, float param); 18417 18418 void glLineWidth(int); 18419 18420 18421 void glTexCoord2f(float, float); 18422 void glVertex2i(int, int); 18423 void glBlendFunc (int, int); 18424 void glDepthFunc (int); 18425 void glViewport(int, int, int, int); 18426 18427 void glClearDepth(double); 18428 18429 void glReadBuffer(uint); 18430 void glReadPixels(int, int, int, int, int, int, void*); 18431 18432 void glFlush(); 18433 void glFinish(); 18434 18435 version(Windows) { 18436 BOOL wglCopyContext(HGLRC, HGLRC, UINT); 18437 HGLRC wglCreateContext(HDC); 18438 HGLRC wglCreateLayerContext(HDC, int); 18439 BOOL wglDeleteContext(HGLRC); 18440 BOOL wglDescribeLayerPlane(HDC, int, int, UINT, LPLAYERPLANEDESCRIPTOR); 18441 HGLRC wglGetCurrentContext(); 18442 HDC wglGetCurrentDC(); 18443 int wglGetLayerPaletteEntries(HDC, int, int, int, COLORREF*); 18444 PROC wglGetProcAddress(LPCSTR); 18445 BOOL wglMakeCurrent(HDC, HGLRC); 18446 BOOL wglRealizeLayerPalette(HDC, int, BOOL); 18447 int wglSetLayerPaletteEntries(HDC, int, int, int, const(COLORREF)*); 18448 BOOL wglShareLists(HGLRC, HGLRC); 18449 BOOL wglSwapLayerBuffers(HDC, UINT); 18450 BOOL wglUseFontBitmapsA(HDC, DWORD, DWORD, DWORD); 18451 BOOL wglUseFontBitmapsW(HDC, DWORD, DWORD, DWORD); 18452 BOOL wglUseFontOutlinesA(HDC, DWORD, DWORD, DWORD, FLOAT, FLOAT, int, LPGLYPHMETRICSFLOAT); 18453 BOOL wglUseFontOutlinesW(HDC, DWORD, DWORD, DWORD, FLOAT, FLOAT, int, LPGLYPHMETRICSFLOAT); 18454 } 18455 18456 } 18457 18458 interface GL3 { 18459 extern(System) @nogc nothrow: 18460 18461 void glGenVertexArrays(GLsizei, GLuint*); 18462 void glBindVertexArray(GLuint); 18463 void glDeleteVertexArrays(GLsizei, const(GLuint)*); 18464 void glGenerateMipmap(GLenum); 18465 void glBufferSubData(GLenum, GLintptr, GLsizeiptr, const(GLvoid)*); 18466 void glStencilMask(GLuint); 18467 void glStencilFunc(GLenum, GLint, GLuint); 18468 void glGetShaderInfoLog(GLuint, GLsizei, GLsizei*, GLchar*); 18469 void glGetProgramInfoLog(GLuint, GLsizei, GLsizei*, GLchar*); 18470 GLuint glCreateProgram(); 18471 GLuint glCreateShader(GLenum); 18472 void glShaderSource(GLuint, GLsizei, const(GLchar*)*, const(GLint)*); 18473 void glCompileShader(GLuint); 18474 void glGetShaderiv(GLuint, GLenum, GLint*); 18475 void glAttachShader(GLuint, GLuint); 18476 void glBindAttribLocation(GLuint, GLuint, const(GLchar)*); 18477 void glLinkProgram(GLuint); 18478 void glGetProgramiv(GLuint, GLenum, GLint*); 18479 void glDeleteProgram(GLuint); 18480 void glDeleteShader(GLuint); 18481 GLint glGetUniformLocation(GLuint, const(GLchar)*); 18482 void glGenBuffers(GLsizei, GLuint*); 18483 18484 void glUniform1f(GLint location, GLfloat v0); 18485 void glUniform2f(GLint location, GLfloat v0, GLfloat v1); 18486 void glUniform3f(GLint location, GLfloat v0, GLfloat v1, GLfloat v2); 18487 void glUniform4f(GLint location, GLfloat v0, GLfloat v1, GLfloat v2, GLfloat v3); 18488 void glUniform1i(GLint location, GLint v0); 18489 void glUniform2i(GLint location, GLint v0, GLint v1); 18490 void glUniform3i(GLint location, GLint v0, GLint v1, GLint v2); 18491 void glUniform4i(GLint location, GLint v0, GLint v1, GLint v2, GLint v3); 18492 void glUniform1ui(GLint location, GLuint v0); 18493 void glUniform2ui(GLint location, GLuint v0, GLuint v1); 18494 void glUniform3ui(GLint location, GLuint v0, GLuint v1, GLuint v2); 18495 void glUniform4ui(GLint location, GLuint v0, GLuint v1, GLuint v2, GLuint v3); 18496 void glUniform1fv(GLint location, GLsizei count, const GLfloat *value); 18497 void glUniform2fv(GLint location, GLsizei count, const GLfloat *value); 18498 void glUniform3fv(GLint location, GLsizei count, const GLfloat *value); 18499 void glUniform4fv(GLint location, GLsizei count, const GLfloat *value); 18500 void glUniform1iv(GLint location, GLsizei count, const GLint *value); 18501 void glUniform2iv(GLint location, GLsizei count, const GLint *value); 18502 void glUniform3iv(GLint location, GLsizei count, const GLint *value); 18503 void glUniform4iv(GLint location, GLsizei count, const GLint *value); 18504 void glUniform1uiv(GLint location, GLsizei count, const GLuint *value); 18505 void glUniform2uiv(GLint location, GLsizei count, const GLuint *value); 18506 void glUniform3uiv(GLint location, GLsizei count, const GLuint *value); 18507 void glUniform4uiv(GLint location, GLsizei count, const GLuint *value); 18508 void glUniformMatrix2fv(GLint location, GLsizei count, GLboolean transpose, const GLfloat *value); 18509 void glUniformMatrix3fv(GLint location, GLsizei count, GLboolean transpose, const GLfloat *value); 18510 void glUniformMatrix4fv(GLint location, GLsizei count, GLboolean transpose, const GLfloat *value); 18511 void glUniformMatrix2x3fv(GLint location, GLsizei count, GLboolean transpose, const GLfloat *value); 18512 void glUniformMatrix3x2fv(GLint location, GLsizei count, GLboolean transpose, const GLfloat *value); 18513 void glUniformMatrix2x4fv(GLint location, GLsizei count, GLboolean transpose, const GLfloat *value); 18514 void glUniformMatrix4x2fv(GLint location, GLsizei count, GLboolean transpose, const GLfloat *value); 18515 void glUniformMatrix3x4fv(GLint location, GLsizei count, GLboolean transpose, const GLfloat *value); 18516 void glUniformMatrix4x3fv(GLint location, GLsizei count, GLboolean transpose, const GLfloat *value); 18517 18518 void glColorMask(GLboolean, GLboolean, GLboolean, GLboolean); 18519 void glStencilOpSeparate(GLenum, GLenum, GLenum, GLenum); 18520 void glDrawArrays(GLenum, GLint, GLsizei); 18521 void glStencilOp(GLenum, GLenum, GLenum); 18522 void glUseProgram(GLuint); 18523 void glCullFace(GLenum); 18524 void glFrontFace(GLenum); 18525 void glActiveTexture(GLenum); 18526 void glBindBuffer(GLenum, GLuint); 18527 void glBufferData(GLenum, GLsizeiptr, const(void)*, GLenum); 18528 void glEnableVertexAttribArray(GLuint); 18529 void glVertexAttribPointer(GLuint, GLint, GLenum, GLboolean, GLsizei, const(void)*); 18530 void glUniform1i(GLint, GLint); 18531 void glUniform2fv(GLint, GLsizei, const(GLfloat)*); 18532 void glDisableVertexAttribArray(GLuint); 18533 void glDeleteBuffers(GLsizei, const(GLuint)*); 18534 void glBlendFuncSeparate(GLenum, GLenum, GLenum, GLenum); 18535 void glLogicOp (GLenum opcode); 18536 void glFramebufferTexture2D (GLenum target, GLenum attachment, GLenum textarget, GLuint texture, GLint level); 18537 void glDeleteFramebuffers (GLsizei n, const(GLuint)* framebuffers); 18538 void glGenFramebuffers (GLsizei n, GLuint* framebuffers); 18539 GLenum glCheckFramebufferStatus (GLenum target); 18540 void glBindFramebuffer (GLenum target, GLuint framebuffer); 18541 } 18542 18543 interface GL4 { 18544 extern(System) @nogc nothrow: 18545 18546 void glTextureSubImage2D(uint texture, int level, int xoffset, int yoffset, 18547 /*GLsizei*/int width, /*GLsizei*/int height, 18548 uint/*GLenum*/ format, uint/*GLenum*/ type, scope const void* pixels); 18549 } 18550 18551 interface GLU { 18552 extern(System) @nogc nothrow: 18553 18554 void gluLookAt(double, double, double, double, double, double, double, double, double); 18555 void gluPerspective(double, double, double, double); 18556 18557 char* gluErrorString(uint); 18558 } 18559 18560 18561 enum GL_RED = 0x1903; 18562 enum GL_ALPHA = 0x1906; 18563 18564 enum uint GL_FRONT = 0x0404; 18565 18566 enum uint GL_BLEND = 0x0be2; 18567 enum uint GL_LEQUAL = 0x0203; 18568 18569 18570 enum uint GL_RGB = 0x1907; 18571 enum uint GL_BGRA = 0x80e1; 18572 enum uint GL_RGBA = 0x1908; 18573 enum uint GL_TEXTURE_2D = 0x0DE1; 18574 enum uint GL_TEXTURE_MIN_FILTER = 0x2801; 18575 enum uint GL_NEAREST = 0x2600; 18576 enum uint GL_LINEAR = 0x2601; 18577 enum uint GL_TEXTURE_MAG_FILTER = 0x2800; 18578 enum uint GL_TEXTURE_WRAP_S = 0x2802; 18579 enum uint GL_TEXTURE_WRAP_T = 0x2803; 18580 enum uint GL_REPEAT = 0x2901; 18581 enum uint GL_CLAMP = 0x2900; 18582 enum uint GL_CLAMP_TO_EDGE = 0x812F; 18583 enum uint GL_CLAMP_TO_BORDER = 0x812D; 18584 enum uint GL_DECAL = 0x2101; 18585 enum uint GL_MODULATE = 0x2100; 18586 enum uint GL_TEXTURE_ENV = 0x2300; 18587 enum uint GL_TEXTURE_ENV_MODE = 0x2200; 18588 enum uint GL_REPLACE = 0x1E01; 18589 enum uint GL_LIGHTING = 0x0B50; 18590 enum uint GL_DITHER = 0x0BD0; 18591 18592 enum uint GL_NO_ERROR = 0; 18593 18594 18595 18596 enum int GL_VIEWPORT = 0x0BA2; 18597 enum int GL_MODELVIEW = 0x1700; 18598 enum int GL_TEXTURE = 0x1702; 18599 enum int GL_PROJECTION = 0x1701; 18600 enum int GL_DEPTH_TEST = 0x0B71; 18601 18602 enum int GL_COLOR_BUFFER_BIT = 0x00004000; 18603 enum int GL_ACCUM_BUFFER_BIT = 0x00000200; 18604 enum int GL_DEPTH_BUFFER_BIT = 0x00000100; 18605 enum uint GL_STENCIL_BUFFER_BIT = 0x00000400; 18606 18607 enum int GL_POINTS = 0x0000; 18608 enum int GL_LINES = 0x0001; 18609 enum int GL_LINE_LOOP = 0x0002; 18610 enum int GL_LINE_STRIP = 0x0003; 18611 enum int GL_TRIANGLES = 0x0004; 18612 enum int GL_TRIANGLE_STRIP = 5; 18613 enum int GL_TRIANGLE_FAN = 6; 18614 enum int GL_QUADS = 7; 18615 enum int GL_QUAD_STRIP = 8; 18616 enum int GL_POLYGON = 9; 18617 18618 alias GLvoid = void; 18619 alias GLboolean = ubyte; 18620 alias GLuint = uint; 18621 alias GLenum = uint; 18622 alias GLchar = char; 18623 alias GLsizei = int; 18624 alias GLfloat = float; 18625 alias GLintptr = size_t; 18626 alias GLsizeiptr = ptrdiff_t; 18627 18628 18629 enum uint GL_INVALID_ENUM = 0x0500; 18630 18631 enum uint GL_ZERO = 0; 18632 enum uint GL_ONE = 1; 18633 18634 enum uint GL_BYTE = 0x1400; 18635 enum uint GL_UNSIGNED_BYTE = 0x1401; 18636 enum uint GL_SHORT = 0x1402; 18637 enum uint GL_UNSIGNED_SHORT = 0x1403; 18638 enum uint GL_INT = 0x1404; 18639 enum uint GL_UNSIGNED_INT = 0x1405; 18640 enum uint GL_FLOAT = 0x1406; 18641 enum uint GL_2_BYTES = 0x1407; 18642 enum uint GL_3_BYTES = 0x1408; 18643 enum uint GL_4_BYTES = 0x1409; 18644 enum uint GL_DOUBLE = 0x140A; 18645 18646 enum uint GL_STREAM_DRAW = 0x88E0; 18647 18648 enum uint GL_CCW = 0x0901; 18649 18650 enum uint GL_STENCIL_TEST = 0x0B90; 18651 enum uint GL_SCISSOR_TEST = 0x0C11; 18652 18653 enum uint GL_EQUAL = 0x0202; 18654 enum uint GL_NOTEQUAL = 0x0205; 18655 18656 enum uint GL_ALWAYS = 0x0207; 18657 enum uint GL_KEEP = 0x1E00; 18658 18659 enum uint GL_INCR = 0x1E02; 18660 18661 enum uint GL_INCR_WRAP = 0x8507; 18662 enum uint GL_DECR_WRAP = 0x8508; 18663 18664 enum uint GL_CULL_FACE = 0x0B44; 18665 enum uint GL_BACK = 0x0405; 18666 18667 enum uint GL_FRAGMENT_SHADER = 0x8B30; 18668 enum uint GL_VERTEX_SHADER = 0x8B31; 18669 18670 enum uint GL_COMPILE_STATUS = 0x8B81; 18671 enum uint GL_LINK_STATUS = 0x8B82; 18672 18673 enum uint GL_ELEMENT_ARRAY_BUFFER = 0x8893; 18674 18675 enum uint GL_STATIC_DRAW = 0x88E4; 18676 18677 enum uint GL_UNPACK_ALIGNMENT = 0x0CF5; 18678 enum uint GL_UNPACK_ROW_LENGTH = 0x0CF2; 18679 enum uint GL_UNPACK_SKIP_PIXELS = 0x0CF4; 18680 enum uint GL_UNPACK_SKIP_ROWS = 0x0CF3; 18681 18682 enum uint GL_GENERATE_MIPMAP = 0x8191; 18683 enum uint GL_LINEAR_MIPMAP_LINEAR = 0x2703; 18684 18685 enum uint GL_TEXTURE0 = 0x84C0U; 18686 enum uint GL_TEXTURE1 = 0x84C1U; 18687 18688 enum uint GL_ARRAY_BUFFER = 0x8892; 18689 18690 enum uint GL_SRC_COLOR = 0x0300; 18691 enum uint GL_ONE_MINUS_SRC_COLOR = 0x0301; 18692 enum uint GL_SRC_ALPHA = 0x0302; 18693 enum uint GL_ONE_MINUS_SRC_ALPHA = 0x0303; 18694 enum uint GL_DST_ALPHA = 0x0304; 18695 enum uint GL_ONE_MINUS_DST_ALPHA = 0x0305; 18696 enum uint GL_DST_COLOR = 0x0306; 18697 enum uint GL_ONE_MINUS_DST_COLOR = 0x0307; 18698 enum uint GL_SRC_ALPHA_SATURATE = 0x0308; 18699 18700 enum uint GL_INVERT = 0x150AU; 18701 18702 enum uint GL_DEPTH_STENCIL = 0x84F9U; 18703 enum uint GL_UNSIGNED_INT_24_8 = 0x84FAU; 18704 18705 enum uint GL_FRAMEBUFFER = 0x8D40U; 18706 enum uint GL_COLOR_ATTACHMENT0 = 0x8CE0U; 18707 enum uint GL_DEPTH_STENCIL_ATTACHMENT = 0x821AU; 18708 18709 enum uint GL_FRAMEBUFFER_COMPLETE = 0x8CD5U; 18710 enum uint GL_FRAMEBUFFER_INCOMPLETE_ATTACHMENT = 0x8CD6U; 18711 enum uint GL_FRAMEBUFFER_INCOMPLETE_MISSING_ATTACHMENT = 0x8CD7U; 18712 enum uint GL_FRAMEBUFFER_INCOMPLETE_DIMENSIONS = 0x8CD9U; 18713 enum uint GL_FRAMEBUFFER_UNSUPPORTED = 0x8CDDU; 18714 18715 enum uint GL_COLOR_LOGIC_OP = 0x0BF2U; 18716 enum uint GL_CLEAR = 0x1500U; 18717 enum uint GL_COPY = 0x1503U; 18718 enum uint GL_XOR = 0x1506U; 18719 18720 enum uint GL_FRAMEBUFFER_BINDING = 0x8CA6U; 18721 18722 enum uint GL_TEXTURE_LOD_BIAS = 0x8501; 18723 18724 } 18725 } 18726 18727 /++ 18728 History: 18729 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. 18730 +/ 18731 __gshared bool gluSuccessfullyLoaded = true; 18732 18733 version(without_opengl) {} else { 18734 static if(!SdpyIsUsingIVGLBinds) { 18735 version(Windows) { 18736 mixin DynamicLoad!(GL, "opengl32", 1, openGlLibrariesSuccessfullyLoaded) gl; 18737 mixin DynamicLoad!(GLU, "glu32", 1, gluSuccessfullyLoaded) glu; 18738 } else { 18739 mixin DynamicLoad!(GL, "GL", 1, openGlLibrariesSuccessfullyLoaded) gl; 18740 mixin DynamicLoad!(GLU, "GLU", 3, gluSuccessfullyLoaded) glu; 18741 } 18742 mixin DynamicLoadSupplementalOpenGL!(GL3) gl3; 18743 18744 18745 shared static this() { 18746 gl.loadDynamicLibrary(); 18747 18748 // FIXME: this is NOT actually required and should NOT fail if it is not loaded 18749 // unless those functions are actually used 18750 // go to mark b openGlLibrariesSuccessfullyLoaded = false; 18751 glu.loadDynamicLibrary(); 18752 } 18753 } 18754 } 18755 18756 /++ 18757 Convenience method for converting D arrays to opengl buffer data 18758 18759 I would LOVE to overload it with the original glBufferData, but D won't 18760 let me since glBufferData is a function pointer :( 18761 18762 Added: August 25, 2020 (version 8.5) 18763 +/ 18764 version(without_opengl) {} else 18765 void glBufferDataSlice(GLenum target, const(void[]) data, GLenum usage) { 18766 glBufferData(target, data.length, data.ptr, usage); 18767 } 18768 18769 /+ 18770 /++ 18771 A matrix for simple uses that easily integrates with [OpenGlShader]. 18772 18773 Might not be useful to you since it only as some simple functions and 18774 probably isn't that fast. 18775 18776 Note it uses an inline static array for its storage, so copying it 18777 may be expensive. 18778 +/ 18779 struct BasicMatrix(int columns, int rows, T = float) { 18780 import core.stdc.math; 18781 18782 T[columns * rows] data = 0.0; 18783 18784 /++ 18785 Basic operations that operate *in place*. 18786 +/ 18787 void translate() { 18788 18789 } 18790 18791 /// ditto 18792 void scale() { 18793 18794 } 18795 18796 /// ditto 18797 void rotate() { 18798 18799 } 18800 18801 /++ 18802 18803 +/ 18804 static if(columns == rows) 18805 static BasicMatrix identity() { 18806 BasicMatrix m; 18807 foreach(i; 0 .. columns) 18808 data[0 + i + i * columns] = 1.0; 18809 return m; 18810 } 18811 18812 static BasicMatrix ortho() { 18813 return BasicMatrix.init; 18814 } 18815 } 18816 +/ 18817 18818 /++ 18819 Convenience class for using opengl shaders. 18820 18821 Ensure that you've loaded opengl 3+ and set your active 18822 context before trying to use this. 18823 18824 Added: August 25, 2020 (version 8.5) 18825 +/ 18826 version(without_opengl) {} else 18827 final class OpenGlShader { 18828 private int shaderProgram_; 18829 private @property void shaderProgram(int a) { 18830 shaderProgram_ = a; 18831 } 18832 /// Get the program ID for use in OpenGL functions. 18833 public @property int shaderProgram() { 18834 return shaderProgram_; 18835 } 18836 18837 /++ 18838 18839 +/ 18840 static struct Source { 18841 uint type; /// GL_FRAGMENT_SHADER, GL_VERTEX_SHADER, etc. 18842 string code; /// 18843 } 18844 18845 /++ 18846 Helper method to just compile some shader code and check for errors 18847 while you do glCreateShader, etc. on the outside yourself. 18848 18849 This just does `glShaderSource` and `glCompileShader` for the given code. 18850 18851 If you the OpenGlShader class constructor, you never need to call this yourself. 18852 +/ 18853 static void compile(int sid, Source code) { 18854 const(char)*[1] buffer; 18855 int[1] lengthBuffer; 18856 18857 buffer[0] = code.code.ptr; 18858 lengthBuffer[0] = cast(int) code.code.length; 18859 18860 glShaderSource(sid, cast(int) buffer.length, buffer.ptr, lengthBuffer.ptr); 18861 glCompileShader(sid); 18862 18863 int success; 18864 glGetShaderiv(sid, GL_COMPILE_STATUS, &success); 18865 if(!success) { 18866 char[512] info; 18867 int len; 18868 glGetShaderInfoLog(sid, info.length, &len, info.ptr); 18869 18870 throw new Exception("Shader compile failure: " ~ cast(immutable) info[0 .. len]); 18871 } 18872 } 18873 18874 /++ 18875 Calls `glLinkProgram` and throws if error a occurs. 18876 18877 If you the OpenGlShader class constructor, you never need to call this yourself. 18878 +/ 18879 static void link(int shaderProgram) { 18880 glLinkProgram(shaderProgram); 18881 int success; 18882 glGetProgramiv(shaderProgram, GL_LINK_STATUS, &success); 18883 if(!success) { 18884 char[512] info; 18885 int len; 18886 glGetProgramInfoLog(shaderProgram, info.length, &len, info.ptr); 18887 18888 throw new Exception("Shader link failure: " ~ cast(immutable) info[0 .. len]); 18889 } 18890 } 18891 18892 /++ 18893 Constructs the shader object by calling `glCreateProgram`, then 18894 compiling each given [Source], and finally, linking them together. 18895 18896 Throws: on compile or link failure. 18897 +/ 18898 this(Source[] codes...) { 18899 shaderProgram = glCreateProgram(); 18900 18901 int[16] shadersBufferStack; 18902 18903 int[] shadersBuffer = codes.length <= shadersBufferStack.length ? 18904 shadersBufferStack[0 .. codes.length] : 18905 new int[](codes.length); 18906 18907 foreach(idx, code; codes) { 18908 shadersBuffer[idx] = glCreateShader(code.type); 18909 18910 compile(shadersBuffer[idx], code); 18911 18912 glAttachShader(shaderProgram, shadersBuffer[idx]); 18913 } 18914 18915 link(shaderProgram); 18916 18917 foreach(s; shadersBuffer) 18918 glDeleteShader(s); 18919 } 18920 18921 /// Calls `glUseProgram(this.shaderProgram)` 18922 void use() { 18923 glUseProgram(this.shaderProgram); 18924 } 18925 18926 /// Deletes the program. 18927 void delete_() { 18928 glDeleteProgram(shaderProgram); 18929 shaderProgram = 0; 18930 } 18931 18932 /++ 18933 [OpenGlShader.uniforms].name gives you one of these. 18934 18935 You can get the id out of it or just assign 18936 +/ 18937 static struct Uniform { 18938 /// the id passed to glUniform* 18939 int id; 18940 18941 /// Assigns the 4 floats. You will probably have to call this via the .opAssign name 18942 void opAssign(float x, float y, float z, float w) { 18943 if(id != -1) 18944 glUniform4f(id, x, y, z, w); 18945 } 18946 18947 void opAssign(float x) { 18948 if(id != -1) 18949 glUniform1f(id, x); 18950 } 18951 18952 void opAssign(float x, float y) { 18953 if(id != -1) 18954 glUniform2f(id, x, y); 18955 } 18956 18957 void opAssign(T)(T t) { 18958 t.glUniform(id); 18959 } 18960 } 18961 18962 static struct UniformsHelper { 18963 OpenGlShader _shader; 18964 18965 @property Uniform opDispatch(string name)() { 18966 auto i = glGetUniformLocation(_shader.shaderProgram, name.ptr); 18967 // FIXME: decide what to do here; the exception is liable to be swallowed by the event syste 18968 //if(i == -1) 18969 //throw new Exception("Could not find uniform " ~ name); 18970 return Uniform(i); 18971 } 18972 18973 @property void opDispatch(string name, T)(T t) { 18974 Uniform f = this.opDispatch!name; 18975 t.glUniform(f); 18976 } 18977 } 18978 18979 /++ 18980 Gives access to the uniforms through dot access. 18981 `OpenGlShader.Uniform = shader.uniforms.foo; // calls glGetUniformLocation(this, "foo"); 18982 +/ 18983 @property UniformsHelper uniforms() { return UniformsHelper(this); } 18984 } 18985 18986 version(without_opengl) {} else { 18987 /++ 18988 A static container of experimental types and value constructors for opengl 3+ shaders. 18989 18990 18991 You can declare variables like: 18992 18993 ``` 18994 OGL.vec3f something; 18995 ``` 18996 18997 But generally it would be used with [OpenGlShader]'s uniform helpers like 18998 18999 ``` 19000 shader.uniforms.mouse = OGL.vec(mouseX, mouseY); // or OGL.vec2f if you want to be more specific 19001 ``` 19002 19003 This is still extremely experimental, not very useful at this point, and thus subject to change at random. 19004 19005 19006 History: 19007 Added December 7, 2021. Not yet stable. 19008 +/ 19009 final class OGL { 19010 static: 19011 19012 private template typeFromSpecifier(string specifier) { 19013 static if(specifier == "f") 19014 alias typeFromSpecifier = GLfloat; 19015 else static if(specifier == "i") 19016 alias typeFromSpecifier = GLint; 19017 else static if(specifier == "ui") 19018 alias typeFromSpecifier = GLuint; 19019 else static assert(0, "I don't know this ogl type suffix " ~ specifier); 19020 } 19021 19022 private template CommonType(T...) { 19023 static if(T.length == 1) 19024 alias CommonType = T[0]; 19025 else static if(is(typeof(true ? T[0].init : T[1].init) C)) 19026 alias CommonType = CommonType!(C, T[2 .. $]); 19027 } 19028 19029 private template typesToSpecifier(T...) { 19030 static if(is(CommonType!T == float)) 19031 enum typesToSpecifier = "f"; 19032 else static if(is(CommonType!T == int)) 19033 enum typesToSpecifier = "i"; 19034 else static if(is(CommonType!T == uint)) 19035 enum typesToSpecifier = "ui"; 19036 else static assert(0, "I can't find a gl type suffix for common type " ~ CommonType!T.stringof); 19037 } 19038 19039 private template genNames(size_t dim, size_t dim2 = 0) { 19040 string helper() { 19041 string s; 19042 if(dim2) { 19043 s ~= "type["~(dim + '0')~"]["~(dim2 + '0')~"] matrix;"; 19044 } else { 19045 if(dim > 0) s ~= "type x = 0;"; 19046 if(dim > 1) s ~= "type y = 0;"; 19047 if(dim > 2) s ~= "type z = 0;"; 19048 if(dim > 3) s ~= "type w = 0;"; 19049 } 19050 return s; 19051 } 19052 19053 enum genNames = helper(); 19054 } 19055 19056 // there's vec, arrays of vec, mat, and arrays of mat 19057 template opDispatch(string name) 19058 if(name.length > 4 && (name[0 .. 3] == "vec" || name[0 .. 3] == "mat")) 19059 { 19060 static if(name[4] == 'x') { 19061 enum dimX = cast(int) (name[3] - '0'); 19062 static assert(dimX > 0 && dimX <= 4, "Bad dimension for OGL X type " ~ name[3]); 19063 19064 enum dimY = cast(int) (name[5] - '0'); 19065 static assert(dimY > 0 && dimY <= 4, "Bad dimension for OGL Y type " ~ name[5]); 19066 19067 enum isArray = name[$ - 1] == 'v'; 19068 enum typeSpecifier = isArray ? name[6 .. $ - 1] : name[6 .. $]; 19069 alias type = typeFromSpecifier!typeSpecifier; 19070 } else { 19071 enum dim = cast(int) (name[3] - '0'); 19072 static assert(dim > 0 && dim <= 4, "Bad dimension for OGL type " ~ name[3]); 19073 enum isArray = name[$ - 1] == 'v'; 19074 enum typeSpecifier = isArray ? name[4 .. $ - 1] : name[4 .. $]; 19075 alias type = typeFromSpecifier!typeSpecifier; 19076 } 19077 19078 align(1) 19079 struct opDispatch { 19080 align(1): 19081 static if(name[4] == 'x') 19082 mixin(genNames!(dimX, dimY)); 19083 else 19084 mixin(genNames!dim); 19085 19086 private void glUniform(OpenGlShader.Uniform assignTo) { 19087 glUniform(assignTo.id); 19088 } 19089 private void glUniform(int assignTo) { 19090 static if(name[4] == 'x') { 19091 // FIXME 19092 pragma(msg, "This matrix uniform helper has never been tested!!!!"); 19093 mixin("glUniformMatrix" ~ name[3 .. $] ~ "v")(assignTo, dimX * dimY, false, this.matrix.ptr); 19094 } else 19095 mixin("glUniform" ~ name[3 .. $])(assignTo, this.tupleof); 19096 } 19097 } 19098 } 19099 19100 auto vec(T...)(T members) { 19101 return typeof(this).opDispatch!("vec" ~ toInternal!string(cast(int) T.length)~ typesToSpecifier!T)(members); 19102 } 19103 } 19104 } 19105 19106 version(linux) { 19107 version(with_eventloop) {} else { 19108 private int epollFd = -1; 19109 void prepareEventLoop() { 19110 if(epollFd != -1) 19111 return; // already initialized, no need to do it again 19112 import ep = core.sys.linux.epoll; 19113 19114 epollFd = ep.epoll_create1(ep.EPOLL_CLOEXEC); 19115 if(epollFd == -1) 19116 throw new Exception("epoll create failure"); 19117 } 19118 } 19119 } else version(Posix) { 19120 void prepareEventLoop() {} 19121 } 19122 19123 version(X11) { 19124 import core.stdc.locale : LC_ALL; // rdmd fix 19125 __gshared bool sdx_isUTF8Locale; 19126 19127 // This whole crap is used to initialize X11 locale, so that you can use XIM methods later. 19128 // Yes, there are people with non-utf locale (it's me, Ketmar!), but XIM (composing) will 19129 // not work right if app/X11 locale is not utf. This sux. That's why all that "utf detection" 19130 // anal magic is here. I (Ketmar) hope you like it. 19131 // We will use `sdx_isUTF8Locale` on XIM creation to enforce UTF-8 locale, so XCompose will 19132 // always return correct unicode symbols. The detection is here 'cause user can change locale 19133 // later. 19134 19135 // NOTE: IT IS VERY IMPORTANT THAT THIS BE THE LAST STATIC CTOR OF THE FILE since it tests librariesSuccessfullyLoaded 19136 shared static this () { 19137 if(!librariesSuccessfullyLoaded) 19138 return; 19139 19140 import core.stdc.locale : setlocale, LC_ALL, LC_CTYPE; 19141 19142 // this doesn't hurt; it may add some locking, but the speed is still 19143 // allows doing 60 FPS videogames; also, ignore the result, as most 19144 // users will probably won't do mulththreaded X11 anyway (and I (ketmar) 19145 // never seen this failing). 19146 if (XInitThreads() == 0) { import core.stdc.stdio; fprintf(stderr, "XInitThreads() failed!\n"); } 19147 19148 setlocale(LC_ALL, ""); 19149 // check if out locale is UTF-8 19150 auto lct = setlocale(LC_CTYPE, null); 19151 if (lct is null) { 19152 sdx_isUTF8Locale = false; 19153 } else { 19154 for (size_t idx = 0; lct[idx] && lct[idx+1] && lct[idx+2]; ++idx) { 19155 if ((lct[idx+0] == 'u' || lct[idx+0] == 'U') && 19156 (lct[idx+1] == 't' || lct[idx+1] == 'T') && 19157 (lct[idx+2] == 'f' || lct[idx+2] == 'F')) 19158 { 19159 sdx_isUTF8Locale = true; 19160 break; 19161 } 19162 } 19163 } 19164 //{ import core.stdc.stdio : stderr, fprintf; fprintf(stderr, "UTF8: %s\n", sdx_isUTF8Locale ? "tan".ptr : "ona".ptr); } 19165 } 19166 } 19167 19168 class ExperimentalTextComponent2 { 19169 /+ 19170 Stage 1: get it working monospace 19171 Stage 2: use proportional font 19172 Stage 3: allow changes in inline style 19173 Stage 4: allow new fonts and sizes in the middle 19174 Stage 5: optimize gap buffer 19175 Stage 6: optimize layout 19176 Stage 7: word wrap 19177 Stage 8: justification 19178 Stage 9: editing, selection, etc. 19179 19180 Operations: 19181 insert text 19182 overstrike text 19183 select 19184 cut 19185 modify 19186 +/ 19187 19188 /++ 19189 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. 19190 +/ 19191 this(SimpleWindow window) { 19192 this.window = window; 19193 } 19194 19195 private SimpleWindow window; 19196 19197 19198 /++ 19199 When you render a [ComponentInFlow], it returns an arbitrary number of these interfaces 19200 representing the internal parts. The first pass is focused on the x parameter, then the 19201 renderer is responsible for going back to the parts in the current line and calling 19202 adjustDownForAscent to change the y params. 19203 +/ 19204 static interface ComponentRenderHelper { 19205 19206 /+ 19207 When you do an edit, possibly stuff on the same line previously need to move (to adjust 19208 the baseline), stuff subsequent needs to move (adjust x) and possibly stuff below needs 19209 to move (adjust y to make room for new line) until you get back to the same position, 19210 then you can stop - if one thing is unchanged, nothing after it is changed too. 19211 19212 Word wrap might change this as if can rewrap tons of stuff, but the same idea applies, 19213 once you reach something that is unchanged, you can stop. 19214 +/ 19215 19216 void adjustDownForAscent(int amount); // at the end of the line it needs to do these 19217 19218 int ascent() const; 19219 int descent() const; 19220 19221 int advance() const; 19222 19223 bool endsWithExplititLineBreak() const; 19224 } 19225 19226 static interface RenderResult { 19227 /++ 19228 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. 19229 +/ 19230 void popFront(); 19231 @property bool empty() const; 19232 @property ComponentRenderHelper front() const; 19233 19234 void repositionForNextLine(Point baseline, int availableWidth); 19235 } 19236 19237 static interface ComponentInFlow { 19238 void draw(ScreenPainter painter); 19239 //RenderResult render(Point baseline, int availableWidth); // FIXME: it needs to be able to say "my cache is good, nothing different" 19240 19241 bool startsWithExplicitLineBreak() const; 19242 } 19243 19244 static class TextFlowComponent : ComponentInFlow { 19245 bool startsWithExplicitLineBreak() const { return false; } // FIXME: if it is block this can return true 19246 19247 Color foreground; 19248 Color background; 19249 19250 OperatingSystemFont font; // should NEVER be null 19251 19252 ubyte attributes; // underline, strike through, display on new block 19253 19254 version(Windows) 19255 const(wchar)[] content; 19256 else 19257 const(char)[] content; // this should NEVER have a newline, except at the end 19258 19259 RenderedComponent[] rendered; // entirely controlled by [rerender] 19260 19261 // could prolly put some spacing around it too like margin / padding 19262 19263 this(Color f, Color b, OperatingSystemFont font, ubyte attr, const(char)[] c) 19264 in { assert(font !is null); 19265 assert(!font.isNull); } 19266 do 19267 { 19268 this.foreground = f; 19269 this.background = b; 19270 this.font = font; 19271 19272 this.attributes = attr; 19273 version(Windows) { 19274 auto conversionFlags = 0;//WindowsStringConversionFlags.convertNewLines; 19275 auto sz = sizeOfConvertedWstring(c, conversionFlags); 19276 auto buffer = new wchar[](sz); 19277 this.content = makeWindowsString(c, buffer, conversionFlags); 19278 } else { 19279 this.content = c.dup; 19280 } 19281 } 19282 19283 void draw(ScreenPainter painter) { 19284 painter.setFont(this.font); 19285 painter.outlineColor = this.foreground; 19286 painter.fillColor = Color.transparent; 19287 foreach(rendered; this.rendered) { 19288 // the component works in term of baseline, 19289 // but the painter works in term of upper left bounding box 19290 // so need to translate that 19291 19292 if(this.background.a) { 19293 painter.fillColor = this.background; 19294 painter.outlineColor = this.background; 19295 19296 painter.drawRectangle(Point(rendered.startX, rendered.startY - this.font.ascent), Size(rendered.width, this.font.height)); 19297 19298 painter.outlineColor = this.foreground; 19299 painter.fillColor = Color.transparent; 19300 } 19301 19302 painter.drawText(Point(rendered.startX, rendered.startY - this.font.ascent), rendered.slice); 19303 19304 // FIXME: strike through, underline, highlight selection, etc. 19305 } 19306 } 19307 } 19308 19309 // I could split the parts into words on render 19310 // for easier word-wrap, each one being an unbreakable "inline-block" 19311 private TextFlowComponent[] parts; 19312 private int needsRerenderFrom; 19313 19314 void addPart(Color f, Color b, OperatingSystemFont font, ubyte attr, const(char)[] c) { 19315 // FIXME: needsRerenderFrom. Basically if the bounding box and baseline is the same as the previous thing, it can prolly just stop. 19316 parts ~= new TextFlowComponent(f, b, font, attr, c); 19317 } 19318 19319 static struct RenderedComponent { 19320 int startX; 19321 int startY; 19322 short width; 19323 // 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! 19324 // for individual chars in here you've gotta process on demand 19325 version(Windows) 19326 const(wchar)[] slice; 19327 else 19328 const(char)[] slice; 19329 } 19330 19331 19332 void rerender(Rectangle boundingBox) { 19333 Point baseline = boundingBox.upperLeft; 19334 19335 this.boundingBox.left = boundingBox.left; 19336 this.boundingBox.top = boundingBox.top; 19337 19338 auto remainingParts = parts; 19339 19340 int largestX; 19341 19342 19343 foreach(part; parts) 19344 part.font.prepareContext(window); 19345 scope(exit) 19346 foreach(part; parts) 19347 part.font.releaseContext(); 19348 19349 calculateNextLine: 19350 19351 int nextLineHeight = 0; 19352 int nextBiggestDescent = 0; 19353 19354 foreach(part; remainingParts) { 19355 auto height = part.font.ascent; 19356 if(height > nextLineHeight) 19357 nextLineHeight = height; 19358 if(part.font.descent > nextBiggestDescent) 19359 nextBiggestDescent = part.font.descent; 19360 if(part.content.length && part.content[$-1] == '\n') 19361 break; 19362 } 19363 19364 baseline.y += nextLineHeight; 19365 auto lineStart = baseline; 19366 19367 while(remainingParts.length) { 19368 remainingParts[0].rendered = null; 19369 19370 bool eol; 19371 if(remainingParts[0].content.length && remainingParts[0].content[$-1] == '\n') 19372 eol = true; 19373 19374 // FIXME: word wrap 19375 auto font = remainingParts[0].font; 19376 auto slice = remainingParts[0].content[0 .. $ - (eol ? 1 : 0)]; 19377 auto width = font.stringWidth(slice, window); 19378 remainingParts[0].rendered ~= RenderedComponent(baseline.x, baseline.y, cast(short) width, slice); 19379 19380 remainingParts = remainingParts[1 .. $]; 19381 baseline.x += width; 19382 19383 if(eol) { 19384 baseline.y += nextBiggestDescent; 19385 if(baseline.x > largestX) 19386 largestX = baseline.x; 19387 baseline.x = lineStart.x; 19388 goto calculateNextLine; 19389 } 19390 } 19391 19392 if(baseline.x > largestX) 19393 largestX = baseline.x; 19394 19395 this.boundingBox.right = largestX; 19396 this.boundingBox.bottom = baseline.y; 19397 } 19398 19399 // you must call rerender first! 19400 void draw(ScreenPainter painter) { 19401 foreach(part; parts) { 19402 part.draw(painter); 19403 } 19404 } 19405 19406 struct IdentifyResult { 19407 TextFlowComponent part; 19408 int charIndexInPart; 19409 int totalCharIndex = -1; // if this is -1, it just means the end 19410 19411 Rectangle boundingBox; 19412 } 19413 19414 IdentifyResult identify(Point pt, bool exact = false) { 19415 if(parts.length == 0) 19416 return IdentifyResult(null, 0); 19417 19418 if(pt.y < boundingBox.top) { 19419 if(exact) 19420 return IdentifyResult(null, 1); 19421 return IdentifyResult(parts[0], 0); 19422 } 19423 if(pt.y > boundingBox.bottom) { 19424 if(exact) 19425 return IdentifyResult(null, 2); 19426 return IdentifyResult(parts[$-1], cast(int) parts[$-1].content.length); 19427 } 19428 19429 int tci = 0; 19430 19431 // I should probably like binary search this or something... 19432 foreach(ref part; parts) { 19433 foreach(rendered; part.rendered) { 19434 auto rect = Rectangle(rendered.startX, rendered.startY - part.font.ascent, rendered.startX + rendered.width, rendered.startY + part.font.descent); 19435 if(rect.contains(pt)) { 19436 auto x = pt.x - rendered.startX; 19437 auto estimatedIdx = x / part.font.averageWidth; 19438 19439 if(estimatedIdx < 0) 19440 estimatedIdx = 0; 19441 19442 if(estimatedIdx > rendered.slice.length) 19443 estimatedIdx = cast(int) rendered.slice.length; 19444 19445 int idx; 19446 int x1, x2; 19447 if(part.font.isMonospace) { 19448 auto w = part.font.averageWidth; 19449 if(!exact && x > (estimatedIdx + 1) * w) 19450 return IdentifyResult(null, 4); 19451 idx = estimatedIdx; 19452 x1 = idx * w; 19453 x2 = (idx + 1) * w; 19454 } else { 19455 idx = estimatedIdx; 19456 19457 part.font.prepareContext(window); 19458 scope(exit) part.font.releaseContext(); 19459 19460 // int iterations; 19461 19462 while(true) { 19463 // iterations++; 19464 x1 = idx ? part.font.stringWidth(rendered.slice[0 .. idx - 1]) : 0; 19465 x2 = part.font.stringWidth(rendered.slice[0 .. idx]); // should be the maximum since `averageWidth` kinda lies. 19466 19467 x1 += rendered.startX; 19468 x2 += rendered.startX; 19469 19470 if(pt.x < x1) { 19471 if(idx == 0) { 19472 if(exact) 19473 return IdentifyResult(null, 6); 19474 else 19475 break; 19476 } 19477 idx--; 19478 } else if(pt.x > x2) { 19479 idx++; 19480 if(idx > rendered.slice.length) { 19481 if(exact) 19482 return IdentifyResult(null, 5); 19483 else 19484 break; 19485 } 19486 } else if(pt.x >= x1 && pt.x <= x2) { 19487 if(idx) 19488 idx--; // point it at the original index 19489 break; // we fit 19490 } 19491 } 19492 19493 // writeln(iterations) 19494 } 19495 19496 19497 return IdentifyResult(part, idx, tci + idx, Rectangle(x1, rect.top, x2, rect.bottom)); // FIXME: utf-8? 19498 } 19499 } 19500 tci += cast(int) part.content.length; // FIXME: utf-8? 19501 } 19502 return IdentifyResult(null, 3); 19503 } 19504 19505 Rectangle boundingBox; // only set after [rerender] 19506 19507 // text will be positioned around the exclusion zone 19508 static struct ExclusionZone { 19509 19510 } 19511 19512 ExclusionZone[] exclusionZones; 19513 } 19514 19515 19516 // Don't use this yet. When I'm happy with it, I will move it to the 19517 // regular module namespace. 19518 mixin template ExperimentalTextComponent() { 19519 19520 static: 19521 19522 alias Rectangle = arsd.color.Rectangle; 19523 19524 struct ForegroundColor { 19525 Color color; 19526 alias color this; 19527 19528 this(Color c) { 19529 color = c; 19530 } 19531 19532 this(int r, int g, int b, int a = 255) { 19533 color = Color(r, g, b, a); 19534 } 19535 19536 static ForegroundColor opDispatch(string s)() if(__traits(compiles, ForegroundColor(mixin("Color." ~ s)))) { 19537 return ForegroundColor(mixin("Color." ~ s)); 19538 } 19539 } 19540 19541 struct BackgroundColor { 19542 Color color; 19543 alias color this; 19544 19545 this(Color c) { 19546 color = c; 19547 } 19548 19549 this(int r, int g, int b, int a = 255) { 19550 color = Color(r, g, b, a); 19551 } 19552 19553 static BackgroundColor opDispatch(string s)() if(__traits(compiles, BackgroundColor(mixin("Color." ~ s)))) { 19554 return BackgroundColor(mixin("Color." ~ s)); 19555 } 19556 } 19557 19558 static class InlineElement { 19559 string text; 19560 19561 BlockElement containingBlock; 19562 19563 Color color = Color.black; 19564 Color backgroundColor = Color.transparent; 19565 ushort styles; 19566 19567 string font; 19568 int fontSize; 19569 19570 int lineHeight; 19571 19572 void* identifier; 19573 19574 Rectangle boundingBox; 19575 int[] letterXs; // FIXME: maybe i should do bounding boxes for every character 19576 19577 bool isMergeCompatible(InlineElement other) { 19578 return 19579 containingBlock is other.containingBlock && 19580 color == other.color && 19581 backgroundColor == other.backgroundColor && 19582 styles == other.styles && 19583 font == other.font && 19584 fontSize == other.fontSize && 19585 lineHeight == other.lineHeight && 19586 true; 19587 } 19588 19589 int xOfIndex(size_t index) { 19590 if(index < letterXs.length) 19591 return letterXs[index]; 19592 else 19593 return boundingBox.right; 19594 } 19595 19596 InlineElement clone() { 19597 auto ie = new InlineElement(); 19598 ie.tupleof = this.tupleof; 19599 return ie; 19600 } 19601 19602 InlineElement getPreviousInlineElement() { 19603 InlineElement prev = null; 19604 foreach(ie; this.containingBlock.parts) { 19605 if(ie is this) 19606 break; 19607 prev = ie; 19608 } 19609 if(prev is null) { 19610 BlockElement pb; 19611 BlockElement cb = this.containingBlock; 19612 moar: 19613 foreach(ie; this.containingBlock.containingLayout.blocks) { 19614 if(ie is cb) 19615 break; 19616 pb = ie; 19617 } 19618 if(pb is null) 19619 return null; 19620 if(pb.parts.length == 0) { 19621 cb = pb; 19622 goto moar; 19623 } 19624 19625 prev = pb.parts[$-1]; 19626 19627 } 19628 return prev; 19629 } 19630 19631 InlineElement getNextInlineElement() { 19632 InlineElement next = null; 19633 foreach(idx, ie; this.containingBlock.parts) { 19634 if(ie is this) { 19635 if(idx + 1 < this.containingBlock.parts.length) 19636 next = this.containingBlock.parts[idx + 1]; 19637 break; 19638 } 19639 } 19640 if(next is null) { 19641 BlockElement n; 19642 foreach(idx, ie; this.containingBlock.containingLayout.blocks) { 19643 if(ie is this.containingBlock) { 19644 if(idx + 1 < this.containingBlock.containingLayout.blocks.length) 19645 n = this.containingBlock.containingLayout.blocks[idx + 1]; 19646 break; 19647 } 19648 } 19649 if(n is null) 19650 return null; 19651 19652 if(n.parts.length) 19653 next = n.parts[0]; 19654 else {} // FIXME 19655 19656 } 19657 return next; 19658 } 19659 19660 } 19661 19662 // Block elements are used entirely for positioning inline elements, 19663 // which are the things that are actually drawn. 19664 class BlockElement { 19665 InlineElement[] parts; 19666 uint alignment; 19667 19668 int whiteSpace; // pre, pre-wrap, wrap 19669 19670 TextLayout containingLayout; 19671 19672 // inputs 19673 Point where; 19674 Size minimumSize; 19675 Size maximumSize; 19676 Rectangle[] excludedBoxes; // like if you want it to write around a floated image or something. Coordinates are relative to the bounding box. 19677 void* identifier; 19678 19679 Rectangle margin; 19680 Rectangle padding; 19681 19682 // outputs 19683 Rectangle[] boundingBoxes; 19684 } 19685 19686 struct TextIdentifyResult { 19687 InlineElement element; 19688 int offset; 19689 19690 private TextIdentifyResult fixupNewline() { 19691 if(element !is null && offset < element.text.length && element.text[offset] == '\n') { 19692 offset--; 19693 } else if(element !is null && offset == element.text.length && element.text.length > 1 && element.text[$-1] == '\n') { 19694 offset--; 19695 } 19696 return this; 19697 } 19698 } 19699 19700 class TextLayout { 19701 BlockElement[] blocks; 19702 Rectangle boundingBox_; 19703 Rectangle boundingBox() { return boundingBox_; } 19704 void boundingBox(Rectangle r) { 19705 if(r != boundingBox_) { 19706 boundingBox_ = r; 19707 layoutInvalidated = true; 19708 } 19709 } 19710 19711 Rectangle contentBoundingBox() { 19712 Rectangle r; 19713 foreach(block; blocks) 19714 foreach(ie; block.parts) { 19715 if(ie.boundingBox.right > r.right) 19716 r.right = ie.boundingBox.right; 19717 if(ie.boundingBox.bottom > r.bottom) 19718 r.bottom = ie.boundingBox.bottom; 19719 } 19720 return r; 19721 } 19722 19723 BlockElement[] getBlocks() { 19724 return blocks; 19725 } 19726 19727 InlineElement[] getTexts() { 19728 InlineElement[] elements; 19729 foreach(block; blocks) 19730 elements ~= block.parts; 19731 return elements; 19732 } 19733 19734 string getPlainText() { 19735 string text; 19736 foreach(block; blocks) 19737 foreach(part; block.parts) 19738 text ~= part.text; 19739 return text; 19740 } 19741 19742 string getHtml() { 19743 return null; // FIXME 19744 } 19745 19746 this(Rectangle boundingBox) { 19747 this.boundingBox = boundingBox; 19748 } 19749 19750 BlockElement addBlock(InlineElement after = null, Rectangle margin = Rectangle(0, 0, 0, 0), Rectangle padding = Rectangle(0, 0, 0, 0)) { 19751 auto be = new BlockElement(); 19752 be.containingLayout = this; 19753 if(after is null) 19754 blocks ~= be; 19755 else { 19756 foreach(idx, b; blocks) { 19757 if(b is after.containingBlock) { 19758 blocks = blocks[0 .. idx + 1] ~ be ~ blocks[idx + 1 .. $]; 19759 break; 19760 } 19761 } 19762 } 19763 return be; 19764 } 19765 19766 void clear() { 19767 blocks = null; 19768 selectionStart = selectionEnd = caret = Caret.init; 19769 } 19770 19771 void addText(Args...)(Args args) { 19772 if(blocks.length == 0) 19773 addBlock(); 19774 19775 InlineElement ie = new InlineElement(); 19776 foreach(idx, arg; args) { 19777 static if(is(typeof(arg) == ForegroundColor)) 19778 ie.color = arg; 19779 else static if(is(typeof(arg) == TextFormat)) { 19780 if(arg & 0x8000) // ~TextFormat.something turns it off 19781 ie.styles &= arg; 19782 else 19783 ie.styles |= arg; 19784 } else static if(is(typeof(arg) == string)) { 19785 static if(idx == 0 && args.length > 1) 19786 static assert(0, "Put styles before the string."); 19787 size_t lastLineIndex; 19788 foreach(cidx, char a; arg) { 19789 if(a == '\n') { 19790 ie.text = arg[lastLineIndex .. cidx + 1]; 19791 lastLineIndex = cidx + 1; 19792 ie.containingBlock = blocks[$-1]; 19793 blocks[$-1].parts ~= ie.clone; 19794 ie.text = null; 19795 } else { 19796 19797 } 19798 } 19799 19800 ie.text = arg[lastLineIndex .. $]; 19801 ie.containingBlock = blocks[$-1]; 19802 blocks[$-1].parts ~= ie.clone; 19803 caret = Caret(this, blocks[$-1].parts[$-1], cast(int) blocks[$-1].parts[$-1].text.length); 19804 } 19805 } 19806 19807 invalidateLayout(); 19808 } 19809 19810 void tryMerge(InlineElement into, InlineElement what) { 19811 if(!into.isMergeCompatible(what)) { 19812 return; // cannot merge, different configs 19813 } 19814 19815 // cool, can merge, bring text together... 19816 into.text ~= what.text; 19817 19818 // and remove what 19819 for(size_t a = 0; a < what.containingBlock.parts.length; a++) { 19820 if(what.containingBlock.parts[a] is what) { 19821 for(size_t i = a; i < what.containingBlock.parts.length - 1; i++) 19822 what.containingBlock.parts[i] = what.containingBlock.parts[i + 1]; 19823 what.containingBlock.parts = what.containingBlock.parts[0 .. $-1]; 19824 19825 } 19826 } 19827 19828 // FIXME: ensure no other carets have a reference to it 19829 } 19830 19831 /// exact = true means return null if no match. otherwise, get the closest one that makes sense for a mouse click. 19832 TextIdentifyResult identify(int x, int y, bool exact = false) { 19833 TextIdentifyResult inexactMatch; 19834 foreach(block; blocks) { 19835 foreach(part; block.parts) { 19836 if(x >= part.boundingBox.left && x < part.boundingBox.right && y >= part.boundingBox.top && y < part.boundingBox.bottom) { 19837 19838 // FIXME binary search 19839 int tidx; 19840 int lastX; 19841 foreach_reverse(idxo, lx; part.letterXs) { 19842 int idx = cast(int) idxo; 19843 if(lx <= x) { 19844 if(lastX && lastX - x < x - lx) 19845 tidx = idx + 1; 19846 else 19847 tidx = idx; 19848 break; 19849 } 19850 lastX = lx; 19851 } 19852 19853 return TextIdentifyResult(part, tidx).fixupNewline; 19854 } else if(!exact) { 19855 // we're not in the box, but are we on the same line? 19856 if(y >= part.boundingBox.top && y < part.boundingBox.bottom) 19857 inexactMatch = TextIdentifyResult(part, x == 0 ? 0 : cast(int) part.text.length); 19858 } 19859 } 19860 } 19861 19862 if(!exact && inexactMatch is TextIdentifyResult.init && blocks.length && blocks[$-1].parts.length) 19863 return TextIdentifyResult(blocks[$-1].parts[$-1], cast(int) blocks[$-1].parts[$-1].text.length).fixupNewline; 19864 19865 return exact ? TextIdentifyResult.init : inexactMatch.fixupNewline; 19866 } 19867 19868 void moveCaretToPixelCoordinates(int x, int y) { 19869 auto result = identify(x, y); 19870 caret.inlineElement = result.element; 19871 caret.offset = result.offset; 19872 } 19873 19874 void selectToPixelCoordinates(int x, int y) { 19875 auto result = identify(x, y); 19876 19877 if(y < caretLastDrawnY1) { 19878 // on a previous line, carat is selectionEnd 19879 selectionEnd = caret; 19880 19881 selectionStart = Caret(this, result.element, result.offset); 19882 } else if(y > caretLastDrawnY2) { 19883 // on a later line 19884 selectionStart = caret; 19885 19886 selectionEnd = Caret(this, result.element, result.offset); 19887 } else { 19888 // on the same line... 19889 if(x <= caretLastDrawnX) { 19890 selectionEnd = caret; 19891 selectionStart = Caret(this, result.element, result.offset); 19892 } else { 19893 selectionStart = caret; 19894 selectionEnd = Caret(this, result.element, result.offset); 19895 } 19896 19897 } 19898 } 19899 19900 19901 /// Call this if the inputs change. It will reflow everything 19902 void redoLayout(ScreenPainter painter) { 19903 //painter.setClipRectangle(boundingBox); 19904 auto pos = Point(boundingBox.left, boundingBox.top); 19905 19906 int lastHeight; 19907 void nl() { 19908 pos.x = boundingBox.left; 19909 pos.y += lastHeight; 19910 } 19911 foreach(block; blocks) { 19912 nl(); 19913 foreach(part; block.parts) { 19914 part.letterXs = null; 19915 19916 auto size = painter.textSize(part.text); 19917 version(Windows) 19918 if(part.text.length && part.text[$-1] == '\n') 19919 size.height /= 2; // windows counts the new line at the end, but we don't want that 19920 19921 part.boundingBox = Rectangle(pos.x, pos.y, pos.x + size.width, pos.y + size.height); 19922 19923 foreach(idx, char c; part.text) { 19924 // FIXME: unicode 19925 part.letterXs ~= painter.textSize(part.text[0 .. idx]).width + pos.x; 19926 } 19927 19928 pos.x += size.width; 19929 if(pos.x >= boundingBox.right) { 19930 pos.y += size.height; 19931 pos.x = boundingBox.left; 19932 lastHeight = 0; 19933 } else { 19934 lastHeight = size.height; 19935 } 19936 19937 if(part.text.length && part.text[$-1] == '\n') 19938 nl(); 19939 } 19940 } 19941 19942 layoutInvalidated = false; 19943 } 19944 19945 bool layoutInvalidated = true; 19946 void invalidateLayout() { 19947 layoutInvalidated = true; 19948 } 19949 19950 // FIXME: caret can remain sometimes when inserting 19951 // FIXME: inserting at the beginning once you already have something can eff it up. 19952 void drawInto(ScreenPainter painter, bool focused = false) { 19953 if(layoutInvalidated) 19954 redoLayout(painter); 19955 foreach(block; blocks) { 19956 foreach(part; block.parts) { 19957 painter.outlineColor = part.color; 19958 painter.fillColor = part.backgroundColor; 19959 19960 auto pos = part.boundingBox.upperLeft; 19961 auto size = part.boundingBox.size; 19962 19963 painter.drawText(pos, part.text); 19964 if(part.styles & TextFormat.underline) 19965 painter.drawLine(Point(pos.x, pos.y + size.height - 4), Point(pos.x + size.width, pos.y + size.height - 4)); 19966 if(part.styles & TextFormat.strikethrough) 19967 painter.drawLine(Point(pos.x, pos.y + size.height/2), Point(pos.x + size.width, pos.y + size.height/2)); 19968 } 19969 } 19970 19971 // on every redraw, I will force the caret to be 19972 // redrawn too, in order to eliminate perceived lag 19973 // when moving around with the mouse. 19974 eraseCaret(painter); 19975 19976 if(focused) { 19977 highlightSelection(painter); 19978 drawCaret(painter); 19979 } 19980 } 19981 19982 Color selectionXorColor = Color(255, 255, 127); 19983 19984 void highlightSelection(ScreenPainter painter) { 19985 if(selectionStart is selectionEnd) 19986 return; // no selection 19987 19988 if(selectionStart.inlineElement is null) return; 19989 if(selectionEnd.inlineElement is null) return; 19990 19991 assert(selectionStart.inlineElement !is null); 19992 assert(selectionEnd.inlineElement !is null); 19993 19994 painter.rasterOp = RasterOp.xor; 19995 painter.outlineColor = Color.transparent; 19996 painter.fillColor = selectionXorColor; 19997 19998 auto at = selectionStart.inlineElement; 19999 auto atOffset = selectionStart.offset; 20000 bool done; 20001 while(at) { 20002 auto box = at.boundingBox; 20003 if(atOffset < at.letterXs.length) 20004 box.left = at.letterXs[atOffset]; 20005 20006 if(at is selectionEnd.inlineElement) { 20007 if(selectionEnd.offset < at.letterXs.length) 20008 box.right = at.letterXs[selectionEnd.offset]; 20009 done = true; 20010 } 20011 20012 painter.drawRectangle(box.upperLeft, box.width, box.height); 20013 20014 if(done) 20015 break; 20016 20017 at = at.getNextInlineElement(); 20018 atOffset = 0; 20019 } 20020 } 20021 20022 int caretLastDrawnX, caretLastDrawnY1, caretLastDrawnY2; 20023 bool caretShowingOnScreen = false; 20024 void drawCaret(ScreenPainter painter) { 20025 //painter.setClipRectangle(boundingBox); 20026 int x, y1, y2; 20027 if(caret.inlineElement is null) { 20028 x = boundingBox.left; 20029 y1 = boundingBox.top + 2; 20030 y2 = boundingBox.top + painter.fontHeight; 20031 } else { 20032 x = caret.inlineElement.xOfIndex(caret.offset); 20033 y1 = caret.inlineElement.boundingBox.top + 2; 20034 y2 = caret.inlineElement.boundingBox.bottom - 2; 20035 } 20036 20037 if(caretShowingOnScreen && (x != caretLastDrawnX || y1 != caretLastDrawnY1 || y2 != caretLastDrawnY2)) 20038 eraseCaret(painter); 20039 20040 painter.pen = Pen(Color.white, 1); 20041 painter.rasterOp = RasterOp.xor; 20042 painter.drawLine( 20043 Point(x, y1), 20044 Point(x, y2) 20045 ); 20046 painter.rasterOp = RasterOp.normal; 20047 caretShowingOnScreen = !caretShowingOnScreen; 20048 20049 if(caretShowingOnScreen) { 20050 caretLastDrawnX = x; 20051 caretLastDrawnY1 = y1; 20052 caretLastDrawnY2 = y2; 20053 } 20054 } 20055 20056 Rectangle caretBoundingBox() { 20057 int x, y1, y2; 20058 if(caret.inlineElement is null) { 20059 x = boundingBox.left; 20060 y1 = boundingBox.top + 2; 20061 y2 = boundingBox.top + 16; 20062 } else { 20063 x = caret.inlineElement.xOfIndex(caret.offset); 20064 y1 = caret.inlineElement.boundingBox.top + 2; 20065 y2 = caret.inlineElement.boundingBox.bottom - 2; 20066 } 20067 20068 return Rectangle(x, y1, x + 1, y2); 20069 } 20070 20071 void eraseCaret(ScreenPainter painter) { 20072 //painter.setClipRectangle(boundingBox); 20073 if(!caretShowingOnScreen) return; 20074 painter.pen = Pen(Color.white, 1); 20075 painter.rasterOp = RasterOp.xor; 20076 painter.drawLine( 20077 Point(caretLastDrawnX, caretLastDrawnY1), 20078 Point(caretLastDrawnX, caretLastDrawnY2) 20079 ); 20080 20081 caretShowingOnScreen = false; 20082 painter.rasterOp = RasterOp.normal; 20083 } 20084 20085 /// Caret movement api 20086 /// These should give the user a logical result based on what they see on screen... 20087 /// thus they locate predominately by *pixels* not char index. (These will generally coincide with monospace fonts tho!) 20088 void moveUp() { 20089 if(caret.inlineElement is null) return; 20090 auto x = caret.inlineElement.xOfIndex(caret.offset); 20091 auto y = caret.inlineElement.boundingBox.top + 2; 20092 20093 y -= caret.inlineElement.boundingBox.bottom - caret.inlineElement.boundingBox.top; 20094 if(y < 0) 20095 return; 20096 20097 auto i = identify(x, y); 20098 20099 if(i.element) { 20100 caret.inlineElement = i.element; 20101 caret.offset = i.offset; 20102 } 20103 } 20104 void moveDown() { 20105 if(caret.inlineElement is null) return; 20106 auto x = caret.inlineElement.xOfIndex(caret.offset); 20107 auto y = caret.inlineElement.boundingBox.bottom - 2; 20108 20109 y += caret.inlineElement.boundingBox.bottom - caret.inlineElement.boundingBox.top; 20110 20111 auto i = identify(x, y); 20112 if(i.element) { 20113 caret.inlineElement = i.element; 20114 caret.offset = i.offset; 20115 } 20116 } 20117 void moveLeft() { 20118 if(caret.inlineElement is null) return; 20119 if(caret.offset) 20120 caret.offset--; 20121 else { 20122 auto p = caret.inlineElement.getPreviousInlineElement(); 20123 if(p) { 20124 caret.inlineElement = p; 20125 if(p.text.length && p.text[$-1] == '\n') 20126 caret.offset = cast(int) p.text.length - 1; 20127 else 20128 caret.offset = cast(int) p.text.length; 20129 } 20130 } 20131 } 20132 void moveRight() { 20133 if(caret.inlineElement is null) return; 20134 if(caret.offset < caret.inlineElement.text.length && caret.inlineElement.text[caret.offset] != '\n') { 20135 caret.offset++; 20136 } else { 20137 auto p = caret.inlineElement.getNextInlineElement(); 20138 if(p) { 20139 caret.inlineElement = p; 20140 caret.offset = 0; 20141 } 20142 } 20143 } 20144 void moveHome() { 20145 if(caret.inlineElement is null) return; 20146 auto x = 0; 20147 auto y = caret.inlineElement.boundingBox.top + 2; 20148 20149 auto i = identify(x, y); 20150 20151 if(i.element) { 20152 caret.inlineElement = i.element; 20153 caret.offset = i.offset; 20154 } 20155 } 20156 void moveEnd() { 20157 if(caret.inlineElement is null) return; 20158 auto x = int.max; 20159 auto y = caret.inlineElement.boundingBox.top + 2; 20160 20161 auto i = identify(x, y); 20162 20163 if(i.element) { 20164 caret.inlineElement = i.element; 20165 caret.offset = i.offset; 20166 } 20167 20168 } 20169 void movePageUp(ref Caret caret) {} 20170 void movePageDown(ref Caret caret) {} 20171 20172 void moveDocumentStart(ref Caret caret) { 20173 if(blocks.length && blocks[0].parts.length) 20174 caret = Caret(this, blocks[0].parts[0], 0); 20175 else 20176 caret = Caret.init; 20177 } 20178 20179 void moveDocumentEnd(ref Caret caret) { 20180 if(blocks.length) { 20181 auto parts = blocks[$-1].parts; 20182 if(parts.length) { 20183 caret = Caret(this, parts[$-1], cast(int) parts[$-1].text.length); 20184 } else { 20185 caret = Caret.init; 20186 } 20187 } else 20188 caret = Caret.init; 20189 } 20190 20191 void deleteSelection() { 20192 if(selectionStart is selectionEnd) 20193 return; 20194 20195 if(selectionStart.inlineElement is null) return; 20196 if(selectionEnd.inlineElement is null) return; 20197 20198 assert(selectionStart.inlineElement !is null); 20199 assert(selectionEnd.inlineElement !is null); 20200 20201 auto at = selectionStart.inlineElement; 20202 20203 if(selectionEnd.inlineElement is at) { 20204 // same element, need to chop out 20205 at.text = at.text[0 .. selectionStart.offset] ~ at.text[selectionEnd.offset .. $]; 20206 at.letterXs = at.letterXs[0 .. selectionStart.offset] ~ at.letterXs[selectionEnd.offset .. $]; 20207 selectionEnd.offset -= selectionEnd.offset - selectionStart.offset; 20208 } else { 20209 // different elements, we can do it with slicing 20210 at.text = at.text[0 .. selectionStart.offset]; 20211 if(selectionStart.offset < at.letterXs.length) 20212 at.letterXs = at.letterXs[0 .. selectionStart.offset]; 20213 20214 at = at.getNextInlineElement(); 20215 20216 while(at) { 20217 if(at is selectionEnd.inlineElement) { 20218 at.text = at.text[selectionEnd.offset .. $]; 20219 if(selectionEnd.offset < at.letterXs.length) 20220 at.letterXs = at.letterXs[selectionEnd.offset .. $]; 20221 selectionEnd.offset = 0; 20222 break; 20223 } else { 20224 auto cfd = at; 20225 cfd.text = null; // delete the whole thing 20226 20227 at = at.getNextInlineElement(); 20228 20229 if(cfd.text.length == 0) { 20230 // and remove cfd 20231 for(size_t a = 0; a < cfd.containingBlock.parts.length; a++) { 20232 if(cfd.containingBlock.parts[a] is cfd) { 20233 for(size_t i = a; i < cfd.containingBlock.parts.length - 1; i++) 20234 cfd.containingBlock.parts[i] = cfd.containingBlock.parts[i + 1]; 20235 cfd.containingBlock.parts = cfd.containingBlock.parts[0 .. $-1]; 20236 20237 } 20238 } 20239 } 20240 } 20241 } 20242 } 20243 20244 caret = selectionEnd; 20245 selectNone(); 20246 20247 invalidateLayout(); 20248 20249 } 20250 20251 /// Plain text editing api. These work at the current caret inside the selected inline element. 20252 void insert(in char[] text) { 20253 foreach(dchar ch; text) 20254 insert(ch); 20255 } 20256 /// ditto 20257 void insert(dchar ch) { 20258 20259 bool selectionDeleted = false; 20260 if(selectionStart !is selectionEnd) { 20261 deleteSelection(); 20262 selectionDeleted = true; 20263 } 20264 20265 if(ch == 127) { 20266 delete_(); 20267 return; 20268 } 20269 if(ch == 8) { 20270 if(!selectionDeleted) 20271 backspace(); 20272 return; 20273 } 20274 20275 invalidateLayout(); 20276 20277 if(ch == 13) ch = 10; 20278 auto e = caret.inlineElement; 20279 if(e is null) { 20280 addText("" ~ cast(char) ch) ; // FIXME 20281 return; 20282 } 20283 20284 if(caret.offset == e.text.length) { 20285 e.text ~= cast(char) ch; // FIXME 20286 caret.offset++; 20287 if(ch == 10) { 20288 auto c = caret.inlineElement.clone; 20289 c.text = null; 20290 c.letterXs = null; 20291 insertPartAfter(c,e); 20292 caret = Caret(this, c, 0); 20293 } 20294 } else { 20295 // FIXME cast char sucks 20296 if(ch == 10) { 20297 auto c = caret.inlineElement.clone; 20298 c.text = e.text[caret.offset .. $]; 20299 if(caret.offset < c.letterXs.length) 20300 c.letterXs = e.letterXs[caret.offset .. $]; // FIXME boundingBox 20301 e.text = e.text[0 .. caret.offset] ~ cast(char) ch; 20302 if(caret.offset <= e.letterXs.length) { 20303 e.letterXs = e.letterXs[0 .. caret.offset] ~ 0; // FIXME bounding box 20304 } 20305 insertPartAfter(c,e); 20306 caret = Caret(this, c, 0); 20307 } else { 20308 e.text = e.text[0 .. caret.offset] ~ cast(char) ch ~ e.text[caret.offset .. $]; 20309 caret.offset++; 20310 } 20311 } 20312 } 20313 20314 void insertPartAfter(InlineElement what, InlineElement where) { 20315 foreach(idx, p; where.containingBlock.parts) { 20316 if(p is where) { 20317 if(idx + 1 == where.containingBlock.parts.length) 20318 where.containingBlock.parts ~= what; 20319 else 20320 where.containingBlock.parts = where.containingBlock.parts[0 .. idx + 1] ~ what ~ where.containingBlock.parts[idx + 1 .. $]; 20321 return; 20322 } 20323 } 20324 } 20325 20326 void cleanupStructures() { 20327 for(size_t i = 0; i < blocks.length; i++) { 20328 auto block = blocks[i]; 20329 for(size_t a = 0; a < block.parts.length; a++) { 20330 auto part = block.parts[a]; 20331 if(part.text.length == 0) { 20332 for(size_t b = a; b < block.parts.length - 1; b++) 20333 block.parts[b] = block.parts[b+1]; 20334 block.parts = block.parts[0 .. $-1]; 20335 } 20336 } 20337 if(block.parts.length == 0) { 20338 for(size_t a = i; a < blocks.length - 1; a++) 20339 blocks[a] = blocks[a+1]; 20340 blocks = blocks[0 .. $-1]; 20341 } 20342 } 20343 } 20344 20345 void backspace() { 20346 try_again: 20347 auto e = caret.inlineElement; 20348 if(e is null) 20349 return; 20350 if(caret.offset == 0) { 20351 auto prev = e.getPreviousInlineElement(); 20352 if(prev is null) 20353 return; 20354 auto newOffset = cast(int) prev.text.length; 20355 tryMerge(prev, e); 20356 caret.inlineElement = prev; 20357 caret.offset = prev is null ? 0 : newOffset; 20358 20359 goto try_again; 20360 } else if(caret.offset == e.text.length) { 20361 e.text = e.text[0 .. $-1]; 20362 caret.offset--; 20363 } else { 20364 e.text = e.text[0 .. caret.offset - 1] ~ e.text[caret.offset .. $]; 20365 caret.offset--; 20366 } 20367 //cleanupStructures(); 20368 20369 invalidateLayout(); 20370 } 20371 void delete_() { 20372 if(selectionStart !is selectionEnd) 20373 deleteSelection(); 20374 else { 20375 auto before = caret; 20376 moveRight(); 20377 if(caret != before) { 20378 backspace(); 20379 } 20380 } 20381 20382 invalidateLayout(); 20383 } 20384 void overstrike() {} 20385 20386 /// Selection API. See also: caret movement. 20387 void selectAll() { 20388 moveDocumentStart(selectionStart); 20389 moveDocumentEnd(selectionEnd); 20390 } 20391 bool selectNone() { 20392 if(selectionStart != selectionEnd) { 20393 selectionStart = selectionEnd = Caret.init; 20394 return true; 20395 } 20396 return false; 20397 } 20398 20399 /// Rich text editing api. These allow you to manipulate the meta data of the current element and add new elements. 20400 /// They will modify the current selection if there is one and will splice one in if needed. 20401 void changeAttributes() {} 20402 20403 20404 /// Text search api. They manipulate the selection and/or caret. 20405 void findText(string text) {} 20406 void findIndex(size_t textIndex) {} 20407 20408 // sample event handlers 20409 20410 void handleEvent(KeyEvent event) { 20411 //if(event.type == KeyEvent.Type.KeyPressed) { 20412 20413 //} 20414 } 20415 20416 void handleEvent(dchar ch) { 20417 20418 } 20419 20420 void handleEvent(MouseEvent event) { 20421 20422 } 20423 20424 bool contentEditable; // can it be edited? 20425 bool contentCaretable; // is there a caret/cursor that moves around in there? 20426 bool contentSelectable; // selectable? 20427 20428 Caret caret; 20429 Caret selectionStart; 20430 Caret selectionEnd; 20431 20432 bool insertMode; 20433 } 20434 20435 struct Caret { 20436 TextLayout layout; 20437 InlineElement inlineElement; 20438 int offset; 20439 } 20440 20441 enum TextFormat : ushort { 20442 // decorations 20443 underline = 1, 20444 strikethrough = 2, 20445 20446 // font selectors 20447 20448 bold = 0x4000 | 1, // weight 700 20449 light = 0x4000 | 2, // weight 300 20450 veryBoldOrLight = 0x4000 | 4, // weight 100 with light, weight 900 with bold 20451 // bold | light is really invalid but should give weight 500 20452 // veryBoldOrLight without one of the others should just give the default for the font; it should be ignored. 20453 20454 italic = 0x4000 | 8, 20455 smallcaps = 0x4000 | 16, 20456 } 20457 20458 void* findFont(string family, int weight, TextFormat formats) { 20459 return null; 20460 } 20461 20462 } 20463 20464 /++ 20465 $(PITFALL This is not yet stable and may break in future versions without notice.) 20466 20467 History: 20468 Added February 19, 2021 20469 +/ 20470 /// Group: drag_and_drop 20471 interface DropHandler { 20472 /++ 20473 Called when the drag enters the handler's area. 20474 +/ 20475 DragAndDropAction dragEnter(DropPackage*); 20476 /++ 20477 Called when the drag leaves the handler's area or is 20478 cancelled. You should free your resources when this is called. 20479 +/ 20480 void dragLeave(); 20481 /++ 20482 Called continually as the drag moves over the handler's area. 20483 20484 Returns: feedback to the dragger 20485 +/ 20486 DropParameters dragOver(Point pt); 20487 /++ 20488 The user dropped the data and you should process it now. You can 20489 access the data through the given [DropPackage]. 20490 +/ 20491 void drop(scope DropPackage*); 20492 /++ 20493 Called when the drop is complete. You should free whatever temporary 20494 resources you were using. It is often reasonable to simply forward 20495 this call to [dragLeave]. 20496 +/ 20497 void finish(); 20498 20499 /++ 20500 Parameters returned by [DropHandler.drop]. 20501 +/ 20502 static struct DropParameters { 20503 /++ 20504 Acceptable action over this area. 20505 +/ 20506 DragAndDropAction action; 20507 /++ 20508 Rectangle, in client coordinates, where the dragger can expect the same result during this drag session and thus need not ask again. 20509 20510 If you leave this as Rectangle.init, the dragger will continue to ask and this can waste resources. 20511 +/ 20512 Rectangle consistentWithin; 20513 } 20514 } 20515 20516 /++ 20517 History: 20518 Added February 19, 2021 20519 +/ 20520 /// Group: drag_and_drop 20521 enum DragAndDropAction { 20522 none = 0, 20523 copy, 20524 move, 20525 link, 20526 ask, 20527 custom 20528 } 20529 20530 /++ 20531 An opaque structure representing dropped data. It contains 20532 private, platform-specific data that your `drop` function 20533 should simply forward. 20534 20535 $(PITFALL This is not yet stable and may break in future versions without notice.) 20536 20537 History: 20538 Added February 19, 2021 20539 +/ 20540 /// Group: drag_and_drop 20541 struct DropPackage { 20542 /++ 20543 Lists the available formats as magic numbers. You should compare these 20544 against looked-up formats (see [DraggableData.getFormatId]) you know you support and can 20545 understand the passed data. 20546 +/ 20547 DraggableData.FormatId[] availableFormats() { 20548 version(X11) { 20549 return xFormats; 20550 } else version(Windows) { 20551 if(pDataObj is null) 20552 return null; 20553 20554 typeof(return) ret; 20555 20556 IEnumFORMATETC ef; 20557 if(pDataObj.EnumFormatEtc(DATADIR.DATADIR_GET, &ef) == S_OK) { 20558 FORMATETC fmt; 20559 ULONG fetched; 20560 while(ef.Next(1, &fmt, &fetched) == S_OK) { 20561 if(fetched == 0) 20562 break; 20563 20564 if(fmt.lindex != -1) 20565 continue; 20566 if(fmt.dwAspect != DVASPECT.DVASPECT_CONTENT) 20567 continue; 20568 if(!(fmt.tymed & TYMED.TYMED_HGLOBAL)) 20569 continue; 20570 20571 ret ~= fmt.cfFormat; 20572 } 20573 } 20574 20575 return ret; 20576 } 20577 } 20578 20579 /++ 20580 Gets data from the drop and optionally accepts it. 20581 20582 Returns: 20583 void because the data is fed asynchronously through the `dg` parameter. 20584 20585 Params: 20586 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. 20587 20588 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. 20589 20590 Calling `getData` again after accepting a drop is not permitted. 20591 20592 format = the format you want, from [availableFormats]. Use [DraggableData.getFormatId] to convert from a MIME string or well-known standard format. 20593 20594 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. 20595 20596 Throws: 20597 if `format` was not compatible with the [availableFormats] or if the drop has already been accepted. 20598 20599 History: 20600 Included in first release of [DropPackage]. 20601 +/ 20602 void getData(DragAndDropAction acceptedAction, DraggableData.FormatId format, void delegate(scope ubyte[] data) dg) { 20603 version(X11) { 20604 20605 auto display = XDisplayConnection.get(); 20606 auto selectionAtom = GetAtom!"XdndSelection"(display); 20607 auto best = format; 20608 20609 static class X11GetSelectionHandler_Drop : X11GetSelectionHandler { 20610 20611 XDisplay* display; 20612 Atom selectionAtom; 20613 DraggableData.FormatId best; 20614 DraggableData.FormatId format; 20615 void delegate(scope ubyte[] data) dg; 20616 DragAndDropAction acceptedAction; 20617 Window sourceWindow; 20618 SimpleWindow win; 20619 this(XDisplay* display, SimpleWindow win, Window sourceWindow, DraggableData.FormatId format, Atom selectionAtom, DraggableData.FormatId best, void delegate(scope ubyte[] data) dg, DragAndDropAction acceptedAction) { 20620 this.display = display; 20621 this.win = win; 20622 this.sourceWindow = sourceWindow; 20623 this.format = format; 20624 this.selectionAtom = selectionAtom; 20625 this.best = best; 20626 this.dg = dg; 20627 this.acceptedAction = acceptedAction; 20628 } 20629 20630 20631 mixin X11GetSelectionHandler_Basics; 20632 20633 void handleData(Atom target, in ubyte[] data) { 20634 //if(target == GetAtom!"UTF8_STRING"(XDisplayConnection.get) || target == XA_STRING || target == GetAtom!"text/plain"(XDisplayConnection.get)) 20635 20636 dg(cast(ubyte[]) data); 20637 20638 if(acceptedAction != DragAndDropAction.none) { 20639 auto display = XDisplayConnection.get; 20640 20641 XClientMessageEvent xclient; 20642 20643 xclient.type = EventType.ClientMessage; 20644 xclient.window = sourceWindow; 20645 xclient.message_type = GetAtom!"XdndFinished"(display); 20646 xclient.format = 32; 20647 xclient.data.l[0] = win.impl.window; 20648 xclient.data.l[1] = 1; // drop successful 20649 xclient.data.l[2] = dndActionAtom(display, acceptedAction); 20650 20651 XSendEvent( 20652 display, 20653 sourceWindow, 20654 false, 20655 EventMask.NoEventMask, 20656 cast(XEvent*) &xclient 20657 ); 20658 20659 XFlush(display); 20660 } 20661 } 20662 20663 Atom findBestFormat(Atom[] answer) { 20664 Atom best = None; 20665 foreach(option; answer) { 20666 if(option == format) { 20667 best = option; 20668 break; 20669 } 20670 /* 20671 if(option == GetAtom!"UTF8_STRING"(XDisplayConnection.get)) { 20672 best = option; 20673 break; 20674 } else if(option == XA_STRING) { 20675 best = option; 20676 } else if(option == GetAtom!"text/plain"(XDisplayConnection.get)) { 20677 best = option; 20678 } 20679 */ 20680 } 20681 return best; 20682 } 20683 } 20684 20685 win.impl.getSelectionHandlers[selectionAtom] = new X11GetSelectionHandler_Drop(display, win, sourceWindow, format, selectionAtom, best, dg, acceptedAction); 20686 20687 XConvertSelection(display, selectionAtom, best, GetAtom!("SDD_DATA", true)(display), win.impl.window, dataTimestamp); 20688 20689 } else version(Windows) { 20690 20691 // clean up like DragLeave 20692 // pass effect back up 20693 20694 FORMATETC t; 20695 assert(format >= 0 && format <= ushort.max); 20696 t.cfFormat = cast(ushort) format; 20697 t.lindex = -1; 20698 t.dwAspect = DVASPECT.DVASPECT_CONTENT; 20699 t.tymed = TYMED.TYMED_HGLOBAL; 20700 20701 STGMEDIUM m; 20702 20703 if(pDataObj.GetData(&t, &m) != S_OK) { 20704 // fail 20705 } else { 20706 // succeed, take the data and clean up 20707 20708 // FIXME: ensure it is legit HGLOBAL 20709 auto handle = m.hGlobal; 20710 20711 if(handle) { 20712 auto sz = GlobalSize(handle); 20713 if(auto ptr = cast(ubyte*) GlobalLock(handle)) { 20714 scope(exit) GlobalUnlock(handle); 20715 scope(exit) GlobalFree(handle); 20716 20717 auto data = ptr[0 .. sz]; 20718 20719 dg(data); 20720 } 20721 } 20722 } 20723 } 20724 } 20725 20726 private: 20727 20728 version(X11) { 20729 SimpleWindow win; 20730 Window sourceWindow; 20731 Time dataTimestamp; 20732 20733 Atom[] xFormats; 20734 } 20735 version(Windows) { 20736 IDataObject pDataObj; 20737 } 20738 } 20739 20740 /++ 20741 A generic helper base class for making a drop handler with a preference list of custom types. 20742 This is the base for [TextDropHandler] and [FilesDropHandler] and you can use it for your own 20743 droppers too. 20744 20745 It assumes the whole window it used, but you can subclass to change that. 20746 20747 $(PITFALL This is not yet stable and may break in future versions without notice.) 20748 20749 History: 20750 Added February 19, 2021 20751 +/ 20752 /// Group: drag_and_drop 20753 class GenericDropHandlerBase : DropHandler { 20754 // no fancy state here so no need to do anything here 20755 void finish() { } 20756 void dragLeave() { } 20757 20758 private DragAndDropAction acceptedAction; 20759 private DraggableData.FormatId acceptedFormat; 20760 private void delegate(scope ubyte[]) acceptedHandler; 20761 20762 struct FormatHandler { 20763 DraggableData.FormatId format; 20764 void delegate(scope ubyte[]) handler; 20765 } 20766 20767 protected abstract FormatHandler[] formatHandlers(); 20768 20769 DragAndDropAction dragEnter(DropPackage* pkg) { 20770 debug(sdpy_dnd) { foreach(fmt; pkg.availableFormats()) writeln(fmt, " ", DraggableData.getFormatName(fmt)); } 20771 foreach(fmt; formatHandlers()) 20772 foreach(f; pkg.availableFormats()) 20773 if(f == fmt.format) { 20774 acceptedFormat = f; 20775 acceptedHandler = fmt.handler; 20776 return acceptedAction = DragAndDropAction.copy; 20777 } 20778 return acceptedAction = DragAndDropAction.none; 20779 } 20780 DropParameters dragOver(Point pt) { 20781 return DropParameters(acceptedAction); 20782 } 20783 20784 void drop(scope DropPackage* dropPackage) { 20785 if(!acceptedFormat || acceptedHandler is null) { 20786 debug(sdpy_dnd) { writeln("drop called w/ handler ", acceptedHandler, " and format ", acceptedFormat); } 20787 return; // prolly shouldn't happen anyway... 20788 } 20789 20790 dropPackage.getData(acceptedAction, acceptedFormat, acceptedHandler); 20791 } 20792 } 20793 20794 /++ 20795 A simple handler for making your window accept drops of plain text. 20796 20797 $(PITFALL This is not yet stable and may break in future versions without notice.) 20798 20799 History: 20800 Added February 22, 2021 20801 +/ 20802 /// Group: drag_and_drop 20803 class TextDropHandler : GenericDropHandlerBase { 20804 private void delegate(in char[] text) dg; 20805 20806 /++ 20807 20808 +/ 20809 this(void delegate(in char[] text) dg) { 20810 this.dg = dg; 20811 } 20812 20813 protected override FormatHandler[] formatHandlers() { 20814 version(X11) 20815 return [ 20816 FormatHandler(GetAtom!"UTF8_STRING"(XDisplayConnection.get), &translator), 20817 FormatHandler(GetAtom!"text/plain;charset=utf-8"(XDisplayConnection.get), &translator), 20818 ]; 20819 else version(Windows) 20820 return [ 20821 FormatHandler(CF_UNICODETEXT, &translator), 20822 ]; 20823 } 20824 20825 private void translator(scope ubyte[] data) { 20826 version(X11) 20827 dg(cast(char[]) data); 20828 else version(Windows) 20829 dg(makeUtf8StringFromWindowsString(cast(wchar[]) data)); 20830 } 20831 } 20832 20833 /++ 20834 A simple handler for making your window accept drops of files, issued to you as file names. 20835 20836 $(PITFALL This is not yet stable and may break in future versions without notice.) 20837 20838 History: 20839 Added February 22, 2021 20840 +/ 20841 /// Group: drag_and_drop 20842 20843 class FilesDropHandler : GenericDropHandlerBase { 20844 private void delegate(in char[][]) dg; 20845 20846 /++ 20847 20848 +/ 20849 this(void delegate(in char[][] fileNames) dg) { 20850 this.dg = dg; 20851 } 20852 20853 protected override FormatHandler[] formatHandlers() { 20854 version(X11) 20855 return [ 20856 FormatHandler(GetAtom!"text/uri-list"(XDisplayConnection.get), &translator), 20857 ]; 20858 else version(Windows) 20859 return [ 20860 FormatHandler(CF_HDROP, &translator), 20861 ]; 20862 } 20863 20864 private void translator(scope ubyte[] data) { 20865 version(X11) { 20866 char[] listString = cast(char[]) data; 20867 char[][16] buffer; 20868 int count; 20869 char[][] result = buffer[]; 20870 20871 void commit(char[] s) { 20872 if(count == result.length) 20873 result.length += 16; 20874 if(s.length > 7 && s[0 ..7] == "file://") 20875 s = s[7 .. $]; // FIXME: also may need to trim out the host and do some entity decoding 20876 result[count++] = s; 20877 } 20878 20879 size_t last; 20880 foreach(idx, char c; listString) { 20881 if(c == '\n') { 20882 commit(listString[last .. idx - 1]); // a \r 20883 last = idx + 1; // a \n 20884 } 20885 } 20886 20887 if(last < listString.length) { 20888 commit(listString[last .. $]); 20889 } 20890 20891 // FIXME: they are uris now, should I translate it to local file names? 20892 // of course the host name is supposed to be there cuz of X rokking... 20893 20894 dg(result[0 .. count]); 20895 } else version(Windows) { 20896 20897 static struct DROPFILES { 20898 DWORD pFiles; 20899 POINT pt; 20900 BOOL fNC; 20901 BOOL fWide; 20902 } 20903 20904 20905 const(char)[][16] buffer; 20906 int count; 20907 const(char)[][] result = buffer[]; 20908 size_t last; 20909 20910 void commitA(in char[] stuff) { 20911 if(count == result.length) 20912 result.length += 16; 20913 result[count++] = stuff; 20914 } 20915 20916 void commitW(in wchar[] stuff) { 20917 commitA(makeUtf8StringFromWindowsString(stuff)); 20918 } 20919 20920 void magic(T)(T chars) { 20921 size_t idx; 20922 while(chars[idx]) { 20923 last = idx; 20924 while(chars[idx]) { 20925 idx++; 20926 } 20927 static if(is(T == char*)) 20928 commitA(chars[last .. idx]); 20929 else 20930 commitW(chars[last .. idx]); 20931 idx++; 20932 } 20933 } 20934 20935 auto df = cast(DROPFILES*) data.ptr; 20936 if(df.fWide) { 20937 wchar* chars = cast(wchar*) (data.ptr + df.pFiles); 20938 magic(chars); 20939 } else { 20940 char* chars = cast(char*) (data.ptr + df.pFiles); 20941 magic(chars); 20942 } 20943 dg(result[0 .. count]); 20944 } 20945 } 20946 } 20947 20948 /++ 20949 Interface to describe data being dragged. See also [draggable] helper function. 20950 20951 $(PITFALL This is not yet stable and may break in future versions without notice.) 20952 20953 History: 20954 Added February 19, 2021 20955 +/ 20956 interface DraggableData { 20957 version(X11) 20958 alias FormatId = Atom; 20959 else 20960 alias FormatId = uint; 20961 /++ 20962 Gets the platform-specific FormatId associated with the given named format. 20963 20964 This may be a MIME type, but may also be other various strings defined by the 20965 programs you want to interoperate with. 20966 20967 FIXME: sdpy needs to offer data adapter things that look for compatible formats 20968 and convert it to some particular type for you. 20969 +/ 20970 static FormatId getFormatId(string name)() { 20971 version(X11) 20972 return GetAtom!name(XDisplayConnection.get); 20973 else version(Windows) { 20974 static UINT cache; 20975 if(!cache) 20976 cache = RegisterClipboardFormatA(name); 20977 return cache; 20978 } else 20979 throw new NotYetImplementedException(); 20980 } 20981 20982 /++ 20983 Looks up a string to represent the name for the given format, if there is one. 20984 20985 You should avoid using this function because it is slow. It is provided more for 20986 debugging than for primary use. 20987 +/ 20988 static string getFormatName(FormatId format) { 20989 version(X11) { 20990 if(format == 0) 20991 return "None"; 20992 else 20993 return getAtomName(format, XDisplayConnection.get); 20994 } else version(Windows) { 20995 switch(format) { 20996 case CF_UNICODETEXT: return "CF_UNICODETEXT"; 20997 case CF_DIBV5: return "CF_DIBV5"; 20998 case CF_RIFF: return "CF_RIFF"; 20999 case CF_WAVE: return "CF_WAVE"; 21000 case CF_HDROP: return "CF_HDROP"; 21001 default: 21002 char[1024] name; 21003 auto count = GetClipboardFormatNameA(format, name.ptr, name.length); 21004 return name[0 .. count].idup; 21005 } 21006 } 21007 } 21008 21009 FormatId[] availableFormats(); 21010 // Return the slice of data you filled, empty slice if done. 21011 // this is to support the incremental thing 21012 ubyte[] getData(FormatId format, return scope ubyte[] data); 21013 21014 size_t dataLength(FormatId format); 21015 } 21016 21017 /++ 21018 $(PITFALL This is not yet stable and may break in future versions without notice.) 21019 21020 History: 21021 Added February 19, 2021 21022 +/ 21023 DraggableData draggable(string s) { 21024 version(X11) 21025 return new class X11SetSelectionHandler_Text, DraggableData { 21026 this() { 21027 super(s); 21028 } 21029 21030 override FormatId[] availableFormats() { 21031 return X11SetSelectionHandler_Text.availableFormats(); 21032 } 21033 21034 override ubyte[] getData(FormatId format, return scope ubyte[] data) { 21035 return X11SetSelectionHandler_Text.getData(format, data); 21036 } 21037 21038 size_t dataLength(FormatId format) { 21039 return s.length; 21040 } 21041 }; 21042 version(Windows) 21043 return new class DraggableData { 21044 FormatId[] availableFormats() { 21045 return [CF_UNICODETEXT]; 21046 } 21047 21048 ubyte[] getData(FormatId format, return scope ubyte[] data) { 21049 return cast(ubyte[]) makeWindowsString(s, cast(wchar[]) data, WindowsStringConversionFlags.convertNewLines | WindowsStringConversionFlags.zeroTerminate); 21050 } 21051 21052 size_t dataLength(FormatId format) { 21053 return sizeOfConvertedWstring(s, WindowsStringConversionFlags.convertNewLines | WindowsStringConversionFlags.zeroTerminate) * wchar.sizeof; 21054 } 21055 }; 21056 } 21057 21058 /++ 21059 $(PITFALL This is not yet stable and may break in future versions without notice.) 21060 21061 History: 21062 Added February 19, 2021 21063 +/ 21064 /// Group: drag_and_drop 21065 int doDragDrop(SimpleWindow window, DraggableData handler, DragAndDropAction action = DragAndDropAction.copy) 21066 in { 21067 assert(window !is null); 21068 assert(handler !is null); 21069 } 21070 do 21071 { 21072 version(X11) { 21073 auto sh = cast(X11SetSelectionHandler) handler; 21074 if(sh is null) { 21075 // gotta make my own adapter. 21076 sh = new class X11SetSelectionHandler { 21077 mixin X11SetSelectionHandler_Basics; 21078 21079 Atom[] availableFormats() { return handler.availableFormats(); } 21080 ubyte[] getData(Atom format, return scope ubyte[] data) { 21081 return handler.getData(format, data); 21082 } 21083 21084 // since the drop selection is only ever used once it isn't important 21085 // to reset it. 21086 void done() {} 21087 }; 21088 } 21089 return doDragDropX11(window, sh, action); 21090 } else version(Windows) { 21091 return doDragDropWindows(window, handler, action); 21092 } else throw new NotYetImplementedException(); 21093 } 21094 21095 version(Windows) 21096 private int doDragDropWindows(SimpleWindow window, DraggableData handler, DragAndDropAction action = DragAndDropAction.copy) { 21097 IDataObject obj = new class IDataObject { 21098 ULONG refCount; 21099 ULONG AddRef() { 21100 return ++refCount; 21101 } 21102 ULONG Release() { 21103 return --refCount; 21104 } 21105 HRESULT QueryInterface(const (IID)*riid, LPVOID *ppv) { 21106 if (IID_IUnknown == *riid) { 21107 *ppv = cast(void*) cast(IUnknown) this; 21108 } 21109 else if (IID_IDataObject == *riid) { 21110 *ppv = cast(void*) cast(IDataObject) this; 21111 } 21112 else { 21113 *ppv = null; 21114 return E_NOINTERFACE; 21115 } 21116 21117 AddRef(); 21118 return NOERROR; 21119 } 21120 21121 HRESULT DAdvise(FORMATETC* pformatetc, DWORD advf, IAdviseSink pAdvSink, DWORD* pdwConnection) { 21122 // writeln("Advise"); 21123 return E_NOTIMPL; 21124 } 21125 HRESULT DUnadvise(DWORD dwConnection) { 21126 return E_NOTIMPL; 21127 } 21128 HRESULT EnumDAdvise(IEnumSTATDATA* ppenumAdvise) { 21129 // writeln("EnumDAdvise"); 21130 return OLE_E_ADVISENOTSUPPORTED; 21131 } 21132 // tell what formats it supports 21133 21134 FORMATETC[] types; 21135 this() { 21136 FORMATETC t; 21137 foreach(ty; handler.availableFormats()) { 21138 assert(ty <= ushort.max && ty >= 0); 21139 t.cfFormat = cast(ushort) ty; 21140 t.lindex = -1; 21141 t.dwAspect = DVASPECT.DVASPECT_CONTENT; 21142 t.tymed = TYMED.TYMED_HGLOBAL; 21143 } 21144 types ~= t; 21145 } 21146 HRESULT EnumFormatEtc(DWORD dwDirection, IEnumFORMATETC* ppenumFormatEtc) { 21147 if(dwDirection == DATADIR.DATADIR_GET) { 21148 *ppenumFormatEtc = new class IEnumFORMATETC { 21149 ULONG refCount; 21150 ULONG AddRef() { 21151 return ++refCount; 21152 } 21153 ULONG Release() { 21154 return --refCount; 21155 } 21156 HRESULT QueryInterface(const (IID)*riid, LPVOID *ppv) { 21157 if (IID_IUnknown == *riid) { 21158 *ppv = cast(void*) cast(IUnknown) this; 21159 } 21160 else if (IID_IEnumFORMATETC == *riid) { 21161 *ppv = cast(void*) cast(IEnumFORMATETC) this; 21162 } 21163 else { 21164 *ppv = null; 21165 return E_NOINTERFACE; 21166 } 21167 21168 AddRef(); 21169 return NOERROR; 21170 } 21171 21172 21173 int pos; 21174 this() { 21175 pos = 0; 21176 } 21177 21178 HRESULT Clone(IEnumFORMATETC* ppenum) { 21179 // writeln("clone"); 21180 return E_NOTIMPL; // FIXME 21181 } 21182 21183 // Caller is responsible for freeing memory 21184 HRESULT Next(ULONG celt, FORMATETC* rgelt, ULONG* pceltFetched) { 21185 // fetched may be null if celt is one 21186 if(celt != 1) 21187 return E_NOTIMPL; // FIXME 21188 21189 if(celt + pos > types.length) 21190 return S_FALSE; 21191 21192 *rgelt = types[pos++]; 21193 21194 if(pceltFetched !is null) 21195 *pceltFetched = 1; 21196 21197 // writeln("ok celt ", celt); 21198 return S_OK; 21199 } 21200 21201 HRESULT Reset() { 21202 pos = 0; 21203 return S_OK; 21204 } 21205 21206 HRESULT Skip(ULONG celt) { 21207 if(celt + pos <= types.length) { 21208 pos += celt; 21209 return S_OK; 21210 } 21211 return S_FALSE; 21212 } 21213 }; 21214 21215 return S_OK; 21216 } else 21217 return E_NOTIMPL; 21218 } 21219 // given a format, return the format you'd prefer to use cuz it is identical 21220 HRESULT GetCanonicalFormatEtc(FORMATETC* pformatectIn, FORMATETC* pformatetcOut) { 21221 // FIXME: prolly could be better but meh 21222 // writeln("gcf: ", *pformatectIn); 21223 *pformatetcOut = *pformatectIn; 21224 return S_OK; 21225 } 21226 HRESULT GetData(FORMATETC* pformatetcIn, STGMEDIUM* pmedium) { 21227 foreach(ty; types) { 21228 if(ty == *pformatetcIn) { 21229 auto format = ty.cfFormat; 21230 // writeln("A: ", *pformatetcIn, "\nB: ", ty); 21231 STGMEDIUM medium; 21232 medium.tymed = TYMED.TYMED_HGLOBAL; 21233 21234 auto sz = handler.dataLength(format); 21235 auto handle = GlobalAlloc(GMEM_MOVEABLE, sz); 21236 if(handle is null) throw new WindowsApiException("GlobalAlloc", GetLastError()); 21237 if(auto data = cast(wchar*) GlobalLock(handle)) { 21238 auto slice = data[0 .. sz]; 21239 scope(exit) 21240 GlobalUnlock(handle); 21241 21242 handler.getData(format, cast(ubyte[]) slice[]); 21243 } 21244 21245 21246 medium.hGlobal = handle; // FIXME 21247 *pmedium = medium; 21248 return S_OK; 21249 } 21250 } 21251 return DV_E_FORMATETC; 21252 } 21253 HRESULT GetDataHere(FORMATETC* pformatetcIn, STGMEDIUM* pmedium) { 21254 // writeln("GDH: ", *pformatetcIn); 21255 return E_NOTIMPL; // FIXME 21256 } 21257 HRESULT QueryGetData(FORMATETC* pformatetc) { 21258 auto search = *pformatetc; 21259 search.tymed &= TYMED.TYMED_HGLOBAL; 21260 foreach(ty; types) 21261 if(ty == search) { 21262 // writeln("QueryGetData ", search, " ", types[0]); 21263 return S_OK; 21264 } 21265 if(pformatetc.cfFormat==CF_UNICODETEXT) { 21266 //writeln("QueryGetData FALSE ", search, " ", types[0]); 21267 } 21268 return S_FALSE; 21269 } 21270 HRESULT SetData(FORMATETC* pformatetc, STGMEDIUM* pmedium, BOOL fRelease) { 21271 // writeln("SetData: "); 21272 return E_NOTIMPL; 21273 } 21274 }; 21275 21276 21277 IDropSource src = new class IDropSource { 21278 ULONG refCount; 21279 ULONG AddRef() { 21280 return ++refCount; 21281 } 21282 ULONG Release() { 21283 return --refCount; 21284 } 21285 HRESULT QueryInterface(const (IID)*riid, LPVOID *ppv) { 21286 if (IID_IUnknown == *riid) { 21287 *ppv = cast(void*) cast(IUnknown) this; 21288 } 21289 else if (IID_IDropSource == *riid) { 21290 *ppv = cast(void*) cast(IDropSource) this; 21291 } 21292 else { 21293 *ppv = null; 21294 return E_NOINTERFACE; 21295 } 21296 21297 AddRef(); 21298 return NOERROR; 21299 } 21300 21301 int QueryContinueDrag(int fEscapePressed, uint grfKeyState) { 21302 if(fEscapePressed) 21303 return DRAGDROP_S_CANCEL; 21304 if(!(grfKeyState & MK_LBUTTON)) 21305 return DRAGDROP_S_DROP; 21306 return S_OK; 21307 } 21308 21309 int GiveFeedback(uint dwEffect) { 21310 return DRAGDROP_S_USEDEFAULTCURSORS; 21311 } 21312 }; 21313 21314 DWORD effect; 21315 21316 if(action == DragAndDropAction.none) assert(0, "Don't drag something with a none effect."); 21317 21318 DROPEFFECT de = win32DragAndDropAction(action); 21319 21320 // I'm not as concerned about the GC here since DoDragDrop blocks so the stack frame still sane the whole time 21321 // but still prolly a FIXME 21322 21323 auto ret = DoDragDrop(obj, src, de, &effect); 21324 /+ 21325 if(ret == DRAGDROP_S_DROP) 21326 writeln("drop ", effect); 21327 else if(ret == DRAGDROP_S_CANCEL) 21328 writeln("cancel"); 21329 else if(ret == S_OK) 21330 writeln("ok"); 21331 else writeln(ret); 21332 +/ 21333 21334 return ret; 21335 } 21336 21337 version(Windows) 21338 DROPEFFECT win32DragAndDropAction(DragAndDropAction action) { 21339 DROPEFFECT de; 21340 21341 with(DragAndDropAction) 21342 with(DROPEFFECT) 21343 final switch(action) { 21344 case none: de = DROPEFFECT_NONE; break; 21345 case copy: de = DROPEFFECT_COPY; break; 21346 case move: de = DROPEFFECT_MOVE; break; 21347 case link: de = DROPEFFECT_LINK; break; 21348 case ask: throw new Exception("ask not implemented yet"); 21349 case custom: throw new Exception("custom not implemented yet"); 21350 } 21351 21352 return de; 21353 } 21354 21355 21356 /++ 21357 History: 21358 Added February 19, 2021 21359 +/ 21360 /// Group: drag_and_drop 21361 void enableDragAndDrop(SimpleWindow window, DropHandler handler) { 21362 version(X11) { 21363 auto display = XDisplayConnection.get; 21364 21365 Atom atom = 5; // right??? 21366 21367 XChangeProperty( 21368 display, 21369 window.impl.window, 21370 GetAtom!"XdndAware"(display), 21371 XA_ATOM, 21372 32 /* bits */, 21373 PropModeReplace, 21374 &atom, 21375 1); 21376 21377 window.dropHandler = handler; 21378 } else version(Windows) { 21379 21380 initDnd(); 21381 21382 auto dropTarget = new class (handler) IDropTarget { 21383 DropHandler handler; 21384 this(DropHandler handler) { 21385 this.handler = handler; 21386 } 21387 ULONG refCount; 21388 ULONG AddRef() { 21389 return ++refCount; 21390 } 21391 ULONG Release() { 21392 return --refCount; 21393 } 21394 HRESULT QueryInterface(const (IID)*riid, LPVOID *ppv) { 21395 if (IID_IUnknown == *riid) { 21396 *ppv = cast(void*) cast(IUnknown) this; 21397 } 21398 else if (IID_IDropTarget == *riid) { 21399 *ppv = cast(void*) cast(IDropTarget) this; 21400 } 21401 else { 21402 *ppv = null; 21403 return E_NOINTERFACE; 21404 } 21405 21406 AddRef(); 21407 return NOERROR; 21408 } 21409 21410 21411 // /////////////////// 21412 21413 HRESULT DragEnter(IDataObject pDataObj, DWORD grfKeyState, POINTL pt, DWORD* pdwEffect) { 21414 DropPackage dropPackage = DropPackage(pDataObj); 21415 *pdwEffect = win32DragAndDropAction(handler.dragEnter(&dropPackage)); 21416 return S_OK; // https://docs.microsoft.com/en-us/windows/win32/api/oleidl/nf-oleidl-idroptarget-dragenter 21417 } 21418 21419 HRESULT DragLeave() { 21420 handler.dragLeave(); 21421 // release the IDataObject if needed 21422 return S_OK; 21423 } 21424 21425 HRESULT DragOver(DWORD grfKeyState, POINTL pt, DWORD* pdwEffect) { 21426 auto res = handler.dragOver(Point(pt.x, pt.y)); // FIXME: translate screen coordinates back to window coordinates 21427 21428 *pdwEffect = win32DragAndDropAction(res.action); 21429 // same as DragEnter basically 21430 return S_OK; 21431 } 21432 21433 HRESULT Drop(IDataObject pDataObj, DWORD grfKeyState, POINTL pt, DWORD* pdwEffect) { 21434 DropPackage pkg = DropPackage(pDataObj); 21435 handler.drop(&pkg); 21436 21437 return S_OK; 21438 } 21439 }; 21440 // Windows can hold on to the handler and try to call it 21441 // during which time the GC can't see it. so important to 21442 // manually manage this. At some point i'll FIXME and make 21443 // all my com instances manually managed since they supposed 21444 // to respect the refcount. 21445 import core.memory; 21446 GC.addRoot(cast(void*) dropTarget); 21447 21448 if(RegisterDragDrop(window.impl.hwnd, dropTarget) != S_OK) 21449 throw new WindowsApiException("RegisterDragDrop", GetLastError()); 21450 21451 window.dropHandler = handler; 21452 } else throw new NotYetImplementedException(); 21453 } 21454 21455 21456 21457 static if(UsingSimpledisplayX11) { 21458 21459 enum _NET_WM_STATE_ADD = 1; 21460 enum _NET_WM_STATE_REMOVE = 0; 21461 enum _NET_WM_STATE_TOGGLE = 2; 21462 21463 /// X-specific. Use [SimpleWindow.requestAttention] instead for most cases. 21464 void demandAttention(SimpleWindow window, bool needs = true) { 21465 demandAttention(window.impl.window, needs); 21466 } 21467 21468 /// ditto 21469 void demandAttention(Window window, bool needs = true) { 21470 setNetWmStateAtom(window, GetAtom!("_NET_WM_STATE_DEMANDS_ATTENTION", false)(XDisplayConnection.get), needs); 21471 } 21472 21473 void setNetWmStateAtom(Window window, Atom atom, bool set = true, Atom atom2 = None) { 21474 auto display = XDisplayConnection.get(); 21475 if(atom == None) 21476 return; // non-failure error 21477 //auto atom2 = GetAtom!"_NET_WM_STATE_SHADED"(display); 21478 21479 XClientMessageEvent xclient; 21480 21481 xclient.type = EventType.ClientMessage; 21482 xclient.window = window; 21483 xclient.message_type = GetAtom!"_NET_WM_STATE"(display); 21484 xclient.format = 32; 21485 xclient.data.l[0] = set ? _NET_WM_STATE_ADD : _NET_WM_STATE_REMOVE; 21486 xclient.data.l[1] = atom; 21487 xclient.data.l[2] = atom2; 21488 xclient.data.l[3] = 1; 21489 // [3] == source. 0 == unknown, 1 == app, 2 == else 21490 21491 XSendEvent( 21492 display, 21493 RootWindow(display, DefaultScreen(display)), 21494 false, 21495 EventMask.SubstructureRedirectMask | EventMask.SubstructureNotifyMask, 21496 cast(XEvent*) &xclient 21497 ); 21498 21499 /+ 21500 XChangeProperty( 21501 display, 21502 window.impl.window, 21503 GetAtom!"_NET_WM_STATE"(display), 21504 XA_ATOM, 21505 32 /* bits */, 21506 PropModeAppend, 21507 &atom, 21508 1); 21509 +/ 21510 } 21511 21512 private Atom dndActionAtom(Display* display, DragAndDropAction action) { 21513 Atom actionAtom; 21514 with(DragAndDropAction) 21515 final switch(action) { 21516 case none: actionAtom = None; break; 21517 case copy: actionAtom = GetAtom!"XdndActionCopy"(display); break; 21518 case move: actionAtom = GetAtom!"XdndActionMove"(display); break; 21519 case link: actionAtom = GetAtom!"XdndActionLink"(display); break; 21520 case ask: actionAtom = GetAtom!"XdndActionAsk"(display); break; 21521 case custom: actionAtom = GetAtom!"XdndActionCustom"(display); break; 21522 } 21523 21524 return actionAtom; 21525 } 21526 21527 private int doDragDropX11(SimpleWindow window, X11SetSelectionHandler handler, DragAndDropAction action) { 21528 // FIXME: I need to show user feedback somehow. 21529 auto display = XDisplayConnection.get; 21530 21531 auto actionAtom = dndActionAtom(display, action); 21532 assert(actionAtom, "Don't use action none to accept a drop"); 21533 21534 setX11Selection!"XdndSelection"(window, handler, null); 21535 21536 auto oldKeyHandler = window.handleKeyEvent; 21537 scope(exit) window.handleKeyEvent = oldKeyHandler; 21538 21539 auto oldCharHandler = window.handleCharEvent; 21540 scope(exit) window.handleCharEvent = oldCharHandler; 21541 21542 auto oldMouseHandler = window.handleMouseEvent; 21543 scope(exit) window.handleMouseEvent = oldMouseHandler; 21544 21545 Window[Window] eligibility; // 0 == not eligible, otherwise it is the window id of an eligible child 21546 21547 import core.sys.posix.sys.time; 21548 timeval tv; 21549 gettimeofday(&tv, null); 21550 21551 Time dataTimestamp = tv.tv_sec * 1000 + tv.tv_usec / 1000; 21552 21553 Time lastMouseTimestamp; 21554 21555 bool dnding = true; 21556 Window lastIn = None; 21557 21558 void leave() { 21559 if(lastIn == None) 21560 return; 21561 21562 XEvent ev; 21563 ev.xclient.type = EventType.ClientMessage; 21564 ev.xclient.window = lastIn; 21565 ev.xclient.message_type = GetAtom!("XdndLeave", true)(display); 21566 ev.xclient.format = 32; 21567 ev.xclient.data.l[0] = window.impl.window; 21568 21569 XSendEvent(XDisplayConnection.get, lastIn, false, EventMask.NoEventMask, &ev); 21570 XFlush(display); 21571 21572 lastIn = None; 21573 } 21574 21575 void enter(Window w) { 21576 assert(lastIn == None); 21577 21578 lastIn = w; 21579 21580 XEvent ev; 21581 ev.xclient.type = EventType.ClientMessage; 21582 ev.xclient.window = lastIn; 21583 ev.xclient.message_type = GetAtom!("XdndEnter", true)(display); 21584 ev.xclient.format = 32; 21585 ev.xclient.data.l[0] = window.impl.window; 21586 ev.xclient.data.l[1] = (5 << 24) | 0; // version 5, no more sources. FIXME source types 21587 21588 auto types = handler.availableFormats(); 21589 assert(types.length > 0); 21590 21591 ev.xclient.data.l[2] = types[0]; 21592 if(types.length > 1) 21593 ev.xclient.data.l[3] = types[1]; 21594 if(types.length > 2) 21595 ev.xclient.data.l[4] = types[2]; 21596 21597 // FIXME: other types?!?!? and make sure we skip TARGETS 21598 21599 XSendEvent(XDisplayConnection.get, lastIn, false, EventMask.NoEventMask, &ev); 21600 XFlush(display); 21601 } 21602 21603 void position(int rootX, int rootY) { 21604 assert(lastIn != None); 21605 21606 XEvent ev; 21607 ev.xclient.type = EventType.ClientMessage; 21608 ev.xclient.window = lastIn; 21609 ev.xclient.message_type = GetAtom!("XdndPosition", true)(display); 21610 ev.xclient.format = 32; 21611 ev.xclient.data.l[0] = window.impl.window; 21612 ev.xclient.data.l[1] = 0; // reserved 21613 ev.xclient.data.l[2] = (rootX << 16) | rootY; 21614 ev.xclient.data.l[3] = dataTimestamp; 21615 ev.xclient.data.l[4] = actionAtom; 21616 21617 XSendEvent(XDisplayConnection.get, lastIn, false, EventMask.NoEventMask, &ev); 21618 XFlush(display); 21619 21620 } 21621 21622 void drop() { 21623 XEvent ev; 21624 ev.xclient.type = EventType.ClientMessage; 21625 ev.xclient.window = lastIn; 21626 ev.xclient.message_type = GetAtom!("XdndDrop", true)(display); 21627 ev.xclient.format = 32; 21628 ev.xclient.data.l[0] = window.impl.window; 21629 ev.xclient.data.l[1] = 0; // reserved 21630 ev.xclient.data.l[2] = dataTimestamp; 21631 21632 XSendEvent(XDisplayConnection.get, lastIn, false, EventMask.NoEventMask, &ev); 21633 XFlush(display); 21634 21635 lastIn = None; 21636 dnding = false; 21637 } 21638 21639 // fyi nativeEventHandler can return 0 if it handles it, or otherwise it goes back to the normal handler 21640 // but idk if i should... 21641 21642 window.setEventHandlers( 21643 delegate(KeyEvent ev) { 21644 if(ev.pressed == true && ev.key == Key.Escape) { 21645 // cancel 21646 dnding = false; 21647 } 21648 }, 21649 delegate(MouseEvent ev) { 21650 if(ev.timestamp < lastMouseTimestamp) 21651 return; 21652 21653 lastMouseTimestamp = ev.timestamp; 21654 21655 if(ev.type == MouseEventType.motion) { 21656 auto display = XDisplayConnection.get; 21657 auto root = RootWindow(display, DefaultScreen(display)); 21658 21659 Window topWindow; 21660 int rootX, rootY; 21661 21662 XTranslateCoordinates(display, window.impl.window, root, ev.x, ev.y, &rootX, &rootY, &topWindow); 21663 21664 if(topWindow == None) 21665 return; 21666 21667 top: 21668 if(auto result = topWindow in eligibility) { 21669 auto dropWindow = *result; 21670 if(dropWindow == None) { 21671 leave(); 21672 return; 21673 } 21674 21675 if(dropWindow != lastIn) { 21676 leave(); 21677 enter(dropWindow); 21678 position(rootX, rootY); 21679 } else { 21680 position(rootX, rootY); 21681 } 21682 } else { 21683 // determine eligibility 21684 auto data = cast(Atom[]) getX11PropertyData(topWindow, GetAtom!"XdndAware"(display), XA_ATOM); 21685 if(data.length == 1) { 21686 // in case there is no WM or it isn't reparenting 21687 eligibility[topWindow] = (data[0] == 5) ? topWindow : None; // FIXME I'm supposed to handle older versions too but meh 21688 } else { 21689 21690 Window tryScanChildren(Window search, int maxRecurse) { 21691 // could be reparenting window manager, so gotta check the next few children too 21692 Window child; 21693 int x; 21694 int y; 21695 XTranslateCoordinates(display, window.impl.window, search, ev.x, ev.y, &x, &y, &child); 21696 21697 if(child == None) 21698 return None; 21699 auto data = cast(Atom[]) getX11PropertyData(child, GetAtom!"XdndAware"(display), XA_ATOM); 21700 if(data.length == 1) { 21701 return (data[0] == 5) ? child : None; // FIXME I'm supposed to handle older versions too but meh 21702 } else { 21703 if(maxRecurse) 21704 return tryScanChildren(child, maxRecurse - 1); 21705 else 21706 return None; 21707 } 21708 21709 } 21710 21711 // if a WM puts more than 3 layers on it, like wtf is it doing, screw that. 21712 auto topResult = tryScanChildren(topWindow, 3); 21713 // it is easy to have a false negative due to the mouse going over a WM 21714 // child window like the close button if separate from the frame... so I 21715 // can't really cache negatives, :( 21716 if(topResult != None) { 21717 eligibility[topWindow] = topResult; 21718 goto top; // reload to do the positioning iff eligibility changed lest we endless loop 21719 } 21720 } 21721 21722 } 21723 21724 } else if(ev.type == MouseEventType.buttonReleased) { 21725 drop(); 21726 dnding = false; 21727 } 21728 } 21729 ); 21730 21731 window.grabInput(); 21732 scope(exit) 21733 window.releaseInputGrab(); 21734 21735 21736 EventLoop.get.run(() => dnding); 21737 21738 return 0; 21739 } 21740 21741 /// X-specific 21742 TrueColorImage getWindowNetWmIcon(Window window) { 21743 try { 21744 auto display = XDisplayConnection.get; 21745 21746 auto data = getX11PropertyData (window, GetAtom!"_NET_WM_ICON"(display), XA_CARDINAL); 21747 21748 if (data.length > arch_ulong.sizeof * 2) { 21749 auto meta = cast(arch_ulong[]) (data[0 .. arch_ulong.sizeof * 2]); 21750 // these are an array of rgba images that we have to convert into pixmaps ourself 21751 21752 int width = cast(int) meta[0]; 21753 int height = cast(int) meta[1]; 21754 21755 auto bytes = cast(ubyte[]) (data[arch_ulong.sizeof * 2 .. $]); 21756 21757 static if(arch_ulong.sizeof == 4) { 21758 bytes = bytes[0 .. width * height * 4]; 21759 alias imageData = bytes; 21760 } else static if(arch_ulong.sizeof == 8) { 21761 bytes = bytes[0 .. width * height * 8]; 21762 auto imageData = new ubyte[](4 * width * height); 21763 } else static assert(0); 21764 21765 21766 21767 // this returns ARGB. Remember it is little-endian so 21768 // we have BGRA 21769 // our thing uses RGBA, which in little endian, is ABGR 21770 for(int idx = 0, idx2 = 0; idx < bytes.length; idx += arch_ulong.sizeof, idx2 += 4) { 21771 auto r = bytes[idx + 2]; 21772 auto g = bytes[idx + 1]; 21773 auto b = bytes[idx + 0]; 21774 auto a = bytes[idx + 3]; 21775 21776 imageData[idx2 + 0] = r; 21777 imageData[idx2 + 1] = g; 21778 imageData[idx2 + 2] = b; 21779 imageData[idx2 + 3] = a; 21780 } 21781 21782 return new TrueColorImage(width, height, imageData); 21783 } 21784 21785 return null; 21786 } catch(Exception e) { 21787 return null; 21788 } 21789 } 21790 21791 } /* UsingSimpledisplayX11 */ 21792 21793 21794 void loadBinNameToWindowClassName () { 21795 import core.stdc.stdlib : realloc; 21796 version(linux) { 21797 // args[0] MAY be empty, so we'll just use this 21798 import core.sys.posix.unistd : readlink; 21799 char[1024] ebuf = void; // 1KB should be enough for everyone! 21800 auto len = readlink("/proc/self/exe", ebuf.ptr, ebuf.length); 21801 if (len < 1) return; 21802 } else /*version(Windows)*/ { 21803 import core.runtime : Runtime; 21804 if (Runtime.args.length == 0 || Runtime.args[0].length == 0) return; 21805 auto ebuf = Runtime.args[0]; 21806 auto len = ebuf.length; 21807 } 21808 auto pos = len; 21809 while (pos > 0 && ebuf[pos-1] != '/') --pos; 21810 sdpyWindowClassStr = cast(char*)realloc(sdpyWindowClassStr, len-pos+1); 21811 if (sdpyWindowClassStr is null) return; // oops 21812 sdpyWindowClassStr[0..len-pos+1] = 0; // just in case 21813 sdpyWindowClassStr[0..len-pos] = ebuf[pos..len]; 21814 } 21815 21816 /++ 21817 An interface representing a font that is drawn with custom facilities. 21818 21819 You might want [OperatingSystemFont] instead, which represents 21820 a font loaded and drawn by functions native to the operating system. 21821 21822 WARNING: I might still change this. 21823 +/ 21824 interface DrawableFont : MeasurableFont { 21825 /++ 21826 Please note the point is upperLeft, NOT baseline! This is the point of a bounding box of the string. 21827 21828 Implementations must use the painter's fillColor to draw a rectangle behind the string, 21829 then use the outlineColor to draw the string. It might alpha composite if there's a transparent 21830 fill color, but that's up to the implementation. 21831 +/ 21832 void drawString(ScreenPainter painter, Point upperLeft, in char[] text); 21833 21834 /++ 21835 Requests that the given string is added to the image cache. You should only do this rarely, but 21836 if you have a string that you know will be used over and over again, adding it to a cache can 21837 improve things (assuming the implementation actually has a cache; it is also valid for an implementation 21838 to implement this as a do-nothing method). 21839 +/ 21840 void cacheString(SimpleWindow window, Color foreground, Color background, string text); 21841 } 21842 21843 /++ 21844 Loads a true type font using [arsd.ttf] that can be drawn as images on windows 21845 through a [ScreenPainter]. That module must be compiled in if you choose to use this function. 21846 21847 You should also consider [OperatingSystemFont], which loads and draws a font with 21848 facilities native to the user's operating system. You might also consider 21849 [arsd.ttf.OpenGlLimitedFont] or using [arsd.nanovega] if you are making some kind 21850 of game, as they have their own ways to draw text too. 21851 21852 Be warned: this can be slow, especially on remote connections to the X server, since 21853 it needs to create and transfer bitmaps instead of just text. The [DrawableFont] interface 21854 offers [DrawableFont.cacheString] which can help with this, sometimes. You might want to 21855 experiment in your specific case. 21856 21857 Please note that the return type of [DrawableFont] also includes an implementation of 21858 [MeasurableFont]. 21859 +/ 21860 DrawableFont arsdTtfFont()(in ubyte[] data, int size) { 21861 import arsd.ttf; 21862 static class ArsdTtfFont : DrawableFont { 21863 TtfFont font; 21864 int size; 21865 this(in ubyte[] data, int size) { 21866 font = TtfFont(data); 21867 this.size = size; 21868 21869 21870 auto scale = stbtt_ScaleForPixelHeight(&font.font, size); 21871 int ascent_, descent_, line_gap; 21872 stbtt_GetFontVMetrics(&font.font, &ascent_, &descent_, &line_gap); 21873 21874 int advance, lsb; 21875 stbtt_GetCodepointHMetrics(&font.font, 'x', &advance, &lsb); 21876 xWidth = cast(int) (advance * scale); 21877 stbtt_GetCodepointHMetrics(&font.font, 'M', &advance, &lsb); 21878 MWidth = cast(int) (advance * scale); 21879 } 21880 21881 private int ascent_; 21882 private int descent_; 21883 private int xWidth; 21884 private int MWidth; 21885 21886 bool isMonospace() { 21887 return xWidth == MWidth; 21888 } 21889 int averageWidth() { 21890 return xWidth; 21891 } 21892 int height() { 21893 return size; 21894 } 21895 int ascent() { 21896 return ascent_; 21897 } 21898 int descent() { 21899 return descent_; 21900 } 21901 21902 int stringWidth(scope const(char)[] s, SimpleWindow window = null) { 21903 int width, height; 21904 font.getStringSize(s, size, width, height); 21905 return width; 21906 } 21907 21908 21909 21910 Sprite[string] cache; 21911 21912 void cacheString(SimpleWindow window, Color foreground, Color background, string text) { 21913 auto sprite = new Sprite(window, stringToImage(foreground, background, text)); 21914 cache[text] = sprite; 21915 } 21916 21917 Image stringToImage(Color fg, Color bg, in char[] text) { 21918 int width, height; 21919 auto data = font.renderString(text, size, width, height); 21920 auto image = new TrueColorImage(width, height); 21921 int pos = 0; 21922 foreach(y; 0 .. height) 21923 foreach(x; 0 .. width) { 21924 fg.a = data[0]; 21925 bg.a = 255; 21926 auto color = alphaBlend(fg, bg); 21927 image.imageData.bytes[pos++] = color.r; 21928 image.imageData.bytes[pos++] = color.g; 21929 image.imageData.bytes[pos++] = color.b; 21930 image.imageData.bytes[pos++] = data[0]; 21931 data = data[1 .. $]; 21932 } 21933 assert(data.length == 0); 21934 21935 return Image.fromMemoryImage(image); 21936 } 21937 21938 void drawString(ScreenPainter painter, Point upperLeft, in char[] text) { 21939 Sprite sprite = (text in cache) ? *(text in cache) : null; 21940 21941 auto fg = painter.impl._outlineColor; 21942 auto bg = painter.impl._fillColor; 21943 21944 if(sprite !is null) { 21945 auto w = cast(SimpleWindow) painter.window; 21946 assert(w !is null); 21947 21948 sprite.drawAt(painter, upperLeft); 21949 } else { 21950 painter.drawImage(upperLeft, stringToImage(fg, bg, text)); 21951 } 21952 } 21953 } 21954 21955 return new ArsdTtfFont(data, size); 21956 } 21957 21958 class NotYetImplementedException : Exception { 21959 this(string file = __FILE__, size_t line = __LINE__) { 21960 super("Not yet implemented", file, line); 21961 } 21962 } 21963 21964 /// 21965 __gshared bool librariesSuccessfullyLoaded = true; 21966 /// 21967 __gshared bool openGlLibrariesSuccessfullyLoaded = true; 21968 21969 private mixin template DynamicLoadSupplementalOpenGL(Iface) { 21970 mixin(staticForeachReplacement!Iface); 21971 21972 void loadDynamicLibrary() @nogc { 21973 (cast(void function() @nogc) &loadDynamicLibraryForReal)(); 21974 } 21975 21976 void loadDynamicLibraryForReal() { 21977 foreach(name; __traits(derivedMembers, Iface)) { 21978 mixin("alias tmp = " ~ name ~ ";"); 21979 tmp = cast(typeof(tmp)) glbindGetProcAddress(name); 21980 if(tmp is null) throw new Exception("load failure of function " ~ name ~ " from supplemental OpenGL"); 21981 } 21982 } 21983 } 21984 21985 private const(char)[] staticForeachReplacement(Iface)() pure { 21986 /* 21987 // just this for gdc 9.... 21988 // when i drop support for it and switch to gdc10, we can put this original back for a slight compile time ram decrease 21989 21990 static foreach(name; __traits(derivedMembers, Iface)) 21991 mixin("__gshared typeof(&__traits(getMember, Iface, name)) " ~ name ~ ";"); 21992 */ 21993 21994 char[] code = new char[](__traits(derivedMembers, Iface).length * 64); 21995 size_t pos; 21996 21997 void append(in char[] what) { 21998 if(pos + what.length > code.length) 21999 code.length = (code.length * 3) / 2; 22000 code[pos .. pos + what.length] = what[]; 22001 pos += what.length; 22002 } 22003 22004 foreach(name; __traits(derivedMembers, Iface)) { 22005 append(`__gshared typeof(&__traits(getMember, Iface, "`); 22006 append(name); 22007 append(`")) `); 22008 append(name); 22009 append(";"); 22010 } 22011 22012 return code[0 .. pos]; 22013 } 22014 22015 private mixin template DynamicLoad(Iface, string library, int majorVersion, alias success) { 22016 mixin(staticForeachReplacement!Iface); 22017 22018 private __gshared void* libHandle; 22019 private __gshared bool attempted; 22020 22021 void loadDynamicLibrary() @nogc { 22022 (cast(void function() @nogc) &loadDynamicLibraryForReal)(); 22023 } 22024 22025 bool loadAttempted() { 22026 return attempted; 22027 } 22028 bool loadSuccessful() { 22029 return libHandle !is null; 22030 } 22031 22032 void loadDynamicLibraryForReal() { 22033 attempted = true; 22034 version(Posix) { 22035 import core.sys.posix.dlfcn; 22036 version(OSX) { 22037 version(X11) 22038 libHandle = dlopen("/usr/X11/lib/lib" ~ library ~ ".dylib", RTLD_NOW); 22039 else 22040 libHandle = dlopen(library ~ ".dylib", RTLD_NOW); 22041 } else { 22042 libHandle = dlopen("lib" ~ library ~ ".so", RTLD_NOW); 22043 if(libHandle is null) 22044 libHandle = dlopen(("lib" ~ library ~ ".so." ~ toInternal!string(majorVersion) ~ "\0").ptr, RTLD_NOW); 22045 } 22046 22047 static void* loadsym(void* l, const char* name) { 22048 import core.stdc.stdlib; 22049 if(l is null) 22050 return &abort; 22051 return dlsym(l, name); 22052 } 22053 } else version(Windows) { 22054 import core.sys.windows.winbase; 22055 libHandle = LoadLibrary(library ~ ".dll"); 22056 static void* loadsym(void* l, const char* name) { 22057 import core.stdc.stdlib; 22058 if(l is null) 22059 return &abort; 22060 return GetProcAddress(l, name); 22061 } 22062 } 22063 if(libHandle is null) { 22064 success = false; 22065 //throw new Exception("load failure of library " ~ library); 22066 } 22067 foreach(name; __traits(derivedMembers, Iface)) { 22068 mixin("alias tmp = " ~ name ~ ";"); 22069 tmp = cast(typeof(tmp)) loadsym(libHandle, name); 22070 if(tmp is null) throw new Exception("load failure of function " ~ name ~ " from " ~ library); 22071 } 22072 } 22073 22074 void unloadDynamicLibrary() { 22075 version(Posix) { 22076 import core.sys.posix.dlfcn; 22077 dlclose(libHandle); 22078 } else version(Windows) { 22079 import core.sys.windows.winbase; 22080 FreeLibrary(libHandle); 22081 } 22082 foreach(name; __traits(derivedMembers, Iface)) 22083 mixin(name ~ " = null;"); 22084 } 22085 } 22086 22087 /+ 22088 The GC can be called from any thread, and a lot of cleanup must be done 22089 on the gui thread. Since the GC can interrupt any locks - including being 22090 triggered inside a critical section - it is vital to avoid deadlocks to get 22091 these functions called from the right place. 22092 22093 If the buffer overflows, things are going to get leaked. I'm kinda ok with that 22094 right now. 22095 22096 The cleanup function is run when the event loop gets around to it, which is just 22097 whenever there's something there after it has been woken up for other work. It does 22098 NOT wake up the loop itself - can't risk doing that from inside the GC in another thread. 22099 (Well actually it might be ok but i don't wanna mess with it right now.) 22100 +/ 22101 private struct CleanupQueue { 22102 import core.stdc.stdlib; 22103 22104 void queue(alias func, T...)(T args) { 22105 static struct Args { 22106 T args; 22107 } 22108 static struct RealJob { 22109 Job j; 22110 Args a; 22111 } 22112 static void call(Job* data) { 22113 auto rj = cast(RealJob*) data; 22114 func(rj.a.args); 22115 } 22116 22117 RealJob* thing = cast(RealJob*) malloc(RealJob.sizeof); 22118 thing.j.call = &call; 22119 thing.a.args = args; 22120 22121 buffer[tail++] = cast(Job*) thing; 22122 22123 // FIXME: set overflowed 22124 } 22125 22126 void process() { 22127 const tail = this.tail; 22128 22129 while(tail != head) { 22130 Job* job = cast(Job*) buffer[head++]; 22131 job.call(job); 22132 free(job); 22133 } 22134 22135 if(overflowed) 22136 throw new Exception("cleanup overflowed"); 22137 } 22138 22139 private: 22140 22141 ubyte tail; // must ONLY be written by queue 22142 ubyte head; // must ONLY be written by process 22143 bool overflowed; 22144 22145 static struct Job { 22146 void function(Job*) call; 22147 } 22148 22149 void*[256] buffer; 22150 } 22151 private __gshared CleanupQueue cleanupQueue; 22152 22153 version(X11) 22154 /++ 22155 Returns the custom scaling factor read out of environment["ARSD_SCALING_FACTOR"]. 22156 22157 $(WARNING 22158 This function is exempted from stability guarantees. 22159 ) 22160 +/ 22161 float customScalingFactorForMonitor(int monitorNumber) { 22162 import core.stdc.stdlib; 22163 auto val = getenv("ARSD_SCALING_FACTOR"); 22164 22165 // FIXME: maybe we should assume a default nbased on the dpi thing if this isn't given 22166 if(val is null) 22167 return 1.0; 22168 22169 char[16] buffer = 0; 22170 int pos; 22171 22172 const(char)* at = val; 22173 22174 foreach(item; 0 .. monitorNumber + 1) { 22175 if(*at == 0) 22176 break; // reuse the last number when we at the end of the string 22177 pos = 0; 22178 while(pos + 1 < buffer.length && *at && *at != ';') { 22179 buffer[pos++] = *at; 22180 at++; 22181 } 22182 if(*at) 22183 at++; // skip the semicolon 22184 buffer[pos] = 0; 22185 } 22186 22187 //sdpyPrintDebugString(buffer[0 .. pos]); 22188 22189 import core.stdc.math; 22190 auto f = atof(buffer.ptr); 22191 22192 if(f <= 0.0 || isnan(f) || isinf(f)) 22193 return 1.0; 22194 22195 return f; 22196 } 22197 22198 void guiAbortProcess(string msg) { 22199 import core.stdc.stdlib; 22200 version(Windows) { 22201 WCharzBuffer t = WCharzBuffer(msg); 22202 MessageBoxW(null, t.ptr, "Program Termination"w.ptr, 0); 22203 } else { 22204 import core.stdc.stdio; 22205 fwrite(msg.ptr, 1, msg.length, stderr); 22206 msg = "\n"; 22207 fwrite(msg.ptr, 1, msg.length, stderr); 22208 fflush(stderr); 22209 } 22210 22211 abort(); 22212 } 22213 22214 private int minInternal(int a, int b) { 22215 return (a < b) ? a : b; 22216 } 22217 22218 private alias scriptable = arsd_jsvar_compatible;